| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #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 { |
| |
| // Vorbis is not supported. See http://crbug.com/710924 for details. |
| |
| 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 // BUILDFLAG(USE_PROPRIETARY_CODECS) |
| }; |
| |
| 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 // BUILDFLAG(USE_PROPRIETARY_CODECS) |
| }; |
| |
| // Is an audio sink connected which supports the given codec? |
| static bool CanPassthrough(media::AudioCodec codec) { |
| return (media::AudioManagerAndroid::GetHdmiOutputEncodingFormats() & |
| media::ConvertAudioCodecToBitstreamFormat(codec)) != 0; |
| } |
| |
| // Determine the capabilities for `key_system` based on `webm_supported` and |
| // `mp4_supported`, and call `cdm_capability_cb` with the resulting capability. |
| // This class assumes that MediaDrm does support `key_system`. |
| 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); |
| } |
| } |
| } |
| |
| // |audio_codecs| and |video_codecs| should not have multiple entries with |
| // the same codec, so if the loop above added them, no need to test the same |
| // codec again. |
| if (mp4_supported) { |
| // It is possible that a device that is not able to decode the audio stream |
| // is connected to an audiosink device that can. In this case, CanDecode() |
| // returns false but CanPassthrough() will return true. CanPassthrough() |
| // calls AudioManagerAndroid::GetSinkAudioEncodingFormats() to retrieve a |
| // bitmask representing audio bitstream formats that are supported by the |
| // connected audiosink device. This bitmask is then matched against current |
| // audio stream's codec type. A match indicates that the connected |
| // audiosink device is able to decode the current audio stream and Chromium |
| // should passthrough the audio bitstream instead of trying to decode it. |
| 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()) { |
| // There are currently no hardware secure audio codecs on Android. |
| // If there are no hardware secure video codecs available and the check |
| // is for hardware secure, then this key system is not available. |
| // TODO(b/266240828): Add checking for hardware secure audio codecs |
| // once they exist. |
| 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; |
| } |
| |
| // For VP9 and HEVC, if they are supported, then determine which profiles |
| // are supported. |
| 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)); |
| } |
| |
| // Used to determine if `key_system` is supported, and if it is whether WebM and |
| // MP4 mime types are also supported. Done via a separate process so that |
| // crashes in MediaDrm do not crash the browser. This class destructs itself |
| // when complete. |
| 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; |
| |
| // Creates the remote process and calls it. |
| void CheckKeySystemSupport() { |
| media_drm_service_ = |
| content::ServiceProcessHost::Launch<media::mojom::MediaDrmSupport>( |
| content::ServiceProcessHost::Options() |
| .WithDisplayName("MediaDrmSupport") |
| .Pass()); |
| |
| // As the calls to MediaDrm can crash and take out the utility process, |
| // set up a handler for when this happens. |
| 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))); |
| } |
| |
| // Called when the remote process has determined if `key_system` and WebM/MP4 |
| // are supported. |
| 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) { |
| // `version` is null which means that querying the version failed to set |
| // the security level (if enabled). This means that the device does not |
| // support this robustness, so fail the query. |
| 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; |
| } |
| |
| // If the remote service fails, assume it has crashed and thus `key_system` |
| // is not supported. |
| 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_; |
| }; |
| |
| } // namespace |
| |
| void GetAndroidCdmCapability(const std::string& key_system, |
| CdmInfo::Robustness robustness, |
| media::CdmCapabilityCB cdm_capability_cb) { |
| // Rendering of hardware secure codecs is only supported when AndroidOverlay |
| // is enabled. |
| 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; |
| } |
| } |
| |
| // Calls to MediaDrm.isCryptoSchemeSupported() are known to crash |
| // (see b/308692917), so calling them via a utility process to avoid |
| // crashing the browser if allowed. |
| if (base::FeatureList::IsEnabled(media::kMediaDrmQueryInSeparateProcess)) { |
| // The class CheckCdmCompatibility will manage it's own lifetime |
| // (destruct after calling `cdm_capability_cb`). |
| auto* check_cdm_compatibility = new CheckCdmCompatibility( |
| key_system, is_secure, std::move(cdm_capability_cb)); |
| check_cdm_compatibility->CheckKeySystemSupport(); |
| return; |
| } |
| |
| // Multiple processes are not allowed, so call MediaDrmBridge directly. |
| 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) { |
| // Failed to determine version as `security_level` not supported. Currently |
| // only checking for L1. |
| 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())); |
| } |
| |
| } // namespace content |