Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: media/base/pipeline.cc

Issue 10837206: Rewrite media::Pipeline state transition machinery and simplify shutdown. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src
Patch Set: one more time Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698