diff --git a/packager/app/test/testdata/bear-320x240-vp9-golden.webm b/packager/app/test/testdata/bear-320x240-vp9-golden.webm index b6d4bb46fa..3b18d7666f 100644 Binary files a/packager/app/test/testdata/bear-320x240-vp9-golden.webm and b/packager/app/test/testdata/bear-320x240-vp9-golden.webm differ diff --git a/packager/app/test/testdata/bear-320x240-vp9-opus-webm-golden.mpd b/packager/app/test/testdata/bear-320x240-vp9-opus-webm-golden.mpd index 7b0802b23f..3e3610a38b 100644 --- a/packager/app/test/testdata/bear-320x240-vp9-opus-webm-golden.mpd +++ b/packager/app/test/testdata/bear-320x240-vp9-opus-webm-golden.mpd @@ -12,10 +12,10 @@ - + output_video.webm - - + + diff --git a/packager/media/codecs/vp_codec_configuration_record.cc b/packager/media/codecs/vp_codec_configuration_record.cc index be670c24d0..93c651aa86 100644 --- a/packager/media/codecs/vp_codec_configuration_record.cc +++ b/packager/media/codecs/vp_codec_configuration_record.cc @@ -9,6 +9,7 @@ #include "packager/base/strings/string_number_conversions.h" #include "packager/base/strings/string_util.h" #include "packager/media/base/bit_reader.h" +#include "packager/media/base/buffer_reader.h" #include "packager/media/base/buffer_writer.h" #include "packager/media/base/rcheck.h" #include "packager/base/strings/stringprintf.h" @@ -16,6 +17,12 @@ namespace shaka { namespace media { namespace { +enum VP9CodecFeatures { + kFeatureProfile = 1, + kFeatureLevel = 2, + kFeatureBitDepth = 3, + kFeatureChromaSubsampling = 4, +}; std::string VPCodecAsString(VideoCodec codec) { switch (codec) { @@ -33,14 +40,7 @@ std::string VPCodecAsString(VideoCodec codec) { } // namespace -VPCodecConfigurationRecord::VPCodecConfigurationRecord() - : profile_(0), - level_(0), - bit_depth_(0), - color_space_(0), - chroma_subsampling_(0), - transfer_function_(0), - video_full_range_flag_(false) {} +VPCodecConfigurationRecord::VPCodecConfigurationRecord() {} VPCodecConfigurationRecord::VPCodecConfigurationRecord( uint8_t profile, @@ -58,12 +58,26 @@ VPCodecConfigurationRecord::VPCodecConfigurationRecord( chroma_subsampling_(chroma_subsampling), transfer_function_(transfer_function), video_full_range_flag_(video_full_range_flag), + profile_is_set_(true), + level_is_set_(true), + bit_depth_is_set_(true), + 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) {} VPCodecConfigurationRecord::~VPCodecConfigurationRecord(){}; -bool VPCodecConfigurationRecord::Parse(const std::vector& data) { +bool VPCodecConfigurationRecord::ParseMP4(const std::vector& data) { BitReader reader(data.data(), data.size()); + profile_is_set_ = true; + level_is_set_ = true; + bit_depth_is_set_ = true; + color_space_is_set_ = true; + chroma_subsampling_is_set_ = true; + transfer_function_is_set_ = true; + video_full_range_flag_is_set_ = true; RCHECK(reader.ReadBits(8, &profile_)); RCHECK(reader.ReadBits(8, &level_)); RCHECK(reader.ReadBits(4, &bit_depth_)); @@ -81,7 +95,47 @@ bool VPCodecConfigurationRecord::Parse(const std::vector& data) { return true; } -void VPCodecConfigurationRecord::Write(std::vector* data) const { +bool VPCodecConfigurationRecord::ParseWebM(const std::vector& data) { + BufferReader reader(data.data(), data.size()); + + while (reader.HasBytes(1)) { + uint8_t id; + uint8_t size; + RCHECK(reader.Read1(&id)); + RCHECK(reader.Read1(&size)); + + switch (id) { + case kFeatureProfile: + RCHECK(size == 1); + RCHECK(reader.Read1(&profile_)); + profile_is_set_ = true; + break; + case kFeatureLevel: + RCHECK(size == 1); + RCHECK(reader.Read1(&level_)); + level_is_set_ = true; + break; + case kFeatureBitDepth: + RCHECK(size == 1); + RCHECK(reader.Read1(&bit_depth_)); + bit_depth_is_set_ = true; + break; + case kFeatureChromaSubsampling: + RCHECK(size == 1); + RCHECK(reader.Read1(&chroma_subsampling_)); + chroma_subsampling_is_set_ = true; + break; + default: { + LOG(WARNING) << "Skipping unknown VP9 codec feature " << id; + RCHECK(reader.SkipBytes(size)); + } + } + } + + return true; +} + +void VPCodecConfigurationRecord::WriteMP4(std::vector* data) const { BufferWriter writer; writer.AppendInt(profile_); writer.AppendInt(level_); @@ -96,6 +150,36 @@ void VPCodecConfigurationRecord::Write(std::vector* data) const { writer.SwapBuffer(data); } +void VPCodecConfigurationRecord::WriteWebM(std::vector* data) const { + BufferWriter writer; + + writer.AppendInt(static_cast(kFeatureProfile)); // ID = 1 + writer.AppendInt(static_cast(1)); // Length = 1 + writer.AppendInt(static_cast(profile_)); + + if (level_ != 0) { + writer.AppendInt(static_cast(kFeatureLevel)); // ID = 2 + writer.AppendInt(static_cast(1)); // Length = 1 + writer.AppendInt(static_cast(level_)); + } + + writer.AppendInt(static_cast(kFeatureBitDepth)); // ID = 3 + writer.AppendInt(static_cast(1)); // Length = 1 + writer.AppendInt(static_cast(bit_depth_)); + + // 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_; + writer.AppendInt(static_cast(kFeatureChromaSubsampling)); // ID = 4 + writer.AppendInt(static_cast(1)); // Length = 1 + writer.AppendInt(subsampling); + + writer.SwapBuffer(data); +} + std::string VPCodecConfigurationRecord::GetCodecString(VideoCodec codec) const { const std::string fields[] = { base::IntToString(profile_), @@ -117,5 +201,75 @@ std::string VPCodecConfigurationRecord::GetCodecString(VideoCodec codec) const { return codec_string; } +void VPCodecConfigurationRecord::MergeFrom( + const VPCodecConfigurationRecord& other) { + if (!profile_is_set_ || other.profile_is_set_) { + profile_ = other.profile(); + profile_is_set_ = true; + } + if (!level_is_set_ || other.level_is_set_) { + if (level_is_set_ && other.level() != level_) { + LOG(WARNING) << "VPx level is inconsistent, " << level_ << " vs " + << other.level(); + } + level_ = other.level(); + level_is_set_ = true; + } + if (!bit_depth_is_set_ || other.bit_depth_is_set_) { + if (bit_depth_is_set_ && bit_depth_ != other.bit_depth()) { + LOG(WARNING) << "VPx bit depth is inconsistent, " << bit_depth_ << " vs " + << other.bit_depth(); + } + bit_depth_ = other.bit_depth(); + bit_depth_is_set_ = true; + } + if (!color_space_is_set_ || other.color_space_is_set_) { + if (color_space_is_set_ && color_space_ != other.color_space()) { + LOG(WARNING) << "VPx color space is inconsistent, " << color_space_ + << " vs " << other.color_space(); + } + color_space_ = other.color_space(); + color_space_is_set_ = true; + } + if (!chroma_subsampling_is_set_ || other.chroma_subsampling_is_set_) { + if (chroma_subsampling_is_set_ && + chroma_subsampling_ != other.chroma_subsampling_) { + LOG(WARNING) << "VPx chroma subsampling is inconsistent, " + << chroma_subsampling_ << " vs " + << other.chroma_subsampling(); + } + chroma_subsampling_ = other.chroma_subsampling(); + chroma_subsampling_is_set_ = true; + } + if (!transfer_function_is_set_ || other.transfer_function_is_set_) { + if (transfer_function_is_set_ && + transfer_function_ != other.transfer_function_) { + LOG(WARNING) << "VPx transfer function is inconsistent, " + << transfer_function_ << " vs " + << other.transfer_function(); + } + transfer_function_ = other.transfer_function(); + transfer_function_is_set_ = true; + } + if (!video_full_range_flag_is_set_ || other.video_full_range_flag_is_set_) { + if (video_full_range_flag_is_set_ && + video_full_range_flag_ != other.video_full_range_flag_) { + LOG(WARNING) << "VPx video full-range flag is inconsistent, " + << video_full_range_flag_<< " vs " + << other.video_full_range_flag(); + } + video_full_range_flag_ = other.video_full_range_flag(); + video_full_range_flag_is_set_ = true; + } + if (codec_initialization_data_.empty() || + !other.codec_initialization_data_.empty()) { + if (!codec_initialization_data_.empty() && + codec_initialization_data_ != other.codec_initialization_data_) { + LOG(WARNING) << "VPx codec initialization data is inconsistent"; + } + codec_initialization_data_ = other.codec_initialization_data_; + } +} + } // namespace media } // namespace shaka diff --git a/packager/media/codecs/vp_codec_configuration_record.h b/packager/media/codecs/vp_codec_configuration_record.h index fc6363a284..b298bdd9d1 100644 --- a/packager/media/codecs/vp_codec_configuration_record.h +++ b/packager/media/codecs/vp_codec_configuration_record.h @@ -51,26 +51,52 @@ class VPCodecConfigurationRecord { const std::vector& codec_initialization_data); ~VPCodecConfigurationRecord(); - /// Parses input to extract VP codec configuration record. + /// Parses input (in MP4 format) to extract VP codec configuration record. /// @return false if there is parsing errors. - bool Parse(const std::vector& data); + bool ParseMP4(const std::vector& data); + + /// Parses input (in WebM format) to extract VP codec configuration record. + /// @return false if there is parsing errors. + bool ParseWebM(const std::vector& data); /// @param data should not be null. - /// Writes VP codec configuration record to buffer. - void Write(std::vector* data) const; + /// Writes VP codec configuration record to buffer using MP4 format. + void WriteMP4(std::vector* data) const; + + /// @param data should not be null. + /// Writes VP codec configuration record to buffer using WebM format. + void WriteWebM(std::vector* data) const; /// @return The codec string. std::string GetCodecString(VideoCodec codec) const; - void set_profile(uint8_t profile) { profile_ = profile; } - void set_level(uint8_t level) { level_ = level; } - void set_bit_depth(uint8_t bit_depth) { bit_depth_ = bit_depth; } - void set_color_space(uint8_t color_space) { color_space_ = color_space; } + // Merges the values from the given configuration. If there are values in + // both |*this| and |other|, the values in |other| take precedence. + void MergeFrom(const VPCodecConfigurationRecord& other); + + void set_profile(uint8_t profile) { + profile_ = profile; + profile_is_set_ = true; + } + 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) { 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) { video_full_range_flag_ = video_full_range_flag; @@ -85,13 +111,20 @@ class VPCodecConfigurationRecord { bool video_full_range_flag() const { return video_full_range_flag_; } private: - uint8_t profile_; - uint8_t level_; - uint8_t bit_depth_; - uint8_t color_space_; - uint8_t chroma_subsampling_; - uint8_t transfer_function_; - bool video_full_range_flag_; + uint8_t profile_ = 0; + uint8_t level_ = 0; + uint8_t bit_depth_ = 0; + uint8_t color_space_ = 0; + uint8_t chroma_subsampling_ = 0; + uint8_t transfer_function_ = 0; + bool video_full_range_flag_ = false; + bool profile_is_set_ = false; + 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 codec_initialization_data_; // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler diff --git a/packager/media/codecs/vp_codec_configuration_record_unittest.cc b/packager/media/codecs/vp_codec_configuration_record_unittest.cc index caea6993cd..2dac9227d9 100644 --- a/packager/media/codecs/vp_codec_configuration_record_unittest.cc +++ b/packager/media/codecs/vp_codec_configuration_record_unittest.cc @@ -17,7 +17,7 @@ TEST(VPCodecConfigurationRecordTest, Parse) { }; VPCodecConfigurationRecord vp_config; - ASSERT_TRUE(vp_config.Parse(std::vector( + ASSERT_TRUE(vp_config.ParseMP4(std::vector( kVpCodecConfigurationData, kVpCodecConfigurationData + arraysize(kVpCodecConfigurationData)))); @@ -38,19 +38,38 @@ TEST(VPCodecConfigurationRecordTest, ParseWithInsufficientData) { }; VPCodecConfigurationRecord vp_config; - ASSERT_FALSE(vp_config.Parse(std::vector( + ASSERT_FALSE(vp_config.ParseMP4(std::vector( kVpCodecConfigurationData, kVpCodecConfigurationData + arraysize(kVpCodecConfigurationData)))); } -TEST(VPCodecConfigurationRecordTest, Write) { +TEST(VPCodecConfigurationRecordTest, WriteMP4) { const uint8_t kExpectedVpCodecConfigurationData[] = { 0x02, 0x01, 0x80, 0x21, 0x00, 0x00, }; VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x00, 0x02, 0x00, true, std::vector()); std::vector data; - vp_config.Write(&data); + vp_config.WriteMP4(&data); + + EXPECT_EQ( + std::vector(kExpectedVpCodecConfigurationData, + kExpectedVpCodecConfigurationData + + arraysize(kExpectedVpCodecConfigurationData)), + data); +} + +TEST(VPCodecConfigurationRecordTest, WriteWebM) { + const uint8_t kExpectedVpCodecConfigurationData[] = { + 0x01, 0x01, 0x02, + 0x02, 0x01, 0x01, + 0x03, 0x01, 0x08, + 0x04, 0x01, 0x03 + }; + VPCodecConfigurationRecord vp_config(0x02, 0x01, 0x08, 0x00, 0x03, 0x00, true, + std::vector()); + std::vector data; + vp_config.WriteWebM(&data); EXPECT_EQ( std::vector(kExpectedVpCodecConfigurationData, diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 75b73bf824..5f1dbbec4a 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -562,7 +562,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { case FOURCC_vp09: case FOURCC_vp10: { VPCodecConfigurationRecord vp_config; - if (!vp_config.Parse(entry.codec_configuration.data)) { + if (!vp_config.ParseMP4(entry.codec_configuration.data)) { LOG(ERROR) << "Failed to parse vpcc."; return false; } diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index b6a231239d..8f63e7f97d 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -14,6 +14,7 @@ #include "packager/media/base/muxer_util.h" #include "packager/media/base/stream_info.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/codecs/vp_codec_configuration_record.h" #include "packager/media/event/muxer_listener.h" #include "packager/media/event/progress_listener.h" #include "packager/third_party/libwebm/src/mkvmuxerutil.hpp" @@ -271,8 +272,23 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) { track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId); } else if (info->codec() == kCodecVP9) { track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId); + + // The |StreamInfo::extra_data| field is stored using the MP4 format; we + // need to convert it to the WebM format. + VPCodecConfigurationRecord vp_config; + if (!vp_config.ParseMP4(info->extra_data())) { + return Status(error::INTERNAL_ERROR, + "Unable to parse VP9 codec configuration"); + } + + std::vector extra_data; + vp_config.WriteWebM(&extra_data); + if (!track->SetCodecPrivate(extra_data.data(), extra_data.size())) { + return Status(error::INTERNAL_ERROR, + "Private codec data required for VP9 streams"); + } } else { - LOG(ERROR) << "Only VP8 and VP9 video codec is supported."; + LOG(ERROR) << "Only VP8 and VP9 video codecs are supported."; return Status(error::UNIMPLEMENTED, "Only VP8 and VP9 video codecs are supported."); } diff --git a/packager/media/formats/webm/webm_cluster_parser.cc b/packager/media/formats/webm/webm_cluster_parser.cc index fdfb2e74fa..946c6a3aa9 100644 --- a/packager/media/formats/webm/webm_cluster_parser.cc +++ b/packager/media/formats/webm/webm_cluster_parser.cc @@ -456,12 +456,15 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, return false; } - const VPCodecConfigurationRecord* codec_config = - &vpx_parser->codec_config(); + VPCodecConfigurationRecord codec_config; + if (!video_stream_info_->extra_data().empty()) + codec_config.ParseMP4(video_stream_info_->extra_data()); + codec_config.MergeFrom(vpx_parser->codec_config()); + video_stream_info_->set_codec_string( - codec_config->GetCodecString(video_stream_info_->codec())); + codec_config.GetCodecString(video_stream_info_->codec())); std::vector extra_data; - codec_config->Write(&extra_data); + codec_config.WriteMP4(&extra_data); video_stream_info_->set_extra_data(extra_data); streams.push_back(video_stream_info_); init_cb_.Run(streams); diff --git a/packager/media/formats/webm/webm_video_client.cc b/packager/media/formats/webm/webm_video_client.cc index dc832e23c1..200442f90e 100644 --- a/packager/media/formats/webm/webm_video_client.cc +++ b/packager/media/formats/webm/webm_video_client.cc @@ -6,6 +6,7 @@ #include "packager/base/logging.h" #include "packager/base/stl_util.h" +#include "packager/media/codecs/vp_codec_configuration_record.h" #include "packager/media/formats/webm/webm_constants.h" namespace { @@ -50,13 +51,22 @@ void WebMVideoClient::Reset() { scoped_refptr WebMVideoClient::GetVideoStreamInfo( int64_t track_num, const std::string& codec_id, - const std::vector& codec_private, + const std::vector& codec_private_in, bool is_encrypted) { + std::vector codec_private = codec_private_in; VideoCodec video_codec = kUnknownVideoCodec; if (codec_id == "V_VP8") { video_codec = kCodecVP8; } else if (codec_id == "V_VP9") { video_codec = kCodecVP9; + + // Need to parse and convert the codec private data to MP4 format. + VPCodecConfigurationRecord vp_config; + if (!vp_config.ParseWebM(codec_private)) { + LOG(ERROR) << "Unable to parse VP9 codec configuration"; + return scoped_refptr(); + } + vp_config.WriteMP4(&codec_private); } else if (codec_id == "V_VP10") { video_codec = kCodecVP10; } else { @@ -107,8 +117,8 @@ scoped_refptr WebMVideoClient::GetVideoStreamInfo( return scoped_refptr(new VideoStreamInfo( track_num, kWebMTimeScale, 0, video_codec, std::string(), std::string(), - width_after_crop, height_after_crop, sar_x, sar_y, 0, 0, NULL, 0, - is_encrypted)); + width_after_crop, height_after_crop, sar_x, sar_y, 0, 0, + codec_private.data(), codec_private.size(), is_encrypted)); } bool WebMVideoClient::OnUInt(int id, int64_t val) {