Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/base/pipeline.h" | 5 #include "media/base/pipeline.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/callback.h" | 10 #include "base/callback.h" |
| 11 #include "base/callback_helpers.h" | 11 #include "base/callback_helpers.h" |
| 12 #include "base/compiler_specific.h" | 12 #include "base/compiler_specific.h" |
| 13 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
| 14 #include "base/message_loop.h" | 14 #include "base/message_loop.h" |
| 15 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
| 16 #include "base/string_number_conversions.h" | 16 #include "base/string_number_conversions.h" |
| 17 #include "base/string_util.h" | 17 #include "base/string_util.h" |
| 18 #include "base/synchronization/condition_variable.h" | 18 #include "base/synchronization/condition_variable.h" |
| 19 #include "media/base/audio_decoder.h" | 19 #include "media/base/audio_decoder.h" |
| 20 #include "media/base/audio_renderer.h" | 20 #include "media/base/audio_renderer.h" |
| 21 #include "media/base/buffers.h" | |
| 22 #include "media/base/clock.h" | 21 #include "media/base/clock.h" |
| 23 #include "media/base/filter_collection.h" | 22 #include "media/base/filter_collection.h" |
| 24 #include "media/base/media_log.h" | 23 #include "media/base/media_log.h" |
| 25 #include "media/base/video_decoder.h" | 24 #include "media/base/video_decoder.h" |
| 26 #include "media/base/video_decoder_config.h" | 25 #include "media/base/video_decoder_config.h" |
| 27 #include "media/base/video_renderer.h" | 26 #include "media/base/video_renderer.h" |
| 28 | 27 |
| 29 using base::TimeDelta; | 28 using base::TimeDelta; |
| 30 | 29 |
| 31 namespace media { | 30 namespace media { |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 56 while (!notified_) | 55 while (!notified_) |
| 57 cv_.Wait(); | 56 cv_.Wait(); |
| 58 } | 57 } |
| 59 | 58 |
| 60 media::PipelineStatus PipelineStatusNotification::status() { | 59 media::PipelineStatus PipelineStatusNotification::status() { |
| 61 base::AutoLock auto_lock(lock_); | 60 base::AutoLock auto_lock(lock_); |
| 62 DCHECK(notified_); | 61 DCHECK(notified_); |
| 63 return status_; | 62 return status_; |
| 64 } | 63 } |
| 65 | 64 |
| 66 struct Pipeline::PipelineInitState { | |
| 67 scoped_refptr<AudioDecoder> audio_decoder; | |
| 68 }; | |
| 69 | |
| 70 Pipeline::Pipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop, | 65 Pipeline::Pipeline(const scoped_refptr<base::MessageLoopProxy>& message_loop, |
| 71 MediaLog* media_log) | 66 MediaLog* media_log) |
| 72 : message_loop_(message_loop), | 67 : message_loop_(message_loop), |
| 73 media_log_(media_log), | 68 media_log_(media_log), |
| 74 running_(false), | 69 running_(false), |
| 75 seek_pending_(false), | |
| 76 tearing_down_(false), | |
| 77 playback_rate_change_pending_(false), | |
| 78 did_loading_progress_(false), | 70 did_loading_progress_(false), |
| 79 total_bytes_(0), | 71 total_bytes_(0), |
| 80 natural_size_(0, 0), | 72 natural_size_(0, 0), |
| 81 volume_(1.0f), | 73 volume_(1.0f), |
| 82 playback_rate_(0.0f), | 74 playback_rate_(0.0f), |
| 83 pending_playback_rate_(0.0f), | |
| 84 clock_(new Clock(&base::Time::Now)), | 75 clock_(new Clock(&base::Time::Now)), |
| 85 waiting_for_clock_update_(false), | 76 waiting_for_clock_update_(false), |
| 86 status_(PIPELINE_OK), | 77 status_(PIPELINE_OK), |
| 87 has_audio_(false), | 78 has_audio_(false), |
| 88 has_video_(false), | 79 has_video_(false), |
| 89 state_(kCreated), | 80 state_(kCreated), |
| 90 seek_timestamp_(kNoTimestamp()), | |
| 91 audio_ended_(false), | 81 audio_ended_(false), |
| 92 video_ended_(false), | 82 video_ended_(false), |
| 93 audio_disabled_(false), | 83 audio_disabled_(false), |
| 94 creation_time_(base::Time::Now()) { | 84 creation_time_(base::Time::Now()) { |
| 95 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); | 85 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); |
| 96 media_log_->AddEvent( | 86 media_log_->AddEvent( |
| 97 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); | 87 media_log_->CreateEvent(MediaLogEvent::PIPELINE_CREATED)); |
| 98 } | 88 } |
| 99 | 89 |
| 100 Pipeline::~Pipeline() { | 90 Pipeline::~Pipeline() { |
| 101 base::AutoLock auto_lock(lock_); | 91 base::AutoLock auto_lock(lock_); |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
lock in dtor ==> bug
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 102 DCHECK(!running_) << "Stop() must complete before destroying object"; | 92 DCHECK(!running_) << "Stop() must complete before destroying object"; |
| 103 DCHECK(stop_cb_.is_null()); | 93 DCHECK(stop_cb_.is_null()); |
| 104 DCHECK(!seek_pending_); | 94 DCHECK(seek_cb_.is_null()); |
| 105 | 95 |
| 106 media_log_->AddEvent( | 96 media_log_->AddEvent( |
| 107 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); | 97 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); |
| 108 } | 98 } |
| 109 | 99 |
| 110 void Pipeline::Start(scoped_ptr<FilterCollection> collection, | 100 void Pipeline::Start(scoped_ptr<FilterCollection> collection, |
| 111 const PipelineStatusCB& ended_cb, | 101 const PipelineStatusCB& ended_cb, |
| 112 const PipelineStatusCB& error_cb, | 102 const PipelineStatusCB& error_cb, |
| 113 const PipelineStatusCB& seek_cb, | 103 const PipelineStatusCB& seek_cb, |
| 114 const BufferingStateCB& buffering_state_cb) { | 104 const BufferingStateCB& buffering_state_cb) { |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 253 if (state_ != kStarted && next_state == kStarted && | 243 if (state_ != kStarted && next_state == kStarted && |
| 254 !creation_time_.is_null()) { | 244 !creation_time_.is_null()) { |
| 255 UMA_HISTOGRAM_TIMES( | 245 UMA_HISTOGRAM_TIMES( |
| 256 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); | 246 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); |
| 257 creation_time_ = base::Time(); | 247 creation_time_ = base::Time(); |
| 258 } | 248 } |
| 259 state_ = next_state; | 249 state_ = next_state; |
| 260 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); | 250 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); |
| 261 } | 251 } |
| 262 | 252 |
| 263 bool Pipeline::IsPipelineOk() { | 253 #define RETURN_STRING(state) \ |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
s/R_S/MAYBE_RETURN_STRING/?
scherkus (not reviewing)
2012/09/05 15:04:17
Can't think of a good name and MAYBE_R_S seems fun
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
SGTM. Or leave as-is, given the short-livedness o
| |
| 264 base::AutoLock auto_lock(lock_); | 254 case state: return #state; |
| 265 return status_ == PIPELINE_OK; | 255 |
| 256 const char* Pipeline::GetStateString(State state) { | |
| 257 switch (state) { | |
| 258 RETURN_STRING(kCreated); | |
| 259 RETURN_STRING(kInitDemuxer); | |
| 260 RETURN_STRING(kInitAudioDecoder); | |
| 261 RETURN_STRING(kInitAudioRenderer); | |
| 262 RETURN_STRING(kInitVideoRenderer); | |
| 263 RETURN_STRING(kInitPrerolling); | |
| 264 RETURN_STRING(kSeeking); | |
| 265 RETURN_STRING(kStarting); | |
| 266 RETURN_STRING(kStarted); | |
| 267 RETURN_STRING(kStopping); | |
| 268 RETURN_STRING(kStopped); | |
| 269 } | |
| 270 return ""; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
NOTREACHED?
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
nit: How about "INVALID_STATE" instead of "" so th
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 266 } | 271 } |
| 267 | 272 |
| 268 bool Pipeline::IsPipelineSeeking() { | 273 #undef RETURN_STRING |
| 274 | |
| 275 Pipeline::State Pipeline::GetNextState() const { | |
| 269 DCHECK(message_loop_->BelongsToCurrentThread()); | 276 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 270 if (!seek_pending_) | 277 DCHECK(stop_cb_.is_null()) |
| 271 return false; | 278 << "State transitions don't happen when stopping"; |
| 272 DCHECK(kSeeking == state_ || kPausing == state_ || | 279 DCHECK_EQ(status_, PIPELINE_OK) |
| 273 kFlushing == state_ || kStarting == state_) | 280 << "State transitions don't happen when there's an error"; |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
emit status_
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 274 << "Current state : " << state_; | |
| 275 return true; | |
| 276 } | |
| 277 | 281 |
| 278 void Pipeline::ReportStatus(const PipelineStatusCB& cb, PipelineStatus status) { | 282 switch (state_) { |
| 279 DCHECK(message_loop_->BelongsToCurrentThread()); | 283 case kCreated: |
| 280 if (cb.is_null()) | 284 return kInitDemuxer; |
| 281 return; | 285 case kInitDemuxer: |
| 282 cb.Run(status); | 286 return kInitAudioDecoder; |
| 283 // Prevent double-reporting of errors to clients. | 287 case kInitAudioDecoder: |
| 284 if (status != PIPELINE_OK) | 288 return kInitAudioRenderer; |
| 285 error_cb_.Reset(); | 289 case kInitAudioRenderer: |
| 286 } | 290 return kInitVideoRenderer; |
| 291 case kInitVideoRenderer: | |
| 292 return kInitPrerolling; | |
| 293 case kInitPrerolling: | |
| 294 return kStarting; | |
| 295 case kSeeking: | |
| 296 return kStarting; | |
| 297 case kStarting: | |
| 298 return kStarted; | |
| 287 | 299 |
| 288 void Pipeline::FinishSeek() { | 300 case kStarted: |
| 289 DCHECK(message_loop_->BelongsToCurrentThread()); | 301 case kStopping: |
| 290 seek_timestamp_ = kNoTimestamp(); | 302 case kStopped: |
| 291 seek_pending_ = false; | 303 break; |
| 292 | |
| 293 // Execute the seek callback, if present. Note that this might be the | |
| 294 // initial callback passed into Start(). | |
| 295 ReportStatus(seek_cb_, status_); | |
| 296 seek_cb_.Reset(); | |
| 297 } | |
| 298 | |
| 299 // static | |
| 300 bool Pipeline::TransientState(State state) { | |
| 301 return state == kPausing || | |
| 302 state == kFlushing || | |
| 303 state == kSeeking || | |
| 304 state == kStarting || | |
| 305 state == kStopping; | |
| 306 } | |
| 307 | |
| 308 // static | |
| 309 Pipeline::State Pipeline::FindNextState(State current) { | |
| 310 // TODO(scherkus): refactor InitializeTask() to make use of this function. | |
| 311 if (current == kPausing) { | |
| 312 return kFlushing; | |
| 313 } else if (current == kFlushing) { | |
| 314 // We will always honor Seek() before Stop(). This is based on the | |
| 315 // assumption that we never accept Seek() after Stop(). | |
| 316 DCHECK(IsPipelineSeeking() || | |
| 317 !stop_cb_.is_null() || | |
| 318 tearing_down_); | |
| 319 return IsPipelineSeeking() ? kSeeking : kStopping; | |
| 320 } else if (current == kSeeking) { | |
| 321 return kStarting; | |
| 322 } else if (current == kStarting) { | |
| 323 return kStarted; | |
| 324 } else if (current == kStopping) { | |
| 325 return kStopped; | |
| 326 } else { | |
| 327 return current; | |
| 328 } | 304 } |
| 305 NOTREACHED() << "State has no transition: " << state_; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
Put this and the next line at l.303?
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 306 return state_; | |
| 329 } | 307 } |
| 330 | 308 |
| 331 void Pipeline::OnDemuxerError(PipelineStatus error) { | 309 void Pipeline::OnDemuxerError(PipelineStatus error) { |
| 332 SetError(error); | 310 SetError(error); |
| 333 } | 311 } |
| 334 | 312 |
| 335 void Pipeline::SetError(PipelineStatus error) { | 313 void Pipeline::SetError(PipelineStatus error) { |
| 336 DCHECK(IsRunning()); | 314 DCHECK(IsRunning()); |
| 337 DCHECK_NE(PIPELINE_OK, error); | 315 DCHECK_NE(PIPELINE_OK, error); |
| 338 VLOG(1) << "Media pipeline error: " << error; | 316 VLOG(1) << "Media pipeline error: " << error; |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 415 // Since the byte->time calculation is approximate, fudge the beginning & | 393 // Since the byte->time calculation is approximate, fudge the beginning & |
| 416 // ending areas to look better. | 394 // ending areas to look better. |
| 417 TimeDelta epsilon = clock_->Duration() / 100; | 395 TimeDelta epsilon = clock_->Duration() / 100; |
| 418 if (time_offset < epsilon) | 396 if (time_offset < epsilon) |
| 419 return TimeDelta(); | 397 return TimeDelta(); |
| 420 if (time_offset + epsilon > clock_->Duration()) | 398 if (time_offset + epsilon > clock_->Duration()) |
| 421 return clock_->Duration(); | 399 return clock_->Duration(); |
| 422 return time_offset; | 400 return time_offset; |
| 423 } | 401 } |
| 424 | 402 |
| 425 void Pipeline::DoPause(const PipelineStatusCB& done_cb) { | 403 void Pipeline::OnStateTransition(PipelineStatus status) { |
| 404 // Force post to process state transitions after current execution frame. | |
| 405 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 406 &Pipeline::DoStateTransition, this, status)); | |
| 407 } | |
| 408 | |
| 409 void Pipeline::DoStateTransition(PipelineStatus status) { | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
why not StateTransitionTask?
scherkus (not reviewing)
2012/09/05 15:04:17
Somewhere along the way I started mixing task-post
| |
| 410 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 411 | |
| 412 // Preserve existing abnormal status, otherwise update based on the result of | |
| 413 // the previous operation. | |
| 414 status_ = (status_ != PIPELINE_OK ? status_ : status); | |
| 415 | |
| 416 // No-op any state transitions if we're stopping. | |
| 417 if (state_ == kStopping || state_ == kStopped) | |
| 418 return; | |
| 419 | |
| 420 if (status_ != PIPELINE_OK) { | |
| 421 ErrorChangedTask(status_); | |
| 422 return; | |
| 423 } | |
| 424 | |
| 425 // TODO(scherkus): not every state transition uses |pending_callbacks_| today. | |
| 426 if (state_ == kInitPrerolling || state_ == kStarting || state_ == kSeeking) { | |
| 427 DCHECK(pending_callbacks_.get()) | |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
nit: !pending_callbacks_.is_null() ?
scherkus (not reviewing)
2012/09/05 15:04:17
Done (see new code / response to fischman)
| |
| 428 << "Filter state transitions must be completed via pending_callbacks_"; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
wat?
Does this want to
DCHECK_EQ(pending_callbacks
scherkus (not reviewing)
2012/09/05 15:04:17
Yes
| |
| 429 } | |
| 430 pending_callbacks_.reset(); | |
| 431 | |
| 432 PipelineStatusCB done_cb = base::Bind(&Pipeline::OnStateTransition, this); | |
| 433 | |
| 434 DVLOG(2) << GetStateString(state_) << " -> " | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
Put this idea in SetState() ?
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 435 << GetStateString(GetNextState()); | |
| 436 | |
| 437 SetState(GetNextState()); | |
| 438 switch (state_) { | |
| 439 case kInitDemuxer: | |
| 440 InitializeDemuxer(done_cb); | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
FWIW I'm pretty sure all our toolchains support re
scherkus (not reviewing)
2012/09/05 15:04:17
I find it odd looking but will give it a shot.
| |
| 441 return; | |
| 442 | |
| 443 case kInitAudioDecoder: | |
| 444 { | |
| 445 base::AutoLock l(lock_); | |
| 446 // We do not want to start the clock running. We only want to set the | |
| 447 // base media time so our timestamp calculations will be correct. | |
| 448 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime()); | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
What can race with this?
IOW why the lock?
scherkus (not reviewing)
2012/09/05 15:04:17
Pipeline::GetMediaTime() is called from any thread
| |
| 449 } | |
| 450 InitializeAudioDecoder(done_cb); | |
| 451 return; | |
| 452 | |
| 453 case kInitAudioRenderer: | |
| 454 InitializeAudioRenderer(done_cb); | |
| 455 return; | |
| 456 | |
| 457 case kInitVideoRenderer: | |
| 458 InitializeVideoRenderer(done_cb); | |
| 459 return; | |
| 460 | |
| 461 case kInitPrerolling: | |
| 462 filter_collection_.reset(); | |
| 463 { | |
| 464 base::AutoLock l(lock_); | |
| 465 // TODO(scherkus): |has_audio_| should be true no matter what -- | |
| 466 // otherwise people with muted/disabled sound cards will make our | |
| 467 // default controls look as if every video doesn't contain an audio | |
| 468 // track. | |
| 469 has_audio_ = !!audio_renderer_ && !audio_disabled_; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
Are the !!'s really necessary? (ditto for next li
scherkus (not reviewing)
2012/09/05 15:04:17
AFAIK it gets around MSVC performance warning -- s
| |
| 470 has_video_ = !!video_renderer_; | |
| 471 } | |
| 472 if (!audio_renderer_ && !video_renderer_) { | |
| 473 done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER); | |
| 474 return; | |
| 475 } | |
| 476 | |
| 477 buffering_state_cb_.Run(kHaveMetadata); | |
| 478 | |
| 479 DoInitialPreroll(demuxer_->GetStartTime(), done_cb); | |
| 480 return; | |
| 481 | |
| 482 case kStarting: | |
| 483 DoPlay(done_cb); | |
| 484 return; | |
| 485 | |
| 486 case kStarted: | |
| 487 { | |
| 488 base::AutoLock l(lock_); | |
| 489 // We use audio stream to update the clock. So if there is such a | |
| 490 // stream, we pause the clock until we receive a valid timestamp. | |
| 491 waiting_for_clock_update_ = true; | |
| 492 if (!has_audio_) { | |
| 493 clock_->SetMaxTime(clock_->Duration()); | |
| 494 StartClockIfWaitingForTimeUpdate_Locked(); | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 // Fire canplaythrough immediately after playback begins because of | |
| 499 // crbug.com/106480. | |
| 500 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. | |
| 501 if (status_ == PIPELINE_OK) | |
| 502 buffering_state_cb_.Run(kPrerollCompleted); | |
| 503 | |
| 504 DCHECK(!seek_cb_.is_null()); | |
| 505 DCHECK_EQ(status_, PIPELINE_OK); | |
| 506 base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK); | |
| 507 return; | |
| 508 | |
| 509 case kStopping: | |
| 510 case kStopped: | |
| 511 case kCreated: | |
| 512 case kSeeking: | |
| 513 NOTREACHED() << "State has no transition: " << state_; | |
| 514 break; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
You should see the comment two lines down and retu
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 515 } | |
| 516 NOTREACHED() << "You should return after transitioning, not break!"; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
A smart compiler should flag this as dead code. N
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 517 } | |
| 518 | |
| 519 void Pipeline::DoInitialPreroll( | |
| 520 const base::TimeDelta& seek_timestamp, | |
| 521 const PipelineStatusCB& done_cb) { | |
| 426 DCHECK(message_loop_->BelongsToCurrentThread()); | 522 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 427 DCHECK(!pending_callbacks_.get()); | 523 DCHECK(!pending_callbacks_.get()); |
| 428 SerialRunner::Queue bound_fns; | 524 SerialRunner::Queue bound_fns; |
| 429 | 525 |
| 430 if (audio_renderer_) | 526 // Preroll renderers. |
| 431 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_)); | 527 if (audio_renderer_) { |
| 528 bound_fns.Push(base::Bind( | |
| 529 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 530 } | |
| 432 | 531 |
| 433 if (video_renderer_) | 532 if (video_renderer_) { |
| 434 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_)); | 533 bound_fns.Push(base::Bind( |
| 534 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 535 } | |
| 435 | 536 |
| 436 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 537 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 437 } | 538 } |
| 438 | 539 |
| 439 void Pipeline::DoFlush(const PipelineStatusCB& done_cb) { | 540 void Pipeline::DoSeek( |
| 541 const base::TimeDelta& seek_timestamp, | |
| 542 const PipelineStatusCB& done_cb) { | |
| 440 DCHECK(message_loop_->BelongsToCurrentThread()); | 543 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 441 DCHECK(!pending_callbacks_.get()); | 544 DCHECK(!pending_callbacks_.get()); |
| 442 SerialRunner::Queue bound_fns; | 545 SerialRunner::Queue bound_fns; |
| 443 | 546 |
| 547 // Pause. | |
| 548 if (audio_renderer_) | |
| 549 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_)); | |
| 550 if (video_renderer_) | |
| 551 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_)); | |
| 552 | |
| 553 // Flush. | |
| 444 if (audio_renderer_) | 554 if (audio_renderer_) |
| 445 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); | 555 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); |
| 446 | |
| 447 if (video_renderer_) | 556 if (video_renderer_) |
| 448 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_)); | 557 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_)); |
| 449 | 558 |
| 559 // Seek demuxer. | |
| 560 bound_fns.Push(base::Bind( | |
| 561 &Demuxer::Seek, demuxer_, seek_timestamp)); | |
| 562 | |
| 563 // Preroll renderers. | |
| 564 if (audio_renderer_) { | |
| 565 bound_fns.Push(base::Bind( | |
| 566 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 567 } | |
| 568 | |
| 569 if (video_renderer_) { | |
| 570 bound_fns.Push(base::Bind( | |
| 571 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 572 } | |
| 573 | |
| 450 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 574 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 451 } | 575 } |
| 452 | 576 |
| 453 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { | 577 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { |
| 454 DCHECK(message_loop_->BelongsToCurrentThread()); | 578 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 455 DCHECK(!pending_callbacks_.get()); | 579 DCHECK(!pending_callbacks_.get()); |
| 456 SerialRunner::Queue bound_fns; | 580 SerialRunner::Queue bound_fns; |
| 457 | 581 |
| 582 PlaybackRateChangedTask(GetPlaybackRate()); | |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Why was this moved from before initial preroll to
scherkus (not reviewing)
2012/09/05 15:04:17
Good catch -- playback rate should always get set
| |
| 583 VolumeChangedTask(GetVolume()); | |
| 584 | |
| 458 if (audio_renderer_) | 585 if (audio_renderer_) |
| 459 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_)); | 586 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_)); |
| 460 | 587 |
| 461 if (video_renderer_) | 588 if (video_renderer_) |
| 462 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_)); | 589 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_)); |
| 463 | 590 |
| 464 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 591 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 465 } | 592 } |
| 466 | 593 |
| 467 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { | 594 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { |
| 468 DCHECK(message_loop_->BelongsToCurrentThread()); | 595 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 469 DCHECK(!pending_callbacks_.get()); | 596 DCHECK(!pending_callbacks_.get()); |
| 470 SerialRunner::Queue bound_fns; | 597 SerialRunner::Queue bound_fns; |
| 471 | 598 |
| 472 if (demuxer_) | 599 if (demuxer_) |
| 473 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_)); | 600 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_)); |
| 474 | 601 |
| 475 if (audio_renderer_) | 602 if (audio_renderer_) |
| 476 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); | 603 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); |
| 477 | 604 |
| 478 if (video_renderer_) | 605 if (video_renderer_) |
| 479 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_)); | 606 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_)); |
| 480 | 607 |
| 481 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | 608 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); |
| 482 } | 609 } |
| 483 | 610 |
| 611 void Pipeline::OnStopCompleted(PipelineStatus status) { | |
| 612 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 613 DCHECK_EQ(state_, kStopping); | |
| 614 SetState(kStopped); | |
| 615 | |
| 616 pending_callbacks_.reset(); | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
Shouldn't you be able to DCHECK that this already
scherkus (not reviewing)
2012/09/05 15:04:17
No -- pending_callbacks_ is non-NULL but has compl
| |
| 617 filter_collection_.reset(); | |
| 618 audio_decoder_ = NULL; | |
| 619 audio_renderer_ = NULL; | |
| 620 video_renderer_ = NULL; | |
| 621 demuxer_ = NULL; | |
| 622 { | |
| 623 base::AutoLock l(lock_); | |
| 624 running_ = false; | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
You sure you don't want to do this first?
scherkus (not reviewing)
2012/09/05 15:04:17
Not sure, but seems reasonable!
| |
| 625 } | |
| 626 | |
| 627 if (!seek_cb_.is_null()) { | |
| 628 base::ResetAndReturn(&seek_cb_).Run(status_); | |
| 629 error_cb_.Reset(); | |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
nit: early return here and below & de-indent the e
scherkus (not reviewing)
2012/09/05 15:04:17
If we call Stop() during initialization/seek we'll
| |
| 630 } | |
| 631 if (!stop_cb_.is_null()) { | |
| 632 base::ResetAndReturn(&stop_cb_).Run(); | |
| 633 error_cb_.Reset(); | |
| 634 } | |
| 635 if (!error_cb_.is_null()) { | |
| 636 DCHECK_NE(status_, PIPELINE_OK); | |
| 637 base::ResetAndReturn(&error_cb_).Run(status_); | |
| 638 } | |
| 639 } | |
| 640 | |
| 484 void Pipeline::AddBufferedByteRange(int64 start, int64 end) { | 641 void Pipeline::AddBufferedByteRange(int64 start, int64 end) { |
| 485 DCHECK(IsRunning()); | 642 DCHECK(IsRunning()); |
| 486 base::AutoLock auto_lock(lock_); | 643 base::AutoLock auto_lock(lock_); |
| 487 buffered_byte_ranges_.Add(start, end); | 644 buffered_byte_ranges_.Add(start, end); |
| 488 did_loading_progress_ = true; | 645 did_loading_progress_ = true; |
| 489 } | 646 } |
| 490 | 647 |
| 491 void Pipeline::AddBufferedTimeRange(base::TimeDelta start, | 648 void Pipeline::AddBufferedTimeRange(base::TimeDelta start, |
| 492 base::TimeDelta end) { | 649 base::TimeDelta end) { |
| 493 DCHECK(IsRunning()); | 650 DCHECK(IsRunning()); |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 513 } | 670 } |
| 514 | 671 |
| 515 void Pipeline::OnVideoRendererEnded() { | 672 void Pipeline::OnVideoRendererEnded() { |
| 516 // Force post to process ended messages after current execution frame. | 673 // Force post to process ended messages after current execution frame. |
| 517 message_loop_->PostTask(FROM_HERE, base::Bind( | 674 message_loop_->PostTask(FROM_HERE, base::Bind( |
| 518 &Pipeline::DoVideoRendererEnded, this)); | 675 &Pipeline::DoVideoRendererEnded, this)); |
| 519 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED)); | 676 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED)); |
| 520 } | 677 } |
| 521 | 678 |
| 522 // Called from any thread. | 679 // Called from any thread. |
| 523 void Pipeline::OnFilterInitialize(PipelineStatus status) { | |
| 524 // Continue the initialize task by proceeding to the next stage. | |
| 525 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 526 &Pipeline::InitializeTask, this, status)); | |
| 527 } | |
| 528 | |
| 529 // Called from any thread. | |
| 530 // This method makes the PipelineStatusCB behave like a Closure. It | |
| 531 // makes it look like a host()->SetError() call followed by a call to | |
| 532 // OnFilterStateTransition() when errors occur. | |
| 533 // | |
| 534 // TODO(scherkus): Revisit this code when SetError() is removed from FilterHost | |
| 535 // and all the Closures are converted to PipelineStatusCB. | |
| 536 void Pipeline::OnFilterStateTransition(PipelineStatus status) { | |
| 537 if (status != PIPELINE_OK) | |
| 538 SetError(status); | |
| 539 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 540 &Pipeline::FilterStateTransitionTask, this)); | |
| 541 } | |
| 542 | |
| 543 void Pipeline::OnTeardownStateTransition(PipelineStatus status) { | |
| 544 // Ignore any errors during teardown. | |
| 545 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 546 &Pipeline::TeardownStateTransitionTask, this)); | |
| 547 } | |
| 548 | |
| 549 // Called from any thread. | |
| 550 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { | 680 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) { |
| 551 base::AutoLock auto_lock(lock_); | 681 base::AutoLock auto_lock(lock_); |
| 552 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; | 682 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; |
| 553 statistics_.video_bytes_decoded += stats.video_bytes_decoded; | 683 statistics_.video_bytes_decoded += stats.video_bytes_decoded; |
| 554 statistics_.video_frames_decoded += stats.video_frames_decoded; | 684 statistics_.video_frames_decoded += stats.video_frames_decoded; |
| 555 statistics_.video_frames_dropped += stats.video_frames_dropped; | 685 statistics_.video_frames_dropped += stats.video_frames_dropped; |
| 556 } | 686 } |
| 557 | 687 |
| 558 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, | 688 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, |
| 559 const PipelineStatusCB& ended_cb, | 689 const PipelineStatusCB& ended_cb, |
| 560 const PipelineStatusCB& error_cb, | 690 const PipelineStatusCB& error_cb, |
| 561 const PipelineStatusCB& seek_cb, | 691 const PipelineStatusCB& seek_cb, |
| 562 const BufferingStateCB& buffering_state_cb) { | 692 const BufferingStateCB& buffering_state_cb) { |
| 563 DCHECK(message_loop_->BelongsToCurrentThread()); | 693 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 564 CHECK_EQ(kCreated, state_) | 694 CHECK_EQ(kCreated, state_) |
| 565 << "Media pipeline cannot be started more than once"; | 695 << "Media pipeline cannot be started more than once"; |
| 566 | 696 |
| 567 filter_collection_ = filter_collection.Pass(); | 697 filter_collection_ = filter_collection.Pass(); |
| 568 ended_cb_ = ended_cb; | 698 ended_cb_ = ended_cb; |
| 569 error_cb_ = error_cb; | 699 error_cb_ = error_cb; |
| 570 seek_cb_ = seek_cb; | 700 seek_cb_ = seek_cb; |
| 571 buffering_state_cb_ = buffering_state_cb; | 701 buffering_state_cb_ = buffering_state_cb; |
| 572 | 702 |
| 573 // Kick off initialization. | 703 DoStateTransition(PIPELINE_OK); |
| 574 pipeline_init_state_.reset(new PipelineInitState()); | |
| 575 | |
| 576 SetState(kInitDemuxer); | |
| 577 InitializeDemuxer(); | |
| 578 } | 704 } |
| 579 | 705 |
| 580 // Main initialization method called on the pipeline thread. This code attempts | |
| 581 // to use the specified filter factory to build a pipeline. | |
| 582 // Initialization step performed in this method depends on current state of this | |
| 583 // object, indicated by |state_|. After each step of initialization, this | |
| 584 // object transits to the next stage. It starts by creating a Demuxer, and then | |
| 585 // connects the Demuxer's audio stream to an AudioDecoder which is then | |
| 586 // connected to an AudioRenderer. If the media has video, then it connects a | |
| 587 // VideoDecoder to the Demuxer's video stream, and then connects the | |
| 588 // VideoDecoder to a VideoRenderer. | |
| 589 // | |
| 590 // When all required filters have been created and have called their | |
| 591 // FilterHost's InitializationComplete() method, the pipeline will update its | |
| 592 // state to kStarted and |init_cb_|, will be executed. | |
| 593 // | |
| 594 // TODO(hclam): InitializeTask() is now starting the pipeline asynchronously. It | |
| 595 // works like a big state change table. If we no longer need to start filters | |
| 596 // in order, we need to get rid of all the state change. | |
| 597 void Pipeline::InitializeTask(PipelineStatus last_stage_status) { | |
| 598 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 599 | |
| 600 if (last_stage_status != PIPELINE_OK) { | |
| 601 SetError(last_stage_status); | |
| 602 return; | |
| 603 } | |
| 604 | |
| 605 // If we have received the stop or error signal, return immediately. | |
| 606 if (!stop_cb_.is_null() || state_ == kStopped || !IsPipelineOk()) | |
| 607 return; | |
| 608 | |
| 609 DCHECK(state_ == kInitDemuxer || | |
| 610 state_ == kInitAudioDecoder || | |
| 611 state_ == kInitAudioRenderer || | |
| 612 state_ == kInitVideoRenderer); | |
| 613 | |
| 614 // Demuxer created, create audio decoder. | |
| 615 if (state_ == kInitDemuxer) { | |
| 616 SetState(kInitAudioDecoder); | |
| 617 // If this method returns false, then there's no audio stream. | |
| 618 if (InitializeAudioDecoder(demuxer_)) | |
| 619 return; | |
| 620 } | |
| 621 | |
| 622 // Assuming audio decoder was created, create audio renderer. | |
| 623 if (state_ == kInitAudioDecoder) { | |
| 624 SetState(kInitAudioRenderer); | |
| 625 | |
| 626 // Returns false if there's no audio stream. | |
| 627 if (InitializeAudioRenderer(pipeline_init_state_->audio_decoder)) { | |
| 628 base::AutoLock auto_lock(lock_); | |
| 629 has_audio_ = true; | |
| 630 return; | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 // Assuming audio renderer was created, create video renderer. | |
| 635 if (state_ == kInitAudioRenderer) { | |
| 636 SetState(kInitVideoRenderer); | |
| 637 scoped_refptr<DemuxerStream> video_stream = | |
| 638 demuxer_->GetStream(DemuxerStream::VIDEO); | |
| 639 if (InitializeVideoRenderer(video_stream)) { | |
| 640 base::AutoLock auto_lock(lock_); | |
| 641 has_video_ = true; | |
| 642 | |
| 643 // Get an initial natural size so we have something when we signal | |
| 644 // the kHaveMetadata buffering state. | |
| 645 natural_size_ = video_stream->video_decoder_config().natural_size(); | |
| 646 return; | |
| 647 } | |
| 648 } | |
| 649 | |
| 650 if (state_ == kInitVideoRenderer) { | |
| 651 if (!IsPipelineOk() || !(HasAudio() || HasVideo())) { | |
| 652 SetError(PIPELINE_ERROR_COULD_NOT_RENDER); | |
| 653 return; | |
| 654 } | |
| 655 | |
| 656 // Clear initialization state now that we're done. | |
| 657 filter_collection_.reset(); | |
| 658 pipeline_init_state_.reset(); | |
| 659 | |
| 660 // Initialization was successful, we are now considered paused, so it's safe | |
| 661 // to set the initial playback rate and volume. | |
| 662 PlaybackRateChangedTask(GetPlaybackRate()); | |
| 663 VolumeChangedTask(GetVolume()); | |
| 664 | |
| 665 buffering_state_cb_.Run(kHaveMetadata); | |
| 666 | |
| 667 // Fire a seek request to get the renderers to preroll. We can skip a seek | |
| 668 // here as the demuxer should be at the start of the stream. | |
| 669 seek_pending_ = true; | |
| 670 SetState(kSeeking); | |
| 671 seek_timestamp_ = demuxer_->GetStartTime(); | |
| 672 DoSeek(seek_timestamp_, true, | |
| 673 base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 674 } | |
| 675 } | |
| 676 | |
| 677 // This method is called as a result of the client calling Pipeline::Stop() or | |
| 678 // as the result of an error condition. | |
| 679 // We stop the filters in the reverse order. | |
| 680 // | |
| 681 // TODO(scherkus): beware! this can get posted multiple times since we post | |
| 682 // Stop() tasks even if we've already stopped. Perhaps this should no-op for | |
| 683 // additional calls, however most of this logic will be changing. | |
| 684 void Pipeline::StopTask(const base::Closure& stop_cb) { | 706 void Pipeline::StopTask(const base::Closure& stop_cb) { |
| 685 DCHECK(message_loop_->BelongsToCurrentThread()); | 707 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 686 DCHECK(stop_cb_.is_null()); | 708 DCHECK(stop_cb_.is_null()); |
| 687 | 709 |
| 688 if (state_ == kStopped) { | 710 if (state_ == kStopped) { |
| 689 stop_cb.Run(); | 711 stop_cb.Run(); |
| 690 return; | 712 return; |
| 691 } | 713 } |
| 692 | 714 |
| 715 #if 0 | |
| 716 // TODO(scherkus): Can I remove this?! | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
Not before testing it! (and the CL that removes t
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
No. The GPU video decoder & WebRTC decoders need t
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 693 if (video_renderer_) | 717 if (video_renderer_) |
| 694 video_renderer_->PrepareForShutdownHack(); | 718 video_renderer_->PrepareForShutdownHack(); |
| 719 #endif | |
| 695 | 720 |
| 696 if (tearing_down_ && status_ != PIPELINE_OK) { | 721 SetState(kStopping); |
| 697 // If we are stopping due to SetError(), stop normally instead of | 722 pending_callbacks_.reset(); |
| 698 // going to error state and calling |error_cb_|. This converts | |
| 699 // the teardown in progress from an error teardown into one that acts | |
| 700 // like the error never occurred. | |
| 701 base::AutoLock auto_lock(lock_); | |
| 702 status_ = PIPELINE_OK; | |
| 703 } | |
| 704 | |
| 705 stop_cb_ = stop_cb; | 723 stop_cb_ = stop_cb; |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
I'm pretty sure you could get rid of stop_cb_ if y
scherkus (not reviewing)
2012/09/05 15:04:17
It's admittedly used in a few spots for DCHECK-ing
| |
| 706 | 724 |
| 707 if (!IsPipelineSeeking() && !tearing_down_) { | 725 DoStop(base::Bind(&Pipeline::OnStopCompleted, this)); |
| 708 // We will tear down pipeline immediately when there is no seek operation | |
| 709 // pending and no teardown in progress. This should include the case where | |
| 710 // we are partially initialized. | |
| 711 TearDownPipeline(); | |
| 712 } | |
| 713 } | 726 } |
| 714 | 727 |
| 715 void Pipeline::ErrorChangedTask(PipelineStatus error) { | 728 void Pipeline::ErrorChangedTask(PipelineStatus error) { |
| 716 DCHECK(message_loop_->BelongsToCurrentThread()); | 729 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 717 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; | 730 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; |
| 718 | 731 |
| 719 // Suppress executing additional error logic. Note that if we are currently | 732 if (state_ == kStopping || state_ == kStopped) |
| 720 // performing a normal stop, then we return immediately and continue the | |
| 721 // normal stop. | |
| 722 if (state_ == kStopped || tearing_down_) { | |
| 723 return; | 733 return; |
| 724 } | |
| 725 | 734 |
| 726 base::AutoLock auto_lock(lock_); | 735 SetState(kStopping); |
| 736 pending_callbacks_.reset(); | |
| 727 status_ = error; | 737 status_ = error; |
| 728 | 738 |
| 729 // Posting TearDownPipeline() to message loop so that we can make sure | 739 DoStop(base::Bind(&Pipeline::OnStopCompleted, this)); |
| 730 // it runs after any pending callbacks that are already queued. | |
| 731 // |tearing_down_| is set early here to make sure that pending callbacks | |
| 732 // don't modify the state before TearDownPipeline() can run. | |
| 733 tearing_down_ = true; | |
| 734 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 735 &Pipeline::TearDownPipeline, this)); | |
| 736 } | 740 } |
| 737 | 741 |
| 738 void Pipeline::PlaybackRateChangedTask(float playback_rate) { | 742 void Pipeline::PlaybackRateChangedTask(float playback_rate) { |
| 739 DCHECK(message_loop_->BelongsToCurrentThread()); | 743 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 740 | 744 |
| 741 if (state_ == kStopped || tearing_down_) | 745 if (state_ == kStopping || state_ == kStopped) |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
This can be removed since it is covered by the con
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 742 return; | 746 return; |
| 743 | 747 |
| 744 // Suppress rate change until after seeking. | 748 // Playback rate changes are only carried out while playing. |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
We no longer track pending PR changes?
scherkus (not reviewing)
2012/09/05 15:04:17
Since playback_rate_ is set under lock there's no
| |
| 745 if (IsPipelineSeeking()) { | 749 if (state_ != kStarting && state_ != kStarted) |
| 746 pending_playback_rate_ = playback_rate; | |
| 747 playback_rate_change_pending_ = true; | |
| 748 return; | 750 return; |
| 749 } | |
| 750 | 751 |
| 751 { | 752 { |
| 752 base::AutoLock auto_lock(lock_); | 753 base::AutoLock auto_lock(lock_); |
| 753 clock_->SetPlaybackRate(playback_rate); | 754 clock_->SetPlaybackRate(playback_rate); |
| 754 } | 755 } |
| 755 | 756 |
| 756 // These will get set after initialization completes in case playback rate is | |
| 757 // set prior to initialization. | |
| 758 if (demuxer_) | 757 if (demuxer_) |
| 759 demuxer_->SetPlaybackRate(playback_rate); | 758 demuxer_->SetPlaybackRate(playback_rate); |
| 760 if (audio_renderer_) | 759 if (audio_renderer_) |
| 761 audio_renderer_->SetPlaybackRate(playback_rate_); | 760 audio_renderer_->SetPlaybackRate(playback_rate_); |
| 762 if (video_renderer_) | 761 if (video_renderer_) |
| 763 video_renderer_->SetPlaybackRate(playback_rate_); | 762 video_renderer_->SetPlaybackRate(playback_rate_); |
| 764 } | 763 } |
| 765 | 764 |
| 766 void Pipeline::VolumeChangedTask(float volume) { | 765 void Pipeline::VolumeChangedTask(float volume) { |
| 767 DCHECK(message_loop_->BelongsToCurrentThread()); | 766 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 768 | 767 |
| 769 if (state_ == kStopped || tearing_down_) | 768 if (state_ == kStopping || state_ == kStopped) |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
ditto. covered by check below
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 769 return; | |
| 770 | |
| 771 // Volume changes are only carried out while playing. | |
| 772 if (state_ != kStarting && state_ != kStarted) | |
| 770 return; | 773 return; |
| 771 | 774 |
| 772 if (audio_renderer_) | 775 if (audio_renderer_) |
| 773 audio_renderer_->SetVolume(volume); | 776 audio_renderer_->SetVolume(volume); |
| 774 } | 777 } |
| 775 | 778 |
| 776 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { | 779 void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { |
| 777 DCHECK(message_loop_->BelongsToCurrentThread()); | 780 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 778 DCHECK(stop_cb_.is_null()); | 781 DCHECK(stop_cb_.is_null()); |
| 779 | 782 |
| 780 // Suppress seeking if we're not fully started. | 783 // Suppress seeking if we're not fully started. |
| 781 if (state_ != kStarted) { | 784 if (state_ != kStarted) { |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Add DCHECK(state_ == kStopping || state_ == kStopp
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 782 // TODO(scherkus): should we run the callback? I'm tempted to say the API | 785 // TODO(scherkus): should we run the callback? I'm tempted to say the API |
| 783 // will only execute the first Seek() request. | 786 // will only execute the first Seek() request. |
| 784 DVLOG(1) << "Media pipeline has not started, ignoring seek to " | 787 DVLOG(1) << "Media pipeline has not started, ignoring seek to " |
| 785 << time.InMicroseconds() << " (current state: " << state_ << ")"; | 788 << time.InMicroseconds() << " (current state: " << state_ << ")"; |
| 786 return; | 789 return; |
| 787 } | 790 } |
| 788 | 791 |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
DCHECK_EQ(state_, kStarted) ?
scherkus (not reviewing)
2012/09/05 15:04:17
Hrmm.. unlike seek_cb_ it doesn't seem to be a hig
| |
| 789 DCHECK(!seek_pending_); | 792 DCHECK(seek_cb_.is_null()); |
| 790 seek_pending_ = true; | |
| 791 | 793 |
| 792 // We'll need to pause every filter before seeking. The state transition | 794 SetState(kSeeking); |
| 793 // is as follows: | 795 base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime()); |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Should this also be capped by duration?
scherkus (not reviewing)
2012/09/05 15:04:17
Today we rely on demuxers to cap the seek -- I'd r
| |
| 794 // kStarted | 796 seek_cb_ = seek_cb; |
| 795 // kPausing (for each filter) | |
| 796 // kSeeking (for each filter) | |
| 797 // kStarting (for each filter) | |
| 798 // kStarted | |
| 799 SetState(kPausing); | |
| 800 audio_ended_ = false; | 797 audio_ended_ = false; |
| 801 video_ended_ = false; | 798 video_ended_ = false; |
| 802 seek_timestamp_ = std::max(time, demuxer_->GetStartTime()); | |
| 803 seek_cb_ = seek_cb; | |
| 804 | 799 |
| 805 // Kick off seeking! | 800 // Kick off seeking! |
| 806 { | 801 { |
| 807 base::AutoLock auto_lock(lock_); | 802 base::AutoLock auto_lock(lock_); |
| 808 if (clock_->IsPlaying()) | 803 if (clock_->IsPlaying()) |
| 809 clock_->Pause(); | 804 clock_->Pause(); |
| 805 clock_->SetTime(seek_timestamp, seek_timestamp); | |
| 810 } | 806 } |
| 811 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); | 807 DoSeek(seek_timestamp, base::Bind(&Pipeline::OnStateTransition, this)); |
| 812 } | 808 } |
| 813 | 809 |
| 814 void Pipeline::DoAudioRendererEnded() { | 810 void Pipeline::DoAudioRendererEnded() { |
| 815 DCHECK(message_loop_->BelongsToCurrentThread()); | 811 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 816 | 812 |
| 817 if (state_ != kStarted) | 813 if (state_ != kStarted) |
| 818 return; | 814 return; |
| 819 | 815 |
| 820 DCHECK(!audio_ended_); | 816 DCHECK(!audio_ended_); |
| 821 audio_ended_ = true; | 817 audio_ended_ = true; |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 849 return; | 845 return; |
| 850 | 846 |
| 851 if (video_renderer_ && !video_ended_) | 847 if (video_renderer_ && !video_ended_) |
| 852 return; | 848 return; |
| 853 | 849 |
| 854 { | 850 { |
| 855 base::AutoLock auto_lock(lock_); | 851 base::AutoLock auto_lock(lock_); |
| 856 clock_->EndOfStream(); | 852 clock_->EndOfStream(); |
| 857 } | 853 } |
| 858 | 854 |
| 859 ReportStatus(ended_cb_, status_); | 855 ended_cb_.Run(status_); |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Are there plans to change this to a closure? It se
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 860 } | 856 } |
| 861 | 857 |
| 862 void Pipeline::AudioDisabledTask() { | 858 void Pipeline::AudioDisabledTask() { |
| 863 DCHECK(message_loop_->BelongsToCurrentThread()); | 859 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 864 | 860 |
| 865 base::AutoLock auto_lock(lock_); | 861 base::AutoLock auto_lock(lock_); |
| 866 has_audio_ = false; | 862 has_audio_ = false; |
| 867 audio_disabled_ = true; | 863 audio_disabled_ = true; |
| 868 | 864 |
| 869 // Notify our demuxer that we're no longer rendering audio. | 865 // Notify our demuxer that we're no longer rendering audio. |
| 870 demuxer_->OnAudioRendererDisabled(); | 866 demuxer_->OnAudioRendererDisabled(); |
| 871 | 867 |
| 872 // Start clock since there is no more audio to | 868 // Start clock since there is no more audio to trigger clock updates. |
| 873 // trigger clock updates. | |
| 874 clock_->SetMaxTime(clock_->Duration()); | 869 clock_->SetMaxTime(clock_->Duration()); |
| 875 StartClockIfWaitingForTimeUpdate_Locked(); | 870 StartClockIfWaitingForTimeUpdate_Locked(); |
| 876 } | 871 } |
| 877 | 872 |
| 878 void Pipeline::FilterStateTransitionTask() { | 873 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) { |
| 879 DCHECK(message_loop_->BelongsToCurrentThread()); | 874 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 880 DCHECK(pending_callbacks_.get()) | |
| 881 << "Filter state transitions must be completed via pending_callbacks_"; | |
| 882 pending_callbacks_.reset(); | |
| 883 | 875 |
| 884 // State transitions while tearing down are handled via | 876 demuxer_ = filter_collection_->GetDemuxer(); |
| 885 // TeardownStateTransitionTask(). | 877 if (!demuxer_) { |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
DCHECK(demuxer_)? I can't think of a case where th
scherkus (not reviewing)
2012/09/05 15:04:17
Done.
| |
| 886 // | 878 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 887 // TODO(scherkus): Merge all state machinery! | |
| 888 if (state_ == kStopped || tearing_down_) { | |
| 889 return; | 879 return; |
| 890 } | 880 } |
| 891 | 881 |
| 892 if (!TransientState(state_)) { | 882 demuxer_->Initialize(this, done_cb); |
| 893 NOTREACHED() << "Invalid current state: " << state_; | 883 } |
| 894 SetError(PIPELINE_ERROR_ABORT); | 884 |
| 885 void Pipeline::InitializeAudioDecoder(const PipelineStatusCB& done_cb) { | |
| 886 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 887 | |
| 888 scoped_refptr<DemuxerStream> stream = | |
| 889 demuxer_->GetStream(DemuxerStream::AUDIO); | |
| 890 | |
| 891 if (!stream) { | |
| 892 done_cb.Run(PIPELINE_OK); | |
| 895 return; | 893 return; |
| 896 } | 894 } |
| 897 | 895 |
| 898 // Decrement the number of remaining transitions, making sure to transition | 896 filter_collection_->SelectAudioDecoder(&audio_decoder_); |
| 899 // to the next state if needed. | |
| 900 SetState(FindNextState(state_)); | |
| 901 if (state_ == kSeeking) { | |
| 902 base::AutoLock auto_lock(lock_); | |
| 903 DCHECK(seek_timestamp_ != kNoTimestamp()); | |
| 904 clock_->SetTime(seek_timestamp_, seek_timestamp_); | |
| 905 } | |
| 906 | 897 |
| 907 // Carry out the action for the current state. | 898 if (!audio_decoder_) { |
| 908 if (TransientState(state_)) { | 899 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 909 if (state_ == kPausing) { | |
| 910 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 911 } else if (state_ == kFlushing) { | |
| 912 DoFlush(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 913 } else if (state_ == kSeeking) { | |
| 914 DoSeek(seek_timestamp_, false, | |
| 915 base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 916 } else if (state_ == kStarting) { | |
| 917 DoPlay(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 918 } else if (state_ == kStopping) { | |
| 919 DoStop(base::Bind(&Pipeline::OnFilterStateTransition, this)); | |
| 920 } else { | |
| 921 NOTREACHED() << "Unexpected state: " << state_; | |
| 922 } | |
| 923 } else if (state_ == kStarted) { | |
| 924 | |
| 925 // Fire canplaythrough immediately after playback begins because of | |
| 926 // crbug.com/106480. | |
| 927 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed. | |
| 928 if (status_ == PIPELINE_OK) | |
| 929 buffering_state_cb_.Run(kPrerollCompleted); | |
| 930 | |
| 931 FinishSeek(); | |
| 932 | |
| 933 // If a playback rate change was requested during a seek, do it now that | |
| 934 // the seek has compelted. | |
| 935 if (playback_rate_change_pending_) { | |
| 936 playback_rate_change_pending_ = false; | |
| 937 PlaybackRateChangedTask(pending_playback_rate_); | |
| 938 } | |
| 939 | |
| 940 base::AutoLock auto_lock(lock_); | |
| 941 // We use audio stream to update the clock. So if there is such a stream, | |
| 942 // we pause the clock until we receive a valid timestamp. | |
| 943 waiting_for_clock_update_ = true; | |
| 944 if (!has_audio_) { | |
| 945 clock_->SetMaxTime(clock_->Duration()); | |
| 946 StartClockIfWaitingForTimeUpdate_Locked(); | |
| 947 } | |
| 948 | |
| 949 // Check if we have a pending stop request that needs to be honored. | |
| 950 if (!stop_cb_.is_null()) { | |
| 951 TearDownPipeline(); | |
| 952 } | |
| 953 } else { | |
| 954 NOTREACHED() << "Unexpected state: " << state_; | |
| 955 } | |
| 956 } | |
| 957 | |
| 958 void Pipeline::TeardownStateTransitionTask() { | |
| 959 DCHECK(tearing_down_); | |
| 960 DCHECK(pending_callbacks_.get()) | |
| 961 << "Teardown state transitions must be completed via pending_callbacks_"; | |
| 962 pending_callbacks_.reset(); | |
| 963 | |
| 964 switch (state_) { | |
| 965 case kStopping: | |
| 966 SetState(kStopped); | |
| 967 FinishDestroyingFiltersTask(); | |
| 968 break; | |
| 969 case kPausing: | |
| 970 SetState(kFlushing); | |
| 971 DoFlush(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 972 break; | |
| 973 case kFlushing: | |
| 974 SetState(kStopping); | |
| 975 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 976 break; | |
| 977 | |
| 978 case kCreated: | |
| 979 case kInitDemuxer: | |
| 980 case kInitAudioDecoder: | |
| 981 case kInitAudioRenderer: | |
| 982 case kInitVideoRenderer: | |
| 983 case kSeeking: | |
| 984 case kStarting: | |
| 985 case kStopped: | |
| 986 case kStarted: | |
| 987 NOTREACHED() << "Unexpected state for teardown: " << state_; | |
| 988 break; | |
| 989 // default: intentionally left out to force new states to cause compiler | |
| 990 // errors. | |
| 991 }; | |
| 992 } | |
| 993 | |
| 994 void Pipeline::FinishDestroyingFiltersTask() { | |
| 995 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 996 DCHECK_EQ(state_, kStopped); | |
| 997 | |
| 998 audio_renderer_ = NULL; | |
| 999 video_renderer_ = NULL; | |
| 1000 demuxer_ = NULL; | |
| 1001 tearing_down_ = false; | |
| 1002 { | |
| 1003 base::AutoLock l(lock_); | |
| 1004 running_ = false; | |
| 1005 } | |
| 1006 | |
| 1007 if (!IsPipelineOk() && !error_cb_.is_null()) | |
| 1008 error_cb_.Run(status_); | |
| 1009 | |
| 1010 if (!stop_cb_.is_null()) | |
| 1011 base::ResetAndReturn(&stop_cb_).Run(); | |
| 1012 } | |
| 1013 | |
| 1014 void Pipeline::InitializeDemuxer() { | |
| 1015 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1016 DCHECK(IsPipelineOk()); | |
| 1017 | |
| 1018 demuxer_ = filter_collection_->GetDemuxer(); | |
| 1019 if (!demuxer_) { | |
| 1020 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1021 return; | 900 return; |
| 1022 } | 901 } |
| 1023 | 902 |
| 1024 demuxer_->Initialize(this, base::Bind(&Pipeline::OnDemuxerInitialized, this)); | 903 audio_decoder_->Initialize( |
| 904 stream, done_cb, base::Bind(&Pipeline::OnUpdateStatistics, this)); | |
| 1025 } | 905 } |
| 1026 | 906 |
| 1027 void Pipeline::OnDemuxerInitialized(PipelineStatus status) { | 907 void Pipeline::InitializeAudioRenderer(const PipelineStatusCB& done_cb) { |
| 1028 if (!message_loop_->BelongsToCurrentThread()) { | 908 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 1029 message_loop_->PostTask(FROM_HERE, base::Bind( | 909 |
| 1030 &Pipeline::OnDemuxerInitialized, this, status)); | 910 if (!audio_decoder_) { |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
nit: seems strange that this check is here instead
scherkus (not reviewing)
2012/09/05 15:04:17
I wanted to keep the state machine switch statemen
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
Yes, that's what I meant.
| |
| 911 done_cb.Run(PIPELINE_OK); | |
| 1031 return; | 912 return; |
| 1032 } | 913 } |
| 1033 | 914 |
| 1034 if (status != PIPELINE_OK) { | 915 filter_collection_->SelectAudioRenderer(&audio_renderer_); |
| 1035 SetError(status); | 916 if (!audio_renderer_) { |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Should we DCHECK() instead?
scherkus (not reviewing)
2012/09/05 15:04:17
Maybe. IIRC today we require a demuxer (i.e., othe
Ami GONE FROM CHROMIUM
2012/09/06 09:11:34
Yes please.
| |
| 917 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1036 return; | 918 return; |
| 1037 } | 919 } |
| 1038 | 920 |
| 1039 { | |
| 1040 base::AutoLock auto_lock(lock_); | |
| 1041 // We do not want to start the clock running. We only want to set the base | |
| 1042 // media time so our timestamp calculations will be correct. | |
| 1043 clock_->SetTime(demuxer_->GetStartTime(), demuxer_->GetStartTime()); | |
| 1044 } | |
| 1045 | |
| 1046 OnFilterInitialize(PIPELINE_OK); | |
| 1047 } | |
| 1048 | |
| 1049 bool Pipeline::InitializeAudioDecoder( | |
| 1050 const scoped_refptr<Demuxer>& demuxer) { | |
| 1051 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1052 DCHECK(IsPipelineOk()); | |
| 1053 DCHECK(demuxer); | |
| 1054 | |
| 1055 scoped_refptr<DemuxerStream> stream = | |
| 1056 demuxer->GetStream(DemuxerStream::AUDIO); | |
| 1057 | |
| 1058 if (!stream) | |
| 1059 return false; | |
| 1060 | |
| 1061 filter_collection_->SelectAudioDecoder(&pipeline_init_state_->audio_decoder); | |
| 1062 | |
| 1063 if (!pipeline_init_state_->audio_decoder) { | |
| 1064 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1065 return false; | |
| 1066 } | |
| 1067 | |
| 1068 pipeline_init_state_->audio_decoder->Initialize( | |
| 1069 stream, | |
| 1070 base::Bind(&Pipeline::OnFilterInitialize, this), | |
| 1071 base::Bind(&Pipeline::OnUpdateStatistics, this)); | |
| 1072 return true; | |
| 1073 } | |
| 1074 | |
| 1075 bool Pipeline::InitializeAudioRenderer( | |
| 1076 const scoped_refptr<AudioDecoder>& decoder) { | |
| 1077 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1078 DCHECK(IsPipelineOk()); | |
| 1079 | |
| 1080 if (!decoder) | |
| 1081 return false; | |
| 1082 | |
| 1083 filter_collection_->SelectAudioRenderer(&audio_renderer_); | |
| 1084 if (!audio_renderer_) { | |
| 1085 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | |
| 1086 return false; | |
| 1087 } | |
| 1088 | |
| 1089 audio_renderer_->Initialize( | 921 audio_renderer_->Initialize( |
| 1090 decoder, | 922 audio_decoder_, |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
std::swap() to a local variable here so we can cle
scherkus (not reviewing)
2012/09/05 15:04:17
I think it's OK for now -- member variable will go
| |
| 1091 base::Bind(&Pipeline::OnFilterInitialize, this), | 923 done_cb, |
| 1092 base::Bind(&Pipeline::OnAudioUnderflow, this), | 924 base::Bind(&Pipeline::OnAudioUnderflow, this), |
| 1093 base::Bind(&Pipeline::OnAudioTimeUpdate, this), | 925 base::Bind(&Pipeline::OnAudioTimeUpdate, this), |
| 1094 base::Bind(&Pipeline::OnAudioRendererEnded, this), | 926 base::Bind(&Pipeline::OnAudioRendererEnded, this), |
| 1095 base::Bind(&Pipeline::OnAudioDisabled, this), | 927 base::Bind(&Pipeline::OnAudioDisabled, this), |
| 1096 base::Bind(&Pipeline::SetError, this)); | 928 base::Bind(&Pipeline::SetError, this)); |
| 1097 return true; | |
| 1098 } | 929 } |
| 1099 | 930 |
| 1100 bool Pipeline::InitializeVideoRenderer( | 931 void Pipeline::InitializeVideoRenderer(const PipelineStatusCB& done_cb) { |
| 1101 const scoped_refptr<DemuxerStream>& stream) { | |
| 1102 DCHECK(message_loop_->BelongsToCurrentThread()); | 932 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 1103 DCHECK(IsPipelineOk()); | |
| 1104 | 933 |
| 1105 if (!stream) | 934 scoped_refptr<DemuxerStream> stream = |
| 1106 return false; | 935 demuxer_->GetStream(DemuxerStream::VIDEO); |
| 936 | |
| 937 if (!stream) { | |
|
Ami GONE FROM CHROMIUM
2012/09/02 20:29:29
ditto
| |
| 938 done_cb.Run(PIPELINE_OK); | |
| 939 return; | |
| 940 } | |
| 1107 | 941 |
| 1108 filter_collection_->SelectVideoRenderer(&video_renderer_); | 942 filter_collection_->SelectVideoRenderer(&video_renderer_); |
| 1109 if (!video_renderer_) { | 943 if (!video_renderer_) { |
|
acolwell GONE FROM CHROMIUM
2012/09/05 13:22:22
Should we put a DCHECK() here since this failing i
| |
| 1110 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); | 944 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); |
| 1111 return false; | 945 return; |
| 946 } | |
| 947 | |
| 948 { | |
| 949 // Get an initial natural size so we have something when we signal | |
| 950 // the kHaveMetadata buffering state. | |
| 951 base::AutoLock l(lock_); | |
| 952 natural_size_ = stream->video_decoder_config().natural_size(); | |
| 1112 } | 953 } |
| 1113 | 954 |
| 1114 video_renderer_->Initialize( | 955 video_renderer_->Initialize( |
| 1115 stream, | 956 stream, |
| 1116 *filter_collection_->GetVideoDecoders(), | 957 *filter_collection_->GetVideoDecoders(), |
| 1117 base::Bind(&Pipeline::OnFilterInitialize, this), | 958 done_cb, |
| 1118 base::Bind(&Pipeline::OnUpdateStatistics, this), | 959 base::Bind(&Pipeline::OnUpdateStatistics, this), |
| 1119 base::Bind(&Pipeline::OnVideoTimeUpdate, this), | 960 base::Bind(&Pipeline::OnVideoTimeUpdate, this), |
| 1120 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), | 961 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), |
| 1121 base::Bind(&Pipeline::OnVideoRendererEnded, this), | 962 base::Bind(&Pipeline::OnVideoRendererEnded, this), |
| 1122 base::Bind(&Pipeline::SetError, this), | 963 base::Bind(&Pipeline::SetError, this), |
| 1123 base::Bind(&Pipeline::GetMediaTime, this), | 964 base::Bind(&Pipeline::GetMediaTime, this), |
| 1124 base::Bind(&Pipeline::GetMediaDuration, this)); | 965 base::Bind(&Pipeline::GetMediaDuration, this)); |
| 1125 filter_collection_->GetVideoDecoders()->clear(); | 966 filter_collection_->GetVideoDecoders()->clear(); |
| 1126 return true; | |
| 1127 } | |
| 1128 | |
| 1129 void Pipeline::TearDownPipeline() { | |
| 1130 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1131 DCHECK_NE(kStopped, state_); | |
| 1132 | |
| 1133 // We're either... | |
| 1134 // 1) ...tearing down due to Stop() (it doesn't set tearing_down_) | |
| 1135 // 2) ...tearing down due to an error (it does set tearing_down_) | |
| 1136 // 3) ...tearing down due to an error and Stop() was called during that time | |
| 1137 DCHECK(!tearing_down_ || | |
| 1138 (tearing_down_ && status_ != PIPELINE_OK) || | |
| 1139 (tearing_down_ && !stop_cb_.is_null())); | |
| 1140 | |
| 1141 // Mark that we already start tearing down operation. | |
| 1142 tearing_down_ = true; | |
| 1143 | |
| 1144 // Cancel any pending operation so we can proceed with teardown. | |
| 1145 pending_callbacks_.reset(); | |
| 1146 | |
| 1147 switch (state_) { | |
| 1148 case kCreated: | |
| 1149 SetState(kStopped); | |
| 1150 // Need to put this in the message loop to make sure that it comes | |
| 1151 // after any pending callback tasks that are already queued. | |
| 1152 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 1153 &Pipeline::FinishDestroyingFiltersTask, this)); | |
| 1154 break; | |
| 1155 | |
| 1156 case kInitDemuxer: | |
| 1157 case kInitAudioDecoder: | |
| 1158 case kInitAudioRenderer: | |
| 1159 case kInitVideoRenderer: | |
| 1160 // Make it look like initialization was successful. | |
| 1161 filter_collection_.reset(); | |
| 1162 pipeline_init_state_.reset(); | |
| 1163 | |
| 1164 SetState(kStopping); | |
| 1165 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1166 | |
| 1167 FinishSeek(); | |
| 1168 break; | |
| 1169 | |
| 1170 case kPausing: | |
| 1171 case kSeeking: | |
| 1172 case kFlushing: | |
| 1173 case kStarting: | |
| 1174 SetState(kStopping); | |
| 1175 DoStop(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1176 | |
| 1177 if (seek_pending_) | |
| 1178 FinishSeek(); | |
| 1179 | |
| 1180 break; | |
| 1181 | |
| 1182 case kStarted: | |
| 1183 SetState(kPausing); | |
| 1184 DoPause(base::Bind(&Pipeline::OnTeardownStateTransition, this)); | |
| 1185 break; | |
| 1186 | |
| 1187 case kStopping: | |
| 1188 case kStopped: | |
| 1189 NOTREACHED() << "Unexpected state for teardown: " << state_; | |
| 1190 break; | |
| 1191 // default: intentionally left out to force new states to cause compiler | |
| 1192 // errors. | |
| 1193 }; | |
| 1194 } | |
| 1195 | |
| 1196 void Pipeline::DoSeek(base::TimeDelta seek_timestamp, | |
| 1197 bool skip_demuxer_seek, | |
| 1198 const PipelineStatusCB& done_cb) { | |
| 1199 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 1200 DCHECK(!pending_callbacks_.get()); | |
| 1201 SerialRunner::Queue bound_fns; | |
| 1202 | |
| 1203 if (!skip_demuxer_seek) { | |
| 1204 bound_fns.Push(base::Bind( | |
| 1205 &Demuxer::Seek, demuxer_, seek_timestamp)); | |
| 1206 } | |
| 1207 | |
| 1208 if (audio_renderer_) { | |
| 1209 bound_fns.Push(base::Bind( | |
| 1210 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp)); | |
| 1211 } | |
| 1212 | |
| 1213 if (video_renderer_) { | |
| 1214 bound_fns.Push(base::Bind( | |
| 1215 &VideoRenderer::Preroll, video_renderer_, seek_timestamp)); | |
| 1216 } | |
| 1217 | |
| 1218 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); | |
| 1219 } | 967 } |
| 1220 | 968 |
| 1221 void Pipeline::OnAudioUnderflow() { | 969 void Pipeline::OnAudioUnderflow() { |
| 1222 if (!message_loop_->BelongsToCurrentThread()) { | 970 if (!message_loop_->BelongsToCurrentThread()) { |
| 1223 message_loop_->PostTask(FROM_HERE, base::Bind( | 971 message_loop_->PostTask(FROM_HERE, base::Bind( |
| 1224 &Pipeline::OnAudioUnderflow, this)); | 972 &Pipeline::OnAudioUnderflow, this)); |
| 1225 return; | 973 return; |
| 1226 } | 974 } |
| 1227 | 975 |
| 1228 if (state_ != kStarted) | 976 if (state_ != kStarted) |
| 1229 return; | 977 return; |
| 1230 | 978 |
| 1231 if (audio_renderer_) | 979 if (audio_renderer_) |
| 1232 audio_renderer_->ResumeAfterUnderflow(true); | 980 audio_renderer_->ResumeAfterUnderflow(true); |
| 1233 } | 981 } |
| 1234 | 982 |
| 1235 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { | 983 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { |
| 1236 lock_.AssertAcquired(); | 984 lock_.AssertAcquired(); |
| 1237 if (!waiting_for_clock_update_) | 985 if (!waiting_for_clock_update_) |
| 1238 return; | 986 return; |
| 1239 | 987 |
| 1240 waiting_for_clock_update_ = false; | 988 waiting_for_clock_update_ = false; |
| 1241 clock_->Play(); | 989 clock_->Play(); |
| 1242 } | 990 } |
| 1243 | 991 |
| 1244 } // namespace media | 992 } // namespace media |
| OLD | NEW |