diff --git a/packager/mpd/base/mpd_builder.cc b/packager/mpd/base/mpd_builder.cc index 737721eadc..18c4991b71 100644 --- a/packager/mpd/base/mpd_builder.cc +++ b/packager/mpd/base/mpd_builder.cc @@ -526,6 +526,23 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) { 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.video_info_size() > 0) { + const MediaInfo::VideoInfo& video_info = media_info.video_info(0); + DCHECK(video_info.has_width()); + DCHECK(video_info.has_height()); + video_widths_.insert(video_info.width()); + video_heights_.insert(video_info.height()); + + if (video_info.has_time_scale() && video_info.has_frame_duration()) { + video_frame_rates_[static_cast(video_info.time_scale()) / + video_info.frame_duration()] = + base::IntToString(video_info.time_scale()) + "/" + + base::IntToString(video_info.frame_duration()); + } + } + representations_.push_back(representation.get()); return representation.release(); } @@ -553,7 +570,7 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { for (; representation_it != representations_.end(); ++representation_it) { xml::ScopedXmlPtr::type child((*representation_it)->GetXml()); - if (!child.get() || !adaptation_set.AddChild(child.Pass())) + if (!child || !adaptation_set.AddChild(child.Pass())) return xml::ScopedXmlPtr::type(); } @@ -561,6 +578,26 @@ xml::ScopedXmlPtr::type AdaptationSet::GetXml() { if (!lang_.empty() && lang_ != "und") { adaptation_set.SetStringAttribute("lang", LanguageToShortestForm(lang_)); } + + // Note that std::{set,map} are ordered, so the last element is the max value. + if (video_widths_.size() == 1) { + adaptation_set.SetIntegerAttribute("width", *video_widths_.begin()); + } else if (video_widths_.size() > 1) { + adaptation_set.SetIntegerAttribute("maxWidth", *video_widths_.rbegin()); + } + if (video_heights_.size() == 1) { + adaptation_set.SetIntegerAttribute("height", *video_heights_.begin()); + } else if (video_heights_.size() > 1) { + adaptation_set.SetIntegerAttribute("maxHeight", *video_heights_.rbegin()); + } + + if (video_frame_rates_.size() == 1) { + adaptation_set.SetStringAttribute("frameRate", + video_frame_rates_.begin()->second); + } else if (video_frame_rates_.size() > 1) { + adaptation_set.SetStringAttribute("maxFrameRate", + video_frame_rates_.rbegin()->second); + } return adaptation_set.PassScopedPtr(); } diff --git a/packager/mpd/base/mpd_builder.h b/packager/mpd/base/mpd_builder.h index 3e8299c2dc..81bc912d2b 100644 --- a/packager/mpd/base/mpd_builder.h +++ b/packager/mpd/base/mpd_builder.h @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include "packager/base/atomic_sequence_num.h" @@ -204,6 +206,20 @@ class AdaptationSet { const std::string lang_; const MpdOptions& mpd_options_; + // Video widths and heights of Representations. Note that this is a set; if + // there is only 1 resolution, then @width & @height should be set, otherwise + // @maxWidth & @maxHeight should be set for DASH IOP. + std::set video_widths_; + std::set video_heights_; + + // Video representations' frame rates. + // The frame rate notation for MPD is / (where the + // denominator is optional). This means the frame rate could be non-whole + // rational value, therefore the key is of type double. + // Value is / in string form. + // So, key == CalculatedValue(value) + std::map video_frame_rates_; + DISALLOW_COPY_AND_ASSIGN(AdaptationSet); }; diff --git a/packager/mpd/base/mpd_builder_unittest.cc b/packager/mpd/base/mpd_builder_unittest.cc index 14440c473f..e1e40dd665 100644 --- a/packager/mpd/base/mpd_builder_unittest.cc +++ b/packager/mpd/base/mpd_builder_unittest.cc @@ -60,15 +60,22 @@ void CheckIdEqual(uint32_t expected_id, T* node) { ASSERT_NO_FATAL_FAILURE(ExpectXmlElementIdEqual(node_xml.get(), expected_id)); } -void ExpectAttributeHasString(base::StringPiece attribute, - base::StringPiece expected_value, - xmlNodePtr node) { +void ExpectAttributeEqString(base::StringPiece attribute, + base::StringPiece expected_value, + xmlNodePtr node) { xml::ScopedXmlPtr::type attribute_xml_str( xmlGetProp(node, BAD_CAST attribute.data())); ASSERT_TRUE(attribute_xml_str); EXPECT_STREQ(expected_value.data(), reinterpret_cast(attribute_xml_str.get())); } + +// |attribute| should not be set in |node|. +void ExpectAttributeNotSet(base::StringPiece attribute, xmlNodePtr node) { + xml::ScopedXmlPtr::type attribute_xml_str( + xmlGetProp(node, BAD_CAST attribute.data())); + ASSERT_FALSE(attribute_xml_str); +} } // namespace template @@ -200,7 +207,8 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest { "availabilityStartTime=\"2011-12-25T12:30:00\" minBufferTime=\"PT2S\" " "type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n" " \n" - " \n" + " \n" " \n" @@ -282,7 +290,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest { "type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" " "timeShiftBufferDepth=\"PT%dS\">\n" " \n" - " \n" + " \n" " \n" @@ -366,29 +375,221 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) { Representation representation( ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId); EXPECT_TRUE(representation.Init()); - xml::ScopedXmlPtr::type node_xml(representation.GetXml()); EXPECT_NO_FATAL_FAILURE( - ExpectAttributeHasString("codecs", "avc1", node_xml.get())); + ExpectAttributeEqString("codecs", "avc1", node_xml.get())); EXPECT_NO_FATAL_FAILURE( - ExpectAttributeHasString("width", "1280", node_xml.get())); + ExpectAttributeEqString("width", "1280", node_xml.get())); EXPECT_NO_FATAL_FAILURE( - ExpectAttributeHasString("height", "720", node_xml.get())); + ExpectAttributeEqString("height", "720", node_xml.get())); EXPECT_NO_FATAL_FAILURE( - ExpectAttributeHasString("sar", "1:1", node_xml.get())); + ExpectAttributeEqString("sar", "1:1", node_xml.get())); EXPECT_NO_FATAL_FAILURE( - ExpectAttributeHasString("frameRate", "10/10", node_xml.get())); + ExpectAttributeEqString("frameRate", "10/10", node_xml.get())); } TEST_F(CommonMpdBuilderTest, CheckAdaptationSetId) { base::AtomicSequenceNumber sequence_counter; const uint32_t kAdaptationSetId = 42; - AdaptationSet adaptation_set( kAdaptationSetId, "", MpdOptions(), &sequence_counter); ASSERT_NO_FATAL_FAILURE(CheckIdEqual(kAdaptationSetId, &adaptation_set)); } +// Verify that if all video Representations in an AdaptationSet have the same +// frame rate, AdaptationSet also has a frameRate attribute. +TEST_F(CommonMpdBuilderTest, AdapatationSetFrameRate) { + const char kVideoMediaInfo1[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 3\n" + "}\n" + "container_type: 1\n"; + const char kVideoMediaInfo2[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 3\n" + "}\n" + "container_type: 1\n"; + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::ScopedXmlPtr::type adaptation_set_xml( + video_adaptation_set->GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("frameRate", "10/3", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("maxFrameRate", adaptation_set_xml.get())); +} + +// Verify that if there are videos with different frame rates, the maxFrameRate +// is set. +TEST_F(CommonMpdBuilderTest, AdapatationSetMaxFrameRate) { + // 30fps video. + const char kVideoMediaInfo30fps[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + "}\n" + "container_type: 1\n"; + const char kVideoMediaInfo15fps[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 3000\n" + " frame_duration: 200\n" + "}\n" + "container_type: 1\n"; + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo30fps))); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo15fps))); + + xml::ScopedXmlPtr::type adaptation_set_xml( + video_adaptation_set->GetXml()); + EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("maxFrameRate", "3000/100", + adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("frameRate", adaptation_set_xml.get())); +} + +// Catch the case where it ends up wrong if integer division is used to check +// the frame rate. +// IOW, A/B != C/D but when using integer division A/B == C/D. +// SO, maxFrameRate should be set instead of frameRate. +TEST_F(CommonMpdBuilderTest, + AdapatationSetMaxFrameRateIntegerDivisionEdgeCase) { + // 11/3 != 10/3 but IntegerDiv(11,3) == IntegerDiv(10,3). + const char kVideoMediaInfo1[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 11\n" + " frame_duration: 3\n" + "}\n" + "container_type: 1\n"; + const char kVideoMediaInfo2[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 720\n" + " height: 480\n" + " time_scale: 10\n" + " frame_duration: 3\n" + "}\n" + "container_type: 1\n"; + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::ScopedXmlPtr::type adaptation_set_xml( + video_adaptation_set->GetXml()); + EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("maxFrameRate", "11/3", + adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("frameRate", adaptation_set_xml.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) { + // Both 720p. + const char kVideoMediaInfo1[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1280\n" + " height: 720\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + "}\n" + "container_type: 1\n"; + const char kVideoMediaInfo2[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1280\n" + " height: 720\n" + " time_scale: 3000\n" + " frame_duration: 200\n" + "}\n" + "container_type: 1\n"; + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1))); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo2))); + + xml::ScopedXmlPtr::type adaptation_set_xml( + video_adaptation_set->GetXml()); + ASSERT_NO_FATAL_FAILURE( + ExpectAttributeEqString("width", "1280", adaptation_set_xml.get())); + ASSERT_NO_FATAL_FAILURE( + ExpectAttributeEqString("height", "720", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("maxWidth", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("maxHeight", adaptation_set_xml.get())); +} + +// Verify that the maxWidth and maxHeight attribute are set if there are +// multiple video resolutions. +TEST_F(StaticMpdBuilderTest, AdapatationSetMaxWidthAndMaxHeight) { + const char kVideoMediaInfo1080p[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1920\n" + " height: 1080\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + "}\n" + "container_type: 1\n"; + const char kVideoMediaInfo720p[] = + "video_info {\n" + " codec: \"avc1\"\n" + " width: 1280\n" + " height: 720\n" + " time_scale: 3000\n" + " frame_duration: 100\n" + "}\n" + "container_type: 1\n"; + AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet(""); + ASSERT_TRUE(video_adaptation_set); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo1080p))); + ASSERT_TRUE(video_adaptation_set->AddRepresentation( + ConvertToMediaInfo(kVideoMediaInfo720p))); + + xml::ScopedXmlPtr::type adaptation_set_xml( + video_adaptation_set->GetXml()); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("maxWidth", "1920", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeEqString("maxHeight", "1080", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("width", adaptation_set_xml.get())); + EXPECT_NO_FATAL_FAILURE( + ExpectAttributeNotSet("height", adaptation_set_xml.get())); +} + TEST_F(CommonMpdBuilderTest, CheckRepresentationId) { const MediaInfo video_media_info = GetTestMediaInfo(kFileNameVideoMediaInfo1); const uint32_t kRepresentationId = 1; diff --git a/packager/mpd/base/xml/xml_node.h b/packager/mpd/base/xml/xml_node.h index acc04aba7d..cce2310d13 100644 --- a/packager/mpd/base/xml/xml_node.h +++ b/packager/mpd/base/xml/xml_node.h @@ -47,7 +47,7 @@ class XmlNode { void SetStringAttribute(const char* attribute_name, const std::string& attribute); - /// Sets an interger attribute. + /// Sets an integer attribute. /// @param attribute_name The name (lhs) of the attribute. /// @param number The value (rhs) of the attribute. void SetIntegerAttribute(const char* attribute_name, uint64_t number); diff --git a/packager/mpd/test/data/audio_media_info1_video_media_info1_expected_mpd_output.txt b/packager/mpd/test/data/audio_media_info1_video_media_info1_expected_mpd_output.txt index 7b6d668e0a..d47680bd82 100644 --- a/packager/mpd/test/data/audio_media_info1_video_media_info1_expected_mpd_output.txt +++ b/packager/mpd/test/data/audio_media_info1_video_media_info1_expected_mpd_output.txt @@ -1,7 +1,7 @@ - + test_output_file_name1.mp4 diff --git a/packager/mpd/test/data/dynamic_normal_mpd.txt b/packager/mpd/test/data/dynamic_normal_mpd.txt index 51ab38e477..c369ede3a0 100644 --- a/packager/mpd/test/data/dynamic_normal_mpd.txt +++ b/packager/mpd/test/data/dynamic_normal_mpd.txt @@ -1,7 +1,7 @@ - + diff --git a/packager/mpd/test/data/language_audio_media_info_expected_output.txt b/packager/mpd/test/data/language_audio_media_info_expected_output.txt index 604adc2e4b..bcd7fc6b01 100644 --- a/packager/mpd/test/data/language_audio_media_info_expected_output.txt +++ b/packager/mpd/test/data/language_audio_media_info_expected_output.txt @@ -1,7 +1,7 @@ - + test_output_file_name1.mp4 diff --git a/packager/mpd/test/data/video_media_info1_expected_mpd_output.txt b/packager/mpd/test/data/video_media_info1_expected_mpd_output.txt index 6c93d8ce77..f6e69b444f 100644 --- a/packager/mpd/test/data/video_media_info1_expected_mpd_output.txt +++ b/packager/mpd/test/data/video_media_info1_expected_mpd_output.txt @@ -1,7 +1,7 @@ - + test_output_file_name1.mp4 diff --git a/packager/mpd/test/data/video_media_info1and2_expected_mpd_output.txt b/packager/mpd/test/data/video_media_info1and2_expected_mpd_output.txt index 7eb5249aa7..8a9e9edee1 100644 --- a/packager/mpd/test/data/video_media_info1and2_expected_mpd_output.txt +++ b/packager/mpd/test/data/video_media_info1and2_expected_mpd_output.txt @@ -1,7 +1,7 @@ - + test_output_file_name1.mp4