Implement Representation::GetDurationSeconds

Also added Period::GetAdaptationSets and
AdaptationSet::GetRepresentations.

Instead of implementing GetDurationSeconds in all MpdBuilder classes
(MpdBuilder/Period/Adaptation/Representation) like what we used to do
with GetEarliestTimestamp, the two new functions allows MpdBuilder to
iterate through the Representations to get durations.

Also updates GetEarliestTimestamp functions to use the same iteration
method.

Change-Id: I682b70c07c248c0f6511ec3d9019086f986ee10e
This commit is contained in:
KongQun Yang 2018-01-04 18:31:27 -08:00
parent 509963d60f
commit ef2c424876
10 changed files with 181 additions and 119 deletions

View File

@ -387,23 +387,13 @@ void AdaptationSet::AddTrickPlayReferenceId(uint32_t id) {
trick_play_reference_ids_.insert(id); trick_play_reference_ids_.insert(id);
} }
bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { const std::list<Representation*> AdaptationSet::GetRepresentations() const {
DCHECK(timestamp_seconds); std::list<Representation*> representations;
double earliest_timestamp(-1);
for (const std::unique_ptr<Representation>& representation : for (const std::unique_ptr<Representation>& representation :
representations_) { representations_) {
double timestamp; representations.push_back(representation.get());
if (representation->GetEarliestTimestamp(&timestamp) &&
((earliest_timestamp < 0) || (timestamp < earliest_timestamp))) {
earliest_timestamp = timestamp;
}
} }
if (earliest_timestamp < 0) return representations;
return false;
*timestamp_seconds = earliest_timestamp;
return true;
} }
// This implementation assumes that each representations' segments' are // This implementation assumes that each representations' segments' are

View File

