Support HEVC in mp4 parser and muxer
Issue #46 Change-Id: I36bf8418a335181ad71509e6e0cccdce210467f0
This commit is contained in:
parent
8c53995335
commit
4c10755d40
|
@ -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.
|
@ -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>
|
|
@ -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:
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace media {
|
|||
enum VideoCodec {
|
||||
kUnknownVideoCodec = 0,
|
||||
kCodecH264,
|
||||
kCodecHEV1,
|
||||
kCodecHVC1,
|
||||
kCodecVC1,
|
||||
kCodecMPEG2,
|
||||
kCodecMPEG4,
|
||||
|
|
|
@ -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': [
|
||||
|
|
|
@ -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<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
|
|
@ -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_
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
Loading…
Reference in New Issue