Use pixel_{height,width} to set AdaptationSet@par

- Pass pixel_height and pixel_width from VideoStreamInfo to MediaInfo.
- par can only be set if all the Representations have the same
  picture aspect ratio. That is the ratio of sar_x*width:sar_y*height
  should all be the same.
- If there is a Representation that does not have the sar attribute set,
  par is also not set.

Change-Id: Id34c95e4a99da9ce028a9f35737cfe5bca9e5697
This commit is contained in:
Rintaro Kuroiwa 2015-06-29 17:21:05 -07:00
parent ec21b04fab
commit 21e43966db
10 changed files with 271 additions and 25 deletions

View File

@ -86,6 +86,12 @@ void AddVideoInfo(const VideoStreamInfo* video_stream_info,
video_info->set_height(video_stream_info->height());
video_info->set_time_scale(video_stream_info->time_scale());
if (video_stream_info->pixel_width() > 0)
video_info->set_pixel_width(video_stream_info->pixel_width());
if (video_stream_info->pixel_height() > 0)
video_info->set_pixel_height(video_stream_info->pixel_height());
const std::vector<uint8_t>& extra_data = video_stream_info->extra_data();
if (!extra_data.empty()) {
video_info->set_decoder_config(&extra_data[0], extra_data.size());

View File

@ -35,6 +35,8 @@ struct VideoStreamInfoParameters {
std::string language;
uint16_t width;
uint16_t height;
uint32_t pixel_width;
uint32_t pixel_height;
uint8_t nalu_length_size;
std::vector<uint8_t> extra_data;
bool is_encrypted;
@ -63,10 +65,8 @@ scoped_refptr<StreamInfo> CreateVideoStreamInfo(
param.language,
param.width,
param.height,
// TODO(rkuroiwa): Once MedianInfo proto change that
// adds pizel_{width,height} lands, add tests.
0, // No pixel width.
0, // No pixel height.
param.pixel_width,
param.pixel_height,
0, // trick_play_rate
param.nalu_length_size,
vector_as_array(&param.extra_data),
@ -85,16 +85,18 @@ VideoStreamInfoParameters GetDefaultVideoStreamInfoParams() {
const char* kLanuageUndefined = "und";
const uint16_t kWidth = 720;
const uint16_t kHeight = 480;
const uint32_t kPixelWidth = 1;
const uint32_t kPixelHeight = 1;
const uint8_t kNaluLengthSize = 1;
const std::vector<uint8_t> kExtraData;
const bool kEncryptedFlag = false;
VideoStreamInfoParameters param = {
kTrackId, kTimeScale, kVideoStreamDuration, kH264Codec,
VideoStreamInfo::GetCodecString(
kCodecH264, kH264Profile, kH264CompatibleProfile, kH264Level),
kLanuageUndefined, kWidth, kHeight, kNaluLengthSize, kExtraData,
kEncryptedFlag};
VideoStreamInfo::GetCodecString(kCodecH264, kH264Profile,
kH264CompatibleProfile, kH264Level),
kLanuageUndefined, kWidth, kHeight, kPixelWidth, kPixelHeight,
kNaluLengthSize, kExtraData, kEncryptedFlag};
return param;
}
@ -224,10 +226,12 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal) {
const char kExpectedProtobufOutput[] =
"bandwidth: 7620\n"
"video_info {\n"
" codec: \"avc1.010101\"\n"
" codec: 'avc1.010101'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"init_range {\n"
" begin: 0\n"
@ -239,7 +243,7 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, UnencryptedStream_Normal) {
"}\n"
"reference_time_scale: 1000\n"
"container_type: 1\n"
"media_file_name: \"test_output_file_name.mp4\"\n"
"media_file_name: 'test_output_file_name.mp4'\n"
"media_duration_seconds: 10.5\n";
ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput));
}
@ -249,26 +253,26 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) {
scoped_refptr<StreamInfo> stream_info =
CreateVideoStreamInfo(GetDefaultVideoStreamInfoParams());
FireOnMediaStartWithDefaultMuxerOptions(*stream_info, kEnableEncryption);
OnMediaEndParameters media_end_param = GetDefaultOnMediaEndParams();
FireOnMediaEndWithParams(media_end_param);
const char kExpectedProtobufOutput[] =
"bandwidth: 7620\n"
"video_info {\n"
" codec: \"avc1.010101\"\n"
" codec: 'avc1.010101'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"content_protections {\n"
" scheme_id_uri: \"urn:mpeg:dash:mp4protection:2011\"\n"
" value: \"cenc\"\n"
" scheme_id_uri: 'urn:mpeg:dash:mp4protection:2011'\n"
" value: 'cenc'\n"
"}\n"
"content_protections {\n"
" scheme_id_uri: \"http://foo.com/bar\"\n"
" scheme_id_uri: 'http://foo.com/bar'\n"
"}\n"
"init_range {\n"
" begin: 0\n"
@ -280,7 +284,44 @@ TEST_F(VodMediaInfoDumpMuxerListenerTest, EncryptedStream_Normal) {
"}\n"
"reference_time_scale: 1000\n"
"container_type: 1\n"
"media_file_name: \"test_output_file_name.mp4\"\n"
"media_file_name: 'test_output_file_name.mp4'\n"
"media_duration_seconds: 10.5\n";
ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput));
}
// Verify that VideoStreamInfo with non-0 pixel_{width,height} is set in the
// generated MediaInfo.
TEST_F(VodMediaInfoDumpMuxerListenerTest, CheckPixelWidthAndHeightSet) {
VideoStreamInfoParameters params = GetDefaultVideoStreamInfoParams();
params.pixel_width = 8;
params.pixel_height = 9;
scoped_refptr<StreamInfo> stream_info = CreateVideoStreamInfo(params);
FireOnMediaStartWithDefaultMuxerOptions(*stream_info, !kEnableEncryption);
OnMediaEndParameters media_end_param = GetDefaultOnMediaEndParams();
FireOnMediaEndWithParams(media_end_param);
const char kExpectedProtobufOutput[] =
"bandwidth: 7620\n"
"video_info {\n"
" codec: 'avc1.010101'\n"
" width: 720\n"
" height: 480\n"
" time_scale: 10\n"
" pixel_width: 8\n"
" pixel_height: 9\n"
"}\n"
"init_range {\n"
" begin: 0\n"
" end: 120\n"
"}\n"
"index_range {\n"
" begin: 121\n"
" end: 221\n"
"}\n"
"reference_time_scale: 1000\n"
"container_type: 1\n"
"media_file_name: 'test_output_file_name.mp4'\n"
"media_duration_seconds: 10.5\n";
ASSERT_NO_FATAL_FAILURE(ExpectTempFileToEqual(kExpectedProtobufOutput));
}

