summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorservolk <servolk@chromium.org>2015-09-08 15:33:50 -0700
committerCommit bot <commit-bot@chromium.org>2015-09-08 22:34:30 +0000
commit4585056bcf1491d19262aaef0577542840f54f19 (patch)
treea2934a2e239ef7efbdaeeb1a09873936e4291a6e /media
parentb1a17c24b420b95b78558fe36701e0dc1fd6e214 (diff)
downloadchromium_src-4585056bcf1491d19262aaef0577542840f54f19.zip
chromium_src-4585056bcf1491d19262aaef0577542840f54f19.tar.gz
chromium_src-4585056bcf1491d19262aaef0577542840f54f19.tar.bz2
Implemented HEVC video demuxing and parsing
Chromecast team is interested in adding HEVC/H265 video codec support. Since we are using hardware decoders on our platforms, we don't need to add software decoder, but we still need to be able to demux and parse HEVC in mp4 containers. HEVC is very similar to H264 in many aspects, so we can reuse a lot of the existing H264 functionality. BUG=454948 Review URL: https://codereview.chromium.org/816353010 Cr-Commit-Position: refs/heads/master@{#347814}
Diffstat (limited to 'media')
-rw-r--r--media/BUILD.gn21
-rw-r--r--media/base/android/demuxer_stream_player_params.cc1
-rw-r--r--media/base/android/media_codec_bridge.cc6
-rw-r--r--media/base/mime_util.cc61
-rw-r--r--media/base/video_decoder_config.cc2
-rw-r--r--media/base/video_decoder_config.h3
-rw-r--r--media/ffmpeg/ffmpeg_common.cc8
-rw-r--r--media/filters/ffmpeg_demuxer.cc31
-rw-r--r--media/filters/ffmpeg_demuxer_unittest.cc16
-rw-r--r--media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc93
-rw-r--r--media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h49
-rw-r--r--media/filters/h264_parser.cc7
-rw-r--r--media/filters/h264_parser.h12
-rw-r--r--media/filters/h265_parser.cc159
-rw-r--r--media/filters/h265_parser.h151
-rw-r--r--media/filters/h265_parser_unittest.cc43
-rw-r--r--media/filters/stream_parser_factory.cc13
-rw-r--r--media/formats/mp4/box_definitions.cc62
-rw-r--r--media/formats/mp4/box_definitions.h4
-rw-r--r--media/formats/mp4/fourccs.h5
-rw-r--r--media/formats/mp4/hevc.cc237
-rw-r--r--media/formats/mp4/hevc.h106
-rw-r--r--media/formats/mp4/mp4_stream_parser.cc9
-rw-r--r--media/formats/mp4/mp4_stream_parser_unittest.cc9
-rw-r--r--media/media.gyp32
-rw-r--r--media/media_options.gni4
-rw-r--r--media/mojo/interfaces/media_types.mojom3
-rw-r--r--media/mojo/services/media_type_converters.cc1
-rw-r--r--media/test/data/bear-hevc-frag.mp4bin0 -> 15633 bytes
-rw-r--r--media/test/data/bear.hevcbin0 -> 14428 bytes
30 files changed, 1126 insertions, 22 deletions
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 0308bc0..51fb153 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -32,6 +32,9 @@ config("media_config") {
if (use_cras) {
defines += [ "USE_CRAS" ]
}
+ if (proprietary_codecs && enable_hevc_demuxing) {
+ defines += [ "ENABLE_HEVC_DEMUXING" ]
+ }
}
config("media_implementation") {
@@ -327,6 +330,21 @@ component("media") {
}
}
+ if (proprietary_codecs && enable_hevc_demuxing) {
+ sources += [
+ "filters/h265_parser.cc",
+ "filters/h265_parser.h",
+ "formats/mp4/hevc.cc",
+ "formats/mp4/hevc.h",
+ ]
+ if (media_use_ffmpeg) {
+ sources += [
+ "filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc",
+ "filters/ffmpeg_h265_to_annex_b_bitstream_converter.h",
+ ]
+ }
+ }
+
if (current_cpu == "arm" && arm_use_neon) {
defines += [ "USE_NEON" ]
}
@@ -686,6 +704,9 @@ test("media_unittests") {
"filters/ffmpeg_h264_to_annex_b_bitstream_converter_unittest.cc",
]
}
+ if (enable_hevc_demuxing) {
+ sources += [ "filters/h265_parser_unittest.cc" ]
+ }
}
if (is_mac || is_ios) {
diff --git a/media/base/android/demuxer_stream_player_params.cc b/media/base/android/demuxer_stream_player_params.cc
index 364ca76..6e86a7f 100644
--- a/media/base/android/demuxer_stream_player_params.cc
+++ b/media/base/android/demuxer_stream_player_params.cc
@@ -77,6 +77,7 @@ const char* AsString(VideoCodec codec) {
switch (codec) {
RETURN_STRING(kUnknownVideoCodec);
RETURN_STRING(kCodecH264);
+ RETURN_STRING(kCodecHEVC);
RETURN_STRING(kCodecVC1);
RETURN_STRING(kCodecMPEG2);
RETURN_STRING(kCodecMPEG4);
diff --git a/media/base/android/media_codec_bridge.cc b/media/base/android/media_codec_bridge.cc
index 558587e..53c5943 100644
--- a/media/base/android/media_codec_bridge.cc
+++ b/media/base/android/media_codec_bridge.cc
@@ -53,6 +53,8 @@ static const std::string VideoCodecToAndroidMimeType(const VideoCodec& codec) {
switch (codec) {
case kCodecH264:
return "video/avc";
+ case kCodecHEVC:
+ return "video/hevc";
case kCodecVP8:
return "video/x-vnd.on2.vp8";
case kCodecVP9:
@@ -66,6 +68,8 @@ static const std::string CodecTypeToAndroidMimeType(const std::string& codec) {
// TODO(xhwang): Shall we handle more detailed strings like "mp4a.40.2"?
if (codec == "avc1")
return "video/avc";
+ if (codec == "hvc1")
+ return "video/hevc";
if (codec == "mp4a")
return "audio/mp4a-latm";
if (codec == "vp8" || codec == "vp8.0")
@@ -85,6 +89,8 @@ static const std::string AndroidMimeTypeToCodecType(const std::string& mime) {
return "mp4v";
if (mime == "video/avc")
return "avc1";
+ if (mime == "video/hevc")
+ return "hvc1";
if (mime == "video/x-vnd.on2.vp8")
return "vp8";
if (mime == "video/x-vnd.on2.vp9")
diff --git a/media/base/mime_util.cc b/media/base/mime_util.cc
index 06445d7..3679539 100644
--- a/media/base/mime_util.cc
+++ b/media/base/mime_util.cc
@@ -36,6 +36,7 @@ class MimeUtil {
H264_BASELINE,
H264_MAIN,
H264_HIGH,
+ HEVC_MAIN,
VP8,
VP9,
THEORA
@@ -201,6 +202,15 @@ static bool IsCodecSupportedOnAndroid(MimeUtil::Codec codec) {
case MimeUtil::VORBIS:
return true;
+ case MimeUtil::HEVC_MAIN:
+#if defined(ENABLE_HEVC_DEMUXING)
+ // HEVC/H.265 is supported in Lollipop+ (API Level 21), according to
+ // http://developer.android.com/reference/android/media/MediaFormat.html
+ return base::android::BuildInfo::GetInstance()->sdk_int() >= 21;
+#else
+ return false;
+#endif
+
case MimeUtil::MPEG2_AAC_LC:
case MimeUtil::MPEG2_AAC_MAIN:
case MimeUtil::MPEG2_AAC_SSR:
@@ -255,6 +265,11 @@ static const char kMP4VideoCodecsExpression[] =
// kUnambiguousCodecStringMap/kAmbiguousCodecStringMap should be the only
// mapping from strings to codecs. See crbug.com/461009.
"avc1.42E00A,avc1.4D400A,avc1.64000A,"
+#if defined(ENABLE_HEVC_DEMUXING)
+ // Any valid unambiguous HEVC codec id will work here, since these strings
+ // are parsed and mapped to MimeUtil::Codec enum values.
+ "hev1.1.6.L93.B0,"
+#endif
"mp4a.66,mp4a.67,mp4a.68,mp4a.69,mp4a.6B,mp4a.40.2,mp4a.40.02,mp4a.40.5,"
"mp4a.40.05,mp4a.40.29";
@@ -548,6 +563,42 @@ static bool ParseH264CodecID(const std::string& codec_id,
return true;
}
+#if defined(ENABLE_HEVC_DEMUXING)
+// ISO/IEC FDIS 14496-15 standard section E.3 describes the syntax of codec ids
+// reserved for HEVC. According to that spec HEVC codec id must start with
+// either "hev1." or "hvc1.". We don't yet support full parsing of HEVC codec
+// ids, but since no other codec id starts with those string we'll just treat
+// any string starting with "hev1." or "hvc1." as valid HEVC codec ids.
+// crbug.com/482761
+static bool ParseHEVCCodecID(const std::string& codec_id,
+ MimeUtil::Codec* codec,
+ bool* is_ambiguous) {
+ if (base::StartsWith(codec_id, "hev1.", base::CompareCase::SENSITIVE) ||
+ base::StartsWith(codec_id, "hvc1.", base::CompareCase::SENSITIVE)) {
+ *codec = MimeUtil::HEVC_MAIN;
+
+ // TODO(servolk): Full HEVC codec id parsing is not implemented yet (see
+ // crbug.com/482761). So treat HEVC codec ids as ambiguous for now.
+ *is_ambiguous = true;
+
+ // TODO(servolk): Most HEVC codec ids are treated as ambiguous (see above),
+ // but we need to recognize at least one valid unambiguous HEVC codec id,
+ // which is added into kMP4VideoCodecsExpression. We need it to be
+ // unambiguous to avoid DCHECK(!is_ambiguous) in InitializeMimeTypeMaps. We
+ // also use these in unit tests (see
+ // content/browser/media/media_canplaytype_browsertest.cc).
+ // Remove this workaround after crbug.com/482761 is fixed.
+ if (codec_id == "hev1.1.6.L93.B0" || codec_id == "hvc1.1.6.L93.B0") {
+ *is_ambiguous = false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+#endif
+
bool MimeUtil::StringToCodec(const std::string& codec_id,
Codec* codec,
bool* is_ambiguous) const {
@@ -560,8 +611,13 @@ bool MimeUtil::StringToCodec(const std::string& codec_id,
}
// If |codec_id| is not in |string_to_codec_map_|, then we assume that it is
- // an H.264 codec ID because currently those are the only ones that can't be
- // stored in the |string_to_codec_map_| and require parsing.
+ // either H.264 or HEVC/H.265 codec ID because currently those are the only
+ // ones that are not added to the |string_to_codec_map_| and require parsing.
+#if defined(ENABLE_HEVC_DEMUXING)
+ if (ParseHEVCCodecID(codec_id, codec, is_ambiguous)) {
+ return true;
+ }
+#endif
return ParseH264CodecID(codec_id, codec, is_ambiguous);
}
@@ -589,6 +645,7 @@ bool MimeUtil::IsCodecProprietary(Codec codec) const {
case H264_BASELINE:
case H264_MAIN:
case H264_HIGH:
+ case HEVC_MAIN:
return true;
case PCM:
diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc
index 501bf4d..f867ad1 100644
--- a/media/base/video_decoder_config.cc
+++ b/media/base/video_decoder_config.cc
@@ -127,6 +127,8 @@ std::string VideoDecoderConfig::GetHumanReadableCodecName() const {
return "unknown";
case kCodecH264:
return "h264";
+ case kCodecHEVC:
+ return "hevc";
case kCodecVC1:
return "vc1";
case kCodecMPEG2:
diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h
index 91ead43..abc1de8 100644
--- a/media/base/video_decoder_config.h
+++ b/media/base/video_decoder_config.h
@@ -28,12 +28,13 @@ enum VideoCodec {
kCodecTheora,
kCodecVP8,
kCodecVP9,
+ kCodecHEVC,
// DO NOT ADD RANDOM VIDEO CODECS!
//
// The only acceptable time to add a new codec is if there is production code
// that uses said codec in the same CL.
- kVideoCodecMax = kCodecVP9 // Must equal the last "real" codec above.
+ kVideoCodecMax = kCodecHEVC // Must equal the last "real" codec above.
};
// Video stream profile. This *must* match PP_VideoDecoder_Profile.
diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc
index 2d536e9..a92bf7c 100644
--- a/media/ffmpeg/ffmpeg_common.cc
+++ b/media/ffmpeg/ffmpeg_common.cc
@@ -154,6 +154,10 @@ static VideoCodec CodecIDToVideoCodec(AVCodecID codec_id) {
switch (codec_id) {
case AV_CODEC_ID_H264:
return kCodecH264;
+#if defined(ENABLE_HEVC_DEMUXING)
+ case AV_CODEC_ID_HEVC:
+ return kCodecHEVC;
+#endif
case AV_CODEC_ID_THEORA:
return kCodecTheora;
case AV_CODEC_ID_MPEG4:
@@ -172,6 +176,10 @@ AVCodecID VideoCodecToCodecID(VideoCodec video_codec) {
switch (video_codec) {
case kCodecH264:
return AV_CODEC_ID_H264;
+#if defined(ENABLE_HEVC_DEMUXING)
+ case kCodecHEVC:
+ return AV_CODEC_ID_HEVC;
+#endif
case kCodecTheora:
return AV_CODEC_ID_THEORA;
case kCodecMPEG4:
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index bbc6079..f6ed205 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -34,6 +34,10 @@
#include "media/filters/webvtt_util.h"
#include "media/formats/webm/webm_crypto_helpers.h"
+#if defined(ENABLE_HEVC_DEMUXING)
+#include "media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h"
+#endif
+
namespace media {
static base::Time ExtractTimelineOffset(AVFormatContext* format_context) {
@@ -548,6 +552,11 @@ void FFmpegDemuxerStream::InitBitstreamConverter() {
if (stream_->codec->codec_id == AV_CODEC_ID_H264) {
bitstream_converter_.reset(
new FFmpegH264ToAnnexBBitstreamConverter(stream_->codec));
+#if defined(ENABLE_HEVC_DEMUXING)
+ } else if (stream_->codec->codec_id == AV_CODEC_ID_HEVC) {
+ bitstream_converter_.reset(
+ new FFmpegH265ToAnnexBBitstreamConverter(stream_->codec));
+#endif
} else if (stream_->codec->codec_id == AV_CODEC_ID_AAC) {
bitstream_converter_.reset(
new FFmpegAACBitstreamConverter(stream_->codec));
@@ -996,6 +1005,28 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
if (video_stream)
continue;
+
+#if defined(ENABLE_HEVC_DEMUXING)
+ if (stream->codec->codec_id == AV_CODEC_ID_HEVC) {
+ // If ffmpeg is built without HEVC parser/decoder support, it will be
+ // able to demux HEVC based solely on container-provided information,
+ // but unable to get some of the parameters without parsing the stream
+ // (e.g. coded size needs to be read from SPS, pixel format is typically
+ // deduced from decoder config in hvcC box). These are not really needed
+ // when using external decoder (e.g. hardware decoder), so override them
+ // here, to make sure this translates into a valid VideoDecoderConfig.
+ if (stream->codec->coded_width == 0 &&
+ stream->codec->coded_height == 0) {
+ DCHECK(stream->codec->width > 0);
+ DCHECK(stream->codec->height > 0);
+ stream->codec->coded_width = stream->codec->width;
+ stream->codec->coded_height = stream->codec->height;
+ }
+ if (stream->codec->pix_fmt == AV_PIX_FMT_NONE) {
+ stream->codec->pix_fmt = PIX_FMT_YUV420P;
+ }
+ }
+#endif
// Log the codec detected, whether it is supported or not.
UMA_HISTOGRAM_SPARSE_SLOWLY("Media.DetectedVideoCodec",
codec_context->codec_id);
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc
index ac3ad97..21158c2 100644
--- a/media/filters/ffmpeg_demuxer_unittest.cc
+++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -1072,4 +1072,20 @@ TEST_F(FFmpegDemuxerTest, NaturalSizeWithPASP) {
#endif
+#if defined(ENABLE_HEVC_DEMUXING)
+TEST_F(FFmpegDemuxerTest, HEVC_in_MP4_container) {
+ CreateDemuxer("bear-hevc-frag.mp4");
+ InitializeDemuxer();
+
+ DemuxerStream* video = demuxer_->GetStream(DemuxerStream::VIDEO);
+ ASSERT_TRUE(video);
+
+ video->Read(NewReadCB(FROM_HERE, 3569, 66733, true));
+ message_loop_.Run();
+
+ video->Read(NewReadCB(FROM_HERE, 1042, 200200, false));
+ message_loop_.Run();
+}
+#endif
+
} // namespace media
diff --git a/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc b/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc
new file mode 100644
index 0000000..a643f53
--- /dev/null
+++ b/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc
@@ -0,0 +1,93 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h"
+
+#include "base/logging.h"
+#include "media/base/decrypt_config.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/formats/mp4/avc.h"
+#include "media/formats/mp4/box_definitions.h"
+#include "media/formats/mp4/hevc.h"
+
+namespace media {
+
+FFmpegH265ToAnnexBBitstreamConverter::FFmpegH265ToAnnexBBitstreamConverter(
+ AVCodecContext* stream_codec_context)
+ : stream_codec_context_(stream_codec_context) {
+ CHECK(stream_codec_context_);
+}
+
+FFmpegH265ToAnnexBBitstreamConverter::~FFmpegH265ToAnnexBBitstreamConverter() {}
+
+bool FFmpegH265ToAnnexBBitstreamConverter::ConvertPacket(AVPacket* packet) {
+ DVLOG(3) << __FUNCTION__;
+ if (packet == NULL || !packet->data)
+ return false;
+
+ // Calculate the needed output buffer size.
+ if (!hevc_config_) {
+ if (!stream_codec_context_->extradata ||
+ stream_codec_context_->extradata_size <= 0) {
+ DVLOG(1) << "HEVCDecoderConfiguration not found, no extra codec data";
+ return false;
+ }
+
+ hevc_config_.reset(new mp4::HEVCDecoderConfigurationRecord());
+
+ if (!hevc_config_->Parse(
+ stream_codec_context_->extradata,
+ stream_codec_context_->extradata_size)) {
+ DVLOG(1) << "Parsing HEVCDecoderConfiguration failed";
+ return false;
+ }
+ }
+
+ std::vector<uint8> input_frame;
+ std::vector<SubsampleEntry> subsamples;
+ // TODO(servolk): Performance could be improved here, by reducing unnecessary
+ // data copying, but first annex b conversion code needs to be refactored to
+ // allow that (see crbug.com/455379).
+ input_frame.insert(input_frame.end(),
+ packet->data, packet->data + packet->size);
+ int nalu_size_len = hevc_config_->lengthSizeMinusOne + 1;
+ if (!mp4::AVC::ConvertFrameToAnnexB(nalu_size_len, &input_frame,
+ &subsamples)) {
+ DVLOG(1) << "AnnexB conversion failed";
+ return false;
+ }
+
+ if (packet->flags & AV_PKT_FLAG_KEY) {
+ RCHECK(mp4::HEVC::InsertParamSetsAnnexB(*hevc_config_.get(),
+ &input_frame, &subsamples));
+ DVLOG(4) << "Inserted HEVC decoder params";
+ }
+
+ uint32 output_packet_size = input_frame.size();
+
+ if (output_packet_size == 0)
+ return false; // Invalid input packet.
+
+ // Allocate new packet for the output.
+ AVPacket dest_packet;
+ if (av_new_packet(&dest_packet, output_packet_size) != 0)
+ return false; // Memory allocation failure.
+
+ // This is a bit tricky: since the interface does not allow us to replace
+ // the pointer of the old packet with a new one, we will initially copy the
+ // metadata from old packet to new bigger packet.
+ av_packet_copy_props(&dest_packet, packet);
+
+ // Proceed with the conversion of the actual in-band NAL units, leave room
+ // for configuration in the beginning.
+ memcpy(dest_packet.data, &input_frame[0], input_frame.size());
+
+ // At the end we must destroy the old packet.
+ av_free_packet(packet);
+ *packet = dest_packet; // Finally, replace the values in the input packet.
+
+ return true;
+}
+
+} // namespace media
diff --git a/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h b/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h
new file mode 100644
index 0000000..5892f42
--- /dev/null
+++ b/media/filters/ffmpeg_h265_to_annex_b_bitstream_converter.h
@@ -0,0 +1,49 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FILTERS_FFMPEG_H265_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
+#define MEDIA_FILTERS_FFMPEG_H265_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/filters/ffmpeg_bitstream_converter.h"
+#include "media/formats/mp4/hevc.h"
+
+// Forward declarations for FFmpeg datatypes used.
+struct AVCodecContext;
+struct AVPacket;
+
+namespace media {
+
+// Bitstream converter that converts H.265 bitstream based FFmpeg packets into
+// H.265 Annex B bytestream format.
+class MEDIA_EXPORT FFmpegH265ToAnnexBBitstreamConverter
+ : public FFmpegBitstreamConverter {
+ public:
+ // The |stream_codec_context| will be used during conversion and should be the
+ // AVCodecContext for the stream sourcing these packets. A reference to
+ // |stream_codec_context| is retained, so it must outlive this class.
+ explicit FFmpegH265ToAnnexBBitstreamConverter(
+ AVCodecContext* stream_codec_context);
+
+ ~FFmpegH265ToAnnexBBitstreamConverter() override;
+
+ // FFmpegBitstreamConverter implementation.
+ bool ConvertPacket(AVPacket* packet) override;
+
+ private:
+ scoped_ptr<mp4::HEVCDecoderConfigurationRecord> hevc_config_;
+
+ // Variable to hold a pointer to memory where we can access the global
+ // data from the FFmpeg file format's global headers.
+ AVCodecContext* stream_codec_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegH265ToAnnexBBitstreamConverter);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_FFMPEG_H265_TO_ANNEX_B_BITSTREAM_CONVERTER_H_
+
diff --git a/media/filters/h264_parser.cc b/media/filters/h264_parser.cc
index 22d420b..fe2a443 100644
--- a/media/filters/h264_parser.cc
+++ b/media/filters/h264_parser.cc
@@ -213,6 +213,7 @@ bool H264Parser::LocateNALU(off_t* nalu_size, off_t* start_code_size) {
off_t annexb_start_code_size = 0;
if (!FindStartCodeInClearRanges(stream_, bytes_left_,
+ encrypted_ranges_,
&nalu_start_off, &annexb_start_code_size)) {
DVLOG(4) << "Could not find start code, end of stream?";
return false;
@@ -238,6 +239,7 @@ bool H264Parser::LocateNALU(off_t* nalu_size, off_t* start_code_size) {
off_t next_start_code_size = 0;
off_t nalu_size_without_start_code = 0;
if (!FindStartCodeInClearRanges(nalu_data, max_nalu_data_size,
+ encrypted_ranges_,
&nalu_size_without_start_code,
&next_start_code_size)) {
nalu_size_without_start_code = max_nalu_data_size;
@@ -250,9 +252,10 @@ bool H264Parser::LocateNALU(off_t* nalu_size, off_t* start_code_size) {
bool H264Parser::FindStartCodeInClearRanges(
const uint8* data,
off_t data_size,
+ const Ranges<const uint8*>& encrypted_ranges,
off_t* offset,
off_t* start_code_size) {
- if (encrypted_ranges_.size() == 0)
+ if (encrypted_ranges.size() == 0)
return FindStartCode(data, data_size, offset, start_code_size);
DCHECK_GE(data_size, 0);
@@ -270,7 +273,7 @@ bool H264Parser::FindStartCodeInClearRanges(
Ranges<const uint8*> start_code_range;
start_code_range.Add(start_code, start_code_end + 1);
- if (encrypted_ranges_.IntersectionWith(start_code_range).size() > 0) {
+ if (encrypted_ranges.IntersectionWith(start_code_range).size() > 0) {
// The start code is inside an encrypted section so we need to scan
// for another start code.
*start_code_size = 0;
diff --git a/media/filters/h264_parser.h b/media/filters/h264_parser.h
index b8dde50..36467ba 100644
--- a/media/filters/h264_parser.h
+++ b/media/filters/h264_parser.h
@@ -341,6 +341,12 @@ class MEDIA_EXPORT H264Parser {
static bool FindStartCode(const uint8* data, off_t data_size,
off_t* offset, off_t* start_code_size);
+ // Wrapper for FindStartCode() that skips over start codes that
+ // may appear inside of |encrypted_ranges_|.
+ // Returns true if a start code was found. Otherwise returns false.
+ static bool FindStartCodeInClearRanges(const uint8* data, off_t data_size,
+ const Ranges<const uint8*>& ranges,
+ off_t* offset, off_t* start_code_size);
H264Parser();
~H264Parser();
@@ -406,12 +412,6 @@ class MEDIA_EXPORT H264Parser {
// - the size in bytes of the start code is returned in |*start_code_size|.
bool LocateNALU(off_t* nalu_size, off_t* start_code_size);
- // Wrapper for FindStartCode() that skips over start codes that
- // may appear inside of |encrypted_ranges_|.
- // Returns true if a start code was found. Otherwise returns false.
- bool FindStartCodeInClearRanges(const uint8* data, off_t data_size,
- off_t* offset, off_t* start_code_size);
-
// Exp-Golomb code parsing as specified in chapter 9.1 of the spec.
// Read one unsigned exp-Golomb code from the stream and return in |*val|.
Result ReadUE(int* val);
diff --git a/media/filters/h265_parser.cc b/media/filters/h265_parser.cc
new file mode 100644
index 0000000..30a8233
--- /dev/null
+++ b/media/filters/h265_parser.cc
@@ -0,0 +1,159 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+
+#include "media/base/decrypt_config.h"
+#include "media/filters/h265_parser.h"
+
+namespace media {
+
+#define READ_BITS_OR_RETURN(num_bits, out) \
+ do { \
+ int _out; \
+ if (!br_.ReadBits(num_bits, &_out)) { \
+ DVLOG(1) \
+ << "Error in stream: unexpected EOS while trying to read " #out; \
+ return kInvalidStream; \
+ } \
+ *out = _out; \
+ } while (0)
+
+#define TRUE_OR_RETURN(a) \
+ do { \
+ if (!(a)) { \
+ DVLOG(1) << "Error in stream: invalid value, expected " << #a; \
+ return kInvalidStream; \
+ } \
+ } while (0)
+
+H265NALU::H265NALU() {
+ memset(this, 0, sizeof(*this));
+}
+
+H265Parser::H265Parser() {
+ Reset();
+}
+
+H265Parser::~H265Parser() {
+}
+
+void H265Parser::Reset() {
+ stream_ = NULL;
+ bytes_left_ = 0;
+ encrypted_ranges_.clear();
+}
+
+void H265Parser::SetStream(const uint8* stream, off_t stream_size) {
+ std::vector<SubsampleEntry> subsamples;
+ SetEncryptedStream(stream, stream_size, subsamples);
+}
+
+void H265Parser::SetEncryptedStream(
+ const uint8* stream, off_t stream_size,
+ const std::vector<SubsampleEntry>& subsamples) {
+ DCHECK(stream);
+ DCHECK_GT(stream_size, 0);
+
+ stream_ = stream;
+ bytes_left_ = stream_size;
+
+ encrypted_ranges_.clear();
+ const uint8* start = stream;
+ const uint8* stream_end = stream_ + bytes_left_;
+ for (size_t i = 0; i < subsamples.size() && start < stream_end; ++i) {
+ start += subsamples[i].clear_bytes;
+
+ const uint8* end = std::min(start + subsamples[i].cypher_bytes, stream_end);
+ encrypted_ranges_.Add(start, end);
+ start = end;
+ }
+}
+
+bool H265Parser::LocateNALU(off_t* nalu_size, off_t* start_code_size) {
+ // Find the start code of next NALU.
+ off_t nalu_start_off = 0;
+ off_t annexb_start_code_size = 0;
+
+ if (!H264Parser::FindStartCodeInClearRanges(stream_, bytes_left_,
+ encrypted_ranges_,
+ &nalu_start_off,
+ &annexb_start_code_size)) {
+ DVLOG(4) << "Could not find start code, end of stream?";
+ return false;
+ }
+
+ // Move the stream to the beginning of the NALU (pointing at the start code).
+ stream_ += nalu_start_off;
+ bytes_left_ -= nalu_start_off;
+
+ const uint8* nalu_data = stream_ + annexb_start_code_size;
+ off_t max_nalu_data_size = bytes_left_ - annexb_start_code_size;
+ if (max_nalu_data_size <= 0) {
+ DVLOG(3) << "End of stream";
+ return false;
+ }
+
+ // Find the start code of next NALU;
+ // if successful, |nalu_size_without_start_code| is the number of bytes from
+ // after previous start code to before this one;
+ // if next start code is not found, it is still a valid NALU since there
+ // are some bytes left after the first start code: all the remaining bytes
+ // belong to the current NALU.
+ off_t next_start_code_size = 0;
+ off_t nalu_size_without_start_code = 0;
+ if (!H264Parser::FindStartCodeInClearRanges(nalu_data, max_nalu_data_size,
+ encrypted_ranges_,
+ &nalu_size_without_start_code,
+ &next_start_code_size)) {
+ nalu_size_without_start_code = max_nalu_data_size;
+ }
+ *nalu_size = nalu_size_without_start_code + annexb_start_code_size;
+ *start_code_size = annexb_start_code_size;
+ return true;
+}
+
+H265Parser::Result H265Parser::AdvanceToNextNALU(H265NALU* nalu) {
+ off_t start_code_size;
+ off_t nalu_size_with_start_code;
+ if (!LocateNALU(&nalu_size_with_start_code, &start_code_size)) {
+ DVLOG(4) << "Could not find next NALU, bytes left in stream: "
+ << bytes_left_;
+ return kEOStream;
+ }
+
+ nalu->data = stream_ + start_code_size;
+ nalu->size = nalu_size_with_start_code - start_code_size;
+ DVLOG(4) << "NALU found: size=" << nalu_size_with_start_code;
+
+ // Initialize bit reader at the start of found NALU.
+ if (!br_.Initialize(nalu->data, nalu->size))
+ return kEOStream;
+
+ // Move parser state to after this NALU, so next time AdvanceToNextNALU
+ // is called, we will effectively be skipping it;
+ // other parsing functions will use the position saved
+ // in bit reader for parsing, so we don't have to remember it here.
+ stream_ += nalu_size_with_start_code;
+ bytes_left_ -= nalu_size_with_start_code;
+
+ // Read NALU header, skip the forbidden_zero_bit, but check for it.
+ int data;
+ READ_BITS_OR_RETURN(1, &data);
+ TRUE_OR_RETURN(data == 0);
+
+ READ_BITS_OR_RETURN(6, &nalu->nal_unit_type);
+ READ_BITS_OR_RETURN(6, &nalu->nuh_layer_id);
+ READ_BITS_OR_RETURN(3, &nalu->nuh_temporal_id_plus1);
+
+ DVLOG(4) << "NALU type: " << static_cast<int>(nalu->nal_unit_type)
+ << " at: " << reinterpret_cast<const void*>(nalu->data)
+ << " size: " << nalu->size;
+
+ return kOk;
+}
+
+} // namespace media
diff --git a/media/filters/h265_parser.h b/media/filters/h265_parser.h
new file mode 100644
index 0000000..f3cf713
--- /dev/null
+++ b/media/filters/h265_parser.h
@@ -0,0 +1,151 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This file contains an implementation of an H265 Annex-B video stream parser.
+
+#ifndef MEDIA_FILTERS_H265_PARSER_H_
+#define MEDIA_FILTERS_H265_PARSER_H_
+
+#include <sys/types.h>
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/macros.h"
+#include "media/base/media_export.h"
+#include "media/base/ranges.h"
+#include "media/filters/h264_bit_reader.h"
+#include "media/filters/h264_parser.h"
+
+namespace media {
+
+struct SubsampleEntry;
+
+// For explanations of each struct and its members, see H.265 specification
+// at http://www.itu.int/rec/T-REC-H.265.
+struct MEDIA_EXPORT H265NALU {
+ H265NALU();
+
+ // NAL Unit types are taken from Table 7-1 of HEVC/H265 standard
+ // http://www.itu.int/rec/T-REC-H.265-201410-I/en
+ enum Type {
+ TRAIL_N = 0,
+ TRAIL_R = 1,
+ TSA_N = 2,
+ TSA_R = 3,
+ STSA_N = 4,
+ STSA_R = 5,
+ RADL_N = 6,
+ RADL_R = 7,
+ RASL_N = 8,
+ RASL_R = 9,
+ RSV_VCL_N10 = 10,
+ RSV_VCL_R11 = 11,
+ RSV_VCL_N12 = 12,
+ RSV_VCL_R13 = 13,
+ RSV_VCL_N14 = 14,
+ RSV_VCL_R15 = 15,
+ BLA_W_LP = 16,
+ BLA_W_RADL = 17,
+ BLA_N_LP = 18,
+ IDR_W_RADL = 19,
+ IDR_N_LP = 20,
+ CRA_NUT = 21,
+ RSV_IRAP_VCL22 = 22,
+ RSV_IRAP_VCL23 = 23,
+ RSV_VCL24 = 24,
+ RSV_VCL25 = 25,
+ RSV_VCL26 = 26,
+ RSV_VCL27 = 27,
+ RSV_VCL28 = 28,
+ RSV_VCL29 = 29,
+ RSV_VCL30 = 30,
+ RSV_VCL31 = 31,
+ VPS_NUT = 32,
+ SPS_NUT = 33,
+ PPS_NUT = 34,
+ AUD_NUT = 35,
+ EOS_NUT = 36,
+ EOB_NUT = 37,
+ FD_NUT = 38,
+ PREFIX_SEI_NUT = 39,
+ SUFFIX_SEI_NUT = 40,
+ RSV_NVCL41 = 41,
+ RSV_NVCL42 = 42,
+ RSV_NVCL43 = 43,
+ RSV_NVCL44 = 44,
+ RSV_NVCL45 = 45,
+ RSV_NVCL46 = 46,
+ RSV_NVCL47 = 47,
+ };
+
+ // After (without) start code; we don't own the underlying memory
+ // and a shallow copy should be made when copying this struct.
+ const uint8* data;
+ off_t size; // From after start code to start code of next NALU (or EOS).
+
+ int nal_unit_type;
+ int nuh_layer_id;
+ int nuh_temporal_id_plus1;
+};
+
+// Class to parse an Annex-B H.265 stream.
+class MEDIA_EXPORT H265Parser {
+ public:
+ enum Result {
+ kOk,
+ kInvalidStream, // error in stream
+ kUnsupportedStream, // stream not supported by the parser
+ kEOStream, // end of stream
+ };
+
+ H265Parser();
+ ~H265Parser();
+
+ void Reset();
+ // Set current stream pointer to |stream| of |stream_size| in bytes,
+ // |stream| owned by caller.
+ // |subsamples| contains information about what parts of |stream| are
+ // encrypted.
+ void SetStream(const uint8* stream, off_t stream_size);
+ void SetEncryptedStream(const uint8* stream, off_t stream_size,
+ const std::vector<SubsampleEntry>& subsamples);
+
+ // Read the stream to find the next NALU, identify it and return
+ // that information in |*nalu|. This advances the stream to the beginning
+ // of this NALU, but not past it, so subsequent calls to NALU-specific
+ // parsing functions (ParseSPS, etc.) will parse this NALU.
+ // If the caller wishes to skip the current NALU, it can call this function
+ // again, instead of any NALU-type specific parse functions below.
+ Result AdvanceToNextNALU(H265NALU* nalu);
+
+ private:
+ // Move the stream pointer to the beginning of the next NALU,
+ // i.e. pointing at the next start code.
+ // Return true if a NALU has been found.
+ // If a NALU is found:
+ // - its size in bytes is returned in |*nalu_size| and includes
+ // the start code as well as the trailing zero bits.
+ // - the size in bytes of the start code is returned in |*start_code_size|.
+ bool LocateNALU(off_t* nalu_size, off_t* start_code_size);
+
+ // Pointer to the current NALU in the stream.
+ const uint8* stream_;
+
+ // Bytes left in the stream after the current NALU.
+ off_t bytes_left_;
+
+ H264BitReader br_;
+
+ // Ranges of encrypted bytes in the buffer passed to
+ // SetEncryptedStream().
+ Ranges<const uint8*> encrypted_ranges_;
+
+ DISALLOW_COPY_AND_ASSIGN(H265Parser);
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_H265_PARSER_H_
diff --git a/media/filters/h265_parser_unittest.cc b/media/filters/h265_parser_unittest.cc
new file mode 100644
index 0000000..c590b50
--- /dev/null
+++ b/media/filters/h265_parser_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/memory_mapped_file.h"
+#include "base/logging.h"
+#include "media/base/test_data_util.h"
+#include "media/filters/h265_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+TEST(H265ParserTest, RawHevcStreamFileParsing) {
+ base::FilePath file_path = GetTestDataFilePath("bear.hevc");
+ // Number of NALUs in the test stream to be parsed.
+ const int num_nalus = 35;
+
+ base::MemoryMappedFile stream;
+ ASSERT_TRUE(stream.Initialize(file_path))
+ << "Couldn't open stream file: " << file_path.MaybeAsASCII();
+
+ H265Parser parser;
+ parser.SetStream(stream.data(), stream.length());
+
+ // Parse until the end of stream/unsupported stream/error in stream is found.
+ int num_parsed_nalus = 0;
+ while (true) {
+ H265NALU nalu;
+ H265Parser::Result res = parser.AdvanceToNextNALU(&nalu);
+ if (res == H265Parser::kEOStream) {
+ DVLOG(1) << "Number of successfully parsed NALUs before EOS: "
+ << num_parsed_nalus;
+ ASSERT_EQ(num_nalus, num_parsed_nalus);
+ return;
+ }
+ ASSERT_EQ(res, H265Parser::kOk);
+
+ ++num_parsed_nalus;
+ DVLOG(4) << "Found NALU " << nalu.nal_unit_type;
+ }
+}
+
+} // namespace media
diff --git a/media/filters/stream_parser_factory.cc b/media/filters/stream_parser_factory.cc
index 83054f3..fc3257b 100644
--- a/media/filters/stream_parser_factory.cc
+++ b/media/filters/stream_parser_factory.cc
@@ -52,7 +52,8 @@ struct CodecInfo {
HISTOGRAM_EAC3,
HISTOGRAM_MP3,
HISTOGRAM_OPUS,
- HISTOGRAM_MAX = HISTOGRAM_OPUS // Must be equal to largest logged entry.
+ HISTOGRAM_HEVC,
+ HISTOGRAM_MAX = HISTOGRAM_HEVC // Must be equal to largest logged entry.
};
const char* pattern;
@@ -148,6 +149,12 @@ static const CodecInfo kH264AVC1CodecInfo = { "avc1.*", CodecInfo::VIDEO, NULL,
CodecInfo::HISTOGRAM_H264 };
static const CodecInfo kH264AVC3CodecInfo = { "avc3.*", CodecInfo::VIDEO, NULL,
CodecInfo::HISTOGRAM_H264 };
+#if defined(ENABLE_HEVC_DEMUXING)
+static const CodecInfo kHEVCHEV1CodecInfo = { "hev1.*", CodecInfo::VIDEO, NULL,
+ CodecInfo::HISTOGRAM_HEVC };
+static const CodecInfo kHEVCHVC1CodecInfo = { "hvc1.*", CodecInfo::VIDEO, NULL,
+ CodecInfo::HISTOGRAM_HEVC };
+#endif
static const CodecInfo kMPEG4AACCodecInfo = { "mp4a.40.*", CodecInfo::AUDIO,
&ValidateMP4ACodecID,
CodecInfo::HISTOGRAM_MPEG4AAC };
@@ -158,6 +165,10 @@ static const CodecInfo kMPEG2AACLCCodecInfo = { "mp4a.67", CodecInfo::AUDIO,
static const CodecInfo* kVideoMP4Codecs[] = {
&kH264AVC1CodecInfo,
&kH264AVC3CodecInfo,
+#if defined(ENABLE_HEVC_DEMUXING)
+ &kHEVCHEV1CodecInfo,
+ &kHEVCHVC1CodecInfo,
+#endif
&kMPEG4AACCodecInfo,
&kMPEG2AACLCCodecInfo,
NULL
diff --git a/media/formats/mp4/box_definitions.cc b/media/formats/mp4/box_definitions.cc
index 9b46138..fc0250d 100644
--- a/media/formats/mp4/box_definitions.cc
+++ b/media/formats/mp4/box_definitions.cc
@@ -5,9 +5,16 @@
#include "media/formats/mp4/box_definitions.h"
#include "base/logging.h"
+#include "media/base/video_types.h"
+#include "media/base/video_util.h"
+#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/es_descriptor.h"
#include "media/formats/mp4/rcheck.h"
+#if defined(ENABLE_HEVC_DEMUXING)
+#include "media/formats/mp4/hevc.h"
+#endif
+
namespace media {
namespace mp4 {
@@ -455,7 +462,9 @@ VideoSampleEntry::VideoSampleEntry()
: format(FOURCC_NULL),
data_reference_index(0),
width(0),
- height(0) {}
+ height(0),
+ video_codec(kUnknownVideoCodec),
+ video_codec_profile(VIDEO_CODEC_PROFILE_UNKNOWN) {}
VideoSampleEntry::~VideoSampleEntry() {}
FourCC VideoSampleEntry::BoxType() const {
@@ -464,6 +473,26 @@ FourCC VideoSampleEntry::BoxType() const {
return FOURCC_NULL;
}
+namespace {
+
+bool IsFormatValidH264(const FourCC& format,
+ const ProtectionSchemeInfo& sinf) {
+ return format == FOURCC_AVC1 || format == FOURCC_AVC3 ||
+ (format == FOURCC_ENCV && (sinf.format.format == FOURCC_AVC1 ||
+ sinf.format.format == FOURCC_AVC3));
+}
+
+#if defined(ENABLE_HEVC_DEMUXING)
+bool IsFormatValidHEVC(const FourCC& format,
+ const ProtectionSchemeInfo& sinf) {
+ return format == FOURCC_HEV1 || format == FOURCC_HVC1 ||
+ (format == FOURCC_ENCV && (sinf.format.format == FOURCC_HEV1 ||
+ sinf.format.format == FOURCC_HVC1));
+}
+#endif
+
+}
+
bool VideoSampleEntry::Parse(BoxReader* reader) {
format = reader->type();
RCHECK(reader->SkipBytes(6) &&
@@ -485,21 +514,44 @@ bool VideoSampleEntry::Parse(BoxReader* reader) {
}
}
- if (IsFormatValid()) {
+ if (IsFormatValidH264(format, sinf)) {
+ DVLOG(2) << __FUNCTION__
+ << " reading AVCDecoderConfigurationRecord (avcC)";
scoped_ptr<AVCDecoderConfigurationRecord> avcConfig(
new AVCDecoderConfigurationRecord());
RCHECK(reader->ReadChild(avcConfig.get()));
frame_bitstream_converter = make_scoped_refptr(
new AVCBitstreamConverter(avcConfig.Pass()));
+ video_codec = kCodecH264;
+ video_codec_profile = H264PROFILE_MAIN;
+#if defined(ENABLE_HEVC_DEMUXING)
+ } else if (IsFormatValidHEVC(format, sinf)) {
+ DVLOG(2) << __FUNCTION__
+ << " parsing HEVCDecoderConfigurationRecord (hvcC)";
+ scoped_ptr<HEVCDecoderConfigurationRecord> hevcConfig(
+ new HEVCDecoderConfigurationRecord());
+ RCHECK(reader->ReadChild(hevcConfig.get()));
+ frame_bitstream_converter = make_scoped_refptr(
+ new HEVCBitstreamConverter(hevcConfig.Pass()));
+ video_codec = kCodecHEVC;
+#endif
+ } else {
+ // Unknown/unsupported format
+ MEDIA_LOG(ERROR, reader->media_log()) << __FUNCTION__
+ << " unsupported video format "
+ << FourCCToString(format);
+ return false;
}
return true;
}
bool VideoSampleEntry::IsFormatValid() const {
- return format == FOURCC_AVC1 || format == FOURCC_AVC3 ||
- (format == FOURCC_ENCV && (sinf.format.format == FOURCC_AVC1 ||
- sinf.format.format == FOURCC_AVC3));
+#if defined(ENABLE_HEVC_DEMUXING)
+ if (IsFormatValidHEVC(format, sinf))
+ return true;
+#endif
+ return IsFormatValidH264(format, sinf);
}
ElementaryStreamDescriptor::ElementaryStreamDescriptor()
diff --git a/media/formats/mp4/box_definitions.h b/media/formats/mp4/box_definitions.h
index 39a19b7..cdcd775 100644
--- a/media/formats/mp4/box_definitions.h
+++ b/media/formats/mp4/box_definitions.h
@@ -12,6 +12,7 @@
#include "base/compiler_specific.h"
#include "media/base/media_export.h"
#include "media/base/media_log.h"
+#include "media/base/video_decoder_config.h"
#include "media/formats/mp4/aac.h"
#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/box_reader.h"
@@ -207,6 +208,9 @@ struct MEDIA_EXPORT VideoSampleEntry : Box {
PixelAspectRatioBox pixel_aspect;
ProtectionSchemeInfo sinf;
+ VideoCodec video_codec;
+ VideoCodecProfile video_codec_profile;
+
bool IsFormatValid() const;
scoped_refptr<BitstreamConverter> frame_bitstream_converter;
diff --git a/media/formats/mp4/fourccs.h b/media/formats/mp4/fourccs.h
index d9086fa..fd97797 100644
--- a/media/formats/mp4/fourccs.h
+++ b/media/formats/mp4/fourccs.h
@@ -31,6 +31,11 @@ enum FourCC {
FOURCC_FTYP = 0x66747970,
FOURCC_HDLR = 0x68646c72,
FOURCC_HINT = 0x68696e74,
+#if defined(ENABLE_HEVC_DEMUXING)
+ FOURCC_HEV1 = 0x68657631,
+ FOURCC_HVC1 = 0x68766331,
+ FOURCC_HVCC = 0x68766343,
+#endif
FOURCC_IODS = 0x696f6473,
FOURCC_MDAT = 0x6d646174,
FOURCC_MDHD = 0x6d646864,
diff --git a/media/formats/mp4/hevc.cc b/media/formats/mp4/hevc.cc
new file mode 100644
index 0000000..4d6b53e
--- /dev/null
+++ b/media/formats/mp4/hevc.cc
@@ -0,0 +1,237 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/formats/mp4/hevc.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/logging.h"
+#include "media/base/decrypt_config.h"
+#include "media/filters/h265_parser.h"
+#include "media/formats/mp4/avc.h"
+#include "media/formats/mp4/box_definitions.h"
+#include "media/formats/mp4/box_reader.h"
+
+namespace media {
+namespace mp4 {
+
+HEVCDecoderConfigurationRecord::HEVCDecoderConfigurationRecord()
+ : configurationVersion(0),
+ general_profile_space(0),
+ general_tier_flag(0),
+ general_profile_idc(0),
+ general_profile_compatibility_flags(0),
+ general_constraint_indicator_flags(0),
+ general_level_idc(0),
+ min_spatial_segmentation_idc(0),
+ parallelismType(0),
+ chromaFormat(0),
+ bitDepthLumaMinus8(0),
+ bitDepthChromaMinus8(0),
+ avgFrameRate(0),
+ constantFrameRate(0),
+ numTemporalLayers(0),
+ temporalIdNested(0),
+ lengthSizeMinusOne(0),
+ numOfArrays(0) {}
+
+HEVCDecoderConfigurationRecord::~HEVCDecoderConfigurationRecord() {}
+FourCC HEVCDecoderConfigurationRecord::BoxType() const { return FOURCC_HVCC; }
+
+bool HEVCDecoderConfigurationRecord::Parse(BoxReader* reader) {
+ return ParseInternal(reader, reader->media_log());
+}
+
+bool HEVCDecoderConfigurationRecord::Parse(const uint8* data, int data_size) {
+ BufferReader reader(data, data_size);
+ return ParseInternal(&reader, new MediaLog());
+}
+
+HEVCDecoderConfigurationRecord::HVCCNALArray::HVCCNALArray()
+ : first_byte(0) {}
+
+HEVCDecoderConfigurationRecord::HVCCNALArray::~HVCCNALArray() {}
+
+bool HEVCDecoderConfigurationRecord::ParseInternal(
+ BufferReader* reader,
+ const scoped_refptr<MediaLog>& media_log) {
+ uint8 profile_indication = 0;
+ uint32 general_constraint_indicator_flags_hi = 0;
+ uint16 general_constraint_indicator_flags_lo = 0;
+ uint8 misc = 0;
+ RCHECK(reader->Read1(&configurationVersion) && configurationVersion == 1 &&
+ reader->Read1(&profile_indication) &&
+ reader->Read4(&general_profile_compatibility_flags) &&
+ reader->Read4(&general_constraint_indicator_flags_hi) &&
+ reader->Read2(&general_constraint_indicator_flags_lo) &&
+ reader->Read1(&general_level_idc) &&
+ reader->Read2(&min_spatial_segmentation_idc) &&
+ reader->Read1(&parallelismType) &&
+ reader->Read1(&chromaFormat) &&
+ reader->Read1(&bitDepthLumaMinus8) &&
+ reader->Read1(&bitDepthChromaMinus8) &&
+ reader->Read2(&avgFrameRate) &&
+ reader->Read1(&misc) &&
+ reader->Read1(&numOfArrays));
+
+ general_profile_space = profile_indication >> 6;
+ general_tier_flag = (profile_indication >> 5) & 1;
+ general_profile_idc = profile_indication & 0x1f;
+
+ general_constraint_indicator_flags = general_constraint_indicator_flags_hi;
+ general_constraint_indicator_flags <<= 16;
+ general_constraint_indicator_flags |= general_constraint_indicator_flags_lo;
+
+ min_spatial_segmentation_idc &= 0xfff;
+ parallelismType &= 3;
+ chromaFormat &= 3;
+ bitDepthLumaMinus8 &= 7;
+ bitDepthChromaMinus8 &= 7;
+
+ constantFrameRate = misc >> 6;
+ numTemporalLayers = (misc >> 3) & 7;
+ temporalIdNested = (misc >> 2) & 1;
+ lengthSizeMinusOne = misc & 3;
+
+ DVLOG(2) << __FUNCTION__ << " numOfArrays=" << (int)numOfArrays;
+ arrays.resize(numOfArrays);
+ for (uint32 j = 0; j < numOfArrays; j++) {
+ RCHECK(reader->Read1(&arrays[j].first_byte));
+ uint16 numNalus = 0;
+ RCHECK(reader->Read2(&numNalus));
+ arrays[j].units.resize(numNalus);
+ for (uint32 i = 0; i < numNalus; ++i) {
+ uint16 naluLength = 0;
+ RCHECK(reader->Read2(&naluLength) &&
+ reader->ReadVec(&arrays[j].units[i], naluLength));
+ DVLOG(4) << __FUNCTION__ << " naluType="
+ << (int)(arrays[j].first_byte & 0x3f)
+ << " size=" << arrays[j].units[i].size();
+ }
+ }
+
+ if (media_log.get()) {
+ MEDIA_LOG(INFO, media_log) << "Video codec: hevc";
+ }
+
+ return true;
+}
+
+static const uint8 kAnnexBStartCode[] = {0, 0, 0, 1};
+static const int kAnnexBStartCodeSize = 4;
+
+bool HEVC::InsertParamSetsAnnexB(
+ const HEVCDecoderConfigurationRecord& hevc_config,
+ std::vector<uint8>* buffer,
+ std::vector<SubsampleEntry>* subsamples) {
+ DCHECK(HEVC::IsValidAnnexB(*buffer, *subsamples));
+
+ scoped_ptr<H265Parser> parser(new H265Parser());
+ const uint8* start = &(*buffer)[0];
+ parser->SetEncryptedStream(start, buffer->size(), *subsamples);
+
+ H265NALU nalu;
+ if (parser->AdvanceToNextNALU(&nalu) != H265Parser::kOk)
+ return false;
+
+ std::vector<uint8>::iterator config_insert_point = buffer->begin();
+
+ if (nalu.nal_unit_type == H265NALU::AUD_NUT) {
+ // Move insert point to just after the AUD.
+ config_insert_point += (nalu.data + nalu.size) - start;
+ }
+
+ // Clear |parser| and |start| since they aren't needed anymore and
+ // will hold stale pointers once the insert happens.
+ parser.reset();
+ start = NULL;
+
+ std::vector<uint8> param_sets;
+ RCHECK(HEVC::ConvertConfigToAnnexB(hevc_config, &param_sets));
+ DVLOG(4) << __FUNCTION__ << " converted hvcC to AnnexB "
+ << " size=" << param_sets.size() << " inserted at "
+ << (int)(config_insert_point - buffer->begin());
+
+ if (subsamples && !subsamples->empty()) {
+ int subsample_index = AVC::FindSubsampleIndex(*buffer, subsamples,
+ &(*config_insert_point));
+ // Update the size of the subsample where SPS/PPS is to be inserted.
+ (*subsamples)[subsample_index].clear_bytes += param_sets.size();
+ }
+
+ buffer->insert(config_insert_point,
+ param_sets.begin(), param_sets.end());
+
+ DCHECK(HEVC::IsValidAnnexB(*buffer, *subsamples));
+ return true;
+}
+
+bool HEVC::ConvertConfigToAnnexB(
+ const HEVCDecoderConfigurationRecord& hevc_config,
+ std::vector<uint8>* buffer) {
+ DCHECK(buffer->empty());
+ buffer->clear();
+
+ for (size_t j = 0; j < hevc_config.arrays.size(); j++) {
+ uint8 naluType = hevc_config.arrays[j].first_byte & 0x3f;
+ for (size_t i = 0; i < hevc_config.arrays[j].units.size(); ++i) {
+ DVLOG(3) << __FUNCTION__ << " naluType=" << (int)naluType
+ << " size=" << hevc_config.arrays[j].units[i].size();
+ buffer->insert(buffer->end(), kAnnexBStartCode,
+ kAnnexBStartCode + kAnnexBStartCodeSize);
+ buffer->insert(buffer->end(), hevc_config.arrays[j].units[i].begin(),
+ hevc_config.arrays[j].units[i].end());
+ }
+ }
+
+ return true;
+}
+
+// Verifies AnnexB NALU order according to section 7.4.2.4.4 of ISO/IEC 23008-2.
+bool HEVC::IsValidAnnexB(const std::vector<uint8>& buffer,
+ const std::vector<SubsampleEntry>& subsamples) {
+ return IsValidAnnexB(&buffer[0], buffer.size(), subsamples);
+}
+
+bool HEVC::IsValidAnnexB(const uint8* buffer, size_t size,
+ const std::vector<SubsampleEntry>& subsamples) {
+ DCHECK(buffer);
+
+ if (size == 0)
+ return true;
+
+ // TODO(servolk): Implement this, see crbug.com/527595
+ return true;
+}
+
+HEVCBitstreamConverter::HEVCBitstreamConverter(
+ scoped_ptr<HEVCDecoderConfigurationRecord> hevc_config)
+ : hevc_config_(hevc_config.Pass()) {
+ DCHECK(hevc_config_);
+}
+
+HEVCBitstreamConverter::~HEVCBitstreamConverter() {
+}
+
+bool HEVCBitstreamConverter::ConvertFrame(
+ std::vector<uint8>* frame_buf,
+ bool is_keyframe,
+ std::vector<SubsampleEntry>* subsamples) const {
+ RCHECK(AVC::ConvertFrameToAnnexB(hevc_config_->lengthSizeMinusOne + 1,
+ frame_buf, subsamples));
+
+ if (is_keyframe) {
+ // If this is a keyframe, we (re-)inject HEVC params headers at the start of
+ // a frame. If subsample info is present, we also update the clear byte
+ // count for that first subsample.
+ RCHECK(HEVC::InsertParamSetsAnnexB(*hevc_config_, frame_buf, subsamples));
+ }
+
+ DCHECK(HEVC::IsValidAnnexB(*frame_buf, *subsamples));
+ return true;
+}
+
+} // namespace mp4
+} // namespace media
diff --git a/media/formats/mp4/hevc.h b/media/formats/mp4/hevc.h
new file mode 100644
index 0000000..06974c0d
--- /dev/null
+++ b/media/formats/mp4/hevc.h
@@ -0,0 +1,106 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FORMATS_MP4_HEVC_H_
+#define MEDIA_FORMATS_MP4_HEVC_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "media/base/media_export.h"
+#include "media/formats/mp4/bitstream_converter.h"
+#include "media/formats/mp4/box_definitions.h"
+
+namespace media {
+
+struct SubsampleEntry;
+
+namespace mp4 {
+
+struct MEDIA_EXPORT HEVCDecoderConfigurationRecord : Box {
+ DECLARE_BOX_METHODS(HEVCDecoderConfigurationRecord);
+
+ // Parses HEVCDecoderConfigurationRecord data encoded in |data|.
+ // Note: This method is intended to parse data outside the MP4StreamParser
+ // context and therefore the box header is not expected to be present
+ // in |data|.
+ // Returns true if |data| was successfully parsed.
+ bool Parse(const uint8* data, int data_size);
+
+ uint8 configurationVersion;
+ uint8 general_profile_space;
+ uint8 general_tier_flag;
+ uint8 general_profile_idc;
+ uint32 general_profile_compatibility_flags;
+ uint64 general_constraint_indicator_flags;
+ uint8 general_level_idc;
+ uint16 min_spatial_segmentation_idc;
+ uint8 parallelismType;
+ uint8 chromaFormat;
+ uint8 bitDepthLumaMinus8;
+ uint8 bitDepthChromaMinus8;
+ uint16 avgFrameRate;
+ uint8 constantFrameRate;
+ uint8 numTemporalLayers;
+ uint8 temporalIdNested;
+ uint8 lengthSizeMinusOne;
+ uint8 numOfArrays;
+
+ typedef std::vector<uint8> HVCCNALUnit;
+ struct HVCCNALArray {
+ HVCCNALArray();
+ ~HVCCNALArray();
+ uint8 first_byte;
+ std::vector<HVCCNALUnit> units;
+ };
+ std::vector<HVCCNALArray> arrays;
+
+ private:
+ bool ParseInternal(BufferReader* reader,
+ const scoped_refptr<MediaLog>& media_log);
+};
+
+class MEDIA_EXPORT HEVC {
+ public:
+ static bool ConvertConfigToAnnexB(
+ const HEVCDecoderConfigurationRecord& hevc_config,
+ std::vector<uint8>* buffer);
+
+ static bool InsertParamSetsAnnexB(
+ const HEVCDecoderConfigurationRecord& hevc_config,
+ std::vector<uint8>* buffer,
+ std::vector<SubsampleEntry>* subsamples);
+
+ // Verifies that the contents of |buffer| conform to
+ // Section 7.4.2.4.4 of ISO/IEC 23008-2.
+ // |subsamples| contains the information about what parts of the buffer are
+ // encrypted and which parts are clear.
+ // Returns true if |buffer| contains conformant Annex B data
+ // TODO(servolk): Remove the std::vector version when we can use,
+ // C++11's std::vector<T>::data() method.
+ static bool IsValidAnnexB(const std::vector<uint8>& buffer,
+ const std::vector<SubsampleEntry>& subsamples);
+ static bool IsValidAnnexB(const uint8* buffer, size_t size,
+ const std::vector<SubsampleEntry>& subsamples);
+};
+
+class HEVCBitstreamConverter : public BitstreamConverter {
+ public:
+ explicit HEVCBitstreamConverter(
+ scoped_ptr<HEVCDecoderConfigurationRecord> hevc_config);
+
+ // BitstreamConverter interface
+ bool ConvertFrame(std::vector<uint8>* frame_buf,
+ bool is_keyframe,
+ std::vector<SubsampleEntry>* subsamples) const override;
+ private:
+ ~HEVCBitstreamConverter() override;
+ scoped_ptr<HEVCDecoderConfigurationRecord> hevc_config_;
+};
+
+} // namespace mp4
+} // namespace media
+
+#endif // MEDIA_FORMATS_MP4_HEVC_H_
diff --git a/media/formats/mp4/mp4_stream_parser.cc b/media/formats/mp4/mp4_stream_parser.cc
index 59164d9..18698b3 100644
--- a/media/formats/mp4/mp4_stream_parser.cc
+++ b/media/formats/mp4/mp4_stream_parser.cc
@@ -305,9 +305,9 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
is_video_track_encrypted_ = entry.sinf.info.track_encryption.is_encrypted;
DVLOG(1) << "is_video_track_encrypted_: " << is_video_track_encrypted_;
- video_config.Initialize(kCodecH264, H264PROFILE_MAIN, PIXEL_FORMAT_YV12,
- COLOR_SPACE_HD_REC709, coded_size, visible_rect,
- natural_size,
+ video_config.Initialize(entry.video_codec, entry.video_codec_profile,
+ PIXEL_FORMAT_YV12, COLOR_SPACE_HD_REC709,
+ coded_size, visible_rect, natural_size,
// No decoder-specific buffer needed for AVC;
// SPS/PPS are embedded in the video stream
NULL, 0, is_video_track_encrypted_);
@@ -478,7 +478,8 @@ bool MP4StreamParser::EnqueueSample(BufferQueue* audio_buffers,
DCHECK(runs_->video_description().frame_bitstream_converter);
if (!runs_->video_description().frame_bitstream_converter->ConvertFrame(
&frame_buf, runs_->is_keyframe(), &subsamples)) {
- MEDIA_LOG(ERROR, media_log_) << "Failed to prepare AVC sample for decode";
+ MEDIA_LOG(ERROR, media_log_)
+ << "Failed to prepare video sample for decode";
*err = true;
return false;
}
diff --git a/media/formats/mp4/mp4_stream_parser_unittest.cc b/media/formats/mp4/mp4_stream_parser_unittest.cc
index 620b81f..60e4165 100644
--- a/media/formats/mp4/mp4_stream_parser_unittest.cc
+++ b/media/formats/mp4/mp4_stream_parser_unittest.cc
@@ -294,6 +294,15 @@ TEST_F(MP4StreamParserTest, VideoSamplesStartWithAUDs) {
ParseMP4File("bear-1280x720-av_with-aud-nalus_frag.mp4", 512);
}
+#if defined(ENABLE_HEVC_DEMUXING)
+TEST_F(MP4StreamParserTest, HEVC_in_MP4_container) {
+ InitializeParserAndExpectLiveness(DemuxerStream::LIVENESS_RECORDED);
+ scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("bear-hevc-frag.mp4");
+ EXPECT_MEDIA_LOG(VideoCodecLog("hevc"));
+ EXPECT_TRUE(AppendDataInPieces(buffer->data(), buffer->data_size(), 512));
+}
+#endif
+
TEST_F(MP4StreamParserTest, CENC) {
// Encrypted test mp4 files have non-zero duration and are treated as
// recorded streams.
diff --git a/media/media.gyp b/media/media.gyp
index 4fe8a2a..3d48e59 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -33,6 +33,13 @@
}, {
'use_low_memory_buffer%': 0,
}],
+ ['chromecast==1', {
+ # Enable HEVC/H265 demuxing. Actual decoding must be provided by the
+ # platform.
+ 'enable_hevc_demuxing%': 1,
+ }, {
+ 'enable_hevc_demuxing%': 0,
+ }],
],
},
'includes': [
@@ -1096,6 +1103,23 @@
'formats/mpeg/mpeg_audio_stream_parser_base.h',
],
}],
+ ['proprietary_codecs==1 and enable_hevc_demuxing==1', {
+ 'defines': [
+ 'ENABLE_HEVC_DEMUXING'
+ ],
+ 'sources': [
+ 'filters/h265_parser.cc',
+ 'filters/h265_parser.h',
+ 'formats/mp4/hevc.cc',
+ 'formats/mp4/hevc.h',
+ ],
+ }],
+ ['proprietary_codecs==1 and enable_hevc_demuxing==1 and media_use_ffmpeg==1', {
+ 'sources': [
+ 'filters/ffmpeg_h265_to_annex_b_bitstream_converter.cc',
+ 'filters/ffmpeg_h265_to_annex_b_bitstream_converter.h',
+ ],
+ }],
['target_arch=="ia32" or target_arch=="x64"', {
'dependencies': [
'media_asm',
@@ -1304,6 +1328,14 @@
'USE_NEON'
],
}],
+ ['proprietary_codecs==1 and enable_hevc_demuxing==1', {
+ 'defines': [
+ 'ENABLE_HEVC_DEMUXING'
+ ],
+ 'sources': [
+ 'filters/h265_parser_unittest.cc',
+ ],
+ }],
['media_use_ffmpeg==1', {
'dependencies': [
'../third_party/ffmpeg/ffmpeg.gyp:ffmpeg',
diff --git a/media/media_options.gni b/media/media_options.gni
index a3b3a8a..746d96d 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -49,6 +49,10 @@ declare_args() {
# default since it's not available on the normal Web Platform and costs money.
enable_mpeg2ts_stream_parser = false
+ # Enable HEVC/H265 demuxing. Actual decoding must be provided by the
+ # platform.
+ enable_hevc_demuxing = false
+
# Experiment to enable mojo media application: http://crbug.com/431776
# Valid options are:
# - "none": Do not use mojo media application.
diff --git a/media/mojo/interfaces/media_types.mojom b/media/mojo/interfaces/media_types.mojom
index 864c030..8470197 100644
--- a/media/mojo/interfaces/media_types.mojom
+++ b/media/mojo/interfaces/media_types.mojom
@@ -130,7 +130,8 @@ enum VideoCodec {
Theora,
VP8,
VP9,
- Max = VP9,
+ HEVC,
+ Max = HEVC,
};
// See media/base/video_decoder_config.h for descriptions.
diff --git a/media/mojo/services/media_type_converters.cc b/media/mojo/services/media_type_converters.cc
index 8b32124..4527010 100644
--- a/media/mojo/services/media_type_converters.cc
+++ b/media/mojo/services/media_type_converters.cc
@@ -160,6 +160,7 @@ ASSERT_ENUM_EQ_RAW(ColorSpace, COLOR_SPACE_MAX, COLOR_SPACE_MAX);
// VideoCodec
ASSERT_ENUM_EQ_RAW(VideoCodec, kUnknownVideoCodec, VIDEO_CODEC_UNKNOWN);
ASSERT_ENUM_EQ(VideoCodec, kCodec, VIDEO_CODEC_, H264);
+ASSERT_ENUM_EQ(VideoCodec, kCodec, VIDEO_CODEC_, HEVC);
ASSERT_ENUM_EQ(VideoCodec, kCodec, VIDEO_CODEC_, VC1);
ASSERT_ENUM_EQ(VideoCodec, kCodec, VIDEO_CODEC_, MPEG2);
ASSERT_ENUM_EQ(VideoCodec, kCodec, VIDEO_CODEC_, MPEG4);
diff --git a/media/test/data/bear-hevc-frag.mp4 b/media/test/data/bear-hevc-frag.mp4
new file mode 100644
index 0000000..1b85aba
--- /dev/null
+++ b/media/test/data/bear-hevc-frag.mp4
Binary files differ
diff --git a/media/test/data/bear.hevc b/media/test/data/bear.hevc
new file mode 100644
index 0000000..f2a0545
--- /dev/null
+++ b/media/test/data/bear.hevc
Binary files differ