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/logging.h"
15 #include "base/stl_util.h"
16 #include "base/time/time.h"
17 #include "base/trace_event/trace_event.h"
18 #include "media/base/cdm_callback_promise.h"
19 #include "media/base/cdm_key_information.h"
20 #include "media/base/decoder_buffer.h"
21 #include "media/base/decrypt_config.h"
22 #include "media/base/key_systems.h"
23 #include "media/cdm/json_web_key.h"
24 #include "media/cdm/ppapi/cdm_file_io_test.h"
25 #include "media/cdm/ppapi/external_clear_key/cdm_video_decoder.h"
28 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
29 #include "base/basictypes.h"
30 const int64 kNoTimestamp
= kint64min
;
31 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
33 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
34 #include "base/at_exit.h"
35 #include "base/files/file_path.h"
36 #include "base/path_service.h"
37 #include "media/base/media.h"
38 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_audio_decoder.h"
39 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_video_decoder.h"
41 // Include FFmpeg avformat.h for av_register_all().
43 // Temporarily disable possible loss of data warning.
44 MSVC_PUSH_DISABLE_WARNING(4244);
45 #include <libavformat/avformat.h>
49 // TODO(tomfinegan): When COMPONENT_BUILD is not defined an AtExitManager must
50 // exist before the call to InitializeFFmpegLibraries(). This should no longer
51 // be required after http://crbug.com/91970 because we'll be able to get rid of
52 // InitializeFFmpegLibraries().
53 #if !defined COMPONENT_BUILD
54 static base::AtExitManager g_at_exit_manager
;
57 // TODO(tomfinegan): InitializeFFmpegLibraries() and |g_cdm_module_initialized|
58 // are required for running in the sandbox, and should no longer be required
59 // after http://crbug.com/91970 is fixed.
60 static bool InitializeFFmpegLibraries() {
61 base::FilePath file_path
;
62 CHECK(PathService::Get(base::DIR_MODULE
, &file_path
));
63 CHECK(media::InitializeMediaLibrary(file_path
));
67 static bool g_ffmpeg_lib_initialized
= InitializeFFmpegLibraries();
68 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
70 const char kClearKeyCdmVersion
[] = "0.1.0.1";
71 const char kExternalClearKeyKeySystem
[] = "org.chromium.externalclearkey";
72 const char kExternalClearKeyDecryptOnlyKeySystem
[] =
73 "org.chromium.externalclearkey.decryptonly";
74 const char kExternalClearKeyFileIOTestKeySystem
[] =
75 "org.chromium.externalclearkey.fileiotest";
76 const char kExternalClearKeyCrashKeySystem
[] =
77 "org.chromium.externalclearkey.crash";
79 // Constants for the enumalted session that can be loaded by LoadSession().
80 // These constants need to be in sync with
81 // chrome/test/data/media/encrypted_media_utils.js
82 const char kLoadableSessionId
[] = "LoadableSession";
83 const uint8 kLoadableSessionKeyId
[] = "0123456789012345";
84 const uint8 kLoadableSessionKey
[] =
85 {0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
86 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c};
88 const int64 kSecondsPerMinute
= 60;
89 const int64 kMsPerSecond
= 1000;
90 const int64 kInitialTimerDelayMs
= 200;
91 const int64 kMaxTimerDelayMs
= 1 * kSecondsPerMinute
* kMsPerSecond
;
92 // Renewal message header. For prefixed EME, if a key message starts with
93 // |kRenewalHeader|, it's a renewal message. Otherwise, it's a key request.
94 // FIXME(jrummell): Remove this once prefixed EME goes away.
95 const char kRenewalHeader
[] = "RENEWAL";
96 // CDM file IO test result header.
97 const char kFileIOTestResultHeader
[] = "FILEIOTESTRESULT";
99 // Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is
100 // empty, an empty (end-of-stream) media::DecoderBuffer is returned.
101 static scoped_refptr
<media::DecoderBuffer
> CopyDecoderBufferFrom(
102 const cdm::InputBuffer
& input_buffer
) {
103 if (!input_buffer
.data
) {
104 DCHECK(!input_buffer
.data_size
);
105 return media::DecoderBuffer::CreateEOSBuffer();
108 // TODO(xhwang): Get rid of this copy.
109 scoped_refptr
<media::DecoderBuffer
> output_buffer
=
110 media::DecoderBuffer::CopyFrom(input_buffer
.data
, input_buffer
.data_size
);
112 std::vector
<media::SubsampleEntry
> subsamples
;
113 for (uint32_t i
= 0; i
< input_buffer
.num_subsamples
; ++i
) {
114 subsamples
.push_back(
115 media::SubsampleEntry(input_buffer
.subsamples
[i
].clear_bytes
,
116 input_buffer
.subsamples
[i
].cipher_bytes
));
119 scoped_ptr
<media::DecryptConfig
> decrypt_config(new media::DecryptConfig(
120 std::string(reinterpret_cast<const char*>(input_buffer
.key_id
),
121 input_buffer
.key_id_size
),
122 std::string(reinterpret_cast<const char*>(input_buffer
.iv
),
123 input_buffer
.iv_size
),
126 output_buffer
->set_decrypt_config(decrypt_config
.Pass());
127 output_buffer
->set_timestamp(
128 base::TimeDelta::FromMicroseconds(input_buffer
.timestamp
));
130 return output_buffer
;
133 static std::string
GetFileIOTestResultMessage(bool success
) {
134 std::string
message(kFileIOTestResultHeader
);
135 message
+= success
? '1' : '0';
139 static cdm::Error
ConvertException(media::MediaKeys::Exception exception_code
) {
140 switch (exception_code
) {
141 case media::MediaKeys::NOT_SUPPORTED_ERROR
:
142 return cdm::kNotSupportedError
;
143 case media::MediaKeys::INVALID_STATE_ERROR
:
144 return cdm::kInvalidStateError
;
145 case media::MediaKeys::INVALID_ACCESS_ERROR
:
146 return cdm::kInvalidAccessError
;
147 case media::MediaKeys::QUOTA_EXCEEDED_ERROR
:
148 return cdm::kQuotaExceededError
;
149 case media::MediaKeys::UNKNOWN_ERROR
:
150 return cdm::kUnknownError
;
151 case media::MediaKeys::CLIENT_ERROR
:
152 return cdm::kClientError
;
153 case media::MediaKeys::OUTPUT_ERROR
:
154 return cdm::kOutputError
;
157 return cdm::kUnknownError
;
160 static media::MediaKeys::SessionType
ConvertSessionType(
161 cdm::SessionType session_type
) {
162 switch (session_type
) {
163 case cdm::kTemporary
:
164 return media::MediaKeys::TEMPORARY_SESSION
;
165 case cdm::kPersistentLicense
:
166 return media::MediaKeys::PERSISTENT_LICENSE_SESSION
;
167 case cdm::kPersistentKeyRelease
:
168 return media::MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION
;
171 return media::MediaKeys::TEMPORARY_SESSION
;
174 static media::EmeInitDataType
ConvertInitDataType(
175 cdm::InitDataType init_data_type
) {
176 switch (init_data_type
) {
178 return media::EmeInitDataType::CENC
;
180 return media::EmeInitDataType::KEYIDS
;
182 return media::EmeInitDataType::WEBM
;
185 return media::EmeInitDataType::UNKNOWN
;
188 cdm::KeyStatus
ConvertKeyStatus(media::CdmKeyInformation::KeyStatus status
) {
190 case media::CdmKeyInformation::KeyStatus::USABLE
:
192 case media::CdmKeyInformation::KeyStatus::INTERNAL_ERROR
:
193 return cdm::kInternalError
;
194 case media::CdmKeyInformation::KeyStatus::EXPIRED
:
195 return cdm::kExpired
;
196 case media::CdmKeyInformation::KeyStatus::OUTPUT_NOT_ALLOWED
:
197 return cdm::kOutputNotAllowed
;
198 case media::CdmKeyInformation::KeyStatus::OUTPUT_DOWNSCALED
:
199 return cdm::kOutputDownscaled
;
200 case media::CdmKeyInformation::KeyStatus::KEY_STATUS_PENDING
:
201 return cdm::kStatusPending
;
204 return cdm::kInternalError
;
207 // Shallow copy all the key information from |keys_info| into |keys_vector|.
208 // |keys_vector| is only valid for the lifetime of |keys_info| because it
209 // contains pointers into the latter.
210 void ConvertCdmKeysInfo(const std::vector
<media::CdmKeyInformation
*>& keys_info
,
211 std::vector
<cdm::KeyInformation
>* keys_vector
) {
212 keys_vector
->reserve(keys_info
.size());
213 for (const auto& key_info
: keys_info
) {
214 cdm::KeyInformation key
;
215 key
.key_id
= vector_as_array(&key_info
->key_id
);
216 key
.key_id_size
= key_info
->key_id
.size();
217 key
.status
= ConvertKeyStatus(key_info
->status
);
218 key
.system_code
= key_info
->system_code
;
219 keys_vector
->push_back(key
);
223 template<typename Type
>
224 class ScopedResetter
{
226 explicit ScopedResetter(Type
* object
) : object_(object
) {}
227 ~ScopedResetter() { object_
->Reset(); }
233 void INITIALIZE_CDM_MODULE() {
234 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
235 DVLOG(2) << "FFmpeg libraries initialized: " << g_ffmpeg_lib_initialized
;
237 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
240 void DeinitializeCdmModule() {
243 void* CreateCdmInstance(int cdm_interface_version
,
244 const char* key_system
, uint32_t key_system_size
,
245 GetCdmHostFunc get_cdm_host_func
,
247 DVLOG(1) << "CreateCdmInstance()";
249 std::string
key_system_string(key_system
, key_system_size
);
250 if (key_system_string
!= kExternalClearKeyKeySystem
&&
251 key_system_string
!= kExternalClearKeyDecryptOnlyKeySystem
&&
252 key_system_string
!= kExternalClearKeyFileIOTestKeySystem
&&
253 key_system_string
!= kExternalClearKeyCrashKeySystem
) {
254 DVLOG(1) << "Unsupported key system:" << key_system_string
;
258 if (cdm_interface_version
!= media::ClearKeyCdmInterface::kVersion
)
261 media::ClearKeyCdmHost
* host
= static_cast<media::ClearKeyCdmHost
*>(
262 get_cdm_host_func(media::ClearKeyCdmHost::kVersion
, user_data
));
266 // TODO(jrummell): Obtain the proper origin for this instance.
267 return new media::ClearKeyCdm(host
, key_system_string
, GURL::EmptyGURL());
270 const char* GetCdmVersion() {
271 return kClearKeyCdmVersion
;
276 ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost
* host
,
277 const std::string
& key_system
,
281 base::Bind(&ClearKeyCdm::OnSessionMessage
, base::Unretained(this)),
282 base::Bind(&ClearKeyCdm::OnSessionClosed
, base::Unretained(this)),
283 base::Bind(&ClearKeyCdm::OnSessionKeysChange
,
284 base::Unretained(this))),
286 key_system_(key_system
),
287 has_received_keys_change_event_for_emulated_loadsession_(false),
288 timer_delay_ms_(kInitialTimerDelayMs
),
289 renewal_timer_set_(false) {
290 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
292 bits_per_channel_
= 0;
293 samples_per_second_
= 0;
294 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
295 total_samples_generated_
= 0;
296 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
299 ClearKeyCdm::~ClearKeyCdm() {}
301 void ClearKeyCdm::Initialize(bool /* allow_distinctive_identifier */,
302 bool /* allow_persistent_state */) {
303 // Implementation doesn't use distinctive identifier nor save persistent data,
304 // so nothing to do with these values.
307 void ClearKeyCdm::CreateSessionAndGenerateRequest(
309 cdm::SessionType session_type
,
310 cdm::InitDataType init_data_type
,
311 const uint8
* init_data
,
312 uint32 init_data_size
) {
313 DVLOG(1) << __FUNCTION__
;
315 scoped_ptr
<media::NewSessionCdmPromise
> promise(
316 new media::CdmCallbackPromise
<std::string
>(
317 base::Bind(&ClearKeyCdm::OnSessionCreated
,
318 base::Unretained(this),
320 base::Bind(&ClearKeyCdm::OnPromiseFailed
,
321 base::Unretained(this),
323 decryptor_
.CreateSessionAndGenerateRequest(
324 ConvertSessionType(session_type
), ConvertInitDataType(init_data_type
),
325 init_data
, init_data_size
, promise
.Pass());
327 if (key_system_
== kExternalClearKeyFileIOTestKeySystem
)
331 // Loads a emulated stored session. Currently only |kLoadableSessionId|
332 // (containing a |kLoadableSessionKey| for |kLoadableSessionKeyId|) is
334 void ClearKeyCdm::LoadSession(uint32 promise_id
,
335 cdm::SessionType session_type
,
336 const char* session_id
,
337 uint32_t session_id_length
) {
338 DVLOG(1) << __FUNCTION__
;
339 DCHECK_EQ(session_type
, cdm::kPersistentLicense
);
341 if (std::string(kLoadableSessionId
) !=
342 std::string(session_id
, session_id_length
)) {
343 // TODO(jrummell): This should be resolved with undefined, not rejected.
344 std::string
message("Incorrect session id specified for LoadSession().");
345 host_
->OnRejectPromise(promise_id
,
346 cdm::kInvalidAccessError
,
353 // Only allowed to successfully load this session once.
354 DCHECK(session_id_for_emulated_loadsession_
.empty());
356 scoped_ptr
<media::NewSessionCdmPromise
> promise(
357 new media::CdmCallbackPromise
<std::string
>(
358 base::Bind(&ClearKeyCdm::OnSessionLoaded
,
359 base::Unretained(this),
361 base::Bind(&ClearKeyCdm::OnPromiseFailed
,
362 base::Unretained(this),
364 decryptor_
.CreateSessionAndGenerateRequest(MediaKeys::TEMPORARY_SESSION
,
365 EmeInitDataType::WEBM
, NULL
, 0,
369 void ClearKeyCdm::UpdateSession(uint32 promise_id
,
370 const char* session_id
,
371 uint32_t session_id_length
,
372 const uint8
* response
,
373 uint32 response_size
) {
374 DVLOG(1) << __FUNCTION__
;
375 std::string
web_session_str(session_id
, session_id_length
);
377 // If updating the loadable session, use the actual session id generated.
378 if (web_session_str
== std::string(kLoadableSessionId
))
379 web_session_str
= session_id_for_emulated_loadsession_
;
381 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
382 base::Bind(&ClearKeyCdm::OnPromiseResolved
, base::Unretained(this),
384 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
386 decryptor_
.UpdateSession(
387 web_session_str
, response
, response_size
, promise
.Pass());
389 if (!renewal_timer_set_
) {
390 ScheduleNextRenewal();
391 renewal_timer_set_
= true;
395 void ClearKeyCdm::CloseSession(uint32 promise_id
,
396 const char* session_id
,
397 uint32_t session_id_length
) {
398 DVLOG(1) << __FUNCTION__
;
399 std::string
web_session_str(session_id
, session_id_length
);
401 // If closing the loadable session, use the actual session id generated.
402 if (web_session_str
== std::string(kLoadableSessionId
))
403 web_session_str
= session_id_for_emulated_loadsession_
;
405 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
407 &ClearKeyCdm::OnPromiseResolved
, base::Unretained(this), promise_id
),
409 &ClearKeyCdm::OnPromiseFailed
, base::Unretained(this), promise_id
)));
410 decryptor_
.CloseSession(web_session_str
, promise
.Pass());
413 void ClearKeyCdm::RemoveSession(uint32 promise_id
,
414 const char* session_id
,
415 uint32_t session_id_length
) {
416 DVLOG(1) << __FUNCTION__
;
417 std::string
web_session_str(session_id
, session_id_length
);
419 // RemoveSession only allowed for the loadable session.
420 if (web_session_str
== std::string(kLoadableSessionId
)) {
421 web_session_str
= session_id_for_emulated_loadsession_
;
423 // TODO(jrummell): This should be a DCHECK once blink does the proper
425 std::string
message("Not supported for non-persistent sessions.");
426 host_
->OnRejectPromise(promise_id
,
427 cdm::kInvalidAccessError
,
434 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
435 base::Bind(&ClearKeyCdm::OnPromiseResolved
, base::Unretained(this),
437 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
439 decryptor_
.RemoveSession(web_session_str
, promise
.Pass());
442 void ClearKeyCdm::SetServerCertificate(uint32 promise_id
,
443 const uint8_t* server_certificate_data
,
444 uint32_t server_certificate_data_size
) {
445 // ClearKey doesn't use a server certificate.
446 host_
->OnResolvePromise(promise_id
);
449 void ClearKeyCdm::TimerExpired(void* context
) {
450 if (context
== &session_id_for_emulated_loadsession_
) {
451 LoadLoadableSession();
455 DCHECK(renewal_timer_set_
);
456 std::string renewal_message
;
457 if (!next_renewal_message_
.empty() &&
458 context
== &next_renewal_message_
[0]) {
459 renewal_message
= next_renewal_message_
;
461 renewal_message
= "ERROR: Invalid timer context found!";
464 // This URL is only used for testing the code path for defaultURL.
465 // There is no service at this URL, so applications should ignore it.
466 const char url
[] = "http://test.externalclearkey.chromium.org";
468 host_
->OnSessionMessage(last_session_id_
.data(), last_session_id_
.length(),
469 cdm::kLicenseRenewal
, renewal_message
.data(),
470 renewal_message
.length(), url
, arraysize(url
) - 1);
472 ScheduleNextRenewal();
475 static void CopyDecryptResults(
476 media::Decryptor::Status
* status_copy
,
477 scoped_refptr
<media::DecoderBuffer
>* buffer_copy
,
478 media::Decryptor::Status status
,
479 const scoped_refptr
<media::DecoderBuffer
>& buffer
) {
480 *status_copy
= status
;
481 *buffer_copy
= buffer
;
484 cdm::Status
ClearKeyCdm::Decrypt(const cdm::InputBuffer
& encrypted_buffer
,
485 cdm::DecryptedBlock
* decrypted_block
) {
486 DVLOG(1) << "Decrypt()";
487 DCHECK(encrypted_buffer
.data
);
489 scoped_refptr
<media::DecoderBuffer
> buffer
;
490 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
492 if (status
!= cdm::kSuccess
)
495 DCHECK(buffer
->data());
496 decrypted_block
->SetDecryptedBuffer(
497 host_
->Allocate(buffer
->data_size()));
498 memcpy(reinterpret_cast<void*>(decrypted_block
->DecryptedBuffer()->Data()),
500 buffer
->data_size());
501 decrypted_block
->DecryptedBuffer()->SetSize(buffer
->data_size());
502 decrypted_block
->SetTimestamp(buffer
->timestamp().InMicroseconds());
504 return cdm::kSuccess
;
507 cdm::Status
ClearKeyCdm::InitializeAudioDecoder(
508 const cdm::AudioDecoderConfig
& audio_decoder_config
) {
509 if (key_system_
== kExternalClearKeyDecryptOnlyKeySystem
)
510 return cdm::kSessionError
;
512 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
514 audio_decoder_
.reset(new media::FFmpegCdmAudioDecoder(host_
));
516 if (!audio_decoder_
->Initialize(audio_decoder_config
))
517 return cdm::kSessionError
;
519 return cdm::kSuccess
;
520 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
521 channel_count_
= audio_decoder_config
.channel_count
;
522 bits_per_channel_
= audio_decoder_config
.bits_per_channel
;
523 samples_per_second_
= audio_decoder_config
.samples_per_second
;
524 return cdm::kSuccess
;
527 return cdm::kSessionError
;
528 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
531 cdm::Status
ClearKeyCdm::InitializeVideoDecoder(
532 const cdm::VideoDecoderConfig
& video_decoder_config
) {
533 if (key_system_
== kExternalClearKeyDecryptOnlyKeySystem
)
534 return cdm::kSessionError
;
536 if (video_decoder_
&& video_decoder_
->is_initialized()) {
537 DCHECK(!video_decoder_
->is_initialized());
538 return cdm::kSessionError
;
541 // Any uninitialized decoder will be replaced.
542 video_decoder_
= CreateVideoDecoder(host_
, video_decoder_config
);
544 return cdm::kSessionError
;
546 return cdm::kSuccess
;
549 void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type
) {
550 DVLOG(1) << "ResetDecoder()";
551 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
552 switch (decoder_type
) {
553 case cdm::kStreamTypeVideo
:
554 video_decoder_
->Reset();
556 case cdm::kStreamTypeAudio
:
557 audio_decoder_
->Reset();
560 NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType";
562 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
563 if (decoder_type
== cdm::kStreamTypeAudio
) {
564 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
565 total_samples_generated_
= 0;
567 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
570 void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type
) {
571 DVLOG(1) << "DeinitializeDecoder()";
572 switch (decoder_type
) {
573 case cdm::kStreamTypeVideo
:
574 video_decoder_
->Deinitialize();
576 case cdm::kStreamTypeAudio
:
577 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
578 audio_decoder_
->Deinitialize();
579 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
580 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
581 total_samples_generated_
= 0;
585 NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType";
589 cdm::Status
ClearKeyCdm::DecryptAndDecodeFrame(
590 const cdm::InputBuffer
& encrypted_buffer
,
591 cdm::VideoFrame
* decoded_frame
) {
592 DVLOG(1) << "DecryptAndDecodeFrame()";
593 TRACE_EVENT0("media", "ClearKeyCdm::DecryptAndDecodeFrame");
595 scoped_refptr
<media::DecoderBuffer
> buffer
;
596 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
598 if (status
!= cdm::kSuccess
)
601 const uint8_t* data
= NULL
;
603 int64_t timestamp
= 0;
604 if (!buffer
->end_of_stream()) {
605 data
= buffer
->data();
606 size
= buffer
->data_size();
607 timestamp
= encrypted_buffer
.timestamp
;
610 return video_decoder_
->DecodeFrame(data
, size
, timestamp
, decoded_frame
);
613 cdm::Status
ClearKeyCdm::DecryptAndDecodeSamples(
614 const cdm::InputBuffer
& encrypted_buffer
,
615 cdm::AudioFrames
* audio_frames
) {
616 DVLOG(1) << "DecryptAndDecodeSamples()";
618 // Trigger a crash on purpose for testing purpose.
619 if (key_system_
== kExternalClearKeyCrashKeySystem
)
622 scoped_refptr
<media::DecoderBuffer
> buffer
;
623 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
625 if (status
!= cdm::kSuccess
)
628 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
629 const uint8_t* data
= NULL
;
631 int64_t timestamp
= 0;
632 if (!buffer
->end_of_stream()) {
633 data
= buffer
->data();
634 size
= buffer
->data_size();
635 timestamp
= encrypted_buffer
.timestamp
;
638 return audio_decoder_
->DecodeBuffer(data
, size
, timestamp
, audio_frames
);
639 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
640 int64 timestamp_in_microseconds
= kNoTimestamp
;
641 if (!buffer
->end_of_stream()) {
642 timestamp_in_microseconds
= buffer
->GetTimestamp().InMicroseconds();
643 DCHECK(timestamp_in_microseconds
!= kNoTimestamp
);
645 return GenerateFakeAudioFrames(timestamp_in_microseconds
, audio_frames
);
647 return cdm::kSuccess
;
648 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
651 void ClearKeyCdm::Destroy() {
652 DVLOG(1) << "Destroy()";
656 void ClearKeyCdm::ScheduleNextRenewal() {
657 // Prepare the next renewal message and set timer.
658 std::ostringstream msg_stream
;
659 msg_stream
<< kRenewalHeader
<< " from ClearKey CDM set at time "
660 << host_
->GetCurrentWallTime() << ".";
661 next_renewal_message_
= msg_stream
.str();
663 host_
->SetTimer(timer_delay_ms_
, &next_renewal_message_
[0]);
665 // Use a smaller timer delay at start-up to facilitate testing. Increase the
666 // timer delay up to a limit to avoid message spam.
667 if (timer_delay_ms_
< kMaxTimerDelayMs
)
668 timer_delay_ms_
= std::min(2 * timer_delay_ms_
, kMaxTimerDelayMs
);
671 cdm::Status
ClearKeyCdm::DecryptToMediaDecoderBuffer(
672 const cdm::InputBuffer
& encrypted_buffer
,
673 scoped_refptr
<media::DecoderBuffer
>* decrypted_buffer
) {
674 DCHECK(decrypted_buffer
);
675 scoped_refptr
<media::DecoderBuffer
> buffer
=
676 CopyDecoderBufferFrom(encrypted_buffer
);
678 if (buffer
->end_of_stream()) {
679 *decrypted_buffer
= buffer
;
680 return cdm::kSuccess
;
683 // Callback is called synchronously, so we can use variables on the stack.
684 media::Decryptor::Status status
= media::Decryptor::kError
;
685 // The AesDecryptor does not care what the stream type is. Pass kVideo
686 // for both audio and video decryption.
688 media::Decryptor::kVideo
,
690 base::Bind(&CopyDecryptResults
, &status
, decrypted_buffer
));
692 if (status
== media::Decryptor::kError
)
693 return cdm::kDecryptError
;
695 if (status
== media::Decryptor::kNoKey
)
698 DCHECK_EQ(status
, media::Decryptor::kSuccess
);
699 return cdm::kSuccess
;
702 void ClearKeyCdm::OnPlatformChallengeResponse(
703 const cdm::PlatformChallengeResponse
& response
) {
707 void ClearKeyCdm::OnQueryOutputProtectionStatus(
708 cdm::QueryResult result
,
710 uint32_t output_protection_mask
) {
714 void ClearKeyCdm::LoadLoadableSession() {
715 std::string jwk_set
= GenerateJWKSet(kLoadableSessionKey
,
716 sizeof(kLoadableSessionKey
),
717 kLoadableSessionKeyId
,
718 sizeof(kLoadableSessionKeyId
) - 1);
719 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
720 base::Bind(&ClearKeyCdm::OnLoadSessionUpdated
, base::Unretained(this)),
721 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
722 promise_id_for_emulated_loadsession_
)));
723 decryptor_
.UpdateSession(session_id_for_emulated_loadsession_
,
724 reinterpret_cast<const uint8
*>(jwk_set
.data()),
729 void ClearKeyCdm::OnSessionMessage(const std::string
& session_id
,
730 MediaKeys::MessageType message_type
,
731 const std::vector
<uint8
>& message
,
732 const GURL
& legacy_destination_url
) {
733 DVLOG(1) << "OnSessionMessage: " << message
.size();
735 // Ignore the message when we are waiting to update the loadable session.
736 if (session_id
== session_id_for_emulated_loadsession_
)
739 // OnSessionMessage() only called during CreateSession(), so no promise
740 // involved (OnSessionCreated() called to resolve the CreateSession()
742 host_
->OnSessionMessage(session_id
.data(), session_id
.length(),
743 cdm::kLicenseRequest
,
744 reinterpret_cast<const char*>(message
.data()),
745 message
.size(), legacy_destination_url
.spec().data(),
746 legacy_destination_url
.spec().size());
749 void ClearKeyCdm::OnSessionKeysChange(const std::string
& session_id
,
750 bool has_additional_usable_key
,
751 CdmKeysInfo keys_info
) {
752 DVLOG(1) << "OnSessionKeysChange: " << keys_info
.size();
754 std::string new_session_id
= session_id
;
755 if (new_session_id
== session_id_for_emulated_loadsession_
) {
756 // Save |keys_info| if the loadable session is still being created. This
757 // event will then be forwarded on in OnLoadSessionUpdated().
758 if (promise_id_for_emulated_loadsession_
!= 0) {
759 has_received_keys_change_event_for_emulated_loadsession_
= true;
760 keys_info_for_emulated_loadsession_
.swap(keys_info
);
764 // Loadable session has already been created, so pass this event on,
765 // using the session_id callers expect to see.
766 new_session_id
= std::string(kLoadableSessionId
);
769 std::vector
<cdm::KeyInformation
> keys_vector
;
770 ConvertCdmKeysInfo(keys_info
.get(), &keys_vector
);
771 host_
->OnSessionKeysChange(new_session_id
.data(), new_session_id
.length(),
772 has_additional_usable_key
,
773 vector_as_array(&keys_vector
), keys_vector
.size());
776 void ClearKeyCdm::OnSessionClosed(const std::string
& session_id
) {
777 std::string new_session_id
= session_id
;
778 if (new_session_id
== session_id_for_emulated_loadsession_
)
779 new_session_id
= std::string(kLoadableSessionId
);
780 host_
->OnSessionClosed(new_session_id
.data(), new_session_id
.length());
783 void ClearKeyCdm::OnSessionCreated(uint32 promise_id
,
784 const std::string
& session_id
) {
785 // Save the latest session ID for renewal and file IO test messages.
786 last_session_id_
= session_id
;
788 host_
->OnResolveNewSessionPromise(promise_id
, session_id
.data(),
789 session_id
.length());
792 void ClearKeyCdm::OnSessionLoaded(uint32 promise_id
,
793 const std::string
& session_id
) {
794 // Save the latest session ID for renewal and file IO test messages.
795 last_session_id_
= session_id
;
797 // |decryptor_| created some session as |session_id|, but going forward
798 // we need to map that to |kLoadableSessionId|, as that is what callers
800 session_id_for_emulated_loadsession_
= session_id
;
802 // Delay LoadLoadableSession() to test the case where Decrypt*() calls are
803 // made before the session is fully loaded.
804 const int64 kDelayToLoadSessionMs
= 500;
806 // Defer resolving the promise until the session is loaded.
807 promise_id_for_emulated_loadsession_
= promise_id
;
809 // Use the address of |session_id_for_emulated_loadsession_| as the timer
810 // context so that we can call LoadLoadableSession() when the timer expires.
811 host_
->SetTimer(kDelayToLoadSessionMs
, &session_id_for_emulated_loadsession_
);
814 void ClearKeyCdm::OnLoadSessionUpdated() {
815 // This method is only called to finish loading sessions, so handle
818 // |promise_id_for_emulated_loadsession_| is the LoadSession() promise,
819 // so resolve appropriately.
820 host_
->OnResolveNewSessionPromise(promise_id_for_emulated_loadsession_
,
822 strlen(kLoadableSessionId
));
823 promise_id_for_emulated_loadsession_
= 0;
825 // Generate the KeysChange event now that the session is "loaded" if one
827 // TODO(jrummell): Once the order of events is fixed in the spec, either
828 // require the keyschange event to have happened, or remove this code.
829 // http://crbug.com/448225
830 if (has_received_keys_change_event_for_emulated_loadsession_
) {
831 std::vector
<cdm::KeyInformation
> keys_vector
;
832 CdmKeysInfo keys_info
;
833 keys_info
.swap(keys_info_for_emulated_loadsession_
);
834 has_received_keys_change_event_for_emulated_loadsession_
= false;
835 DCHECK(!keys_vector
.empty());
836 ConvertCdmKeysInfo(keys_info
.get(), &keys_vector
);
837 host_
->OnSessionKeysChange(
838 kLoadableSessionId
, strlen(kLoadableSessionId
), !keys_vector
.empty(),
839 vector_as_array(&keys_vector
), keys_vector
.size());
843 void ClearKeyCdm::OnPromiseResolved(uint32 promise_id
) {
844 host_
->OnResolvePromise(promise_id
);
847 void ClearKeyCdm::OnPromiseFailed(uint32 promise_id
,
848 MediaKeys::Exception exception_code
,
850 const std::string
& error_message
) {
851 host_
->OnRejectPromise(promise_id
,
852 ConvertException(exception_code
),
854 error_message
.data(),
855 error_message
.length());
858 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
859 int64
ClearKeyCdm::CurrentTimeStampInMicroseconds() const {
860 return output_timestamp_base_in_microseconds_
+
861 base::Time::kMicrosecondsPerSecond
*
862 total_samples_generated_
/ samples_per_second_
;
865 int ClearKeyCdm::GenerateFakeAudioFramesFromDuration(
866 int64 duration_in_microseconds
,
867 cdm::AudioFrames
* audio_frames
) const {
868 int64 samples_to_generate
= static_cast<double>(samples_per_second_
) *
869 duration_in_microseconds
/ base::Time::kMicrosecondsPerSecond
+ 0.5;
870 if (samples_to_generate
<= 0)
873 int64 bytes_per_sample
= channel_count_
* bits_per_channel_
/ 8;
874 // |frame_size| must be a multiple of |bytes_per_sample|.
875 int64 frame_size
= bytes_per_sample
* samples_to_generate
;
877 int64 timestamp
= CurrentTimeStampInMicroseconds();
879 const int kHeaderSize
= sizeof(timestamp
) + sizeof(frame_size
);
880 audio_frames
->SetFrameBuffer(host_
->Allocate(kHeaderSize
+ frame_size
));
881 uint8_t* data
= audio_frames
->FrameBuffer()->Data();
883 memcpy(data
, ×tamp
, sizeof(timestamp
));
884 data
+= sizeof(timestamp
);
885 memcpy(data
, &frame_size
, sizeof(frame_size
));
886 data
+= sizeof(frame_size
);
887 // You won't hear anything because we have all zeros here. But the video
888 // should play just fine!
889 memset(data
, 0, frame_size
);
891 audio_frames
->FrameBuffer()->SetSize(kHeaderSize
+ frame_size
);
893 return samples_to_generate
;
896 cdm::Status
ClearKeyCdm::GenerateFakeAudioFrames(
897 int64 timestamp_in_microseconds
,
898 cdm::AudioFrames
* audio_frames
) {
899 if (timestamp_in_microseconds
== kNoTimestamp
)
900 return cdm::kNeedMoreData
;
902 // Return kNeedMoreData for the first frame because duration is unknown.
903 if (output_timestamp_base_in_microseconds_
== kNoTimestamp
) {
904 output_timestamp_base_in_microseconds_
= timestamp_in_microseconds
;
905 return cdm::kNeedMoreData
;
908 int samples_generated
= GenerateFakeAudioFramesFromDuration(
909 timestamp_in_microseconds
- CurrentTimeStampInMicroseconds(),
911 total_samples_generated_
+= samples_generated
;
913 return samples_generated
== 0 ? cdm::kNeedMoreData
: cdm::kSuccess
;
915 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
917 void ClearKeyCdm::StartFileIOTest() {
918 file_io_test_runner_
.reset(new FileIOTestRunner(
919 base::Bind(&ClearKeyCdmHost::CreateFileIO
, base::Unretained(host_
))));
920 file_io_test_runner_
->RunAllTests(
921 base::Bind(&ClearKeyCdm::OnFileIOTestComplete
, base::Unretained(this)));
924 void ClearKeyCdm::OnFileIOTestComplete(bool success
) {
925 DVLOG(1) << __FUNCTION__
<< ": " << success
;
926 std::string message
= GetFileIOTestResultMessage(success
);
927 host_
->OnSessionMessage(last_session_id_
.data(), last_session_id_
.length(),
928 cdm::kLicenseRequest
, message
.data(),
929 message
.length(), NULL
, 0);
930 file_io_test_runner_
.reset();