OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "webkit/renderer/media/crypto/ppapi/clear_key_cdm.h" | |
6 | |
7 #include <algorithm> | |
8 #include <sstream> | |
9 #include <string> | |
10 #include <vector> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/debug/trace_event.h" | |
14 #include "base/logging.h" | |
15 #include "base/time/time.h" | |
16 #include "media/base/decoder_buffer.h" | |
17 #include "media/base/decrypt_config.h" | |
18 #include "webkit/renderer/media/crypto/ppapi/cdm_video_decoder.h" | |
19 | |
20 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
21 #include "base/basictypes.h" | |
22 static const int64 kNoTimestamp = kint64min; | |
23 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER | |
24 | |
25 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
26 #include "base/at_exit.h" | |
27 #include "base/files/file_path.h" | |
28 #include "base/path_service.h" | |
29 #include "media/base/media.h" | |
30 #include "webkit/renderer/media/crypto/ppapi/ffmpeg_cdm_audio_decoder.h" | |
31 #include "webkit/renderer/media/crypto/ppapi/ffmpeg_cdm_video_decoder.h" | |
32 | |
33 // Include FFmpeg avformat.h for av_register_all(). | |
34 extern "C" { | |
35 // Temporarily disable possible loss of data warning. | |
36 MSVC_PUSH_DISABLE_WARNING(4244); | |
37 #include <libavformat/avformat.h> | |
38 MSVC_POP_WARNING(); | |
39 } // extern "C" | |
40 | |
41 // TODO(tomfinegan): When COMPONENT_BUILD is not defined an AtExitManager must | |
42 // exist before the call to InitializeFFmpegLibraries(). This should no longer | |
43 // be required after http://crbug.com/91970 because we'll be able to get rid of | |
44 // InitializeFFmpegLibraries(). | |
45 #if !defined COMPONENT_BUILD | |
46 static base::AtExitManager g_at_exit_manager; | |
47 #endif | |
48 | |
49 // TODO(tomfinegan): InitializeFFmpegLibraries() and |g_cdm_module_initialized| | |
50 // are required for running in the sandbox, and should no longer be required | |
51 // after http://crbug.com/91970 is fixed. | |
52 static bool InitializeFFmpegLibraries() { | |
53 base::FilePath file_path; | |
54 CHECK(PathService::Get(base::DIR_MODULE, &file_path)); | |
55 CHECK(media::InitializeMediaLibrary(file_path)); | |
56 return true; | |
57 } | |
58 | |
59 static bool g_ffmpeg_lib_initialized = InitializeFFmpegLibraries(); | |
60 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER | |
61 | |
62 static const char kClearKeyCdmVersion[] = "0.1.0.1"; | |
63 static const char kExternalClearKey[] = "org.chromium.externalclearkey"; | |
64 static const int64 kSecondsPerMinute = 60; | |
65 static const int64 kMsPerSecond = 1000; | |
66 static const int64 kInitialTimerDelayMs = 200; | |
67 static const int64 kMaxTimerDelayMs = 1 * kSecondsPerMinute * kMsPerSecond; | |
68 // Heart beat message header. If a key message starts with |kHeartBeatHeader|, | |
69 // it's a heart beat message. Otherwise, it's a key request. | |
70 static const char kHeartBeatHeader[] = "HEARTBEAT"; | |
71 | |
72 // Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is | |
73 // empty, an empty (end-of-stream) media::DecoderBuffer is returned. | |
74 static scoped_refptr<media::DecoderBuffer> CopyDecoderBufferFrom( | |
75 const cdm::InputBuffer& input_buffer) { | |
76 if (!input_buffer.data) { | |
77 DCHECK_EQ(input_buffer.data_size, 0); | |
78 return media::DecoderBuffer::CreateEOSBuffer(); | |
79 } | |
80 | |
81 // TODO(tomfinegan): Get rid of this copy. | |
82 scoped_refptr<media::DecoderBuffer> output_buffer = | |
83 media::DecoderBuffer::CopyFrom(input_buffer.data, input_buffer.data_size); | |
84 | |
85 std::vector<media::SubsampleEntry> subsamples; | |
86 for (int32_t i = 0; i < input_buffer.num_subsamples; ++i) { | |
87 media::SubsampleEntry subsample; | |
88 subsample.clear_bytes = input_buffer.subsamples[i].clear_bytes; | |
89 subsample.cypher_bytes = input_buffer.subsamples[i].cipher_bytes; | |
90 subsamples.push_back(subsample); | |
91 } | |
92 | |
93 scoped_ptr<media::DecryptConfig> decrypt_config(new media::DecryptConfig( | |
94 std::string(reinterpret_cast<const char*>(input_buffer.key_id), | |
95 input_buffer.key_id_size), | |
96 std::string(reinterpret_cast<const char*>(input_buffer.iv), | |
97 input_buffer.iv_size), | |
98 input_buffer.data_offset, | |
99 subsamples)); | |
100 | |
101 output_buffer->set_decrypt_config(decrypt_config.Pass()); | |
102 output_buffer->set_timestamp( | |
103 base::TimeDelta::FromMicroseconds(input_buffer.timestamp)); | |
104 | |
105 return output_buffer; | |
106 } | |
107 | |
108 template<typename Type> | |
109 class ScopedResetter { | |
110 public: | |
111 explicit ScopedResetter(Type* object) : object_(object) {} | |
112 ~ScopedResetter() { object_->Reset(); } | |
113 | |
114 private: | |
115 Type* const object_; | |
116 }; | |
117 | |
118 void INITIALIZE_CDM_MODULE() { | |
119 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
120 DVLOG(2) << "FFmpeg libraries initialized: " << g_ffmpeg_lib_initialized; | |
121 av_register_all(); | |
122 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER | |
123 } | |
124 | |
125 void DeinitializeCdmModule() { | |
126 } | |
127 | |
128 void* CreateCdmInstance( | |
129 int cdm_interface_version, | |
130 const char* key_system, int key_system_size, | |
131 GetCdmHostFunc get_cdm_host_func, void* user_data) { | |
132 DVLOG(1) << "CreateCdmInstance()"; | |
133 | |
134 if (cdm_interface_version != cdm::kCdmInterfaceVersion) | |
135 return NULL; | |
136 | |
137 cdm::Host* host = static_cast<cdm::Host*>( | |
138 get_cdm_host_func(cdm::kHostInterfaceVersion, user_data)); | |
139 if (!host) | |
140 return NULL; | |
141 | |
142 return static_cast<cdm::ContentDecryptionModule*>( | |
143 new webkit_media::ClearKeyCdm(host)); | |
144 } | |
145 | |
146 const char* GetCdmVersion() { | |
147 return kClearKeyCdmVersion; | |
148 } | |
149 | |
150 namespace webkit_media { | |
151 | |
152 ClearKeyCdm::Client::Client() : status_(kKeyError) {} | |
153 | |
154 ClearKeyCdm::Client::~Client() {} | |
155 | |
156 void ClearKeyCdm::Client::Reset() { | |
157 status_ = kKeyError; | |
158 session_id_.clear(); | |
159 key_message_.clear(); | |
160 default_url_.clear(); | |
161 } | |
162 | |
163 void ClearKeyCdm::Client::KeyAdded(const std::string& session_id) { | |
164 status_ = kKeyAdded; | |
165 session_id_ = session_id; | |
166 } | |
167 | |
168 void ClearKeyCdm::Client::KeyError(const std::string& session_id, | |
169 media::MediaKeys::KeyError error_code, | |
170 int system_code) { | |
171 status_ = kKeyError; | |
172 session_id_ = session_id; | |
173 } | |
174 | |
175 void ClearKeyCdm::Client::KeyMessage(const std::string& session_id, | |
176 const std::vector<uint8>& message, | |
177 const std::string& default_url) { | |
178 status_ = kKeyMessage; | |
179 session_id_ = session_id; | |
180 key_message_ = message; | |
181 default_url_ = default_url; | |
182 } | |
183 | |
184 ClearKeyCdm::ClearKeyCdm(cdm::Host* host) | |
185 : decryptor_(base::Bind(&Client::KeyAdded, base::Unretained(&client_)), | |
186 base::Bind(&Client::KeyError, base::Unretained(&client_)), | |
187 base::Bind(&Client::KeyMessage, base::Unretained(&client_))), | |
188 host_(host), | |
189 timer_delay_ms_(kInitialTimerDelayMs), | |
190 timer_set_(false) { | |
191 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
192 channel_count_ = 0; | |
193 bits_per_channel_ = 0; | |
194 samples_per_second_ = 0; | |
195 output_timestamp_base_in_microseconds_ = kNoTimestamp; | |
196 total_samples_generated_ = 0; | |
197 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER | |
198 } | |
199 | |
200 ClearKeyCdm::~ClearKeyCdm() {} | |
201 | |
202 cdm::Status ClearKeyCdm::GenerateKeyRequest(const char* type, int type_size, | |
203 const uint8_t* init_data, | |
204 int init_data_size) { | |
205 DVLOG(1) << "GenerateKeyRequest()"; | |
206 base::AutoLock auto_lock(client_lock_); | |
207 ScopedResetter<Client> auto_resetter(&client_); | |
208 decryptor_.GenerateKeyRequest(std::string(type, type_size), | |
209 init_data, init_data_size); | |
210 | |
211 if (client_.status() != Client::kKeyMessage) { | |
212 host_->SendKeyError(NULL, 0, cdm::kUnknownError, 0); | |
213 return cdm::kSessionError; | |
214 } | |
215 | |
216 host_->SendKeyMessage( | |
217 client_.session_id().data(), client_.session_id().size(), | |
218 reinterpret_cast<const char*>(&client_.key_message()[0]), | |
219 client_.key_message().size(), | |
220 client_.default_url().data(), client_.default_url().size()); | |
221 | |
222 // Only save the latest session ID for heartbeat messages. | |
223 heartbeat_session_id_ = client_.session_id(); | |
224 | |
225 return cdm::kSuccess; | |
226 } | |
227 | |
228 cdm::Status ClearKeyCdm::AddKey(const char* session_id, | |
229 int session_id_size, | |
230 const uint8_t* key, | |
231 int key_size, | |
232 const uint8_t* key_id, | |
233 int key_id_size) { | |
234 DVLOG(1) << "AddKey()"; | |
235 base::AutoLock auto_lock(client_lock_); | |
236 ScopedResetter<Client> auto_resetter(&client_); | |
237 decryptor_.AddKey(key, key_size, key_id, key_id_size, | |
238 std::string(session_id, session_id_size)); | |
239 | |
240 if (client_.status() != Client::kKeyAdded) | |
241 return cdm::kSessionError; | |
242 | |
243 if (!timer_set_) { | |
244 ScheduleNextHeartBeat(); | |
245 timer_set_ = true; | |
246 } | |
247 | |
248 return cdm::kSuccess; | |
249 } | |
250 | |
251 cdm::Status ClearKeyCdm::CancelKeyRequest(const char* session_id, | |
252 int session_id_size) { | |
253 DVLOG(1) << "CancelKeyRequest()"; | |
254 base::AutoLock auto_lock(client_lock_); | |
255 ScopedResetter<Client> auto_resetter(&client_); | |
256 decryptor_.CancelKeyRequest(std::string(session_id, session_id_size)); | |
257 return cdm::kSuccess; | |
258 } | |
259 | |
260 void ClearKeyCdm::TimerExpired(void* context) { | |
261 std::string heartbeat_message; | |
262 if (!next_heartbeat_message_.empty() && | |
263 context == &next_heartbeat_message_[0]) { | |
264 heartbeat_message = next_heartbeat_message_; | |
265 } else { | |
266 heartbeat_message = "ERROR: Invalid timer context found!"; | |
267 } | |
268 | |
269 // This URL is only used for testing the code path for defaultURL. | |
270 // There is no service at this URL, so applications should ignore it. | |
271 const char url[] = "http://test.externalclearkey.chromium.org"; | |
272 | |
273 host_->SendKeyMessage( | |
274 heartbeat_session_id_.data(), heartbeat_session_id_.size(), | |
275 heartbeat_message.data(), heartbeat_message.size(), | |
276 url, arraysize(url) - 1); | |
277 | |
278 ScheduleNextHeartBeat(); | |
279 } | |
280 | |
281 static void CopyDecryptResults( | |
282 media::Decryptor::Status* status_copy, | |
283 scoped_refptr<media::DecoderBuffer>* buffer_copy, | |
284 media::Decryptor::Status status, | |
285 const scoped_refptr<media::DecoderBuffer>& buffer) { | |
286 *status_copy = status; | |
287 *buffer_copy = buffer; | |
288 } | |
289 | |
290 cdm::Status ClearKeyCdm::Decrypt( | |
291 const cdm::InputBuffer& encrypted_buffer, | |
292 cdm::DecryptedBlock* decrypted_block) { | |
293 DVLOG(1) << "Decrypt()"; | |
294 DCHECK(encrypted_buffer.data); | |
295 | |
296 scoped_refptr<media::DecoderBuffer> buffer; | |
297 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); | |
298 | |
299 if (status != cdm::kSuccess) | |
300 return status; | |
301 | |
302 DCHECK(buffer->data()); | |
303 decrypted_block->SetDecryptedBuffer( | |
304 host_->Allocate(buffer->data_size())); | |
305 memcpy(reinterpret_cast<void*>(decrypted_block->DecryptedBuffer()->Data()), | |
306 buffer->data(), | |
307 buffer->data_size()); | |
308 decrypted_block->DecryptedBuffer()->SetSize(buffer->data_size()); | |
309 decrypted_block->SetTimestamp(buffer->timestamp().InMicroseconds()); | |
310 | |
311 return cdm::kSuccess; | |
312 } | |
313 | |
314 cdm::Status ClearKeyCdm::InitializeAudioDecoder( | |
315 const cdm::AudioDecoderConfig& audio_decoder_config) { | |
316 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
317 if (!audio_decoder_) | |
318 audio_decoder_.reset(new webkit_media::FFmpegCdmAudioDecoder(host_)); | |
319 | |
320 if (!audio_decoder_->Initialize(audio_decoder_config)) | |
321 return cdm::kSessionError; | |
322 | |
323 return cdm::kSuccess; | |
324 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
325 channel_count_ = audio_decoder_config.channel_count; | |
326 bits_per_channel_ = audio_decoder_config.bits_per_channel; | |
327 samples_per_second_ = audio_decoder_config.samples_per_second; | |
328 return cdm::kSuccess; | |
329 #else | |
330 NOTIMPLEMENTED(); | |
331 return cdm::kSessionError; | |
332 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER | |
333 } | |
334 | |
335 cdm::Status ClearKeyCdm::InitializeVideoDecoder( | |
336 const cdm::VideoDecoderConfig& video_decoder_config) { | |
337 if (video_decoder_ && video_decoder_->is_initialized()) { | |
338 DCHECK(!video_decoder_->is_initialized()); | |
339 return cdm::kSessionError; | |
340 } | |
341 | |
342 // Any uninitialized decoder will be replaced. | |
343 video_decoder_ = CreateVideoDecoder(host_, video_decoder_config); | |
344 if (!video_decoder_) | |
345 return cdm::kSessionError; | |
346 | |
347 return cdm::kSuccess; | |
348 } | |
349 | |
350 void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type) { | |
351 DVLOG(1) << "ResetDecoder()"; | |
352 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
353 switch (decoder_type) { | |
354 case cdm::kStreamTypeVideo: | |
355 video_decoder_->Reset(); | |
356 break; | |
357 case cdm::kStreamTypeAudio: | |
358 audio_decoder_->Reset(); | |
359 break; | |
360 default: | |
361 NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType"; | |
362 } | |
363 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
364 if (decoder_type == cdm::kStreamTypeAudio) { | |
365 output_timestamp_base_in_microseconds_ = kNoTimestamp; | |
366 total_samples_generated_ = 0; | |
367 } | |
368 #endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER | |
369 } | |
370 | |
371 void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type) { | |
372 DVLOG(1) << "DeinitializeDecoder()"; | |
373 switch (decoder_type) { | |
374 case cdm::kStreamTypeVideo: | |
375 video_decoder_->Deinitialize(); | |
376 break; | |
377 case cdm::kStreamTypeAudio: | |
378 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
379 audio_decoder_->Deinitialize(); | |
380 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
381 output_timestamp_base_in_microseconds_ = kNoTimestamp; | |
382 total_samples_generated_ = 0; | |
383 #endif | |
384 break; | |
385 default: | |
386 NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType"; | |
387 } | |
388 } | |
389 | |
390 cdm::Status ClearKeyCdm::DecryptAndDecodeFrame( | |
391 const cdm::InputBuffer& encrypted_buffer, | |
392 cdm::VideoFrame* decoded_frame) { | |
393 DVLOG(1) << "DecryptAndDecodeFrame()"; | |
394 TRACE_EVENT0("eme", "ClearKeyCdm::DecryptAndDecodeFrame"); | |
395 | |
396 scoped_refptr<media::DecoderBuffer> buffer; | |
397 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); | |
398 | |
399 if (status != cdm::kSuccess) | |
400 return status; | |
401 | |
402 const uint8_t* data = NULL; | |
403 int32_t size = 0; | |
404 int64_t timestamp = 0; | |
405 if (!buffer->end_of_stream()) { | |
406 data = buffer->data(); | |
407 size = buffer->data_size(); | |
408 timestamp = encrypted_buffer.timestamp; | |
409 } | |
410 | |
411 return video_decoder_->DecodeFrame(data, size, timestamp, decoded_frame); | |
412 } | |
413 | |
414 cdm::Status ClearKeyCdm::DecryptAndDecodeSamples( | |
415 const cdm::InputBuffer& encrypted_buffer, | |
416 cdm::AudioFrames* audio_frames) { | |
417 DVLOG(1) << "DecryptAndDecodeSamples()"; | |
418 | |
419 scoped_refptr<media::DecoderBuffer> buffer; | |
420 cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); | |
421 | |
422 if (status != cdm::kSuccess) | |
423 return status; | |
424 | |
425 #if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) | |
426 const uint8_t* data = NULL; | |
427 int32_t size = 0; | |
428 int64_t timestamp = 0; | |
429 if (!buffer->end_of_stream()) { | |
430 data = buffer->data(); | |
431 size = buffer->data_size(); | |
432 timestamp = encrypted_buffer.timestamp; | |
433 } | |
434 | |
435 return audio_decoder_->DecodeBuffer(data, size, timestamp, audio_frames); | |
436 #elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
437 int64 timestamp_in_microseconds = kNoTimestamp; | |
438 if (!buffer->end_of_stream()) { | |
439 timestamp_in_microseconds = buffer->GetTimestamp().InMicroseconds(); | |
440 DCHECK(timestamp_in_microseconds != kNoTimestamp); | |
441 } | |
442 return GenerateFakeAudioFrames(timestamp_in_microseconds, audio_frames); | |
443 #else | |
444 return cdm::kSuccess; | |
445 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER | |
446 } | |
447 | |
448 void ClearKeyCdm::Destroy() { | |
449 DVLOG(1) << "Destroy()"; | |
450 delete this; | |
451 } | |
452 | |
453 void ClearKeyCdm::ScheduleNextHeartBeat() { | |
454 // Prepare the next heartbeat message and set timer. | |
455 std::ostringstream msg_stream; | |
456 msg_stream << kHeartBeatHeader << " from ClearKey CDM set at time " | |
457 << host_->GetCurrentWallTimeInSeconds() << "."; | |
458 next_heartbeat_message_ = msg_stream.str(); | |
459 | |
460 host_->SetTimer(timer_delay_ms_, &next_heartbeat_message_[0]); | |
461 | |
462 // Use a smaller timer delay at start-up to facilitate testing. Increase the | |
463 // timer delay up to a limit to avoid message spam. | |
464 if (timer_delay_ms_ < kMaxTimerDelayMs) | |
465 timer_delay_ms_ = std::min(2 * timer_delay_ms_, kMaxTimerDelayMs); | |
466 } | |
467 | |
468 cdm::Status ClearKeyCdm::DecryptToMediaDecoderBuffer( | |
469 const cdm::InputBuffer& encrypted_buffer, | |
470 scoped_refptr<media::DecoderBuffer>* decrypted_buffer) { | |
471 DCHECK(decrypted_buffer); | |
472 scoped_refptr<media::DecoderBuffer> buffer = | |
473 CopyDecoderBufferFrom(encrypted_buffer); | |
474 | |
475 if (buffer->end_of_stream()) { | |
476 *decrypted_buffer = buffer; | |
477 return cdm::kSuccess; | |
478 } | |
479 | |
480 // Callback is called synchronously, so we can use variables on the stack. | |
481 media::Decryptor::Status status = media::Decryptor::kError; | |
482 // The AesDecryptor does not care what the stream type is. Pass kVideo | |
483 // for both audio and video decryption. | |
484 decryptor_.Decrypt( | |
485 media::Decryptor::kVideo, | |
486 buffer, | |
487 base::Bind(&CopyDecryptResults, &status, decrypted_buffer)); | |
488 | |
489 if (status == media::Decryptor::kError) | |
490 return cdm::kDecryptError; | |
491 | |
492 if (status == media::Decryptor::kNoKey) | |
493 return cdm::kNoKey; | |
494 | |
495 DCHECK_EQ(status, media::Decryptor::kSuccess); | |
496 return cdm::kSuccess; | |
497 } | |
498 | |
499 #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) | |
500 int64 ClearKeyCdm::CurrentTimeStampInMicroseconds() const { | |
501 return output_timestamp_base_in_microseconds_ + | |
502 base::Time::kMicrosecondsPerSecond * | |
503 total_samples_generated_ / samples_per_second_; | |
504 } | |
505 | |
506 int ClearKeyCdm::GenerateFakeAudioFramesFromDuration( | |
507 int64 duration_in_microseconds, | |
508 cdm::AudioFrames* audio_frames) const { | |
509 int64 samples_to_generate = static_cast<double>(samples_per_second_) * | |
510 duration_in_microseconds / base::Time::kMicrosecondsPerSecond + 0.5; | |
511 if (samples_to_generate <= 0) | |
512 return 0; | |
513 | |
514 int64 bytes_per_sample = channel_count_ * bits_per_channel_ / 8; | |
515 // |frame_size| must be a multiple of |bytes_per_sample|. | |
516 int64 frame_size = bytes_per_sample * samples_to_generate; | |
517 | |
518 int64 timestamp = CurrentTimeStampInMicroseconds(); | |
519 | |
520 const int kHeaderSize = sizeof(timestamp) + sizeof(frame_size); | |
521 audio_frames->SetFrameBuffer(host_->Allocate(kHeaderSize + frame_size)); | |
522 uint8_t* data = audio_frames->FrameBuffer()->Data(); | |
523 | |
524 memcpy(data, ×tamp, sizeof(timestamp)); | |
525 data += sizeof(timestamp); | |
526 memcpy(data, &frame_size, sizeof(frame_size)); | |
527 data += sizeof(frame_size); | |
528 // You won't hear anything because we have all zeros here. But the video | |
529 // should play just fine! | |
530 memset(data, 0, frame_size); | |
531 | |
532 audio_frames->FrameBuffer()->SetSize(kHeaderSize + frame_size); | |
533 | |
534 return samples_to_generate; | |
535 } | |
536 | |
537 cdm::Status ClearKeyCdm::GenerateFakeAudioFrames( | |
538 int64 timestamp_in_microseconds, | |
539 cdm::AudioFrames* audio_frames) { | |
540 if (timestamp_in_microseconds == kNoTimestamp) | |
541 return cdm::kNeedMoreData; | |
542 | |
543 // Return kNeedMoreData for the first frame because duration is unknown. | |
544 if (output_timestamp_base_in_microseconds_ == kNoTimestamp) { | |
545 output_timestamp_base_in_microseconds_ = timestamp_in_microseconds; | |
546 return cdm::kNeedMoreData; | |
547 } | |
548 | |
549 int samples_generated = GenerateFakeAudioFramesFromDuration( | |
550 timestamp_in_microseconds - CurrentTimeStampInMicroseconds(), | |
551 audio_frames); | |
552 total_samples_generated_ += samples_generated; | |
553 | |
554 return samples_generated == 0 ? cdm::kNeedMoreData : cdm::kSuccess; | |
555 } | |
556 #endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER | |
557 | |
558 } // namespace webkit_media | |
OLD | NEW |