max{Width,Height,FrameRate} for AdaptationSet

- DASH IOP requires AdaptationSets with video Representations to have:
  @maxWidth (or @width if all the Representations have the same width).
  @maxHeight (or @height if all the Representations have the same width).
  @maxFrameRate (or @frameRate if all the Representations have the same
  frame rate).

Change-Id: I1247b7461237255aeb70b7fb40f78d4439f9c529
This commit is contained in:
Rintaro Kuroiwa 2015-06-16 18:37:23 -07:00
parent d685edef62
commit a02e3b60df
9 changed files with 273 additions and 19 deletions

View File

@ -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<double>(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<xmlNode>::type AdaptationSet::GetXml() {
for (; representation_it != representations_.end(); ++representation_it) {
xml::ScopedXmlPtr<xmlNode>::type child((*representation_it)->GetXml());
if (!child.get() || !adaptation_set.AddChild(child.Pass()))
if (!child || !adaptation_set.AddChild(child.Pass()))
return xml::ScopedXmlPtr<xmlNode>::type();
}
@ -561,6 +578,26 @@ xml::ScopedXmlPtr<xmlNode>::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();
}

View File

@ -17,6 +17,8 @@
#include <stdint.h>
#include <list>
#include <map>
#include <set>
#include <string>
#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<uint32_t> video_widths_;
std::set<uint32_t> video_heights_;
// Video representations' frame rates.
// The frame rate notation for MPD is <integer>/<integer> (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 <integer>/<integer> in string form.
// So, key == CalculatedValue(value)
std::map<double, std::string> video_frame_rates_;
DISALLOW_COPY_AND_ASSIGN(AdaptationSet);
};

View File

@ -60,7 +60,7 @@ void CheckIdEqual(uint32_t expected_id, T* node) {
ASSERT_NO_FATAL_FAILURE(ExpectXmlElementIdEqual(node_xml.get(), expected_id));
}
void ExpectAttributeHasString(base::StringPiece attribute,
void ExpectAttributeEqString(base::StringPiece attribute,
base::StringPiece expected_value,
xmlNodePtr node) {
xml::ScopedXmlPtr<xmlChar>::type attribute_xml_str(
@ -69,6 +69,13 @@ void ExpectAttributeHasString(base::StringPiece attribute,
EXPECT_STREQ(expected_value.data(),
reinterpret_cast<const char*>(attribute_xml_str.get()));
}
// |attribute| should not be set in |node|.
void ExpectAttributeNotSet(base::StringPiece attribute, xmlNodePtr node) {
xml::ScopedXmlPtr<xmlChar>::type attribute_xml_str(
xmlGetProp(node, BAD_CAST attribute.data()));
ASSERT_FALSE(attribute_xml_str);
}
} // namespace
template <MpdBuilder::MpdType type>
@ -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"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/5\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/5\" sar=\"1:1\">\n"
@ -282,7 +290,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
"type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" "
"timeShiftBufferDepth=\"PT%dS\">\n"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/2\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/2\" sar=\"1:1\">\n"
@ -366,29 +375,221 @@ TEST_F(CommonMpdBuilderTest, CheckVideoInfoReflectedInXml) {
Representation representation(
ConvertToMediaInfo(kTestMediaInfo), MpdOptions(), kAnyRepresentationId);
EXPECT_TRUE(representation.Init());
xml::ScopedXmlPtr<xmlNode>::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<xmlNode>::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<xmlNode>::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<xmlNode>::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<xmlNode>::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<xmlNode>::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;

View File

@ -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);

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period>
<AdaptationSet id="0">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1">
<Representation id="1" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/1" sar="1:1">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" availabilityStartTime="2011-12-25T12:30:00" minBufferTime="PT2S" type="dynamic" profiles="urn:mpeg:dash:profile:isoff-live:2011">
<Period start="PT0S">
<AdaptationSet id="0">
<AdaptationSet id="0" width="720" height="480" frameRate="10/5">
<Representation id="0" bandwidth="102400" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/5" sar="1:1">
<SegmentTemplate timescale="1000" initialization="init.mp4" media="$Time$.mp4">
<SegmentTimeline>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period>
<AdaptationSet id="0">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1">
<Representation id="3" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/1" sar="1:1">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period>
<AdaptationSet id="0">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1">
<Representation id="0" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/1" sar="1:1">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT10.5S">
<Period>
<AdaptationSet id="0">
<AdaptationSet id="0" maxWidth="720" maxHeight="480" maxFrameRate="10/1">
<Representation id="0" bandwidth="7620" codecs="avc1.010101" mimeType="video/mp4" width="720" height="480" frameRate="10/1" sar="1:1">
<BaseURL>test_output_file_name1.mp4</BaseURL>
<SegmentBase indexRange="121-221" timescale="1000">