@ -147,6 +147,9 @@ class AdaptationSet {
/// @param id the id of the reference (or main) adapation set. /// @param id the id of the reference (or main) adapation set.
virtual void AddTrickPlayReferenceId(uint32_t id); virtual void AddTrickPlayReferenceId(uint32_t id);
// Return the list of Representations in this AdaptationSet.
const std::list<Representation*> GetRepresentations() const;
protected: protected:
/// @param adaptation_set_id is an ID number for this AdaptationSet. /// @param adaptation_set_id is an ID number for this AdaptationSet.
/// @param lang is the language of this AdaptationSet. Mainly relevant for /// @param lang is the language of this AdaptationSet. Mainly relevant for
@ -188,10 +191,6 @@ class AdaptationSet {
// 2 -> [0, 200, 400] // 2 -> [0, 200, 400]
typedef std::map<uint32_t, std::list<uint64_t>> RepresentationTimeline; typedef std::map<uint32_t, std::list<uint64_t>> RepresentationTimeline;
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
/// Called from OnNewSegmentForRepresentation(). Checks whether the segments /// Called from OnNewSegmentForRepresentation(). Checks whether the segments
/// are aligned. Sets segments_aligned_. /// are aligned. Sets segments_aligned_.
/// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used. /// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used.

View File

@ -16,6 +16,7 @@
#include "packager/mpd/test/xml_compare.h" #include "packager/mpd/test/xml_compare.h"
using ::testing::Not; using ::testing::Not;
using ::testing::UnorderedElementsAre;
namespace shaka { namespace shaka {
@ -563,6 +564,43 @@ TEST_F(AdaptationSetTest, BubbleUpAttributesToAdaptationSet) {
EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate"))); EXPECT_THAT(no_common_attributes.get(), Not(AttributeSet("frameRate")));
} }
TEST_F(AdaptationSetTest, GetRepresentations) {
const char k480pMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 8\n"
" pixel_height: 9\n"
"}\n"
"container_type: 1\n";
const char k360pMediaInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 640\n"
" height: 360\n"
" time_scale: 10\n"
" frame_duration: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage);
Representation* representation_480p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo));
EXPECT_THAT(adaptation_set->GetRepresentations(),
UnorderedElementsAre(representation_480p));
Representation* representation_360p =
adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo));
EXPECT_THAT(adaptation_set->GetRepresentations(),
UnorderedElementsAre(representation_360p, representation_480p));
}
// Verify that subsegmentAlignment is set to true if all the Representations' // Verify that subsegmentAlignment is set to true if all the Representations'
// segments are aligned and the DASH profile is OnDemand. // segments are aligned and the DASH profile is OnDemand.
// Also checking that not all Representations have to be added before calling // Also checking that not all Representations have to be added before calling
@ -961,9 +999,7 @@ TEST_F(OnDemandAdaptationSetTest,
const char kExpectedOutput[] = const char kExpectedOutput[] =
"<AdaptationSet id=\"1\" contentType=\"audio\">" "<AdaptationSet id=\"1\" contentType=\"audio\">"
" <Representation id=\"0\" bandwidth=\"195857\" codecs=\"mp4a.40.2\"" " <Representation id=\"0\" bandwidth=\"195857\" codecs=\"mp4a.40.2\""
" mimeType=\"audio/mp4\" audioSamplingRate=\"44100\" " " mimeType=\"audio/mp4\" audioSamplingRate=\"44100\">"
// Temporary attribute. Will be removed when generating final mpd.
" duration=\"24.0094\">"
" <AudioChannelConfiguration" " <AudioChannelConfiguration"
" schemeIdUri=" " schemeIdUri="
" \"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\"" " \"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\""
@ -1011,9 +1047,7 @@ TEST_F(OnDemandAdaptationSetTest, Text) {
" <Role schemeIdUri=\"urn:mpeg:dash:role:2011\"" " <Role schemeIdUri=\"urn:mpeg:dash:role:2011\""
" value=\"subtitle\"/>\n" " value=\"subtitle\"/>\n"
" <Representation id=\"0\" bandwidth=\"1000\"" " <Representation id=\"0\" bandwidth=\"1000\""
" mimeType=\"application/ttml+xml\" " " mimeType=\"application/ttml+xml\">"
// Temporary attribute. Will be removed when generating final mpd.
" duration=\"35\">"
" <BaseURL>subtitle.xml</BaseURL>" " <BaseURL>subtitle.xml</BaseURL>"
" </Representation>" " </Representation>"
"</AdaptationSet>"; "</AdaptationSet>";

View File

@ -16,6 +16,7 @@
#include "packager/mpd/base/adaptation_set.h" #include "packager/mpd/base/adaptation_set.h"
#include "packager/mpd/base/mpd_utils.h" #include "packager/mpd/base/mpd_utils.h"
#include "packager/mpd/base/period.h" #include "packager/mpd/base/period.h"
#include "packager/mpd/base/representation.h"
#include "packager/mpd/base/xml/xml_node.h" #include "packager/mpd/base/xml/xml_node.h"
#include "packager/version/version.h" #include "packager/version/version.h"
@ -44,28 +45,6 @@ void AddMpdNameSpaceInfo(XmlNode* mpd) {
mpd->SetStringAttribute("xmlns:cenc", kCencNamespace); mpd->SetStringAttribute("xmlns:cenc", kCencNamespace);
} }
bool IsPeriodNode(xmlNodePtr node) {
DCHECK(node);
int kEqual = 0;
return xmlStrcmp(node->name, reinterpret_cast<const xmlChar*>("Period")) ==
kEqual;
}
// Find the first <Period> element. This does not recurse down the tree,
// only checks direct children. Returns the pointer to Period element on
// success, otherwise returns false.
// As noted here, we must traverse.
// http://www.xmlsoft.org/tutorial/ar01s04.html
xmlNodePtr FindPeriodNode(XmlNode* xml_node) {
for (xmlNodePtr node = xml_node->GetRawPtr()->xmlChildrenNode; node != NULL;
node = node->next) {
if (IsPeriodNode(node))
return node;
}
return NULL;
}
bool Positive(double d) { bool Positive(double d) {
return d > 0.0; return d > 0.0;
} }
@ -306,44 +285,46 @@ float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) {
DCHECK(mpd_node); DCHECK(mpd_node);
DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type);
if (periods_.empty()) {
LOG(WARNING) << "No Period found. Set MPD duration to 0.";
return 0.0f;
}
// Attribute mediaPresentationDuration must be present for 'static' MPD. So // Attribute mediaPresentationDuration must be present for 'static' MPD. So
// setting "PT0S" is required even if none of the representaions have duration // setting "PT0S" is required even if none of the representaions have duration
// attribute. // attribute.
float max_duration = 0.0f; float max_duration = 0.0f;
xmlNodePtr period_node = FindPeriodNode(mpd_node); // TODO(kqyang): Right now all periods contain the duration for the whole MPD.
if (!period_node) { // Simply get the duration from the first period. Ideally the period duration
LOG(WARNING) << "No Period node found. Set MPD duration to 0."; // should only count the (sub)segments in that period.
return 0.0f; for (const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
} for (const auto* representation : adaptation_set->GetRepresentations()) {
max_duration =
DCHECK(IsPeriodNode(period_node)); std::max(representation->GetDurationSeconds(), max_duration);
// TODO(kqyang): Why don't we iterate the C++ classes instead of iterating XML
// elements?
// TODO(kqyang): Verify if this works for static + live profile.
for (xmlNodePtr adaptation_set = xmlFirstElementChild(period_node);
adaptation_set; adaptation_set = xmlNextElementSibling(adaptation_set)) {
for (xmlNodePtr representation = xmlFirstElementChild(adaptation_set);
representation;
representation = xmlNextElementSibling(representation)) {
float duration = 0.0f;
if (GetDurationAttribute(representation, &duration)) {
max_duration = max_duration > duration ? max_duration : duration;
// 'duration' attribute is there only to help generate MPD, not
// necessary for MPD, remove the attribute.
xmlUnsetProp(representation, BAD_CAST "duration");
}
} }
} }
return max_duration; return max_duration;
} }
bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) { bool MpdBuilder::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds); DCHECK(timestamp_seconds);
DCHECK(!periods_.empty()); DCHECK(!periods_.empty());
return periods_.front()->GetEarliestTimestamp(timestamp_seconds); double timestamp = 0;
double earliest_timestamp = -1;
// The first period should have the earliest timestamp.
for (const auto* adaptation_set : periods_.front()->GetAdaptationSets()) {
for (const auto* representation : adaptation_set->GetRepresentations()) {
if (representation->GetEarliestTimestamp(&timestamp) &&
(earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
earliest_timestamp = timestamp;
}
}
}
if (earliest_timestamp < 0)
return false;
*timestamp_seconds = earliest_timestamp;
return true;
} }
void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path, void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path,

View File

@ -110,24 +110,13 @@ xml::scoped_xml_ptr<xmlNode> Period::GetXml() {
return period.PassScopedPtr(); return period.PassScopedPtr();
} }
bool Period::GetEarliestTimestamp(double* timestamp_seconds) { const std::list<AdaptationSet*> Period::GetAdaptationSets() const {
DCHECK(timestamp_seconds); std::list<AdaptationSet*> adaptation_sets;
double earliest_timestamp(-1);
for (const std::unique_ptr<AdaptationSet>& adaptation_set : for (const std::unique_ptr<AdaptationSet>& adaptation_set :
adaptation_sets_) { adaptation_sets_) {
double timestamp; adaptation_sets.push_back(adaptation_set.get());
if (adaptation_set->GetEarliestTimestamp(&timestamp) &&
(earliest_timestamp < 0 || timestamp < earliest_timestamp)) {
DCHECK_GE(timestamp, 0);
earliest_timestamp = timestamp;
}
} }
if (earliest_timestamp < 0) return adaptation_sets;
return false;
*timestamp_seconds = earliest_timestamp;
return true;
} }
std::unique_ptr<AdaptationSet> Period::NewAdaptationSet( std::unique_ptr<AdaptationSet> Period::NewAdaptationSet(

View File

@ -50,6 +50,9 @@ class Period {
/// NULL scoped_xml_ptr. /// NULL scoped_xml_ptr.
xml::scoped_xml_ptr<xmlNode> GetXml(); xml::scoped_xml_ptr<xmlNode> GetXml();
/// @return The list of AdaptationSets in this Period.
const std::list<AdaptationSet*> GetAdaptationSets() const;
protected: protected:
/// @param mpd_options is the options for this MPD. /// @param mpd_options is the options for this MPD.
/// @param adaptation_set_counter is a counter for assigning ID numbers to /// @param adaptation_set_counter is a counter for assigning ID numbers to
@ -67,10 +70,6 @@ class Period {
friend class MpdBuilder; friend class MpdBuilder;
friend class PeriodTest; friend class PeriodTest;
// Gets the earliest, normalized segment timestamp. Returns true on success,
// false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
// Calls AdaptationSet constructor. For mock injection. // Calls AdaptationSet constructor. For mock injection.
virtual std::unique_ptr<AdaptationSet> NewAdaptationSet( virtual std::unique_ptr<AdaptationSet> NewAdaptationSet(
uint32_t adaptation_set_id, uint32_t adaptation_set_id,

View File

@ -712,7 +712,6 @@ TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
"reference_time_scale: 50\n" "reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n" "container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n"; "media_duration_seconds: 10.5\n";
const char kAacGermanAudioContent[] = const char kAacGermanAudioContent[] =
"audio_info {\n" "audio_info {\n"
" codec: 'mp4a.40.2'\n" " codec: 'mp4a.40.2'\n"
@ -724,7 +723,6 @@ TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
"reference_time_scale: 50\n" "reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n" "container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n"; "media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent1[] = const char kVorbisGermanAudioContent1[] =
"audio_info {\n" "audio_info {\n"
" codec: 'vorbis'\n" " codec: 'vorbis'\n"
@ -736,7 +734,6 @@ TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
"reference_time_scale: 50\n" "reference_time_scale: 50\n"
"container_type: CONTAINER_WEBM\n" "container_type: CONTAINER_WEBM\n"
"media_duration_seconds: 10.5\n"; "media_duration_seconds: 10.5\n";
const char kVorbisGermanAudioContent2[] = const char kVorbisGermanAudioContent2[] =
"audio_info {\n" "audio_info {\n"
" codec: 'vorbis'\n" " codec: 'vorbis'\n"
@ -784,6 +781,57 @@ TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) {
content_protection_in_adaptation_set_)); content_protection_in_adaptation_set_));
} }
TEST_P(PeriodTest, GetAdaptationSets) {
const char kAacEnglishAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'eng'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
const char kAacGermanAudioContent[] =
"audio_info {\n"
" codec: 'mp4a.40.2'\n"
" sampling_frequency: 44100\n"
" time_scale: 1200\n"
" num_channels: 2\n"
" language: 'ger'\n"
"}\n"
"reference_time_scale: 50\n"
"container_type: CONTAINER_MP4\n"
"media_duration_seconds: 10.5\n";
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_eng_adaptation_set(
new StrictMock<MockAdaptationSet>(1));
auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get();
std::unique_ptr<StrictMock<MockAdaptationSet>> aac_ger_adaptation_set(
new StrictMock<MockAdaptationSet>(2));
auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get();
EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _))
.WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set))))
.WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set))));
ASSERT_EQ(aac_eng_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacEnglishAudioContent),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
UnorderedElementsAre(aac_eng_adaptation_set_ptr));
ASSERT_EQ(aac_ger_adaptation_set_ptr,
testable_period_.GetOrCreateAdaptationSet(
ConvertToMediaInfo(kAacGermanAudioContent),
content_protection_in_adaptation_set_));
EXPECT_THAT(testable_period_.GetAdaptationSets(),
UnorderedElementsAre(aac_eng_adaptation_set_ptr,
aac_ger_adaptation_set_ptr));
}
INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet, INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet,
PeriodTest, PeriodTest,
::testing::Bool()); ::testing::Bool());

