blob: 2d89808c1b0fbca9bbb6dd196d5fb134c9f26194 [file] [log] [blame]
// Copyright 2012 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/base/mime_util_internal.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "media/base/media.h"
#include "media/base/media_client.h"
#include "media/base/media_switches.h"
#include "media/base/supported_types.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/media_buildflags.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
// TODO(dalecurtis): This include is not allowed by media/base since
// media/base/android is technically a different component. We should move
// mime_util*.{cc,h} out of media/base to fix this.
#include "media/base/android/media_codec_util.h" // nogncheck
#endif
namespace media {
namespace internal {
// A map from codec string to MimeUtil::Codec.
using StringToCodecMap = base::flat_map<std::string, MimeUtil::Codec>;
// Wrapped to avoid static initializer startup cost.
const StringToCodecMap& GetStringToCodecMap() {
static const base::NoDestructor<StringToCodecMap> kStringToCodecMap({
// We only allow this for WAV so it isn't ambiguous.
{"1", MimeUtil::PCM},
// avc1/avc3.XXXXXX may be unambiguous; handled by
// ParseAVCCodecId(). hev1/hvc1.XXXXXX may be unambiguous;
// handled by ParseHEVCCodecID(). vp9, vp9.0,
// vp09.xx.xx.xx.xx.xx.xx.xx may be unambiguous; handled by
// ParseVp9CodecID().
{"mp3", MimeUtil::MP3},
// Following is the list of RFC 6381 compliant audio codec
// strings:
// mp4a.66 - MPEG-2 AAC MAIN
// mp4a.67 - MPEG-2 AAC LC
// mp4a.68 - MPEG-2 AAC SSR
// mp4a.69 - MPEG-2 extension to MPEG-1 (MP3)
// mp4a.6B - MPEG-1 audio (MP3)
// mp4a.40.2 - MPEG-4 AAC LC
// mp4a.40.02 - MPEG-4 AAC LC (leading 0 in aud-oti for
// compatibility)
// mp4a.40.5 - MPEG-4 HE-AAC v1 (AAC LC + SBR)
// mp4a.40.05 - MPEG-4 HE-AAC v1 (AAC LC + SBR) (leading 0
// in aud-oti for compatibility)
// mp4a.40.29 - MPEG-4 HE-AAC v2 (AAC LC + SBR + PS)
{"mp4a.66", MimeUtil::MPEG2_AAC},
{"mp4a.67", MimeUtil::MPEG2_AAC},
{"mp4a.68", MimeUtil::MPEG2_AAC},
{"mp4a.69", MimeUtil::MP3},
{"mp4a.6B", MimeUtil::MP3},
{"mp4a.40.2", MimeUtil::MPEG4_AAC},
{"mp4a.40.02", MimeUtil::MPEG4_AAC},
{"mp4a.40.5", MimeUtil::MPEG4_AAC},
{"mp4a.40.05", MimeUtil::MPEG4_AAC},
{"mp4a.40.29", MimeUtil::MPEG4_AAC},
{"mp4a.40.42", MimeUtil::MPEG4_XHE_AAC},
// TODO(servolk): Strictly speaking only mp4a.A5 and mp4a.A6
// codec ids are valid according to RFC 6381 section 3.3, 3.4.
// Lower-case oti (mp4a.a5 and mp4a.a6) should be rejected. But
// we used to allow those in older versions of Chromecast
// firmware and some apps (notably MPL) depend on those codec
// types being supported, so they should be allowed for now
// (crbug.com/564960).
{"ac-3", MimeUtil::AC3},
{"mp4a.a5", MimeUtil::AC3},
{"mp4a.A5", MimeUtil::AC3},
{"ec-3", MimeUtil::EAC3},
{"mp4a.a6", MimeUtil::EAC3},
{"mp4a.A6", MimeUtil::EAC3},
{"vorbis", MimeUtil::VORBIS},
{"opus", MimeUtil::OPUS},
{"flac", MimeUtil::FLAC},
{"vp8", MimeUtil::VP8},
{"vp8.0", MimeUtil::VP8},
{"theora", MimeUtil::THEORA},
{"dtsc", MimeUtil::DTS},
{"mp4a.a9", MimeUtil::DTS},
{"mp4a.A9", MimeUtil::DTS},
{"dtsx", MimeUtil::DTSXP2},
{"mp4a.b2", MimeUtil::DTSXP2},
{"mp4a.B2", MimeUtil::DTSXP2},
});
return *kStringToCodecMap;
}
static bool ParseVp9CodecID(const std::string& mime_type_lower_case,
const std::string& codec_id,
VideoCodecProfile* out_profile,
uint8_t* out_level,
VideoColorSpace* out_color_space) {
if (ParseNewStyleVp9CodecID(codec_id, out_profile, out_level,
out_color_space)) {
// New style (e.g. vp09.00.10.08) is accepted with any mime type (including
// empty mime type).
return true;
}
// Legacy style (e.g. "vp9") is ambiguous about codec profile, and is only
// valid with video/webm for legacy reasons.
if (mime_type_lower_case == "video/webm")
return ParseLegacyVp9CodecID(codec_id, out_profile, out_level);
return false;
}
static bool IsValidH264Level(uint8_t level_idc) {
// Valid levels taken from Table A-1 in ISO/IEC 14496-10.
// Level_idc represents the standard level represented as decimal number
// multiplied by ten, e.g. level_idc==32 corresponds to level==3.2
return ((level_idc >= 10 && level_idc <= 13) ||
(level_idc >= 20 && level_idc <= 22) ||
(level_idc >= 30 && level_idc <= 32) ||
(level_idc >= 40 && level_idc <= 42) ||
(level_idc >= 50 && level_idc <= 52) ||
(level_idc >= 60 && level_idc <= 62));
}
// Make a default ParsedCodecResult. Values should indicate "unspecified"
// where possible. Color space is an exception where we choose a default value
// because most codec strings will not describe a color space.
static MimeUtil::ParsedCodecResult MakeDefaultParsedCodecResult() {
return {
MimeUtil::INVALID_CODEC, false, VIDEO_CODEC_PROFILE_UNKNOWN, 0,
// We choose 709 as default color space elsewhere, so defaulting to 709
// here as well. See here for context: https://crrev.com/1221903003/
VideoColorSpace::REC709()};
}
MimeUtil::MimeUtil() {
#if BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
platform_info_.has_platform_dv_decoder =
MediaCodecUtil::IsDolbyVisionDecoderAvailable();
#endif
platform_info_.has_platform_vp8_decoder =
MediaCodecUtil::IsVp8DecoderAvailable();
platform_info_.has_platform_vp9_decoder =
MediaCodecUtil::IsVp9DecoderAvailable();
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
platform_info_.has_platform_hevc_decoder =
MediaCodecUtil::IsHEVCDecoderAvailable();
#endif
platform_info_.has_platform_opus_decoder =
MediaCodecUtil::IsOpusDecoderAvailable();
#endif // BUILDFLAG(IS_ANDROID)
InitializeMimeTypeMaps();
}
MimeUtil::~MimeUtil() = default;
AudioCodec MimeUtilToAudioCodec(MimeUtil::Codec codec) {
switch (codec) {
case MimeUtil::PCM:
return AudioCodec::kPCM;
case MimeUtil::MP3:
return AudioCodec::kMP3;
case MimeUtil::AC3:
return AudioCodec::kAC3;
case MimeUtil::EAC3:
return AudioCodec::kEAC3;
case MimeUtil::MPEG2_AAC:
case MimeUtil::MPEG4_AAC:
case MimeUtil::MPEG4_XHE_AAC:
return AudioCodec::kAAC;
case MimeUtil::MPEG_H_AUDIO:
return AudioCodec::kMpegHAudio;
case MimeUtil::VORBIS:
return AudioCodec::kVorbis;
case MimeUtil::OPUS:
return AudioCodec::kOpus;
case MimeUtil::FLAC:
return AudioCodec::kFLAC;
case MimeUtil::DTS:
return AudioCodec::kDTS;
case MimeUtil::DTSXP2:
return AudioCodec::kDTSXP2;
default:
break;
}
return AudioCodec::kUnknown;
}
VideoCodec MimeUtilToVideoCodec(MimeUtil::Codec codec) {
switch (codec) {
case MimeUtil::AV1:
return VideoCodec::kAV1;
case MimeUtil::H264:
return VideoCodec::kH264;
case MimeUtil::HEVC:
return VideoCodec::kHEVC;
case MimeUtil::VP8:
return VideoCodec::kVP8;
case MimeUtil::VP9:
return VideoCodec::kVP9;
case MimeUtil::THEORA:
return VideoCodec::kTheora;
case MimeUtil::DOLBY_VISION:
return VideoCodec::kDolbyVision;
default:
break;
}
return VideoCodec::kUnknown;
}
SupportsType MimeUtil::AreSupportedCodecs(
const std::vector<ParsedCodecResult>& parsed_codecs,
const std::string& mime_type_lower_case,
bool is_encrypted) const {
DCHECK(!parsed_codecs.empty());
DCHECK_EQ(base::ToLowerASCII(mime_type_lower_case), mime_type_lower_case);
SupportsType combined_result = SupportsType::kSupported;
for (const auto& parsed_codec : parsed_codecs) {
// Make conservative guesses to resolve ambiguity before checking platform
// support. Historically we allowed some ambiguity in H264 and VP9 codec
// strings, so we must continue to allow going forward. DO NOT ADD NEW
// SUPPORT FOR MORE AMBIGUOUS STRINGS.
VideoCodecProfile video_profile = parsed_codec.video_profile;
uint8_t video_level = parsed_codec.video_level;
if (parsed_codec.is_ambiguous) {
switch (parsed_codec.codec) {
case MimeUtil::H264:
if (video_profile == VIDEO_CODEC_PROFILE_UNKNOWN)
video_profile = H264PROFILE_BASELINE;
if (!IsValidH264Level(video_level))
video_level = 10;
break;
case MimeUtil::VP9:
if (video_profile == VIDEO_CODEC_PROFILE_UNKNOWN)
video_profile = VP9PROFILE_PROFILE0;
if (video_level == 0)
video_level = 10;
break;
case MimeUtil::MPEG4_AAC:
// Nothing to do for AAC; no notion of profile / level to guess.
break;
default:
NOTREACHED()
<< "Only VP9, H264, and AAC codec strings can be ambiguous.";
}
}
// Check platform support.
SupportsType result = IsCodecSupported(
mime_type_lower_case, parsed_codec.codec, video_profile, video_level,
parsed_codec.video_color_space, is_encrypted);
if (result == SupportsType::kNotSupported) {
DVLOG(2) << __func__ << ": Codec " << parsed_codec.codec
<< " not supported by platform.";
return SupportsType::kNotSupported;
}
// If any codec is "kMaybeSupported", return Maybe for the combined result.
if (result == SupportsType::kMaybeSupported ||
// Downgrade to kMaybeSupported if we had to guess the meaning of one of
// the codec strings. Do not downgrade for VP9 because we historically
// returned "Probably" for the old "vp9" string and cannot change to
// returning "Maybe" as this will break sites.
(result == SupportsType::kSupported && parsed_codec.is_ambiguous &&
parsed_codec.codec != MimeUtil::VP9)) {
combined_result = SupportsType::kMaybeSupported;
}
}
return combined_result;
}
void MimeUtil::InitializeMimeTypeMaps() {
AddSupportedMediaFormats();
}
// Each call to AddContainerWithCodecs() contains a media type
// (https://en.wikipedia.org/wiki/Media_type) and corresponding media codec(s)
// supported by these types/containers.
void MimeUtil::AddSupportedMediaFormats() {
const CodecSet wav_codecs{PCM};
const CodecSet ogg_audio_codecs{FLAC, OPUS, VORBIS};
CodecSet ogg_video_codecs{VP8};
#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
ogg_video_codecs.emplace(THEORA);
#endif // BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
CodecSet ogg_codecs(ogg_audio_codecs);
ogg_codecs.insert(ogg_video_codecs.begin(), ogg_video_codecs.end());
const CodecSet webm_audio_codecs{OPUS, VORBIS};
CodecSet webm_video_codecs{VP8, VP9};
#if BUILDFLAG(ENABLE_AV1_DECODER)
webm_video_codecs.emplace(AV1);
#endif
CodecSet webm_codecs(webm_audio_codecs);
webm_codecs.insert(webm_video_codecs.begin(), webm_video_codecs.end());
const CodecSet mp3_codecs{MP3};
CodecSet mp4_audio_codecs{FLAC, MP3, OPUS};
// Only VP9 with valid codec string vp09.xx.xx.xx.xx.xx.xx.xx is supported.
// See ParseVp9CodecID for details.
CodecSet mp4_video_codecs;
mp4_video_codecs.emplace(VP9);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
const CodecSet aac{MPEG2_AAC, MPEG4_AAC, MPEG4_XHE_AAC};
mp4_audio_codecs.insert(aac.begin(), aac.end());
CodecSet avc_and_aac(aac);
avc_and_aac.emplace(H264);
#if BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
mp4_audio_codecs.emplace(AC3);
mp4_audio_codecs.emplace(EAC3);
#endif // BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
#if BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO)
mp4_audio_codecs.emplace(MPEG_H_AUDIO);
#endif // BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO)
mp4_video_codecs.emplace(H264);
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
mp4_video_codecs.emplace(HEVC);
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
mp4_video_codecs.emplace(DOLBY_VISION);
#endif // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
#if BUILDFLAG(ENABLE_AV1_DECODER)
mp4_video_codecs.emplace(AV1);
#endif
#if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
mp4_audio_codecs.emplace(DTS);
mp4_audio_codecs.emplace(DTSXP2);
#endif // BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
CodecSet mp4_codecs(mp4_audio_codecs);
mp4_codecs.insert(mp4_video_codecs.begin(), mp4_video_codecs.end());
const CodecSet implicit_codec;
AddContainerWithCodecs("audio/wav", wav_codecs);
AddContainerWithCodecs("audio/x-wav", wav_codecs);
AddContainerWithCodecs("audio/webm", webm_audio_codecs);
DCHECK(!webm_video_codecs.empty());
AddContainerWithCodecs("video/webm", webm_codecs);
AddContainerWithCodecs("audio/ogg", ogg_audio_codecs);
// video/ogg is only supported if an appropriate video codec is supported.
// Note: This assumes such codecs cannot be later excluded.
if (!ogg_video_codecs.empty())
AddContainerWithCodecs("video/ogg", ogg_codecs);
// TODO(ddorwin): Should the application type support Opus?
AddContainerWithCodecs("application/ogg", ogg_codecs);
AddContainerWithCodecs("audio/flac", implicit_codec);
AddContainerWithCodecs("audio/mpeg", mp3_codecs); // Allow "mp3".
AddContainerWithCodecs("audio/mp3", implicit_codec);
AddContainerWithCodecs("audio/x-mp3", implicit_codec);
AddContainerWithCodecs("audio/mp4", mp4_audio_codecs);
DCHECK(!mp4_video_codecs.empty());
AddContainerWithCodecs("video/mp4", mp4_codecs);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
AddContainerWithCodecs("audio/aac", implicit_codec); // AAC / ADTS.
// These strings are supported for backwards compatibility only and thus only
// support the codecs needed for compatibility.
AddContainerWithCodecs("audio/x-m4a", aac);
AddContainerWithCodecs("video/x-m4v", avc_and_aac);
CodecSet video_3gpp_codecs(aac);
video_3gpp_codecs.emplace(H264);
AddContainerWithCodecs("video/3gpp", video_3gpp_codecs);
#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER)
CodecSet mp2t_codecs{H264, MPEG2_AAC, MPEG4_AAC, MP3};
AddContainerWithCodecs("video/mp2t", mp2t_codecs);
#endif // BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER)
#if BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(kCanPlayHls)) {
// HTTP Live Streaming (HLS).
CodecSet hls_codecs{H264,
// TODO(ddorwin): Is any MP3 codec string variant
// included in real queries?
MP3,
// Android HLS only supports MPEG4_AAC (missing demuxer
// support for MPEG2_AAC)
MPEG4_AAC};
AddContainerWithCodecs("application/x-mpegurl", hls_codecs);
AddContainerWithCodecs("application/vnd.apple.mpegurl", hls_codecs);
AddContainerWithCodecs("audio/mpegurl", hls_codecs);
// Not documented by Apple, but unfortunately used extensively by Apple and
// others for both audio-only and audio+video playlists. See
// https://crbug.com/675552 for details and examples.
AddContainerWithCodecs("audio/x-mpegurl", hls_codecs);
}
#endif // BUILDFLAG(IS_ANDROID)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
}
void MimeUtil::AddContainerWithCodecs(const std::string& mime_type,
const CodecSet& codecs) {
media_format_map_[mime_type] = codecs;
}
bool MimeUtil::IsSupportedMediaMimeType(const std::string& mime_type) const {
return media_format_map_.contains(base::ToLowerASCII(mime_type));
}
void MimeUtil::SplitCodecs(const std::string& codecs,
std::vector<std::string>* codecs_out) const {
*codecs_out =
base::SplitString(base::TrimString(codecs, "\"", base::TRIM_ALL), ",",
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
// Convert empty or all-whitespace input to 0 results.
if (codecs_out->size() == 1 && (*codecs_out)[0].empty())
codecs_out->clear();
}
void MimeUtil::StripCodecs(std::vector<std::string>* codecs) const {
// Strip everything past the first '.'
for (auto it = codecs->begin(); it != codecs->end(); ++it) {
size_t found = it->find_first_of('.');
if (found != std::string::npos)
it->resize(found);
}
}
bool MimeUtil::ParseVideoCodecString(const std::string& mime_type,
const std::string& codec_id,
bool* out_is_ambiguous,
VideoCodec* out_codec,
VideoCodecProfile* out_profile,
uint8_t* out_level,
VideoColorSpace* out_color_space) const {
DCHECK(out_is_ambiguous);
DCHECK(out_codec);
DCHECK(out_profile);
DCHECK(out_level);
DCHECK(out_color_space);
// Internal parsing API expects a vector of codecs.
std::vector<ParsedCodecResult> parsed_results;
std::vector<std::string> codec_strings;
if (!codec_id.empty())
codec_strings.push_back(codec_id);
if (!ParseCodecStrings(base::ToLowerASCII(mime_type), codec_strings,
&parsed_results)) {
DVLOG(3) << __func__ << " Failed to parse mime/codec pair: "
<< (mime_type.empty() ? "<empty mime>" : mime_type) << "; "
<< codec_id;
return false;
}
CHECK_EQ(1U, parsed_results.size());
*out_is_ambiguous = parsed_results[0].is_ambiguous;
*out_codec = MimeUtilToVideoCodec(parsed_results[0].codec);
*out_profile = parsed_results[0].video_profile;
*out_level = parsed_results[0].video_level;
*out_color_space = parsed_results[0].video_color_space;
if (*out_codec == VideoCodec::kUnknown) {
DVLOG(3) << __func__ << " Codec string " << codec_id
<< " is not a VIDEO codec.";
return false;
}
return true;
}
bool MimeUtil::ParseAudioCodecString(const std::string& mime_type,
const std::string& codec_id,
bool* out_is_ambiguous,
AudioCodec* out_codec) const {
DCHECK(out_is_ambiguous);
DCHECK(out_codec);
// Internal parsing API expects a vector of codecs.
std::vector<ParsedCodecResult> parsed_results;
std::vector<std::string> codec_strings;
if (!codec_id.empty())
codec_strings.push_back(codec_id);
if (!ParseCodecStrings(base::ToLowerASCII(mime_type), codec_strings,
&parsed_results)) {
DVLOG(3) << __func__ << " Failed to parse mime/codec pair:"
<< (mime_type.empty() ? "<empty mime>" : mime_type) << "; "
<< codec_id;
return false;
}
CHECK_EQ(1U, parsed_results.size());
*out_is_ambiguous = parsed_results[0].is_ambiguous;
*out_codec = MimeUtilToAudioCodec(parsed_results[0].codec);
if (*out_codec == AudioCodec::kUnknown) {
DVLOG(3) << __func__ << " Codec string " << codec_id
<< " is not an AUDIO codec.";
return false;
}
return true;
}
SupportsType MimeUtil::IsSupportedMediaFormat(
const std::string& mime_type,
const std::vector<std::string>& codecs,
bool is_encrypted) const {
const std::string mime_type_lower_case = base::ToLowerASCII(mime_type);
std::vector<ParsedCodecResult> parsed_results;
if (!ParseCodecStrings(mime_type_lower_case, codecs, &parsed_results)) {
DVLOG(3) << __func__ << " Media format unsupported; codec parsing failed "
<< mime_type << " " << base::JoinString(codecs, ",");
return SupportsType::kNotSupported;
}
if (parsed_results.empty()) {
NOTREACHED() << __func__ << " Successful parsing should output results.";
return SupportsType::kNotSupported;
}
// We get here if the mime type expects to get a codecs parameter
// but none was provided and no default codec was implied. In this case
// the best we can do is say "maybe" because we don't have enough
// information.
if (codecs.empty() && parsed_results.size() == 1 &&
parsed_results[0].codec == INVALID_CODEC) {
DCHECK(parsed_results[0].is_ambiguous);
return SupportsType::kMaybeSupported;
}
return AreSupportedCodecs(parsed_results, mime_type_lower_case, is_encrypted);
}
// static
bool MimeUtil::IsCodecSupportedOnAndroid(
Codec codec,
const std::string& mime_type_lower_case,
bool is_encrypted,
VideoCodecProfile video_profile,
const PlatformInfo& platform_info) {
DVLOG(3) << __func__;
DCHECK_NE(mime_type_lower_case, "");
// NOTE: We do not account for Media Source Extensions (MSE) within these
// checks since it has its own isTypeSupported() which will handle platform
// specific codec rejections. See http://crbug.com/587303.
switch (codec) {
// ----------------------------------------------------------------------
// The following codecs are never supported.
// ----------------------------------------------------------------------
case INVALID_CODEC:
case THEORA:
return false;
// ----------------------------------------------------------------------
// The remaining codecs may be supported depending on platform abilities.
// ----------------------------------------------------------------------
case AV1:
return BUILDFLAG(ENABLE_AV1_DECODER);
case MPEG2_AAC:
// MPEG2_AAC cannot be used in HLS (mpegurl suffix), but this is enforced
// in the parsing step by excluding MPEG2_AAC from the list of
// valid codecs to be used with HLS mime types.
DCHECK(!base::EndsWith(mime_type_lower_case, "mpegurl",
base::CompareCase::SENSITIVE));
[[fallthrough]];
case PCM:
case MP3:
case MPEG4_AAC:
case FLAC:
case VORBIS:
// These codecs are always supported; via a platform decoder (when used
// with MSE/EME) or with a software decoder (the unified pipeline).
return true;
case MPEG4_XHE_AAC:
return true;
case MPEG_H_AUDIO:
return false;
case OPUS:
// If clear, the unified pipeline can always decode Opus in software.
if (!is_encrypted)
return true;
// Otherwise, platform support is required.
if (!platform_info.has_platform_opus_decoder) {
DVLOG(3) << "Platform does not support opus";
return false;
}
return true;
case H264:
return true;
case HEVC:
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
return platform_info.has_platform_hevc_decoder;
#else
return false;
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
case VP8:
// If clear, the unified pipeline can always decode VP8 in software.
return is_encrypted ? platform_info.has_platform_vp8_decoder : true;
case VP9: {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kReportVp9AsAnUnsupportedMimeType)) {
return false;
}
// If clear, the unified pipeline can always decode VP9.0,1 in software.
// If we don't know the profile, then support is ambiguous, but default to
// true for historical reasons.
if (!is_encrypted && (video_profile == VP9PROFILE_PROFILE0 ||
video_profile == VP9PROFILE_PROFILE1 ||
video_profile == VIDEO_CODEC_PROFILE_UNKNOWN)) {
return true;
}
if (!platform_info.has_platform_vp9_decoder)
return false;
return true;
}
case DOLBY_VISION:
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
return platform_info.has_platform_dv_decoder;
#else
return false;
#endif
case AC3:
case EAC3:
#if BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
return true;
#else
return false;
#endif
case DTS:
case DTSXP2:
#if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
return true;
#else
return false;
#endif
}
return false;
}
bool MimeUtil::ParseCodecStrings(
const std::string& mime_type_lower_case,
const std::vector<std::string>& codecs,
std::vector<ParsedCodecResult>* out_results) const {
DCHECK(out_results);
// Nothing to parse.
if (mime_type_lower_case.empty() && codecs.empty())
return false;
// When mime type is provided, it may imply a codec or only be valid with
// certain codecs.
const CodecSet* valid_codecs_for_mime;
if (!mime_type_lower_case.empty()) {
// Reject unrecognized mime types.
auto it_media_format_map = media_format_map_.find(mime_type_lower_case);
if (it_media_format_map == media_format_map_.end()) {
DVLOG(3) << __func__
<< " Unrecognized mime type: " << mime_type_lower_case;
return false;
}
valid_codecs_for_mime = &it_media_format_map->second;
if (valid_codecs_for_mime->empty()) {
// We get here if the mimetype does not expect a codecs parameter.
if (!codecs.empty()) {
DVLOG(3) << __func__
<< " Codecs unexpected for mime type:" << mime_type_lower_case;
return false;
}
// Determine implied codec for mime type.
ParsedCodecResult implied_result = MakeDefaultParsedCodecResult();
if (!GetDefaultCodec(mime_type_lower_case, &implied_result.codec)) {
NOTREACHED() << " Mime types must offer a default codec if no explicit "
"codecs are expected";
return false;
}
out_results->push_back(implied_result);
return true;
}
if (codecs.empty()) {
// We get here if the mimetype expects to get a codecs parameter,
// but didn't get one. If |mime_type_lower_case| does not have a default
// codec, the string is considered ambiguous.
ParsedCodecResult implied_result = MakeDefaultParsedCodecResult();
implied_result.is_ambiguous =
!GetDefaultCodec(mime_type_lower_case, &implied_result.codec);
out_results->push_back(implied_result);
return true;
}
}
// All empty cases handled above.
DCHECK(!codecs.empty());
for (std::string codec_string : codecs) {
ParsedCodecResult result;
#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER)
if (mime_type_lower_case == "video/mp2t")
codec_string = TranslateLegacyAvc1CodecIds(codec_string);
#endif
if (!ParseCodecHelper(mime_type_lower_case, codec_string, &result)) {
DVLOG(3) << __func__ << " Failed to parse mime/codec pair: "
<< (mime_type_lower_case.empty() ? "<empty mime>"
: mime_type_lower_case)
<< "; " << codec_string;
return false;
}
DCHECK_NE(INVALID_CODEC, result.codec);
// If mime type given, fail if mime + codec is not a valid combination.
if (!mime_type_lower_case.empty() &&
!valid_codecs_for_mime->contains(result.codec)) {
DVLOG(3) << __func__
<< " Incompatible mime/codec pair: " << mime_type_lower_case
<< "; " << codec_string;
return false;
}
out_results->push_back(result);
}
return true;
}
bool MimeUtil::ParseCodecHelper(const std::string& mime_type_lower_case,
const std::string& codec_id,
ParsedCodecResult* out_result) const {
DCHECK_EQ(base::ToLowerASCII(mime_type_lower_case), mime_type_lower_case);
DCHECK(out_result);
*out_result = MakeDefaultParsedCodecResult();
// Simple codecs can be found in the codec map.
auto itr = GetStringToCodecMap().find(codec_id);
if (itr != GetStringToCodecMap().end()) {
out_result->codec = itr->second;
// Even "simple" video codecs should have an associated profile.
if (MimeUtilToVideoCodec(out_result->codec) != VideoCodec::kUnknown) {
switch (out_result->codec) {
case Codec::VP8:
out_result->video_profile = VP8PROFILE_ANY;
break;
case Codec::THEORA:
out_result->video_profile = THEORAPROFILE_ANY;
break;
default:
NOTREACHED();
}
}
return true;
}
// Check codec string against short list of allowed ambiguous codecs.
// Hard-coded to discourage expansion. DO NOT ADD TO THIS LIST. DO NOT
// INCREASE PLACES WHERE |ambiguous_codec_string| = true.
// NOTE: avc1/avc3.XXXXXX may be ambiguous handled after ParseAVCCodecId().
if (codec_id == "avc1" || codec_id == "avc3") {
out_result->codec = MimeUtil::H264;
out_result->is_ambiguous = true;
return true;
} else if (codec_id == "mp4a.40") {
out_result->codec = MimeUtil::MPEG4_AAC;
out_result->is_ambiguous = true;
return true;
}
// If |codec_id| is not in |kStringToCodecMap|, then we assume that it is
// either VP9, H.264 or HEVC/H.265 codec ID because currently those are the
// only ones that are not added to the |kStringToCodecMap| and require
// parsing.
VideoCodecProfile* out_profile = &out_result->video_profile;
uint8_t* out_level = &out_result->video_level;
VideoColorSpace* out_color_space = &out_result->video_color_space;
if (ParseVp9CodecID(mime_type_lower_case, codec_id, out_profile, out_level,
out_color_space)) {
out_result->codec = MimeUtil::VP9;
// Original VP9 codec string did not describe the profile.
if (out_result->video_profile == VIDEO_CODEC_PROFILE_UNKNOWN) {
// New VP9 string should never be ambiguous.
DCHECK(!base::StartsWith(codec_id, "vp09", base::CompareCase::SENSITIVE));
out_result->is_ambiguous = true;
}
return true;
}
#if BUILDFLAG(ENABLE_AV1_DECODER)
if (ParseAv1CodecId(codec_id, out_profile, out_level, out_color_space)) {
out_result->codec = MimeUtil::AV1;
return true;
}
#endif
if (ParseAVCCodecId(codec_id, out_profile, out_level)) {
out_result->codec = MimeUtil::H264;
// Allowed string ambiguity since 2014. DO NOT ADD NEW CASES FOR AMBIGUITY.
out_result->is_ambiguous = !IsValidH264Level(*out_level);
return true;
}
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
if (ParseHEVCCodecId(codec_id, out_profile, out_level)) {
out_result->codec = MimeUtil::HEVC;
return true;
}
#endif
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
if (ParseDolbyVisionCodecId(codec_id, out_profile, out_level)) {
out_result->codec = MimeUtil::DOLBY_VISION;
return true;
}
#endif
#if BUILDFLAG(ENABLE_PLATFORM_MPEG_H_AUDIO)
if (base::StartsWith(codec_id, "mhm1.", base::CompareCase::SENSITIVE) ||
base::StartsWith(codec_id, "mha1.", base::CompareCase::SENSITIVE)) {
out_result->codec = MimeUtil::MPEG_H_AUDIO;
return true;
}
#endif
DVLOG(2) << __func__ << ": Unrecognized codec id \"" << codec_id << "\"";
return false;
}
SupportsType MimeUtil::IsCodecSupported(const std::string& mime_type_lower_case,
Codec codec,
VideoCodecProfile video_profile,
uint8_t video_level,
const VideoColorSpace& color_space,
bool is_encrypted) const {
DVLOG(3) << __func__;
DCHECK_EQ(base::ToLowerASCII(mime_type_lower_case), mime_type_lower_case);
DCHECK_NE(codec, INVALID_CODEC);
VideoCodec video_codec = MimeUtilToVideoCodec(codec);
if (video_codec != VideoCodec::kUnknown &&
// Theora and VP8 do not have profiles/levels.
video_codec != VideoCodec::kTheora && video_codec != VideoCodec::kVP8 &&
// TODO(dalecurtis): AV1 has levels, but they aren't supported yet;
// http://crbug.com/784993
video_codec != VideoCodec::kAV1) {
DCHECK_NE(video_profile, VIDEO_CODEC_PROFILE_UNKNOWN);
DCHECK_GT(video_level, 0);
}
// Check for cases of ambiguous platform support.
// TODO(chcunningham): DELETE THIS. Platform should know its capabilities.
// Answer should come from MediaClient.
bool ambiguous_platform_support = false;
if (codec == MimeUtil::H264) {
switch (video_profile) {
// Always supported
case H264PROFILE_BASELINE:
case H264PROFILE_MAIN:
case H264PROFILE_HIGH:
break;
// HIGH10PROFILE is supported through fallback to the ffmpeg decoder
// which is not available on Android, or if FFMPEG is not used.
#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
case H264PROFILE_HIGH10PROFILE:
// FFmpeg is not generally used for encrypted videos, so we do not
// know whether 10-bit is supported.
ambiguous_platform_support = is_encrypted;
break;
#endif
default:
ambiguous_platform_support = true;
}
}
AudioCodec audio_codec = MimeUtilToAudioCodec(codec);
if (audio_codec != AudioCodec::kUnknown) {
AudioCodecProfile audio_profile = AudioCodecProfile::kUnknown;
if (codec == MPEG4_XHE_AAC)
audio_profile = AudioCodecProfile::kXHE_AAC;
if (!IsSupportedAudioType({audio_codec, audio_profile, false}))
return SupportsType::kNotSupported;
}
if (video_codec != VideoCodec::kUnknown) {
if (!IsSupportedVideoType(
{video_codec, video_profile, video_level, color_space})) {
return SupportsType::kNotSupported;
}
}
#if BUILDFLAG(IS_ANDROID)
// TODO(chcunningham): Delete this. Android platform support should be
// handled by (android specific) media::IsSupportedVideoType() above.
if (!IsCodecSupportedOnAndroid(codec, mime_type_lower_case, is_encrypted,
video_profile, platform_info_)) {
return SupportsType::kNotSupported;
}
#endif
return ambiguous_platform_support ? SupportsType::kMaybeSupported
: SupportsType::kSupported;
}
bool MimeUtil::GetDefaultCodec(const std::string& mime_type,
Codec* default_codec) const {
// Codecs below are unambiguously implied by the mime type string. DO NOT add
// default codecs for ambiguous mime types.
if (mime_type == "audio/mpeg" || mime_type == "audio/mp3" ||
mime_type == "audio/x-mp3") {
*default_codec = MimeUtil::MP3;
return true;
}
if (mime_type == "audio/aac") {
*default_codec = MimeUtil::MPEG4_AAC;
return true;
}
if (mime_type == "audio/flac") {
*default_codec = MimeUtil::FLAC;
return true;
}
return false;
}
} // namespace internal
} // namespace media