diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 3100be2315..a215ea44f3 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -1203,6 +1203,15 @@ class PackagerFunctionalTest(PackagerAppTest): self.assertPackageSuccess(streams, flags) self._CheckTestResults('hevc-with-encryption', verify_decryption=True) + def testDolbyVisionWithEncryption(self): + streams = [ + self._GetStream('video', test_file='426x240-dvh1.mp4') + ] + flags = self._GetFlags(encryption=True, output_dash=True, output_hls=True) + + self.assertPackageSuccess(streams, flags) + self._CheckTestResults('dvh1-with-encryption') + def testVp8Mp4WithEncryption(self): streams = [ self._GetStream('video', diff --git a/packager/app/test/testdata/dvh1-with-encryption/426x240-dvh1-video.mp4 b/packager/app/test/testdata/dvh1-with-encryption/426x240-dvh1-video.mp4 new file mode 100644 index 0000000000..c41c6c4ee4 Binary files /dev/null and b/packager/app/test/testdata/dvh1-with-encryption/426x240-dvh1-video.mp4 differ diff --git a/packager/app/test/testdata/dvh1-with-encryption/output.m3u8 b/packager/app/test/testdata/dvh1-with-encryption/output.m3u8 new file mode 100644 index 0000000000..cbf30c150f --- /dev/null +++ b/packager/app/test/testdata/dvh1-with-encryption/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=375371,AVERAGE-BANDWIDTH=368196,CODECS="dvh1.05.01",RESOLUTION=426x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/dvh1-with-encryption/output.mpd b/packager/app/test/testdata/dvh1-with-encryption/output.mpd new file mode 100644 index 0000000000..e18101a2a9 --- /dev/null +++ b/packager/app/test/testdata/dvh1-with-encryption/output.mpd @@ -0,0 +1,18 @@ + + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + 426x240-dvh1-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/dvh1-with-encryption/stream_0.m3u8 b/packager/app/test/testdata/dvh1-with-encryption/stream_0.m3u8 new file mode 100644 index 0000000000..013f75d541 --- /dev/null +++ b/packager/app/test/testdata/dvh1-with-encryption/stream_0.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:6 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="426x240-dvh1-video.mp4",BYTERANGE="1353@0" +#EXT-X-KEY:METHOD=SAMPLE-AES-CTR,URI="data:text/plain;base64,MTIzNDU2Nzg5MDEyMzQ1Ng==",KEYFORMAT="identity" +#EXTINF:5.005, +#EXT-X-BYTERANGE:234841@1421 +426x240-dvh1-video.mp4 +#EXTINF:5.005, +#EXT-X-BYTERANGE:218284 +426x240-dvh1-video.mp4 +#EXTINF:0.042, +#EXT-X-BYTERANGE:9513 +426x240-dvh1-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index c1185a1843..d62435b9e8 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -53,6 +53,10 @@ enum FourCC : uint32_t { FOURCC_dtsl = 0x6474736c, FOURCC_dtsm = 0x6474732d, // "dts-" FOURCC_dtsp = 0x6474732b, // "dts+" + FOURCC_dvcC = 0x64766343, + FOURCC_dvh1 = 0x64766831, + FOURCC_dvhe = 0x64766865, + FOURCC_dvvC = 0x64767643, FOURCC_ec_3 = 0x65632d33, // "ec-3" FOURCC_ec3d = 0x65633364, FOURCC_edts = 0x65647473, @@ -69,6 +73,7 @@ enum FourCC : uint32_t { FOURCC_hint = 0x68696e74, FOURCC_hvc1 = 0x68766331, FOURCC_hvcC = 0x68766343, + FOURCC_hvcE = 0x68766345, FOURCC_iden = 0x6964656e, FOURCC_iso6 = 0x69736f36, FOURCC_iso8 = 0x69736f38, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index ac9b9d5947..ba5bbc7dcd 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -32,6 +32,7 @@ enum Codec { kCodecAV1 = kCodecVideo, kCodecH264, kCodecH265, + kCodecH265DolbyVision, kCodecVP8, kCodecVP9, kCodecVideoMaxPlusOne, diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index e9caa507b5..1e58f0f578 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -24,6 +24,8 @@ std::string VideoCodecToString(Codec codec) { return "H264"; case kCodecH265: return "H265"; + case kCodecH265DolbyVision: + return "H265 Dolby Vision"; case kCodecVP8: return "VP8"; case kCodecVP9: diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index 20d5f66a79..5c6a49a063 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -53,6 +53,7 @@ class VideoStreamInfo : public StreamInfo { std::unique_ptr Clone() const override; /// @} + const std::vector& extra_config() const { return extra_config_; } H26xStreamFormat h26x_stream_format() const { return h26x_stream_format_; } uint16_t width() const { return width_; } uint16_t height() const { return height_; } @@ -67,6 +68,9 @@ class VideoStreamInfo : public StreamInfo { uint32_t playback_rate() const { return playback_rate_; } const std::vector& eme_init_data() const { return eme_init_data_; } + void set_extra_config(const std::vector& extra_config) { + extra_config_ = extra_config; + } void set_width(uint32_t width) { width_ = width; } void set_height(uint32_t height) { height_ = height; } void set_pixel_width(uint32_t pixel_width) { pixel_width_ = pixel_width; } @@ -83,6 +87,9 @@ class VideoStreamInfo : public StreamInfo { } private: + // Extra codec configuration in a stream of mp4 boxes. It is only applicable + // to mp4 container only. It is needed by some codecs, e.g. Dolby Vision. + std::vector extra_config_; H26xStreamFormat h26x_stream_format_; uint16_t width_; uint16_t height_; diff --git a/packager/media/codecs/codecs.gyp b/packager/media/codecs/codecs.gyp index d8978925da..b965f630b0 100644 --- a/packager/media/codecs/codecs.gyp +++ b/packager/media/codecs/codecs.gyp @@ -25,6 +25,8 @@ 'avc_decoder_configuration_record.h', 'decoder_configuration_record.cc', 'decoder_configuration_record.h', + 'dovi_decoder_configuration_record.cc', + 'dovi_decoder_configuration_record.h', 'ec3_audio_util.cc', 'ec3_audio_util.h', 'es_descriptor.cc', @@ -73,6 +75,7 @@ 'av1_codec_configuration_record_unittest.cc', 'av1_parser_unittest.cc', 'avc_decoder_configuration_record_unittest.cc', + 'dovi_decoder_configuration_record_unittest.cc', 'ec3_audio_util_unittest.cc', 'es_descriptor_unittest.cc', 'h264_byte_to_unit_stream_converter_unittest.cc', diff --git a/packager/media/codecs/dovi_decoder_configuration_record.cc b/packager/media/codecs/dovi_decoder_configuration_record.cc new file mode 100644 index 0000000000..31c5688774 --- /dev/null +++ b/packager/media/codecs/dovi_decoder_configuration_record.cc @@ -0,0 +1,38 @@ +// Copyright 2019 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/codecs/dovi_decoder_configuration_record.h" + +#include "packager/base/strings/stringprintf.h" +#include "packager/media/base/bit_reader.h" +#include "packager/media/base/rcheck.h" + +namespace shaka { +namespace media { + +bool DOVIDecoderConfigurationRecord::Parse(const std::vector& data) { + BitReader reader(data.data(), data.size()); + + // Dolby Vision Streams Within the ISO Base Media File Format Version 2.0: + // https://www.dolby.com/us/en/technologies/dolby-vision/dolby-vision-bitstreams-within-the-iso-base-media-file-format-v2.0.pdf + uint8_t major_version = 0; + uint8_t minor_version = 0; + RCHECK(reader.ReadBits(8, &major_version) && major_version == 1 && + reader.ReadBits(8, &minor_version) && minor_version == 0 && + reader.ReadBits(7, &profile_) && reader.ReadBits(6, &level_)); + return true; +} + +std::string DOVIDecoderConfigurationRecord::GetCodecString( + FourCC codec_fourcc) const { + // Dolby Vision Streams within the HTTP Live Streaming format Version 2.0: + // https://www.dolby.com/us/en/technologies/dolby-vision/dolby-vision-streams-within-the-http-live-streaming-format-v2.0.pdf + return base::StringPrintf( + "%s.%02d.%02d", FourCCToString(codec_fourcc).c_str(), profile_, level_); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/codecs/dovi_decoder_configuration_record.h b/packager/media/codecs/dovi_decoder_configuration_record.h new file mode 100644 index 0000000000..0dc4ebb6af --- /dev/null +++ b/packager/media/codecs/dovi_decoder_configuration_record.h @@ -0,0 +1,51 @@ +// Copyright 2019 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#ifndef PACKAGER_MEDIA_CODECS_DOVI_DECODER_CONFIGURATION_RECORD_H_ +#define PACKAGER_MEDIA_CODECS_DOVI_DECODER_CONFIGURATION_RECORD_H_ + +#include +#include +#include + +#include "packager/media/base/fourccs.h" + +namespace shaka { +namespace media { + +/// Class for parsing Dolby Vision decoder configuration record. +// Implemented according to Dolby Vision Streams Within the ISO Base Media File +// Format Version 2.0: +// https://www.dolby.com/us/en/technologies/dolby-vision/dolby-vision-bitstreams-within-the-iso-base-media-file-format-v2.0.pdf +// and Dolby Vision Streams within the HTTP Live Streaming format Version 2.0: +// https://www.dolby.com/us/en/technologies/dolby-vision/dolby-vision-streams-within-the-http-live-streaming-format-v2.0.pdf +class DOVIDecoderConfigurationRecord { + public: + DOVIDecoderConfigurationRecord() = default; + ~DOVIDecoderConfigurationRecord() = default; + + /// Parses input to extract decoder configuration record. + /// @return false if there are parsing errors. + bool Parse(const std::vector& data); + + /// @return The codec string in the format defined by RFC6381. It is used in + /// DASH and HLS manifests. + std::string GetCodecString(FourCC codec_fourcc) const; + + private: + DOVIDecoderConfigurationRecord(const DOVIDecoderConfigurationRecord&) = + delete; + DOVIDecoderConfigurationRecord& operator=( + const DOVIDecoderConfigurationRecord&) = delete; + + uint8_t profile_ = 0; + uint8_t level_ = 0; +}; + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_CODECS_DOVI_DECODER_CONFIGURATION_RECORD_H_ diff --git a/packager/media/codecs/dovi_decoder_configuration_record_unittest.cc b/packager/media/codecs/dovi_decoder_configuration_record_unittest.cc new file mode 100644 index 0000000000..144a7f36f6 --- /dev/null +++ b/packager/media/codecs/dovi_decoder_configuration_record_unittest.cc @@ -0,0 +1,48 @@ +// Copyright 2019 Google LLC. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +#include "packager/media/codecs/dovi_decoder_configuration_record.h" + +#include + +#include "packager/base/macros.h" + +namespace shaka { +namespace media { + +TEST(DOVIDecoderConfigurationRecordTest, Success) { + const std::vector dovi_config_data = { + 0x01, // Major Version + 0x00, // Minor Version + 0x05 << 1, // Profile + 0x08 << 3, // Level + // Other data we do not care. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + DOVIDecoderConfigurationRecord dovi_config; + ASSERT_TRUE(dovi_config.Parse(dovi_config_data)); + + EXPECT_EQ("dvh1.05.08", dovi_config.GetCodecString(FOURCC_dvh1)); + EXPECT_EQ("dvhe.05.08", dovi_config.GetCodecString(FOURCC_dvhe)); +} + +TEST(DOVIDecoderConfigurationRecordTest, FailOnIncorectVersion) { + const std::vector dovi_config_data = {0x02, 0x00, 0x0A, 0x04}; + + DOVIDecoderConfigurationRecord dovi_config; + ASSERT_FALSE(dovi_config.Parse(dovi_config_data)); +} + +TEST(DOVIDecoderConfigurationRecordTest, FailOnInsufficientData) { + const std::vector dovi_config_data = {0x01, 0x00, 0x0A}; + + DOVIDecoderConfigurationRecord dovi_config; + ASSERT_FALSE(dovi_config.Parse(dovi_config_data)); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/crypto/subsample_generator.cc b/packager/media/crypto/subsample_generator.cc index 8a572bb771..d2d3c70d27 100644 --- a/packager/media/crypto/subsample_generator.cc +++ b/packager/media/crypto/subsample_generator.cc @@ -142,6 +142,7 @@ Status SubsampleGenerator::Initialize(FourCC protection_scheme, header_parser_.reset(new H264VideoSliceHeaderParser); break; case kCodecH265: + case kCodecH265DolbyVision: header_parser_.reset(new H265VideoSliceHeaderParser); break; default: @@ -220,6 +221,7 @@ Status SubsampleGenerator::GenerateSubsamples( case kCodecH264: FALLTHROUGH_INTENDED; case kCodecH265: + case kCodecH265DolbyVision: return GenerateSubsamplesFromH26xFrame(frame, frame_size, subsamples); case kCodecVP9: if (vp9_subsample_encryption_) @@ -300,7 +302,8 @@ Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame( SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples); const Nalu::CodecType nalu_type = - (codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264; + (codec_ == kCodecH265 || codec_ == kCodecH265DolbyVision) ? Nalu::kH265 + : Nalu::kH264; NaluReader reader(nalu_type, nalu_length_size_, frame, frame_size); Nalu nalu; diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 7785b91ee0..afac5cae52 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -36,6 +36,7 @@ const uint16_t kVideoDepth = 0x0018; const uint32_t kCompressorNameSize = 32u; const char kAv1CompressorName[] = "\012AOM Coding"; const char kAvcCompressorName[] = "\012AVC Coding"; +const char kDolbyVisionCompressorName[] = "\013DOVI Coding"; const char kHevcCompressorName[] = "\013HEVC Coding"; const char kVpcCompressorName[] = "\012VPC Coding"; @@ -1488,6 +1489,11 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { compressor_name.assign(std::begin(kAvcCompressorName), std::end(kAvcCompressorName)); break; + case FOURCC_dvh1: + case FOURCC_dvhe: + compressor_name.assign(std::begin(kDolbyVisionCompressorName), + std::end(kDolbyVisionCompressorName)); + break; case FOURCC_hev1: case FOURCC_hvc1: compressor_name.assign(std::begin(kHevcCompressorName), @@ -1544,6 +1550,27 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { return false; RCHECK(buffer->ReadWriteChild(&codec_configuration)); + + if (buffer->Reading()) { + extra_codec_configs.clear(); + // Handle Dolby Vision boxes. + const bool is_hevc = + actual_format == FOURCC_dvhe || actual_format == FOURCC_dvh1 || + actual_format == FOURCC_hev1 || actual_format == FOURCC_hvc1; + if (is_hevc) { + for (FourCC fourcc : {FOURCC_dvcC, FOURCC_dvvC, FOURCC_hvcE}) { + CodecConfiguration dv_box; + dv_box.box_type = fourcc; + RCHECK(buffer->TryReadWriteChild(&dv_box)); + if (!dv_box.data.empty()) + extra_codec_configs.push_back(std::move(dv_box)); + } + } + } else { + for (CodecConfiguration& extra_codec_config : extra_codec_configs) + RCHECK(buffer->ReadWriteChild(&extra_codec_config)); + } + RCHECK(buffer->TryReadWriteChild(&pixel_aspect)); // Somehow Edge does not support having sinf box before codec_configuration, @@ -1562,12 +1589,15 @@ size_t VideoSampleEntry::ComputeSizeInternal() { return 0; codec_configuration.box_type = GetCodecConfigurationBoxType(actual_format); DCHECK_NE(codec_configuration.box_type, FOURCC_NULL); - return HeaderSize() + sizeof(data_reference_index) + sizeof(width) + - sizeof(height) + sizeof(kVideoResolution) * 2 + - sizeof(kVideoFrameCount) + sizeof(kVideoDepth) + - pixel_aspect.ComputeSize() + sinf.ComputeSize() + - codec_configuration.ComputeSize() + kCompressorNameSize + 6 + 4 + 16 + - 2; // 6 + 4 bytes reserved, 16 + 2 bytes predefined. + size_t size = HeaderSize() + sizeof(data_reference_index) + sizeof(width) + + sizeof(height) + sizeof(kVideoResolution) * 2 + + sizeof(kVideoFrameCount) + sizeof(kVideoDepth) + + pixel_aspect.ComputeSize() + sinf.ComputeSize() + + codec_configuration.ComputeSize() + kCompressorNameSize + 6 + + 4 + 16 + 2; // 6 + 4 bytes reserved, 16 + 2 bytes predefined. + for (CodecConfiguration& codec_config : extra_codec_configs) + size += codec_config.ComputeSize(); + return size; } FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { @@ -1577,6 +1607,8 @@ FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { case FOURCC_avc1: case FOURCC_avc3: return FOURCC_avcC; + case FOURCC_dvh1: + case FOURCC_dvhe: case FOURCC_hev1: case FOURCC_hvc1: return FOURCC_hvcC; @@ -1589,6 +1621,33 @@ FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { } } +std::vector VideoSampleEntry::ExtraCodecConfigsAsVector() const { + BufferWriter buffer; + for (CodecConfiguration codec_config : extra_codec_configs) + codec_config.Write(&buffer); + return std::vector(buffer.Buffer(), buffer.Buffer() + buffer.Size()); +} + +bool VideoSampleEntry::ParseExtraCodecConfigsVector( + const std::vector& data) { + extra_codec_configs.clear(); + size_t pos = 0; + while (pos < data.size()) { + bool err = false; + std::unique_ptr box_reader( + BoxReader::ReadBox(data.data() + pos, data.size() - pos, &err)); + RCHECK(!err && box_reader); + + CodecConfiguration codec_config; + codec_config.box_type = box_reader->type(); + RCHECK(codec_config.Parse(box_reader.get())); + extra_codec_configs.push_back(std::move(codec_config)); + + pos += box_reader->pos(); + } + return true; +} + ElementaryStreamDescriptor::ElementaryStreamDescriptor() = default; ElementaryStreamDescriptor::~ElementaryStreamDescriptor() = default; diff --git a/packager/media/formats/mp4/box_definitions.h b/packager/media/formats/mp4/box_definitions.h index b6ab86d18c..164541340b 100644 --- a/packager/media/formats/mp4/box_definitions.h +++ b/packager/media/formats/mp4/box_definitions.h @@ -284,6 +284,11 @@ struct VideoSampleEntry : Box { // Returns the box type of codec configuration box from video format. FourCC GetCodecConfigurationBoxType(FourCC format) const; + // Convert |extra_codec_configs| to vector. + std::vector ExtraCodecConfigsAsVector() const; + // Parse |extra_codec_configs| from vector. + bool ParseExtraCodecConfigsVector(const std::vector& data); + FourCC format = FOURCC_NULL; // data_reference_index is 1-based and "dref" box is mandatory so it is // always present. @@ -294,6 +299,9 @@ struct VideoSampleEntry : Box { PixelAspectRatio pixel_aspect; ProtectionSchemeInfo sinf; CodecConfiguration codec_configuration; + // Some codecs, e.g. Dolby Vision, have extra codec configuration boxes that + // need to be propagated to muxers. + std::vector extra_codec_configs; }; struct ElementaryStreamDescriptor : FullBox { diff --git a/packager/media/formats/mp4/box_definitions_unittest.cc b/packager/media/formats/mp4/box_definitions_unittest.cc index 9545c98dd0..2323ac97c7 100644 --- a/packager/media/formats/mp4/box_definitions_unittest.cc +++ b/packager/media/formats/mp4/box_definitions_unittest.cc @@ -355,19 +355,30 @@ class BoxDefinitionsTestGeneral : public testing::Test { kCodecConfigurationData + arraysize(kCodecConfigurationData)); } - void Fill(VideoSampleEntry* encv) { - encv->format = FOURCC_encv; - encv->data_reference_index = 1; - encv->width = 800; - encv->height = 600; - Fill(&encv->pixel_aspect); - Fill(&encv->sinf); - Fill(&encv->codec_configuration); + void Fill(VideoSampleEntry* entry) { + entry->format = FOURCC_encv; + entry->data_reference_index = 1; + entry->width = 800; + entry->height = 600; + Fill(&entry->pixel_aspect); + Fill(&entry->sinf); + Fill(&entry->codec_configuration); + + const uint8_t kExtraCodecConfigData[] = {0x01, 0x02, 0x03, 0x04}; + CodecConfiguration extra_codec_config; + extra_codec_config.data.assign(std::begin(kExtraCodecConfigData), + std::end(kExtraCodecConfigData)); + for (FourCC fourcc : {FOURCC_dvcC, FOURCC_dvvC, FOURCC_hvcE}) { + extra_codec_config.box_type = fourcc; + entry->extra_codec_configs.push_back(extra_codec_config); + // Increment it so the boxes have different data. + extra_codec_config.data[0]++; + } } - void Modify(VideoSampleEntry* encv) { - encv->height += 600; - Modify(&encv->codec_configuration); + void Modify(VideoSampleEntry* entry) { + entry->height += 600; + Modify(&entry->codec_configuration); } void Fill(ElementaryStreamDescriptor* esds) { @@ -1261,6 +1272,24 @@ TEST_F(BoxDefinitionsTest, FlacSampleEntry) { ASSERT_EQ(entry, entry_readback); } +TEST_F(BoxDefinitionsTest, SampleEntryExtraCodecConfigs) { + VideoSampleEntry entry; + Fill(&entry); + + const uint8_t kExpectedVector[] = { + 0, 0, 0, 12, 'd', 'v', 'c', 'C', 1, 2, 3, 4, + 0, 0, 0, 12, 'd', 'v', 'v', 'C', 2, 2, 3, 4, + 0, 0, 0, 12, 'h', 'v', 'c', 'E', 3, 2, 3, 4, + }; + const std::vector expected_vector(std::begin(kExpectedVector), + std::end(kExpectedVector)); + EXPECT_EQ(expected_vector, entry.ExtraCodecConfigsAsVector()); + + VideoSampleEntry new_entry; + ASSERT_TRUE(new_entry.ParseExtraCodecConfigsVector(expected_vector)); + EXPECT_EQ(entry.extra_codec_configs, new_entry.extra_codec_configs); +} + TEST_F(BoxDefinitionsTest, CompactSampleSize_FieldSize16) { CompactSampleSize stz2; stz2.field_size = 16; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 5dcf329ce9..77371f8c68 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -24,6 +24,7 @@ #include "packager/media/codecs/ac3_audio_util.h" #include "packager/media/codecs/av1_codec_configuration_record.h" #include "packager/media/codecs/avc_decoder_configuration_record.h" +#include "packager/media/codecs/dovi_decoder_configuration_record.h" #include "packager/media/codecs/ec3_audio_util.h" #include "packager/media/codecs/es_descriptor.h" #include "packager/media/codecs/hevc_decoder_configuration_record.h" @@ -46,13 +47,13 @@ uint64_t Rescale(uint64_t time_in_old_scale, H26xStreamFormat GetH26xStreamFormat(FourCC fourcc) { switch (fourcc) { case FOURCC_avc1: - return H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus; - case FOURCC_avc3: - return H26xStreamFormat::kNalUnitStreamWithParameterSetNalus; - case FOURCC_hev1: - return H26xStreamFormat::kNalUnitStreamWithParameterSetNalus; + case FOURCC_dvh1: case FOURCC_hvc1: return H26xStreamFormat::kNalUnitStreamWithoutParameterSetNalus; + case FOURCC_avc3: + case FOURCC_dvhe: + case FOURCC_hev1: + return H26xStreamFormat::kNalUnitStreamWithParameterSetNalus; default: return H26xStreamFormat::kUnSpecified; } @@ -65,6 +66,9 @@ Codec FourCCToCodec(FourCC fourcc) { case FOURCC_avc1: case FOURCC_avc3: return kCodecH264; + case FOURCC_dvh1: + case FOURCC_dvhe: + return kCodecH265DolbyVision; case FOURCC_hev1: case FOURCC_hvc1: return kCodecH265; @@ -115,6 +119,36 @@ Codec ObjectTypeToCodec(ObjectType object_type) { } } +std::vector GetDOVIDecoderConfig( + const std::vector& configs) { + for (const CodecConfiguration& config : configs) { + if (config.box_type == FOURCC_dvcC || config.box_type == FOURCC_dvvC) { + return config.data; + } + } + return std::vector(); +} + +bool UpdateCodecStringForDolbyVision( + FourCC actual_format, + const std::vector& configs, + std::string* codec_string) { + DOVIDecoderConfigurationRecord dovi_config; + if (!dovi_config.Parse(GetDOVIDecoderConfig(configs))) { + LOG(ERROR) << "Failed to parse Dolby Vision decoder " + "configuration record."; + return false; + } + if (actual_format == FOURCC_dvh1 || actual_format == FOURCC_dvhe) { + // Non-Backward compatibility mode. Replace the code string with + // Dolby Vision only. + *codec_string = dovi_config.GetCodecString(actual_format); + } else { + // TODO(kqyang): Support backward compatible signaling. + } + return true; +} + const uint64_t kNanosecondsPerSecond = 1000000000ull; } // namespace @@ -579,6 +613,8 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } break; } + case FOURCC_dvh1: + case FOURCC_dvhe: case FOURCC_hev1: case FOURCC_hvc1: { HEVCDecoderConfigurationRecord hevc_config; @@ -588,6 +624,13 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } codec_string = hevc_config.GetCodecString(actual_format); nalu_length_size = hevc_config.nalu_length_size(); + + if (!entry.extra_codec_configs.empty()) { + if (!UpdateCodecStringForDolbyVision( + actual_format, entry.extra_codec_configs, &codec_string)) { + return false; + } + } break; } case FOURCC_vp08: @@ -631,6 +674,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { coded_width, coded_height, pixel_width, pixel_height, 0, // trick_play_factor nalu_length_size, track->media.header.language.code, is_encrypted)); + video_stream_info->set_extra_config(entry.ExtraCodecConfigsAsVector()); // Set pssh raw data if it has. if (moov_->pssh.size() > 0) { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index b0f45ddced..4778bbfcf2 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -8,6 +8,7 @@ #include +#include "packager/base/strings/string_number_conversions.h" #include "packager/base/time/clock.h" #include "packager/base/time/time.h" #include "packager/file/file.h" @@ -56,6 +57,11 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { H26xStreamFormat::kNalUnitStreamWithParameterSetNalus ? FOURCC_hev1 : FOURCC_hvc1; + case kCodecH265DolbyVision: + return h26x_stream_format == + H26xStreamFormat::kNalUnitStreamWithParameterSetNalus + ? FOURCC_dvhe + : FOURCC_dvh1; case kCodecVP8: return FOURCC_vp08; case kCodecVP9: @@ -410,6 +416,12 @@ bool MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info, video.width = video_info->width(); video.height = video_info->height(); video.codec_configuration.data = video_info->codec_config(); + if (!video.ParseExtraCodecConfigsVector(video_info->extra_config())) { + LOG(ERROR) << "Malformed extra codec configs: " + << base::HexEncode(video_info->extra_config().data(), + video_info->extra_config().size()); + return false; + } if (pixel_width != 1 || pixel_height != 1) { video.pixel_aspect.h_spacing = pixel_width; video.pixel_aspect.v_spacing = pixel_height; diff --git a/packager/media/test/data/426x240-dvh1.mp4 b/packager/media/test/data/426x240-dvh1.mp4 new file mode 100644 index 0000000000..64bc837222 Binary files /dev/null and b/packager/media/test/data/426x240-dvh1.mp4 differ