0

media: Report CDM Metrics via UKM

This CL contains the infrastructure and reporting to be able to record
metrics via UKM that are sent from the CDM via the ReportMetrics
function. This CL also updates the DEPS, and as part of that, supports
the CDM v12, and CDM v11 which both contain the ReportMetrics function.

UKM when I set Chromium to allow CDM v11 on Linux:

```
URL

https://integration.widevine.com/player

Media.EME.CdmMetrics
NumberOfOnMessageEvents	1
NumberOfUpdateCalls	1
SdkVersion	0x00001147c8416880
```

UKM collection doc: http://shortn/_gLIyckWz0f

Validate-Test-Flakiness: skip
Bug: 353733810
Change-Id: I6a81ab4eb53c4295634eda3d062a9e35bc50ed79
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5807537
Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Reviewed-by: John Rummell <jrummell@chromium.org>
Reviewed-by: Alex Gough <ajgo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1395042}
This commit is contained in:
Vikram Pasupathy
2024-12-11 12:33:10 -08:00
committed by Chromium LUCI CQ
parent 3fbbc57c60
commit b977079cce
26 changed files with 390 additions and 21 deletions

2
DEPS

@ -1389,7 +1389,7 @@ deps = {
},
'src/media/cdm/api':
Var('chromium_git') + '/chromium/cdm.git' + '@' + 'eb21edc44e8e5a82095037be80c8b15c51624293',
Var('chromium_git') + '/chromium/cdm.git' + '@' + '82340ffad5f88d6c2efd458da85d9e5f243eabb3',
'src/native_client': {
'url': Var('chromium_git') + '/native_client/src/native_client.git' + '@' + Var('nacl_revision'),

@ -2435,6 +2435,8 @@ static_library("browser") {
"//services/device/public/mojom:usb",
"//services/image_annotation:service",
"//services/media_session/public/mojom",
"//services/metrics",
"//services/metrics/public/cpp:metrics_cpp",
"//services/metrics/public/cpp:ukm_builders",
"//services/network:network_service",
"//services/network/public/cpp",

@ -472,6 +472,7 @@ include_rules = [
"+services/media_session/public",
"+services/metrics/metrics_mojo_service.h",
"+services/metrics/public",
"+services/metrics",
"+services/network/network_service.h",
"+services/network/public",
"+services/network/test",

@ -84,6 +84,7 @@
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
#include "chrome/browser/media/output_protection_impl.h"
#include "services/metrics/ukm_recorder_factory_impl.h"
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
#if BUILDFLAG(ENABLE_MOJO_CDM) && BUILDFLAG(IS_ANDROID)
@ -303,6 +304,12 @@ void ChromeContentBrowserClient::BindMediaServiceReceiver(
OutputProtectionImpl::Create(render_frame_host, std::move(r));
return;
}
if (auto r = receiver.As<ukm::mojom::UkmRecorderFactory>()) {
metrics::UkmRecorderFactoryImpl::Create(ukm::UkmRecorder::Get(),
std::move(r));
return;
}
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
#if BUILDFLAG(ENABLE_LIBRARY_CDMS) || BUILDFLAG(IS_WIN)

@ -125,6 +125,7 @@ using testing::UnorderedElementsAre;
using testing::Values;
using testing::WithParamInterface;
using ukm::builders::Media_EME_ApiPromiseRejection;
using ukm::builders::Media_EME_CdmMetrics;
using ukm::builders::Media_EME_CreateMediaKeys;
using ukm::builders::Media_EME_RequestMediaKeySystemAccess;
using ukm::builders::Media_EME_Usage;
@ -496,6 +497,60 @@ class ECKEncryptedMediaFileIOTest : public EncryptedMediaTestBase,
}
};
class ECKEncryptedMediaReportMetricsTest : public EncryptedMediaTestBase,
public WithParamInterface<int> {
public:
int GetCdmInterfaceVersion() { return GetParam(); }
void SetUpOnMainThread() override {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDown() override {
auto report_metric_entries = ukm_recorder_->GetEntries(
Media_EME_CdmMetrics::kEntryName,
{
Media_EME_CdmMetrics::kCertificateSerialNumberName,
Media_EME_CdmMetrics::kDecoderBypassBlockCountName,
Media_EME_CdmMetrics::kLicenseSdkVersionName,
Media_EME_CdmMetrics::kNumberOfOnMessageEventsName,
Media_EME_CdmMetrics::kNumberOfUpdateCallsName,
});
// The ReportMetrics functionality only works in v11 and onwards, but verify
// that in v10, RecordUkm is not called and the Ukm is not set.
if (GetCdmInterfaceVersion() > 10) {
EXPECT_EQ(report_metric_entries.size(), 1u);
// The ClearKey cdm does not report kCertificateSerialNumber or
// kDecoderBypassBlockCount, so the entries should not even be set in the
// ukm data.
EXPECT_THAT(
report_metric_entries[0].metrics,
UnorderedElementsAre(
Pair(Media_EME_CdmMetrics::kLicenseSdkVersionName, 12345),
Pair(Media_EME_CdmMetrics::kNumberOfOnMessageEventsName, 1),
Pair(Media_EME_CdmMetrics::kNumberOfUpdateCallsName, 1)));
} else {
EXPECT_EQ(report_metric_entries.size(), 0u);
}
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
EncryptedMediaTestBase::SetUpCommandLine(command_line);
SetUpCommandLineForKeySystem(media::kExternalClearKeyKeySystem,
command_line);
// Override enabled CDM interface version for testing.
command_line->AppendSwitchASCII(
switches::kOverrideEnabledCdmInterfaceVersion,
base::NumberToString(GetCdmInterfaceVersion()));
}
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
// Tests encrypted media playback with output protection using ExternalClearKey
// key system with a specific display surface to be captured specified as the
// test parameter.
@ -1115,13 +1170,32 @@ IN_PROC_BROWSER_TEST_P(MseEncryptedMediaTest,
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
// Test CDM_10 through CDM_11.
static_assert(media::CheckSupportedCdmInterfaceVersions(10, 11),
// Test CDM_10 through CDM_12.
static_assert(media::CheckSupportedCdmInterfaceVersions(10, 12),
"Mismatch between implementation and test coverage");
INSTANTIATE_TEST_SUITE_P(CDM_10, ECKEncryptedMediaTest, Values(10));
INSTANTIATE_TEST_SUITE_P(CDM_11, ECKEncryptedMediaTest, Values(11));
INSTANTIATE_TEST_SUITE_P(CDM_12, ECKEncryptedMediaTest, Values(12));
INSTANTIATE_TEST_SUITE_P(CDM_10, ECKEncryptedMediaFileIOTest, Values(10));
INSTANTIATE_TEST_SUITE_P(CDM_11, ECKEncryptedMediaFileIOTest, Values(10));
INSTANTIATE_TEST_SUITE_P(CDM_11, ECKEncryptedMediaFileIOTest, Values(11));
INSTANTIATE_TEST_SUITE_P(CDM_12, ECKEncryptedMediaFileIOTest, Values(12));
INSTANTIATE_TEST_SUITE_P(CDM_10,
ECKEncryptedMediaReportMetricsTest,
Values(10));
INSTANTIATE_TEST_SUITE_P(CDM_11,
ECKEncryptedMediaReportMetricsTest,
Values(11));
INSTANTIATE_TEST_SUITE_P(CDM_12,
ECKEncryptedMediaReportMetricsTest,
Values(12));
IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaReportMetricsTest, RecordUkmTest) {
RunSimpleEncryptedMediaTest("bear-320x240-av_enc-a.webm",
media::kExternalClearKeyKeySystem, SrcType::SRC,
PlayCount::ONCE);
base::RunLoop().RunUntilIdle();
}
IN_PROC_BROWSER_TEST_P(ECKEncryptedMediaTest, InitializeCDMFail) {
TestNonPlaybackCases(kExternalClearKeyInitializeFailKeySystem,

@ -67,6 +67,7 @@ class MockFrameInterfaceFactory : public media::mojom::FrameInterfaceFactory {
MOCK_METHOD(void, CreateCdmStorage, (mojo::PendingReceiver<BrowserStorage>));
MOCK_METHOD(bool, GetCdmOrigin, (url::Origin*));
MOCK_METHOD(void, GetCdmOrigin, (GetCdmOriginCallback));
MOCK_METHOD(void, GetPageUkmSourceId, (GetPageUkmSourceIdCallback callback));
MOCK_METHOD(void, BindEmbedderReceiver, (mojo::GenericPendingReceiver));
};

@ -208,6 +208,10 @@ class FrameInterfaceFactoryImpl : public media::mojom::FrameInterfaceFactory,
render_frame_host_->GetLastCommittedOrigin());
}
void GetPageUkmSourceId(GetPageUkmSourceIdCallback callback) override {
return std::move(callback).Run(render_frame_host_->GetPageUkmSourceId());
}
void BindEmbedderReceiver(mojo::GenericPendingReceiver receiver) override {
GetContentClient()->browser()->BindMediaServiceReceiver(
render_frame_host_, std::move(receiver));

@ -38,7 +38,6 @@
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/cdm/cdm_auxiliary_helper.h"
#include "media/cdm/cdm_helpers.h"
#include "media/cdm/cdm_type_conversion.h"
#include "media/cdm/cdm_wrapper.h"
@ -130,7 +129,7 @@ void* GetCdmHost(int host_interface_version, void* user_data) {
static_assert(
CheckSupportedCdmHostVersions(cdm::Host_10::kVersion,
cdm::Host_11::kVersion),
cdm::Host_12::kVersion),
"Mismatch between GetCdmHost() and IsSupportedCdmHostVersion()");
DCHECK(IsSupportedCdmHostVersion(host_interface_version));
@ -142,6 +141,8 @@ void* GetCdmHost(int host_interface_version, void* user_data) {
return static_cast<cdm::Host_10*>(cdm_adapter);
case cdm::Host_11::kVersion:
return static_cast<cdm::Host_11*>(cdm_adapter);
case cdm::Host_12::kVersion:
return static_cast<cdm::Host_12*>(cdm_adapter);
// When future Host versions are used, update to include them over here.
// Older Chrome versions that don't support new host versions would return
// nullptr.
@ -238,6 +239,15 @@ CdmAdapter::CdmAdapter(
CdmAdapter::~CdmAdapter() {
DVLOG(1) << __func__;
// Only Cdms using an interface version greater than 10 have access to the
// ReportMetrics function, so to prevent from reporting a lot of metrics that
// are left unset, check if the interface version is greater than 10. We
// should also only report to the UKM in cases where at least one of the CDM
// values are set, otherwise too many impractical values will be reported.
if (GetInterfaceVersion() > 10 && cdm_metrics_data_.IsCdmValueSet()) {
helper_->RecordUkm(cdm_metrics_data_);
}
// Reject any outstanding promises and close all the existing sessions.
cdm_promise_adapter_.Clear(CdmPromiseAdapter::ClearReason::kDestruction);
@ -375,6 +385,8 @@ void CdmAdapter::UpdateSession(const std::string& session_id,
DCHECK(!response.empty());
TRACE_EVENT1("media", "CdmAdapter::UpdateSession", "session_id", session_id);
cdm_metrics_data_.number_of_update_calls++;
uint32_t promise_id =
cdm_promise_adapter_.SavePromise(std::move(promise), __func__);
DVLOG(2) << __func__ << ": session_id = " << session_id
@ -772,6 +784,8 @@ void CdmAdapter::OnSessionMessage(const char* session_id,
DVLOG(2) << __func__ << ": session_id = " << session_id_str;
DCHECK(task_runner_->BelongsToCurrentThread());
cdm_metrics_data_.number_of_on_message_events++;
TRACE_EVENT2("media", "CdmAdapter::OnSessionMessage", "session_id",
session_id_str, "message_type", message_type);
@ -1037,6 +1051,21 @@ void CdmAdapter::RequestStorageId(uint32_t version) {
weak_factory_.GetWeakPtr()));
}
void CdmAdapter::ReportMetrics(cdm::MetricName metric_name, uint64_t value) {
switch (metric_name) {
case cdm::kSdkVersion:
cdm_metrics_data_.license_sdk_version = value;
return;
case cdm::kCertificateSerialNumber:
cdm_metrics_data_.certificate_serial_number = value;
return;
case cdm::kDecoderBypassBlockCount:
cdm_metrics_data_.decoder_bypass_block_count =
cdm_metrics_data_.decoder_bypass_block_count.value_or(0) + value;
return;
}
}
void CdmAdapter::OnStorageIdObtained(uint32_t version,
const std::vector<uint8_t>& storage_id) {
DVLOG(2) << __func__ << ": version = " << version;

@ -30,19 +30,20 @@
#include "media/base/media_export.h"
#include "media/base/video_aspect_ratio.h"
#include "media/cdm/api/content_decryption_module.h"
#include "media/cdm/cdm_auxiliary_helper.h"
#include "ui/gfx/geometry/size.h"
namespace media {
class AudioFramesImpl;
class CdmAuxiliaryHelper;
class CdmWrapper;
class MEDIA_EXPORT CdmAdapter final : public ContentDecryptionModule,
public CdmContext,
public Decryptor,
public cdm::Host_10,
public cdm::Host_11 {
public cdm::Host_11,
public cdm::Host_12 {
public:
using CreateCdmFunc = void* (*)(int cdm_interface_version,
const char* key_system,
@ -156,6 +157,7 @@ class MEDIA_EXPORT CdmAdapter final : public ContentDecryptionModule,
cdm::Status decoder_status) override;
cdm::FileIO* CreateFileIO(cdm::FileIOClient* client) override;
void RequestStorageId(uint32_t version) override;
void ReportMetrics(cdm::MetricName metric_name, uint64_t value) override;
private:
CdmAdapter(const CdmConfig& cdm_config,
@ -256,6 +258,9 @@ class MEDIA_EXPORT CdmAdapter final : public ContentDecryptionModule,
int last_read_file_size_kb_ = 0;
bool file_size_uma_reported_ = false;
// Tracks UKM related data.
CdmMetricsData cdm_metrics_data_;
// Used to keep track of promises while the CDM is processing the request.
CdmPromiseAdapter cdm_promise_adapter_;

@ -37,14 +37,14 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::Values;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::_;
using ::testing::Values;
MATCHER(IsNotEmpty, "") {
return !arg.empty();
@ -59,6 +59,15 @@ MATCHER(IsNullPlatformChallengeResponse, "") {
return !arg.signed_data;
}
MATCHER_P(HasLicenseSdkVersion, expected_version, "") {
return arg.license_sdk_version.has_value() &&
arg.license_sdk_version.value() == expected_version;
}
MATCHER(HasNoLicenseSdkVersion, "") {
return !arg.license_sdk_version.has_value();
}
// TODO(jrummell): These tests are a subset of those in aes_decryptor_unittest.
// Refactor aes_decryptor_unittest.cc to handle AesDecryptor directly and
// via CdmAdapter once CdmAdapter supports decrypting functionality. There
@ -69,6 +78,8 @@ namespace media {
namespace {
const uint64_t kExpectedLicenseSdkVersion = 12345;
// Random key ID used to create a session.
const uint8_t kKeyId[] = {
// base64 equivalent is AQIDBAUGBwgJCgsMDQ4PEA
@ -240,6 +251,15 @@ class CdmAdapterTestWithClearKeyCdm : public CdmAdapterTestBase {
EXPECT_CALL(cdm_client_, OnSessionMessage(IsNotEmpty(), _, _));
}
// The ClearKeyCdm records the LicenseSdkVersion in CreateSession.
if (GetCdmInterfaceVersion() > 10) {
EXPECT_CALL(*cdm_helper_,
RecordUkm(HasLicenseSdkVersion(kExpectedLicenseSdkVersion)))
.Times(1);
} else {
EXPECT_CALL(*cdm_helper_, RecordUkm(_)).Times(0);
}
cdm_->CreateSessionAndGenerateRequest(
CdmSessionType::kTemporary, data_type, key_id,
CreateSessionPromise(expected_result));
@ -343,6 +363,19 @@ class CdmAdapterTestWithClearKeyCdm : public CdmAdapterTestBase {
class CdmAdapterTestWithMockCdm : public CdmAdapterTestBase {
public:
~CdmAdapterTestWithMockCdm() override {
// If the test is not a ukm test, no cdm values will be set, and
// RecordUkm should not be called at all. If it is a ukm test, and the
// interface version is greater than 10, verify that the license sdk
// reported is what we expect.
if (!is_ukm_test_) {
EXPECT_CALL(*cdm_helper_, RecordUkm(_)).Times(0);
} else if (GetCdmInterfaceVersion() > 10) {
EXPECT_CALL(*cdm_helper_,
RecordUkm(HasLicenseSdkVersion(kExpectedLicenseSdkVersion)));
} else {
EXPECT_CALL(*cdm_helper_, RecordUkm(_)).Times(0);
}
// Makes sure Destroy() is called on CdmAdapter destruction.
EXPECT_CALL(*mock_library_cdm_, DestroyCalled());
}
@ -368,6 +401,8 @@ class CdmAdapterTestWithMockCdm : public CdmAdapterTestBase {
// These are both owned by `cdm_`.
raw_ptr<MockLibraryCdm> mock_library_cdm_ = nullptr;
raw_ptr<CdmHostProxy> cdm_host_proxy_ = nullptr;
bool is_ukm_test_ = false;
};
// Instantiate test cases
@ -571,4 +606,12 @@ TEST_P(CdmAdapterTestWithMockCdm, GetDecryptor) {
EXPECT_TRUE(cdm_context->GetDecryptor());
}
TEST_P(CdmAdapterTestWithMockCdm, RecordUkmCalled) {
is_ukm_test_ = true;
CdmConfig cdm_config = GetCdmConfig();
InitializeWithCdmConfig(cdm_config);
cdm_host_proxy_->ReportMetrics(cdm::kSdkVersion, 12345);
}
} // namespace media

@ -29,6 +29,8 @@ url::Origin CdmAuxiliaryHelper::GetCdmOrigin() {
return url::Origin();
}
void CdmAuxiliaryHelper::RecordUkm(const CdmMetricsData& cdm_metrics_data) {}
cdm::Buffer* CdmAuxiliaryHelper::CreateCdmBuffer(size_t capacity) {
return nullptr;
}

@ -27,6 +27,24 @@ class FileIOClient;
namespace media {
// Some of these fields are optional because the CDM can choose to not report
// some fields and report others. These fields will then be left as unset when
// reported via UKM, and are treated differently than if the field was reported
// as the default value, e.g 0.
struct MEDIA_EXPORT CdmMetricsData {
std::optional<uint64_t> license_sdk_version;
uint64_t number_of_update_calls = 0;
uint64_t number_of_on_message_events = 0;
std::optional<uint64_t> certificate_serial_number;
std::optional<uint64_t> decoder_bypass_block_count;
bool IsCdmValueSet() {
return (license_sdk_version.has_value() ||
certificate_serial_number.has_value() ||
decoder_bypass_block_count.has_value());
}
};
// Provides a wrapper on the auxiliary functions (CdmAllocator, CdmFileIO,
// OutputProtection, CdmDocumentService) needed by the library CDM. The
// default implementation does nothing -- it simply returns nullptr, false, 0,
@ -54,6 +72,10 @@ class MEDIA_EXPORT CdmAuxiliaryHelper : public CdmAllocator,
// if the origin is unavailable or if error happened.
virtual url::Origin GetCdmOrigin();
// Records a UKM for the following metrics from the CDM. This is called on the
// destruction of a CDM instance in cdm_adapter.cc.
virtual void RecordUkm(const CdmMetricsData& cdm_metrics_data);
// CdmAllocator implementation.
cdm::Buffer* CreateCdmBuffer(size_t capacity) override;
std::unique_ptr<VideoFrameImpl> CreateCdmVideoFrame() override;

@ -302,7 +302,8 @@ class CdmWrapperImpl : public CdmWrapper {
std::unique_ptr<CdmInterface, CdmDeleter> cdm_;
};
// Specialization for cdm::ContentDecryptionModule_10 methods.
// Specialization for cdm::ContentDecryptionModule_10 methods and
// cdm::ContentDecryptionModule_11 methods.
template <>
cdm::Status CdmWrapperImpl<10>::InitializeVideoDecoder(
@ -311,13 +312,20 @@ cdm::Status CdmWrapperImpl<10>::InitializeVideoDecoder(
ToVideoDecoderConfig_2(video_decoder_config));
}
template <>
cdm::Status CdmWrapperImpl<11>::InitializeVideoDecoder(
const cdm::VideoDecoderConfig_3& video_decoder_config) {
return cdm_->InitializeVideoDecoder(
ToVideoDecoderConfig_2(video_decoder_config));
}
// static
CdmWrapper* CdmWrapper::Create(CreateCdmFunc create_cdm_func,
const char* key_system,
uint32_t key_system_size,
GetCdmHostFunc get_cdm_host_func,
void* user_data) {
static_assert(CheckSupportedCdmInterfaceVersions(10, 11),
static_assert(CheckSupportedCdmInterfaceVersions(10, 12),
"Mismatch between CdmWrapper::Create() and "
"IsSupportedCdmInterfaceVersion()");
@ -330,7 +338,14 @@ CdmWrapper* CdmWrapper::Create(CreateCdmFunc create_cdm_func,
// Try to use the latest supported and enabled CDM interface first. If it's
// not supported by the CDM, try to create the CDM using older supported
// versions.
if (IsSupportedAndEnabledCdmInterfaceVersion(11)) {
if (IsSupportedAndEnabledCdmInterfaceVersion(12)) {
cdm_wrapper =
CdmWrapperImpl<12>::Create(create_cdm_func, key_system, key_system_size,
get_cdm_host_func, user_data);
}
if (!cdm_wrapper && IsSupportedAndEnabledCdmInterfaceVersion(11)) {
cdm_wrapper =
CdmWrapperImpl<11>::Create(create_cdm_func, key_system, key_system_size,
get_cdm_host_func, user_data);

@ -55,6 +55,7 @@ class CdmHostProxy {
cdm::Status decoder_status) = 0;
virtual cdm::FileIO* CreateFileIO(cdm::FileIOClient* client) = 0;
virtual void RequestStorageId(uint32_t version) = 0;
virtual void ReportMetrics(cdm::MetricName metric_name, uint64_t value) = 0;
};
} // namespace media

@ -116,10 +116,20 @@ class CdmHostProxyImpl : public CdmHostProxy {
host_->RequestStorageId(version);
}
void ReportMetrics(cdm::MetricName metric_name, uint64_t value) final {
host_->ReportMetrics(metric_name, value);
}
private:
const raw_ptr<HostInterface> host_ = nullptr;
};
template <>
void CdmHostProxyImpl<cdm::Host_10>::ReportMetrics(cdm::MetricName metric_name,
uint64_t value) {
// cdm::ContentDecryptionModule_10 CDM should never call this.
}
} // namespace media
#endif // MEDIA_CDM_LIBRARY_CDM_CDM_HOST_PROXY_IMPL_H_

@ -178,11 +178,14 @@ void* CreateCdmInstance(int cdm_interface_version,
return nullptr;
}
// We support CDM_10 and CDM_11.
// We support CDM_10, CDM_11, and CDM_12.
using CDM_10 = cdm::ContentDecryptionModule_10;
using CDM_11 = cdm::ContentDecryptionModule_11;
using CDM_12 = cdm::ContentDecryptionModule_12;
if (cdm_interface_version == CDM_10::kVersion) {
static_assert(CDM_10::kVersion == CDM_10::Host::kVersion,
"CDM host version mismatch");
CDM_10::Host* host = static_cast<CDM_10::Host*>(
get_cdm_host_func(CDM_10::Host::kVersion, user_data));
if (!host)
@ -194,6 +197,8 @@ void* CreateCdmInstance(int cdm_interface_version,
}
if (cdm_interface_version == CDM_11::kVersion) {
static_assert(CDM_11::kVersion == CDM_11::Host::kVersion,
"CDM host version mismatch");
CDM_11::Host* host = static_cast<CDM_11::Host*>(
get_cdm_host_func(CDM_11::Host::kVersion, user_data));
if (!host)
@ -204,6 +209,18 @@ void* CreateCdmInstance(int cdm_interface_version,
new media::ClearKeyCdm(host, key_system_string));
}
if (cdm_interface_version == CDM_12::kVersion) {
CDM_12::Host* host = static_cast<CDM_12::Host*>(
get_cdm_host_func(CDM_12::Host::kVersion, user_data));
if (!host) {
return nullptr;
}
DVLOG(1) << __func__ << ": Create ClearKeyCdm with CDM_12::Host.";
return static_cast<CDM_12*>(
new media::ClearKeyCdm(host, key_system_string));
}
return nullptr;
}
@ -379,6 +396,7 @@ void ClearKeyCdm::CreateSessionAndGenerateRequest(
promise_id),
base::BindOnce(&ClearKeyCdm::OnPromiseFailed, base::Unretained(this),
promise_id));
cdm_host_proxy_->ReportMetrics(cdm::kSdkVersion, 12345);
cdm_->CreateSessionAndGenerateRequest(
ToMediaSessionType(session_type), ToEmeInitDataType(init_data_type),
std::vector<uint8_t>(init_data, init_data + init_data_size),

@ -31,7 +31,8 @@ const int64_t kInitialTimerDelayMs = 200;
// Clear key implementation of the cdm::ContentDecryptionModule interfaces.
class ClearKeyCdm : public cdm::ContentDecryptionModule_10,
public cdm::ContentDecryptionModule_11 {
public cdm::ContentDecryptionModule_11,
public cdm::ContentDecryptionModule_12 {
public:
template <typename HostInterface>
ClearKeyCdm(HostInterface* host, const std::string& key_system);
@ -41,13 +42,14 @@ class ClearKeyCdm : public cdm::ContentDecryptionModule_10,
~ClearKeyCdm() override;
// cdm::ContentDecryptionModule_10 implementation.
// cdm::ContentDecryptionModule_10 and cdm::ContentDecryptionModule_11
// implementation.
cdm::Status InitializeVideoDecoder(
const cdm::VideoDecoderConfig_2& video_decoder_config) override;
cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& encrypted_buffer,
cdm::VideoFrame* video_frame) override;
// cdm::ContentDecryptionModule_11 implementation.
// cdm::ContentDecryptionModule_12 implementation.
cdm::Status InitializeVideoDecoder(
const cdm::VideoDecoderConfig_3& video_decoder_config) override;
cdm::Status DecryptAndDecodeFrame(const cdm::InputBuffer_2& encrypted_buffer,

@ -55,6 +55,8 @@ class MockCdmAuxiliaryHelper : public CdmAuxiliaryHelper {
MOCK_METHOD1(GetStorageIdCalled, std::vector<uint8_t>(uint32_t version));
void GetStorageId(uint32_t version, StorageIdCB callback) override;
MOCK_METHOD(void, RecordUkm, (const CdmMetricsData&), (override));
#if BUILDFLAG(IS_WIN)
MOCK_METHOD(void,
GetMediaFoundationCdmData,

@ -34,9 +34,10 @@ struct SupportedVersion {
bool enabled;
};
constexpr std::array<SupportedVersion, 2> kSupportedCdmInterfaceVersions = {{
constexpr std::array<SupportedVersion, 3> kSupportedCdmInterfaceVersions = {{
{10, true},
{11, false},
{12, false},
}};
// In most cases CdmInterface::kVersion == CdmInterface::Host::kVersion. However
@ -45,7 +46,7 @@ constexpr std::array<SupportedVersion, 2> kSupportedCdmInterfaceVersions = {{
// support. In CdmInterfaceTraits we also static assert that for supported CDM
// interface, CdmInterface::Host::kVersion must also be supported.
constexpr int kMinSupportedCdmHostVersion = 10;
constexpr int kMaxSupportedCdmHostVersion = 11;
constexpr int kMaxSupportedCdmHostVersion = 12;
constexpr bool IsSupportedCdmModuleVersion(int version) {
return version == CDM_MODULE_VERSION;
@ -147,6 +148,18 @@ struct CdmInterfaceTraits<11> {
"Experimental CDM interface should not be enabled by default");
};
template <>
struct CdmInterfaceTraits<12> {
using CdmInterface = cdm::ContentDecryptionModule_12;
static_assert(CdmInterface::kVersion == 12, "CDM interface version mismatch");
static_assert(IsSupportedCdmHostVersion(CdmInterface::Host::kVersion),
"Host not supported");
static_assert(
CdmInterface::kIsStable ||
!IsCdmInterfaceVersionEnabledByDefault(CdmInterface::kVersion),
"Experimental CDM interface should not be enabled by default");
};
} // namespace media
#endif // MEDIA_CDM_SUPPORTED_CDM_VERSIONS_H_

@ -46,6 +46,10 @@ interface FrameInterfaceFactory {
[Sync]
GetCdmOrigin() => (url.mojom.Origin cdm_origin);
// Gets the UKMSourceId of the frame associated with the CDM.
[Sync]
GetPageUkmSourceId() => (int64 ukm_source_id);
// Binds a generic media frame-bound interface. This is to allow //content
// embedders to provide additional interfaces.
BindEmbedderReceiver(mojo_base.mojom.GenericPendingReceiver receiver);

@ -110,6 +110,7 @@ component("services") {
"//media/mojo/common",
"//services/metrics/public/cpp:metrics_cpp",
"//services/metrics/public/cpp:ukm_builders",
"//services/metrics/public/mojom",
"//services/service_manager/public/mojom",
]

@ -13,11 +13,21 @@
#include "media/mojo/services/mojo_cdm_file_io.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace media {
MojoCdmHelper::MojoCdmHelper(mojom::FrameInterfaceFactory* frame_interfaces)
: frame_interfaces_(frame_interfaces) {}
: frame_interfaces_(frame_interfaces) {
// Retrieve the Ukm recording objects in the constructor of MojoCdmHelper
// because the connection to the renderer frame host might be disconnected at
// any time and we record the Ukm in the destructor of CdmAdapter, so we
// should store the objects as early as possible.
RetrieveUkmRecordingObjects();
}
MojoCdmHelper::~MojoCdmHelper() = default;
@ -125,6 +135,46 @@ void MojoCdmHelper::ReportFileReadSize(int file_size_bytes) {
file_read_cb_.Run(file_size_bytes);
}
void MojoCdmHelper::RecordUkm(const CdmMetricsData& cdm_metrics_data) {
if (ukm_source_id_ == ukm::kInvalidSourceId) {
DLOG(ERROR) << "Invalid UKM source ID";
return;
}
auto ukm_builder = ukm::builders::Media_EME_CdmMetrics(ukm_source_id_);
if (cdm_metrics_data.license_sdk_version.has_value()) {
ukm_builder.SetLicenseSdkVersion(
cdm_metrics_data.license_sdk_version.value());
}
ukm_builder.SetNumberOfUpdateCalls(cdm_metrics_data.number_of_update_calls);
ukm_builder.SetNumberOfOnMessageEvents(
cdm_metrics_data.number_of_on_message_events);
if (cdm_metrics_data.certificate_serial_number.has_value()) {
ukm_builder.SetCertificateSerialNumber(
cdm_metrics_data.certificate_serial_number.value());
}
if (cdm_metrics_data.decoder_bypass_block_count.has_value()) {
ukm_builder.SetDecoderBypassBlockCount(
cdm_metrics_data.decoder_bypass_block_count.value());
}
ukm_builder.Record(ukm_recorder_.get());
}
void MojoCdmHelper::RetrieveUkmRecordingObjects() {
ConnectToUkmRecorderFactory();
ukm_recorder_ = ukm::MojoUkmRecorder::Create(*ukm_recorder_factory_);
// TODO(crbug.com/382073085): Find a better way of updating the Ukm Source ID.
frame_interfaces_->GetPageUkmSourceId(&ukm_source_id_);
}
void MojoCdmHelper::ConnectToOutputProtection() {
if (!output_protection_) {
DVLOG(2) << "Connect to mojom::OutputProtection";
@ -145,6 +195,16 @@ void MojoCdmHelper::ConnectToCdmDocumentService() {
}
}
void MojoCdmHelper::ConnectToUkmRecorderFactory() {
if (!ukm_recorder_factory_) {
DVLOG(2) << "Connect to ukm::mojom::UkmRecorderFactory";
frame_interfaces_->BindEmbedderReceiver(
ukm_recorder_factory_.BindNewPipeAndPassReceiver());
// No reset_on_disconnect() since MediaInterfaceProxy should be destroyed
// when document is destroyed, which will destroy MojoCdmHelper as well.
}
}
CdmAllocator* MojoCdmHelper::GetAllocator() {
if (!allocator_)
allocator_ = std::make_unique<MojoCdmAllocator>();

@ -21,6 +21,8 @@
#include "media/mojo/services/media_mojo_export.h"
#include "media/mojo/services/mojo_cdm_file_io.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/mojom/ukm_interface.mojom.h"
namespace media {
@ -39,6 +41,7 @@ class MEDIA_MOJO_EXPORT MojoCdmHelper final : public CdmAuxiliaryHelper,
void SetFileReadCB(FileReadCB file_read_cb) final;
cdm::FileIO* CreateCdmFileIO(cdm::FileIOClient* client) final;
url::Origin GetCdmOrigin() final;
void RecordUkm(const CdmMetricsData& cdm_metrics_data) final;
cdm::Buffer* CreateCdmBuffer(size_t capacity) final;
std::unique_ptr<VideoFrameImpl> CreateCdmVideoFrame() final;
void QueryStatus(QueryStatusCB callback) final;
@ -59,9 +62,13 @@ class MEDIA_MOJO_EXPORT MojoCdmHelper final : public CdmAuxiliaryHelper,
void ReportFileReadSize(int file_size_bytes) final;
private:
// Retrieves instances necessary to recording Ukm from the frame interface.
void RetrieveUkmRecordingObjects();
// All services are created lazily.
void ConnectToOutputProtection();
void ConnectToCdmDocumentService();
void ConnectToUkmRecorderFactory();
CdmAllocator* GetAllocator();
@ -73,6 +80,10 @@ class MEDIA_MOJO_EXPORT MojoCdmHelper final : public CdmAuxiliaryHelper,
// destroyed but RenderFrameHostImpl is not.
mojo::Remote<mojom::OutputProtection> output_protection_;
mojo::Remote<mojom::CdmDocumentService> cdm_document_service_;
mojo::Remote<ukm::mojom::UkmRecorderFactory> ukm_recorder_factory_;
std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder_;
ukm::SourceId ukm_source_id_ = ukm::kInvalidSourceId;
std::unique_ptr<CdmAllocator> allocator_;

@ -81,6 +81,8 @@ class TestFrameInterfaceFactory : public mojom::FrameInterfaceFactory {
mojo::PendingReceiver<mojom::DCOMPSurfaceRegistry> receiver) override {}
#endif // BUILDFLAG(IS_WIN)
void GetCdmOrigin(GetCdmOriginCallback callback) override {}
bool GetPageUkmSourceId(int64_t* ukm_source_id) override { return true; }
void GetPageUkmSourceId(GetPageUkmSourceIdCallback callback) override {}
void BindEmbedderReceiver(mojo::GenericPendingReceiver) override {}
};

@ -12692,6 +12692,46 @@ be describing additional metrics about the same event.
</metric>
</event>
<event name="Media.EME.CdmMetrics">
<owner>vpasupathy@chromium.org</owner>
<owner>media-dev@chromium.org</owner>
<summary>
Metrics reported by the CDM to the browser, collected in an utility process.
This metric is reported every time on the destruction of the CdmAdapter
object. Some fields of this metric can be left unset if the CDM does not
report them.
</summary>
<metric name="CertificateSerialNumber">
<summary>
The certificate serial number used by the main url given by the CDM server
certificate. Different Key systems will report the serial number in
different ways.
</summary>
</metric>
<metric name="DecoderBypassBlockCount">
<summary>
The number of blocks that can bypass the decoder present in an encrypted
playback.
</summary>
</metric>
<metric name="LicenseSdkVersion">
<summary>
The SDK version of the license server of the URL that communicates with
the CDM.
</summary>
</metric>
<metric name="NumberOfOnMessageEvents">
<summary>
The number of OnMessage events for the CDM.
</summary>
</metric>
<metric name="NumberOfUpdateCalls">
<summary>
The number of Update events for the CDM
</summary>
</metric>
</event>
<event name="Media.EME.CdmSystemCode">
<owner>vpasupathy@chromium.org</owner>
<owner>media-dev@chromium.org</owner>