diff options
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(¶llelismType) && + 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, ¶m_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 Binary files differnew file mode 100644 index 0000000..1b85aba --- /dev/null +++ b/media/test/data/bear-hevc-frag.mp4 diff --git a/media/test/data/bear.hevc b/media/test/data/bear.hevc Binary files differnew file mode 100644 index 0000000..f2a0545 --- /dev/null +++ b/media/test/data/bear.hevc |