Commit 1d7c5c6d authored by Xiaohan Wang's avatar Xiaohan Wang Committed by Commit Bot

media: Add MediaFoundationCdmSession

This class adapts Media Foundation IMFContentDecryptionModuleSession
to a class that uses Chromium CDM types. It also handles setting session
ID which isn't available when the session is created. This makes it
easier for MediaFoundationCdm (to be uploaded in the next CL) to manage
multiple sessions.

NOPRESUBMIT=true

Bug: 999747
Change-Id: Id966915d8bebb97406a412c4d2e958f2e2529576
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2265099
Commit-Queue: Xiaohan Wang <xhwang@chromium.org>
Reviewed-by: default avatarJohn Rummell <jrummell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784251}
parent 6e262d54
...@@ -62,6 +62,8 @@ source_set("test_support") { ...@@ -62,6 +62,8 @@ source_set("test_support") {
sources = [ sources = [
"d3d11_mocks.cc", "d3d11_mocks.cc",
"d3d11_mocks.h", "d3d11_mocks.h",
"mf_mocks.cc",
"mf_mocks.h",
"test_utils.h", "test_utils.h",
] ]
deps = [ deps = [
......
// Copyright 2020 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/base/win/mf_mocks.h"
namespace media {
MockMFCdm::MockMFCdm() = default;
MockMFCdm::~MockMFCdm() = default;
MockMFCdmSession::MockMFCdmSession() = default;
MockMFCdmSession::~MockMFCdmSession() = default;
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_BASE_WIN_MF_MOCKS_H_
#define MEDIA_BASE_WIN_MF_MOCKS_H_
#include <mfcontentdecryptionmodule.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#include "media/base/win/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace media {
class MockMFCdm
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
IMFContentDecryptionModule> {
public:
MockMFCdm();
~MockMFCdm() override;
// IMFContentDecryptionModule methods
MOCK_STDCALL_METHOD2(SetContentEnabler,
HRESULT(IMFContentEnabler* content_enabler,
IMFAsyncResult* result));
MOCK_STDCALL_METHOD1(GetSuspendNotify, HRESULT(IMFCdmSuspendNotify** notify));
MOCK_STDCALL_METHOD1(SetPMPHostApp, HRESULT(IMFPMPHostApp* pmp_host_app));
MOCK_STDCALL_METHOD3(
CreateSession,
HRESULT(MF_MEDIAKEYSESSION_TYPE session_type,
IMFContentDecryptionModuleSessionCallbacks* callbacks,
IMFContentDecryptionModuleSession** session));
MOCK_STDCALL_METHOD2(SetServerCertificate,
HRESULT(const BYTE* certificate,
DWORD certificate_size));
MOCK_STDCALL_METHOD3(CreateTrustedInput,
HRESULT(const BYTE* content_init_data,
DWORD content_init_data_size,
IMFTrustedInput** trusted_input));
MOCK_STDCALL_METHOD2(GetProtectionSystemIds,
HRESULT(GUID** system_ids, DWORD* count));
};
class MockMFCdmSession
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
IMFContentDecryptionModuleSession> {
public:
MockMFCdmSession();
~MockMFCdmSession() override;
// IMFContentDecryptionModuleSession methods
MOCK_STDCALL_METHOD1(GetSessionId, HRESULT(LPWSTR* session_id));
MOCK_STDCALL_METHOD1(GetExpiration, HRESULT(double* expiration));
MOCK_STDCALL_METHOD2(GetKeyStatuses,
HRESULT(MFMediaKeyStatus** key_statuses,
UINT* num_key_statuses));
MOCK_STDCALL_METHOD2(Load, HRESULT(LPCWSTR session_id, BOOL* loaded));
MOCK_STDCALL_METHOD3(GenerateRequest,
HRESULT(LPCWSTR init_data_type,
const BYTE* init_data,
DWORD init_data_size));
MOCK_STDCALL_METHOD2(Update,
HRESULT(const BYTE* response, DWORD response_size));
MOCK_STDCALL_METHOD0(Close, HRESULT());
MOCK_STDCALL_METHOD0(Remove, HRESULT());
};
} // namespace media
#endif // MEDIA_BASE_WIN_MF_MOCKS_H_
...@@ -46,6 +46,12 @@ ...@@ -46,6 +46,12 @@
#define COM_ON_CALL(obj, call) ON_CALL(*obj.Get(), call) #define COM_ON_CALL(obj, call) ON_CALL(*obj.Get(), call)
#define COM_EXPECT_CALL(obj, call) EXPECT_CALL(*obj.Get(), call) #define COM_EXPECT_CALL(obj, call) EXPECT_CALL(*obj.Get(), call)
// Helpers for EXPECT or ASSERT success or failed HRESULTs.
#define EXPECT_SUCCESS(expr) EXPECT_TRUE(SUCCEEDED((expr)))
#define EXPECT_FAILED(expr) EXPECT_TRUE(FAILED((expr)))
#define ASSERT_SUCCESS(expr) ASSERT_TRUE(SUCCEEDED((expr)))
#define ASSERT_FAILED(expr) ASSERT_TRUE(FAILED((expr)))
namespace media { namespace media {
// Use this action when using SetArgPointee with COM pointers. // Use this action when using SetArgPointee with COM pointers.
...@@ -72,6 +78,13 @@ ACTION_TEMPLATE(SetComPointeeAndReturnOk, ...@@ -72,6 +78,13 @@ ACTION_TEMPLATE(SetComPointeeAndReturnOk,
return S_OK; return S_OK;
} }
ACTION_TEMPLATE(SaveComPtr,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_1_VALUE_PARAMS(p)) {
*p = std::get<k>(args);
(*p)->AddRef();
}
// Use this function to create a mock so that they are ref-counted correctly. // Use this function to create a mock so that they are ref-counted correctly.
template <typename Interface> template <typename Interface>
Microsoft::WRL::ComPtr<Interface> MakeComPtr() { Microsoft::WRL::ComPtr<Interface> MakeComPtr() {
......
...@@ -96,6 +96,15 @@ source_set("cdm") { ...@@ -96,6 +96,15 @@ source_set("cdm") {
] ]
} }
} }
if (is_win) {
sources += [
"win/media_foundation_cdm_session.cc",
"win/media_foundation_cdm_session.h",
]
deps += [ "//media/base/win:media_foundation_util" ]
}
} }
static_library("cdm_paths") { static_library("cdm_paths") {
...@@ -165,4 +174,8 @@ source_set("unit_tests") { ...@@ -165,4 +174,8 @@ source_set("unit_tests") {
if (proprietary_codecs) { if (proprietary_codecs) {
sources += [ "cenc_utils_unittest.cc" ] sources += [ "cenc_utils_unittest.cc" ]
} }
if (is_win) {
sources += [ "win/media_foundation_cdm_session_unittest.cc" ]
}
} }
// Copyright 2020 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/cdm/win/media_foundation_cdm_session.h"
#include <memory>
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/scoped_co_mem.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/cdm_key_information.h"
#include "media/base/win/mf_helpers.h"
namespace media {
namespace {
using Microsoft::WRL::ClassicCom;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::MakeAndInitialize;
using Microsoft::WRL::RuntimeClass;
using Microsoft::WRL::RuntimeClassFlags;
MF_MEDIAKEYSESSION_TYPE ToMFSessionType(CdmSessionType session_type) {
switch (session_type) {
case CdmSessionType::kTemporary:
return MF_MEDIAKEYSESSION_TYPE_TEMPORARY;
case CdmSessionType::kPersistentLicense:
return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_LICENSE;
case CdmSessionType::kPersistentUsageRecord:
return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_USAGE_RECORD;
}
}
// The strings are defined in https://www.w3.org/TR/eme-initdata-registry/
LPCWSTR InitDataTypeToString(EmeInitDataType init_data_type) {
switch (init_data_type) {
case EmeInitDataType::UNKNOWN:
return L"unknown";
case EmeInitDataType::WEBM:
return L"webm";
case EmeInitDataType::CENC:
return L"cenc";
case EmeInitDataType::KEYIDS:
return L"keyids";
}
}
CdmMessageType ToCdmMessageType(MF_MEDIAKEYSESSION_MESSAGETYPE message_type) {
switch (message_type) {
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST:
return CdmMessageType::LICENSE_REQUEST;
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL:
return CdmMessageType::LICENSE_RENEWAL;
case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE:
return CdmMessageType::LICENSE_RELEASE;
case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST:
return CdmMessageType::INDIVIDUALIZATION_REQUEST;
}
}
CdmKeyInformation::KeyStatus ToCdmKeyStatus(MF_MEDIAKEY_STATUS status) {
switch (status) {
case MF_MEDIAKEY_STATUS_USABLE:
return CdmKeyInformation::KeyStatus::USABLE;
case MF_MEDIAKEY_STATUS_EXPIRED:
return CdmKeyInformation::KeyStatus::EXPIRED;
case MF_MEDIAKEY_STATUS_OUTPUT_DOWNSCALED:
return CdmKeyInformation::KeyStatus::OUTPUT_DOWNSCALED;
// This is for legacy use and should not happen in normal cases. Map it to
// internal error in case it happens.
case MF_MEDIAKEY_STATUS_OUTPUT_NOT_ALLOWED:
return CdmKeyInformation::KeyStatus::INTERNAL_ERROR;
case MF_MEDIAKEY_STATUS_STATUS_PENDING:
return CdmKeyInformation::KeyStatus::KEY_STATUS_PENDING;
case MF_MEDIAKEY_STATUS_INTERNAL_ERROR:
return CdmKeyInformation::KeyStatus::INTERNAL_ERROR;
case MF_MEDIAKEY_STATUS_RELEASED:
return CdmKeyInformation::KeyStatus::RELEASED;
case MF_MEDIAKEY_STATUS_OUTPUT_RESTRICTED:
return CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED;
}
}
CdmKeysInfo ToCdmKeysInfo(const MFMediaKeyStatus* key_statuses, int count) {
CdmKeysInfo keys_info;
keys_info.reserve(count);
for (int i = 0; i < count; ++i) {
const auto& key_status = key_statuses[i];
keys_info.push_back(std::make_unique<CdmKeyInformation>(
key_status.pbKeyId, key_status.cbKeyId,
ToCdmKeyStatus(key_status.eMediaKeyStatus), /*system_code=*/0));
}
return keys_info;
}
class SessionCallbacks
: public RuntimeClass<RuntimeClassFlags<ClassicCom>,
IMFContentDecryptionModuleSessionCallbacks> {
public:
SessionCallbacks() { DVLOG_FUNC(1); }
SessionCallbacks(const SessionCallbacks&) = delete;
SessionCallbacks& operator=(const SessionCallbacks&) = delete;
~SessionCallbacks() final { DVLOG_FUNC(1); }
using MessageCB =
base::RepeatingCallback<void(CdmMessageType message_type,
const std::vector<uint8_t>& message)>;
using KeysChangeCB = base::RepeatingClosure;
HRESULT RuntimeClassInitialize(MessageCB message_cb,
KeysChangeCB keys_change_cb) {
message_cb_ = std::move(message_cb);
keys_change_cb_ = std::move(keys_change_cb);
return S_OK;
}
// IMFContentDecryptionModuleSessionCallbacks implementation
STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE message_type,
const BYTE* message,
DWORD message_size,
LPCWSTR destination_url) final {
DVLOG_FUNC(2) << ": message size=" << message_size;
message_cb_.Run(ToCdmMessageType(message_type),
std::vector<uint8_t>(message, message + message_size));
return S_OK;
}
STDMETHODIMP KeyStatusChanged() final {
DVLOG_FUNC(2);
keys_change_cb_.Run();
return S_OK;
}
private:
MessageCB message_cb_;
KeysChangeCB keys_change_cb_;
};
} // namespace
MediaFoundationCdmSession::MediaFoundationCdmSession(
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb)
: session_message_cb_(session_message_cb),
session_closed_cb_(session_closed_cb),
session_keys_change_cb_(session_keys_change_cb),
session_expiration_update_cb_(session_expiration_update_cb) {
DVLOG_FUNC(1);
}
MediaFoundationCdmSession::~MediaFoundationCdmSession() {
DVLOG_FUNC(1);
}
HRESULT MediaFoundationCdmSession::Initialize(
IMFContentDecryptionModule* mf_cdm,
CdmSessionType session_type) {
DVLOG_FUNC(1);
ComPtr<SessionCallbacks> session_callbacks;
auto weak_this = weak_factory_.GetWeakPtr();
// Use BindToCurrentLoop() because the callbacks can be fired on different
// threads by |mf_cdm_session_|.
RETURN_IF_FAILED(MakeAndInitialize<SessionCallbacks>(
&session_callbacks,
BindToCurrentLoop(base::BindRepeating(
&MediaFoundationCdmSession::OnSessionMessage, weak_this)),
BindToCurrentLoop(base::BindRepeating(
&MediaFoundationCdmSession::OnSessionKeysChange, weak_this))));
// |mf_cdm_session_| holds a ref count to |session_callbacks|.
RETURN_IF_FAILED(mf_cdm->CreateSession(ToMFSessionType(session_type),
session_callbacks.Get(),
&mf_cdm_session_));
return S_OK;
}
HRESULT MediaFoundationCdmSession::GenerateRequest(
EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
SessionIdCB session_id_cb) {
DVLOG_FUNC(1);
DCHECK(session_id_.empty() && !session_id_cb_);
session_id_cb_ = std::move(session_id_cb);
RETURN_IF_FAILED(mf_cdm_session_->GenerateRequest(
InitDataTypeToString(init_data_type), init_data.data(),
base::checked_cast<DWORD>(init_data.size())));
return S_OK;
}
HRESULT MediaFoundationCdmSession::Load(const std::string& session_id) {
DVLOG_FUNC(1);
return E_NOTIMPL;
}
HRESULT
MediaFoundationCdmSession::Update(const std::vector<uint8_t>& response) {
DVLOG_FUNC(1);
RETURN_IF_FAILED(
mf_cdm_session_->Update(reinterpret_cast<const BYTE*>(response.data()),
base::checked_cast<DWORD>(response.size())));
return S_OK;
}
HRESULT MediaFoundationCdmSession::Close() {
DVLOG_FUNC(1);
RETURN_IF_FAILED(mf_cdm_session_->Close());
return S_OK;
}
HRESULT MediaFoundationCdmSession::Remove() {
DVLOG_FUNC(1);
RETURN_IF_FAILED(mf_cdm_session_->Remove());
return S_OK;
}
void MediaFoundationCdmSession::OnSessionMessage(
CdmMessageType message_type,
const std::vector<uint8_t>& message) {
DVLOG_FUNC(2);
if (session_id_.empty())
SetSessionId();
// Empty |session_id_| will be treated as failure by the caller.
if (session_id_cb_)
std::move(session_id_cb_).Run(session_id_);
if (!session_id_.empty())
session_message_cb_.Run(session_id_, message_type, message);
}
void MediaFoundationCdmSession::OnSessionKeysChange() {
DVLOG_FUNC(2);
if (session_id_.empty()) {
DLOG(ERROR) << "Unexpected session keys change ignored";
return;
}
base::win::ScopedCoMem<MFMediaKeyStatus> key_statuses;
UINT count = 0;
if (FAILED(mf_cdm_session_->GetKeyStatuses(&key_statuses, &count))) {
DLOG(ERROR) << __func__ << ": Failed to get key statuses";
return;
}
// TODO(xhwang): Investigate whether we need to set |has_new_usable_key|.
session_keys_change_cb_.Run(session_id_, /*has_new_usable_key=*/true,
ToCdmKeysInfo(key_statuses.get(), count));
// ScopedCoMem<MFMediaKeyStatus> only releases memory for |key_statuses|. We
// need to manually release memory for |pbKeyId| here.
for (UINT i = 0; i < count; ++i) {
const auto& key_status = key_statuses[i];
if (key_status.pbKeyId)
CoTaskMemFree(key_status.pbKeyId);
}
}
void MediaFoundationCdmSession::SetSessionId() {
DCHECK(session_id_.empty());
base::win::ScopedCoMem<wchar_t> session_id;
HRESULT hr = mf_cdm_session_->GetSessionId(&session_id);
if (FAILED(hr) || !session_id)
return;
session_id_ = base::UTF16ToUTF8(session_id.get());
DVLOG_FUNC(1) << "session_id_=" << session_id_;
}
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_CDM_WIN_MEDIA_FOUNDATION_CDM_SESSION_H_
#define MEDIA_CDM_WIN_MEDIA_FOUNDATION_CDM_SESSION_H_
#include <mfcontentdecryptionmodule.h>
#include <wrl.h>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "media/base/content_decryption_module.h"
#include "media/base/media_export.h"
namespace media {
// A class wrapping IMFContentDecryptionModuleSession.
class MEDIA_EXPORT MediaFoundationCdmSession {
public:
MediaFoundationCdmSession(
const SessionMessageCB& session_message_cb,
const SessionClosedCB& session_closed_cb,
const SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb);
MediaFoundationCdmSession(const MediaFoundationCdmSession&) = delete;
MediaFoundationCdmSession& operator=(const MediaFoundationCdmSession&) =
delete;
~MediaFoundationCdmSession();
// Initializes the session. All other methods should only be called after
// Initialize() returns S_OK.
HRESULT Initialize(IMFContentDecryptionModule* mf_cdm,
CdmSessionType session_type);
// EME MediaKeySession methods. Returns S_OK on success, otherwise forwards
// the HRESULT from IMFContentDecryptionModuleSession.
// Note on GenerateRequest():
// - Returns S_OK, which has two cases:
// * If |session_id_| is successfully set, |session_id_cb| will be run
// followed by the session message.
// * Otherwise, |session_id_cb| will be run with an empty session ID to
// indicate error. No session message in this case.
// - Otherwise, no callbacks will be run.
using SessionIdCB = base::OnceCallback<void(const std::string&)>;
HRESULT GenerateRequest(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data,
SessionIdCB session_id_cb);
HRESULT Load(const std::string& session_id);
HRESULT Update(const std::vector<uint8_t>& response);
HRESULT Close();
HRESULT Remove();
private:
// Callbacks for forwarding session events.
void OnSessionMessage(CdmMessageType message_type,
const std::vector<uint8_t>& message);
void OnSessionKeysChange();
// Sets |session_id_|, which could be empty on failure.
void SetSessionId();
// Callbacks for firing session events.
// TODO(xhwang): Implement session expiration update.
SessionMessageCB session_message_cb_;
SessionClosedCB session_closed_cb_;
SessionKeysChangeCB session_keys_change_cb_;
SessionExpirationUpdateCB session_expiration_update_cb_;
Microsoft::WRL::ComPtr<IMFContentDecryptionModuleSession> mf_cdm_session_;
// Callback passed in GenerateRequest() to return the session ID.
SessionIdCB session_id_cb_;
std::string session_id_;
// NOTE: Weak pointers must be invalidated before all other member variables.
base::WeakPtrFactory<MediaFoundationCdmSession> weak_factory_{this};
};
} // namespace media
#endif // MEDIA_CDM_WIN_MEDIA_FOUNDATION_CDM_SESSION_H_
// Copyright 2020 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/cdm/win/media_foundation_cdm_session.h"
#include <wchar.h>
#include "base/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/base/win/mf_mocks.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using ::testing::WithoutArgs;
namespace media {
namespace {
std::vector<uint8_t> StringToVector(const std::string& str) {
return std::vector<uint8_t>(str.begin(), str.end());
}
HRESULT CopyCoTaskMemWideString(LPCWSTR in_string, LPWSTR* out_string) {
if (!in_string || !out_string) {
return E_INVALIDARG;
}
size_t size = (wcslen(in_string) + 1) * sizeof(wchar_t);
LPWSTR copy = reinterpret_cast<LPWSTR>(CoTaskMemAlloc(size));
if (!copy)
return E_OUTOFMEMORY;
wcscpy(copy, in_string);
*out_string = copy;
return S_OK;
}
} // namespace
using Microsoft::WRL::ComPtr;
class MediaFoundationCdmSessionTest : public testing::Test {
public:
MediaFoundationCdmSessionTest()
: mf_cdm_(MakeComPtr<MockMFCdm>()),
mf_cdm_session_(MakeComPtr<MockMFCdmSession>()),
cdm_session_(
base::BindRepeating(&MockCdmClient::OnSessionMessage,
base::Unretained(&cdm_client_)),
base::BindRepeating(&MockCdmClient::OnSessionClosed,
base::Unretained(&cdm_client_)),
base::BindRepeating(&MockCdmClient::OnSessionKeysChange,
base::Unretained(&cdm_client_)),
base::BindRepeating(&MockCdmClient::OnSessionExpirationUpdate,
base::Unretained(&cdm_client_))) {}
~MediaFoundationCdmSessionTest() override {}
void Initialize() {
COM_EXPECT_CALL(mf_cdm_,
CreateSession(MF_MEDIAKEYSESSION_TYPE_TEMPORARY, _, _))
.WillOnce(DoAll(
SaveComPtr<1>(mf_cdm_session_callbacks_.ReleaseAndGetAddressOf()),
SetComPointee<2>(mf_cdm_session_.Get()), Return(S_OK)));
ASSERT_SUCCESS(
cdm_session_.Initialize(mf_cdm_.Get(), CdmSessionType::kTemporary));
}
void GenerateRequest() {
std::vector<uint8_t> init_data = StringToVector("init_data");
std::vector<uint8_t> license_request = StringToVector("request");
base::MockCallback<MediaFoundationCdmSession::SessionIdCB> session_id_cb;
// Session ID to return. Will be released by |mf_cdm_session_|.
LPWSTR session_id = nullptr;
ASSERT_SUCCESS(CopyCoTaskMemWideString(L"session_id", &session_id));
{
// Use InSequence here because the order of events matter. |session_id_cb|
// must be called before OnSessionMessage().
InSequence seq;
COM_EXPECT_CALL(mf_cdm_session_, GenerateRequest(_, _, init_data.size()))
.WillOnce(WithoutArgs([&] {
mf_cdm_session_callbacks_->KeyMessage(
MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST,
license_request.data(), license_request.size(), nullptr);
return S_OK;
}));
COM_EXPECT_CALL(mf_cdm_session_, GetSessionId(_))
.WillOnce(DoAll(SetArgPointee<0>(session_id), Return(S_OK)));
EXPECT_CALL(session_id_cb, Run(_));
EXPECT_CALL(cdm_client_,
OnSessionMessage(_, CdmMessageType::LICENSE_REQUEST,
license_request));
}
EXPECT_SUCCESS(cdm_session_.GenerateRequest(
EmeInitDataType::WEBM, init_data, session_id_cb.Get()));
task_environment_.RunUntilIdle();
}
protected:
base::test::TaskEnvironment task_environment_;
StrictMock<MockCdmClient> cdm_client_;
ComPtr<MockMFCdm> mf_cdm_;
ComPtr<MockMFCdmSession> mf_cdm_session_;
MediaFoundationCdmSession cdm_session_;
ComPtr<IMFContentDecryptionModuleSessionCallbacks> mf_cdm_session_callbacks_;
};
TEST_F(MediaFoundationCdmSessionTest, Initialize) {
Initialize();
}
TEST_F(MediaFoundationCdmSessionTest, Initialize_Failure) {
COM_EXPECT_CALL(mf_cdm_,
CreateSession(MF_MEDIAKEYSESSION_TYPE_TEMPORARY, _, _))
.WillOnce(DoAll(
SaveComPtr<1>(mf_cdm_session_callbacks_.ReleaseAndGetAddressOf()),
SetComPointee<2>(mf_cdm_session_.Get()), Return(E_FAIL)));
EXPECT_FAILED(
cdm_session_.Initialize(mf_cdm_.Get(), CdmSessionType::kTemporary));
}
TEST_F(MediaFoundationCdmSessionTest, GenerateRequest) {
Initialize();
GenerateRequest();
}
TEST_F(MediaFoundationCdmSessionTest, GenerateRequest_Failure) {
Initialize();
std::vector<uint8_t> init_data = StringToVector("init_data");
base::MockCallback<MediaFoundationCdmSession::SessionIdCB> session_id_cb;
COM_EXPECT_CALL(mf_cdm_session_, GenerateRequest(_, _, init_data.size()))
.WillOnce(Return(E_FAIL));
EXPECT_FAILED(cdm_session_.GenerateRequest(EmeInitDataType::WEBM, init_data,
session_id_cb.Get()));
task_environment_.RunUntilIdle();
}
TEST_F(MediaFoundationCdmSessionTest, GetSessionId_Failure) {
Initialize();
std::vector<uint8_t> init_data = StringToVector("init_data");
std::vector<uint8_t> license_request = StringToVector("request");
base::MockCallback<MediaFoundationCdmSession::SessionIdCB> session_id_cb;
COM_EXPECT_CALL(mf_cdm_session_, GenerateRequest(_, _, init_data.size()))
.WillOnce(WithoutArgs([&] {
mf_cdm_session_callbacks_->KeyMessage(
MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST,
license_request.data(), license_request.size(), nullptr);
return S_OK;
}));
COM_EXPECT_CALL(mf_cdm_session_, GetSessionId(_)).WillOnce(Return(E_FAIL));
EXPECT_CALL(session_id_cb, Run(IsEmpty()));
// OnSessionMessage() will not be called.
EXPECT_SUCCESS(cdm_session_.GenerateRequest(EmeInitDataType::WEBM, init_data,
session_id_cb.Get()));
task_environment_.RunUntilIdle();
}
TEST_F(MediaFoundationCdmSessionTest, GetSessionId_Empty) {
Initialize();
std::vector<uint8_t> init_data = StringToVector("init_data");
std::vector<uint8_t> license_request = StringToVector("request");
base::MockCallback<MediaFoundationCdmSession::SessionIdCB> session_id_cb;
// Session ID to return. Will be released by |mf_cdm_session_|.
LPWSTR empty_session_id = nullptr;
ASSERT_SUCCESS(CopyCoTaskMemWideString(L"", &empty_session_id));
COM_EXPECT_CALL(mf_cdm_session_, GenerateRequest(_, _, init_data.size()))
.WillOnce(WithoutArgs([&] {
mf_cdm_session_callbacks_->KeyMessage(
MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST,
license_request.data(), license_request.size(), nullptr);
return S_OK;
}));
COM_EXPECT_CALL(mf_cdm_session_, GetSessionId(_))
.WillOnce(DoAll(SetArgPointee<0>(empty_session_id), Return(S_OK)));
EXPECT_CALL(session_id_cb, Run(IsEmpty()));
// OnSessionMessage() will not be called since session ID is empty.
EXPECT_SUCCESS(cdm_session_.GenerateRequest(EmeInitDataType::WEBM, init_data,
session_id_cb.Get()));
task_environment_.RunUntilIdle();
}
TEST_F(MediaFoundationCdmSessionTest, Update) {
Initialize();
GenerateRequest();
std::vector<uint8_t> response = StringToVector("response");
COM_EXPECT_CALL(mf_cdm_session_, Update(NotNull(), response.size()))
.WillOnce(DoAll([&] { mf_cdm_session_callbacks_->KeyStatusChanged(); },
Return(S_OK)));
COM_EXPECT_CALL(mf_cdm_session_, GetKeyStatuses(_, _)).WillOnce(Return(S_OK));
EXPECT_CALL(cdm_client_, OnSessionKeysChangeCalled(_, true));
EXPECT_SUCCESS(cdm_session_.Update(response));
task_environment_.RunUntilIdle();
}
TEST_F(MediaFoundationCdmSessionTest, Update_Failure) {
Initialize();
GenerateRequest();
std::vector<uint8_t> response = StringToVector("response");
COM_EXPECT_CALL(mf_cdm_session_, Update(NotNull(), response.size()))
.WillOnce(Return(E_FAIL));
EXPECT_FAILED(cdm_session_.Update(response));
}
TEST_F(MediaFoundationCdmSessionTest, Close) {
Initialize();
GenerateRequest();
COM_EXPECT_CALL(mf_cdm_session_, Close()).WillOnce(Return(S_OK));
EXPECT_SUCCESS(cdm_session_.Close());
}
TEST_F(MediaFoundationCdmSessionTest, Close_Failure) {
Initialize();
GenerateRequest();
COM_EXPECT_CALL(mf_cdm_session_, Close()).WillOnce(Return(E_FAIL));
EXPECT_FAILED(cdm_session_.Close());
}
TEST_F(MediaFoundationCdmSessionTest, Remove) {
Initialize();
GenerateRequest();
COM_EXPECT_CALL(mf_cdm_session_, Remove()).WillOnce(Return(S_OK));
EXPECT_SUCCESS(cdm_session_.Remove());
}
TEST_F(MediaFoundationCdmSessionTest, Remove_Failure) {
Initialize();
GenerateRequest();
COM_EXPECT_CALL(mf_cdm_session_, Remove()).WillOnce(Return(E_FAIL));
EXPECT_FAILED(cdm_session_.Remove());
}
} // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment