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