blob: 89871aab5839338b4e5183d4b1755ccbf95ded72 [file] [log] [blame]
// 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