Support v1 vp9 in iso-bmff

- Implemented according to v1.0 spec @ https://www.webmproject.org/vp9/mp4/
- v0 is no longer supported

Change-Id: I189c813d788400beda797eea7da943a83dfa7d79
This commit is contained in:
KongQun Yang 2017-04-24 11:50:49 -07:00
parent 4d81979b16
commit 59f393779c
21 changed files with 319 additions and 203 deletions

View File

@ -20,10 +20,10 @@
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"> <ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh> <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="1" bandwidth="210193" codecs="vp09.00.00.08.00.01.00.00" mimeType="video/mp4" sar="427:320"> <Representation id="1" bandwidth="210205" codecs="vp09.00.10.08.01.02.02.02.00" mimeType="video/mp4" sar="427:320">
<BaseURL>output_video.mp4</BaseURL> <BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="1059-1126" timescale="1000000"> <SegmentBase indexRange="1063-1130" timescale="1000000">
<Initialization range="0-1058"/> <Initialization range="0-1062"/>
</SegmentBase> </SegmentBase>
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>

View File

@ -12,10 +12,10 @@
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>
<AdaptationSet id="1" contentType="video" width="320" height="240" frameRate="1000000/34000" par="16:9"> <AdaptationSet id="1" contentType="video" width="320" height="240" frameRate="1000000/34000" par="16:9">
<Representation id="1" bandwidth="203351" codecs="vp9" mimeType="video/webm" sar="427:320"> <Representation id="1" bandwidth="203360" codecs="vp9" mimeType="video/webm" sar="427:320">
<BaseURL>output_video.webm</BaseURL> <BaseURL>output_video.webm</BaseURL>
<SegmentBase indexRange="299-347" timescale="1000000"> <SegmentBase indexRange="302-350" timescale="1000000">
<Initialization range="0-298"/> <Initialization range="0-301"/>
</SegmentBase> </SegmentBase>
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>

View File

@ -7,10 +7,10 @@
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b"> <ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh> <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
</ContentProtection> </ContentProtection>
<Representation id="0" bandwidth="341006" codecs="vp08.00.00.08.01.01.00.00" mimeType="video/mp4" sar="1:1"> <Representation id="0" bandwidth="341018" codecs="vp08.00.10.08.01.02.02.02.00" mimeType="video/mp4" sar="1:1">
<BaseURL>output_video.mp4</BaseURL> <BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="1027-1094" timescale="1000000"> <SegmentBase indexRange="1031-1098" timescale="1000000">
<Initialization range="0-1026"/> <Initialization range="0-1030"/>
</SegmentBase> </SegmentBase>
</Representation> </Representation>
</AdaptationSet> </AdaptationSet>

View File

@ -154,10 +154,6 @@ bool VP8Parser::Parse(const uint8_t* data,
writable_codec_config()->set_bit_depth(8); writable_codec_config()->set_bit_depth(8);
writable_codec_config()->set_chroma_subsampling( writable_codec_config()->set_chroma_subsampling(
VPCodecConfigurationRecord::CHROMA_420_COLLOCATED_WITH_LUMA); VPCodecConfigurationRecord::CHROMA_420_COLLOCATED_WITH_LUMA);
// VP8 uses YCrCb color space defined in ITU-R_BT.601.
// http://tools.ietf.org/html/rfc6386 Section 9.2.
writable_codec_config()->set_color_space(
VPCodecConfigurationRecord::COLOR_SPACE_BT_601);
VPxFrameInfo vpx_frame; VPxFrameInfo vpx_frame;
vpx_frame.frame_size = data_size; vpx_frame.frame_size = data_size;

View File

@ -43,7 +43,7 @@ TEST(VP8ParserTest, Keyframe) {
VP8Parser parser; VP8Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp08.02.00.08.01.01.00.00", EXPECT_EQ("vp08.02.10.08.01.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP8)); parser.codec_config().GetCodecString(kCodecVP8));
EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 22u, true, EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 22u, true,
320u, 240u))); 320u, 240u)));

View File

