From bb8cf8761798536919efb81ac0533b10144d98d0 Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Wed, 5 Aug 2015 14:44:35 -0700 Subject: [PATCH] Add logic to correctly set subSegmentAlignment for VOD - subSegmentAlignment logic assumed that all Representations were added before calling Representations::AddNewSegment() on any Representation in an AdaptationSet. It is not a valid assumption for VOD and is removed. Change-Id: Ifb7e34ee103ee30027e45a804427baf281f3137c --- packager/mpd/base/mpd_builder.cc | 90 +++++++++++++++++++++-- packager/mpd/base/mpd_builder.h | 17 ++++- packager/mpd/base/mpd_builder_unittest.cc | 37 +++++----- 3 files changed, 116 insertions(+), 28 deletions(-) diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index d28e591754..8e8dca27c7 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -732,6 +733,11 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { video_frame_rates_.rbegin()->second); } + // Note: must be checked before checking segments_aligned_ (below). + if (mpd_type_ == MpdBuilder::kStatic) { + CheckVodSegmentAlignment(); + } + if (segments_aligned_ == kSegmentAlignmentTrue) { adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic ? "subSegmentAlignment" @@ -767,11 +773,24 @@ int AdaptationSet::Group() const { return group_; } +// Check segmentAlignment for Live here. Storing all start_time and duration +// will out-of-memory because there's no way of knowing when it will end. +// VOD subSegmentAlignment check is *not* done here because it is possible +// that some Representations might not have been added yet (e.g. a thread is +// assigned per muxer so one might run faster than others). +// To be clear, for Live, all Representations should be added before a +// segment is added. void AdaptationSet::OnNewSegmentForRepresentation(uint32_t representation_id, uint64_t start_time, uint64_t duration) { base::AutoLock scoped_lock(lock_); - CheckSegmentAlignment(representation_id, start_time, duration); + + if (mpd_type_ == MpdBuilder::kDynamic) { + CheckLiveSegmentAlignment(representation_id, start_time, duration); + } else { + representation_segment_start_times_[representation_id].push_back( + start_time); + } } void AdaptationSet::OnSetFrameRateForRepresentation( @@ -827,12 +846,9 @@ bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { // They are not aligned but this will be marked as aligned. // But since this is unlikely to happen in the packager (and to save // computation), this isn't handled at the moment. -// TODO(rkuroiwa): For VOD, not all Representations get added to an -// AdaptationSet before this is called. Add a similar but separate method that -// keeps the timestamps around. It shouldn't out-of-memory for VOD. -void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id, - uint64_t start_time, - uint64_t /* duration */) { +void AdaptationSet::CheckLiveSegmentAlignment(uint32_t representation_id, + uint64_t start_time, + uint64_t /* duration */) { if (segments_aligned_ == kSegmentAlignmentFalse || force_set_segment_alignment_) { return; @@ -874,6 +890,66 @@ void AdaptationSet::CheckSegmentAlignment(uint32_t representation_id, } } +// Make sure all segements start times match for all Representations. +// This assumes that the segments are contiguous. +void AdaptationSet::CheckVodSegmentAlignment() { + if (segments_aligned_ == kSegmentAlignmentFalse || + force_set_segment_alignment_) { + return; + } + if (representation_segment_start_times_.empty()) + return; + if (representation_segment_start_times_.size() == 1) { + segments_aligned_ = kSegmentAlignmentTrue; + return; + } + + // This is not the most efficient implementation to compare the values + // because expected_time_line is compared against all other time lines, but + // probably the most readable. + const std::list& expected_time_line = + representation_segment_start_times_.begin()->second; + + bool all_segment_time_line_same_length = true; + // Note that the first entry is skipped because it is expected_time_line. + RepresentationTimeline::const_iterator it = + representation_segment_start_times_.begin(); + for (++it; it != representation_segment_start_times_.end(); ++it) { + const std::list& other_time_line = it->second; + if (expected_time_line.size() != other_time_line.size()) { + all_segment_time_line_same_length = false; + } + + const std::list* longer_list = &other_time_line; + const std::list* shorter_list = &expected_time_line; + if (expected_time_line.size() > other_time_line.size()) { + shorter_list = &other_time_line; + longer_list = &expected_time_line; + } + + if (!std::equal(shorter_list->begin(), shorter_list->end(), + longer_list->begin())) { + // Some segments are definitely unaligned. + segments_aligned_ = kSegmentAlignmentFalse; + representation_segment_start_times_.clear(); + return; + } + } + + // TODO(rkuroiwa): The right way to do this is to also check the durations. + // For example: + // (a) 3 4 5 + // (b) 3 4 5 6 + // could be true or false depending on the length of the third segment of (a). + // i.e. if length of the third segment is 2, then this is not aligned. + if (!all_segment_time_line_same_length) { + segments_aligned_ = kSegmentAlignmentUnknown; + return; + } + + segments_aligned_ = kSegmentAlignmentTrue; +} + // Since all AdaptationSet cares about is the maxFrameRate, representation_id // is not passed to this method. void AdaptationSet::RecordFrameRate(uint32_t frame_duration, diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index dca550c9fb..e45453e1cf 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -312,13 +312,18 @@ class AdaptationSet { /// Called from OnNewSegmentForRepresentation(). Checks whether the segments /// are aligned. Sets segments_aligned_. + /// This is only for Live. For VOD, CheckVodSegmentAlignment() should be used. /// @param representation_id is the id of the Representation with a new /// segment. /// @param start_time is the start time of the new segment. /// @param duration is the duration of the new segment. - void CheckSegmentAlignment(uint32_t representation_id, - uint64_t start_time, - uint64_t duration); + void CheckLiveSegmentAlignment(uint32_t representation_id, + uint64_t start_time, + uint64_t duration); + + // Checks representation_segment_start_times_ and sets segments_aligned_. + // Use this for VOD, do not use for Live. + void CheckVodSegmentAlignment(); // Records the framerate of a Representation. void RecordFrameRate(uint32_t frame_duration, uint32_t timescale); @@ -377,6 +382,12 @@ class AdaptationSet { bool force_set_segment_alignment_; // Keeps track of segment start times of Representations. + // For VOD, this will not be cleared, all the segment start times are + // stored in this. This should not out-of-memory for a reasonable length + // video and reasonable subsegment length. + // For Live, the entries are deleted (see CheckLiveSegmentAlignment() + // implementation comment) because storing the entire timeline is not + // reasonable and may cause an out-of-memory problem. RepresentationTimeline representation_segment_start_times_; DISALLOW_COPY_AND_ASSIGN(AdaptationSet); diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 5b56af448c..f83e1aee84 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -947,6 +947,8 @@ TEST_F(CommonMpdBuilderTest, // Verify that subSegmentAlignment is set to true if all the Representations' // segments are aligned and the MPD type is static. +// Also checking that not all Representations have to be added before calling +// AddNewSegment() on a Representation. TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) { base::AtomicSequenceNumber sequence_counter; const char k480pMediaInfo[] = @@ -971,37 +973,36 @@ TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) { " pixel_height: 1\n" "}\n" "container_type: 1\n"; + + // First use same start time and duration, and verify that subSegmentAlignment + // is set to true. + const uint64_t kStartTime = 0u; + const uint64_t kDuration = 10u; + const uint64_t kAnySize = 19834u; + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), MpdBuilder::kStatic, &sequence_counter); Representation* representation_480p = adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + // Add a subsegment immediately before adding the 360p Representation. + // This should still work for VOD. + representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); + Representation* representation_360p = adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); - - // First use same start time and duration, and verify that that - // segmentAlignment is set. - const uint64_t kStartTime = 0u; - const uint64_t kDuration = 10u; - const uint64_t kAnySize = 19834u; - representation_480p->AddNewSegment(kStartTime, kDuration, kAnySize); representation_360p->AddNewSegment(kStartTime, kDuration, kAnySize); + xml::ScopedXmlPtr::type aligned(adaptation_set.GetXml()); EXPECT_NO_FATAL_FAILURE( ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get())); - EXPECT_EQ(2u, adaptation_set.representation_segment_start_times_.size()); - // Also check that the start times are removed from the vector. - EXPECT_EQ(0u, - adaptation_set - .representation_segment_start_times_[representation_480p->id()] - .size()); - EXPECT_EQ(0u, - adaptation_set - .representation_segment_start_times_[representation_360p->id()] - .size()); + // Unknown because 480p has an extra subsegments. + representation_480p->AddNewSegment(11, 20, kAnySize); + xml::ScopedXmlPtr::type alignment_unknown(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("subSegmentAlignment", alignment_unknown.get())); // Add segments that make them not aligned. - representation_480p->AddNewSegment(11, 20, kAnySize); representation_360p->AddNewSegment(10, 1, kAnySize); representation_360p->AddNewSegment(11, 19, kAnySize);