blob: dbd65a4db1f096eae77430a55637d1fe64246b94 [file] [log] [blame]
// 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;
}
}
// 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 final
: public RuntimeClass<RuntimeClassFlags<ClassicCom>,
IMFContentDecryptionModuleSessionCallbacks> {
public:
SessionCallbacks() { DVLOG_FUNC(1); }
SessionCallbacks(const SessionCallbacks&) = delete;
SessionCallbacks& operator=(const SessionCallbacks&) = delete;
~SessionCallbacks() override { 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 SessionKeysChangeCB& session_keys_change_cb,
const SessionExpirationUpdateCB& session_expiration_update_cb)
: session_message_cb_(session_message_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_IF_FAILED(UpdateExpirationIfNeeded());
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_IF_FAILED(UpdateExpirationIfNeeded());
return S_OK;
}
void MediaFoundationCdmSession::OnSessionMessage(
CdmMessageType message_type,
const std::vector<uint8_t>& message) {
DVLOG_FUNC(2);
if (session_id_.empty() && !session_id_cb_) {
DLOG(ERROR) << "Unexpected session message";
return;
}
// If |session_id_| has not been set, set it now.
if (session_id_.empty() && !SetSessionId())
return;
DCHECK(!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);
}
}
bool MediaFoundationCdmSession::SetSessionId() {
DCHECK(session_id_.empty() && session_id_cb_);
base::win::ScopedCoMem<wchar_t> session_id;
HRESULT hr = mf_cdm_session_->GetSessionId(&session_id);
if (FAILED(hr) || !session_id) {
bool success = std::move(session_id_cb_).Run("");
DCHECK(!success) << "Empty session ID should not be accepted";
return false;
}
auto session_id_str = base::WideToUTF8(session_id.get());
if (session_id_str.empty()) {
bool success = std::move(session_id_cb_).Run("");
DCHECK(!success) << "Empty session ID should not be accepted";
return false;
}
bool success = std::move(session_id_cb_).Run(session_id_str);
if (!success) {
DLOG(ERROR) << "Session ID " << session_id_str << " rejected";
return false;
}
DVLOG_FUNC(1) << "session_id_=" << session_id_str;
session_id_ = session_id_str;
return true;
}
HRESULT MediaFoundationCdmSession::UpdateExpirationIfNeeded() {
DCHECK(!session_id_.empty());
// Media Foundation CDM follows the EME spec where Time generally represents
// an instant in time with millisecond accuracy.
double new_expiration_ms = 0.0;
RETURN_IF_FAILED(mf_cdm_session_->GetExpiration(&new_expiration_ms));
auto new_expiration = base::Time::FromJsTime(new_expiration_ms);
if (new_expiration == expiration_)
return S_OK;
DVLOG(2) << "New session expiration: " << new_expiration;
expiration_ = new_expiration;
session_expiration_update_cb_.Run(session_id_, expiration_);
return S_OK;
}
} // namespace media