@ -160,30 +160,66 @@ bool ReadSyncCode(BitReader* reader) {
return sync_code == VP9_SYNC_CODE; return sync_code == VP9_SYNC_CODE;
} }
VPCodecConfigurationRecord::ColorSpace GetColorSpace(uint8_t color_space) { void SetColorAttributes(uint8_t bit_depth,
uint8_t color_space,
VPCodecConfigurationRecord* codec_config) {
switch (color_space) { switch (color_space) {
case VPX_COLOR_SPACE_UNKNOWN: case VPX_COLOR_SPACE_UNKNOWN:
return VPCodecConfigurationRecord::COLOR_SPACE_UNSPECIFIED; codec_config->set_color_primaries(AVCOL_PRI_UNSPECIFIED);
codec_config->set_matrix_coefficients(AVCOL_SPC_UNSPECIFIED);
codec_config->set_transfer_characteristics(AVCOL_TRC_UNSPECIFIED);
break;
case VPX_COLOR_SPACE_BT_601: case VPX_COLOR_SPACE_BT_601:
return VPCodecConfigurationRecord::COLOR_SPACE_BT_601; // Don't know if it is 525 line or 625 line.
codec_config->set_color_primaries(AVCOL_PRI_UNSPECIFIED);
codec_config->set_matrix_coefficients(AVCOL_SPC_UNSPECIFIED);
codec_config->set_transfer_characteristics(AVCOL_TRC_SMPTE170M);
break;
case VPX_COLOR_SPACE_BT_709: case VPX_COLOR_SPACE_BT_709:
return VPCodecConfigurationRecord::COLOR_SPACE_BT_709; codec_config->set_color_primaries(AVCOL_PRI_BT709);
codec_config->set_matrix_coefficients(AVCOL_SPC_BT709);
codec_config->set_transfer_characteristics(AVCOL_TRC_BT709);
break;
case VPX_COLOR_SPACE_SMPTE_170: case VPX_COLOR_SPACE_SMPTE_170:
return VPCodecConfigurationRecord::COLOR_SPACE_SMPTE_170; codec_config->set_color_primaries(AVCOL_PRI_SMPTE170M);
codec_config->set_matrix_coefficients(AVCOL_SPC_SMPTE170M);
codec_config->set_transfer_characteristics(AVCOL_TRC_SMPTE170M);
break;
case VPX_COLOR_SPACE_SMPTE_240: case VPX_COLOR_SPACE_SMPTE_240:
return VPCodecConfigurationRecord::COLOR_SPACE_SMPTE_240; codec_config->set_color_primaries(AVCOL_PRI_SMPTE240M);
codec_config->set_matrix_coefficients(AVCOL_SPC_SMPTE240M);
codec_config->set_transfer_characteristics(AVCOL_TRC_SMPTE240M);
break;
case VPX_COLOR_SPACE_BT_2020: case VPX_COLOR_SPACE_BT_2020:
codec_config->set_color_primaries(AVCOL_PRI_BT2020);
// VP9 does not specify if it is in the form of “constant luminance” or // VP9 does not specify if it is in the form of “constant luminance” or
// “non-constant luminance”. As such, application should rely on the // “non-constant luminance”. As such, application should rely on the
// signaling outside of VP9 bitstream. If there is no such signaling, // signaling outside of VP9 bitstream. If there is no such signaling,
// application may assume non-constant luminance for BT.2020. // application may assume non-constant luminance for BT.2020.
return VPCodecConfigurationRecord:: codec_config->set_matrix_coefficients(AVCOL_SPC_BT2020_NCL);
COLOR_SPACE_BT_2020_NON_CONSTANT_LUMINANCE; switch (bit_depth) {
case 10:
codec_config->set_transfer_characteristics(AVCOL_TRC_BT2020_10);
break;
case 12:
codec_config->set_transfer_characteristics(AVCOL_TRC_BT2020_12);
break;
default:
codec_config->set_transfer_characteristics(AVCOL_TRC_UNSPECIFIED);
break;
}
break;
case VPX_COLOR_SPACE_SRGB: case VPX_COLOR_SPACE_SRGB:
return VPCodecConfigurationRecord::COLOR_SPACE_SRGB; codec_config->set_color_primaries(AVCOL_PRI_UNSPECIFIED);
codec_config->set_matrix_coefficients(AVCOL_SPC_RGB);
codec_config->set_transfer_characteristics(AVCOL_TRC_UNSPECIFIED);
break;
default: default:
LOG(WARNING) << "Unknown color space: " << static_cast<int>(color_space); LOG(WARNING) << "Unknown color space: " << static_cast<int>(color_space);
return VPCodecConfigurationRecord::COLOR_SPACE_UNSPECIFIED; codec_config->set_color_primaries(AVCOL_PRI_UNSPECIFIED);
codec_config->set_matrix_coefficients(AVCOL_SPC_UNSPECIFIED);
codec_config->set_transfer_characteristics(AVCOL_TRC_UNSPECIFIED);
break;
} }
} }
@ -219,7 +255,7 @@ bool ReadBitDepthAndColorSpace(BitReader* reader,
uint8_t color_space; uint8_t color_space;
RCHECK(reader->ReadBits(3, &color_space)); RCHECK(reader->ReadBits(3, &color_space));
codec_config->set_color_space(GetColorSpace(color_space)); SetColorAttributes(bit_depth, color_space, codec_config);
bool yuv_full_range = false; bool yuv_full_range = false;
auto chroma_subsampling = VPCodecConfigurationRecord::CHROMA_444; auto chroma_subsampling = VPCodecConfigurationRecord::CHROMA_444;
@ -261,7 +297,8 @@ bool ReadBitDepthAndColorSpace(BitReader* reader,
VLOG(3) << "\n profile " << static_cast<int>(codec_config->profile()) VLOG(3) << "\n profile " << static_cast<int>(codec_config->profile())
<< "\n bit depth " << static_cast<int>(codec_config->bit_depth()) << "\n bit depth " << static_cast<int>(codec_config->bit_depth())
<< "\n color space " << static_cast<int>(codec_config->color_space()) << "\n matrix coefficients "
<< static_cast<int>(codec_config->matrix_coefficients())
<< "\n full_range " << "\n full_range "
<< static_cast<int>(codec_config->video_full_range_flag()) << static_cast<int>(codec_config->video_full_range_flag())
<< "\n chroma subsampling " << "\n chroma subsampling "

View File

@ -76,7 +76,7 @@ TEST(VP9ParserTest, KeyframeChroma420) {
VP9Parser parser; VP9Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp09.00.00.08.00.01.00.00", EXPECT_EQ("vp09.00.10.08.01.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP9)); parser.codec_config().GetCodecString(kCodecVP9));
EXPECT_THAT(frames, EXPECT_THAT(frames,
ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true, 32u, 8u))); ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true, 32u, 8u)));
@ -98,7 +98,7 @@ TEST(VP9ParserTest, KeyframeProfile1Chroma422) {
VP9Parser parser; VP9Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp09.01.00.08.00.02.00.00", EXPECT_EQ("vp09.01.10.08.02.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP9)); parser.codec_config().GetCodecString(kCodecVP9));
EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true, EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true,
160u, 90u))); 160u, 90u)));
@ -120,7 +120,7 @@ TEST(VP9ParserTest, KeyframeProfile2Chroma420) {
VP9Parser parser; VP9Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp09.02.00.10.00.01.00.00", EXPECT_EQ("vp09.02.10.10.01.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP9)); parser.codec_config().GetCodecString(kCodecVP9));
EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true, EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 18u, true,
160u, 90u))); 160u, 90u)));
@ -142,7 +142,7 @@ TEST(VP9ParserTest, KeyframeProfile3Chroma444) {
VP9Parser parser; VP9Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp09.03.00.12.00.03.00.00", EXPECT_EQ("vp09.03.10.12.03.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP9)); parser.codec_config().GetCodecString(kCodecVP9));
EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 19u, true, 160u, 90u))); EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 19u, true, 160u, 90u)));
} }
@ -164,7 +164,7 @@ TEST(VP9ParserTest, Intra) {
VP9Parser parser; VP9Parser parser;
std::vector<VPxFrameInfo> frames; std::vector<VPxFrameInfo> frames;
ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames)); ASSERT_TRUE(parser.Parse(kData, arraysize(kData), &frames));
EXPECT_EQ("vp09.00.00.08.00.01.00.00", EXPECT_EQ("vp09.00.10.08.01.02.02.02.00",
parser.codec_config().GetCodecString(kCodecVP9)); parser.codec_config().GetCodecString(kCodecVP9));
EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 19u, false, EXPECT_THAT(frames, ElementsAre(EqualVPxFrame(arraysize(kData), 19u, false,
352u, 288u))); 352u, 288u)));

