diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index 9098f04941..479b1fd820 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -84,6 +84,13 @@ class PackagerAppTest(unittest.TestCase): self._DiffGold(self.output[1], 'bear-640x360-v-cenc-golden.mp4') self._DiffGold(self.mpd_output, 'bear-640x360-av-cenc-golden.mpd') + def testPackageHevcWithEncryption(self): + self.packager.Package(self._GetStreams(['video'], + test_file='bear-640x360-hevc.mp4'), + self._GetFlags(encryption=True)) + self._DiffGold(self.output[0], 'bear-640x360-hevc-v-cenc-golden.mp4') + self._DiffGold(self.mpd_output, 'bear-640x360-hevc-v-cenc-golden.mpd') + def testPackageWithEncryptionAndRandomIv(self): self.packager.Package(self._GetStreams(['audio', 'video']), self._GetFlags(encryption=True, random_iv=True)) @@ -198,13 +205,14 @@ class PackagerAppTest(unittest.TestCase): self._AssertStreamInfo(self.output[0], 'is_encrypted: true') self._AssertStreamInfo(self.output[1], 'is_encrypted: true') - def _GetStreams(self, stream_descriptors, live = False): + def _GetStreams(self, stream_descriptors, live = False, + test_file = 'bear-640x360.mp4'): streams = [] self.output = [] test_data_dir = os.path.join( test_env.SRC_DIR, 'packager', 'media', 'test', 'data') - input = os.path.join(test_data_dir, 'bear-640x360.mp4') + input = os.path.join(test_data_dir, test_file) for stream_descriptor in stream_descriptors: if live: # This is still output prefix actually. diff --git a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 new file mode 100644 index 0000000000..5213040a3e Binary files /dev/null and b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mp4 differ diff --git a/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd new file mode 100644 index 0000000000..391d6e2850 --- /dev/null +++ b/packager/app/test/testdata/bear-640x360-hevc-v-cenc-golden.mpd @@ -0,0 +1,17 @@ + + + + + + + + AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2 + + output_video.mp4 + + + + + + + diff --git a/packager/media/base/video_stream_info.cc b/packager/media/base/video_stream_info.cc index fad76d10c4..2ec7fa51a2 100644 --- a/packager/media/base/video_stream_info.cc +++ b/packager/media/base/video_stream_info.cc @@ -21,6 +21,10 @@ std::string VideoCodecToString(VideoCodec video_codec) { switch (video_codec) { case kCodecH264: return "H264"; + case kCodecHEV1: + return "HEV1"; + case kCodecHVC1: + return "HVC1"; case kCodecVC1: return "VC1"; case kCodecMPEG2: diff --git a/packager/media/base/video_stream_info.h b/packager/media/base/video_stream_info.h index 2992e65b59..6786d5457f 100644 --- a/packager/media/base/video_stream_info.h +++ b/packager/media/base/video_stream_info.h @@ -15,6 +15,8 @@ namespace media { enum VideoCodec { kUnknownVideoCodec = 0, kCodecH264, + kCodecHEV1, + kCodecHVC1, kCodecVC1, kCodecMPEG2, kCodecMPEG4, diff --git a/packager/media/filters/filters.gyp b/packager/media/filters/filters.gyp index f836dec71f..dc36a9a225 100644 --- a/packager/media/filters/filters.gyp +++ b/packager/media/filters/filters.gyp @@ -15,6 +15,8 @@ 'sources': [ 'avc_decoder_configuration.cc', 'avc_decoder_configuration.h', + 'hevc_decoder_configuration.cc', + 'hevc_decoder_configuration.h', 'h264_bit_reader.cc', 'h264_bit_reader.h', 'h264_byte_to_unit_stream_converter.cc', @@ -36,6 +38,7 @@ 'h264_bit_reader_unittest.cc', 'h264_byte_to_unit_stream_converter_unittest.cc', 'h264_parser_unittest.cc', + 'hevc_decoder_configuration_unittest.cc', 'vp_codec_configuration_unittest.cc', ], 'dependencies': [ diff --git a/packager/media/filters/hevc_decoder_configuration.cc b/packager/media/filters/hevc_decoder_configuration.cc new file mode 100644 index 0000000000..b265a020c8 --- /dev/null +++ b/packager/media/filters/hevc_decoder_configuration.cc @@ -0,0 +1,139 @@ +// Copyright 2015 Google Inc. 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/filters/hevc_decoder_configuration.h" + +#include "packager/base/stl_util.h" +#include "packager/base/strings/string_number_conversions.h" +#include "packager/base/strings/string_util.h" +#include "packager/media/base/buffer_reader.h" +#include "packager/media/formats/mp4/rcheck.h" + +namespace edash_packager { +namespace media { + +namespace { + +// ISO/IEC 14496-15:2014 Annex E. +std::string GeneralProfileSpaceAsString(uint8_t general_profile_space) { + switch (general_profile_space) { + case 0: + return ""; + case 1: + return "A"; + case 2: + return "B"; + case 3: + return "C"; + default: + LOG(WARNING) << "Unexpected general_profile_space " + << general_profile_space; + return ""; + } +} + +std::string TrimLeadingZeros(const std::string& str) { + DCHECK_GT(str.size(), 0u); + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] == '0') continue; + return str.substr(i); + } + return "0"; +} + +// Encode the 32 bits input, but in reverse bit order, i.e. bit [31] as the most +// significant bit, followed by, bit [30], and down to bit [0] as the least +// significant bit, where bits [i] for i in the range of 0 to 31, inclusive, are +// specified in ISO/IEC 23008‐2, encoded in hexadecimal (leading zeroes may be +// omitted). +std::string ReverseBitsAndHexEncode(uint32_t x) { + x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1); + x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2); + x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4); + const uint8_t bytes[] = {static_cast(x & 0xFF), + static_cast((x >> 8) & 0xFF), + static_cast((x >> 16) & 0xFF), + static_cast((x >> 24) & 0xFF)}; + return TrimLeadingZeros(base::HexEncode(bytes, arraysize(bytes))); +} + +std::string CodecAsString(VideoCodec codec) { + switch (codec) { + case kCodecHEV1: + return "hev1"; + case kCodecHVC1: + return "hvc1"; + default: + LOG(WARNING) << "Unknown codec: " << codec; + return std::string(); + } +} + +} // namespace + +HEVCDecoderConfiguration::HEVCDecoderConfiguration() + : version_(0), + general_profile_space_(0), + general_tier_flag_(false), + general_profile_idc_(0), + general_profile_compatibility_flags_(0), + general_level_idc_(0), + length_size_(0) {} + +HEVCDecoderConfiguration::~HEVCDecoderConfiguration() {} + +bool HEVCDecoderConfiguration::Parse(const std::vector& data) { + BufferReader reader(vector_as_array(&data), data.size()); + + uint8_t profile_indication = 0; + uint8_t length_size_minus_one = 0; + uint8_t num_of_arrays = 0; + RCHECK(reader.Read1(&version_) && version_ == 1 && + reader.Read1(&profile_indication) && + reader.Read4(&general_profile_compatibility_flags_) && + reader.ReadToVector(&general_constraint_indicator_flags_, 6) && + reader.Read1(&general_level_idc_) && + reader.SkipBytes(8) && // Skip uninterested fields. + reader.Read1(&length_size_minus_one) && + reader.Read1(&num_of_arrays)); + + general_profile_space_ = profile_indication >> 6; + RCHECK(general_profile_space_ <= 3u); + general_tier_flag_ = ((profile_indication >> 5) & 1) == 1; + general_profile_idc_ = profile_indication & 0x1f; + + length_size_ = (length_size_minus_one & 0x3) + 1; + + // TODO(kqyang): Parse SPS to get resolutions. + return true; +} + +std::string HEVCDecoderConfiguration::GetCodecString(VideoCodec codec) const { + // ISO/IEC 14496-15:2014 Annex E. + std::vector fields; + fields.push_back(CodecAsString(codec)); + fields.push_back(GeneralProfileSpaceAsString(general_profile_space_) + + base::IntToString(general_profile_idc_)); + fields.push_back( + ReverseBitsAndHexEncode(general_profile_compatibility_flags_)); + fields.push_back((general_tier_flag_ ? "H" : "L") + + base::IntToString(general_level_idc_)); + + // Remove trailing bytes that are zero. + std::vector constraints = general_constraint_indicator_flags_; + size_t size = constraints.size(); + for (; size > 0; --size) { + if (constraints[size - 1] != 0) break; + } + constraints.resize(size); + for (uint8_t constraint : constraints) + fields.push_back(TrimLeadingZeros(base::HexEncode(&constraint, 1))); + + return base::JoinString(fields, "."); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/filters/hevc_decoder_configuration.h b/packager/media/filters/hevc_decoder_configuration.h new file mode 100644 index 0000000000..2783c86f2e --- /dev/null +++ b/packager/media/filters/hevc_decoder_configuration.h @@ -0,0 +1,52 @@ +// Copyright 2015 Google Inc. 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 MEDIA_FILTERS_HEVC_DECODER_CONFIGURATION_H_ +#define MEDIA_FILTERS_HEVC_DECODER_CONFIGURATION_H_ + +#include +#include +#include + +#include "packager/base/macros.h" +#include "packager/media/base/video_stream_info.h" + +namespace edash_packager { +namespace media { + +/// Class for parsing HEVC decoder configuration. +class HEVCDecoderConfiguration { + public: + HEVCDecoderConfiguration(); + ~HEVCDecoderConfiguration(); + + /// Parses input to extract HEVC decoder configuration data. + /// @return false if there is parsing errors. + bool Parse(const std::vector& data); + + /// @return The codec string. + std::string GetCodecString(VideoCodec codec) const; + + /// @return The size of the NAL unit length field. + uint8_t length_size() { return length_size_; } + + private: + uint8_t version_; + uint8_t general_profile_space_; + bool general_tier_flag_; + uint8_t general_profile_idc_; + uint32_t general_profile_compatibility_flags_; + std::vector general_constraint_indicator_flags_; + uint8_t general_level_idc_; + uint8_t length_size_; + + DISALLOW_COPY_AND_ASSIGN(HEVCDecoderConfiguration); +}; + +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FILTERS_HEVC_DECODER_CONFIGURATION_H_ diff --git a/packager/media/filters/hevc_decoder_configuration_unittest.cc b/packager/media/filters/hevc_decoder_configuration_unittest.cc new file mode 100644 index 0000000000..d0f2810644 --- /dev/null +++ b/packager/media/filters/hevc_decoder_configuration_unittest.cc @@ -0,0 +1,47 @@ +// Copyright 2015 Google Inc. 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/filters/hevc_decoder_configuration.h" + +#include + +namespace edash_packager { +namespace media { + +TEST(HEVCDecoderConfigurationTest, Success) { + const uint8_t kHevcDecoderConfigurationData[] = { + 0x01, 0x02, 0x20, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3F, 0xF0, 0x00, 0xFC, 0xFD, 0xFA, 0xFA, 0x00, 0x00, 0x0F, 0x04, 0x20, + 0x00, 0x01, 0x00, 0x18, 0x40, 0x01, 0x0C, 0x01, 0xFF, 0xFF, 0x02, 0x20, + 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, + 0x3F, 0x99, 0x98, 0x09, 0x21, 0x00, 0x01, 0x00, 0x29, 0x42, 0x01, 0x01, + 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, + }; + + HEVCDecoderConfiguration hevc_config; + ASSERT_TRUE(hevc_config.Parse( + std::vector(kHevcDecoderConfigurationData, + kHevcDecoderConfigurationData + + arraysize(kHevcDecoderConfigurationData)))); + + EXPECT_EQ(4u, hevc_config.length_size()); + + EXPECT_EQ("hev1.2.4.L63.90", hevc_config.GetCodecString(kCodecHEV1)); + EXPECT_EQ("hvc1.2.4.L63.90", hevc_config.GetCodecString(kCodecHVC1)); +} + +TEST(HEVCDecoderConfigurationTest, FailOnInsufficientData) { + const uint8_t kHevcDecoderConfigurationData[] = {0x01, 0x02, 0x20, 0x00}; + + HEVCDecoderConfiguration hevc_config; + ASSERT_FALSE(hevc_config.Parse( + std::vector(kHevcDecoderConfigurationData, + kHevcDecoderConfigurationData + + arraysize(kHevcDecoderConfigurationData)))); +} + +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp4/box_definitions.cc b/packager/media/formats/mp4/box_definitions.cc index ea3ba169dc..62acd32148 100644 --- a/packager/media/formats/mp4/box_definitions.cc +++ b/packager/media/formats/mp4/box_definitions.cc @@ -37,6 +37,7 @@ const uint16_t kVideoDepth = 0x0018; const uint32_t kCompressorNameSize = 32u; const char kAvcCompressorName[] = "\012AVC Coding"; +const char kHevcCompressorName[] = "\013HEVC Coding"; const char kVpcCompressorName[] = "\012VPC Coding"; // Utility functions to check if the 64bit integers can fit in 32bit integer. @@ -950,6 +951,12 @@ bool VideoSampleEntry::ReadWrite(BoxBuffer* buffer) { kAvcCompressorName, kAvcCompressorName + arraysize(kAvcCompressorName)); break; + case FOURCC_HEV1: + case FOURCC_HVC1: + compressor_name.assign( + kHevcCompressorName, + kHevcCompressorName + arraysize(kHevcCompressorName)); + break; case FOURCC_VP08: case FOURCC_VP09: case FOURCC_VP10: @@ -1001,6 +1008,10 @@ bool VideoSampleEntry::ReadWrite(BoxBuffer* buffer) { case FOURCC_AVC1: codec_config_record.box_type = FOURCC_AVCC; break; + case FOURCC_HEV1: + case FOURCC_HVC1: + codec_config_record.box_type = FOURCC_HVCC; + break; case FOURCC_VP08: case FOURCC_VP09: case FOURCC_VP10: @@ -1680,7 +1691,9 @@ bool SampleToGroup::ReadWrite(BoxBuffer* buffer) { if (grouping_type != FOURCC_SEIG) { DCHECK(buffer->Reading()); - DLOG(WARNING) << "Sample group '" << grouping_type << "' is not supported."; + DLOG(WARNING) << "Sample group " + << FourCCToString(static_cast(grouping_type)) + << " is not supported."; return true; } diff --git a/packager/media/formats/mp4/fourccs.h b/packager/media/formats/mp4/fourccs.h index e472f870a9..19b603cd57 100644 --- a/packager/media/formats/mp4/fourccs.h +++ b/packager/media/formats/mp4/fourccs.h @@ -32,7 +32,10 @@ enum FourCC { FOURCC_FRMA = 0x66726d61, FOURCC_FTYP = 0x66747970, FOURCC_HDLR = 0x68646c72, + FOURCC_HEV1 = 0x68657631, FOURCC_HINT = 0x68696e74, + FOURCC_HVC1 = 0x68766331, + FOURCC_HVCC = 0x68766343, FOURCC_ISO6 = 0x69736f36, FOURCC_IODS = 0x696f6473, FOURCC_MDAT = 0x6d646174, diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index ecefe7da52..98d85f1f2f 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -21,6 +21,7 @@ #include "packager/media/file/file.h" #include "packager/media/file/file_closer.h" #include "packager/media/filters/avc_decoder_configuration.h" +#include "packager/media/filters/hevc_decoder_configuration.h" #include "packager/media/filters/vp_codec_configuration.h" #include "packager/media/formats/mp4/box_definitions.h" #include "packager/media/formats/mp4/box_reader.h" @@ -43,6 +44,10 @@ VideoCodec FourCCToCodec(FourCC fourcc) { switch (fourcc) { case FOURCC_AVC1: return kCodecH264; + case FOURCC_HEV1: + return kCodecHEV1; + case FOURCC_HVC1: + return kCodecHVC1; case FOURCC_VP08: return kCodecVP8; case FOURCC_VP09: @@ -419,6 +424,17 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) { } break; } + case FOURCC_HEV1: + case FOURCC_HVC1: { + HEVCDecoderConfiguration hevc_config; + if (!hevc_config.Parse(entry.codec_config_record.data)) { + LOG(ERROR) << "Failed to parse hevc."; + return false; + } + codec_string = hevc_config.GetCodecString(video_codec); + nalu_length_size = hevc_config.length_size(); + break; + } case FOURCC_VP08: case FOURCC_VP09: case FOURCC_VP10: { diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 1901aec947..762bc69a28 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -44,6 +44,10 @@ FourCC CodecToFourCC(VideoCodec codec) { switch (codec) { case kCodecH264: return FOURCC_AVC1; + case kCodecHEV1: + return FOURCC_HEV1; + case kCodecHVC1: + return FOURCC_HVC1; case kCodecVP8: return FOURCC_VP08; case kCodecVP9: diff --git a/packager/media/test/data/README b/packager/media/test/data/README index 14bcf67f53..79dc67b7aa 100644 --- a/packager/media/test/data/README +++ b/packager/media/test/data/README @@ -31,6 +31,7 @@ bear-1280x720_ptswraparound.ts - Same as bear-1280x720.ts, with a timestamp wrap // ISO-BMFF streams. bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container. bear-640x360.mp4 - Same as above, but in a different resolution. +bear-640x360-hevc.mp4 - Same content, but encoded with HEVC. bear-320x180.mp4 - Same as above, but in a different resolution. bear-640x360-trailing-moov.mp4 - Same content, but with moov box in the end. bear-640x360-av_frag.mp4 - Same content, but in fragmented mp4. diff --git a/packager/media/test/data/bear-640x360-hevc.mp4 b/packager/media/test/data/bear-640x360-hevc.mp4 new file mode 100644 index 0000000000..b543c9a48f Binary files /dev/null and b/packager/media/test/data/bear-640x360-hevc.mp4 differ