diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 5237167380..194e16a193 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -849,6 +849,33 @@ class PackagerFunctionalTest(PackagerAppTest): self._GetFlags(output_dash=True)) self._CheckTestResults('vorbis-webm') + def testAv1Mp4(self): + self.assertPackageSuccess( + self._GetStreams(['video'], + output_format='mp4', + test_files=['bear-av1.mp4']), + self._GetFlags(output_dash=True, output_hls=True) + ) + self._CheckTestResults('av1-mp4') + + def testAv1Mp4ToWebM(self): + self.assertPackageSuccess( + self._GetStreams(['video'], + output_format='webm', + test_files=['bear-av1.mp4']), + self._GetFlags(output_dash=True, output_hls=True) + ) + self._CheckTestResults('av1-mp4-to-webm') + + def testAv1WebM(self): + self.assertPackageSuccess( + self._GetStreams(['video'], + output_format='mp4', + test_files=['bear-av1.webm']), + self._GetFlags(output_dash=True, output_hls=True) + ) + self._CheckTestResults('av1-webm') + def testEncryption(self): self.assertPackageSuccess( self._GetStreams(['audio', 'video']), diff --git a/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm b/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm new file mode 100644 index 0000000000..a945902fb0 Binary files /dev/null and b/packager/app/test/testdata/av1-mp4-to-webm/bear-av1-video.webm differ diff --git a/packager/app/test/testdata/av1-mp4-to-webm/output.m3u8 b/packager/app/test/testdata/av1-mp4-to-webm/output.m3u8 new file mode 100644 index 0000000000..815dd2738a --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-to-webm/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=69160,AVERAGE-BANDWIDTH=69160,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/av1-mp4-to-webm/output.mpd b/packager/app/test/testdata/av1-mp4-to-webm/output.mpd new file mode 100644 index 0000000000..938730bcd2 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-to-webm/output.mpd @@ -0,0 +1,14 @@ + + + + + + + bear-av1-video.webm + + + + + + + diff --git a/packager/app/test/testdata/av1-mp4-to-webm/stream_0.m3u8 b/packager/app/test/testdata/av1-mp4-to-webm/stream_0.m3u8 new file mode 100644 index 0000000000..75a85d62a7 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-to-webm/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-av1-video.webm",BYTERANGE="314@0" +#EXTINF:2.736, +#EXT-X-BYTERANGE:23653@333 +bear-av1-video.webm +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/av1-mp4/bear-av1-video.mp4 b/packager/app/test/testdata/av1-mp4/bear-av1-video.mp4 new file mode 100644 index 0000000000..fd64d498e1 Binary files /dev/null and b/packager/app/test/testdata/av1-mp4/bear-av1-video.mp4 differ diff --git a/packager/app/test/testdata/av1-mp4/output.m3u8 b/packager/app/test/testdata/av1-mp4/output.m3u8 new file mode 100644 index 0000000000..2ced9b0908 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=69777,AVERAGE-BANDWIDTH=69777,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/av1-mp4/output.mpd b/packager/app/test/testdata/av1-mp4/output.mpd new file mode 100644 index 0000000000..6dadc45a81 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4/output.mpd @@ -0,0 +1,14 @@ + + + + + + + bear-av1-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/av1-mp4/stream_0.m3u8 b/packager/app/test/testdata/av1-mp4/stream_0.m3u8 new file mode 100644 index 0000000000..3ab711063a --- /dev/null +++ b/packager/app/test/testdata/av1-mp4/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-av1-video.mp4",BYTERANGE="798@0" +#EXTINF:2.736, +#EXT-X-BYTERANGE:23864@842 +bear-av1-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/av1-webm/bear-av1-video.mp4 b/packager/app/test/testdata/av1-webm/bear-av1-video.mp4 new file mode 100644 index 0000000000..5021b08e92 Binary files /dev/null and b/packager/app/test/testdata/av1-webm/bear-av1-video.mp4 differ diff --git a/packager/app/test/testdata/av1-webm/output.m3u8 b/packager/app/test/testdata/av1-webm/output.m3u8 new file mode 100644 index 0000000000..7f14f48547 --- /dev/null +++ b/packager/app/test/testdata/av1-webm/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=70752,AVERAGE-BANDWIDTH=70752,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/av1-webm/output.mpd b/packager/app/test/testdata/av1-webm/output.mpd new file mode 100644 index 0000000000..cc84f078f7 --- /dev/null +++ b/packager/app/test/testdata/av1-webm/output.mpd @@ -0,0 +1,14 @@ + + + + + + + bear-av1-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/av1-webm/stream_0.m3u8 b/packager/app/test/testdata/av1-webm/stream_0.m3u8 new file mode 100644 index 0000000000..5182dd0c0d --- /dev/null +++ b/packager/app/test/testdata/av1-webm/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-av1-video.mp4",BYTERANGE="798@0" +#EXTINF:2.735, +#EXT-X-BYTERANGE:24188@842 +bear-av1-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/media/base/fourccs.h b/packager/media/base/fourccs.h index 5390b0d417..c1185a1843 100644 --- a/packager/media/base/fourccs.h +++ b/packager/media/base/fourccs.h @@ -22,6 +22,8 @@ enum FourCC : uint32_t { FOURCC_ac_3 = 0x61632d33, // "ac-3" FOURCC_ac3d = 0x61633364, FOURCC_apad = 0x61706164, + FOURCC_av01 = 0x61763031, + FOURCC_av1C = 0x61763143, FOURCC_avc1 = 0x61766331, FOURCC_avc3 = 0x61766333, FOURCC_avcC = 0x61766343, @@ -139,7 +141,6 @@ enum FourCC : uint32_t { FOURCC_vmhd = 0x766d6864, FOURCC_vp08 = 0x76703038, FOURCC_vp09 = 0x76703039, - FOURCC_vp10 = 0x76703130, FOURCC_vpcC = 0x76706343, FOURCC_vsid = 0x76736964, FOURCC_vttC = 0x76747443, diff --git a/packager/media/base/stream_info.h b/packager/media/base/stream_info.h index d6ce354476..ac9b9d5947 100644 --- a/packager/media/base/stream_info.h +++ b/packager/media/base/stream_info.h @@ -29,11 +29,11 @@ enum Codec { kUnknownCodec = 0, kCodecVideo = 100, - kCodecH264 = kCodecVideo, + kCodecAV1 = kCodecVideo, + kCodecH264, kCodecH265, kCodecVP8, kCodecVP9, - kCodecVP10, kCodecVideoMaxPlusOne, kCodecAudio = 200, diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index dc6f3eb194..e9caa507b5 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -18,6 +18,8 @@ namespace media { namespace { std::string VideoCodecToString(Codec codec) { switch (codec) { + case kCodecAV1: + return "AV1"; case kCodecH264: return "H264"; case kCodecH265: @@ -26,8 +28,6 @@ std::string VideoCodecToString(Codec codec) { return "VP8"; case kCodecVP9: return "VP9"; - case kCodecVP10: - return "VP10"; default: NOTIMPLEMENTED() << "Unknown Video Codec: " << codec; return "UnknownCodec"; diff --git a/packager/media/codecs/av1_codec_configuration_record.cc b/packager/media/codecs/av1_codec_configuration_record.cc new file mode 100644 index 0000000000..81f156ee28 --- /dev/null +++ b/packager/media/codecs/av1_codec_configuration_record.cc @@ -0,0 +1,90 @@ +// Copyright 2018 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/av1_codec_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 { + +AV1CodecConfigurationRecord::AV1CodecConfigurationRecord() = default; + +AV1CodecConfigurationRecord::~AV1CodecConfigurationRecord() = default; + +// https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-section +// aligned (8) class AV1CodecConfigurationRecord { +// unsigned int (1) marker = 1; +// unsigned int (7) version = 1; +// unsigned int (3) seq_profile; +// unsigned int (5) seq_level_idx_0; +// unsigned int (1) seq_tier_0; +// unsigned int (1) high_bitdepth; +// unsigned int (1) twelve_bit; +// unsigned int (1) monochrome; +// unsigned int (1) chroma_subsampling_x; +// unsigned int (1) chroma_subsampling_y; +// unsigned int (2) chroma_sample_position; +// unsigned int (3) reserved = 0; +// +// unsigned int (1) initial_presentation_delay_present; +// if (initial_presentation_delay_present) { +// unsigned int (4) initial_presentation_delay_minus_one; +// } else { +// unsigned int (4) reserved = 0; +// } +// +// unsigned int (8)[] configOBUs; +// } +bool AV1CodecConfigurationRecord::Parse(const uint8_t* data, size_t data_size) { + RCHECK(data_size > 0); + + BitReader reader(data, data_size); + + int marker; + RCHECK(reader.ReadBits(1, &marker)); + RCHECK(marker == 1); + + int version; + RCHECK(reader.ReadBits(7, &version)); + RCHECK(version == 1); + + RCHECK(reader.ReadBits(3, &profile_)); + RCHECK(reader.ReadBits(5, &level_)); + RCHECK(reader.ReadBits(1, &tier_)); + + int high_bitdepth; + int twelve_bit; + RCHECK(reader.ReadBits(1, &high_bitdepth)); + RCHECK(reader.ReadBits(1, &twelve_bit)); + bit_depth_ = twelve_bit ? 12 : (high_bitdepth ? 10 : 8); + + RCHECK(reader.ReadBits(1, &mono_chrome_)); + RCHECK(reader.ReadBits(1, &chroma_subsampling_x_)); + RCHECK(reader.ReadBits(1, &chroma_subsampling_y_)); + RCHECK(reader.ReadBits(2, &chroma_sample_position_)); + + // Skip other fields (e.g. initial_presentation_delay) which we do not need. + return true; +} + +// https://aomediacodec.github.io/av1-isobmff/#codecsparam +// ..... +// ... +// . +// The parameters starting from colorPrimaries are omitted as they are not +// present in AV1 Codec Configuration Record and they are optional. +std::string AV1CodecConfigurationRecord::GetCodecString() const { + return base::StringPrintf("av01.%d.%02d%c.%02d.%d.%d%d%d", profile_, level_, + tier_ ? 'H' : 'M', bit_depth_, mono_chrome_, + chroma_subsampling_x_, chroma_subsampling_y_, + chroma_sample_position_); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/codecs/av1_codec_configuration_record.h b/packager/media/codecs/av1_codec_configuration_record.h new file mode 100644 index 0000000000..463e3ba708 --- /dev/null +++ b/packager/media/codecs/av1_codec_configuration_record.h @@ -0,0 +1,54 @@ +// Copyright 2018 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_AV1_CODEC_CONFIGURATION_RECORD_H_ +#define PACKAGER_MEDIA_CODECS_AV1_CODEC_CONFIGURATION_RECORD_H_ + +#include +#include +#include + +namespace shaka { +namespace media { + +/// Class for parsing AV1 codec configuration record. +class AV1CodecConfigurationRecord { + public: + AV1CodecConfigurationRecord(); + ~AV1CodecConfigurationRecord(); + + /// Parses input to extract codec configuration record. + /// @return false if there are parsing errors. + bool Parse(const std::vector& data) { + return Parse(data.data(), data.size()); + } + + /// Parses input to extract decoder configuration record. + /// @return false if there are parsing errors. + bool Parse(const uint8_t* data, size_t data_size); + + /// @return The codec string. + std::string GetCodecString() const; + + private: + int profile_ = 0; + int level_ = 0; + int tier_ = 0; + int bit_depth_ = 0; + int mono_chrome_ = 0; + int chroma_subsampling_x_ = 0; + int chroma_subsampling_y_ = 0; + int chroma_sample_position_ = 0; + + // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler + // generated copy constructor and assignment operator. Since the internal data + // is small, the performance impact is minimal. +}; + +} // namespace media +} // namespace shaka + +#endif // PACKAGER_MEDIA_CODECS_AV1_CODEC_CONFIGURATION_RECORD_H_ diff --git a/packager/media/codecs/av1_codec_configuration_record_unittest.cc b/packager/media/codecs/av1_codec_configuration_record_unittest.cc new file mode 100644 index 0000000000..7c877f3f7a --- /dev/null +++ b/packager/media/codecs/av1_codec_configuration_record_unittest.cc @@ -0,0 +1,91 @@ +// Copyright 2018 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/av1_codec_configuration_record.h" + +#include + +namespace shaka { +namespace media { + +TEST(AV1CodecConfigurationRecordTest, Success) { + const uint8_t kAV1CodecConfigurationData[] = { + 0x81, // mark bit and version + 0x04, // profile = 0, level = 4 + 0x4E, // tier = 0, bit_depth = 10, mono_chrome = 0 + // chroma_subsampling_x = 1, chroma_subsampling_y = 1, + // chroma_sample_position = 2 + // We do not care about other data. + 0x00, + }; + + AV1CodecConfigurationRecord av1_config; + ASSERT_TRUE(av1_config.Parse( + std::vector(std::begin(kAV1CodecConfigurationData), + std::end(kAV1CodecConfigurationData)))); + + EXPECT_EQ(av1_config.GetCodecString(), "av01.0.04M.10.0.112"); +} + +TEST(AV1CodecConfigurationRecordTest, Success2) { + const uint8_t kAV1CodecConfigurationData[] = { + 0x81, // mark bit and version + 0x35, // profile = 1, level = 15 + 0xF4, // tier = 1, bit_depth = 12, mono_chrome = 1 + // chroma_subsampling_x = 0, chroma_subsampling_y = 1, + // chroma_sample_position = 0 + // We do not care about other data. + 0x00, + }; + + AV1CodecConfigurationRecord av1_config; + ASSERT_TRUE(av1_config.Parse( + std::vector(std::begin(kAV1CodecConfigurationData), + std::end(kAV1CodecConfigurationData)))); + + EXPECT_EQ(av1_config.GetCodecString(), "av01.1.21H.12.1.010"); +} + +TEST(AV1CodecConfigurationRecordTest, InsufficientData) { + const uint8_t kAV1CodecConfigurationData[] = { + 0x81, + 0x04, + }; + + AV1CodecConfigurationRecord av1_config; + ASSERT_FALSE(av1_config.Parse( + std::vector(std::begin(kAV1CodecConfigurationData), + std::end(kAV1CodecConfigurationData)))); +} + +TEST(AV1CodecConfigurationRecordTest, IncorrectMarkerBit) { + const uint8_t kAV1CodecConfigurationData[] = { + 0x01, + 0x04, + 0x4E, + }; + + AV1CodecConfigurationRecord av1_config; + ASSERT_FALSE(av1_config.Parse( + std::vector(std::begin(kAV1CodecConfigurationData), + std::end(kAV1CodecConfigurationData)))); +} + +TEST(AV1CodecConfigurationRecordTest, IncorrectVersion) { + const uint8_t kAV1CodecConfigurationData[] = { + 0x82, + 0x04, + 0x4E, + }; + + AV1CodecConfigurationRecord av1_config; + ASSERT_FALSE(av1_config.Parse( + std::vector(std::begin(kAV1CodecConfigurationData), + std::end(kAV1CodecConfigurationData)))); +} + +} // namespace media +} // namespace shaka diff --git a/packager/media/codecs/codecs.gyp b/packager/media/codecs/codecs.gyp index ae4537ba4a..44d2b329b1 100644 --- a/packager/media/codecs/codecs.gyp +++ b/packager/media/codecs/codecs.gyp @@ -17,6 +17,8 @@ 'aac_audio_specific_config.h', 'ac3_audio_util.cc', 'ac3_audio_util.h', + 'av1_codec_configuration_record.cc', + 'av1_codec_configuration_record.h', 'avc_decoder_configuration_record.cc', 'avc_decoder_configuration_record.h', 'decoder_configuration_record.cc', @@ -66,6 +68,7 @@ 'sources': [ 'aac_audio_specific_config_unittest.cc', 'ac3_audio_util_unittest.cc', + 'av1_codec_configuration_record_unittest.cc', 'avc_decoder_configuration_record_unittest.cc', 'ec3_audio_util_unittest.cc', 'es_descriptor_unittest.cc', diff --git a/packager/media/codecs/vp_codec_configuration_record.cc b/packager/media/codecs/vp_codec_configuration_record.cc index 9a8fc28c2a..ec6c26cd6f 100644 --- a/packager/media/codecs/vp_codec_configuration_record.cc +++ b/packager/media/codecs/vp_codec_configuration_record.cc @@ -30,8 +30,6 @@ std::string VPCodecAsString(Codec codec) { return "vp08"; case kCodecVP9: return "vp09"; - case kCodecVP10: - return "vp10"; default: LOG(WARNING) << "Unknown VP codec: " << codec; return std::string(); diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index 73e43e018e..2d9603a806 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -34,6 +34,7 @@ const uint16_t kVideoFrameCount = 1; const uint16_t kVideoDepth = 0x0018; const uint32_t kCompressorNameSize = 32u; +const char kAv1CompressorName[] = "\012AOM Coding"; const char kAvcCompressorName[] = "\012AVC Coding"; const char kHevcCompressorName[] = "\013HEVC Coding"; const char kVpcCompressorName[] = "\012VPC Coding"; @@ -1478,24 +1479,24 @@ bool VideoSampleEntry::ReadWriteInternal(BoxBuffer* buffer) { const FourCC actual_format = GetActualFormat(); switch (actual_format) { + case FOURCC_av01: + compressor_name.assign(std::begin(kAv1CompressorName), + std::end(kAv1CompressorName)); + break; case FOURCC_avc1: case FOURCC_avc3: - compressor_name.assign( - kAvcCompressorName, - kAvcCompressorName + arraysize(kAvcCompressorName)); + compressor_name.assign(std::begin(kAvcCompressorName), + std::end(kAvcCompressorName)); break; case FOURCC_hev1: case FOURCC_hvc1: - compressor_name.assign( - kHevcCompressorName, - kHevcCompressorName + arraysize(kHevcCompressorName)); + compressor_name.assign(std::begin(kHevcCompressorName), + std::end(kHevcCompressorName)); break; case FOURCC_vp08: case FOURCC_vp09: - case FOURCC_vp10: - compressor_name.assign( - kVpcCompressorName, - kVpcCompressorName + arraysize(kVpcCompressorName)); + compressor_name.assign(std::begin(kVpcCompressorName), + std::end(kVpcCompressorName)); break; default: LOG(ERROR) << FourCCToString(actual_format) << " is not supported."; @@ -1571,6 +1572,8 @@ size_t VideoSampleEntry::ComputeSizeInternal() { FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { switch (format) { + case FOURCC_av01: + return FOURCC_av1C; case FOURCC_avc1: case FOURCC_avc3: return FOURCC_avcC; @@ -1579,7 +1582,6 @@ FourCC VideoSampleEntry::GetCodecConfigurationBoxType(FourCC format) const { return FOURCC_hvcC; case FOURCC_vp08: case FOURCC_vp09: - case FOURCC_vp10: return FOURCC_vpcC; default: LOG(ERROR) << FourCCToString(format) << " is not supported."; diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 4358853d29..1e41ff0d71 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -22,6 +22,7 @@ #include "packager/media/base/rcheck.h" #include "packager/media/base/video_stream_info.h" #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/ec3_audio_util.h" #include "packager/media/codecs/es_descriptor.h" @@ -59,6 +60,8 @@ H26xStreamFormat GetH26xStreamFormat(FourCC fourcc) { Codec FourCCToCodec(FourCC fourcc) { switch (fourcc) { + case FOURCC_av01: + return kCodecAV1; case FOURCC_avc1: case FOURCC_avc3: return kCodecH264; @@ -69,8 +72,6 @@ Codec FourCCToCodec(FourCC fourcc) { return kCodecVP8; case FOURCC_vp09: return kCodecVP9; - case FOURCC_vp10: - return kCodecVP10; case FOURCC_Opus: return kCodecOpus; case FOURCC_dtsc: @@ -518,6 +519,15 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { const FourCC actual_format = entry.GetActualFormat(); const Codec video_codec = FourCCToCodec(actual_format); switch (actual_format) { + case FOURCC_av01: { + AV1CodecConfigurationRecord av1_config; + if (!av1_config.Parse(entry.codec_configuration.data)) { + LOG(ERROR) << "Failed to parse av1c."; + return false; + } + codec_string = av1_config.GetCodecString(); + break; + } case FOURCC_avc1: case FOURCC_avc3: { AVCDecoderConfigurationRecord avc_config; @@ -567,8 +577,7 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { break; } case FOURCC_vp08: - case FOURCC_vp09: - case FOURCC_vp10: { + case FOURCC_vp09: { VPCodecConfigurationRecord vp_config; if (!vp_config.ParseMP4(entry.codec_configuration.data)) { LOG(ERROR) << "Failed to parse vpcc."; diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 7a7d303ef6..5314250cae 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -44,6 +44,8 @@ void SetStartAndEndFromOffsetAndSize(size_t offset, FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { switch (codec) { + case kCodecAV1: + return FOURCC_av01; case kCodecH264: return h26x_stream_format == H26xStreamFormat::kNalUnitStreamWithParameterSetNalus @@ -58,8 +60,6 @@ FourCC CodecToFourCC(Codec codec, H26xStreamFormat h26x_stream_format) { return FOURCC_vp08; case kCodecVP9: return FOURCC_vp09; - case kCodecVP10: - return FOURCC_vp10; case kCodecAAC: return FOURCC_mp4a; case kCodecAC3: diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index 6435284ba7..e9efa6bc98 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -284,10 +284,17 @@ void Segmenter::UpdateProgress(uint64_t progress) { Status Segmenter::InitializeVideoTrack(const VideoStreamInfo& info, VideoTrack* track) { - if (info.codec() == kCodecVP8) { - track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId); + if (info.codec() == kCodecAV1) { + track->set_codec_id("V_AV1"); + if (!track->SetCodecPrivate(info.codec_config().data(), + info.codec_config().size())) { + return Status(error::INTERNAL_ERROR, + "Private codec data required for AV1 streams"); + } + } else if (info.codec() == kCodecVP8) { + track->set_codec_id("V_VP8"); } else if (info.codec() == kCodecVP9) { - track->set_codec_id(mkvmuxer::Tracks::kVp9CodecId); + track->set_codec_id("V_VP9"); // The |StreamInfo::codec_config| field is stored using the MP4 format; we // need to convert it to the WebM format. @@ -319,9 +326,9 @@ Status Segmenter::InitializeVideoTrack(const VideoStreamInfo& info, "Private codec data required for VPx streams"); } } else { - LOG(ERROR) << "Only VP8 and VP9 video codecs are supported in WebM."; + LOG(ERROR) << "Only VP8, VP9 and AV1 video codecs are supported in WebM."; return Status(error::UNIMPLEMENTED, - "Only VP8 and VP9 video codecs are supported in WebM."); + "Only VP8, VP9 and AV1 video codecs are supported in WebM."); } track->set_uid(info.track_id()); diff --git a/packager/media/formats/webm/webm_cluster_parser.cc b/packager/media/formats/webm/webm_cluster_parser.cc index 350342a75f..b3dca771b6 100644 --- a/packager/media/formats/webm/webm_cluster_parser.cc +++ b/packager/media/formats/webm/webm_cluster_parser.cc @@ -433,36 +433,42 @@ bool WebMClusterParser::OnBlock(bool is_simple_block, streams.push_back(audio_stream_info_); if (video_stream_info_) { if (stream_type == kStreamVideo) { - std::unique_ptr vpx_parser; - switch (video_stream_info_->codec()) { - case kCodecVP8: - vpx_parser.reset(new VP8Parser); - break; - case kCodecVP9: - vpx_parser.reset(new VP9Parser); - break; - default: - NOTIMPLEMENTED() << "Unsupported codec " - << video_stream_info_->codec(); + // Setup codec string and codec config for VP8 and VP9. + // Codec config for AV1 is already retrieved from WebM CodecPrivate + // instead of extracted from the bit stream. + if (video_stream_info_->codec() != kCodecAV1) { + std::unique_ptr vpx_parser; + switch (video_stream_info_->codec()) { + case kCodecVP8: + vpx_parser.reset(new VP8Parser); + break; + case kCodecVP9: + vpx_parser.reset(new VP9Parser); + break; + default: + NOTIMPLEMENTED() + << "Unsupported codec " << video_stream_info_->codec(); + return false; + } + std::vector vpx_frames; + if (!vpx_parser->Parse(buffer->data(), buffer->data_size(), + &vpx_frames)) { + LOG(ERROR) << "Failed to parse vpx frame."; return false; - } - std::vector vpx_frames; - if (!vpx_parser->Parse(buffer->data(), buffer->data_size(), - &vpx_frames)) { - LOG(ERROR) << "Failed to parse vpx frame."; - return false; - } - if (vpx_frames.size() != 1u || !vpx_frames[0].is_keyframe) { - LOG(ERROR) << "The first frame should be a key frame."; - return false; + } + if (vpx_frames.size() != 1u || !vpx_frames[0].is_keyframe) { + LOG(ERROR) << "The first frame should be a key frame."; + return false; + } + + vp_config_.MergeFrom(vpx_parser->codec_config()); + video_stream_info_->set_codec_string( + vp_config_.GetCodecString(video_stream_info_->codec())); + std::vector config_serialized; + vp_config_.WriteMP4(&config_serialized); + video_stream_info_->set_codec_config(config_serialized); } - vp_config_.MergeFrom(vpx_parser->codec_config()); - video_stream_info_->set_codec_string( - vp_config_.GetCodecString(video_stream_info_->codec())); - std::vector config_serialized; - vp_config_.WriteMP4(&config_serialized); - video_stream_info_->set_codec_config(config_serialized); streams.push_back(video_stream_info_); init_cb_.Run(streams); initialized_ = true; diff --git a/packager/media/formats/webm/webm_tracks_parser.cc b/packager/media/formats/webm/webm_tracks_parser.cc index 246bcb1513..84b5c26e5b 100644 --- a/packager/media/formats/webm/webm_tracks_parser.cc +++ b/packager/media/formats/webm/webm_tracks_parser.cc @@ -223,10 +223,14 @@ bool WebMTracksParser::OnListEnd(int id) { } video_default_duration_ = default_duration_; + // |vp_config_| is only useful for VP8 and VP9. + if (codec_id_ == "V_VP8" || codec_id_ == "V_VP9") + vp_config_ = video_client_.GetVpCodecConfig(codec_private_); + DCHECK(!video_stream_info_); - vp_config_ = video_client_.GetVpCodecConfig(codec_private_); video_stream_info_ = video_client_.GetVideoStreamInfo( - video_track_num_, codec_id_, !video_encryption_key_id_.empty()); + video_track_num_, codec_id_, codec_private_, + !video_encryption_key_id_.empty()); if (!video_stream_info_) return false; } else { diff --git a/packager/media/formats/webm/webm_video_client.cc b/packager/media/formats/webm/webm_video_client.cc index da52a68ed7..96335c3d9e 100644 --- a/packager/media/formats/webm/webm_video_client.cc +++ b/packager/media/formats/webm/webm_video_client.cc @@ -5,6 +5,7 @@ #include "packager/media/formats/webm/webm_video_client.h" #include "packager/base/logging.h" +#include "packager/media/codecs/av1_codec_configuration_record.h" #include "packager/media/codecs/vp_codec_configuration_record.h" #include "packager/media/formats/webm/webm_constants.h" @@ -57,24 +58,34 @@ void WebMVideoClient::Reset() { std::shared_ptr WebMVideoClient::GetVideoStreamInfo( int64_t track_num, const std::string& codec_id, + const std::vector& codec_private, bool is_encrypted) { + std::string codec_string; Codec video_codec = kUnknownCodec; - if (codec_id == "V_VP8") { + if (codec_id == "V_AV1") { + video_codec = kCodecAV1; + + // CodecPrivate is mandatory per AV in Matroska / WebM specification. + // https://github.com/Matroska-Org/matroska-specification/blob/av1-mappin/codec/av1.md#codecprivate-1 + AV1CodecConfigurationRecord av1_config; + if (!av1_config.Parse(codec_private)) { + LOG(ERROR) << "Failed to parse AV1 codec_private."; + return nullptr; + } + codec_string = av1_config.GetCodecString(); + } else if (codec_id == "V_VP8") { video_codec = kCodecVP8; + // codec_string for VP8 is parsed later. } else if (codec_id == "V_VP9") { video_codec = kCodecVP9; - // The codec private data is in WebM format, but needs to be converted to - // MP4 format. Don't do it yet, it will be handled in - // webm_cluster_parser.cc - } else if (codec_id == "V_VP10") { - video_codec = kCodecVP10; + // codec_string for VP9 is parsed later. } else { LOG(ERROR) << "Unsupported video codec_id " << codec_id; - return std::shared_ptr(); + return nullptr; } if (pixel_width_ <= 0 || pixel_height_ <= 0) - return std::shared_ptr(); + return nullptr; // Set crop and display unit defaults if these elements are not present. if (crop_bottom_ == -1) @@ -102,10 +113,10 @@ std::shared_ptr WebMVideoClient::GetVideoStreamInfo( display_height_ = height_after_crop; } else if (display_unit_ == 3) { if (display_width_ <= 0 || display_height_ <= 0) - return std::shared_ptr(); + return nullptr; } else { LOG(ERROR) << "Unsupported display unit type " << display_unit_; - return std::shared_ptr(); + return nullptr; } // Calculate sample aspect ratio. int64_t sar_x = display_width_ * height_after_crop; @@ -114,10 +125,14 @@ std::shared_ptr WebMVideoClient::GetVideoStreamInfo( sar_x /= gcd; sar_y /= gcd; + // |codec_private| may be overriden later for some codecs, e.g. VP9 since for + // VP9, the format for MP4 and WebM are different; MP4 format is used as the + // intermediate format. return std::make_shared( track_num, kWebMTimeScale, 0, video_codec, H26xStreamFormat::kUnSpecified, - std::string(), nullptr, 0, width_after_crop, height_after_crop, sar_x, - sar_y, 0, 0, std::string(), is_encrypted); + codec_string, codec_private.data(), codec_private.size(), + width_after_crop, height_after_crop, sar_x, sar_y, 0, 0, std::string(), + is_encrypted); } VPCodecConfigurationRecord WebMVideoClient::GetVpCodecConfig( diff --git a/packager/media/formats/webm/webm_video_client.h b/packager/media/formats/webm/webm_video_client.h index 1bf8892dd2..551c98d713 100644 --- a/packager/media/formats/webm/webm_video_client.h +++ b/packager/media/formats/webm/webm_video_client.h @@ -27,14 +27,15 @@ class WebMVideoClient : public WebMParserClient { void Reset(); /// Create a VideoStreamInfo with the data in |track_num|, |codec_id|, - /// |is_encrypted| and the fields parsed from the last video track element - /// this object was used to parse. + /// |codec_private|, |is_encrypted| and the fields parsed from the last video + /// track element this object was used to parse. /// @return A VideoStreamInfo if successful. /// @return An empty pointer if there was unexpected values in the /// provided parameters or video track element fields. std::shared_ptr GetVideoStreamInfo( int64_t track_num, const std::string& codec_id, + const std::vector& codec_private, bool is_encrypted); /// Extracts VPCodecConfigurationRecord parsed from codec private data and diff --git a/packager/media/test/data/bear-av1.mp4 b/packager/media/test/data/bear-av1.mp4 new file mode 100644 index 0000000000..c45c120260 Binary files /dev/null and b/packager/media/test/data/bear-av1.mp4 differ diff --git a/packager/media/test/data/bear-av1.webm b/packager/media/test/data/bear-av1.webm new file mode 100644 index 0000000000..a945902fb0 Binary files /dev/null and b/packager/media/test/data/bear-av1.webm differ