Support HEVC in mp4 parser and muxer

Issue #46

Change-Id: I36bf8418a335181ad71509e6e0cccdce210467f0
This commit is contained in:
KongQun Yang 2015-10-28 17:26:29 -07:00
parent 8c53995335
commit 4c10755d40
15 changed files with 312 additions and 3 deletions

View File

@ -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.

Binary file not shown.

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" minBufferTime="PT2S" type="static" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" mediaPresentationDuration="PT2.7694332599639893S">
<Period>
<AdaptationSet id="0" contentType="video" width="640" height="360" frameRate="30000/1001" subSegmentAlignment="true" par="16:9">
<Representation id="0" bandwidth="264624" codecs="hev1.1.6.L63.80" mimeType="video/mp4" sar="1:1" width="640" height="360" frameRate="30000/1001">
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cenc:pssh>AAAAMHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAABAxMjM0NTY3ODkwMTIzNDU2</cenc:pssh>
</ContentProtection>
<BaseURL>output_video.mp4</BaseURL>
<SegmentBase indexRange="2721-2764" timescale="30000">
<Initialization range="0-2720"/>
</SegmentBase>
</Representation>
</AdaptationSet>
</Period>
</MPD>

View File

@ -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:

View File

@ -15,6 +15,8 @@ namespace media {
enum VideoCodec {
kUnknownVideoCodec = 0,
kCodecH264,
kCodecHEV1,
kCodecHVC1,
kCodecVC1,
kCodecMPEG2,
kCodecMPEG4,

View File

@ -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': [

View File

@ -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 230082, 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<uint8_t>(x & 0xFF),
static_cast<uint8_t>((x >> 8) & 0xFF),
static_cast<uint8_t>((x >> 16) & 0xFF),
static_cast<uint8_t>((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<uint8_t>& 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<std::string> 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<uint8_t> 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

View File

@ -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 <stdint.h>
#include <string>
#include <vector>
#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<uint8_t>& 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<uint8_t> 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_

View File

@ -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 <gtest/gtest.h>
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<uint8_t>(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<uint8_t>(kHevcDecoderConfigurationData,
kHevcDecoderConfigurationData +
arraysize(kHevcDecoderConfigurationData))));
}
} // namespace media
} // namespace edash_packager

View File

@ -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<FourCC>(grouping_type))
<< " is not supported.";
return true;
}

View File

@ -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,

View File

@ -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: {

View File

@ -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:

View File

@ -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.

Binary file not shown.