media_foundation_cdm_session.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "media/cdm/win/media_foundation_cdm_session.h"
  5. #include <memory>
  6. #include "base/logging.h"
  7. #include "base/metrics/histogram_functions.h"
  8. #include "base/numerics/safe_conversions.h"
  9. #include "base/strings/utf_string_conversions.h"
  10. #include "base/win/scoped_co_mem.h"
  11. #include "media/base/bind_to_current_loop.h"
  12. #include "media/base/cdm_key_information.h"
  13. #include "media/base/win/mf_helpers.h"
  14. namespace media {
  15. namespace {
  16. using Microsoft::WRL::ClassicCom;
  17. using Microsoft::WRL::ComPtr;
  18. using Microsoft::WRL::MakeAndInitialize;
  19. using Microsoft::WRL::RuntimeClass;
  20. using Microsoft::WRL::RuntimeClassFlags;
  21. MF_MEDIAKEYSESSION_TYPE ToMFSessionType(CdmSessionType session_type) {
  22. switch (session_type) {
  23. case CdmSessionType::kTemporary:
  24. return MF_MEDIAKEYSESSION_TYPE_TEMPORARY;
  25. case CdmSessionType::kPersistentLicense:
  26. return MF_MEDIAKEYSESSION_TYPE_PERSISTENT_LICENSE;
  27. }
  28. }
  29. // The strings are defined in https://www.w3.org/TR/eme-initdata-registry/
  30. LPCWSTR InitDataTypeToString(EmeInitDataType init_data_type) {
  31. switch (init_data_type) {
  32. case EmeInitDataType::UNKNOWN:
  33. return L"unknown";
  34. case EmeInitDataType::WEBM:
  35. return L"webm";
  36. case EmeInitDataType::CENC:
  37. return L"cenc";
  38. case EmeInitDataType::KEYIDS:
  39. return L"keyids";
  40. }
  41. }
  42. CdmMessageType ToCdmMessageType(MF_MEDIAKEYSESSION_MESSAGETYPE message_type) {
  43. switch (message_type) {
  44. case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_REQUEST:
  45. return CdmMessageType::LICENSE_REQUEST;
  46. case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RENEWAL:
  47. return CdmMessageType::LICENSE_RENEWAL;
  48. case MF_MEDIAKEYSESSION_MESSAGETYPE_LICENSE_RELEASE:
  49. return CdmMessageType::LICENSE_RELEASE;
  50. case MF_MEDIAKEYSESSION_MESSAGETYPE_INDIVIDUALIZATION_REQUEST:
  51. return CdmMessageType::INDIVIDUALIZATION_REQUEST;
  52. }
  53. }
  54. CdmKeyInformation::KeyStatus ToCdmKeyStatus(MF_MEDIAKEY_STATUS status) {
  55. switch (status) {
  56. case MF_MEDIAKEY_STATUS_USABLE:
  57. return CdmKeyInformation::KeyStatus::USABLE;
  58. case MF_MEDIAKEY_STATUS_EXPIRED:
  59. return CdmKeyInformation::KeyStatus::EXPIRED;
  60. case MF_MEDIAKEY_STATUS_OUTPUT_DOWNSCALED:
  61. return CdmKeyInformation::KeyStatus::OUTPUT_DOWNSCALED;
  62. // This is for legacy use and should not happen in normal cases. Map it to
  63. // internal error in case it happens.
  64. case MF_MEDIAKEY_STATUS_OUTPUT_NOT_ALLOWED:
  65. return CdmKeyInformation::KeyStatus::INTERNAL_ERROR;
  66. case MF_MEDIAKEY_STATUS_STATUS_PENDING:
  67. return CdmKeyInformation::KeyStatus::KEY_STATUS_PENDING;
  68. case MF_MEDIAKEY_STATUS_INTERNAL_ERROR:
  69. return CdmKeyInformation::KeyStatus::INTERNAL_ERROR;
  70. case MF_MEDIAKEY_STATUS_RELEASED:
  71. return CdmKeyInformation::KeyStatus::RELEASED;
  72. case MF_MEDIAKEY_STATUS_OUTPUT_RESTRICTED:
  73. return CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED;
  74. }
  75. }
  76. CdmKeysInfo ToCdmKeysInfo(const MFMediaKeyStatus* key_statuses, int count) {
  77. CdmKeysInfo keys_info;
  78. keys_info.reserve(count);
  79. for (int i = 0; i < count; ++i) {
  80. const auto& key_status = key_statuses[i];
  81. if (key_status.cbKeyId != sizeof(GUID)) {
  82. DLOG(ERROR) << __func__ << ": Key ID with unsupported size ignored";
  83. continue;
  84. }
  85. GUID* key_id_guid = reinterpret_cast<GUID*>(key_status.pbKeyId);
  86. keys_info.push_back(std::make_unique<CdmKeyInformation>(
  87. ByteArrayFromGUID(*key_id_guid),
  88. ToCdmKeyStatus(key_status.eMediaKeyStatus),
  89. /*system_code=*/0));
  90. }
  91. return keys_info;
  92. }
  93. class SessionCallbacks final
  94. : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
  95. IMFContentDecryptionModuleSessionCallbacks> {
  96. public:
  97. SessionCallbacks() { DVLOG_FUNC(1); }
  98. SessionCallbacks(const SessionCallbacks&) = delete;
  99. SessionCallbacks& operator=(const SessionCallbacks&) = delete;
  100. ~SessionCallbacks() override { DVLOG_FUNC(1); }
  101. using MessageCB =
  102. base::RepeatingCallback<void(CdmMessageType message_type,
  103. const std::vector<uint8_t>& message)>;
  104. using KeysChangeCB = base::RepeatingClosure;
  105. HRESULT RuntimeClassInitialize(MessageCB message_cb,
  106. KeysChangeCB keys_change_cb) {
  107. message_cb_ = std::move(message_cb);
  108. keys_change_cb_ = std::move(keys_change_cb);
  109. return S_OK;
  110. }
  111. // IMFContentDecryptionModuleSessionCallbacks implementation
  112. STDMETHODIMP KeyMessage(MF_MEDIAKEYSESSION_MESSAGETYPE message_type,
  113. const BYTE* message,
  114. DWORD message_size,
  115. LPCWSTR destination_url) final {
  116. DVLOG_FUNC(2) << ": message size=" << message_size;
  117. message_cb_.Run(ToCdmMessageType(message_type),
  118. std::vector<uint8_t>(message, message + message_size));
  119. return S_OK;
  120. }
  121. STDMETHODIMP KeyStatusChanged() final {
  122. DVLOG_FUNC(2);
  123. keys_change_cb_.Run();
  124. return S_OK;
  125. }
  126. private:
  127. MessageCB message_cb_;
  128. KeysChangeCB keys_change_cb_;
  129. };
  130. } // namespace
  131. MediaFoundationCdmSession::MediaFoundationCdmSession(
  132. const std::string& uma_prefix,
  133. const SessionMessageCB& session_message_cb,
  134. const SessionKeysChangeCB& session_keys_change_cb,
  135. const SessionExpirationUpdateCB& session_expiration_update_cb)
  136. : uma_prefix_(uma_prefix),
  137. session_message_cb_(session_message_cb),
  138. session_keys_change_cb_(session_keys_change_cb),
  139. session_expiration_update_cb_(session_expiration_update_cb) {
  140. DVLOG_FUNC(1);
  141. }
  142. MediaFoundationCdmSession::~MediaFoundationCdmSession() {
  143. DVLOG_FUNC(1);
  144. }
  145. HRESULT MediaFoundationCdmSession::Initialize(
  146. IMFContentDecryptionModule* mf_cdm,
  147. CdmSessionType session_type) {
  148. DVLOG_FUNC(1);
  149. ComPtr<SessionCallbacks> session_callbacks;
  150. auto weak_this = weak_factory_.GetWeakPtr();
  151. // Use BindToCurrentLoop() because the callbacks can be fired on different
  152. // threads by |mf_cdm_session_|.
  153. RETURN_IF_FAILED(MakeAndInitialize<SessionCallbacks>(
  154. &session_callbacks,
  155. BindToCurrentLoop(base::BindRepeating(
  156. &MediaFoundationCdmSession::OnSessionMessage, weak_this)),
  157. BindToCurrentLoop(base::BindRepeating(
  158. &MediaFoundationCdmSession::OnSessionKeysChange, weak_this))));
  159. // |mf_cdm_session_| holds a ref count to |session_callbacks|.
  160. RETURN_IF_FAILED(mf_cdm->CreateSession(ToMFSessionType(session_type),
  161. session_callbacks.Get(),
  162. &mf_cdm_session_));
  163. return S_OK;
  164. }
  165. HRESULT MediaFoundationCdmSession::GenerateRequest(
  166. EmeInitDataType init_data_type,
  167. const std::vector<uint8_t>& init_data,
  168. SessionIdCB session_id_cb) {
  169. DVLOG_FUNC(1);
  170. DCHECK(session_id_.empty() && !session_id_cb_);
  171. session_id_cb_ = std::move(session_id_cb);
  172. RETURN_IF_FAILED(WithUmaReported(
  173. mf_cdm_session_->GenerateRequest(
  174. InitDataTypeToString(init_data_type), init_data.data(),
  175. base::checked_cast<DWORD>(init_data.size())),
  176. "GenerateRequest"));
  177. return S_OK;
  178. }
  179. HRESULT MediaFoundationCdmSession::Load(const std::string& session_id) {
  180. DVLOG_FUNC(1);
  181. RETURN_IF_FAILED(WithUmaReported(E_NOTIMPL, "LoadSession"));
  182. return S_OK;
  183. }
  184. HRESULT
  185. MediaFoundationCdmSession::Update(const std::vector<uint8_t>& response) {
  186. DVLOG_FUNC(1);
  187. RETURN_IF_FAILED(WithUmaReported(
  188. mf_cdm_session_->Update(reinterpret_cast<const BYTE*>(response.data()),
  189. base::checked_cast<DWORD>(response.size())),
  190. "UpdateSession"));
  191. RETURN_IF_FAILED(UpdateExpirationIfNeeded());
  192. return S_OK;
  193. }
  194. HRESULT MediaFoundationCdmSession::Close() {
  195. DVLOG_FUNC(1);
  196. RETURN_IF_FAILED(WithUmaReported(mf_cdm_session_->Close(), "CloseSession"));
  197. return S_OK;
  198. }
  199. HRESULT MediaFoundationCdmSession::Remove() {
  200. DVLOG_FUNC(1);
  201. RETURN_IF_FAILED(WithUmaReported(mf_cdm_session_->Remove(), "RemoveSession"));
  202. RETURN_IF_FAILED(UpdateExpirationIfNeeded());
  203. return S_OK;
  204. }
  205. HRESULT MediaFoundationCdmSession::WithUmaReported(HRESULT hr,
  206. const std::string& api) {
  207. base::UmaHistogramSparse(uma_prefix_ + api, hr);
  208. return hr;
  209. }
  210. void MediaFoundationCdmSession::OnSessionMessage(
  211. CdmMessageType message_type,
  212. const std::vector<uint8_t>& message) {
  213. DVLOG_FUNC(2);
  214. if (session_id_.empty() && !session_id_cb_) {
  215. DLOG(ERROR) << "Unexpected session message";
  216. return;
  217. }
  218. // If |session_id_| has not been set, set it now.
  219. if (session_id_.empty() && !SetSessionId())
  220. return;
  221. DCHECK(!session_id_.empty());
  222. session_message_cb_.Run(session_id_, message_type, message);
  223. }
  224. void MediaFoundationCdmSession::OnSessionKeysChange() {
  225. DVLOG_FUNC(2);
  226. if (session_id_.empty()) {
  227. DLOG(ERROR) << "Unexpected session keys change ignored";
  228. return;
  229. }
  230. base::win::ScopedCoMem<MFMediaKeyStatus> key_statuses;
  231. UINT count = 0;
  232. if (FAILED(mf_cdm_session_->GetKeyStatuses(&key_statuses, &count))) {
  233. DLOG(ERROR) << __func__ << ": Failed to get key statuses";
  234. return;
  235. }
  236. // TODO(xhwang): Investigate whether we need to set |has_new_usable_key|.
  237. session_keys_change_cb_.Run(session_id_, /*has_new_usable_key=*/true,
  238. ToCdmKeysInfo(key_statuses.get(), count));
  239. // ScopedCoMem<MFMediaKeyStatus> only releases memory for |key_statuses|. We
  240. // need to manually release memory for |pbKeyId| here.
  241. for (UINT i = 0; i < count; ++i) {
  242. const auto& key_status = key_statuses[i];
  243. if (key_status.pbKeyId)
  244. CoTaskMemFree(key_status.pbKeyId);
  245. }
  246. }
  247. bool MediaFoundationCdmSession::SetSessionId() {
  248. DCHECK(session_id_.empty() && session_id_cb_);
  249. base::win::ScopedCoMem<wchar_t> session_id;
  250. HRESULT hr = mf_cdm_session_->GetSessionId(&session_id);
  251. if (FAILED(hr) || !session_id) {
  252. bool success = std::move(session_id_cb_).Run("");
  253. DCHECK(!success) << "Empty session ID should not be accepted";
  254. return false;
  255. }
  256. auto session_id_str = base::WideToUTF8(session_id.get());
  257. if (session_id_str.empty()) {
  258. bool success = std::move(session_id_cb_).Run("");
  259. DCHECK(!success) << "Empty session ID should not be accepted";
  260. return false;
  261. }
  262. bool success = std::move(session_id_cb_).Run(session_id_str);
  263. if (!success) {
  264. DLOG(ERROR) << "Session ID " << session_id_str << " rejected";
  265. return false;
  266. }
  267. DVLOG_FUNC(1) << "session_id_=" << session_id_str;
  268. session_id_ = session_id_str;
  269. return true;
  270. }
  271. HRESULT MediaFoundationCdmSession::UpdateExpirationIfNeeded() {
  272. DCHECK(!session_id_.empty());
  273. // Media Foundation CDM follows the EME spec where Time generally represents
  274. // an instant in time with millisecond accuracy.
  275. double new_expiration_ms = 0.0;
  276. RETURN_IF_FAILED(mf_cdm_session_->GetExpiration(&new_expiration_ms));
  277. auto new_expiration = base::Time::FromJsTime(new_expiration_ms);
  278. if (new_expiration == expiration_)
  279. return S_OK;
  280. DVLOG(2) << "New session expiration: " << new_expiration;
  281. expiration_ = new_expiration;
  282. session_expiration_update_cb_.Run(session_id_, expiration_);
  283. return S_OK;
  284. }
  285. } // namespace media