| // Copyright 2021 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/mojo/services/media_foundation_service.h" |
| |
| #include <map> |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "media/base/audio_codecs.h" |
| #include "media/base/content_decryption_module.h" |
| #include "media/base/encryption_scheme.h" |
| #include "media/base/video_codecs.h" |
| #include "media/cdm/cdm_capability.h" |
| #include "media/cdm/win/media_foundation_cdm_module.h" |
| #include "media/media_buildflags.h" |
| #include "media/mojo/mojom/interface_factory.mojom.h" |
| #include "media/mojo/mojom/key_system_support.mojom.h" |
| #include "media/mojo/services/interface_factory_impl.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| using Microsoft::WRL::ComPtr; |
| |
| namespace media { |
| |
| namespace { |
| |
| // The feature parameters follow Windows API documentation: |
| // https://docs.microsoft.com/en-us/uwp/api/windows.media.protection.protectioncapabilities.istypesupported?view=winrt-19041 |
| // This default feature string is required to query capability related to video |
| // decoder. Since we only care about the codec support rather than the specific |
| // resolution or bitrate capability, we use the following typical values which |
| // should be supported by most devices for a certain video codec. |
| const char kDefaultFeatures[] = |
| "decode-bpp=8,decode-res-x=1920,decode-res-y=1080,decode-bitrate=10000000," |
| "decode-fps=30"; |
| |
| // These three parameters are an extension of the parameters supported |
| // in the above documentation to support the encryption capability query. |
| const char kRobustnessQueryName[] = "encryption-robustness"; |
| const char kEncryptionSchemeQueryName[] = "encryption-type"; |
| const char kEncryptionIvQueryName[] = "encryption-iv-size"; |
| |
| const char kSwSecureRobustness[] = "SW_SECURE_DECODE"; |
| const char kHwSecureRobustness[] = "HW_SECURE_ALL"; |
| |
| // The followings define the supported codecs and encryption schemes that we try |
| // to query. |
| constexpr VideoCodec kAllVideoCodecs[] = { |
| #if BUILDFLAG(USE_PROPRIETARY_CODECS) |
| VideoCodec::kH264, |
| #if BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| VideoCodec::kHEVC, |
| #if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION) |
| VideoCodec::kDolbyVision, |
| #endif // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION) |
| #endif // BUILDFLAG(ENABLE_PLATFORM_HEVC) |
| #endif // BUILDFLAG(USE_PROPRIETARY_CODECS) |
| VideoCodec::kVP9, VideoCodec::kAV1}; |
| |
| constexpr AudioCodec kAllAudioCodecs[] = { |
| #if BUILDFLAG(USE_PROPRIETARY_CODECS) |
| AudioCodec::kAAC, AudioCodec::kEAC3, AudioCodec::kAC3, |
| AudioCodec::kMpegHAudio, |
| #endif |
| AudioCodec::kVorbis, AudioCodec::kFLAC, AudioCodec::kOpus}; |
| |
| constexpr EncryptionScheme kAllEncryptionSchemes[] = {EncryptionScheme::kCenc, |
| EncryptionScheme::kCbcs}; |
| using IsTypeSupportedCB = |
| base::RepeatingCallback<bool(const std::string& content_type)>; |
| |
| bool IsTypeSupportedInternal( |
| ComPtr<IMFContentDecryptionModuleFactory> cdm_factory, |
| const std::string& key_system, |
| const std::string& content_type) { |
| bool supported = |
| cdm_factory->IsTypeSupported(base::UTF8ToWide(key_system).c_str(), |
| base::UTF8ToWide(content_type).c_str()); |
| DVLOG(3) << __func__ << " " << (supported ? "[yes]" : "[no]") << ": " |
| << key_system << ", " << content_type; |
| return supported; |
| } |
| |
| std::string GetFourCCString(VideoCodec codec) { |
| switch (codec) { |
| case VideoCodec::kH264: |
| return "avc1"; |
| case VideoCodec::kVP9: |
| return "vp09"; |
| case VideoCodec::kHEVC: |
| case VideoCodec::kDolbyVision: |
| return "hvc1"; |
| case VideoCodec::kAV1: |
| return "av01"; |
| default: |
| NOTREACHED() |
| << "This video codec is not supported by MediaFoundationCDM. codec=" |
| << GetCodecName(codec); |
| } |
| return ""; |
| } |
| |
| // Returns an "ext-profile" feature query (with ending comma) for a video codec. |
| // Returns an empty string if "ext-profile" is not needed. |
| std::string GetExtProfile(VideoCodec codec) { |
| if (codec == VideoCodec::kDolbyVision) |
| return "ext-profile=dvhe.05,"; |
| |
| return ""; |
| } |
| |
| std::string GetFourCCString(AudioCodec codec) { |
| switch (codec) { |
| case AudioCodec::kAAC: |
| return "mp4a"; |
| case AudioCodec::kVorbis: |
| return "vrbs"; |
| case AudioCodec::kFLAC: |
| return "fLaC"; |
| case AudioCodec::kOpus: |
| return "Opus"; |
| case AudioCodec::kEAC3: |
| return "ec-3"; |
| case AudioCodec::kAC3: |
| return "ac-3"; |
| case AudioCodec::kMpegHAudio: |
| return "mhm1"; |
| default: |
| NOTREACHED() |
| << "This audio codec is not supported by MediaFoundationCDM. codec=" |
| << GetCodecName(codec); |
| } |
| return ""; |
| } |
| |
| std::string GetName(EncryptionScheme scheme) { |
| switch (scheme) { |
| case EncryptionScheme::kCenc: |
| return "cenc"; |
| case EncryptionScheme::kCbcs: |
| return "cbcs"; |
| default: |
| NOTREACHED() << "Only cenc and cbcs are supported"; |
| } |
| return ""; |
| } |
| |
| // According to the common encryption spec, both 8 and 16 bytes IV are allowed |
| // for CENC and CBCS. But some platforms may only support 8 byte IV CENC and |
| // Chromium does not differentiate IV size for each encryption scheme, so we use |
| // 8 for CENC and 16 for CBCS to provide the best coverage as those combination |
| // are recommended. |
| int GetIvSize(EncryptionScheme scheme) { |
| switch (scheme) { |
| case EncryptionScheme::kCenc: |
| return 8; |
| case EncryptionScheme::kCbcs: |
| return 16; |
| default: |
| NOTREACHED() << "Only cenc and cbcs are supported"; |
| } |
| return 0; |
| } |
| |
| // Feature name:value mapping. |
| using FeatureMap = std::map<std::string, std::string>; |
| |
| // Construct the query type string based on `video_codec`, optional |
| // `audio_codec`, `kDefaultFeatures` and `extra_features`. |
| std::string GetTypeString(VideoCodec video_codec, |
| absl::optional<AudioCodec> audio_codec, |
| const FeatureMap& extra_features) { |
| auto codec_string = GetFourCCString(video_codec); |
| if (audio_codec.has_value()) |
| codec_string += "," + GetFourCCString(audio_codec.value()); |
| |
| auto feature_string = GetExtProfile(video_codec) + kDefaultFeatures; |
| DCHECK(!feature_string.empty()) << "default feature cannot be empty"; |
| for (const auto& feature : extra_features) { |
| DCHECK(!feature.first.empty() && !feature.second.empty()); |
| feature_string += "," + feature.first + "=" + feature.second; |
| } |
| |
| return base::ReplaceStringPlaceholders( |
| "video/mp4;codecs=\"$1\";features=\"$2\"", {codec_string, feature_string}, |
| /*offsets=*/nullptr); |
| } |
| |
| base::flat_set<EncryptionScheme> GetSupportedEncryptionSchemes( |
| IsTypeSupportedCB callback, |
| VideoCodec video_codec, |
| const std::string& robustness) { |
| base::flat_set<EncryptionScheme> supported_schemes; |
| for (const auto scheme : kAllEncryptionSchemes) { |
| auto type = GetTypeString( |
| video_codec, /*audio_codec=*/absl::nullopt, |
| {{kEncryptionSchemeQueryName, GetName(scheme)}, |
| {kEncryptionIvQueryName, base::NumberToString(GetIvSize(scheme))}, |
| {kRobustnessQueryName, robustness.c_str()}}); |
| |
| if (callback.Run(type)) |
| supported_schemes.insert(scheme); |
| } |
| return supported_schemes; |
| } |
| |
| absl::optional<CdmCapability> GetCdmCapability(IsTypeSupportedCB callback, |
| bool is_hw_secure) { |
| DVLOG(2) << __func__ << ", is_hw_secure=" << is_hw_secure; |
| |
| // TODO(hmchen): make this generic for more key systems. |
| const std::string robustness = |
| is_hw_secure ? kHwSecureRobustness : kSwSecureRobustness; |
| |
| CdmCapability capability; |
| |
| // Query video codecs. |
| for (const auto video_codec : kAllVideoCodecs) { |
| auto type = GetTypeString(video_codec, /*audio_codec=*/absl::nullopt, |
| {{kRobustnessQueryName, robustness}}); |
| |
| if (callback.Run(type)) { |
| // IsTypeSupported() does not support querying profiling, so specify {} |
| // to indicate all relevant profiles should be considered supported. |
| const std::vector<media::VideoCodecProfile> kAllProfiles = {}; |
| capability.video_codecs.emplace(video_codec, kAllProfiles); |
| } |
| } |
| |
| // IsTypeSupported query string requires video codec, so stops if no video |
| // codecs are supported. |
| if (capability.video_codecs.empty()) { |
| DVLOG(2) << "No video codecs supported for is_hw_secure=" << is_hw_secure; |
| return absl::nullopt; |
| } |
| |
| // Query audio codecs. |
| // Audio is usually independent to the video codec. So we use <one of the |
| // supported video codecs> + <audio codec> to query the audio capability. |
| for (const auto audio_codec : kAllAudioCodecs) { |
| const auto& video_codec = capability.video_codecs.begin()->first; |
| auto type = GetTypeString(video_codec, audio_codec, |
| {{kRobustnessQueryName, robustness}}); |
| |
| if (callback.Run(type)) |
| capability.audio_codecs.push_back(audio_codec); |
| } |
| |
| // Query encryption scheme. |
| |
| // Note that the CdmCapability assumes all `video_codecs` + `encryption_ |
| // schemes` combinations are supported. However, in Media Foundation, |
| // encryption scheme may be dependent on video codecs, so we query the |
| // encryption scheme for all supported video codecs and get the intersection |
| // of the encryption schemes which work for all codecs. |
| base::flat_set<EncryptionScheme> intersection( |
| std::begin(kAllEncryptionSchemes), std::end(kAllEncryptionSchemes)); |
| for (auto codec : capability.video_codecs) { |
| const auto schemes = |
| GetSupportedEncryptionSchemes(callback, codec.first, robustness); |
| intersection = base::STLSetIntersection<base::flat_set<EncryptionScheme>>( |
| intersection, schemes); |
| } |
| |
| if (intersection.empty()) { |
| // Fail if no supported encryption scheme. |
| return absl::nullopt; |
| } |
| |
| capability.encryption_schemes = intersection; |
| |
| // IsTypeSupported does not support session type yet. So just use temporary |
| // session which is required by EME spec. |
| capability.session_types.insert(CdmSessionType::kTemporary); |
| return capability; |
| } |
| |
| } // namespace |
| |
| MediaFoundationService::MediaFoundationService( |
| mojo::PendingReceiver<mojom::MediaFoundationService> receiver) |
| : receiver_(this, std::move(receiver)) { |
| DVLOG(1) << __func__; |
| mojo_media_client_.Initialize(); |
| } |
| |
| MediaFoundationService::~MediaFoundationService() { |
| DVLOG(1) << __func__; |
| } |
| |
| void MediaFoundationService::IsKeySystemSupported( |
| const std::string& key_system, |
| IsKeySystemSupportedCallback callback) { |
| DVLOG(2) << __func__ << ", key_system=" << key_system; |
| |
| ComPtr<IMFContentDecryptionModuleFactory> cdm_factory; |
| HRESULT hr = MediaFoundationCdmModule::GetInstance()->GetCdmFactory( |
| key_system, cdm_factory); |
| |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to GetCdmFactory."; |
| std::move(callback).Run(false, nullptr); |
| return; |
| } |
| |
| IsTypeSupportedCB is_type_supported_cb = |
| base::BindRepeating(&IsTypeSupportedInternal, cdm_factory, key_system); |
| |
| absl::optional<CdmCapability> sw_secure_capability = |
| GetCdmCapability(is_type_supported_cb, /*is_hw_secure=*/false); |
| absl::optional<CdmCapability> hw_secure_capability = |
| GetCdmCapability(is_type_supported_cb, /*is_hw_secure=*/true); |
| |
| if (!sw_secure_capability && !hw_secure_capability) { |
| DVLOG(2) << "Get empty CdmCapability."; |
| std::move(callback).Run(false, nullptr); |
| return; |
| } |
| |
| auto capability = media::mojom::KeySystemCapability::New(); |
| capability->sw_secure_capability = sw_secure_capability; |
| capability->hw_secure_capability = hw_secure_capability; |
| std::move(callback).Run(true, std::move(capability)); |
| } |
| |
| void MediaFoundationService::CreateInterfaceFactory( |
| mojo::PendingReceiver<mojom::InterfaceFactory> receiver, |
| mojo::PendingRemote<mojom::FrameInterfaceFactory> frame_interfaces) { |
| DVLOG(2) << __func__; |
| interface_factory_receivers_.Add( |
| std::make_unique<InterfaceFactoryImpl>(std::move(frame_interfaces), |
| &mojo_media_client_), |
| std::move(receiver)); |
| } |
| |
| } // namespace media |