OLD | NEW |
| (Empty) |
1 // Copyright 2015 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 "media/base/android/media_codec_decoder.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "base/callback_helpers.h" | |
10 #include "base/logging.h" | |
11 #include "media/base/android/media_codec_bridge.h" | |
12 | |
13 namespace media { | |
14 | |
15 namespace { | |
16 | |
17 // Stop requesting new data in the kPrefetching state when the queue size | |
18 // reaches this limit. | |
19 const int kPrefetchLimit = 8; | |
20 | |
21 // Request new data in the kRunning state if the queue size is less than this. | |
22 const int kPlaybackLowLimit = 4; | |
23 | |
24 // Posting delay of the next frame processing, in milliseconds | |
25 const int kNextFrameDelay = 1; | |
26 | |
27 // Timeout for dequeuing an input buffer from MediaCodec in milliseconds. | |
28 const int kInputBufferTimeout = 20; | |
29 | |
30 // Timeout for dequeuing an output buffer from MediaCodec in milliseconds. | |
31 const int kOutputBufferTimeout = 20; | |
32 } | |
33 | |
34 MediaCodecDecoder::MediaCodecDecoder( | |
35 const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, | |
36 const base::Closure& request_data_cb, | |
37 const base::Closure& starvation_cb, | |
38 const base::Closure& stop_done_cb, | |
39 const base::Closure& error_cb, | |
40 const char* decoder_thread_name) | |
41 : media_task_runner_(media_task_runner), | |
42 decoder_thread_(decoder_thread_name), | |
43 request_data_cb_(request_data_cb), | |
44 starvation_cb_(starvation_cb), | |
45 stop_done_cb_(stop_done_cb), | |
46 error_cb_(error_cb), | |
47 state_(kStopped), | |
48 eos_enqueued_(false), | |
49 completed_(false), | |
50 last_frame_posted_(false), | |
51 weak_factory_(this) { | |
52 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
53 | |
54 DVLOG(1) << "Decoder::Decoder() " << decoder_thread_name; | |
55 | |
56 internal_error_cb_ = | |
57 base::Bind(&MediaCodecDecoder::OnCodecError, weak_factory_.GetWeakPtr()); | |
58 } | |
59 | |
60 MediaCodecDecoder::~MediaCodecDecoder() { | |
61 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
62 | |
63 DVLOG(1) << "Decoder::~Decoder()"; | |
64 | |
65 // NB: ReleaseDecoderResources() is virtual | |
66 ReleaseDecoderResources(); | |
67 } | |
68 | |
69 const char* MediaCodecDecoder::class_name() const { | |
70 return "Decoder"; | |
71 } | |
72 | |
73 void MediaCodecDecoder::ReleaseDecoderResources() { | |
74 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
75 | |
76 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
77 | |
78 decoder_thread_.Stop(); // synchronous | |
79 state_ = kStopped; | |
80 media_codec_bridge_.reset(); | |
81 } | |
82 | |
83 void MediaCodecDecoder::Flush() { | |
84 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
85 | |
86 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
87 | |
88 DCHECK_EQ(GetState(), kStopped); | |
89 | |
90 eos_enqueued_ = false; | |
91 completed_ = false; | |
92 au_queue_.Flush(); | |
93 | |
94 if (media_codec_bridge_) { | |
95 // MediaCodecBridge::Reset() performs MediaCodecBridge.flush() | |
96 MediaCodecStatus flush_status = media_codec_bridge_->Reset(); | |
97 if (flush_status != MEDIA_CODEC_OK) { | |
98 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
99 << "MediaCodecBridge::Reset() failed"; | |
100 media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); | |
101 } | |
102 } | |
103 } | |
104 | |
105 void MediaCodecDecoder::ReleaseMediaCodec() { | |
106 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
107 | |
108 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
109 | |
110 media_codec_bridge_.reset(); | |
111 } | |
112 | |
113 bool MediaCodecDecoder::IsPrefetchingOrPlaying() const { | |
114 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
115 | |
116 base::AutoLock lock(state_lock_); | |
117 return state_ == kPrefetching || state_ == kRunning; | |
118 } | |
119 | |
120 bool MediaCodecDecoder::IsStopped() const { | |
121 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
122 | |
123 return GetState() == kStopped; | |
124 } | |
125 | |
126 bool MediaCodecDecoder::IsCompleted() const { | |
127 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
128 | |
129 return completed_; | |
130 } | |
131 | |
132 base::android::ScopedJavaLocalRef<jobject> MediaCodecDecoder::GetMediaCrypto() { | |
133 base::android::ScopedJavaLocalRef<jobject> media_crypto; | |
134 | |
135 // TODO(timav): implement DRM. | |
136 // drm_bridge_ is not implemented | |
137 // if (drm_bridge_) | |
138 // media_crypto = drm_bridge_->GetMediaCrypto(); | |
139 return media_crypto; | |
140 } | |
141 | |
142 void MediaCodecDecoder::Prefetch(const base::Closure& prefetch_done_cb) { | |
143 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
144 | |
145 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
146 | |
147 DCHECK(GetState() == kStopped); | |
148 | |
149 prefetch_done_cb_ = prefetch_done_cb; | |
150 | |
151 SetState(kPrefetching); | |
152 PrefetchNextChunk(); | |
153 } | |
154 | |
155 MediaCodecDecoder::ConfigStatus MediaCodecDecoder::Configure() { | |
156 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
157 | |
158 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
159 | |
160 if (GetState() == kError) { | |
161 DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": wrong state kError"; | |
162 return CONFIG_FAILURE; | |
163 } | |
164 | |
165 // Here I assume that OnDemuxerConfigsAvailable won't come | |
166 // in the middle of demuxer data. | |
167 | |
168 if (media_codec_bridge_) { | |
169 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
170 << ": reconfiguration is not required, ignoring"; | |
171 return CONFIG_OK; | |
172 } | |
173 | |
174 return ConfigureInternal(); | |
175 } | |
176 | |
177 bool MediaCodecDecoder::Start(base::TimeDelta current_time) { | |
178 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
179 | |
180 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
181 << " current_time:" << current_time; | |
182 | |
183 DecoderState state = GetState(); | |
184 if (state == kRunning) { | |
185 DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": already started"; | |
186 return true; // already started | |
187 } | |
188 | |
189 if (state != kPrefetched) { | |
190 DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": wrong state " | |
191 << AsString(state) << " ignoring"; | |
192 return false; | |
193 } | |
194 | |
195 if (!media_codec_bridge_) { | |
196 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
197 << ": not configured, ignoring"; | |
198 return false; | |
199 } | |
200 | |
201 DCHECK(!decoder_thread_.IsRunning()); | |
202 | |
203 // We only synchronize video stream. | |
204 // When audio is present, the |current_time| is audio time. | |
205 SynchronizePTSWithTime(current_time); | |
206 | |
207 last_frame_posted_ = false; | |
208 | |
209 // Start the decoder thread | |
210 if (!decoder_thread_.Start()) { | |
211 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
212 << ": cannot start decoder thread"; | |
213 return false; | |
214 } | |
215 | |
216 SetState(kRunning); | |
217 | |
218 decoder_thread_.task_runner()->PostTask( | |
219 FROM_HERE, | |
220 base::Bind(&MediaCodecDecoder::ProcessNextFrame, base::Unretained(this))); | |
221 | |
222 return true; | |
223 } | |
224 | |
225 void MediaCodecDecoder::SyncStop() { | |
226 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
227 | |
228 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
229 | |
230 if (GetState() == kError) { | |
231 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
232 << ": wrong state kError, ignoring"; | |
233 return; | |
234 } | |
235 | |
236 // After this method returns, decoder thread will not be running. | |
237 | |
238 decoder_thread_.Stop(); // synchronous | |
239 state_ = kStopped; | |
240 | |
241 // Shall we move |delayed_buffers_| from VideoDecoder to Decoder class? | |
242 ReleaseDelayedBuffers(); | |
243 } | |
244 | |
245 void MediaCodecDecoder::RequestToStop() { | |
246 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
247 | |
248 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
249 | |
250 DecoderState state = GetState(); | |
251 switch (state) { | |
252 case kError: | |
253 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
254 << ": wrong state kError, ignoring"; | |
255 break; | |
256 case kRunning: | |
257 SetState(kStopping); | |
258 break; | |
259 case kStopping: | |
260 break; // ignore | |
261 case kStopped: | |
262 case kPrefetching: | |
263 case kPrefetched: | |
264 // There is nothing to wait for, we can sent nofigication right away. | |
265 DCHECK(!decoder_thread_.IsRunning()); | |
266 SetState(kStopped); | |
267 media_task_runner_->PostTask(FROM_HERE, stop_done_cb_); | |
268 break; | |
269 default: | |
270 NOTREACHED(); | |
271 break; | |
272 } | |
273 } | |
274 | |
275 void MediaCodecDecoder::OnLastFrameRendered(bool completed) { | |
276 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
277 | |
278 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
279 << " completed:" << completed; | |
280 | |
281 decoder_thread_.Stop(); // synchronous | |
282 state_ = kStopped; | |
283 completed_ = completed; | |
284 | |
285 media_task_runner_->PostTask(FROM_HERE, stop_done_cb_); | |
286 } | |
287 | |
288 void MediaCodecDecoder::OnDemuxerDataAvailable(const DemuxerData& data) { | |
289 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
290 | |
291 DVLOG(2) << class_name() << "::" << __FUNCTION__ | |
292 << " #AUs:" << data.access_units.size() | |
293 << " #Configs:" << data.demuxer_configs.size(); | |
294 #if !defined(NDEBUG) | |
295 for (const auto& unit : data.access_units) | |
296 DVLOG(2) << class_name() << "::" << __FUNCTION__ << " au: " << unit; | |
297 #endif | |
298 | |
299 au_queue_.PushBack(data); | |
300 | |
301 if (state_ == kPrefetching) | |
302 PrefetchNextChunk(); | |
303 } | |
304 | |
305 int MediaCodecDecoder::NumDelayedRenderTasks() const { | |
306 return 0; | |
307 } | |
308 | |
309 void MediaCodecDecoder::CheckLastFrame(bool eos_encountered, | |
310 bool has_delayed_tasks) { | |
311 DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); | |
312 | |
313 bool last_frame_when_stopping = GetState() == kStopping && !has_delayed_tasks; | |
314 | |
315 if (last_frame_when_stopping || eos_encountered) { | |
316 media_task_runner_->PostTask( | |
317 FROM_HERE, base::Bind(&MediaCodecDecoder::OnLastFrameRendered, | |
318 weak_factory_.GetWeakPtr(), eos_encountered)); | |
319 last_frame_posted_ = true; | |
320 } | |
321 } | |
322 | |
323 void MediaCodecDecoder::OnCodecError() { | |
324 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
325 | |
326 SetState(kError); | |
327 error_cb_.Run(); | |
328 } | |
329 | |
330 void MediaCodecDecoder::PrefetchNextChunk() { | |
331 DCHECK(media_task_runner_->BelongsToCurrentThread()); | |
332 | |
333 DVLOG(1) << class_name() << "::" << __FUNCTION__; | |
334 | |
335 AccessUnitQueue::Info au_info = au_queue_.GetInfo(); | |
336 | |
337 if (eos_enqueued_ || au_info.length >= kPrefetchLimit || au_info.has_eos) { | |
338 // We are done prefetching | |
339 SetState(kPrefetched); | |
340 DVLOG(1) << class_name() << "::" << __FUNCTION__ << " posting PrefetchDone"; | |
341 media_task_runner_->PostTask(FROM_HERE, | |
342 base::ResetAndReturn(&prefetch_done_cb_)); | |
343 return; | |
344 } | |
345 | |
346 request_data_cb_.Run(); | |
347 } | |
348 | |
349 void MediaCodecDecoder::ProcessNextFrame() { | |
350 DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); | |
351 | |
352 DVLOG(2) << class_name() << "::" << __FUNCTION__; | |
353 | |
354 DecoderState state = GetState(); | |
355 | |
356 if (state != kRunning && state != kStopping) { | |
357 DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": not running"; | |
358 return; | |
359 } | |
360 | |
361 if (state == kStopping) { | |
362 if (NumDelayedRenderTasks() == 0 && !last_frame_posted_) { | |
363 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
364 << ": kStopping, posting OnLastFrameRendered"; | |
365 media_task_runner_->PostTask( | |
366 FROM_HERE, base::Bind(&MediaCodecDecoder::OnLastFrameRendered, | |
367 weak_factory_.GetWeakPtr(), false)); | |
368 last_frame_posted_ = true; | |
369 } | |
370 | |
371 // We can stop processing, the |au_queue_| and MediaCodec queues can freeze. | |
372 // We only need to let finish the delayed rendering tasks. | |
373 return; | |
374 } | |
375 | |
376 DCHECK(state == kRunning); | |
377 | |
378 if (!EnqueueInputBuffer()) | |
379 return; | |
380 | |
381 bool eos_encountered = false; | |
382 if (!DepleteOutputBufferQueue(&eos_encountered)) | |
383 return; | |
384 | |
385 if (eos_encountered) { | |
386 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
387 << " EOS dequeued, stopping frame processing"; | |
388 return; | |
389 } | |
390 | |
391 // We need a small delay if we want to stop this thread by | |
392 // decoder_thread_.Stop() reliably. | |
393 // The decoder thread message loop processes all pending | |
394 // (but not delayed) tasks before it can quit; without a delay | |
395 // the message loop might be forever processing the pendng tasks. | |
396 decoder_thread_.task_runner()->PostDelayedTask( | |
397 FROM_HERE, | |
398 base::Bind(&MediaCodecDecoder::ProcessNextFrame, base::Unretained(this)), | |
399 base::TimeDelta::FromMilliseconds(kNextFrameDelay)); | |
400 } | |
401 | |
402 // Returns false if we should stop decoding process. Right now | |
403 // it happens if we got MediaCodec error or detected starvation. | |
404 bool MediaCodecDecoder::EnqueueInputBuffer() { | |
405 DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); | |
406 | |
407 DVLOG(2) << class_name() << "::" << __FUNCTION__; | |
408 | |
409 if (eos_enqueued_) { | |
410 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
411 << ": eos_enqueued, returning"; | |
412 return true; // Nothing to do | |
413 } | |
414 | |
415 // Keep the number pending video frames low, ideally maintaining | |
416 // the same audio and video duration after stop request | |
417 if (NumDelayedRenderTasks() > 1) { | |
418 DVLOG(2) << class_name() << "::" << __FUNCTION__ << ": # delayed buffers (" | |
419 << NumDelayedRenderTasks() << ") exceeds 1, returning"; | |
420 return true; // Nothing to do | |
421 } | |
422 | |
423 // Get the next frame from the queue and the queue info | |
424 | |
425 AccessUnitQueue::Info au_info = au_queue_.GetInfo(); | |
426 | |
427 // Request the data from Demuxer | |
428 if (au_info.length <= kPlaybackLowLimit && !au_info.has_eos) | |
429 media_task_runner_->PostTask(FROM_HERE, request_data_cb_); | |
430 | |
431 // Get the next frame from the queue | |
432 | |
433 if (!au_info.length) { | |
434 // Report starvation and return, Start() will be called again later. | |
435 DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": starvation detected"; | |
436 media_task_runner_->PostTask(FROM_HERE, starvation_cb_); | |
437 return false; | |
438 } | |
439 | |
440 if (au_info.configs) { | |
441 DVLOG(1) << class_name() << "::" << __FUNCTION__ | |
442 << ": received new configs, not implemented"; | |
443 // post an error for now? | |
444 media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); | |
445 return false; | |
446 } | |
447 | |
448 // Dequeue input buffer | |
449 | |
450 base::TimeDelta timeout = | |
451 base::TimeDelta::FromMilliseconds(kInputBufferTimeout); | |
452 int index = -1; | |
453 MediaCodecStatus status = | |
454 media_codec_bridge_->DequeueInputBuffer(timeout, &index); | |
455 | |
456 DVLOG(2) << class_name() << ":: DequeueInputBuffer index:" << index; | |
457 | |
458 switch (status) { | |
459 case MEDIA_CODEC_ERROR: | |
460 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
461 << ": MEDIA_CODEC_ERROR DequeueInputBuffer failed"; | |
462 media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); | |
463 return false; | |
464 | |
465 case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER: | |
466 return true; | |
467 | |
468 default: | |
469 break; | |
470 } | |
471 | |
472 // We got the buffer | |
473 DCHECK_EQ(status, MEDIA_CODEC_OK); | |
474 DCHECK_GE(index, 0); | |
475 | |
476 const AccessUnit* unit = au_info.front_unit; | |
477 DCHECK(unit); | |
478 | |
479 if (unit->is_end_of_stream) { | |
480 DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": QueueEOS"; | |
481 media_codec_bridge_->QueueEOS(index); | |
482 eos_enqueued_ = true; | |
483 return true; | |
484 } | |
485 | |
486 DVLOG(2) << class_name() << ":: QueueInputBuffer pts:" << unit->timestamp; | |
487 | |
488 status = media_codec_bridge_->QueueInputBuffer( | |
489 index, &unit->data[0], unit->data.size(), unit->timestamp); | |
490 | |
491 if (status == MEDIA_CODEC_ERROR) { | |
492 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
493 << ": MEDIA_CODEC_ERROR: QueueInputBuffer failed"; | |
494 media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); | |
495 return false; | |
496 } | |
497 | |
498 // Have successfully queued input buffer, go to next access unit. | |
499 au_queue_.Advance(); | |
500 return true; | |
501 } | |
502 | |
503 // Returns false if there was MediaCodec error. | |
504 bool MediaCodecDecoder::DepleteOutputBufferQueue(bool* eos_encountered) { | |
505 DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); | |
506 | |
507 DVLOG(2) << class_name() << "::" << __FUNCTION__; | |
508 | |
509 int buffer_index = 0; | |
510 size_t offset = 0; | |
511 size_t size = 0; | |
512 base::TimeDelta pts; | |
513 MediaCodecStatus status; | |
514 | |
515 base::TimeDelta timeout = | |
516 base::TimeDelta::FromMilliseconds(kOutputBufferTimeout); | |
517 | |
518 // Extract all output buffers that are available. | |
519 // Usually there will be only one, but sometimes it is preceeded by | |
520 // MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED or MEDIA_CODEC_OUTPUT_FORMAT_CHANGED. | |
521 do { | |
522 status = media_codec_bridge_->DequeueOutputBuffer( | |
523 timeout, &buffer_index, &offset, &size, &pts, eos_encountered, nullptr); | |
524 | |
525 // Reset the timeout to 0 for the subsequent DequeueOutputBuffer() calls | |
526 // to quickly break the loop after we got all currently available buffers. | |
527 timeout = base::TimeDelta::FromMilliseconds(0); | |
528 | |
529 switch (status) { | |
530 case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED: | |
531 // Output buffers are replaced in MediaCodecBridge, nothing to do. | |
532 break; | |
533 | |
534 case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: | |
535 DVLOG(2) << class_name() << "::" << __FUNCTION__ | |
536 << " MEDIA_CODEC_OUTPUT_FORMAT_CHANGED"; | |
537 OnOutputFormatChanged(); | |
538 break; | |
539 | |
540 case MEDIA_CODEC_OK: | |
541 // We got the decoded frame | |
542 Render(buffer_index, size, true, pts, *eos_encountered); | |
543 break; | |
544 | |
545 case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER: | |
546 // Nothing to do. | |
547 break; | |
548 | |
549 case MEDIA_CODEC_ERROR: | |
550 DVLOG(0) << class_name() << "::" << __FUNCTION__ | |
551 << ": MEDIA_CODEC_ERROR from DequeueOutputBuffer"; | |
552 media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); | |
553 break; | |
554 | |
555 default: | |
556 NOTREACHED(); | |
557 break; | |
558 } | |
559 | |
560 } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER && | |
561 status != MEDIA_CODEC_ERROR && !*eos_encountered); | |
562 | |
563 return status != MEDIA_CODEC_ERROR; | |
564 } | |
565 | |
566 MediaCodecDecoder::DecoderState MediaCodecDecoder::GetState() const { | |
567 base::AutoLock lock(state_lock_); | |
568 return state_; | |
569 } | |
570 | |
571 void MediaCodecDecoder::SetState(DecoderState state) { | |
572 DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << state; | |
573 | |
574 base::AutoLock lock(state_lock_); | |
575 state_ = state; | |
576 } | |
577 | |
578 #undef RETURN_STRING | |
579 #define RETURN_STRING(x) \ | |
580 case x: \ | |
581 return #x; | |
582 | |
583 const char* MediaCodecDecoder::AsString(DecoderState state) { | |
584 switch (state) { | |
585 RETURN_STRING(kStopped); | |
586 RETURN_STRING(kPrefetching); | |
587 RETURN_STRING(kPrefetched); | |
588 RETURN_STRING(kRunning); | |
589 RETURN_STRING(kStopping); | |
590 RETURN_STRING(kError); | |
591 default: | |
592 return "Unknown DecoderState"; | |
593 } | |
594 } | |
595 | |
596 #undef RETURN_STRING | |
597 | |
598 } // namespace media | |
OLD | NEW |