diff --git a/packager/app/test/testdata/bear-640x360-av-cenc-ad_cues-golden.mpd b/packager/app/test/testdata/bear-640x360-av-cenc-ad_cues-golden.mpd index a621a88abf..be58be5815 100644 --- a/packager/app/test/testdata/bear-640x360-av-cenc-ad_cues-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-cenc-ad_cues-golden.mpd @@ -1,7 +1,7 @@ - + @@ -28,7 +28,7 @@ - + diff --git a/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd b/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd index 3c49a432bb..5d454a4045 100644 --- a/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd +++ b/packager/app/test/testdata/bear-640x360-av-live-static-ad_cues-golden.mpd @@ -1,7 +1,7 @@ - + @@ -22,7 +22,7 @@ - + diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index c7d2dc9947..d0a28ce9ce 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -6,6 +6,8 @@ #include "packager/mpd/base/mpd_builder.h" +#include + #include "packager/base/files/file_path.h" #include "packager/base/logging.h" #include "packager/base/strings/string_number_conversions.h" @@ -170,6 +172,21 @@ xmlDocPtr MpdBuilder::GenerateMpd() { return nullptr; } + // Prefer Period@duration to Period@start for static MPD with more than one + // periods. + if (mpd_options_.mpd_type == MpdType::kStatic && periods_.size() > 1) { + // The duration of every period is determined by its start_time and next + // period start_time. The code below traverses |periods_| backwards. + double next_period_start_time = GetStaticMpdDuration(); + std::for_each( + periods_.rbegin(), periods_.rend(), + [&next_period_start_time](const std::unique_ptr& period) { + period->set_duration_seconds(next_period_start_time - + period->start_time_in_seconds()); + next_period_start_time = period->start_time_in_seconds(); + }); + } + for (const auto& period : periods_) { xml::scoped_xml_ptr period_node(period->GetXml()); if (!period_node || !mpd.AddChild(std::move(period_node))) @@ -242,9 +259,8 @@ void MpdBuilder::AddStaticMpdInfo(XmlNode* mpd_node) { static const char kStaticMpdType[] = "static"; mpd_node->SetStringAttribute("type", kStaticMpdType); - mpd_node->SetStringAttribute( - "mediaPresentationDuration", - SecondsToXmlDuration(GetStaticMpdDuration(mpd_node))); + mpd_node->SetStringAttribute("mediaPresentationDuration", + SecondsToXmlDuration(GetStaticMpdDuration())); } void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { @@ -290,8 +306,7 @@ void MpdBuilder::AddDynamicMpdInfo(XmlNode* mpd_node) { mpd_options_.mpd_params.suggested_presentation_delay, mpd_node); } -float MpdBuilder::GetStaticMpdDuration(XmlNode* mpd_node) { - DCHECK(mpd_node); +float MpdBuilder::GetStaticMpdDuration() { DCHECK_EQ(MpdType::kStatic, mpd_options_.mpd_type); if (periods_.empty()) { diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 1cda19e9c6..fa301d424e 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -100,7 +100,7 @@ class MpdBuilder { // Same as AddStaticMpdInfo() but for 'dynamic' MPDs. void AddDynamicMpdInfo(xml::XmlNode* mpd_node); - float GetStaticMpdDuration(xml::XmlNode* mpd_node); + float GetStaticMpdDuration(); // Set MPD attributes for dynamic profile MPD. Uses non-zero |mpd_options_| as // well as various calculations to set attributes for the MPD. diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index f4309a8c32..35da48f45e 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 "packager/mpd/base/adaptation_set.h" @@ -12,6 +13,8 @@ #include "packager/mpd/test/mpd_builder_test_helper.h" #include "packager/version/version.h" +using ::testing::HasSubstr; + namespace shaka { namespace { @@ -150,7 +153,7 @@ TEST_F(OnDemandMpdBuilderTest, MediaInfoMissingBandwidth) { ASSERT_FALSE(mpd_.ToString(&mpd_doc)); } -TEST_F(LiveMpdBuilderTest, MultiplePeriodTest) { +TEST_F(OnDemandMpdBuilderTest, MultiplePeriodTest) { const double kPeriodStartTimeSeconds = 1.0; Period* period = mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds); ASSERT_TRUE(period); @@ -170,6 +173,40 @@ TEST_F(LiveMpdBuilderTest, MultiplePeriodTest) { ASSERT_EQ(kPeriodStartTimeSeconds3, period3->start_time_in_seconds()); } +TEST_F(OnDemandMpdBuilderTest, MultiplePeriodCheckXmlTest) { + const double kPeriodStartTimeSeconds = 0.0; + const double kPeriodStartTimeSeconds2 = 3.1; + const double kPeriodStartTimeSeconds3 = 8.0; + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds); + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds2); + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds3); + + std::string mpd_doc; + ASSERT_TRUE(mpd_.ToString(&mpd_doc)); + EXPECT_THAT(mpd_doc, + HasSubstr(" \n" + " \n" + // There are no Representations so MPD duration is 0, + // which results in a negative duration for the last + // period. This would not happen in practice. + " \n")); +} + +TEST_F(LiveMpdBuilderTest, MultiplePeriodCheckXmlTest) { + const double kPeriodStartTimeSeconds = 0.0; + const double kPeriodStartTimeSeconds2 = 3.1; + const double kPeriodStartTimeSeconds3 = 8.0; + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds); + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds2); + mpd_.GetOrCreatePeriod(kPeriodStartTimeSeconds3); + + std::string mpd_doc; + ASSERT_TRUE(mpd_.ToString(&mpd_doc)); + EXPECT_THAT(mpd_doc, HasSubstr(" \n" + " \n" + " \n")); +} + // Check whether the attributes are set correctly for dynamic element. // This test must use ASSERT_EQ for comparison because XmlEqual() cannot // handle namespaces correctly yet. diff --git a/packager/mpd/base/period.cc b/packager/mpd/base/period.cc index 631b679c45..1d40afeb8e 100644 --- a/packager/mpd/base/period.cc +++ b/packager/mpd/base/period.cc @@ -94,7 +94,7 @@ AdaptationSet* Period::GetOrCreateAdaptationSet( return adaptation_set_ptr; } -xml::scoped_xml_ptr Period::GetXml() { +xml::scoped_xml_ptr Period::GetXml() const { xml::XmlNode period("Period"); // Required for 'dynamic' MPDs. @@ -106,8 +106,11 @@ xml::scoped_xml_ptr Period::GetXml() { return nullptr; } - if (mpd_options_.mpd_type == MpdType::kDynamic || - start_time_in_seconds_ != 0) { + if (duration_seconds_ != 0) { + period.SetStringAttribute("duration", + SecondsToXmlDuration(duration_seconds_)); + } else if (mpd_options_.mpd_type == MpdType::kDynamic || + start_time_in_seconds_ != 0) { period.SetStringAttribute("start", SecondsToXmlDuration(start_time_in_seconds_)); } diff --git a/packager/mpd/base/period.h b/packager/mpd/base/period.h index 4067902075..04bea93a95 100644 --- a/packager/mpd/base/period.h +++ b/packager/mpd/base/period.h @@ -48,7 +48,7 @@ class Period { /// Generates xml element with its child AdaptationSet elements. /// @return On success returns a non-NULL scoped_xml_ptr. Otherwise returns a /// NULL scoped_xml_ptr. - xml::scoped_xml_ptr GetXml(); + xml::scoped_xml_ptr GetXml() const; /// @return The list of AdaptationSets in this Period. const std::list GetAdaptationSets() const; @@ -56,6 +56,11 @@ class Period { /// @return The start time of this Period. double start_time_in_seconds() const { return start_time_in_seconds_; } + /// Set period duration. + void set_duration_seconds(double duration_seconds) { + duration_seconds_ = duration_seconds; + } + protected: /// @param period_id is an ID number for this Period. /// @param start_time_in_seconds is the start time for this Period. @@ -102,6 +107,7 @@ class Period { const uint32_t id_; const double start_time_in_seconds_; + double duration_seconds_ = 0; const MpdOptions& mpd_options_; base::AtomicSequenceNumber* const adaptation_set_counter_; base::AtomicSequenceNumber* const representation_counter_; diff --git a/packager/mpd/base/period_unittest.cc b/packager/mpd/base/period_unittest.cc index 8d20ca9b9d..649f6fbf14 100644 --- a/packager/mpd/base/period_unittest.cc +++ b/packager/mpd/base/period_unittest.cc @@ -177,6 +177,38 @@ TEST_P(PeriodTest, DynamicMpdGetXml) { EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml)); } +TEST_P(PeriodTest, SetDurationAndGetXml) { + const char kVideoMediaInfo[] = + "video_info {\n" + " codec: 'avc1'\n" + " width: 1280\n" + " height: 720\n" + " time_scale: 10\n" + " frame_duration: 10\n" + " pixel_width: 1\n" + " pixel_height: 1\n" + "}\n" + "container_type: 1\n"; + + EXPECT_CALL(testable_period_, NewAdaptationSet(_, _, _, _)) + .WillOnce(Return(ByMove(std::move(default_adaptation_set_)))); + + ASSERT_EQ(default_adaptation_set_ptr_, + testable_period_.GetOrCreateAdaptationSet( + ConvertToMediaInfo(kVideoMediaInfo), + content_protection_in_adaptation_set_)); + + testable_period_.set_duration_seconds(100.234); + + const char kExpectedXml[] = + "" + // ContentType and Representation elements are populated after + // Representation::Init() is called. + " " + ""; + EXPECT_THAT(testable_period_.GetXml().get(), XmlNodeEqual(kExpectedXml)); +} + // Verify ForceSetSegmentAlignment is called. TEST_P(PeriodTest, Text) { const char kTextMediaInfo[] = diff --git a/packager/mpd/base/representation.cc b/packager/mpd/base/representation.cc index 0d5957a9a6..edd04ab4a3 100644 --- a/packager/mpd/base/representation.cc +++ b/packager/mpd/base/representation.cc @@ -315,7 +315,7 @@ float Representation::GetDurationSeconds() const { return media_info_.media_duration_seconds(); } -bool Representation::HasRequiredMediaInfoFields() { +bool Representation::HasRequiredMediaInfoFields() const { if (HasVODOnlyFields(media_info_) && HasLiveOnlyFields(media_info_)) { LOG(ERROR) << "MediaInfo cannot have both VOD and Live fields."; return false; diff --git a/packager/mpd/base/representation.h b/packager/mpd/base/representation.h index 81eddb309d..bc632ecc3b 100644 --- a/packager/mpd/base/representation.h +++ b/packager/mpd/base/representation.h @@ -172,7 +172,7 @@ class Representation { // Returns true if |media_info_| has required fields to generate a valid // Representation. Otherwise returns false. - bool HasRequiredMediaInfoFields(); + bool HasRequiredMediaInfoFields() const; // Return false if the segment should be considered a new segment. True if the // segment is contiguous.