#include "content/browser/media/key_system_support_android.h"
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/types/expected.h"
#include "content/public/browser/android/android_overlay_provider.h"
#include "content/public/browser/service_process_host.h"
#include "media/audio/android/audio_manager_android.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/android/media_drm_bridge.h"
#include "media/base/audio_codecs.h"
#include "media/base/cdm_capability.h"
#include "media/base/content_decryption_module.h"
#include "media/base/eme_constants.h"
#include "media/base/encryption_scheme.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/base/video_codecs.h"
#include "media/media_buildflags.h"
#include "media/mojo/mojom/mediadrm_support.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
using media::MediaCodecUtil;
using media::MediaDrmBridge;
namespace content {
namespace {
const media::AudioCodec kWebMAudioCodecsToQuery[] = {
media::AudioCodec::kOpus,
};
const media::AudioCodec kMP4AudioCodecsToQuery[] = {
media::AudioCodec::kFLAC,
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
media::AudioCodec::kAAC,
#if BUILDFLAG(ENABLE_PLATFORM_AC3_EAC3_AUDIO)
media::AudioCodec::kAC3, media::AudioCodec::kEAC3,
#endif
#if BUILDFLAG(ENABLE_PLATFORM_DTS_AUDIO)
media::AudioCodec::kDTS, media::AudioCodec::kDTSXP2,
#endif
#endif
};
const media::VideoCodec kWebMVideoCodecsToQuery[] = {
media::VideoCodec::kVP8,
media::VideoCodec::kVP9,
};
const media::VideoCodec kMP4VideoCodecsToQuery[] = {
media::VideoCodec::kVP9, media::VideoCodec::kAV1,
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
media::VideoCodec::kH264,
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
media::VideoCodec::kHEVC,
#endif
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
media::VideoCodec::kDolbyVision,
#endif
#endif
};
static bool CanPassthrough(media::AudioCodec codec) {
return (media::AudioManagerAndroid::GetHdmiOutputEncodingFormats() &
media::ConvertAudioCodecToBitstreamFormat(codec)) != 0;
}
void DetermineKeySystemSupport(const std::string& key_system,
bool is_secure,
media::CdmCapabilityCB cdm_capability_cb,
bool webm_supported,
bool mp4_supported,
base::Version version) {
const std::vector<media::VideoCodecProfile> kAllProfiles = {};
media::CdmCapability capability;
DVLOG(1) << __func__ << " mp4_supported: " << mp4_supported
<< ", webm_supported: " << webm_supported;
if (webm_supported) {
for (const auto& codec : kWebMAudioCodecsToQuery) {
if (MediaCodecUtil::CanDecode(codec)) {
capability.audio_codecs.insert(codec);
}
}
for (const auto& codec : kWebMVideoCodecsToQuery) {
if (MediaCodecUtil::CanDecode(codec, is_secure)) {
capability.video_codecs.emplace(codec, kAllProfiles);
}
}
}
if (mp4_supported) {
for (const auto& codec : kMP4AudioCodecsToQuery) {
if (!capability.audio_codecs.contains(codec)) {
if (MediaCodecUtil::CanDecode(codec) || CanPassthrough(codec)) {
capability.audio_codecs.insert(codec);
}
}
}
for (const auto& codec : kMP4VideoCodecsToQuery) {
if (!capability.video_codecs.contains(codec)) {
if (MediaCodecUtil::CanDecode(codec, is_secure)) {
capability.video_codecs.emplace(codec, kAllProfiles);
}
}
}
}
if (is_secure && capability.video_codecs.empty()) {
DVLOG(1) << "Key system " << key_system
<< " not supported as no hardware secure video codecs available.";
std::move(cdm_capability_cb)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kNoSupportedVideoCodec));
return;
}
auto vp9 = capability.video_codecs.find(media::VideoCodec::kVP9);
auto hevc = capability.video_codecs.find(media::VideoCodec::kHEVC);
if (vp9 != capability.video_codecs.end() ||
hevc != capability.video_codecs.end()) {
std::vector<media::CodecProfileLevel> profiles;
media::MediaCodecUtil::AddSupportedCodecProfileLevels(&profiles);
for (const auto& profile : profiles) {
switch (profile.codec) {
case media::VideoCodec::kVP9:
if (vp9 != capability.video_codecs.end()) {
vp9->second.supported_profiles.insert(profile.profile);
}
break;
case media::VideoCodec::kHEVC:
if (hevc != capability.video_codecs.end()) {
hevc->second.supported_profiles.insert(profile.profile);
}
break;
default:
break;
}
}
}
capability.encryption_schemes.insert(media::EncryptionScheme::kCenc);
capability.encryption_schemes.insert(media::EncryptionScheme::kCbcs);
capability.session_types.insert(media::CdmSessionType::kTemporary);
if (MediaDrmBridge::IsPersistentLicenseTypeSupported(key_system)) {
capability.session_types.insert(media::CdmSessionType::kPersistentLicense);
}
capability.version = version;
std::move(cdm_capability_cb).Run(std::move(capability));
}
class CheckCdmCompatibility {
public:
CheckCdmCompatibility(const std::string& key_system,
bool is_secure,
media::CdmCapabilityCB cdm_capability_cb)
: key_system_(key_system),
is_secure_(is_secure),
cdm_capability_cb_(std::move(cdm_capability_cb)) {}
~CheckCdmCompatibility() = default;
void CheckKeySystemSupport() {
media_drm_service_ =
content::ServiceProcessHost::Launch<media::mojom::MediaDrmSupport>(
content::ServiceProcessHost::Options()
.WithDisplayName("MediaDrmSupport")
.Pass());
media_drm_service_.set_disconnect_handler(base::BindOnce(
&CheckCdmCompatibility::OnServiceClosed, base::Unretained(this)));
DVLOG(1) << __func__ << " calling IsKeySystemSupported for " << key_system_;
media_drm_service_->IsKeySystemSupported(
key_system_, is_secure_,
base::BindOnce(&CheckCdmCompatibility::VerifyKeySystemSupport,
base::Unretained(this)));
}
void VerifyKeySystemSupport(
media::mojom::MediaDrmSupportResultPtr key_system_support_result) {
if (key_system_support_result.is_null()) {
DVLOG(1) << "Key system " << key_system_ << " not supported.";
std::move(cdm_capability_cb_)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kUnsupportedKeySystem));
delete this;
return;
}
auto version = key_system_support_result->key_system_version;
if (!version) {
std::move(cdm_capability_cb_)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kNoMediaDrmSupport));
delete this;
return;
}
DetermineKeySystemSupport(
key_system_, is_secure_, std::move(cdm_capability_cb_),
key_system_support_result->key_system_supports_video_webm,
key_system_support_result->key_system_supports_video_mp4,
version.value());
delete this;
}
void OnServiceClosed() {
DVLOG(1) << "IsKeySystemSupported failed for " << key_system_;
if (cdm_capability_cb_) {
std::move(cdm_capability_cb_)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kDisconnectionError));
}
delete this;
}
private:
const std::string key_system_;
const bool is_secure_;
media::CdmCapabilityCB cdm_capability_cb_;
mojo::Remote<media::mojom::MediaDrmSupport> media_drm_service_;
};
}
void GetAndroidCdmCapability(const std::string& key_system,
CdmInfo::Robustness robustness,
media::CdmCapabilityCB cdm_capability_cb) {
const bool is_secure = robustness == CdmInfo::Robustness::kHardwareSecure;
if (is_secure) {
bool are_overlay_supported =
content::AndroidOverlayProvider::GetInstance()->AreOverlaysSupported();
bool overlay_fullscreen_video =
base::FeatureList::IsEnabled(media::kOverlayFullscreenVideo);
if (!are_overlay_supported || !overlay_fullscreen_video) {
DVLOG(1) << "Hardware secure codecs not supported for key system"
<< key_system << ".";
std::move(cdm_capability_cb)
.Run(base::unexpected(media::CdmCapabilityQueryStatus::
kHardwareSecureCodecNotSupported));
return;
}
}
if (base::FeatureList::IsEnabled(media::kMediaDrmQueryInSeparateProcess)) {
auto* check_cdm_compatibility = new CheckCdmCompatibility(
key_system, is_secure, std::move(cdm_capability_cb));
check_cdm_compatibility->CheckKeySystemSupport();
return;
}
if (!MediaDrmBridge::IsKeySystemSupported(key_system)) {
std::move(cdm_capability_cb)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kUnsupportedKeySystem));
return;
}
auto security_level = media::MediaDrmBridge::SECURITY_LEVEL_DEFAULT;
if (base::FeatureList::IsEnabled(
media::kUseSecurityLevelWhenCheckingMediaDrmVersion)) {
security_level = is_secure ? media::MediaDrmBridge::SECURITY_LEVEL_1
: media::MediaDrmBridge::SECURITY_LEVEL_3;
}
auto version = MediaDrmBridge::GetVersion(key_system, security_level);
if (!version.has_value() &&
version.error() ==
media::CreateCdmStatus::kAndroidFailedL1SecurityLevel) {
std::move(cdm_capability_cb)
.Run(base::unexpected(
media::CdmCapabilityQueryStatus::kUnsupportedKeySystem));
return;
}
bool webm_supported =
MediaDrmBridge::IsKeySystemSupportedWithType(key_system, "video/webm");
bool mp4_supported =
MediaDrmBridge::IsKeySystemSupportedWithType(key_system, "video/mp4");
DetermineKeySystemSupport(key_system, is_secure, std::move(cdm_capability_cb),
webm_supported, mp4_supported,
version.value_or(base::Version()));
}
}