summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content/browser/media/media_canplaytype_browsertest.cc120
-rw-r--r--content/content_tests.gypi5
-rw-r--r--content/test/BUILD.gn5
-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
33 files changed, 1256 insertions, 22 deletions
diff --git a/content/browser/media/media_canplaytype_browsertest.cc b/content/browser/media/media_canplaytype_browsertest.cc
index ca3e6f3..8629e01 100644
--- a/content/browser/media/media_canplaytype_browsertest.cc
+++ b/content/browser/media/media_canplaytype_browsertest.cc
@@ -39,6 +39,13 @@ const char kOggOpusProbably[] = "";
const char kMpeg2AacProbably[] = "";
#endif // !OS_ANDROID
+#if defined(ENABLE_HEVC_DEMUXING)
+const char* kHevcSupported = kPropProbably;
+#else
+const char* kHevcSupported = kNot;
+#endif
+
+
namespace content {
class MediaCanPlayTypeTest : public MediaBrowserTest {
@@ -178,6 +185,50 @@ class MediaCanPlayTypeTest : public MediaBrowserTest {
EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"mp4ax\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"unknown\"'"));
+
+ // Don't allow incomplete/ambiguous codec ids for HEVC.
+ // Codec string must have info about codec level/profile, as described in
+ // ISO/IEC FDIS 14496-15 section E.3, for example "hev1.1.6.L93.B0"
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1\"'"));
+
+ // Invalid codecs that look like something similar to HEVC/H.265
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1x\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1x\"'"));
+ // First component of codec id must be "hev1" or "hvc1" (case-sensitive)
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hevc.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev0.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc0.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev2.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc2.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"HEVC.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"HEV0.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"HVC0.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"HEV2.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"HVC2.1.6.L93.B0\"'"));
+
+ // TODO(servolk): Uncomment these after crbug.com/482761 is fixed.
+ // Trailing dot is not allowed.
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0.\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0.\"'"));
+ // Invalid general_profile_space/general_profile_idc
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.x.6.L93.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.x.6.L93.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.d1.6.L93.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.d1.6.L93.B0\"'"));
+ // Invalid general_profile_compatibility_flags
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.x.L93.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.x.L93.B0\"'"));
+ // Invalid general_tier_flag
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.x.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.x.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.Lx.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.Lx.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.Hx.B0\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.Hx.B0\"'"));
+ // Invalid constraint flags
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.x\"'"));
+ //EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.x\"'"));
}
void TestOGGUnacceptableCombinations(const std::string& mime) {
@@ -201,6 +252,11 @@ class MediaCanPlayTypeTest : public MediaBrowserTest {
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc1, opus\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc3, opus\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0,opus\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0,opus\"'"));
+
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.2\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.02\"'"));
@@ -242,6 +298,11 @@ class MediaCanPlayTypeTest : public MediaBrowserTest {
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc1, opus\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc3, opus\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0,opus\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0,opus\"'"));
+
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.2\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.02\"'"));
@@ -279,6 +340,11 @@ class MediaCanPlayTypeTest : public MediaBrowserTest {
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc1, 1\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"avc3, 1\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hev1.1.6.L93.B0,opus\"'"));
+ EXPECT_EQ(kNot, CanPlay("'" + mime + "; codecs=\"hvc1.1.6.L93.B0,opus\"'"));
+
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.2\"'"));
EXPECT_EQ(kNot, CanPlay("'" + mime +"; codecs=\"mp4a.40.02\"'"));
@@ -493,6 +559,15 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_mp4) {
EXPECT_EQ(kPropMaybe,
CanPlay("'video/mp4; codecs=\"avc3.42E01E, mp4a.40\"'"));
+ // TODO(servolk): Add more unit test coverage once we have more info about
+ // various HEVC levels/profiles (crbug.com/482761).
+ EXPECT_EQ(kHevcSupported, CanPlay("'video/mp4; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kHevcSupported, CanPlay("'video/mp4; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/mp4; codecs=\"hev1.1.6.L93.B0, mp4a.40.5\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/mp4; codecs=\"hvc1.1.6.L93.B0, mp4a.40.5\"'"));
+
TestMPEGUnacceptableCombinations("video/mp4");
EXPECT_EQ(kPropMaybe, CanPlay("'video/x-m4v'"));
@@ -536,6 +611,15 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_mp4) {
EXPECT_EQ(kPropMaybe,
CanPlay("'video/x-m4v; codecs=\"avc3.42E01E, mp4a.40\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/x-m4v; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/x-m4v; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/x-m4v; codecs=\"hev1.1.6.L93.B0, mp4a.40.5\"'"));
+ EXPECT_EQ(kHevcSupported,
+ CanPlay("'video/x-m4v; codecs=\"hvc1.1.6.L93.B0, mp4a.40.5\"'"));
+
TestMPEGUnacceptableCombinations("video/x-m4v");
EXPECT_EQ(kPropMaybe, CanPlay("'audio/mp4'"));
@@ -554,6 +638,11 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_mp4) {
EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"avc1.4D401E\"'"));
EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"avc3.64001F\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"hev1.1.6.L93.B0,mp4a.40.5\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/mp4; codecs=\"hvc1.1.6.L93.B0,mp4a.40.5\"'"));
+
TestMPEGUnacceptableCombinations("audio/mp4");
EXPECT_EQ(kPropMaybe, CanPlay("'audio/x-m4a'"));
@@ -572,6 +661,13 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_mp4) {
EXPECT_EQ(kNot, CanPlay("'audio/x-m4a; codecs=\"avc1.4D401E\"'"));
EXPECT_EQ(kNot, CanPlay("'audio/x-m4a; codecs=\"avc3.64001F\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/x-m4a; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot, CanPlay("'audio/x-m4a; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'audio/x-m4a; codecs=\"hev1.1.6.L93.B0, mp4a.40.5\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'audio/x-m4a; codecs=\"hvc1.1.6.L93.B0, mp4a.40.5\"'"));
+
TestMPEGUnacceptableCombinations("audio/x-m4a");
}
@@ -804,6 +900,10 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_AvcLevels) {
EXPECT_EQ(kPropMaybe, CanPlay("'video/mp4; codecs=\"avc1.42E052\"'"));
}
+// TODO(servolk): Add extensive tests for various HEVC profiles, levels and
+// tiers, similar to avc1/avc3 tests above, after proper HEVC codec id parsing
+// is implemented (crbug.com/482761)
+
// All values that return positive results are tested. There are also
// negative tests for values around or that could potentially be confused with
// (e.g. case, truncation, hex <-> deciemal conversion) those values that return
@@ -945,6 +1045,15 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_HLS) {
EXPECT_EQ(probablyCanPlayHLS,
CanPlay("'application/x-mpegurl; codecs=\"avc3.42E01E, mp4a.40.29\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/x-mpegurl; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/x-mpegurl; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/x-mpegurl; codecs=\"hev1.1.6.L93.B0,mp4a.40.5\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/x-mpegurl; codecs=\"hvc1.1.6.L93.B0,mp4a.40.5\"'"));
+
EXPECT_EQ(maybeCanPlayHLS,
CanPlay("'application/x-mpegurl; codecs=\"avc1, mp4a.40.2\"'"));
EXPECT_EQ(maybeCanPlayHLS,
@@ -1017,6 +1126,17 @@ IN_PROC_BROWSER_TEST_F(MediaCanPlayTypeTest, CodecSupportTest_HLS) {
CanPlay("'application/vnd.apple.mpegurl; "
"codecs=\"avc3.42E01E, mp4a.40\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/vnd.apple.mpegurl; codecs=\"hev1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/vnd.apple.mpegurl; codecs=\"hvc1.1.6.L93.B0\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/vnd.apple.mpegurl; "
+ "codecs=\"hev1.1.6.L93.B0,mp4a.40.5\"'"));
+ EXPECT_EQ(kNot,
+ CanPlay("'application/vnd.apple.mpegurl; "
+ "codecs=\"hvc1.1.6.L93.B0,mp4a.40.5\"'"));
+
TestMPEGUnacceptableCombinations("application/vnd.apple.mpegurl");
}
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index d4670b0..299fbc5 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -1522,6 +1522,11 @@
'renderer/external_popup_menu_browsertest.cc',
],
}],
+ ['chromecast==1', {
+ 'defines': [
+ 'ENABLE_HEVC_DEMUXING',
+ ],
+ }],
['use_aura==1 or toolkit_views==1', {
'dependencies': [
'../ui/events/events.gyp:events_test_support',
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index b2e9e1e..83a97f6 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -7,6 +7,7 @@ import("//build/config/crypto.gni")
import("//build/config/features.gni")
import("//build/config/ui.gni")
import("//build/module_args/v8.gni")
+import("//media/media_options.gni")
import("//testing/test.gni")
import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
@@ -310,6 +311,10 @@ if (!is_mac) {
defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+ if (proprietary_codecs && enable_hevc_demuxing) {
+ defines += [ "ENABLE_HEVC_DEMUXING" ]
+ }
+
configs += [
"//build/config:precompiled_headers",
"//build/config/compiler:no_size_t_to_int_warning",
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