View File

@ -212,6 +212,65 @@ bool HasRequiredVideoFields(const MediaInfo_VideoInfo& video_info) {
return true;
}
// Euclidean algorithm.
// gcd(a,0) = a
// gcd(a,b) = gcd(b, a % b)
uint32_t GreatestCommonDivisor(uint32_t a, uint32_t b) {
while (b != 0) {
const uint32_t new_b = a % b;
a = b;
b = new_b;
}
return a;
}
// Returns the picture aspect ratio string e.g. "16:9", "4:3".
std::string GetPictureAspectRatio(uint32_t width, uint32_t height,
uint32_t pixel_width, uint32_t pixel_height) {
const uint32_t scaled_width = pixel_width * width;
const uint32_t scaled_height = pixel_height * height;
const uint32_t gcd = GreatestCommonDivisor(scaled_width, scaled_height);
DCHECK_NE(gcd, 0u) << "GCD of width*pix_width (" << scaled_width
<< ") and height*pix_height (" << scaled_height
<< ") is 0.";
const uint32_t par_x = scaled_width / gcd;
const uint32_t par_y = scaled_height / gcd;
return base::IntToString(par_x) + ":" + base::IntToString(par_y);
}
// Adds an entry to picture_aspect_ratio if the size of picture_aspect_ratio is
// less than 2 and video_info has both pixel width and pixel height.
void AddPictureAspectRatio(
const MediaInfo::VideoInfo& video_info,
std::set<std::string>* picture_aspect_ratio) {
// If there are more than one entries in picture_aspect_ratio, the @par
// attribute cannot be set, so skip.
if (picture_aspect_ratio->size() > 1)
return;
if (video_info.width() == 0 || video_info.height() == 0 ||
video_info.pixel_width() == 0 || video_info.pixel_height() == 0) {
// If there is even one Representation without a @sar attribute, @par cannot
// be calculated.
// Just populate the set with at least 2 bogus strings so that further call
// to this function will bail out immediately.
picture_aspect_ratio->insert("bogus");
picture_aspect_ratio->insert("entries");
return;
}
const std::string par = GetPictureAspectRatio(
video_info.width(), video_info.height(),
video_info.pixel_width(), video_info.pixel_height());
DVLOG(1) << "Setting par as: " << par
<< " for video with width: " << video_info.width()
<< " height: " << video_info.height()
<< " pixel_width: " << video_info.pixel_width() << " pixel_height; "
<< video_info.pixel_height();
picture_aspect_ratio->insert(par);
}
// Spooky static initialization/cleanup of libxml.
class LibXmlInitializer {
public:
@ -536,6 +595,8 @@ Representation* AdaptationSet::AddRepresentation(const MediaInfo& media_info) {
base::IntToString(video_info.time_scale()) + "/" +
base::IntToString(video_info.frame_duration());
}
AddPictureAspectRatio(video_info, &picture_aspect_ratio_);
}
if (media_info.has_video_info()) {
@ -600,6 +661,9 @@ xml::ScopedXmlPtr<xmlNode>::type AdaptationSet::GetXml() {
adaptation_set.SetStringAttribute("maxFrameRate",
video_frame_rates_.rbegin()->second);
}
if (picture_aspect_ratio_.size() == 1)
adaptation_set.SetStringAttribute("par", *picture_aspect_ratio_.begin());
return adaptation_set.PassScopedPtr();
}

View File

