From fe851f692bd4daa6217099da325a644638234f4f Mon Sep 17 00:00:00 2001 From: Rintaro Kuroiwa Date: Mon, 13 Jul 2015 10:44:52 -0700 Subject: [PATCH] Calculate (sub)SegmentAlignment for AdaptationSets - AdaptationSet::OnNewSegmentForRepresentation() calculates whether the Representations are aligned by looking at the start time and duration. (Except that duration is not used in this implementation). - Add RepresentationStateChangeListener to callback AdaptationSet::OnNewSegmentForRepresentation() from Representation. Change-Id: I3c30bd6652880dabb9d5c619d8a733ffc789eec9 --- packager/mpd/base/mpd_builder.cc | 158 +++++++++-- packager/mpd/base/mpd_builder.h | 107 ++++++- packager/mpd/base/mpd_builder_unittest.cc | 263 ++++++++++++++++-- packager/mpd/mpd.gyp | 1 + packager/mpd/test/data/dynamic_normal_mpd.txt | 2 +- 5 files changed, 489 insertions(+), 42 deletions(-) diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 86e56a472b..1a21f9d1f0 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -329,16 +329,39 @@ class LibXmlInitializer { DISALLOW_COPY_AND_ASSIGN(LibXmlInitializer); }; +class RepresentationStateChangeListenerImpl + : public RepresentationStateChangeListener { + public: + // |adaptation_set| is not owned by this class. + RepresentationStateChangeListenerImpl(uint32_t representation_id, + AdaptationSet* adaptation_set) + : representation_id_(representation_id), adaptation_set_(adaptation_set) { + DCHECK(adaptation_set_); + } + virtual ~RepresentationStateChangeListenerImpl() OVERRIDE {} + + // RepresentationStateChangeListener implementation. + virtual void OnNewSegmentForRepresentation(uint64_t start_time, + uint64_t duration) OVERRIDE { + adaptation_set_->OnNewSegmentForRepresentation(representation_id_, + start_time, duration); + } + + private: + const uint32_t representation_id_; + AdaptationSet* const adaptation_set_; + + DISALLOW_COPY_AND_ASSIGN(RepresentationStateChangeListenerImpl); +}; + } // namespace MpdBuilder::MpdBuilder(MpdType type, const MpdOptions& mpd_options) : type_(type), mpd_options_(mpd_options), - adaptation_sets_deleter_(&adaptation_sets_) { -} + adaptation_sets_deleter_(&adaptation_sets_) {} -MpdBuilder::~MpdBuilder() { -} +MpdBuilder::~MpdBuilder() {} void MpdBuilder::AddBaseUrl(const std::string& base_url) { base::AutoLock scoped_lock(lock_); @@ -349,6 +372,7 @@ AdaptationSet* MpdBuilder::AddAdaptationSet(const std::string& lang) { base::AutoLock scoped_lock(lock_); scoped_ptr adaptation_set( new AdaptationSet(adaptation_set_counter_.GetNext(), lang, mpd_options_, + type_, &representation_counter_)); DCHECK(adaptation_set); @@ -592,30 +616,38 @@ void MpdBuilder::MakePathsRelativeToMpd(const std::string& mpd_path, AdaptationSet::AdaptationSet(uint32_t adaptation_set_id, const std::string& lang, const MpdOptions& mpd_options, + MpdBuilder::MpdType mpd_type, base::AtomicSequenceNumber* counter) : representations_deleter_(&representations_), representation_counter_(counter), id_(adaptation_set_id), lang_(lang), mpd_options_(mpd_options), - group_(kAdaptationSetGroupNotSet) { + mpd_type_(mpd_type), + group_(kAdaptationSetGroupNotSet), + segments_aligned_(kSegmentAlignmentUnknown), + force_set_segment_alignment_(false) { DCHECK(counter); } -AdaptationSet::~AdaptationSet() { -} +AdaptationSet::~AdaptationSet() {} Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) { base::AutoLock scoped_lock(lock_); + const uint32_t representation_id = representation_counter_->GetNext(); + // Note that AdaptationSet outlive Representation, so this object + // will die before AdaptationSet. + scoped_ptr listener( + new RepresentationStateChangeListenerImpl(representation_id, this)); scoped_ptr representation(new Representation( - media_info, mpd_options_, representation_counter_->GetNext())); + media_info, mpd_options_, representation_id, listener.Pass())); if (!representation->Init()) return NULL; // For videos, record the width, height, and the frame rate to calculate the // max {width,height,framerate} required for DASH IOP. - if(media_info.has_video_info()) { + if (media_info.has_video_info()) { const MediaInfo::VideoInfo& video_info = media_info.video_info(); DCHECK(video_info.has_width()); DCHECK(video_info.has_height()); @@ -699,6 +731,13 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { video_frame_rates_.rbegin()->second); } + if (segments_aligned_ == kSegmentAlignmentTrue) { + adaptation_set.SetStringAttribute(mpd_type_ == MpdBuilder::kStatic + ? "subSegmentAlignment" + : "segmentAlignment", + "true"); + } + if (picture_aspect_ratio_.size() == 1) adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin()); @@ -713,6 +752,19 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { return adaptation_set.PassScopedPtr(); } +void AdaptationSet::ForceSetSegmentAlignment(bool segment_alignment) { + segments_aligned_ = + segment_alignment ? kSegmentAlignmentTrue : kSegmentAlignmentFalse; + force_set_segment_alignment_ = true; +} + +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); +} + bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { DCHECK(timestamp_seconds); @@ -734,18 +786,90 @@ bool AdaptationSet::GetEarliestTimestamp(double* timestamp_seconds) { return true; } -Representation::Representation(const MediaInfo& media_info, - const MpdOptions& mpd_options, - uint32_t id) +// This implementation assumes that each representations' segments' are +// contiguous. +// Also assumes that all Representations are added before this is called. +// This checks whether the first elements of the lists in +// representation_segment_start_times_ are aligned. +// For example, suppose this method was just called with args rep_id=2 +// start_time=1. +// 1 -> [1, 100, 200] +// 2 -> [1] +// The timestamps of the first elements match, so this flags +// segments_aligned_=true. +// Also since the first segment start times match, the first element of all the +// lists are removed, so the map of lists becomes: +// 1 -> [100, 200] +// 2 -> [] +// Note that there could be false positives. +// e.g. just got rep_id=3 start_time=1 duration=300, and the duration of the +// whole AdaptationSet is 300. +// 1 -> [1, 100, 200] +// 2 -> [1, 90, 100] +// 3 -> [1] +// 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 */) { + if (segments_aligned_ == kSegmentAlignmentFalse || + force_set_segment_alignment_) { + return; + } + + std::list& representation_start_times = + representation_segment_start_times_[representation_id]; + representation_start_times.push_back(start_time); + // There's no way to detemine whether the segments are aligned if some + // representations do not have any segments. + if (representation_segment_start_times_.size() != representations_.size()) + return; + + DCHECK(!representation_start_times.empty()); + const uint64_t expected_start_time = representation_start_times.front(); + for (RepresentationTimeline::const_iterator it = + representation_segment_start_times_.begin(); + it != representation_segment_start_times_.end(); ++it) { + // If there are no entries in a list, then there is no way for the + // segment alignment status to change. + // Note that it can be empty because entries get deleted below. + if (it->second.empty()) + return; + + if (expected_start_time != it->second.front()) { + // Flag as false and clear the start times data, no need to keep it + // around. + segments_aligned_ = kSegmentAlignmentFalse; + representation_segment_start_times_.clear(); + return; + } + } + segments_aligned_ = kSegmentAlignmentTrue; + + for (RepresentationTimeline::iterator it = + representation_segment_start_times_.begin(); + it != representation_segment_start_times_.end(); ++it) { + it->second.pop_front(); + } +} + +Representation::Representation( + const MediaInfo& media_info, + const MpdOptions& mpd_options, + uint32_t id, + scoped_ptr state_change_listener) : media_info_(media_info), id_(id), bandwidth_estimator_(BandwidthEstimator::kUseAllBlocks), mpd_options_(mpd_options), - start_number_(1) { -} + start_number_(1), + state_change_listener_(state_change_listener.Pass()) {} -Representation::~Representation() { -} +Representation::~Representation() {} bool Representation::Init() { codecs_ = GetCodecs(media_info_); @@ -801,6 +925,8 @@ void Representation::AddNewSegment(uint64_t start_time, } base::AutoLock scoped_lock(lock_); + if (state_change_listener_) + state_change_listener_->OnNewSegmentForRepresentation(start_time, duration); if (IsContiguous(start_time, duration, size)) { ++segment_infos_.back().repeat; } else { diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index c220996823..0792515d06 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -200,6 +200,13 @@ class AdaptationSet { /// NULL ScopedXmlPtr. xml::ScopedXmlPtr::type GetXml(); + /// Forces the (sub)segmentAlignment field to be set to @a segment_alignment. + /// Use this if you are certain that the (sub)segments are alinged/unaligned + /// for the AdaptationSet. + /// @param segment_alignment is the value used for (sub)segmentAlignment + /// attribute. + void ForceSetSegmentAlignment(bool segment_alignment); + /// Sets the AdaptationSet@group attribute. /// Passing a negative value to this method will unset the attribute. /// Note that group=0 is a special group, as mentioned in the DASH MPD @@ -212,7 +219,43 @@ class AdaptationSet { // Must be unique in the Period. uint32_t id() const { return id_; } + /// Notifies the AdaptationSet instance that a new (sub)segment was added to + /// the Representation with @a representation_id. + /// This must be called every time a (sub)segment is added to a + /// Representation in this AdaptationSet. + /// If a Representation is constructed using AddRepresentation() this + /// is called automatically whenever Representation::AddNewSegment() is + /// is called. + /// @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 OnNewSegmentForRepresentation(uint32_t representation_id, + uint64_t start_time, + uint64_t duration); + private: + // kSegmentAlignmentUnknown means that it is uncertain if the + // (sub)segments are aligned or not. + // kSegmentAlignmentTrue means that it is certain that the all the (current) + // segments added to the adaptation set are aligned. + // kSegmentAlignmentFalse means that it is it is certain that some segments + // are not aligned. This is useful to disable the computation for + // segment alignment, once it is certain that some segments are not aligned. + enum SegmentAligmentStatus { + kSegmentAlignmentUnknown, + kSegmentAlignmentTrue, + kSegmentAlignmentFalse + }; + + // This maps Representations (IDs) to a list of start times of the segments. + // e.g. + // If Representation 1 has start time 0, 100, 200 and Representation 2 has + // start times 0, 200, 400, then the map contains: + // 1 -> [0, 100, 200] + // 2 -> [0, 200, 400] + typedef std::map > RepresentationTimeline; + friend class MpdBuilder; FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckAdaptationSetId); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, @@ -222,19 +265,37 @@ class AdaptationSet { FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckAdaptationSetTextContentType); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, SetAdaptationSetGroup); + FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, SubSegmentAlignment); + FRIEND_TEST_ALL_PREFIXES(StaticMpdBuilderTest, ForceSetSubSegmentAlignment); + FRIEND_TEST_ALL_PREFIXES(DynamicMpdBuilderTest, SegmentAlignment); /// @param adaptation_set_id is an ID number for this AdaptationSet. + /// @param lang is the language of this AdaptationSet. Mainly relevant for + /// audio. + /// @param mpd_options is the options for this MPD. + /// @param mpd_type is the type of this MPD. /// @param representation_counter is a Counter for assigning ID numbers to /// Representation. It can not be NULL. AdaptationSet(uint32_t adaptation_set_id, const std::string& lang, const MpdOptions& mpd_options, + MpdBuilder::MpdType mpd_type, base::AtomicSequenceNumber* representation_counter); // Gets the earliest, normalized segment timestamp. Returns true if // successful, false otherwise. bool GetEarliestTimestamp(double* timestamp_seconds); + /// Called from OnNewSegmentForRepresentation(). Checks whether the segments + /// are aligned. Sets segments_aligned_. + /// @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); + std::list content_protection_elements_; std::list representations_; ::STLElementDeleter > representations_deleter_; @@ -246,6 +307,7 @@ class AdaptationSet { const uint32_t id_; const std::string lang_; const MpdOptions& mpd_options_; + const MpdBuilder::MpdType mpd_type_; // The group attribute for the AdaptationSet. If the value is negative, // no group number is specified. @@ -283,9 +345,31 @@ class AdaptationSet { // The roles of this AdaptationSet. std::set roles_; + // True iff all the segments are aligned. + SegmentAligmentStatus segments_aligned_; + bool force_set_segment_alignment_; + + // Keeps track of segment start times of Representations. + RepresentationTimeline representation_segment_start_times_; + DISALLOW_COPY_AND_ASSIGN(AdaptationSet); }; +// TODO(rkuroiwa): OnSetSampleDuration() must also be added to this to notify +// sample duration change to AdaptationSet, to set the right frame rate. +class RepresentationStateChangeListener { + public: + RepresentationStateChangeListener() {} + virtual ~RepresentationStateChangeListener() {} + + /// Notifies the instance that a new (sub)segment was added to + /// the Representation. + /// @param start_time is the start time of the new segment. + /// @param duration is the duration of the new segment. + virtual void OnNewSegmentForRepresentation(uint64_t start_time, + uint64_t duration) = 0; +}; + /// Representation class contains references to a single media stream, as /// well as optional ContentProtection elements for that stream. class Representation { @@ -309,8 +393,10 @@ class Representation { /// then the former is used. void AddContentProtectionElement(const ContentProtectionElement& element); - /// Add a media segment to the representation. - /// @param start_time is the start time for the segment, in units of the + /// Add a media (sub)segment to the representation. + /// AdaptationSet@{subSegmentAlignment,segmentAlignment} cannot be set + /// if this is not called for all Representations. + /// @param start_time is the start time for the (sub)segment, in units of the /// stream's time scale. /// @param duration is the duration of the segment, in units of the stream's /// time scale. @@ -339,15 +425,22 @@ class Representation { FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, CheckRepresentationId); FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, SetSampleDuration); + FRIEND_TEST_ALL_PREFIXES(CommonMpdBuilderTest, + RepresentationStateChangeListener); /// @param media_info is a MediaInfo containing information on the media. /// @a media_info.bandwidth is required for 'static' profile. If @a /// media_info.bandwidth is not present in 'dynamic' profile, this /// tries to estimate it using the info passed to AddNewSegment(). + /// @param mpd_options is options for the entire MPD. /// @param representation_id is the numeric ID for the . - Representation(const MediaInfo& media_info, - const MpdOptions& mpd_options, - uint32_t representation_id); + /// @param state_change_listener is an event handler for state changes to + /// the representation. If null, no event handler registered. + Representation( + const MediaInfo& media_info, + const MpdOptions& mpd_options, + uint32_t representation_id, + scoped_ptr state_change_listener); bool AddLiveInfo(xml::RepresentationXmlNode* representation); @@ -391,6 +484,10 @@ class Representation { // Starts from 1. uint32_t start_number_; + // If this is not null, then Representation is responsible for calling the + // right methods at right timings. + scoped_ptr state_change_listener_; + DISALLOW_COPY_AND_ASSIGN(Representation); }; diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index e4b3e142a6..20c0c3fe92 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -4,6 +4,7 @@ // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd +#include #include #include #include @@ -77,6 +78,16 @@ void ExpectAttributeNotSet(base::StringPiece attribute, xmlNodePtr node) { xmlGetProp(node, BAD_CAST attribute.data())); ASSERT_FALSE(attribute_xml_str); } + +class MockRepresentationStateChangeListener + : public RepresentationStateChangeListener { + public: + MockRepresentationStateChangeListener() {} + ~MockRepresentationStateChangeListener() {} + + MOCK_METHOD2(OnNewSegmentForRepresentation, + void(uint64_t start_time, uint64_t duration)); +}; } // namespace template @@ -106,6 +117,12 @@ class MpdBuilderTest: public ::testing::Test { representation_ = representation; } + // Helper function to return an empty listener for tests that don't need + // it. + scoped_ptr NoListener() { + return scoped_ptr(); + } + MpdBuilder mpd_; // We usually need only one representation. @@ -199,6 +216,8 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest { std::string TemplateOutputInsertValues(const std::string& s_elements_string, uint64_t bandwidth) { + // Note: Since all the tests have 1 Representation, the AdaptationSet + // always has segmentAligntment=true. const char kOutputTemplate[] = "\n" "\n" " \n" + " par=\"3:2\" segmentAlignment=\"true\">\n" " \n" @@ -282,6 +301,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { void CheckTimeShiftBufferDepthResult(const std::string& expected_s_element, int expected_time_shift_buffer_depth, int expected_start_number) { + // Note: Since all the tests have 1 Representation, the AdaptationSet + // always has segmentAligntment=true. const char kOutputTemplate[] = "\n" "\n" " \n" + " par=\"3:2\" segmentAlignment=\"true\">\n" " \n" @@ -328,8 +349,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { // Verify that AdaptationSet@group can be set and unset. TEST_F(CommonMpdBuilderTest, SetAdaptationSetGroup) { base::AtomicSequenceNumber sequence_counter; - AdaptationSet adaptation_set( - kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter); + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); adaptation_set.set_group(1); xml::ScopedXmlPtr::type xml_with_group(adaptation_set.GetXml()); @@ -357,8 +378,9 @@ TEST_F(CommonMpdBuilderTest, ValidMediaInfo) { " pixel_height: 1\n" "}\n" "container_type: 1\n"; - Representation representation( - ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId); + Representation representation(ConvertToMediaInfo(kTestMediaInfo), + MpdOptions(), kAnyRepresentationId, + NoListener()); EXPECT_TRUE(representation.Init()); } @@ -375,8 +397,9 @@ TEST_F(CommonMpdBuilderTest, InvalidMediaInfo) { " pixel_height: 1\n" "}\n" "container_type: 1\n"; - Representation representation( - ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId); + Representation representation(ConvertToMediaInfo(kTestMediaInfo), + MpdOptions(), kAnyRepresentationId, + NoListener()); EXPECT_FALSE(representation.Init()); } @@ -393,8 +416,9 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) { " pixel_height: 1\n" "}\n" "container_type: 1\n"; - Representation representation( - ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId); + Representation representation(ConvertToMediaInfo(kTestMediaInfo), + MpdOptions(), kAnyRepresentationId, + NoListener()); EXPECT_TRUE(representation.Init()); xml::ScopedXmlPtr::type node_xml(representation.GetXml()); EXPECT_NO_FATAL_FAILURE( @@ -409,6 +433,35 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) { ExpectAttributeEqString("frameRate", "10/10", node_xml.get())); } +// Make sure RepresentationStateChangeListener::OnNewSegmentForRepresentation() +// is called. +TEST_F(CommonMpdBuilderTest, RepresentationStateChangeListener) { + const char kTestMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + const uint64_t kStartTime = 199238u; + const uint64_t kDuration = 98u; + scoped_ptr listener( + new MockRepresentationStateChangeListener()); + EXPECT_CALL(*listener, + OnNewSegmentForRepresentation(kStartTime, kDuration)); + Representation representation( + ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId, + listener.PassAs()); + EXPECT_TRUE(representation.Init()); + + representation.AddNewSegment(kStartTime, kDuration, 10 /* any size */); +} + // Verify that content type is set correctly if video info is present in // MediaInfo. TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) { @@ -425,8 +478,8 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetVideoContentType) { "}\n" "container_type: 1\n"; - AdaptationSet adaptation_set( - kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter); + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); adaptation_set.AddRepresentation(ConvertToMediaInfo(kVideoMediaInfo)); xml::ScopedXmlPtr::type node_xml(adaptation_set.GetXml()); @@ -447,8 +500,8 @@ TEST_F(CommonMpdBuilderTest, CheckAdaptationSetAudioContentType) { "}\n" "container_type: 1\n"; - AdaptationSet adaptation_set( - kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter); + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); adaptation_set.AddRepresentation(ConvertToMediaInfo(kAudioMediaInfo)); xml::ScopedXmlPtr::type node_xml(adaptation_set.GetXml()); @@ -470,8 +523,8 @@ TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) { "}\n" "container_type: 1\n"; - AdaptationSet adaptation_set( - kAnyAdaptationSetId, "", MpdOptions(), &sequence_counter); + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); adaptation_set.AddRepresentation(ConvertToMediaInfo(kTextMediaInfo)); xml::ScopedXmlPtr::type node_xml(adaptation_set.GetXml()); @@ -482,8 +535,8 @@ TEST_F(CommonMpdBuilderTest, DISABLED_CheckAdaptationSetTextContentType) { TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) { base::AtomicSequenceNumber sequence_counter; const uint32_t kAdaptationSetId = 42; - AdaptationSet adaptation_set( - kAdaptationSetId, "", MpdOptions(), &sequence_counter); + AdaptationSet adaptation_set(kAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set)); } @@ -749,6 +802,176 @@ TEST_F(CommonMpdBuilderTest, ExpectAttributeNotSet("frameRate", adaptation_set_xml.get())); } +// Verify that subSegmentAlignment is set to true if all the Representations' +// segments are aligned and the MPD type is static. +TEST_F(StaticMpdBuilderTest, SubSegmentAlignment) { + base::AtomicSequenceNumber sequence_counter; + 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"; + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); + Representation* representation_480p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + 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()); + + // 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); + + xml::ScopedXmlPtr::type unaligned(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("subSegmentAlignment", unaligned.get())); +} + +// Verify that subSegmentAlignment can be force set to true. +TEST_F(StaticMpdBuilderTest, ForceSetSubSegmentAlignment) { + base::AtomicSequenceNumber sequence_counter; + 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"; + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kStatic, &sequence_counter); + Representation* representation_480p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation_360p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + + // Use different starting times to make the segments "not aligned". + const uint64_t kStartTime1 = 1u; + const uint64_t kStartTime2 = 2u; + COMPILE_ASSERT(kStartTime1 != kStartTime2, StartTimesShouldBeDifferent); + const uint64_t kDuration = 10u; + const uint64_t kAnySize = 19834u; + representation_480p->AddNewSegment(kStartTime1, kDuration, kAnySize); + representation_360p->AddNewSegment(kStartTime2, kDuration, kAnySize); + xml::ScopedXmlPtr::type unaligned(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("subSegmentAlignment", unaligned.get())); + + // Then force set the segment alignment attribute to true. + adaptation_set.ForceSetSegmentAlignment(true); + xml::ScopedXmlPtr::type aligned(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("subSegmentAlignment", "true", aligned.get())); +} + +// Verify that segmentAlignment is set to true if all the Representations +// segments' are aligned and the MPD type is dynamic. +TEST_F(DynamicMpdBuilderTest, SegmentAlignment) { + base::AtomicSequenceNumber sequence_counter; + 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"; + AdaptationSet adaptation_set(kAnyAdaptationSetId, "", MpdOptions(), + MpdBuilder::kDynamic, &sequence_counter); + Representation* representation_480p = + adaptation_set.AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + 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("segmentAlignment", "true", aligned.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); + + xml::ScopedXmlPtr::type unaligned(adaptation_set.GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("subSegmentAlignment", unaligned.get())); +} + // Verify that the width and height attribute are set if all the video // representations have the same width and height. TEST_F(StaticMpdBuilderTest, AdapatationSetWidthAndHeight) { @@ -835,7 +1058,7 @@ TEST_F(CommonMpdBuilderTest, CheckRepresentationId) { const uint32_t kRepresentationId = 1; Representation representation( - video_media_info, MpdOptions(), kRepresentationId); + video_media_info, MpdOptions(), kRepresentationId, NoListener()); EXPECT_TRUE(representation.Init()); ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kRepresentationId, &representation)); } @@ -849,7 +1072,7 @@ TEST_F(CommonMpdBuilderTest, SetSampleDuration) { const uint32_t kRepresentationId = 1; Representation representation( - video_media_info, MpdOptions(), kRepresentationId); + video_media_info, MpdOptions(), kRepresentationId, NoListener()); EXPECT_TRUE(representation.Init()); representation.SetSampleDuration(2u); EXPECT_EQ(2u, diff --git a/packager/mpd/mpd.gyp b/packager/mpd/mpd.gyp index 72c96053a8..948c686cab 100644 --- a/packager/mpd/mpd.gyp +++ b/packager/mpd/mpd.gyp @@ -80,6 +80,7 @@ '../base/base.gyp:base', '../media/file/file.gyp:file', '../media/test/media_test.gyp:run_all_unittests', + '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', 'mpd_builder', 'mpd_util', diff --git a/packager/mpd/test/data/dynamic_normal_mpd.txt b/packager/mpd/test/data/dynamic_normal_mpd.txt index 7d1b868cee..a379db8ac7 100644 --- a/packager/mpd/test/data/dynamic_normal_mpd.txt +++ b/packager/mpd/test/data/dynamic_normal_mpd.txt @@ -1,7 +1,7 @@ - +