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

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: 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_);
102 DCHECK(!running_) << "Stop() must complete before destroying object"; 91 DCHECK(!running_) << "Stop() must complete before destroying object";
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 Should this assert running on a particular thread?
scherkus (not reviewing) 2012/09/06 15:33:20 That would be a nice thing! Here's hoping it doesn
103 DCHECK(stop_cb_.is_null()); 92 DCHECK(stop_cb_.is_null());
104 DCHECK(!seek_pending_); 93 DCHECK(seek_cb_.is_null());
105 94
106 media_log_->AddEvent( 95 media_log_->AddEvent(
107 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED)); 96 media_log_->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED));
108 } 97 }
109 98
110 void Pipeline::Start(scoped_ptr<FilterCollection> collection, 99 void Pipeline::Start(scoped_ptr<FilterCollection> collection,
111 const PipelineStatusCB& ended_cb, 100 const PipelineStatusCB& ended_cb,
112 const PipelineStatusCB& error_cb, 101 const PipelineStatusCB& error_cb,
113 const PipelineStatusCB& seek_cb, 102 const PipelineStatusCB& seek_cb,
114 const BufferingStateCB& buffering_state_cb) { 103 const BufferingStateCB& buffering_state_cb) {
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
249 SetError(status); 238 SetError(status);
250 } 239 }
251 240
252 void Pipeline::SetState(State next_state) { 241 void Pipeline::SetState(State next_state) {
253 if (state_ != kStarted && next_state == kStarted && 242 if (state_ != kStarted && next_state == kStarted &&
254 !creation_time_.is_null()) { 243 !creation_time_.is_null()) {
255 UMA_HISTOGRAM_TIMES( 244 UMA_HISTOGRAM_TIMES(
256 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_); 245 "Media.TimeToPipelineStarted", base::Time::Now() - creation_time_);
257 creation_time_ = base::Time(); 246 creation_time_ = base::Time();
258 } 247 }
248
249 DVLOG(2) << GetStateString(state_) << " -> " << GetStateString(next_state);
250
259 state_ = next_state; 251 state_ = next_state;
260 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); 252 media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state));
261 } 253 }
262 254
263 bool Pipeline::IsPipelineOk() { 255 #define RETURN_STRING(state) case state: return #state;
264 base::AutoLock auto_lock(lock_); 256
265 return status_ == PIPELINE_OK; 257 const char* Pipeline::GetStateString(State state) {
258 switch (state) {
259 RETURN_STRING(kCreated);
260 RETURN_STRING(kInitDemuxer);
261 RETURN_STRING(kInitAudioDecoder);
262 RETURN_STRING(kInitAudioRenderer);
263 RETURN_STRING(kInitVideoRenderer);
264 RETURN_STRING(kInitPrerolling);
265 RETURN_STRING(kSeeking);
266 RETURN_STRING(kStarting);
267 RETURN_STRING(kStarted);
268 RETURN_STRING(kStopping);
269 RETURN_STRING(kStopped);
270 }
271 NOTREACHED();
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 Is it not possible to omit this and the next lines
scherkus (not reviewing) 2012/09/06 15:33:20 gcc complains about reaching end of non-void funct
272 return "INVALID";
266 } 273 }
267 274
268 bool Pipeline::IsPipelineSeeking() { 275 #undef RETURN_STRING
276
277 Pipeline::State Pipeline::GetNextState() const {
269 DCHECK(message_loop_->BelongsToCurrentThread()); 278 DCHECK(message_loop_->BelongsToCurrentThread());
270 if (!seek_pending_) 279 DCHECK(stop_cb_.is_null())
271 return false; 280 << "State transitions don't happen when stopping";
272 DCHECK(kSeeking == state_ || kPausing == state_ || 281 DCHECK_EQ(status_, PIPELINE_OK)
273 kFlushing == state_ || kStarting == state_) 282 << "State transitions don't happen when there's an error: " << status_;
274 << "Current state : " << state_;
275 return true;
276 }
277 283
278 void Pipeline::ReportStatus(const PipelineStatusCB& cb, PipelineStatus status) { 284 switch (state_) {
279 DCHECK(message_loop_->BelongsToCurrentThread()); 285 case kCreated:
280 if (cb.is_null()) 286 return kInitDemuxer;
281 return; 287 case kInitDemuxer:
282 cb.Run(status); 288 return kInitAudioDecoder;
283 // Prevent double-reporting of errors to clients. 289 case kInitAudioDecoder:
284 if (status != PIPELINE_OK) 290 return kInitAudioRenderer;
285 error_cb_.Reset(); 291 case kInitAudioRenderer:
286 } 292 return kInitVideoRenderer;
293 case kInitVideoRenderer:
294 return kInitPrerolling;
295 case kInitPrerolling:
296 return kStarting;
297 case kSeeking:
298 return kStarting;
299 case kStarting:
300 return kStarted;
287 301
288 void Pipeline::FinishSeek() { 302 case kStarted:
289 DCHECK(message_loop_->BelongsToCurrentThread()); 303 case kStopping:
290 seek_timestamp_ = kNoTimestamp(); 304 case kStopped:
291 seek_pending_ = false; 305 NOTREACHED() << "State has no transition: " << state_;
292 306 return state_;
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 } 307 }
329 } 308 }
330 309
331 void Pipeline::OnDemuxerError(PipelineStatus error) { 310 void Pipeline::OnDemuxerError(PipelineStatus error) {
332 SetError(error); 311 SetError(error);
333 } 312 }
334 313
335 void Pipeline::SetError(PipelineStatus error) { 314 void Pipeline::SetError(PipelineStatus error) {
336 DCHECK(IsRunning()); 315 DCHECK(IsRunning());
337 DCHECK_NE(PIPELINE_OK, error); 316 DCHECK_NE(PIPELINE_OK, error);
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
415 // Since the byte->time calculation is approximate, fudge the beginning & 394 // Since the byte->time calculation is approximate, fudge the beginning &
416 // ending areas to look better. 395 // ending areas to look better.
417 TimeDelta epsilon = clock_->Duration() / 100; 396 TimeDelta epsilon = clock_->Duration() / 100;
418 if (time_offset < epsilon) 397 if (time_offset < epsilon)
419 return TimeDelta(); 398 return TimeDelta();
420 if (time_offset + epsilon > clock_->Duration()) 399 if (time_offset + epsilon > clock_->Duration())
421 return clock_->Duration(); 400 return clock_->Duration();
422 return time_offset; 401 return time_offset;
423 } 402 }
424 403
425 void Pipeline::DoPause(const PipelineStatusCB& done_cb) { 404 void Pipeline::OnStateTransition(PipelineStatus status) {
405 // Force post to process state transitions after current execution frame.
406 message_loop_->PostTask(FROM_HERE, base::Bind(
407 &Pipeline::StateTransitionTask, this, status));
408 }
409
410 void Pipeline::StateTransitionTask(PipelineStatus status) {
411 DCHECK(message_loop_->BelongsToCurrentThread());
412
413 // Preserve existing abnormal status, otherwise update based on the result of
414 // the previous operation.
415 status_ = (status_ != PIPELINE_OK ? status_ : status);
416
417 // No-op any state transitions if we're stopping.
418 if (state_ == kStopping || state_ == kStopped)
419 return;
420
421 if (status_ != PIPELINE_OK) {
422 ErrorChangedTask(status_);
423 return;
424 }
425
426 // TODO(scherkus): not every state transition uses |pending_callbacks_| today.
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 nit: I think this comment needs to be changed into
scherkus (not reviewing) 2012/09/06 15:33:20 Done.
427 if (state_ == kInitPrerolling || state_ == kStarting || state_ == kSeeking) {
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 I think what you really want is the single-stateme
scherkus (not reviewing) 2012/09/06 15:33:20 The comment I can agree with but the resulting DCH
428 DCHECK(pending_callbacks_.get())
429 << "pending_callbacks_ should exist when in state " << state_;
430 } else {
431 DCHECK(!pending_callbacks_.get())
432 << "pending_callbacks_ should not be used when in state " << state_;
433 }
434 pending_callbacks_.reset();
435
436 PipelineStatusCB done_cb = base::Bind(&Pipeline::OnStateTransition, this);
437
438 SetState(GetNextState());
439 switch (state_) {
440 case kInitDemuxer:
441 return InitializeDemuxer(done_cb);
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());
449 }
450 return InitializeAudioDecoder(done_cb);
451
452 case kInitAudioRenderer:
453 return InitializeAudioRenderer(done_cb);
454
455 case kInitVideoRenderer:
456 return InitializeVideoRenderer(done_cb);
457
458 case kInitPrerolling:
459 filter_collection_.reset();
460 {
461 base::AutoLock l(lock_);
462 // TODO(scherkus): |has_audio_| should be true no matter what --
463 // otherwise people with muted/disabled sound cards will make our
464 // default controls look as if every video doesn't contain an audio
465 // track.
466 has_audio_ = audio_renderer_ && !audio_disabled_;
467 has_video_ = video_renderer_;
scherkus (not reviewing) 2012/09/05 15:19:30 FYI: E:\b\build\slave\win\build\src\media\base\pip
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 I'd prefer video_renderer_ != NULL since that matc
scherkus (not reviewing) 2012/09/06 15:33:20 Done.
468 }
469 if (!audio_renderer_ && !video_renderer_) {
470 done_cb.Run(PIPELINE_ERROR_COULD_NOT_RENDER);
471 return;
472 }
473
474 buffering_state_cb_.Run(kHaveMetadata);
475
476 return DoInitialPreroll(done_cb);
477
478 case kStarting:
479 return DoPlay(done_cb);
480
481 case kStarted:
482 {
483 base::AutoLock l(lock_);
484 // We use audio stream to update the clock. So if there is such a
485 // stream, we pause the clock until we receive a valid timestamp.
486 waiting_for_clock_update_ = true;
487 if (!has_audio_) {
488 clock_->SetMaxTime(clock_->Duration());
489 StartClockIfWaitingForTimeUpdate_Locked();
490 }
491 }
492
493 // Fire canplaythrough immediately after playback begins because of
494 // crbug.com/106480.
495 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
496 if (status_ == PIPELINE_OK)
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 Based on the DCHECK on line 500, I believe this co
scherkus (not reviewing) 2012/09/06 15:33:20 Done.
497 buffering_state_cb_.Run(kPrerollCompleted);
498
499 DCHECK(!seek_cb_.is_null());
500 DCHECK_EQ(status_, PIPELINE_OK);
501 return base::ResetAndReturn(&seek_cb_).Run(PIPELINE_OK);
502
503 case kStopping:
504 case kStopped:
505 case kCreated:
506 case kSeeking:
507 NOTREACHED() << "State has no transition: " << state_;
508 return;
509 }
510 }
511
512 static void SetPlaybackRateForPreroll(
513 const scoped_refptr<AudioRenderer>& audio_renderer,
514 float playback_rate,
515 const base::Closure& done_cb) {
516 audio_renderer->SetPlaybackRate(playback_rate);
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 This doesn't seem right. Won't this cause the audi
scherkus (not reviewing) 2012/09/06 15:33:20 Removed entirely -- see response in pipeline_unitt
517 done_cb.Run();
518 }
519
520 void Pipeline::DoInitialPreroll(const PipelineStatusCB& done_cb) {
426 DCHECK(message_loop_->BelongsToCurrentThread()); 521 DCHECK(message_loop_->BelongsToCurrentThread());
427 DCHECK(!pending_callbacks_.get()); 522 DCHECK(!pending_callbacks_.get());
428 SerialRunner::Queue bound_fns; 523 SerialRunner::Queue bound_fns;
429 524
430 if (audio_renderer_) 525 base::TimeDelta seek_timestamp = demuxer_->GetStartTime();
431 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_));
432 526
433 if (video_renderer_) 527 // Preroll renderers.
434 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_)); 528 if (audio_renderer_) {
529 bound_fns.Push(base::Bind(
530 &SetPlaybackRateForPreroll, audio_renderer_, GetPlaybackRate()));
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 Is there some reason you can't set the playbackrat
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 I don't think this is right. I think you should se
scherkus (not reviewing) 2012/09/06 15:33:20 Removed entirely -- see response in pipeline_unitt
531 bound_fns.Push(base::Bind(
532 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp));
533 }
534
535 if (video_renderer_) {
536 bound_fns.Push(base::Bind(
537 &VideoRenderer::Preroll, video_renderer_, seek_timestamp));
538 }
435 539
436 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); 540 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
437 } 541 }
438 542
439 void Pipeline::DoFlush(const PipelineStatusCB& done_cb) { 543 void Pipeline::DoSeek(
544 base::TimeDelta seek_timestamp,
545 const PipelineStatusCB& done_cb) {
440 DCHECK(message_loop_->BelongsToCurrentThread()); 546 DCHECK(message_loop_->BelongsToCurrentThread());
441 DCHECK(!pending_callbacks_.get()); 547 DCHECK(!pending_callbacks_.get());
442 SerialRunner::Queue bound_fns; 548 SerialRunner::Queue bound_fns;
443 549
550 // Pause.
551 if (audio_renderer_)
552 bound_fns.Push(base::Bind(&AudioRenderer::Pause, audio_renderer_));
553 if (video_renderer_)
554 bound_fns.Push(base::Bind(&VideoRenderer::Pause, video_renderer_));
555
556 // Flush.
444 if (audio_renderer_) 557 if (audio_renderer_)
445 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_)); 558 bound_fns.Push(base::Bind(&AudioRenderer::Flush, audio_renderer_));
446
447 if (video_renderer_) 559 if (video_renderer_)
448 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_)); 560 bound_fns.Push(base::Bind(&VideoRenderer::Flush, video_renderer_));
449 561
562 // Seek demuxer.
563 bound_fns.Push(base::Bind(
564 &Demuxer::Seek, demuxer_, seek_timestamp));
565
566 // Preroll renderers.
567 if (audio_renderer_) {
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 How about creating a helper function for DoSeek()
scherkus (not reviewing) 2012/09/06 15:33:20 I think the tiny bit of duplication here is OK --
568 bound_fns.Push(base::Bind(
569 &SetPlaybackRateForPreroll, audio_renderer_, GetPlaybackRate()));
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 ditto
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 I don't think you want to bind to a static functio
scherkus (not reviewing) 2012/09/06 15:33:20 Removed entirely -- see response in pipeline_unitt
570 bound_fns.Push(base::Bind(
571 &AudioRenderer::Preroll, audio_renderer_, seek_timestamp));
572 }
573
574 if (video_renderer_) {
575 bound_fns.Push(base::Bind(
576 &VideoRenderer::Preroll, video_renderer_, seek_timestamp));
577 }
578
450 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); 579 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
451 } 580 }
452 581
453 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) { 582 void Pipeline::DoPlay(const PipelineStatusCB& done_cb) {
454 DCHECK(message_loop_->BelongsToCurrentThread()); 583 DCHECK(message_loop_->BelongsToCurrentThread());
455 DCHECK(!pending_callbacks_.get()); 584 DCHECK(!pending_callbacks_.get());
456 SerialRunner::Queue bound_fns; 585 SerialRunner::Queue bound_fns;
457 586
587 PlaybackRateChangedTask(GetPlaybackRate());
588 VolumeChangedTask(GetVolume());
589
458 if (audio_renderer_) 590 if (audio_renderer_)
459 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_)); 591 bound_fns.Push(base::Bind(&AudioRenderer::Play, audio_renderer_));
460 592
461 if (video_renderer_) 593 if (video_renderer_)
462 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_)); 594 bound_fns.Push(base::Bind(&VideoRenderer::Play, video_renderer_));
463 595
464 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); 596 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
465 } 597 }
466 598
467 void Pipeline::DoStop(const PipelineStatusCB& done_cb) { 599 void Pipeline::DoStop(const PipelineStatusCB& done_cb) {
468 DCHECK(message_loop_->BelongsToCurrentThread()); 600 DCHECK(message_loop_->BelongsToCurrentThread());
469 DCHECK(!pending_callbacks_.get()); 601 DCHECK(!pending_callbacks_.get());
470 SerialRunner::Queue bound_fns; 602 SerialRunner::Queue bound_fns;
471 603
472 if (demuxer_) 604 if (demuxer_)
473 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_)); 605 bound_fns.Push(base::Bind(&Demuxer::Stop, demuxer_));
474 606
475 if (audio_renderer_) 607 if (audio_renderer_)
476 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_)); 608 bound_fns.Push(base::Bind(&AudioRenderer::Stop, audio_renderer_));
477 609
478 if (video_renderer_) 610 if (video_renderer_)
479 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_)); 611 bound_fns.Push(base::Bind(&VideoRenderer::Stop, video_renderer_));
480 612
481 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); 613 pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb);
482 } 614 }
483 615
616 void Pipeline::OnStopCompleted(PipelineStatus status) {
617 DCHECK(message_loop_->BelongsToCurrentThread());
618 DCHECK_EQ(state_, kStopping);
619 {
620 base::AutoLock l(lock_);
621 running_ = false;
622 }
623
624 SetState(kStopped);
625 pending_callbacks_.reset();
626 filter_collection_.reset();
627 audio_decoder_ = NULL;
628 audio_renderer_ = NULL;
629 video_renderer_ = NULL;
630 demuxer_ = NULL;
631
632 // If we stop during initialization/seeking we want to run |seek_cb_|
633 // followed by |stop_cb_| so we don't leave outstanding callbacks around.
634 if (!seek_cb_.is_null()) {
635 base::ResetAndReturn(&seek_cb_).Run(status_);
636 error_cb_.Reset();
637 }
638 if (!stop_cb_.is_null()) {
639 base::ResetAndReturn(&stop_cb_).Run();
640 error_cb_.Reset();
641 }
642 if (!error_cb_.is_null()) {
643 DCHECK_NE(status_, PIPELINE_OK);
644 base::ResetAndReturn(&error_cb_).Run(status_);
645 }
646 }
647
484 void Pipeline::AddBufferedByteRange(int64 start, int64 end) { 648 void Pipeline::AddBufferedByteRange(int64 start, int64 end) {
485 DCHECK(IsRunning()); 649 DCHECK(IsRunning());
486 base::AutoLock auto_lock(lock_); 650 base::AutoLock auto_lock(lock_);
487 buffered_byte_ranges_.Add(start, end); 651 buffered_byte_ranges_.Add(start, end);
488 did_loading_progress_ = true; 652 did_loading_progress_ = true;
489 } 653 }
490 654
491 void Pipeline::AddBufferedTimeRange(base::TimeDelta start, 655 void Pipeline::AddBufferedTimeRange(base::TimeDelta start,
492 base::TimeDelta end) { 656 base::TimeDelta end) {
493 DCHECK(IsRunning()); 657 DCHECK(IsRunning());
(...skipping 19 matching lines...) Expand all
513 } 677 }
514 678
515 void Pipeline::OnVideoRendererEnded() { 679 void Pipeline::OnVideoRendererEnded() {
516 // Force post to process ended messages after current execution frame. 680 // Force post to process ended messages after current execution frame.
517 message_loop_->PostTask(FROM_HERE, base::Bind( 681 message_loop_->PostTask(FROM_HERE, base::Bind(
518 &Pipeline::DoVideoRendererEnded, this)); 682 &Pipeline::DoVideoRendererEnded, this));
519 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED)); 683 media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::VIDEO_ENDED));
520 } 684 }
521 685
522 // Called from any thread. 686 // 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) { 687 void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats) {
551 base::AutoLock auto_lock(lock_); 688 base::AutoLock auto_lock(lock_);
552 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded; 689 statistics_.audio_bytes_decoded += stats.audio_bytes_decoded;
553 statistics_.video_bytes_decoded += stats.video_bytes_decoded; 690 statistics_.video_bytes_decoded += stats.video_bytes_decoded;
554 statistics_.video_frames_decoded += stats.video_frames_decoded; 691 statistics_.video_frames_decoded += stats.video_frames_decoded;
555 statistics_.video_frames_dropped += stats.video_frames_dropped; 692 statistics_.video_frames_dropped += stats.video_frames_dropped;
556 } 693 }
557 694
558 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection, 695 void Pipeline::StartTask(scoped_ptr<FilterCollection> filter_collection,
559 const PipelineStatusCB& ended_cb, 696 const PipelineStatusCB& ended_cb,
560 const PipelineStatusCB& error_cb, 697 const PipelineStatusCB& error_cb,
561 const PipelineStatusCB& seek_cb, 698 const PipelineStatusCB& seek_cb,
562 const BufferingStateCB& buffering_state_cb) { 699 const BufferingStateCB& buffering_state_cb) {
563 DCHECK(message_loop_->BelongsToCurrentThread()); 700 DCHECK(message_loop_->BelongsToCurrentThread());
564 CHECK_EQ(kCreated, state_) 701 CHECK_EQ(kCreated, state_)
565 << "Media pipeline cannot be started more than once"; 702 << "Media pipeline cannot be started more than once";
566 703
567 filter_collection_ = filter_collection.Pass(); 704 filter_collection_ = filter_collection.Pass();
568 ended_cb_ = ended_cb; 705 ended_cb_ = ended_cb;
569 error_cb_ = error_cb; 706 error_cb_ = error_cb;
570 seek_cb_ = seek_cb; 707 seek_cb_ = seek_cb;
571 buffering_state_cb_ = buffering_state_cb; 708 buffering_state_cb_ = buffering_state_cb;
572 709
573 // Kick off initialization. 710 StateTransitionTask(PIPELINE_OK);
574 pipeline_init_state_.reset(new PipelineInitState());
575
576 SetState(kInitDemuxer);
577 InitializeDemuxer();
578 } 711 }
579 712
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) { 713 void Pipeline::StopTask(const base::Closure& stop_cb) {
685 DCHECK(message_loop_->BelongsToCurrentThread()); 714 DCHECK(message_loop_->BelongsToCurrentThread());
686 DCHECK(stop_cb_.is_null()); 715 DCHECK(stop_cb_.is_null());
687 716
688 if (state_ == kStopped) { 717 if (state_ == kStopped) {
689 stop_cb.Run(); 718 stop_cb.Run();
690 return; 719 return;
691 } 720 }
692 721
722 // TODO(scherkus): Remove after pipeline state machine refactoring has some
723 // time to bake http://crbug.com/110228
693 if (video_renderer_) 724 if (video_renderer_)
694 video_renderer_->PrepareForShutdownHack(); 725 video_renderer_->PrepareForShutdownHack();
695 726
696 if (tearing_down_ && status_ != PIPELINE_OK) { 727 SetState(kStopping);
697 // If we are stopping due to SetError(), stop normally instead of 728 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; 729 stop_cb_ = stop_cb;
706 730
707 if (!IsPipelineSeeking() && !tearing_down_) { 731 DoStop(base::Bind(&Pipeline::OnStopCompleted, this));
acolwell GONE FROM CHROMIUM 2012/09/06 09:19:10 Move the bind into DoStop() since all call sites d
scherkus (not reviewing) 2012/09/06 15:33:20 I prefer to have the resulting callback function d
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 } 732 }
714 733
715 void Pipeline::ErrorChangedTask(PipelineStatus error) { 734 void Pipeline::ErrorChangedTask(PipelineStatus error) {
716 DCHECK(message_loop_->BelongsToCurrentThread()); 735 DCHECK(message_loop_->BelongsToCurrentThread());
717 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; 736 DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
718 737
719 // Suppress executing additional error logic. Note that if we are currently 738 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; 739 return;
724 }
725 740
726 base::AutoLock auto_lock(lock_); 741 SetState(kStopping);
742 pending_callbacks_.reset();
727 status_ = error; 743 status_ = error;
728 744
729 // Posting TearDownPipeline() to message loop so that we can make sure 745 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 } 746 }
737 747
738 void Pipeline::PlaybackRateChangedTask(float playback_rate) { 748 void Pipeline::PlaybackRateChangedTask(float playback_rate) {
739 DCHECK(message_loop_->BelongsToCurrentThread()); 749 DCHECK(message_loop_->BelongsToCurrentThread());
740 750
741 if (state_ == kStopped || tearing_down_) 751 // Playback rate changes are only carried out while playing.
752 if (state_ != kStarting && state_ != kStarted)
742 return; 753 return;
743 754
744 // Suppress rate change until after seeking.
745 if (IsPipelineSeeking()) {
746 pending_playback_rate_ = playback_rate;
747 playback_rate_change_pending_ = true;
748 return;
749 }
750
751 { 755 {
752 base::AutoLock auto_lock(lock_); 756 base::AutoLock auto_lock(lock_);
753 clock_->SetPlaybackRate(playback_rate); 757 clock_->SetPlaybackRate(playback_rate);
754 } 758 }
755 759
756 // These will get set after initialization completes in case playback rate is
757 // set prior to initialization.
758 if (demuxer_) 760 if (demuxer_)
759 demuxer_->SetPlaybackRate(playback_rate); 761 demuxer_->SetPlaybackRate(playback_rate);
760 if (audio_renderer_) 762 if (audio_renderer_)
761 audio_renderer_->SetPlaybackRate(playback_rate_); 763 audio_renderer_->SetPlaybackRate(playback_rate_);
762 if (video_renderer_) 764 if (video_renderer_)
763 video_renderer_->SetPlaybackRate(playback_rate_); 765 video_renderer_->SetPlaybackRate(playback_rate_);
764 } 766 }
765 767
766 void Pipeline::VolumeChangedTask(float volume) { 768 void Pipeline::VolumeChangedTask(float volume) {
767 DCHECK(message_loop_->BelongsToCurrentThread()); 769 DCHECK(message_loop_->BelongsToCurrentThread());
768 770
769 if (state_ == kStopped || tearing_down_) 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) {
785 DCHECK(state_ == kStopping || state_ == kStopped)
786 << "Receive extra seek in unexpected state: " << state_;
787
782 // TODO(scherkus): should we run the callback? I'm tempted to say the API 788 // TODO(scherkus): should we run the callback? I'm tempted to say the API
783 // will only execute the first Seek() request. 789 // will only execute the first Seek() request.
784 DVLOG(1) << "Media pipeline has not started, ignoring seek to " 790 DVLOG(1) << "Media pipeline has not started, ignoring seek to "
785 << time.InMicroseconds() << " (current state: " << state_ << ")"; 791 << time.InMicroseconds() << " (current state: " << state_ << ")";
786 return; 792 return;
787 } 793 }
788 794
789 DCHECK(!seek_pending_); 795 DCHECK(seek_cb_.is_null());
790 seek_pending_ = true;
791 796
792 // We'll need to pause every filter before seeking. The state transition 797 SetState(kSeeking);
793 // is as follows: 798 base::TimeDelta seek_timestamp = std::max(time, demuxer_->GetStartTime());
794 // kStarted 799 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; 800 audio_ended_ = false;
801 video_ended_ = false; 801 video_ended_ = false;
802 seek_timestamp_ = std::max(time, demuxer_->GetStartTime());
803 seek_cb_ = seek_cb;
804 802
805 // Kick off seeking! 803 // Kick off seeking!
806 { 804 {
807 base::AutoLock auto_lock(lock_); 805 base::AutoLock auto_lock(lock_);
808 if (clock_->IsPlaying()) 806 if (clock_->IsPlaying())
809 clock_->Pause(); 807 clock_->Pause();
808 clock_->SetTime(seek_timestamp, seek_timestamp);
810 } 809 }
811 DoPause(base::Bind(&Pipeline::OnFilterStateTransition, this)); 810 DoSeek(seek_timestamp, base::Bind(&Pipeline::OnStateTransition, this));
812 } 811 }
813 812
814 void Pipeline::DoAudioRendererEnded() { 813 void Pipeline::DoAudioRendererEnded() {
815 DCHECK(message_loop_->BelongsToCurrentThread()); 814 DCHECK(message_loop_->BelongsToCurrentThread());
816 815
817 if (state_ != kStarted) 816 if (state_ != kStarted)
818 return; 817 return;
819 818
820 DCHECK(!audio_ended_); 819 DCHECK(!audio_ended_);
821 audio_ended_ = true; 820 audio_ended_ = true;
(...skipping 27 matching lines...) Expand all
849 return; 848 return;
850 849
851 if (video_renderer_ && !video_ended_) 850 if (video_renderer_ && !video_ended_)
852 return; 851 return;
853 852
854 { 853 {
855 base::AutoLock auto_lock(lock_); 854 base::AutoLock auto_lock(lock_);
856 clock_->EndOfStream(); 855 clock_->EndOfStream();
857 } 856 }
858 857
859 ReportStatus(ended_cb_, status_); 858 // TODO(scherkus): Change |ended_cb_| into a Closure.
859 DCHECK_EQ(status_, PIPELINE_OK);
860 ended_cb_.Run(status_);
860 } 861 }
861 862
862 void Pipeline::AudioDisabledTask() { 863 void Pipeline::AudioDisabledTask() {
863 DCHECK(message_loop_->BelongsToCurrentThread()); 864 DCHECK(message_loop_->BelongsToCurrentThread());
864 865
865 base::AutoLock auto_lock(lock_); 866 base::AutoLock auto_lock(lock_);
866 has_audio_ = false; 867 has_audio_ = false;
867 audio_disabled_ = true; 868 audio_disabled_ = true;
868 869
869 // Notify our demuxer that we're no longer rendering audio. 870 // Notify our demuxer that we're no longer rendering audio.
870 demuxer_->OnAudioRendererDisabled(); 871 demuxer_->OnAudioRendererDisabled();
871 872
872 // Start clock since there is no more audio to 873 // Start clock since there is no more audio to trigger clock updates.
873 // trigger clock updates.
874 clock_->SetMaxTime(clock_->Duration()); 874 clock_->SetMaxTime(clock_->Duration());
875 StartClockIfWaitingForTimeUpdate_Locked(); 875 StartClockIfWaitingForTimeUpdate_Locked();
876 } 876 }
877 877
878 void Pipeline::FilterStateTransitionTask() { 878 void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) {
879 DCHECK(message_loop_->BelongsToCurrentThread()); 879 DCHECK(message_loop_->BelongsToCurrentThread());
880 DCHECK(pending_callbacks_.get())
881 << "Filter state transitions must be completed via pending_callbacks_";
882 pending_callbacks_.reset();
883 880
884 // State transitions while tearing down are handled via 881 demuxer_ = filter_collection_->GetDemuxer();
885 // TeardownStateTransitionTask(). 882 if (!demuxer_) {
Ami GONE FROM CHROMIUM 2012/09/06 09:11:34 You said "done" to acolwell's comment about turnin
scherkus (not reviewing) 2012/09/06 15:33:20 Remove this stuff so we crash when faced with miss
886 // 883 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
887 // TODO(scherkus): Merge all state machinery!
888 if (state_ == kStopped || tearing_down_) {
889 return; 884 return;
890 } 885 }
891 886
892 if (!TransientState(state_)) { 887 demuxer_->Initialize(this, done_cb);
893 NOTREACHED() << "Invalid current state: " << state_; 888 }
894 SetError(PIPELINE_ERROR_ABORT); 889
890 void Pipeline::InitializeAudioDecoder(const PipelineStatusCB& done_cb) {
891 DCHECK(message_loop_->BelongsToCurrentThread());
892
893 scoped_refptr<DemuxerStream> stream =
894 demuxer_->GetStream(DemuxerStream::AUDIO);
895
896 if (!stream) {
897 done_cb.Run(PIPELINE_OK);
895 return; 898 return;
896 } 899 }
897 900
898 // Decrement the number of remaining transitions, making sure to transition 901 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 902
907 // Carry out the action for the current state. 903 if (!audio_decoder_) {
908 if (TransientState(state_)) { 904 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; 905 return;
1022 } 906 }
1023 907
1024 demuxer_->Initialize(this, base::Bind(&Pipeline::OnDemuxerInitialized, this)); 908 audio_decoder_->Initialize(
909 stream, done_cb, base::Bind(&Pipeline::OnUpdateStatistics, this));
1025 } 910 }
1026 911
1027 void Pipeline::OnDemuxerInitialized(PipelineStatus status) { 912 void Pipeline::InitializeAudioRenderer(const PipelineStatusCB& done_cb) {
1028 if (!message_loop_->BelongsToCurrentThread()) { 913 DCHECK(message_loop_->BelongsToCurrentThread());
1029 message_loop_->PostTask(FROM_HERE, base::Bind( 914
1030 &Pipeline::OnDemuxerInitialized, this, status)); 915 if (!audio_decoder_) {
916 done_cb.Run(PIPELINE_OK);
1031 return; 917 return;
1032 } 918 }
1033 919
1034 if (status != PIPELINE_OK) { 920 filter_collection_->SelectAudioRenderer(&audio_renderer_);
1035 SetError(status); 921 if (!audio_renderer_) {
922 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1036 return; 923 return;
1037 } 924 }
1038 925
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( 926 audio_renderer_->Initialize(
1090 decoder, 927 audio_decoder_,
1091 base::Bind(&Pipeline::OnFilterInitialize, this), 928 done_cb,
1092 base::Bind(&Pipeline::OnAudioUnderflow, this), 929 base::Bind(&Pipeline::OnAudioUnderflow, this),
1093 base::Bind(&Pipeline::OnAudioTimeUpdate, this), 930 base::Bind(&Pipeline::OnAudioTimeUpdate, this),
1094 base::Bind(&Pipeline::OnAudioRendererEnded, this), 931 base::Bind(&Pipeline::OnAudioRendererEnded, this),
1095 base::Bind(&Pipeline::OnAudioDisabled, this), 932 base::Bind(&Pipeline::OnAudioDisabled, this),
1096 base::Bind(&Pipeline::SetError, this)); 933 base::Bind(&Pipeline::SetError, this));
1097 return true;
1098 } 934 }
1099 935
1100 bool Pipeline::InitializeVideoRenderer( 936 void Pipeline::InitializeVideoRenderer(const PipelineStatusCB& done_cb) {
1101 const scoped_refptr<DemuxerStream>& stream) {
1102 DCHECK(message_loop_->BelongsToCurrentThread()); 937 DCHECK(message_loop_->BelongsToCurrentThread());
1103 DCHECK(IsPipelineOk());
1104 938
1105 if (!stream) 939 scoped_refptr<DemuxerStream> stream =
1106 return false; 940 demuxer_->GetStream(DemuxerStream::VIDEO);
941
942 if (!stream) {
943 done_cb.Run(PIPELINE_OK);
944 return;
945 }
1107 946
1108 filter_collection_->SelectVideoRenderer(&video_renderer_); 947 filter_collection_->SelectVideoRenderer(&video_renderer_);
1109 if (!video_renderer_) { 948 if (!video_renderer_) {
1110 SetError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING); 949 done_cb.Run(PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
1111 return false; 950 return;
951 }
952
953 {
954 // Get an initial natural size so we have something when we signal
955 // the kHaveMetadata buffering state.
956 base::AutoLock l(lock_);
957 natural_size_ = stream->video_decoder_config().natural_size();
1112 } 958 }
1113 959
1114 video_renderer_->Initialize( 960 video_renderer_->Initialize(
1115 stream, 961 stream,
1116 *filter_collection_->GetVideoDecoders(), 962 *filter_collection_->GetVideoDecoders(),
1117 base::Bind(&Pipeline::OnFilterInitialize, this), 963 done_cb,
1118 base::Bind(&Pipeline::OnUpdateStatistics, this), 964 base::Bind(&Pipeline::OnUpdateStatistics, this),
1119 base::Bind(&Pipeline::OnVideoTimeUpdate, this), 965 base::Bind(&Pipeline::OnVideoTimeUpdate, this),
1120 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this), 966 base::Bind(&Pipeline::OnNaturalVideoSizeChanged, this),
1121 base::Bind(&Pipeline::OnVideoRendererEnded, this), 967 base::Bind(&Pipeline::OnVideoRendererEnded, this),
1122 base::Bind(&Pipeline::SetError, this), 968 base::Bind(&Pipeline::SetError, this),
1123 base::Bind(&Pipeline::GetMediaTime, this), 969 base::Bind(&Pipeline::GetMediaTime, this),
1124 base::Bind(&Pipeline::GetMediaDuration, this)); 970 base::Bind(&Pipeline::GetMediaDuration, this));
1125 filter_collection_->GetVideoDecoders()->clear(); 971 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 } 972 }
1220 973
1221 void Pipeline::OnAudioUnderflow() { 974 void Pipeline::OnAudioUnderflow() {
1222 if (!message_loop_->BelongsToCurrentThread()) { 975 if (!message_loop_->BelongsToCurrentThread()) {
1223 message_loop_->PostTask(FROM_HERE, base::Bind( 976 message_loop_->PostTask(FROM_HERE, base::Bind(
1224 &Pipeline::OnAudioUnderflow, this)); 977 &Pipeline::OnAudioUnderflow, this));
1225 return; 978 return;
1226 } 979 }
1227 980
1228 if (state_ != kStarted) 981 if (state_ != kStarted)
1229 return; 982 return;
1230 983
1231 if (audio_renderer_) 984 if (audio_renderer_)
1232 audio_renderer_->ResumeAfterUnderflow(true); 985 audio_renderer_->ResumeAfterUnderflow(true);
1233 } 986 }
1234 987
1235 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() { 988 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() {
1236 lock_.AssertAcquired(); 989 lock_.AssertAcquired();
1237 if (!waiting_for_clock_update_) 990 if (!waiting_for_clock_update_)
1238 return; 991 return;
1239 992
1240 waiting_for_clock_update_ = false; 993 waiting_for_clock_update_ = false;
1241 clock_->Play(); 994 clock_->Play();
1242 } 995 }
1243 996
1244 } // namespace media 997 } // namespace media
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698