@ -230,6 +230,16 @@ class AdaptationSet {
// Determined by examining the MediaInfo passed to AddRepresentation().
std::string content_type_;
// This does not have to be a set, it could be a list or vector because all we
// really care is whether there is more than one entry.
// Contains one entry if all the Representations have the same picture aspect
// ratio (@par attribute for AdaptationSet).
// There will be more than one entry if there are multiple picture aspect
// ratios.
// The @par attribute should only be set if there is exactly one entry
// in this set.
std::set<std::string> picture_aspect_ratio_;
DISALLOW_COPY_AND_ASSIGN(AdaptationSet);
};

View File

@ -209,7 +209,8 @@ class SegmentTemplateTest : public DynamicMpdBuilderTest {
"type=\"dynamic\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\">\n"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/5\" contentType=\"video\">\n"
" frameRate=\"10/5\" contentType=\"video\""
" par=\"3:2\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/5\" sar=\"1:1\">\n"
@ -292,7 +293,8 @@ class TimeShiftBufferDepthTest : public SegmentTemplateTest {
"timeShiftBufferDepth=\"PT%dS\">\n"
" <Period start=\"PT0S\">\n"
" <AdaptationSet id=\"0\" width=\"720\" height=\"480\""
" frameRate=\"10/2\" contentType=\"video\">\n"
" frameRate=\"10/2\" contentType=\"video\""
" par=\"3:2\">\n"
" <Representation id=\"0\" bandwidth=\"%" PRIu64 "\" "
"codecs=\"avc1.010101\" mimeType=\"video/mp4\" width=\"720\" "
"height=\"480\" frameRate=\"10/2\" sar=\"1:1\">\n"
@ -540,6 +542,125 @@ TEST_F(CommonMpdBuilderTest, AdapatationSetMaxFrameRate) {
ExpectAttributeNotSet("frameRate", adaptation_set_xml.get()));
}
// Verify that if the picture aspect ratio of all the Representations are the
// same, @par attribute is present.
TEST_F(CommonMpdBuilderTest, AdaptationSetParAllSame) {
const char k720pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
const char k1080pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1920\n"
" height: 1080\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
// Note that this has non-1 pixel width and height.
// Which makes the par 16:9.
const char k360pVideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 360\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 8\n"
" pixel_height: 9\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k720pVideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k1080pVideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k360pVideoInfo)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(ExpectAttributeEqString("par", "16:9",
adaptation_set_xml.get()));
}
// Verify that adding Representations with different par will generate
// AdaptationSet without @par.
TEST_F(CommonMpdBuilderTest, AdaptationSetParDifferent) {
const char k16by9VideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 1280\n"
" height: 720\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
// Note that 720:360 is 2:1 where as 720p (above) is 16:9.
const char k2by1VideoInfo[] =
"video_info {\n"
" codec: 'avc1'\n"
" width: 720\n"
" height: 360\n"
" time_scale: 3000\n"
" frame_duration: 100\n"
" pixel_width: 1\n"
" pixel_height: 1\n"
"}\n"
"container_type: 1\n";
AdaptationSet* video_adaptation_set = mpd_.AddAdaptationSet("");
ASSERT_TRUE(video_adaptation_set);
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k16by9VideoInfo)));
ASSERT_TRUE(video_adaptation_set->AddRepresentation(
ConvertToMediaInfo(k2by1VideoInfo)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("par", adaptation_set_xml.get()));
}
// Verify that adding Representation without pixel_width and pixel_height will
// not set @par.
TEST_F(CommonMpdBuilderTest, AdaptationSetParUnknown) {
const char kUknownPixelWidthAndHeight[] =
"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(kUknownPixelWidthAndHeight)));
xml::ScopedXmlPtr<xmlNode>::type adaptation_set_xml(
video_adaptation_set->GetXml());
EXPECT_NO_FATAL_FAILURE(
ExpectAttributeNotSet("par", 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.

View File

@ -330,13 +330,17 @@ bool RepresentationXmlNode::AddVideoInfo(const VideoInfo& video_info) {
return false;
}
if (video_info.has_pixel_width() && video_info.has_pixel_height()) {
SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) +
":" +
base::IntToString(video_info.pixel_height()));
}
SetIntegerAttribute("width", video_info.width());
SetIntegerAttribute("height", video_info.height());
SetStringAttribute("frameRate",
base::IntToString(video_info.time_scale()) + "/" +
base::IntToString(video_info.frame_duration()));
SetStringAttribute("sar", base::IntToString(video_info.pixel_width()) + ":" +
base::IntToString(video_info.pixel_height()));
return true;
}

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" width="720" height="480" frameRate="10/1" contentType="video">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1" contentType="video" par="3:2">
<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" width="720" height="480" frameRate="10/5" contentType="video">
<AdaptationSet id="0" width="720" height="480" frameRate="10/5" contentType="video" par="3:2">
<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" width="720" height="480" frameRate="10/1" contentType="video">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1" contentType="video" par="3:2">
<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" width="720" height="480" frameRate="10/1" contentType="video">
<AdaptationSet id="0" width="720" height="480" frameRate="10/1" contentType="video" par="3:2">
<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">