View File

@ -255,15 +255,6 @@ xml::scoped_xml_ptr<xmlNode> Representation::GetXml() {
return xml::scoped_xml_ptr<xmlNode>(); return xml::scoped_xml_ptr<xmlNode>();
} }
// Set media duration for static mpd.
if (mpd_options_.mpd_type == MpdType::kStatic &&
media_info_.has_media_duration_seconds()) {
// Adding 'duration' attribute, so that this information can be used when
// generating one MPD file. This should be removed from the final MPD.
representation.SetFloatingPointAttribute(
"duration", media_info_.media_duration_seconds());
}
if (HasVODOnlyFields(media_info_) && if (HasVODOnlyFields(media_info_) &&
!representation.AddVODOnlyInfo(media_info_)) { !representation.AddVODOnlyInfo(media_info_)) {
LOG(ERROR) << "Failed to add VOD segment info."; LOG(ERROR) << "Failed to add VOD segment info.";
@ -287,6 +278,21 @@ void Representation::SuppressOnce(SuppressFlag flag) {
output_suppression_flags_ |= flag; output_suppression_flags_ |= flag;
} }
bool Representation::GetEarliestTimestamp(double* timestamp_seconds) const {
DCHECK(timestamp_seconds);
if (segment_infos_.empty())
return false;
*timestamp_seconds = static_cast<double>(segment_infos_.begin()->start_time) /
GetTimeScale(media_info_);
return true;
}
float Representation::GetDurationSeconds() const {
return media_info_.media_duration_seconds();
}
bool Representation::HasRequiredMediaInfoFields() { bool Representation::HasRequiredMediaInfoFields() {
if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) { if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) {
LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields."; LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields.";
@ -453,15 +459,4 @@ std::string Representation::GetTextMimeType() const {
return ""; return "";
} }
bool Representation::GetEarliestTimestamp(double* timestamp_seconds) {
DCHECK(timestamp_seconds);
if (segment_infos_.empty())
return false;
*timestamp_seconds = static_cast<double>(segment_infos_.begin()->start_time) /
GetTimeScale(media_info_);
return true;
}
} // namespace shaka } // namespace shaka

View File

@ -125,6 +125,13 @@ class Representation {
/// This may be called multiple times to set different (or the same) flags. /// This may be called multiple times to set different (or the same) flags.
void SuppressOnce(SuppressFlag flag); void SuppressOnce(SuppressFlag flag);
/// Gets the earliest, normalized segment timestamp.
/// @return true if successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds) const;
/// @return The duration of the Representation in seconds.
float GetDurationSeconds() const;
/// @return ID number for <Representation>. /// @return ID number for <Representation>.
uint32_t id() const { return id_; } uint32_t id() const { return id_; }
@ -150,8 +157,6 @@ class Representation {
friend class AdaptationSet; friend class AdaptationSet;
friend class RepresentationTest; friend class RepresentationTest;
bool AddLiveInfo(xml::RepresentationXmlNode* representation);
// Returns true if |media_info_| has required fields to generate a valid // Returns true if |media_info_| has required fields to generate a valid
// Representation. Otherwise returns false. // Representation. Otherwise returns false.
bool HasRequiredMediaInfoFields(); bool HasRequiredMediaInfoFields();
@ -172,10 +177,6 @@ class Representation {
std::string GetAudioMimeType() const; std::string GetAudioMimeType() const;
std::string GetTextMimeType() const; std::string GetTextMimeType() const;
// Gets the earliest, normalized segment timestamp. Returns true if
// successful, false otherwise.
bool GetEarliestTimestamp(double* timestamp_seconds);
// Init() checks that only one of VideoInfo, AudioInfo, or TextInfo is set. So // Init() checks that only one of VideoInfo, AudioInfo, or TextInfo is set. So
// any logic using this can assume only one set. // any logic using this can assume only one set.
MediaInfo media_info_; MediaInfo media_info_;

View File

@ -484,6 +484,32 @@ TEST_F(SegmentTemplateTest, OneSegmentNormal) {
EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml)); EXPECT_THAT(representation_->GetXml().get(), XmlNodeEqual(kExpectedXml));
} }
TEST_F(SegmentTemplateTest, GetEarliestTimestamp) {
double earliest_timestamp;
// No segments.
EXPECT_FALSE(representation_->GetEarliestTimestamp(&earliest_timestamp));
const uint64_t kStartTime = 88;
const uint64_t kDuration = 10;
const uint64_t kSize = 128;
AddSegments(kStartTime, kDuration, kSize, 0);
AddSegments(kStartTime + kDuration, kDuration, kSize, 0);
ASSERT_TRUE(representation_->GetEarliestTimestamp(&earliest_timestamp));
EXPECT_EQ(static_cast<double>(kStartTime) / kDefaultTimeScale,
earliest_timestamp);
}
TEST_F(SegmentTemplateTest, GetDuration) {
const float kMediaDurationSeconds = 88.8f;
MediaInfo media_info = ConvertToMediaInfo(GetDefaultMediaInfo());
media_info.set_media_duration_seconds(kMediaDurationSeconds);
representation_ =
CreateRepresentation(media_info, kAnyRepresentationId, NoListener());
ASSERT_TRUE(representation_->Init());
EXPECT_EQ(kMediaDurationSeconds, representation_->GetDurationSeconds());
}
TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) { TEST_F(SegmentTemplateTest, NormalRepeatedSegmentDuration) {
const uint64_t kSize = 256; const uint64_t kSize = 256;
uint64_t start_time = 0; uint64_t start_time = 0;