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"
11 #include "base/bind.h"
12 #include "base/logging.h"
13 #include "base/stl_util.h"
14 #include "base/time/time.h"
15 #include "base/trace_event/trace_event.h"
16 #include "media/base/cdm_callback_promise.h"
17 #include "media/base/cdm_key_information.h"
18 #include "media/base/decoder_buffer.h"
19 #include "media/base/decrypt_config.h"
20 #include "media/base/key_systems.h"
21 #include "media/cdm/json_web_key.h"
22 #include "media/cdm/ppapi/cdm_file_io_test.h"
23 #include "media/cdm/ppapi/external_clear_key/cdm_video_decoder.h"
26 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
27 #include "base/basictypes.h"
28 const int64 kNoTimestamp
= kint64min
;
29 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
31 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
32 #include "base/at_exit.h"
33 #include "base/files/file_path.h"
34 #include "base/path_service.h"
35 #include "media/base/media.h"
36 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_audio_decoder.h"
37 #include "media/cdm/ppapi/external_clear_key/ffmpeg_cdm_video_decoder.h"
39 // Include FFmpeg avformat.h for av_register_all().
41 // Temporarily disable possible loss of data warning.
42 MSVC_PUSH_DISABLE_WARNING(4244);
43 #include <libavformat/avformat.h>
47 #if !defined COMPONENT_BUILD
48 static base::AtExitManager g_at_exit_manager
;
51 // Prepare media library.
52 static bool InitializeFFmpegLibraries() {
53 media::InitializeMediaLibrary();
56 static bool g_ffmpeg_lib_initialized
= InitializeFFmpegLibraries();
58 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
60 const char kClearKeyCdmVersion
[] = "0.1.0.1";
61 const char kExternalClearKeyKeySystem
[] = "org.chromium.externalclearkey";
62 const char kExternalClearKeyDecryptOnlyKeySystem
[] =
63 "org.chromium.externalclearkey.decryptonly";
64 const char kExternalClearKeyFileIOTestKeySystem
[] =
65 "org.chromium.externalclearkey.fileiotest";
66 const char kExternalClearKeyCrashKeySystem
[] =
67 "org.chromium.externalclearkey.crash";
69 // Constants for the enumalted session that can be loaded by LoadSession().
70 // These constants need to be in sync with
71 // chrome/test/data/media/encrypted_media_utils.js
72 const char kLoadableSessionId
[] = "LoadableSession";
73 const uint8 kLoadableSessionKeyId
[] = "0123456789012345";
74 const uint8 kLoadableSessionKey
[] =
75 {0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
76 0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c};
78 const int64 kSecondsPerMinute
= 60;
79 const int64 kMsPerSecond
= 1000;
80 const int64 kInitialTimerDelayMs
= 200;
81 const int64 kMaxTimerDelayMs
= 1 * kSecondsPerMinute
* kMsPerSecond
;
82 // Renewal message header. For prefixed EME, if a key message starts with
83 // |kRenewalHeader|, it's a renewal message. Otherwise, it's a key request.
84 // FIXME(jrummell): Remove this once prefixed EME goes away.
85 const char kRenewalHeader
[] = "RENEWAL";
86 // CDM file IO test result header.
87 const char kFileIOTestResultHeader
[] = "FILEIOTESTRESULT";
89 // Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is
90 // empty, an empty (end-of-stream) media::DecoderBuffer is returned.
91 static scoped_refptr
<media::DecoderBuffer
> CopyDecoderBufferFrom(
92 const cdm::InputBuffer
& input_buffer
) {
93 if (!input_buffer
.data
) {
94 DCHECK(!input_buffer
.data_size
);
95 return media::DecoderBuffer::CreateEOSBuffer();
98 // TODO(xhwang): Get rid of this copy.
99 scoped_refptr
<media::DecoderBuffer
> output_buffer
=
100 media::DecoderBuffer::CopyFrom(input_buffer
.data
, input_buffer
.data_size
);
102 std::vector
<media::SubsampleEntry
> subsamples
;
103 for (uint32_t i
= 0; i
< input_buffer
.num_subsamples
; ++i
) {
104 subsamples
.push_back(
105 media::SubsampleEntry(input_buffer
.subsamples
[i
].clear_bytes
,
106 input_buffer
.subsamples
[i
].cipher_bytes
));
109 scoped_ptr
<media::DecryptConfig
> decrypt_config(new media::DecryptConfig(
110 std::string(reinterpret_cast<const char*>(input_buffer
.key_id
),
111 input_buffer
.key_id_size
),
112 std::string(reinterpret_cast<const char*>(input_buffer
.iv
),
113 input_buffer
.iv_size
),
116 output_buffer
->set_decrypt_config(decrypt_config
.Pass());
117 output_buffer
->set_timestamp(
118 base::TimeDelta::FromMicroseconds(input_buffer
.timestamp
));
120 return output_buffer
;
123 static std::string
GetFileIOTestResultMessage(bool success
) {
124 std::string
message(kFileIOTestResultHeader
);
125 message
+= success
? '1' : '0';
129 static cdm::Error
ConvertException(media::MediaKeys::Exception exception_code
) {
130 switch (exception_code
) {
131 case media::MediaKeys::NOT_SUPPORTED_ERROR
:
132 return cdm::kNotSupportedError
;
133 case media::MediaKeys::INVALID_STATE_ERROR
:
134 return cdm::kInvalidStateError
;
135 case media::MediaKeys::INVALID_ACCESS_ERROR
:
136 return cdm::kInvalidAccessError
;
137 case media::MediaKeys::QUOTA_EXCEEDED_ERROR
:
138 return cdm::kQuotaExceededError
;
139 case media::MediaKeys::UNKNOWN_ERROR
:
140 return cdm::kUnknownError
;
141 case media::MediaKeys::CLIENT_ERROR
:
142 return cdm::kClientError
;
143 case media::MediaKeys::OUTPUT_ERROR
:
144 return cdm::kOutputError
;
147 return cdm::kUnknownError
;
150 static media::MediaKeys::SessionType
ConvertSessionType(
151 cdm::SessionType session_type
) {
152 switch (session_type
) {
153 case cdm::kTemporary
:
154 return media::MediaKeys::TEMPORARY_SESSION
;
155 case cdm::kPersistentLicense
:
156 return media::MediaKeys::PERSISTENT_LICENSE_SESSION
;
157 case cdm::kPersistentKeyRelease
:
158 return media::MediaKeys::PERSISTENT_RELEASE_MESSAGE_SESSION
;
161 return media::MediaKeys::TEMPORARY_SESSION
;
164 static media::EmeInitDataType
ConvertInitDataType(
165 cdm::InitDataType init_data_type
) {
166 switch (init_data_type
) {
168 return media::EmeInitDataType::CENC
;
170 return media::EmeInitDataType::KEYIDS
;
172 return media::EmeInitDataType::WEBM
;
175 return media::EmeInitDataType::UNKNOWN
;
178 cdm::KeyStatus
ConvertKeyStatus(media::CdmKeyInformation::KeyStatus status
) {
179 // TODO(jrummell): Remove kOutputNotAllowed, add kOutputRestricted to CDM
180 // interface. http://crbug.com/507791.
182 case media::CdmKeyInformation::KeyStatus::USABLE
:
184 case media::CdmKeyInformation::KeyStatus::INTERNAL_ERROR
:
185 return cdm::kInternalError
;
186 case media::CdmKeyInformation::KeyStatus::EXPIRED
:
187 return cdm::kExpired
;
188 case media::CdmKeyInformation::KeyStatus::OUTPUT_RESTRICTED
:
189 return cdm::kOutputNotAllowed
;
190 case media::CdmKeyInformation::KeyStatus::OUTPUT_DOWNSCALED
:
191 return cdm::kOutputDownscaled
;
192 case media::CdmKeyInformation::KeyStatus::KEY_STATUS_PENDING
:
193 return cdm::kStatusPending
;
196 return cdm::kInternalError
;
199 // Shallow copy all the key information from |keys_info| into |keys_vector|.
200 // |keys_vector| is only valid for the lifetime of |keys_info| because it
201 // contains pointers into the latter.
202 void ConvertCdmKeysInfo(const std::vector
<media::CdmKeyInformation
*>& keys_info
,
203 std::vector
<cdm::KeyInformation
>* keys_vector
) {
204 keys_vector
->reserve(keys_info
.size());
205 for (const auto& key_info
: keys_info
) {
206 cdm::KeyInformation key
;
207 key
.key_id
= vector_as_array(&key_info
->key_id
);
208 key
.key_id_size
= key_info
->key_id
.size();
209 key
.status
= ConvertKeyStatus(key_info
->status
);
210 key
.system_code
= key_info
->system_code
;
211 keys_vector
->push_back(key
);
215 template<typename Type
>
216 class ScopedResetter
{
218 explicit ScopedResetter(Type
* object
) : object_(object
) {}
219 ~ScopedResetter() { object_
->Reset(); }
225 void INITIALIZE_CDM_MODULE() {
226 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
228 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
231 void DeinitializeCdmModule() {
234 void* CreateCdmInstance(int cdm_interface_version
,
235 const char* key_system
, uint32_t key_system_size
,
236 GetCdmHostFunc get_cdm_host_func
,
238 DVLOG(1) << "CreateCdmInstance()";
240 std::string
key_system_string(key_system
, key_system_size
);
241 if (key_system_string
!= kExternalClearKeyKeySystem
&&
242 key_system_string
!= kExternalClearKeyDecryptOnlyKeySystem
&&
243 key_system_string
!= kExternalClearKeyFileIOTestKeySystem
&&
244 key_system_string
!= kExternalClearKeyCrashKeySystem
) {
245 DVLOG(1) << "Unsupported key system:" << key_system_string
;
249 if (cdm_interface_version
!= media::ClearKeyCdmInterface::kVersion
)
252 media::ClearKeyCdmHost
* host
= static_cast<media::ClearKeyCdmHost
*>(
253 get_cdm_host_func(media::ClearKeyCdmHost::kVersion
, user_data
));
257 // TODO(jrummell): Obtain the proper origin for this instance.
258 return new media::ClearKeyCdm(host
, key_system_string
, GURL::EmptyGURL());
261 const char* GetCdmVersion() {
262 return kClearKeyCdmVersion
;
267 ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost
* host
,
268 const std::string
& key_system
,
272 base::Bind(&ClearKeyCdm::OnSessionMessage
, base::Unretained(this)),
273 base::Bind(&ClearKeyCdm::OnSessionClosed
, base::Unretained(this)),
274 base::Bind(&ClearKeyCdm::OnSessionKeysChange
,
275 base::Unretained(this))),
277 key_system_(key_system
),
278 has_received_keys_change_event_for_emulated_loadsession_(false),
279 timer_delay_ms_(kInitialTimerDelayMs
),
280 renewal_timer_set_(false) {
281 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
283 bits_per_channel_
= 0;
284 samples_per_second_
= 0;
285 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
286 total_samples_generated_
= 0;
287 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
290 ClearKeyCdm::~ClearKeyCdm() {}
292 void ClearKeyCdm::Initialize(bool /* allow_distinctive_identifier */,
293 bool /* allow_persistent_state */) {
294 // Implementation doesn't use distinctive identifier nor save persistent data,
295 // so nothing to do with these values.
298 void ClearKeyCdm::CreateSessionAndGenerateRequest(
300 cdm::SessionType session_type
,
301 cdm::InitDataType init_data_type
,
302 const uint8
* init_data
,
303 uint32 init_data_size
) {
304 DVLOG(1) << __FUNCTION__
;
306 scoped_ptr
<media::NewSessionCdmPromise
> promise(
307 new media::CdmCallbackPromise
<std::string
>(
308 base::Bind(&ClearKeyCdm::OnSessionCreated
,
309 base::Unretained(this),
311 base::Bind(&ClearKeyCdm::OnPromiseFailed
,
312 base::Unretained(this),
314 decryptor_
.CreateSessionAndGenerateRequest(
315 ConvertSessionType(session_type
), ConvertInitDataType(init_data_type
),
316 std::vector
<uint8_t>(init_data
, init_data
+ init_data_size
),
319 if (key_system_
== kExternalClearKeyFileIOTestKeySystem
)
323 // Loads a emulated stored session. Currently only |kLoadableSessionId|
324 // (containing a |kLoadableSessionKey| for |kLoadableSessionKeyId|) is
326 void ClearKeyCdm::LoadSession(uint32 promise_id
,
327 cdm::SessionType session_type
,
328 const char* session_id
,
329 uint32_t session_id_length
) {
330 DVLOG(1) << __FUNCTION__
;
331 DCHECK_EQ(session_type
, cdm::kPersistentLicense
);
333 if (std::string(kLoadableSessionId
) !=
334 std::string(session_id
, session_id_length
)) {
335 host_
->OnResolveNewSessionPromise(promise_id
, nullptr, 0);
339 // Only allowed to successfully load this session once.
340 DCHECK(session_id_for_emulated_loadsession_
.empty());
342 scoped_ptr
<media::NewSessionCdmPromise
> promise(
343 new media::CdmCallbackPromise
<std::string
>(
344 base::Bind(&ClearKeyCdm::OnSessionLoaded
,
345 base::Unretained(this),
347 base::Bind(&ClearKeyCdm::OnPromiseFailed
,
348 base::Unretained(this),
350 decryptor_
.CreateSessionAndGenerateRequest(
351 MediaKeys::TEMPORARY_SESSION
, EmeInitDataType::WEBM
,
352 std::vector
<uint8_t>(), promise
.Pass());
355 void ClearKeyCdm::UpdateSession(uint32 promise_id
,
356 const char* session_id
,
357 uint32_t session_id_length
,
358 const uint8
* response
,
359 uint32 response_size
) {
360 DVLOG(1) << __FUNCTION__
;
361 std::string
web_session_str(session_id
, session_id_length
);
363 // If updating the loadable session, use the actual session id generated.
364 if (web_session_str
== std::string(kLoadableSessionId
))
365 web_session_str
= session_id_for_emulated_loadsession_
;
367 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
368 base::Bind(&ClearKeyCdm::OnPromiseResolved
, base::Unretained(this),
370 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
372 decryptor_
.UpdateSession(
373 web_session_str
, std::vector
<uint8_t>(response
, response
+ response_size
),
376 if (!renewal_timer_set_
) {
377 ScheduleNextRenewal();
378 renewal_timer_set_
= true;
382 void ClearKeyCdm::CloseSession(uint32 promise_id
,
383 const char* session_id
,
384 uint32_t session_id_length
) {
385 DVLOG(1) << __FUNCTION__
;
386 std::string
web_session_str(session_id
, session_id_length
);
388 // If closing the loadable session, use the actual session id generated.
389 if (web_session_str
== std::string(kLoadableSessionId
))
390 web_session_str
= session_id_for_emulated_loadsession_
;
392 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
394 &ClearKeyCdm::OnPromiseResolved
, base::Unretained(this), promise_id
),
396 &ClearKeyCdm::OnPromiseFailed
, base::Unretained(this), promise_id
)));
397 decryptor_
.CloseSession(web_session_str
, promise
.Pass());
400 void ClearKeyCdm::RemoveSession(uint32 promise_id
,
401 const char* session_id
,
402 uint32_t session_id_length
) {
403 DVLOG(1) << __FUNCTION__
;
404 std::string
web_session_str(session_id
, session_id_length
);
406 // RemoveSession only allowed for the loadable session.
407 if (web_session_str
== std::string(kLoadableSessionId
)) {
408 web_session_str
= session_id_for_emulated_loadsession_
;
410 // TODO(jrummell): This should be a DCHECK once blink does the proper
412 std::string
message("Not supported for non-persistent sessions.");
413 host_
->OnRejectPromise(promise_id
,
414 cdm::kInvalidAccessError
,
421 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
422 base::Bind(&ClearKeyCdm::OnPromiseResolved
, base::Unretained(this),
424 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
426 decryptor_
.RemoveSession(web_session_str
, promise
.Pass());
429 void ClearKeyCdm::SetServerCertificate(uint32 promise_id
,
430 const uint8_t* server_certificate_data
,
431 uint32_t server_certificate_data_size
) {
432 // ClearKey doesn't use a server certificate.
433 host_
->OnResolvePromise(promise_id
);
436 void ClearKeyCdm::TimerExpired(void* context
) {
437 if (context
== &session_id_for_emulated_loadsession_
) {
438 LoadLoadableSession();
442 DCHECK(renewal_timer_set_
);
443 std::string renewal_message
;
444 if (!next_renewal_message_
.empty() &&
445 context
== &next_renewal_message_
[0]) {
446 renewal_message
= next_renewal_message_
;
448 renewal_message
= "ERROR: Invalid timer context found!";
451 // This URL is only used for testing the code path for defaultURL.
452 // There is no service at this URL, so applications should ignore it.
453 const char url
[] = "http://test.externalclearkey.chromium.org";
455 host_
->OnSessionMessage(last_session_id_
.data(), last_session_id_
.length(),
456 cdm::kLicenseRenewal
, renewal_message
.data(),
457 renewal_message
.length(), url
, arraysize(url
) - 1);
459 ScheduleNextRenewal();
462 static void CopyDecryptResults(
463 media::Decryptor::Status
* status_copy
,
464 scoped_refptr
<media::DecoderBuffer
>* buffer_copy
,
465 media::Decryptor::Status status
,
466 const scoped_refptr
<media::DecoderBuffer
>& buffer
) {
467 *status_copy
= status
;
468 *buffer_copy
= buffer
;
471 cdm::Status
ClearKeyCdm::Decrypt(const cdm::InputBuffer
& encrypted_buffer
,
472 cdm::DecryptedBlock
* decrypted_block
) {
473 DVLOG(1) << "Decrypt()";
474 DCHECK(encrypted_buffer
.data
);
476 scoped_refptr
<media::DecoderBuffer
> buffer
;
477 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
479 if (status
!= cdm::kSuccess
)
482 DCHECK(buffer
->data());
483 decrypted_block
->SetDecryptedBuffer(
484 host_
->Allocate(buffer
->data_size()));
485 memcpy(reinterpret_cast<void*>(decrypted_block
->DecryptedBuffer()->Data()),
487 buffer
->data_size());
488 decrypted_block
->DecryptedBuffer()->SetSize(buffer
->data_size());
489 decrypted_block
->SetTimestamp(buffer
->timestamp().InMicroseconds());
491 return cdm::kSuccess
;
494 cdm::Status
ClearKeyCdm::InitializeAudioDecoder(
495 const cdm::AudioDecoderConfig
& audio_decoder_config
) {
496 if (key_system_
== kExternalClearKeyDecryptOnlyKeySystem
)
497 return cdm::kSessionError
;
499 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
501 audio_decoder_
.reset(new media::FFmpegCdmAudioDecoder(host_
));
503 if (!audio_decoder_
->Initialize(audio_decoder_config
))
504 return cdm::kSessionError
;
506 return cdm::kSuccess
;
507 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
508 channel_count_
= audio_decoder_config
.channel_count
;
509 bits_per_channel_
= audio_decoder_config
.bits_per_channel
;
510 samples_per_second_
= audio_decoder_config
.samples_per_second
;
511 return cdm::kSuccess
;
514 return cdm::kSessionError
;
515 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
518 cdm::Status
ClearKeyCdm::InitializeVideoDecoder(
519 const cdm::VideoDecoderConfig
& video_decoder_config
) {
520 if (key_system_
== kExternalClearKeyDecryptOnlyKeySystem
)
521 return cdm::kSessionError
;
523 if (video_decoder_
&& video_decoder_
->is_initialized()) {
524 DCHECK(!video_decoder_
->is_initialized());
525 return cdm::kSessionError
;
528 // Any uninitialized decoder will be replaced.
529 video_decoder_
= CreateVideoDecoder(host_
, video_decoder_config
);
531 return cdm::kSessionError
;
533 return cdm::kSuccess
;
536 void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type
) {
537 DVLOG(1) << "ResetDecoder()";
538 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
539 switch (decoder_type
) {
540 case cdm::kStreamTypeVideo
:
541 video_decoder_
->Reset();
543 case cdm::kStreamTypeAudio
:
544 audio_decoder_
->Reset();
547 NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType";
549 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
550 if (decoder_type
== cdm::kStreamTypeAudio
) {
551 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
552 total_samples_generated_
= 0;
554 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER
557 void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type
) {
558 DVLOG(1) << "DeinitializeDecoder()";
559 switch (decoder_type
) {
560 case cdm::kStreamTypeVideo
:
561 video_decoder_
->Deinitialize();
563 case cdm::kStreamTypeAudio
:
564 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
565 audio_decoder_
->Deinitialize();
566 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
567 output_timestamp_base_in_microseconds_
= kNoTimestamp
;
568 total_samples_generated_
= 0;
572 NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType";
576 cdm::Status
ClearKeyCdm::DecryptAndDecodeFrame(
577 const cdm::InputBuffer
& encrypted_buffer
,
578 cdm::VideoFrame
* decoded_frame
) {
579 DVLOG(1) << "DecryptAndDecodeFrame()";
580 TRACE_EVENT0("media", "ClearKeyCdm::DecryptAndDecodeFrame");
582 scoped_refptr
<media::DecoderBuffer
> buffer
;
583 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
585 if (status
!= cdm::kSuccess
)
588 const uint8_t* data
= NULL
;
590 int64_t timestamp
= 0;
591 if (!buffer
->end_of_stream()) {
592 data
= buffer
->data();
593 size
= buffer
->data_size();
594 timestamp
= encrypted_buffer
.timestamp
;
597 return video_decoder_
->DecodeFrame(data
, size
, timestamp
, decoded_frame
);
600 cdm::Status
ClearKeyCdm::DecryptAndDecodeSamples(
601 const cdm::InputBuffer
& encrypted_buffer
,
602 cdm::AudioFrames
* audio_frames
) {
603 DVLOG(1) << "DecryptAndDecodeSamples()";
605 // Trigger a crash on purpose for testing purpose.
606 if (key_system_
== kExternalClearKeyCrashKeySystem
)
609 scoped_refptr
<media::DecoderBuffer
> buffer
;
610 cdm::Status status
= DecryptToMediaDecoderBuffer(encrypted_buffer
, &buffer
);
612 if (status
!= cdm::kSuccess
)
615 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER)
616 const uint8_t* data
= NULL
;
618 int64_t timestamp
= 0;
619 if (!buffer
->end_of_stream()) {
620 data
= buffer
->data();
621 size
= buffer
->data_size();
622 timestamp
= encrypted_buffer
.timestamp
;
625 return audio_decoder_
->DecodeBuffer(data
, size
, timestamp
, audio_frames
);
626 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
627 int64 timestamp_in_microseconds
= kNoTimestamp
;
628 if (!buffer
->end_of_stream()) {
629 timestamp_in_microseconds
= buffer
->GetTimestamp().InMicroseconds();
630 DCHECK(timestamp_in_microseconds
!= kNoTimestamp
);
632 return GenerateFakeAudioFrames(timestamp_in_microseconds
, audio_frames
);
634 return cdm::kSuccess
;
635 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
638 void ClearKeyCdm::Destroy() {
639 DVLOG(1) << "Destroy()";
643 void ClearKeyCdm::ScheduleNextRenewal() {
644 // Prepare the next renewal message and set timer.
645 std::ostringstream msg_stream
;
646 msg_stream
<< kRenewalHeader
<< " from ClearKey CDM set at time "
647 << host_
->GetCurrentWallTime() << ".";
648 next_renewal_message_
= msg_stream
.str();
650 host_
->SetTimer(timer_delay_ms_
, &next_renewal_message_
[0]);
652 // Use a smaller timer delay at start-up to facilitate testing. Increase the
653 // timer delay up to a limit to avoid message spam.
654 if (timer_delay_ms_
< kMaxTimerDelayMs
)
655 timer_delay_ms_
= std::min(2 * timer_delay_ms_
, kMaxTimerDelayMs
);
658 cdm::Status
ClearKeyCdm::DecryptToMediaDecoderBuffer(
659 const cdm::InputBuffer
& encrypted_buffer
,
660 scoped_refptr
<media::DecoderBuffer
>* decrypted_buffer
) {
661 DCHECK(decrypted_buffer
);
662 scoped_refptr
<media::DecoderBuffer
> buffer
=
663 CopyDecoderBufferFrom(encrypted_buffer
);
665 if (buffer
->end_of_stream()) {
666 *decrypted_buffer
= buffer
;
667 return cdm::kSuccess
;
670 // Callback is called synchronously, so we can use variables on the stack.
671 media::Decryptor::Status status
= media::Decryptor::kError
;
672 // The AesDecryptor does not care what the stream type is. Pass kVideo
673 // for both audio and video decryption.
675 media::Decryptor::kVideo
,
677 base::Bind(&CopyDecryptResults
, &status
, decrypted_buffer
));
679 if (status
== media::Decryptor::kError
)
680 return cdm::kDecryptError
;
682 if (status
== media::Decryptor::kNoKey
)
685 DCHECK_EQ(status
, media::Decryptor::kSuccess
);
686 return cdm::kSuccess
;
689 void ClearKeyCdm::OnPlatformChallengeResponse(
690 const cdm::PlatformChallengeResponse
& response
) {
694 void ClearKeyCdm::OnQueryOutputProtectionStatus(
695 cdm::QueryResult result
,
697 uint32_t output_protection_mask
) {
701 void ClearKeyCdm::LoadLoadableSession() {
702 std::string jwk_set
= GenerateJWKSet(kLoadableSessionKey
,
703 sizeof(kLoadableSessionKey
),
704 kLoadableSessionKeyId
,
705 sizeof(kLoadableSessionKeyId
) - 1);
706 scoped_ptr
<media::SimpleCdmPromise
> promise(new media::CdmCallbackPromise
<>(
707 base::Bind(&ClearKeyCdm::OnLoadSessionUpdated
, base::Unretained(this)),
708 base::Bind(&ClearKeyCdm::OnPromiseFailed
, base::Unretained(this),
709 promise_id_for_emulated_loadsession_
)));
710 decryptor_
.UpdateSession(session_id_for_emulated_loadsession_
,
711 std::vector
<uint8_t>(jwk_set
.begin(), jwk_set
.end()),
715 void ClearKeyCdm::OnSessionMessage(const std::string
& session_id
,
716 MediaKeys::MessageType message_type
,
717 const std::vector
<uint8
>& message
,
718 const GURL
& legacy_destination_url
) {
719 DVLOG(1) << "OnSessionMessage: " << message
.size();
721 // Ignore the message when we are waiting to update the loadable session.
722 if (session_id
== session_id_for_emulated_loadsession_
)
725 // OnSessionMessage() only called during CreateSession(), so no promise
726 // involved (OnSessionCreated() called to resolve the CreateSession()
728 host_
->OnSessionMessage(session_id
.data(), session_id
.length(),
729 cdm::kLicenseRequest
,
730 reinterpret_cast<const char*>(message
.data()),
731 message
.size(), legacy_destination_url
.spec().data(),
732 legacy_destination_url
.spec().size());
735 void ClearKeyCdm::OnSessionKeysChange(const std::string
& session_id
,
736 bool has_additional_usable_key
,
737 CdmKeysInfo keys_info
) {
738 DVLOG(1) << "OnSessionKeysChange: " << keys_info
.size();
740 std::string new_session_id
= session_id
;
741 if (new_session_id
== session_id_for_emulated_loadsession_
) {
742 // Save |keys_info| if the loadable session is still being created. This
743 // event will then be forwarded on in OnLoadSessionUpdated().
744 if (promise_id_for_emulated_loadsession_
!= 0) {
745 has_received_keys_change_event_for_emulated_loadsession_
= true;
746 keys_info_for_emulated_loadsession_
.swap(keys_info
);
750 // Loadable session has already been created, so pass this event on,
751 // using the session_id callers expect to see.
752 new_session_id
= std::string(kLoadableSessionId
);
755 std::vector
<cdm::KeyInformation
> keys_vector
;
756 ConvertCdmKeysInfo(keys_info
.get(), &keys_vector
);
757 host_
->OnSessionKeysChange(new_session_id
.data(), new_session_id
.length(),
758 has_additional_usable_key
,
759 vector_as_array(&keys_vector
), keys_vector
.size());
762 void ClearKeyCdm::OnSessionClosed(const std::string
& session_id
) {
763 std::string new_session_id
= session_id
;
764 if (new_session_id
== session_id_for_emulated_loadsession_
)
765 new_session_id
= std::string(kLoadableSessionId
);
766 host_
->OnSessionClosed(new_session_id
.data(), new_session_id
.length());
769 void ClearKeyCdm::OnSessionCreated(uint32 promise_id
,
770 const std::string
& session_id
) {
771 // Save the latest session ID for renewal and file IO test messages.
772 last_session_id_
= session_id
;
774 host_
->OnResolveNewSessionPromise(promise_id
, session_id
.data(),
775 session_id
.length());
778 void ClearKeyCdm::OnSessionLoaded(uint32 promise_id
,
779 const std::string
& session_id
) {
780 // Save the latest session ID for renewal and file IO test messages.
781 last_session_id_
= session_id
;
783 // |decryptor_| created some session as |session_id|, but going forward
784 // we need to map that to |kLoadableSessionId|, as that is what callers
786 session_id_for_emulated_loadsession_
= session_id
;
788 // Delay LoadLoadableSession() to test the case where Decrypt*() calls are
789 // made before the session is fully loaded.
790 const int64 kDelayToLoadSessionMs
= 500;
792 // Defer resolving the promise until the session is loaded.
793 promise_id_for_emulated_loadsession_
= promise_id
;
795 // Use the address of |session_id_for_emulated_loadsession_| as the timer
796 // context so that we can call LoadLoadableSession() when the timer expires.
797 host_
->SetTimer(kDelayToLoadSessionMs
, &session_id_for_emulated_loadsession_
);
800 void ClearKeyCdm::OnLoadSessionUpdated() {
801 // This method is only called to finish loading sessions, so handle
804 // |promise_id_for_emulated_loadsession_| is the LoadSession() promise,
805 // so resolve appropriately.
806 host_
->OnResolveNewSessionPromise(promise_id_for_emulated_loadsession_
,
808 strlen(kLoadableSessionId
));
809 promise_id_for_emulated_loadsession_
= 0;
811 // Generate the KeysChange event now that the session is "loaded" if one
813 // TODO(jrummell): Once the order of events is fixed in the spec, either
814 // require the keyschange event to have happened, or remove this code.
815 // http://crbug.com/448225
816 if (has_received_keys_change_event_for_emulated_loadsession_
) {
817 std::vector
<cdm::KeyInformation
> keys_vector
;
818 CdmKeysInfo keys_info
;
819 keys_info
.swap(keys_info_for_emulated_loadsession_
);
820 has_received_keys_change_event_for_emulated_loadsession_
= false;
821 DCHECK(!keys_vector
.empty());
822 ConvertCdmKeysInfo(keys_info
.get(), &keys_vector
);
823 host_
->OnSessionKeysChange(
824 kLoadableSessionId
, strlen(kLoadableSessionId
), !keys_vector
.empty(),
825 vector_as_array(&keys_vector
), keys_vector
.size());
829 void ClearKeyCdm::OnPromiseResolved(uint32 promise_id
) {
830 host_
->OnResolvePromise(promise_id
);
833 void ClearKeyCdm::OnPromiseFailed(uint32 promise_id
,
834 MediaKeys::Exception exception_code
,
836 const std::string
& error_message
) {
837 host_
->OnRejectPromise(promise_id
,
838 ConvertException(exception_code
),
840 error_message
.data(),
841 error_message
.length());
844 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER)
845 int64
ClearKeyCdm::CurrentTimeStampInMicroseconds() const {
846 return output_timestamp_base_in_microseconds_
+
847 base::Time::kMicrosecondsPerSecond
*
848 total_samples_generated_
/ samples_per_second_
;
851 int ClearKeyCdm::GenerateFakeAudioFramesFromDuration(
852 int64 duration_in_microseconds
,
853 cdm::AudioFrames
* audio_frames
) const {
854 int64 samples_to_generate
= static_cast<double>(samples_per_second_
) *
855 duration_in_microseconds
/ base::Time::kMicrosecondsPerSecond
+ 0.5;
856 if (samples_to_generate
<= 0)
859 int64 bytes_per_sample
= channel_count_
* bits_per_channel_
/ 8;
860 // |frame_size| must be a multiple of |bytes_per_sample|.
861 int64 frame_size
= bytes_per_sample
* samples_to_generate
;
863 int64 timestamp
= CurrentTimeStampInMicroseconds();
865 const int kHeaderSize
= sizeof(timestamp
) + sizeof(frame_size
);
866 audio_frames
->SetFrameBuffer(host_
->Allocate(kHeaderSize
+ frame_size
));
867 uint8_t* data
= audio_frames
->FrameBuffer()->Data();
869 memcpy(data
, ×tamp
, sizeof(timestamp
));
870 data
+= sizeof(timestamp
);
871 memcpy(data
, &frame_size
, sizeof(frame_size
));
872 data
+= sizeof(frame_size
);
873 // You won't hear anything because we have all zeros here. But the video
874 // should play just fine!
875 memset(data
, 0, frame_size
);
877 audio_frames
->FrameBuffer()->SetSize(kHeaderSize
+ frame_size
);
879 return samples_to_generate
;
882 cdm::Status
ClearKeyCdm::GenerateFakeAudioFrames(
883 int64 timestamp_in_microseconds
,
884 cdm::AudioFrames
* audio_frames
) {
885 if (timestamp_in_microseconds
== kNoTimestamp
)
886 return cdm::kNeedMoreData
;
888 // Return kNeedMoreData for the first frame because duration is unknown.
889 if (output_timestamp_base_in_microseconds_
== kNoTimestamp
) {
890 output_timestamp_base_in_microseconds_
= timestamp_in_microseconds
;
891 return cdm::kNeedMoreData
;
894 int samples_generated
= GenerateFakeAudioFramesFromDuration(
895 timestamp_in_microseconds
- CurrentTimeStampInMicroseconds(),
897 total_samples_generated_
+= samples_generated
;
899 return samples_generated
== 0 ? cdm::kNeedMoreData
: cdm::kSuccess
;
901 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER
903 void ClearKeyCdm::StartFileIOTest() {
904 file_io_test_runner_
.reset(new FileIOTestRunner(
905 base::Bind(&ClearKeyCdmHost::CreateFileIO
, base::Unretained(host_
))));
906 file_io_test_runner_
->RunAllTests(
907 base::Bind(&ClearKeyCdm::OnFileIOTestComplete
, base::Unretained(this)));
910 void ClearKeyCdm::OnFileIOTestComplete(bool success
) {
911 DVLOG(1) << __FUNCTION__
<< ": " << success
;
912 std::string message
= GetFileIOTestResultMessage(success
);
913 host_
->OnSessionMessage(last_session_id_
.data(), last_session_id_
.length(),
914 cdm::kLicenseRequest
, message
.data(),
915 message
.length(), NULL
, 0);
916 file_io_test_runner_
.reset();