diff --git a/packager/mpd/base/adaptation_set.cc b/packager/mpd/base/adaptation_set.cc index 5f8d067bb9..65b941ce63 100644 --- a/packager/mpd/base/adaptation_set.cc +++ b/packager/mpd/base/adaptation_set.cc @@ -196,8 +196,9 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) { return NULL; } UpdateFromMediaInfo(media_info); - representations_.push_back(std::move(new_representation)); - return representations_.back().get(); + Representation* representation_ptr = new_representation.get(); + representation_map_[representation_ptr->id()] = std::move(new_representation); + return representation_ptr; } Representation* AdaptationSet::CopyRepresentationWithTimeOffset( @@ -211,8 +212,9 @@ Representation* AdaptationSet::CopyRepresentationWithTimeOffset( representation, presentation_time_offset, std::move(listener))); UpdateFromMediaInfo(new_representation->GetMediaInfo()); - representations_.push_back(std::move(new_representation)); - return representations_.back().get(); + Representation* representation_ptr = new_representation.get(); + representation_map_[representation_ptr->id()] = std::move(new_representation); + return representation_ptr; } void AdaptationSet::AddContentProtectionElement( @@ -320,8 +322,8 @@ xml::scoped_xml_ptr AdaptationSet::GetXml() { for (AdaptationSet::Role role : roles_) adaptation_set.AddRoleElement("urn:mpeg:dash:role:2011", RoleToText(role)); - for (const std::unique_ptr& representation : - representations_) { + for (const auto& representation_pair : representation_map_) { + const auto& representation = representation_pair.second; if (suppress_representation_width) representation->SuppressOnce(Representation::kSuppressWidth); if (suppress_representation_height) @@ -376,9 +378,8 @@ void AdaptationSet::AddTrickPlayReferenceId(uint32_t id) { const std::list AdaptationSet::GetRepresentations() const { std::list representations; - for (const std::unique_ptr& representation : - representations_) { - representations.push_back(representation.get()); + for (const auto& representation_pair : representation_map_) { + representations.push_back(representation_pair.second.get()); } return representations; } @@ -450,7 +451,7 @@ void AdaptationSet::CheckLiveSegmentAlignment(uint32_t 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()) + if (representation_segment_start_times_.size() != representation_map_.size()) return; DCHECK(!representation_start_times.empty()); diff --git a/packager/mpd/base/adaptation_set.h b/packager/mpd/base/adaptation_set.h index d177e9a90a..ea14fba950 100644 --- a/packager/mpd/base/adaptation_set.h +++ b/packager/mpd/base/adaptation_set.h @@ -229,7 +229,9 @@ class AdaptationSet { void RecordFrameRate(uint32_t frame_duration, uint32_t timescale); std::list content_protection_elements_; - std::list> representations_; + // representation_id => Representation map. It also keeps the representations_ + // sorted by default. + std::map> representation_map_; base::AtomicSequenceNumber* const representation_counter_; diff --git a/packager/mpd/base/adaptation_set_unittest.cc b/packager/mpd/base/adaptation_set_unittest.cc index 1d8b59c102..bce79e37be 100644 --- a/packager/mpd/base/adaptation_set_unittest.cc +++ b/packager/mpd/base/adaptation_set_unittest.cc @@ -15,8 +15,8 @@ #include "packager/mpd/test/mpd_builder_test_helper.h" #include "packager/mpd/test/xml_compare.h" +using ::testing::ElementsAre; using ::testing::Not; -using ::testing::UnorderedElementsAre; namespace shaka { @@ -590,7 +590,7 @@ TEST_F(AdaptationSetTest, BubbleUpAttributesToAdaptationSet) { } TEST_F(AdaptationSetTest, GetRepresentations) { - const char k480pMediaInfo[] = + const char kMediaInfo1[] = "video_info {\n" " codec: 'avc1'\n" " width: 720\n" @@ -601,7 +601,7 @@ TEST_F(AdaptationSetTest, GetRepresentations) { " pixel_height: 9\n" "}\n" "container_type: 1\n"; - const char k360pMediaInfo[] = + const char kMediaInfo2[] = "video_info {\n" " codec: 'avc1'\n" " width: 640\n" @@ -615,15 +615,29 @@ TEST_F(AdaptationSetTest, GetRepresentations) { auto adaptation_set = CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); - Representation* representation_480p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k480pMediaInfo)); + Representation* representation1 = + adaptation_set->AddRepresentation(ConvertToMediaInfo(kMediaInfo1)); EXPECT_THAT(adaptation_set->GetRepresentations(), - UnorderedElementsAre(representation_480p)); + ElementsAre(representation1)); - Representation* representation_360p = - adaptation_set->AddRepresentation(ConvertToMediaInfo(k360pMediaInfo)); + Representation* representation2 = + adaptation_set->AddRepresentation(ConvertToMediaInfo(kMediaInfo2)); EXPECT_THAT(adaptation_set->GetRepresentations(), - UnorderedElementsAre(representation_360p, representation_480p)); + ElementsAre(representation1, representation2)); + + auto new_adaptation_set = + CreateAdaptationSet(kAnyAdaptationSetId, kNoLanguage); + const uint64_t kPresentationTimeOffset = 80; + Representation* new_representation2 = + new_adaptation_set->CopyRepresentationWithTimeOffset( + *representation2, kPresentationTimeOffset); + Representation* new_representation1 = + new_adaptation_set->CopyRepresentationWithTimeOffset( + *representation1, kPresentationTimeOffset); + + EXPECT_THAT(new_adaptation_set->GetRepresentations(), + // Elements are ordered by id(). + ElementsAre(new_representation1, new_representation2)); } // Verify that subsegmentAlignment is set to true if all the Representations' diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index 21b48e1c9e..631b679c45 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -88,9 +88,10 @@ AdaptationSet* Period::GetOrCreateAdaptationSet( } } } - adaptation_sets.push_back(new_adaptation_set.get()); - adaptation_sets_.push_back(std::move(new_adaptation_set)); - return adaptation_sets_.back().get(); + AdaptationSet* adaptation_set_ptr = new_adaptation_set.get(); + adaptation_sets.push_back(adaptation_set_ptr); + adaptation_set_map_[adaptation_set_ptr->id()] = std::move(new_adaptation_set); + return adaptation_set_ptr; } xml::scoped_xml_ptr Period::GetXml() { @@ -99,8 +100,8 @@ xml::scoped_xml_ptr Period::GetXml() { // Required for 'dynamic' MPDs. period.SetId(id_); // Iterate thru AdaptationSets and add them to one big Period element. - for (const auto& adaptation_set : adaptation_sets_) { - xml::scoped_xml_ptr child(adaptation_set->GetXml()); + for (const auto& adaptation_set_pair : adaptation_set_map_) { + xml::scoped_xml_ptr child(adaptation_set_pair.second->GetXml()); if (!child || !period.AddChild(std::move(child))) return nullptr; } @@ -115,9 +116,8 @@ xml::scoped_xml_ptr Period::GetXml() { const std::list Period::GetAdaptationSets() const { std::list adaptation_sets; - for (const std::unique_ptr& adaptation_set : - adaptation_sets_) { - adaptation_sets.push_back(adaptation_set.get()); + for (const auto& adaptation_set_pair : adaptation_set_map_) { + adaptation_sets.push_back(adaptation_set_pair.second.get()); } return adaptation_sets; } diff --git a/packager/mpd/base/period.h b/packager/mpd/base/period.h index 7a745c476d..4067902075 100644 --- a/packager/mpd/base/period.h +++ b/packager/mpd/base/period.h @@ -105,8 +105,9 @@ class Period { const MpdOptions& mpd_options_; base::AtomicSequenceNumber* const adaptation_set_counter_; base::AtomicSequenceNumber* const representation_counter_; - // The list of AdaptationSets in this Period. - std::list> adaptation_sets_; + // adaptation_id => Adaptation map. It also keeps the adaptation_sets_ sorted + // by default. + std::map> adaptation_set_map_; // AdaptationSets grouped by a specific adaptation set grouping key. // AdaptationSets with the same key contain identical parameters except // ContentProtection parameters. A single AdaptationSet would be created diff --git a/packager/mpd/base/period_unittest.cc b/packager/mpd/base/period_unittest.cc index b37289a384..8d20ca9b9d 100644 --- a/packager/mpd/base/period_unittest.cc +++ b/packager/mpd/base/period_unittest.cc @@ -21,7 +21,6 @@ using ::testing::Eq; using ::testing::InSequence; using ::testing::Return; using ::testing::StrictMock; -using ::testing::UnorderedElementsAre; namespace shaka { namespace { @@ -788,7 +787,7 @@ TEST_P(PeriodTest, SplitAdaptationSetsByLanguageAndCodec) { } TEST_P(PeriodTest, GetAdaptationSets) { - const char kAacEnglishAudioContent[] = + const char kContent1[] = "audio_info {\n" " codec: 'mp4a.40.2'\n" " sampling_frequency: 44100\n" @@ -799,7 +798,7 @@ TEST_P(PeriodTest, GetAdaptationSets) { "reference_time_scale: 50\n" "container_type: CONTAINER_MP4\n" "media_duration_seconds: 10.5\n"; - const char kAacGermanAudioContent[] = + const char kContent2[] = "audio_info {\n" " codec: 'mp4a.40.2'\n" " sampling_frequency: 44100\n" @@ -811,33 +810,75 @@ TEST_P(PeriodTest, GetAdaptationSets) { "container_type: CONTAINER_MP4\n" "media_duration_seconds: 10.5\n"; - std::unique_ptr> aac_eng_adaptation_set( + std::unique_ptr> adaptation_set_1( new StrictMock(1)); - auto* aac_eng_adaptation_set_ptr = aac_eng_adaptation_set.get(); - std::unique_ptr> aac_ger_adaptation_set( + auto* adaptation_set_1_ptr = adaptation_set_1.get(); + std::unique_ptr> adaptation_set_2( new StrictMock(2)); - auto* aac_ger_adaptation_set_ptr = aac_ger_adaptation_set.get(); + auto* adaptation_set_2_ptr = adaptation_set_2.get(); EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _)) - .WillOnce(Return(ByMove(std::move(aac_eng_adaptation_set)))) - .WillOnce(Return(ByMove(std::move(aac_ger_adaptation_set)))); + .WillOnce(Return(ByMove(std::move(adaptation_set_1)))) + .WillOnce(Return(ByMove(std::move(adaptation_set_2)))); - ASSERT_EQ(aac_eng_adaptation_set_ptr, - testable_period_.GetOrCreateAdaptationSet( - ConvertToMediaInfo(kAacEnglishAudioContent), - content_protection_in_adaptation_set_)); + ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet( + ConvertToMediaInfo(kContent1), + content_protection_in_adaptation_set_)); EXPECT_THAT(testable_period_.GetAdaptationSets(), - UnorderedElementsAre(aac_eng_adaptation_set_ptr)); + ElementsAre(adaptation_set_1_ptr)); - ASSERT_EQ(aac_ger_adaptation_set_ptr, - testable_period_.GetOrCreateAdaptationSet( - ConvertToMediaInfo(kAacGermanAudioContent), - content_protection_in_adaptation_set_)); + ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet( + ConvertToMediaInfo(kContent2), + content_protection_in_adaptation_set_)); EXPECT_THAT(testable_period_.GetAdaptationSets(), - UnorderedElementsAre(aac_eng_adaptation_set_ptr, - aac_ger_adaptation_set_ptr)); + ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr)); } +TEST_P(PeriodTest, GetAdaptationSetsOrderedByAdaptationSetId) { + const char kContent1[] = + "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 kContent2[] = + "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> adaptation_set_1( + new StrictMock(1)); + auto* adaptation_set_1_ptr = adaptation_set_1.get(); + std::unique_ptr> adaptation_set_2( + new StrictMock(2)); + auto* adaptation_set_2_ptr = adaptation_set_2.get(); + + EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _)) + .WillOnce(Return(ByMove(std::move(adaptation_set_2)))) + .WillOnce(Return(ByMove(std::move(adaptation_set_1)))); + + ASSERT_EQ(adaptation_set_2_ptr, testable_period_.GetOrCreateAdaptationSet( + ConvertToMediaInfo(kContent2), + content_protection_in_adaptation_set_)); + ASSERT_EQ(adaptation_set_1_ptr, testable_period_.GetOrCreateAdaptationSet( + ConvertToMediaInfo(kContent1), + content_protection_in_adaptation_set_)); + EXPECT_THAT(testable_period_.GetAdaptationSets(), + // Elements are ordered by id(). + ElementsAre(adaptation_set_1_ptr, adaptation_set_2_ptr)); +} INSTANTIATE_TEST_CASE_P(ContentProtectionInAdaptationSet, PeriodTest, ::testing::Bool());