1 // Copyright 2013 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.
5 #include "media/cdm/ppapi/external_clear_key/clear_key_cdm.h"
13 #include "base/bind.h"
14 #include "base/debug/trace_event.h"
15 #include "base/logging.h"
16 #include "base/time/time.h"
17 #include "media/base/cdm_callback_promise.h"
18 #include "media/base/decoder_buffer.h"
19 #include "media/base/decrypt_config.h"
20 #include "media/cdm/json_web_key.h"
21 #include "media/cdm/ppapi/cdm_file_io_test.h"
22 #include "media/cdm/ppapi/external_clear_key/cdm_video_decoder.h"
24 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
25 #include "base/basictypes.h"
26 const int64 kNoTimestamp = kint64min;
27 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
29 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
30 #include "base/at_exit.h"
31 #include "base/files/file_path.h"
32 #include "base/path_service.h"
33 #include "media/base/media.h"
34 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_audio_decoder.h"
35 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_video_decoder.h"
37 // Include FFmpeg avformat.h for av_register_all().
39 // Temporarily disable possible loss of data warning.
40 MSVC_PUSH_DISABLE_WARNING(4244);
41 #include <libavformat/avformat.h>
45 // TODO(tomfinegan): When COMPONENT_BUILD is not defined an AtExitManager must
46 // exist before the call to InitializeFFmpegLibraries(). This should no longer
47 // be required after http://crbug.com/91970 because we'll be able to get rid of
48 // InitializeFFmpegLibraries().
49 #if !defined COMPONENT_BUILD
50 static base::AtExitManager g_at_exit_manager;
53 // TODO(tomfinegan): InitializeFFmpegLibraries() and |g_cdm_module_initialized|
54 // are required for running in the sandbox, and should no longer be required
55 // after http://crbug.com/91970 is fixed.
56 static bool InitializeFFmpegLibraries() {
57 base::FilePath file_path;
58 CHECK(PathService::Get(base::DIR_MODULE, &file_path));
59 CHECK(media::InitializeMediaLibrary(file_path));
63 static bool g_ffmpeg_lib_initialized = InitializeFFmpegLibraries();
64 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
66 const char kClearKeyCdmVersion[] = "0.1.0.1";
67 const char kExternalClearKeyKeySystem[] = "org.chromium.externalclearkey";
68 const char kExternalClearKeyDecryptOnlyKeySystem[] =
69 "org.chromium.externalclearkey.decryptonly";
70 const char kExternalClearKeyFileIOTestKeySystem[] =
71 "org.chromium.externalclearkey.fileiotest";
72 const char kExternalClearKeyCrashKeySystem[] =
73 "org.chromium.externalclearkey.crash";
75 // Constants for the enumalted session that can be loaded by LoadSession().
76 // These constants need to be in sync with
77 // chrome/test/data/media/encrypted_media_utils.js
78 const char kLoadableWebSessionId[] = "LoadableSession";
79 const char kLoadableSessionContentType[] = "video/webm";
80 const uint8 kLoadableSessionKeyId[] = "0123456789012345";
81 const uint8 kLoadableSessionKey[] =
82 {0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
83 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c};
85 const int64 kSecondsPerMinute = 60;
86 const int64 kMsPerSecond = 1000;
87 const int64 kInitialTimerDelayMs = 200;
88 const int64 kMaxTimerDelayMs = 1 * kSecondsPerMinute * kMsPerSecond;
89 // Heart beat message header. If a key message starts with |kHeartBeatHeader|,
90 // it's a heart beat message. Otherwise, it's a key request.
91 const char kHeartBeatHeader[] = "HEARTBEAT";
92 // CDM file IO test result header.
93 const char kFileIOTestResultHeader[] = "FILEIOTESTRESULT";
95 // Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is
96 // empty, an empty (end-of-stream) media::DecoderBuffer is returned.
97 static scoped_refptr<media::DecoderBuffer> CopyDecoderBufferFrom(
98 const cdm::InputBuffer& input_buffer) {
99 if (!input_buffer.data) {
100 DCHECK(!input_buffer.data_size);
101 return media::DecoderBuffer::CreateEOSBuffer();
104 // TODO(xhwang): Get rid of this copy.
105 scoped_refptr<media::DecoderBuffer> output_buffer =
106 media::DecoderBuffer::CopyFrom(input_buffer.data, input_buffer.data_size);
108 std::vector<media::SubsampleEntry> subsamples;
109 for (uint32_t i = 0; i < input_buffer.num_subsamples; ++i) {
110 media::SubsampleEntry subsample;
111 subsample.clear_bytes = input_buffer.subsamples[i].clear_bytes;
112 subsample.cypher_bytes = input_buffer.subsamples[i].cipher_bytes;
113 subsamples.push_back(subsample);
116 scoped_ptr<media::DecryptConfig> decrypt_config(new media::DecryptConfig(
117 std::string(reinterpret_cast<const char*>(input_buffer.key_id),
118 input_buffer.key_id_size),
119 std::string(reinterpret_cast<const char*>(input_buffer.iv),
120 input_buffer.iv_size),
123 output_buffer->set_decrypt_config(decrypt_config.Pass());
124 output_buffer->set_timestamp(
125 base::TimeDelta::FromMicroseconds(input_buffer.timestamp));
127 return output_buffer;
130 static std::string GetFileIOTestResultMessage(bool success) {
131 std::string message(kFileIOTestResultHeader);
132 message += success ? '1' : '0';
136 static cdm::Error ConvertException(media::MediaKeys::Exception exception_code) {
137 switch (exception_code) {
138 case media::MediaKeys::NOT_SUPPORTED_ERROR:
139 return cdm::kNotSupportedError;
140 case media::MediaKeys::INVALID_STATE_ERROR:
141 return cdm::kInvalidStateError;
142 case media::MediaKeys::INVALID_ACCESS_ERROR:
143 return cdm::kInvalidAccessError;
144 case media::MediaKeys::QUOTA_EXCEEDED_ERROR:
145 return cdm::kQuotaExceededError;
146 case media::MediaKeys::UNKNOWN_ERROR:
147 return cdm::kUnknownError;
148 case media::MediaKeys::CLIENT_ERROR:
149 return cdm::kClientError;
150 case media::MediaKeys::OUTPUT_ERROR:
151 return cdm::kOutputError;
154 return cdm::kUnknownError;
157 static media::MediaKeys::SessionType ConvertSessionType(
158 cdm::SessionType session_type) {
159 switch (session_type) {
160 case cdm::kPersistent:
161 return media::MediaKeys::PERSISTENT_SESSION;
162 case cdm::kTemporary:
163 return media::MediaKeys::TEMPORARY_SESSION;
166 return media::MediaKeys::TEMPORARY_SESSION;
169 template<typename Type>
170 class ScopedResetter {
172 explicit ScopedResetter(Type* object) : object_(object) {}
173 ~ScopedResetter() { object_->Reset(); }
179 void INITIALIZE_CDM_MODULE() {
180 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
181 DVLOG(2) << "FFmpeg libraries initialized: " << g_ffmpeg_lib_initialized;
183 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
186 void DeinitializeCdmModule() {
189 void* CreateCdmInstance(int cdm_interface_version,
190 const char* key_system, uint32_t key_system_size,
191 GetCdmHostFunc get_cdm_host_func,
193 DVLOG(1) << "CreateCdmInstance()";
195 std::string key_system_string(key_system, key_system_size);
196 if (key_system_string != kExternalClearKeyKeySystem &&
197 key_system_string != kExternalClearKeyDecryptOnlyKeySystem &&
198 key_system_string != kExternalClearKeyFileIOTestKeySystem &&
199 key_system_string != kExternalClearKeyCrashKeySystem) {
200 DVLOG(1) << "Unsupported key system:" << key_system_string;
204 if (cdm_interface_version != media::ClearKeyCdmInterface::kVersion)
207 media::ClearKeyCdmHost* host = static_cast<media::ClearKeyCdmHost*>(
208 get_cdm_host_func(media::ClearKeyCdmHost::kVersion, user_data));
212 return new media::ClearKeyCdm(host, key_system_string);
215 const char* GetCdmVersion() {
216 return kClearKeyCdmVersion;
221 ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, const std::string& key_system)
223 base::Bind(&ClearKeyCdm::OnSessionMessage, base::Unretained(this)),
224 base::Bind(&ClearKeyCdm::OnSessionClosed, base::Unretained(this)),
225 base::Bind(&ClearKeyCdm::OnSessionKeysChange,
226 base::Unretained(this))),
228 key_system_(key_system),
229 timer_delay_ms_(kInitialTimerDelayMs),
230 heartbeat_timer_set_(false) {
231 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
233 bits_per_channel_ = 0;
234 samples_per_second_ = 0;
235 output_timestamp_base_in_microseconds_ = kNoTimestamp;
236 total_samples_generated_ = 0;
237 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
240 ClearKeyCdm::~ClearKeyCdm() {}
242 void ClearKeyCdm::CreateSession(uint32 promise_id,
243 const char* init_data_type,
244 uint32 init_data_type_size,
245 const uint8* init_data,
246 uint32 init_data_size,
247 cdm::SessionType session_type) {
248 DVLOG(1) << __FUNCTION__;
250 scoped_ptr<media::NewSessionCdmPromise> promise(
251 new media::CdmCallbackPromise<std::string>(
252 base::Bind(&ClearKeyCdm::OnSessionCreated,
253 base::Unretained(this),
255 base::Bind(&ClearKeyCdm::OnPromiseFailed,
256 base::Unretained(this),
258 decryptor_.CreateSession(std::string(init_data_type, init_data_type_size),
261 ConvertSessionType(session_type),
264 if (key_system_ == kExternalClearKeyFileIOTestKeySystem)
268 // Loads a emulated stored session. Currently only |kLoadableWebSessionId|
269 // (containing a |kLoadableSessionKey| for |kLoadableSessionKeyId|) is
271 void ClearKeyCdm::LoadSession(uint32 promise_id,
272 const char* web_session_id,
273 uint32_t web_session_id_length) {
274 DVLOG(1) << __FUNCTION__;
276 if (std::string(kLoadableWebSessionId) !=
277 std::string(web_session_id, web_session_id_length)) {
278 // TODO(jrummell): This should be resolved with undefined, not rejected.
279 std::string message("Incorrect session id specified for LoadSession().");
280 host_->OnRejectPromise(promise_id,
281 cdm::kInvalidAccessError,
288 scoped_ptr<media::NewSessionCdmPromise> promise(
289 new media::CdmCallbackPromise<std::string>(
290 base::Bind(&ClearKeyCdm::OnSessionLoaded,
291 base::Unretained(this),
293 base::Bind(&ClearKeyCdm::OnPromiseFailed,
294 base::Unretained(this),
296 decryptor_.CreateSession(std::string(kLoadableSessionContentType),
299 MediaKeys::TEMPORARY_SESSION,
303 void ClearKeyCdm::UpdateSession(uint32 promise_id,
304 const char* web_session_id,
305 uint32_t web_session_id_length,
306 const uint8* response,
307 uint32 response_size) {
308 DVLOG(1) << __FUNCTION__;
309 std::string web_session_str(web_session_id, web_session_id_length);
311 scoped_ptr<media::SimpleCdmPromise> promise(new media::CdmCallbackPromise<>(
312 base::Bind(&ClearKeyCdm::OnSessionUpdated,
313 base::Unretained(this),
317 &ClearKeyCdm::OnPromiseFailed, base::Unretained(this), promise_id)));
318 decryptor_.UpdateSession(
319 web_session_str, response, response_size, promise.Pass());
321 if (!heartbeat_timer_set_) {
322 ScheduleNextHeartBeat();
323 heartbeat_timer_set_ = true;
327 void ClearKeyCdm::CloseSession(uint32 promise_id,
328 const char* web_session_id,
329 uint32_t web_session_id_length) {
330 DVLOG(1) << __FUNCTION__;
331 std::string web_session_str(web_session_id, web_session_id_length);
333 scoped_ptr<media::SimpleCdmPromise> promise(new media::CdmCallbackPromise<>(
335 &ClearKeyCdm::OnPromiseResolved, base::Unretained(this), promise_id),
337 &ClearKeyCdm::OnPromiseFailed, base::Unretained(this), promise_id)));
338 decryptor_.CloseSession(web_session_str, promise.Pass());
341 void ClearKeyCdm::RemoveSession(uint32 promise_id,
342 const char* web_session_id,
343 uint32_t web_session_id_length) {
344 DVLOG(1) << __FUNCTION__;
345 // RemoveSession only allowed for persistent sessions.
346 bool is_persistent_session =
347 std::string(kLoadableWebSessionId) ==
348 std::string(web_session_id, web_session_id_length);
349 if (is_persistent_session) {
350 std::string web_session_str(web_session_id, web_session_id_length);
352 scoped_ptr<media::SimpleCdmPromise> promise(new media::CdmCallbackPromise<>(
353 base::Bind(&ClearKeyCdm::OnPromiseResolved,
354 base::Unretained(this),
356 base::Bind(&ClearKeyCdm::OnPromiseFailed,
357 base::Unretained(this),
359 decryptor_.RemoveSession(web_session_str, promise.Pass());
361 // TODO(jrummell): This should be a DCHECK once blink does the proper
363 std::string message("Not supported for non-persistent sessions.");
364 host_->OnRejectPromise(promise_id,
365 cdm::kInvalidAccessError,
372 void ClearKeyCdm::SetServerCertificate(uint32 promise_id,
373 const uint8_t* server_certificate_data,
374 uint32_t server_certificate_data_size) {
375 // ClearKey doesn't use a server certificate.
376 host_->OnResolvePromise(promise_id);
379 void ClearKeyCdm::GetUsableKeyIds(uint32_t promise_id,
380 const char* web_session_id,
381 uint32_t web_session_id_length) {
382 std::string web_session_str(web_session_id, web_session_id_length);
383 scoped_ptr<media::KeyIdsPromise> promise(
384 new media::CdmCallbackPromise<KeyIdsVector>(
385 base::Bind(&ClearKeyCdm::OnUsableKeyIdsObtained,
386 base::Unretained(this),
388 base::Bind(&ClearKeyCdm::OnPromiseFailed,
389 base::Unretained(this),
391 decryptor_.GetUsableKeyIds(web_session_str, promise.Pass());
394 void ClearKeyCdm::TimerExpired(void* context) {
395 if (context == &session_id_for_emulated_loadsession_) {
396 LoadLoadableSession();
400 DCHECK(heartbeat_timer_set_);
401 std::string heartbeat_message;
402 if (!next_heartbeat_message_.empty() &&
403 context == &next_heartbeat_message_[0]) {
404 heartbeat_message = next_heartbeat_message_;
406 heartbeat_message = "ERROR: Invalid timer context found!";
409 // This URL is only used for testing the code path for defaultURL.
410 // There is no service at this URL, so applications should ignore it.
411 const char url[] = "http://test.externalclearkey.chromium.org";
413 host_->OnSessionMessage(last_session_id_.data(),
414 last_session_id_.length(),
415 heartbeat_message.data(),
416 heartbeat_message.length(),
420 ScheduleNextHeartBeat();
423 static void CopyDecryptResults(
424 media::Decryptor::Status* status_copy,
425 scoped_refptr<media::DecoderBuffer>* buffer_copy,
426 media::Decryptor::Status status,
427 const scoped_refptr<media::DecoderBuffer>& buffer) {
428 *status_copy = status;
429 *buffer_copy = buffer;
432 cdm::Status ClearKeyCdm::Decrypt(const cdm::InputBuffer& encrypted_buffer,
433 cdm::DecryptedBlock* decrypted_block) {
434 DVLOG(1) << "Decrypt()";
435 DCHECK(encrypted_buffer.data);
437 scoped_refptr<media::DecoderBuffer> buffer;
438 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
440 if (status != cdm::kSuccess)
443 DCHECK(buffer->data());
444 decrypted_block->SetDecryptedBuffer(
445 host_->Allocate(buffer->data_size()));
446 memcpy(reinterpret_cast<void*>(decrypted_block->DecryptedBuffer()->Data()),
448 buffer->data_size());
449 decrypted_block->DecryptedBuffer()->SetSize(buffer->data_size());
450 decrypted_block->SetTimestamp(buffer->timestamp().InMicroseconds());
452 return cdm::kSuccess;
455 cdm::Status ClearKeyCdm::InitializeAudioDecoder(
456 const cdm::AudioDecoderConfig& audio_decoder_config) {
457 if (key_system_ == kExternalClearKeyDecryptOnlyKeySystem)
458 return cdm::kSessionError;
460 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
462 audio_decoder_.reset(new media::FFmpegCdmAudioDecoder(host_));
464 if (!audio_decoder_->Initialize(audio_decoder_config))
465 return cdm::kSessionError;
467 return cdm::kSuccess;
468 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
469 channel_count_ = audio_decoder_config.channel_count;
470 bits_per_channel_ = audio_decoder_config.bits_per_channel;
471 samples_per_second_ = audio_decoder_config.samples_per_second;
472 return cdm::kSuccess;
475 return cdm::kSessionError;
476 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
479 cdm::Status ClearKeyCdm::InitializeVideoDecoder(
480 const cdm::VideoDecoderConfig& video_decoder_config) {
481 if (key_system_ == kExternalClearKeyDecryptOnlyKeySystem)
482 return cdm::kSessionError;
484 if (video_decoder_ && video_decoder_->is_initialized()) {
485 DCHECK(!video_decoder_->is_initialized());
486 return cdm::kSessionError;
489 // Any uninitialized decoder will be replaced.
490 video_decoder_ = CreateVideoDecoder(host_, video_decoder_config);
492 return cdm::kSessionError;
494 return cdm::kSuccess;
497 void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type) {
498 DVLOG(1) << "ResetDecoder()";
499 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
500 switch (decoder_type) {
501 case cdm::kStreamTypeVideo:
502 video_decoder_->Reset();
504 case cdm::kStreamTypeAudio:
505 audio_decoder_->Reset();
508 NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType";
510 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
511 if (decoder_type == cdm::kStreamTypeAudio) {
512 output_timestamp_base_in_microseconds_ = kNoTimestamp;
513 total_samples_generated_ = 0;
515 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
518 void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type) {
519 DVLOG(1) << "DeinitializeDecoder()";
520 switch (decoder_type) {
521 case cdm::kStreamTypeVideo:
522 video_decoder_->Deinitialize();
524 case cdm::kStreamTypeAudio:
525 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
526 audio_decoder_->Deinitialize();
527 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
528 output_timestamp_base_in_microseconds_ = kNoTimestamp;
529 total_samples_generated_ = 0;
533 NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType";
537 cdm::Status ClearKeyCdm::DecryptAndDecodeFrame(
538 const cdm::InputBuffer& encrypted_buffer,
539 cdm::VideoFrame* decoded_frame) {
540 DVLOG(1) << "DecryptAndDecodeFrame()";
541 TRACE_EVENT0("media", "ClearKeyCdm::DecryptAndDecodeFrame");
543 scoped_refptr<media::DecoderBuffer> buffer;
544 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
546 if (status != cdm::kSuccess)
549 const uint8_t* data = NULL;
551 int64_t timestamp = 0;
552 if (!buffer->end_of_stream()) {
553 data = buffer->data();
554 size = buffer->data_size();
555 timestamp = encrypted_buffer.timestamp;
558 return video_decoder_->DecodeFrame(data, size, timestamp, decoded_frame);
561 cdm::Status ClearKeyCdm::DecryptAndDecodeSamples(
562 const cdm::InputBuffer& encrypted_buffer,
563 cdm::AudioFrames* audio_frames) {
564 DVLOG(1) << "DecryptAndDecodeSamples()";
566 // Trigger a crash on purpose for testing purpose.
567 if (key_system_ == kExternalClearKeyCrashKeySystem)
570 scoped_refptr<media::DecoderBuffer> buffer;
571 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer);
573 if (status != cdm::kSuccess)
576 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
577 const uint8_t* data = NULL;
579 int64_t timestamp = 0;
580 if (!buffer->end_of_stream()) {
581 data = buffer->data();
582 size = buffer->data_size();
583 timestamp = encrypted_buffer.timestamp;
586 return audio_decoder_->DecodeBuffer(data, size, timestamp, audio_frames);
587 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
588 int64 timestamp_in_microseconds = kNoTimestamp;
589 if (!buffer->end_of_stream()) {
590 timestamp_in_microseconds = buffer->GetTimestamp().InMicroseconds();
591 DCHECK(timestamp_in_microseconds != kNoTimestamp);
593 return GenerateFakeAudioFrames(timestamp_in_microseconds, audio_frames);
595 return cdm::kSuccess;
596 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
599 void ClearKeyCdm::Destroy() {
600 DVLOG(1) << "Destroy()";
604 void ClearKeyCdm::ScheduleNextHeartBeat() {
605 // Prepare the next heartbeat message and set timer.
606 std::ostringstream msg_stream;
607 msg_stream << kHeartBeatHeader << " from ClearKey CDM set at time "
608 << host_->GetCurrentWallTime() << ".";
609 next_heartbeat_message_ = msg_stream.str();
611 host_->SetTimer(timer_delay_ms_, &next_heartbeat_message_[0]);
613 // Use a smaller timer delay at start-up to facilitate testing. Increase the
614 // timer delay up to a limit to avoid message spam.
615 if (timer_delay_ms_ < kMaxTimerDelayMs)
616 timer_delay_ms_ = std::min(2 * timer_delay_ms_, kMaxTimerDelayMs);
619 cdm::Status ClearKeyCdm::DecryptToMediaDecoderBuffer(
620 const cdm::InputBuffer& encrypted_buffer,
621 scoped_refptr<media::DecoderBuffer>* decrypted_buffer) {
622 DCHECK(decrypted_buffer);
623 scoped_refptr<media::DecoderBuffer> buffer =
624 CopyDecoderBufferFrom(encrypted_buffer);
626 if (buffer->end_of_stream()) {
627 *decrypted_buffer = buffer;
628 return cdm::kSuccess;
631 // Callback is called synchronously, so we can use variables on the stack.
632 media::Decryptor::Status status = media::Decryptor::kError;
633 // The AesDecryptor does not care what the stream type is. Pass kVideo
634 // for both audio and video decryption.
636 media::Decryptor::kVideo,
638 base::Bind(&CopyDecryptResults, &status, decrypted_buffer));
640 if (status == media::Decryptor::kError)
641 return cdm::kDecryptError;
643 if (status == media::Decryptor::kNoKey)
646 DCHECK_EQ(status, media::Decryptor::kSuccess);
647 return cdm::kSuccess;
650 void ClearKeyCdm::OnPlatformChallengeResponse(
651 const cdm::PlatformChallengeResponse& response) {
655 void ClearKeyCdm::OnQueryOutputProtectionStatus(
656 uint32_t link_mask, uint32_t output_protection_mask) {
660 void ClearKeyCdm::LoadLoadableSession() {
661 std::string jwk_set = GenerateJWKSet(kLoadableSessionKey,
662 sizeof(kLoadableSessionKey),
663 kLoadableSessionKeyId,
664 sizeof(kLoadableSessionKeyId) - 1);
665 // TODO(xhwang): This triggers OnSessionUpdated(). For prefixed EME support,
666 // this is okay. Check WD EME support.
667 scoped_ptr<media::SimpleCdmPromise> promise(new media::CdmCallbackPromise<>(
668 base::Bind(&ClearKeyCdm::OnSessionUpdated,
669 base::Unretained(this),
670 promise_id_for_emulated_loadsession_,
671 session_id_for_emulated_loadsession_),
672 base::Bind(&ClearKeyCdm::OnPromiseFailed,
673 base::Unretained(this),
674 promise_id_for_emulated_loadsession_)));
675 decryptor_.UpdateSession(session_id_for_emulated_loadsession_,
676 reinterpret_cast<const uint8*>(jwk_set.data()),
681 void ClearKeyCdm::OnSessionMessage(const std::string& web_session_id,
682 const std::vector<uint8>& message,
683 const GURL& destination_url) {
684 DVLOG(1) << "OnSessionMessage: " << message.size();
686 // Ignore the message when we are waiting to update the loadable session.
687 if (web_session_id == session_id_for_emulated_loadsession_)
690 // OnSessionMessage() only called during CreateSession(), so no promise
691 // involved (OnSessionCreated() called to resolve the CreateSession()
693 host_->OnSessionMessage(web_session_id.data(),
694 web_session_id.length(),
695 reinterpret_cast<const char*>(message.data()),
697 destination_url.spec().data(),
698 destination_url.spec().size());
701 void ClearKeyCdm::OnSessionKeysChange(const std::string& web_session_id,
702 bool has_additional_usable_key) {
703 // Ignore the message when we are waiting to update the loadable session.
704 if (web_session_id == session_id_for_emulated_loadsession_)
707 host_->OnSessionUsableKeysChange(web_session_id.data(),
708 web_session_id.length(),
709 has_additional_usable_key);
712 void ClearKeyCdm::OnSessionClosed(const std::string& web_session_id) {
713 host_->OnSessionClosed(web_session_id.data(), web_session_id.length());
716 void ClearKeyCdm::OnSessionCreated(uint32 promise_id,
717 const std::string& web_session_id) {
718 // Save the latest session ID for heartbeat and file IO test messages.
719 last_session_id_ = web_session_id;
721 host_->OnResolveNewSessionPromise(
722 promise_id, web_session_id.data(), web_session_id.length());
725 void ClearKeyCdm::OnSessionLoaded(uint32 promise_id,
726 const std::string& web_session_id) {
727 // Save the latest session ID for heartbeat and file IO test messages.
728 last_session_id_ = web_session_id;
730 // |decryptor_| created some session as |web_session_id|, but going forward
731 // we need to map that to |kLoadableWebSessionId|, as that is what callers
733 session_id_for_emulated_loadsession_ = web_session_id;
735 // Delay LoadLoadableSession() to test the case where Decrypt*() calls are
736 // made before the session is fully loaded.
737 const int64 kDelayToLoadSessionMs = 500;
739 // Defer resolving the promise until the session is loaded.
740 promise_id_for_emulated_loadsession_ = promise_id;
742 // Use the address of |session_id_for_emulated_loadsession_| as the timer
743 // context so that we can call LoadLoadableSession() when the timer expires.
744 host_->SetTimer(kDelayToLoadSessionMs, &session_id_for_emulated_loadsession_);
747 void ClearKeyCdm::OnSessionUpdated(uint32 promise_id,
748 const std::string& web_session_id) {
749 // UpdateSession() may be called to finish loading sessions, so handle
751 if (web_session_id == session_id_for_emulated_loadsession_) {
752 session_id_for_emulated_loadsession_ = std::string();
753 // |promise_id| is the LoadSession() promise, so resolve appropriately.
754 host_->OnResolveNewSessionPromise(
755 promise_id, kLoadableWebSessionId, strlen(kLoadableWebSessionId));
756 // Generate the UsableKeys event now that the session is "loaded".
757 host_->OnSessionUsableKeysChange(
758 kLoadableWebSessionId, strlen(kLoadableWebSessionId), true);
762 host_->OnResolvePromise(promise_id);
765 void ClearKeyCdm::OnUsableKeyIdsObtained(uint32 promise_id,
766 const KeyIdsVector& key_ids) {
767 scoped_ptr<cdm::BinaryData[]> result(new cdm::BinaryData[key_ids.size()]);
768 for (uint32 i = 0; i < key_ids.size(); ++i) {
769 result[i].data = key_ids[i].data();
770 result[i].length = key_ids[i].size();
772 host_->OnResolveKeyIdsPromise(promise_id, result.get(), key_ids.size());
775 void ClearKeyCdm::OnPromiseResolved(uint32 promise_id) {
776 host_->OnResolvePromise(promise_id);
779 void ClearKeyCdm::OnPromiseFailed(uint32 promise_id,
780 MediaKeys::Exception exception_code,
782 const std::string& error_message) {
783 host_->OnRejectPromise(promise_id,
784 ConvertException(exception_code),
786 error_message.data(),
787 error_message.length());
790 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
791 int64 ClearKeyCdm::CurrentTimeStampInMicroseconds() const {
792 return output_timestamp_base_in_microseconds_ +
793 base::Time::kMicrosecondsPerSecond *
794 total_samples_generated_ / samples_per_second_;
797 int ClearKeyCdm::GenerateFakeAudioFramesFromDuration(
798 int64 duration_in_microseconds,
799 cdm::AudioFrames* audio_frames) const {
800 int64 samples_to_generate = static_cast<double>(samples_per_second_) *
801 duration_in_microseconds / base::Time::kMicrosecondsPerSecond + 0.5;
802 if (samples_to_generate <= 0)
805 int64 bytes_per_sample = channel_count_ * bits_per_channel_ / 8;
806 // |frame_size| must be a multiple of |bytes_per_sample|.
807 int64 frame_size = bytes_per_sample * samples_to_generate;
809 int64 timestamp = CurrentTimeStampInMicroseconds();
811 const int kHeaderSize = sizeof(timestamp) + sizeof(frame_size);
812 audio_frames->SetFrameBuffer(host_->Allocate(kHeaderSize + frame_size));
813 uint8_t* data = audio_frames->FrameBuffer()->Data();
815 memcpy(data, ×tamp, sizeof(timestamp));
816 data += sizeof(timestamp);
817 memcpy(data, &frame_size, sizeof(frame_size));
818 data += sizeof(frame_size);
819 // You won't hear anything because we have all zeros here. But the video
820 // should play just fine!
821 memset(data, 0, frame_size);
823 audio_frames->FrameBuffer()->SetSize(kHeaderSize + frame_size);
825 return samples_to_generate;
828 cdm::Status ClearKeyCdm::GenerateFakeAudioFrames(
829 int64 timestamp_in_microseconds,
830 cdm::AudioFrames* audio_frames) {
831 if (timestamp_in_microseconds == kNoTimestamp)
832 return cdm::kNeedMoreData;
834 // Return kNeedMoreData for the first frame because duration is unknown.
835 if (output_timestamp_base_in_microseconds_ == kNoTimestamp) {
836 output_timestamp_base_in_microseconds_ = timestamp_in_microseconds;
837 return cdm::kNeedMoreData;
840 int samples_generated = GenerateFakeAudioFramesFromDuration(
841 timestamp_in_microseconds - CurrentTimeStampInMicroseconds(),
843 total_samples_generated_ += samples_generated;
845 return samples_generated == 0 ? cdm::kNeedMoreData : cdm::kSuccess;
847 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
849 void ClearKeyCdm::StartFileIOTest() {
850 file_io_test_runner_.reset(new FileIOTestRunner(
851 base::Bind(&ClearKeyCdmHost::CreateFileIO, base::Unretained(host_))));
852 file_io_test_runner_->RunAllTests(
853 base::Bind(&ClearKeyCdm::OnFileIOTestComplete, base::Unretained(this)));
856 void ClearKeyCdm::OnFileIOTestComplete(bool success) {
857 DVLOG(1) << __FUNCTION__ << ": " << success;
858 std::string message = GetFileIOTestResultMessage(success);
859 host_->OnSessionMessage(last_session_id_.data(),
860 last_session_id_.length(),
865 file_io_test_runner_.reset();