View File

@ -40,18 +40,17 @@ std::string VPCodecAsString(Codec codec) {
template <typename T> template <typename T>
void MergeField(const std::string& name, void MergeField(const std::string& name,
T source_value, const base::Optional<T>& source_value,
bool source_is_set, base::Optional<T>* dest_value) {
T* dest_value, if (*dest_value) {
bool* dest_is_set) { if (source_value && *source_value != **dest_value) {
if (!*dest_is_set || source_is_set) {
if (*dest_is_set && source_value != *dest_value) {
LOG(WARNING) << "VPx " << name << " is inconsistent, " LOG(WARNING) << "VPx " << name << " is inconsistent, "
<< static_cast<uint32_t>(*dest_value) << " vs " << static_cast<int>(**dest_value) << " vs "
<< static_cast<uint32_t>(source_value); << static_cast<int>(*source_value);
} }
} else {
// Only set dest_value if it is not set.
*dest_value = source_value; *dest_value = source_value;
*dest_is_set = true;
} }
} }
@ -63,45 +62,46 @@ VPCodecConfigurationRecord::VPCodecConfigurationRecord(
uint8_t profile, uint8_t profile,
uint8_t level, uint8_t level,
uint8_t bit_depth, uint8_t bit_depth,
uint8_t color_space,
uint8_t chroma_subsampling, uint8_t chroma_subsampling,
uint8_t transfer_function,
bool video_full_range_flag, bool video_full_range_flag,
uint8_t color_primaries,
uint8_t transfer_characteristics,
uint8_t matrix_coefficients,
const std::vector<uint8_t>& codec_initialization_data) const std::vector<uint8_t>& codec_initialization_data)
: profile_(profile), : profile_(profile),
level_(level), level_(level),
bit_depth_(bit_depth), bit_depth_(bit_depth),
color_space_(color_space),
chroma_subsampling_(chroma_subsampling), chroma_subsampling_(chroma_subsampling),
transfer_function_(transfer_function),
video_full_range_flag_(video_full_range_flag), video_full_range_flag_(video_full_range_flag),
profile_is_set_(true), color_primaries_(color_primaries),
level_is_set_(true), transfer_characteristics_(transfer_characteristics),
bit_depth_is_set_(true), matrix_coefficients_(matrix_coefficients),
color_space_is_set_(true),
chroma_subsampling_is_set_(true),
transfer_function_is_set_(true),
video_full_range_flag_is_set_(true),
codec_initialization_data_(codec_initialization_data) {} codec_initialization_data_(codec_initialization_data) {}
VPCodecConfigurationRecord::~VPCodecConfigurationRecord(){}; VPCodecConfigurationRecord::~VPCodecConfigurationRecord(){};
// https://www.webmproject.org/vp9/mp4/
bool VPCodecConfigurationRecord::ParseMP4(const std::vector<uint8_t>& data) { bool VPCodecConfigurationRecord::ParseMP4(const std::vector<uint8_t>& data) {
BitReader reader(data.data(), data.size()); BitReader reader(data.data(), data.size());
profile_is_set_ = true; uint8_t value;
level_is_set_ = true; RCHECK(reader.ReadBits(8, &value));
bit_depth_is_set_ = true; profile_ = value;
color_space_is_set_ = true; RCHECK(reader.ReadBits(8, &value));
chroma_subsampling_is_set_ = true; level_ = value;
transfer_function_is_set_ = true; RCHECK(reader.ReadBits(4, &value));
video_full_range_flag_is_set_ = true; bit_depth_ = value;
RCHECK(reader.ReadBits(8, &profile_)); RCHECK(reader.ReadBits(3, &value));
RCHECK(reader.ReadBits(8, &level_)); chroma_subsampling_ = value;
RCHECK(reader.ReadBits(4, &bit_depth_)); bool bool_value;
RCHECK(reader.ReadBits(4, &color_space_)); RCHECK(reader.ReadBits(1, &bool_value));
RCHECK(reader.ReadBits(4, &chroma_subsampling_)); video_full_range_flag_ = bool_value;
RCHECK(reader.ReadBits(3, &transfer_function_)); RCHECK(reader.ReadBits(8, &value));
RCHECK(reader.ReadBits(1, &video_full_range_flag_)); color_primaries_ = value;
RCHECK(reader.ReadBits(8, &value));
transfer_characteristics_ = value;
RCHECK(reader.ReadBits(8, &value));
matrix_coefficients_ = value;
uint16_t codec_initialization_data_size = 0; uint16_t codec_initialization_data_size = 0;
RCHECK(reader.ReadBits(16, &codec_initialization_data_size)); RCHECK(reader.ReadBits(16, &codec_initialization_data_size));
RCHECK(reader.bits_available() >= codec_initialization_data_size * 8u); RCHECK(reader.bits_available() >= codec_initialization_data_size * 8u);
@ -121,26 +121,27 @@ bool VPCodecConfigurationRecord::ParseWebM(const std::vector<uint8_t>& data) {
RCHECK(reader.Read1(&id)); RCHECK(reader.Read1(&id));
RCHECK(reader.Read1(&size)); RCHECK(reader.Read1(&size));
uint8_t value = 0;
switch (id) { switch (id) {
case kFeatureProfile: case kFeatureProfile:
RCHECK(size == 1); RCHECK(size == 1);
RCHECK(reader.Read1(&profile_)); RCHECK(reader.Read1(&value));
profile_is_set_ = true; profile_ = value;
break; break;
case kFeatureLevel: case kFeatureLevel:
RCHECK(size == 1); RCHECK(size == 1);
RCHECK(reader.Read1(&level_)); RCHECK(reader.Read1(&value));
level_is_set_ = true; level_ = value;
break; break;
case kFeatureBitDepth: case kFeatureBitDepth:
RCHECK(size == 1); RCHECK(size == 1);
RCHECK(reader.Read1(&bit_depth_)); RCHECK(reader.Read1(&value));
bit_depth_is_set_ = true; bit_depth_ = value;
break; break;
case kFeatureChromaSubsampling: case kFeatureChromaSubsampling:
RCHECK(size == 1); RCHECK(size == 1);
RCHECK(reader.Read1(&chroma_subsampling_)); RCHECK(reader.Read1(&value));
chroma_subsampling_is_set_ = true; chroma_subsampling_ = value;
break; break;
default: { default: {
LOG(WARNING) << "Skipping unknown VP9 codec feature " << id; LOG(WARNING) << "Skipping unknown VP9 codec feature " << id;
@ -154,13 +155,14 @@ bool VPCodecConfigurationRecord::ParseWebM(const std::vector<uint8_t>& data) {
void VPCodecConfigurationRecord::WriteMP4(std::vector<uint8_t>* data) const { void VPCodecConfigurationRecord::WriteMP4(std::vector<uint8_t>* data) const {
BufferWriter writer; BufferWriter writer;
writer.AppendInt(profile_); writer.AppendInt(profile());
writer.AppendInt(level_); writer.AppendInt(level());
uint8_t bit_depth_color_space = (bit_depth_ << 4) | color_space_; uint8_t bit_depth_chroma = (bit_depth() << 4) | (chroma_subsampling() << 1) |
writer.AppendInt(bit_depth_color_space); (video_full_range_flag() ? 1 : 0);
uint8_t chroma = (chroma_subsampling_ << 4) | (transfer_function_ << 1) | writer.AppendInt(bit_depth_chroma);
(video_full_range_flag_ ? 1 : 0); writer.AppendInt(color_primaries());
writer.AppendInt(chroma); writer.AppendInt(transfer_characteristics());
writer.AppendInt(matrix_coefficients());
uint16_t codec_initialization_data_size = uint16_t codec_initialization_data_size =
static_cast<uint16_t>(codec_initialization_data_.size()); static_cast<uint16_t>(codec_initialization_data_.size());
writer.AppendInt(codec_initialization_data_size); writer.AppendInt(codec_initialization_data_size);
@ -171,35 +173,29 @@ void VPCodecConfigurationRecord::WriteMP4(std::vector<uint8_t>* data) const {
void VPCodecConfigurationRecord::WriteWebM(std::vector<uint8_t>* data) const { void VPCodecConfigurationRecord::WriteWebM(std::vector<uint8_t>* data) const {
BufferWriter writer; BufferWriter writer;
if (profile_is_set_) { if (profile_) {
writer.AppendInt(static_cast<uint8_t>(kFeatureProfile)); // ID = 1 writer.AppendInt(static_cast<uint8_t>(kFeatureProfile)); // ID = 1
writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
writer.AppendInt(static_cast<uint8_t>(profile_)); writer.AppendInt(*profile_);
} }
if (level_is_set_ && level_ != 0) { if (level_) {
writer.AppendInt(static_cast<uint8_t>(kFeatureLevel)); // ID = 2 writer.AppendInt(static_cast<uint8_t>(kFeatureLevel)); // ID = 2
writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
writer.AppendInt(static_cast<uint8_t>(level_)); writer.AppendInt(*level_);
} }
if (bit_depth_is_set_) { if (bit_depth_) {
writer.AppendInt(static_cast<uint8_t>(kFeatureBitDepth)); // ID = 3 writer.AppendInt(static_cast<uint8_t>(kFeatureBitDepth)); // ID = 3
writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
writer.AppendInt(static_cast<uint8_t>(bit_depth_)); writer.AppendInt(*bit_depth_);
} }
if (chroma_subsampling_is_set_) { if (chroma_subsampling_) {
// WebM doesn't differentiate whether it is vertical or collocated with luma
// for 4:2:0.
const uint8_t subsampling =
chroma_subsampling_ == CHROMA_420_COLLOCATED_WITH_LUMA
? CHROMA_420_VERTICAL
: chroma_subsampling_;
// ID = 4, Length = 1 // ID = 4, Length = 1
writer.AppendInt(static_cast<uint8_t>(kFeatureChromaSubsampling)); writer.AppendInt(static_cast<uint8_t>(kFeatureChromaSubsampling));
writer.AppendInt(static_cast<uint8_t>(1)); writer.AppendInt(static_cast<uint8_t>(1));
writer.AppendInt(subsampling); writer.AppendInt(*chroma_subsampling_);
} }
writer.SwapBuffer(data); writer.SwapBuffer(data);
@ -207,13 +203,14 @@ void VPCodecConfigurationRecord::WriteWebM(std::vector<uint8_t>* data) const {
std::string VPCodecConfigurationRecord::GetCodecString(Codec codec) const { std::string VPCodecConfigurationRecord::GetCodecString(Codec codec) const {
const std::string fields[] = { const std::string fields[] = {
base::IntToString(profile_), base::IntToString(profile()),
base::IntToString(level_), base::IntToString(level()),
base::IntToString(bit_depth_), base::IntToString(bit_depth()),
base::IntToString(color_space_), base::IntToString(chroma_subsampling()),
base::IntToString(chroma_subsampling_), base::IntToString(color_primaries()),
base::IntToString(transfer_function_), base::IntToString(transfer_characteristics()),
(video_full_range_flag_ ? "01" : "00"), base::IntToString(matrix_coefficients()),
(video_full_range_flag_ && *video_full_range_flag_) ? "01" : "00",
}; };
std::string codec_string = VPCodecAsString(codec); std::string codec_string = VPCodecAsString(codec);
@ -228,23 +225,18 @@ std::string VPCodecConfigurationRecord::GetCodecString(Codec codec) const {
void VPCodecConfigurationRecord::MergeFrom( void VPCodecConfigurationRecord::MergeFrom(
const VPCodecConfigurationRecord& other) { const VPCodecConfigurationRecord& other) {
MergeField("profile", other.profile_, other.profile_is_set_, &profile_, MergeField("profile", other.profile_, &profile_);
&profile_is_set_); MergeField("level", other.level_, &level_);
MergeField("level", other.level_, other.level_is_set_, &level_, MergeField("bit depth", other.bit_depth_, &bit_depth_);
&level_is_set_);
MergeField("bit depth", other.bit_depth_, other.bit_depth_is_set_,
&bit_depth_, &bit_depth_is_set_);
MergeField("color space", other.color_space_, other.color_space_is_set_,
&color_space_, &color_space_is_set_);
MergeField("chroma subsampling", other.chroma_subsampling_, MergeField("chroma subsampling", other.chroma_subsampling_,
other.chroma_subsampling_is_set_, &chroma_subsampling_, &chroma_subsampling_);
&chroma_subsampling_is_set_);
MergeField("transfer function", other.transfer_function_,
other.transfer_function_is_set_, &transfer_function_,
&transfer_function_is_set_);
MergeField("video full range flag", other.video_full_range_flag_, MergeField("video full range flag", other.video_full_range_flag_,
other.video_full_range_flag_is_set_, &video_full_range_flag_, &video_full_range_flag_);
&video_full_range_flag_is_set_); MergeField("color primaries", other.color_primaries_, &color_primaries_);
MergeField("transfer characteristics", other.transfer_characteristics_,
&transfer_characteristics_);
MergeField("matrix coefficients", other.matrix_coefficients_,
&matrix_coefficients_);
if (codec_initialization_data_.empty() || if (codec_initialization_data_.empty() ||
!other.codec_initialization_data_.empty()) { !other.codec_initialization_data_.empty()) {

View File

@ -12,25 +12,118 @@
#include <vector> #include <vector>
#include "packager/base/macros.h" #include "packager/base/macros.h"
#include "packager/base/optional.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
namespace shaka { namespace shaka {
namespace media { namespace media {
/// The below enums are from ffmpeg/libavutil/pixfmt.h.
/// Chromaticity coordinates of the source primaries.
enum AVColorPrimaries {
AVCOL_PRI_RESERVED0 = 0,
/// Also ITU-R BT1361 / IEC 61966-2-4 / SMPTE RP177 Annex B
AVCOL_PRI_BT709 = 1,
AVCOL_PRI_UNSPECIFIED = 2,
AVCOL_PRI_RESERVED = 3,
/// Also FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
AVCOL_PRI_BT470M = 4,
/// Also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM
AVCOL_PRI_BT470BG = 5,
/// Also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC
AVCOL_PRI_SMPTE170M = 6,
/// Functionally identical to above
AVCOL_PRI_SMPTE240M = 7,
/// Colour filters using Illuminant C
AVCOL_PRI_FILM = 8,
/// ITU-R BT2020
AVCOL_PRI_BT2020 = 9,
/// SMPTE ST 428-1 (CIE 1931 XYZ)
AVCOL_PRI_SMPTE428 = 10,
AVCOL_PRI_SMPTEST428_1 = AVCOL_PRI_SMPTE428,
/// SMPTE ST 431-2 (2011)
AVCOL_PRI_SMPTE431 = 11,
/// SMPTE ST 432-1 D65 (2010)
AVCOL_PRI_SMPTE432 = 12,
///< Not part of ABI
AVCOL_PRI_NB
};
/// Color Transfer Characteristic.
enum AVColorTransferCharacteristic {
AVCOL_TRC_RESERVED0 = 0,
/// Also ITU-R BT1361
AVCOL_TRC_BT709 = 1,
AVCOL_TRC_UNSPECIFIED = 2,
AVCOL_TRC_RESERVED = 3,
/// Also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM
AVCOL_TRC_GAMMA22 = 4,
/// Also ITU-R BT470BG
AVCOL_TRC_GAMMA28 = 5,
/// Also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700
/// NTSC
AVCOL_TRC_SMPTE170M = 6,
AVCOL_TRC_SMPTE240M = 7,
/// "Linear transfer characteristics"
AVCOL_TRC_LINEAR = 8,
/// "Logarithmic transfer characteristic (100:1 range)"
AVCOL_TRC_LOG = 9,
/// "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)"
AVCOL_TRC_LOG_SQRT = 10,
/// IEC 61966-2-4
AVCOL_TRC_IEC61966_2_4 = 11,
/// ITU-R BT1361 Extended Colour Gamut
AVCOL_TRC_BT1361_ECG = 12,
/// IEC 61966-2-1 (sRGB or sYCC)
AVCOL_TRC_IEC61966_2_1 = 13,
/// ITU-R BT2020 for 10-bit system
AVCOL_TRC_BT2020_10 = 14,
/// ITU-R BT2020 for 12-bit system
AVCOL_TRC_BT2020_12 = 15,
/// SMPTE ST 2084 for 10-, 12-, 14- and 16-bit systems
AVCOL_TRC_SMPTE2084 = 16,
AVCOL_TRC_SMPTEST2084 = AVCOL_TRC_SMPTE2084,
/// SMPTE ST 428-1
AVCOL_TRC_SMPTE428 = 17,
AVCOL_TRC_SMPTEST428_1 = AVCOL_TRC_SMPTE428,
/// ARIB STD-B67, known as "Hybrid log-gamma"
AVCOL_TRC_ARIB_STD_B67 = 18,
/// Not part of ABI
AVCOL_TRC_NB
};
/// YUV colorspace type (a.c.a matrix coefficients in 23001-8:2016).
enum AVColorSpace {
/// Order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB)
AVCOL_SPC_RGB = 0,
/// Also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / SMPTE RP177 Annex B
AVCOL_SPC_BT709 = 1,
AVCOL_SPC_UNSPECIFIED = 2,
AVCOL_SPC_RESERVED = 3,
/// FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
AVCOL_SPC_FCC = 4,
/// Also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM /
/// IEC 61966-2-4 xvYCC601
AVCOL_SPC_BT470BG = 5,
/// Also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC
AVCOL_SPC_SMPTE170M = 6,
/// Functionally identical to above
AVCOL_SPC_SMPTE240M = 7,
/// Used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16
AVCOL_SPC_YCOCG = 8,
/// ITU-R BT2020 non-constant luminance system
AVCOL_SPC_BT2020_NCL = 9,
/// ITU-R BT2020 constant luminance system
AVCOL_SPC_BT2020_CL = 10,
/// SMPTE 2085, Y'D'zD'x
AVCOL_SPC_SMPTE2085 = 11,
/// Not part of ABI
AVCOL_SPC_NB
};
/// Class for parsing or writing VP codec configuration record. /// Class for parsing or writing VP codec configuration record.
class VPCodecConfigurationRecord { class VPCodecConfigurationRecord {
public: public:
enum ColorSpace {
COLOR_SPACE_UNSPECIFIED = 0,
COLOR_SPACE_BT_601 = 1,
COLOR_SPACE_BT_709 = 2,
COLOR_SPACE_SMPTE_170 = 3,
COLOR_SPACE_SMPTE_240 = 4,
COLOR_SPACE_BT_2020_NON_CONSTANT_LUMINANCE = 5,
COLOR_SPACE_BT_2020_CONSTANT_LUMINANCE = 6,
COLOR_SPACE_SRGB = 7,
};
enum ChromaSubsampling { enum ChromaSubsampling {
CHROMA_420_VERTICAL = 0, CHROMA_420_VERTICAL = 0,
CHROMA_420_COLLOCATED_WITH_LUMA = 1, CHROMA_420_COLLOCATED_WITH_LUMA = 1,
@ -44,10 +137,11 @@ class VPCodecConfigurationRecord {
uint8_t profile, uint8_t profile,
uint8_t level, uint8_t level,
uint8_t bit_depth, uint8_t bit_depth,
uint8_t color_space,
uint8_t chroma_subsampling, uint8_t chroma_subsampling,
uint8_t transfer_function,
bool video_full_range_flag, bool video_full_range_flag,
uint8_t color_primaries,
uint8_t transfer_characteristics,
uint8_t matrix_coefficients,
const std::vector<uint8_t>& codec_initialization_data); const std::vector<uint8_t>& codec_initialization_data);
~VPCodecConfigurationRecord(); ~VPCodecConfigurationRecord();
@ -74,57 +168,53 @@ class VPCodecConfigurationRecord {
// both |*this| and |other|, the values in |other| take precedence. // both |*this| and |other|, the values in |other| take precedence.
void MergeFrom(const VPCodecConfigurationRecord& other); void MergeFrom(const VPCodecConfigurationRecord& other);
void set_profile(uint8_t profile) { void set_profile(uint8_t profile) { profile_ = profile; }
profile_ = profile; void set_level(uint8_t level) { level_ = level; }
profile_is_set_ = true; void set_bit_depth(uint8_t bit_depth) { bit_depth_ = bit_depth; }
}
void set_level(uint8_t level) {
level_ = level;
level_is_set_ = true;
}
void set_bit_depth(uint8_t bit_depth) {
bit_depth_ = bit_depth;
bit_depth_is_set_ = true;
}
void set_color_space(uint8_t color_space) {
color_space_ = color_space;
color_space_is_set_ = true;
}
void set_chroma_subsampling(uint8_t chroma_subsampling) { void set_chroma_subsampling(uint8_t chroma_subsampling) {
chroma_subsampling_ = chroma_subsampling; chroma_subsampling_ = chroma_subsampling;
chroma_subsampling_is_set_ = true;
}
void set_transfer_function(uint8_t transfer_function) {
transfer_function_ = transfer_function;
transfer_function_is_set_ = true;
} }
void set_video_full_range_flag(bool video_full_range_flag) { void set_video_full_range_flag(bool video_full_range_flag) {
video_full_range_flag_ = video_full_range_flag; video_full_range_flag_ = video_full_range_flag;
} }
void set_color_primaries(uint8_t color_primaries) {
color_primaries_ = color_primaries;
}
void set_transfer_characteristics(uint8_t transfer_characteristics) {
transfer_characteristics_ = transfer_characteristics;
}
void set_matrix_coefficients(uint8_t matrix_coefficients) {
matrix_coefficients_ = matrix_coefficients;
}
uint8_t profile() const { return profile_; } uint8_t profile() const { return profile_.value_or(0); }
uint8_t level() const { return level_; } uint8_t level() const { return level_.value_or(10); }
uint8_t bit_depth() const { return bit_depth_; } uint8_t bit_depth() const { return bit_depth_.value_or(8); }
uint8_t color_space() const { return color_space_; } uint8_t chroma_subsampling() const {
uint8_t chroma_subsampling() const { return chroma_subsampling_; } return chroma_subsampling_.value_or(CHROMA_420_COLLOCATED_WITH_LUMA);
uint8_t transfer_function() const { return transfer_function_; } }
bool video_full_range_flag() const { return video_full_range_flag_; } bool video_full_range_flag() const {
return video_full_range_flag_.value_or(false);
}
uint8_t color_primaries() const {
return color_primaries_.value_or(AVCOL_PRI_UNSPECIFIED);
}
uint8_t transfer_characteristics() const {
return transfer_characteristics_.value_or(AVCOL_TRC_UNSPECIFIED);
}
uint8_t matrix_coefficients() const {
return matrix_coefficients_.value_or(AVCOL_SPC_UNSPECIFIED);
}
private: private:
uint8_t profile_ = 0; base::Optional<uint8_t> profile_;
uint8_t level_ = 0; base::Optional<uint8_t> level_;
uint8_t bit_depth_ = 0; base::Optional<uint8_t> bit_depth_;
uint8_t color_space_ = 0; base::Optional<uint8_t> chroma_subsampling_;
uint8_t chroma_subsampling_ = 0; base::Optional<bool> video_full_range_flag_;
uint8_t transfer_function_ = 0; base::Optional<uint8_t> color_primaries_;
bool video_full_range_flag_ = false; base::Optional<uint8_t> transfer_characteristics_;
bool profile_is_set_ = false; base::Optional<uint8_t> matrix_coefficients_;
bool level_is_set_ = false;
bool bit_depth_is_set_ = false;
bool color_space_is_set_ = false;
bool chroma_subsampling_is_set_ = false;
bool transfer_function_is_set_ = false;
bool video_full_range_flag_is_set_ = false;
std::vector<uint8_t> codec_initialization_data_; std::vector<uint8_t> codec_initialization_data_;
// Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler

View File

@ -13,49 +13,49 @@ namespace media {
TEST(VPCodecConfigurationRecordTest, Parse) { TEST(VPCodecConfigurationRecordTest, Parse) {
const uint8_t kVpCodecConfigurationData[] = { const uint8_t kVpCodecConfigurationData[] = {
0x01, 0x00, 0xA2, 0x14, 0x00, 0x01, 0x00, 0x01, 0x14, 0xA2, 0x02, 0x03, 0x04, 0x00, 0x00,
}; };
VPCodecConfigurationRecord vp_config; VPCodecConfigurationRecord vp_config;
ASSERT_TRUE(vp_config.ParseMP4(std::vector<uint8_t>( ASSERT_TRUE(vp_config.ParseMP4(
kVpCodecConfigurationData, std::vector<uint8_t>(std::begin(kVpCodecConfigurationData),
kVpCodecConfigurationData + arraysize(kVpCodecConfigurationData)))); std::end(kVpCodecConfigurationData))));
EXPECT_EQ(1u, vp_config.profile()); EXPECT_EQ(1u, vp_config.profile());
EXPECT_EQ(0u, vp_config.level()); EXPECT_EQ(20u, vp_config.level());
EXPECT_EQ(10u, vp_config.bit_depth()); EXPECT_EQ(10u, vp_config.bit_depth());
EXPECT_EQ(2u, vp_config.color_space());
EXPECT_EQ(1u, vp_config.chroma_subsampling()); EXPECT_EQ(1u, vp_config.chroma_subsampling());
EXPECT_EQ(2u, vp_config.transfer_function());
EXPECT_FALSE(vp_config.video_full_range_flag()); EXPECT_FALSE(vp_config.video_full_range_flag());
EXPECT_EQ(2u, vp_config.color_primaries());
EXPECT_EQ(3u, vp_config.transfer_characteristics());
EXPECT_EQ(4u, vp_config.matrix_coefficients());
EXPECT_EQ("vp09.01.00.10.02.01.02.00", vp_config.GetCodecString(kCodecVP9)); EXPECT_EQ("vp09.01.20.10.01.02.03.04.00",
vp_config.GetCodecString(kCodecVP9));
} }
TEST(VPCodecConfigurationRecordTest, ParseWithInsufficientData) { TEST(VPCodecConfigurationRecordTest, ParseWithInsufficientData) {
const uint8_t kVpCodecConfigurationData[] = { const uint8_t kVpCodecConfigurationData[] = {
0x01, 0x00, 0xA2, 0x14, 0x01, 0x14, 0xA2, 0x02,
}; };
VPCodecConfigurationRecord vp_config; VPCodecConfigurationRecord vp_config;
ASSERT_FALSE(vp_config.ParseMP4(std::vector<uint8_t>( ASSERT_FALSE(vp_config.ParseMP4(
kVpCodecConfigurationData, std::vector<uint8_t>(std::begin(kVpCodecConfigurationData),
kVpCodecConfigurationData + arraysize(kVpCodecConfigurationData)))); std::end(kVpCodecConfigurationData))));
} }
TEST(VPCodecConfigurationRecordTest, WriteMP4) { TEST(VPCodecConfigurationRecordTest, WriteMP4) {
const uint8_t kExpectedVpCodecConfigurationData[] = { const uint8_t kExpectedVpCodecConfigurationData[] = {
0x02, 0x01, 0x80, 0x21, 0x00, 0x00, 0x02, 0x01, 0x85, 0x03, 0x04, 0x05, 0x00, 0x00,
}; };
VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x00, 0x02, 0x00, true, VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x02, true, 0x03, 0x04,
std::vector<uint8_t>()); 0x05, std::vector<uint8_t>());
std::vector<uint8_t> data; std::vector<uint8_t> data;
vp_config.WriteMP4(&data); vp_config.WriteMP4(&data);
EXPECT_EQ( EXPECT_EQ(std::vector<uint8_t>(std::begin(kExpectedVpCodecConfigurationData),
std::vector<uint8_t>(kExpectedVpCodecConfigurationData, std::end(kExpectedVpCodecConfigurationData)),
kExpectedVpCodecConfigurationData +
arraysize(kExpectedVpCodecConfigurationData)),
data); data);
} }
@ -64,17 +64,15 @@ TEST(VPCodecConfigurationRecordTest, WriteWebM) {
0x01, 0x01, 0x02, 0x01, 0x01, 0x02,
0x02, 0x01, 0x01, 0x02, 0x01, 0x01,
0x03, 0x01, 0x08, 0x03, 0x01, 0x08,
0x04, 0x01, 0x03 0x04, 0x01, 0x02,
}; };
VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x00, 0x03, 0x00, true, VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x02, true, 0x03, 0x04,
std::vector<uint8_t>()); 0x05, std::vector<uint8_t>());
std::vector<uint8_t> data; std::vector<uint8_t> data;
vp_config.WriteWebM(&data); vp_config.WriteWebM(&data);
EXPECT_EQ( EXPECT_EQ(std::vector<uint8_t>(std::begin(kExpectedVpCodecConfigurationData),
std::vector<uint8_t>(kExpectedVpCodecConfigurationData, std::end(kExpectedVpCodecConfigurationData)),
kExpectedVpCodecConfigurationData +
arraysize(kExpectedVpCodecConfigurationData)),
data); data);
} }

View File

@ -1446,9 +1446,12 @@ bool CodecConfiguration::ReadWriteInternal(BoxBuffer* buffer) {
// VPCodecConfiguration box inherits from FullBox instead of Box. The extra 4 // VPCodecConfiguration box inherits from FullBox instead of Box. The extra 4
// bytes are handled here. // bytes are handled here.
if (box_type == FOURCC_vpcC) { if (box_type == FOURCC_vpcC) {
uint32_t version_flags = 0; // Only version 1 box is supported.
uint8_t vpcc_version = 1;
uint32_t version_flags = vpcc_version << 24;
RCHECK(buffer->ReadWriteUInt32(&version_flags)); RCHECK(buffer->ReadWriteUInt32(&version_flags));
RCHECK(version_flags == 0); vpcc_version = version_flags >> 24;
RCHECK(vpcc_version == 1);
} }
if (buffer->Reading()) { if (buffer->Reading()) {

View File

@ -821,7 +821,7 @@ TEST_F(WebMClusterParserTest, ParseVP8) {
ASSERT_EQ(2u, streams_from_init_event_.size()); ASSERT_EQ(2u, streams_from_init_event_.size());
EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type()); EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type());
EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type()); EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type());
EXPECT_EQ("vp08.01.00.08.01.01.00.00", EXPECT_EQ("vp08.01.10.08.01.02.02.02.00",
streams_from_init_event_[1]->codec_string()); streams_from_init_event_[1]->codec_string());
} }
@ -835,7 +835,7 @@ TEST_F(WebMClusterParserTest, ParseVP9) {
ASSERT_EQ(2u, streams_from_init_event_.size()); ASSERT_EQ(2u, streams_from_init_event_.size());
EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type()); EXPECT_EQ(kStreamAudio, streams_from_init_event_[0]->stream_type());
EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type()); EXPECT_EQ(kStreamVideo, streams_from_init_event_[1]->stream_type());
EXPECT_EQ("vp09.03.00.12.00.03.00.00", EXPECT_EQ("vp09.03.10.12.03.02.02.02.00",
streams_from_init_event_[1]->codec_string()); streams_from_init_event_[1]->codec_string());
} }