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
3 // found in the LICENSE file.
5 #include "media/base/pipeline.h"
10 #include "base/callback.h"
11 #include "base/callback_helpers.h"
12 #include "base/compiler_specific.h"
13 #include "base/location.h"
14 #include "base/metrics/histogram.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/synchronization/condition_variable.h"
20 #include "media/base/audio_decoder.h"
21 #include "media/base/audio_renderer.h"
22 #include "media/base/clock.h"
23 #include "media/base/filter_collection.h"
24 #include "media/base/media_log.h"
25 #include "media/base/text_renderer.h"
26 #include "media/base/text_track_config.h"
27 #include "media/base/video_decoder.h"
28 #include "media/base/video_decoder_config.h"
29 #include "media/base/video_renderer.h"
31 using base::TimeDelta
;
36 const scoped_refptr
<base::SingleThreadTaskRunner
>& task_runner
,
38 : task_runner_(task_runner
),
39 media_log_(media_log
),
41 did_loading_progress_(false),
45 clock_(new Clock(&default_tick_clock_
)),
46 waiting_for_clock_update_(false),
54 audio_disabled_(false),
56 creation_time_(default_tick_clock_
.NowTicks()) {
57 media_log_
->AddEvent(media_log_
->CreatePipelineStateChangedEvent(kCreated
));
59 media_log_
->CreateEvent(MediaLogEvent::PIPELINE_CREATED
));
62 Pipeline::~Pipeline() {
63 DCHECK(thread_checker_
.CalledOnValidThread())
64 << "Pipeline must be destroyed on same thread that created it";
65 DCHECK(!running_
) << "Stop() must complete before destroying object";
66 DCHECK(stop_cb_
.is_null());
67 DCHECK(seek_cb_
.is_null());
70 media_log_
->CreateEvent(MediaLogEvent::PIPELINE_DESTROYED
));
73 void Pipeline::Start(scoped_ptr
<FilterCollection
> collection
,
74 const base::Closure
& ended_cb
,
75 const PipelineStatusCB
& error_cb
,
76 const PipelineStatusCB
& seek_cb
,
77 const BufferingStateCB
& buffering_state_cb
,
78 const base::Closure
& duration_change_cb
) {
79 base::AutoLock
auto_lock(lock_
);
80 CHECK(!running_
) << "Media pipeline is already running";
81 DCHECK(!buffering_state_cb
.is_null());
84 task_runner_
->PostTask(FROM_HERE
, base::Bind(
85 &Pipeline::StartTask
, base::Unretained(this), base::Passed(&collection
),
86 ended_cb
, error_cb
, seek_cb
, buffering_state_cb
, duration_change_cb
));
89 void Pipeline::Stop(const base::Closure
& stop_cb
) {
90 base::AutoLock
auto_lock(lock_
);
91 task_runner_
->PostTask(FROM_HERE
, base::Bind(
92 &Pipeline::StopTask
, base::Unretained(this), stop_cb
));
95 void Pipeline::Seek(TimeDelta time
, const PipelineStatusCB
& seek_cb
) {
96 base::AutoLock
auto_lock(lock_
);
98 NOTREACHED() << "Media pipeline isn't running";
102 task_runner_
->PostTask(FROM_HERE
, base::Bind(
103 &Pipeline::SeekTask
, base::Unretained(this), time
, seek_cb
));
106 bool Pipeline::IsRunning() const {
107 base::AutoLock
auto_lock(lock_
);
111 bool Pipeline::HasAudio() const {
112 base::AutoLock
auto_lock(lock_
);
116 bool Pipeline::HasVideo() const {
117 base::AutoLock
auto_lock(lock_
);
121 float Pipeline::GetPlaybackRate() const {
122 base::AutoLock
auto_lock(lock_
);
123 return playback_rate_
;
126 void Pipeline::SetPlaybackRate(float playback_rate
) {
127 if (playback_rate
< 0.0f
)
130 base::AutoLock
auto_lock(lock_
);
131 playback_rate_
= playback_rate
;
133 task_runner_
->PostTask(FROM_HERE
, base::Bind(
134 &Pipeline::PlaybackRateChangedTask
, base::Unretained(this),
139 float Pipeline::GetVolume() const {
140 base::AutoLock
auto_lock(lock_
);
144 void Pipeline::SetVolume(float volume
) {
145 if (volume
< 0.0f
|| volume
> 1.0f
)
148 base::AutoLock
auto_lock(lock_
);
151 task_runner_
->PostTask(FROM_HERE
, base::Bind(
152 &Pipeline::VolumeChangedTask
, base::Unretained(this), volume
));
156 TimeDelta
Pipeline::GetMediaTime() const {
157 base::AutoLock
auto_lock(lock_
);
158 return clock_
->Elapsed();
161 Ranges
<TimeDelta
> Pipeline::GetBufferedTimeRanges() {
162 base::AutoLock
auto_lock(lock_
);
163 Ranges
<TimeDelta
> time_ranges
;
164 for (size_t i
= 0; i
< buffered_time_ranges_
.size(); ++i
) {
165 time_ranges
.Add(buffered_time_ranges_
.start(i
),
166 buffered_time_ranges_
.end(i
));
168 if (clock_
->Duration() == TimeDelta() || total_bytes_
== 0)
170 for (size_t i
= 0; i
< buffered_byte_ranges_
.size(); ++i
) {
171 TimeDelta start
= TimeForByteOffset_Locked(buffered_byte_ranges_
.start(i
));
172 TimeDelta end
= TimeForByteOffset_Locked(buffered_byte_ranges_
.end(i
));
173 // Cap approximated buffered time at the length of the video.
174 end
= std::min(end
, clock_
->Duration());
175 time_ranges
.Add(start
, end
);
181 TimeDelta
Pipeline::GetMediaDuration() const {
182 base::AutoLock
auto_lock(lock_
);
183 return clock_
->Duration();
186 int64
Pipeline::GetTotalBytes() const {
187 base::AutoLock
auto_lock(lock_
);
191 gfx::Size
Pipeline::GetInitialNaturalSize() const {
192 base::AutoLock
auto_lock(lock_
);
193 return initial_natural_size_
;
196 bool Pipeline::DidLoadingProgress() const {
197 base::AutoLock
auto_lock(lock_
);
198 bool ret
= did_loading_progress_
;
199 did_loading_progress_
= false;
203 PipelineStatistics
Pipeline::GetStatistics() const {
204 base::AutoLock
auto_lock(lock_
);
208 void Pipeline::SetClockForTesting(Clock
* clock
) {
212 void Pipeline::SetErrorForTesting(PipelineStatus status
) {
216 void Pipeline::SetState(State next_state
) {
217 if (state_
!= kStarted
&& next_state
== kStarted
&&
218 !creation_time_
.is_null()) {
219 UMA_HISTOGRAM_TIMES("Media.TimeToPipelineStarted",
220 default_tick_clock_
.NowTicks() - creation_time_
);
221 creation_time_
= base::TimeTicks();
224 DVLOG(2) << GetStateString(state_
) << " -> " << GetStateString(next_state
);
227 media_log_
->AddEvent(media_log_
->CreatePipelineStateChangedEvent(next_state
));
230 #define RETURN_STRING(state) case state: return #state;
232 const char* Pipeline::GetStateString(State state
) {
234 RETURN_STRING(kCreated
);
235 RETURN_STRING(kInitDemuxer
);
236 RETURN_STRING(kInitAudioRenderer
);
237 RETURN_STRING(kInitVideoRenderer
);
238 RETURN_STRING(kInitPrerolling
);
239 RETURN_STRING(kSeeking
);
240 RETURN_STRING(kStarting
);
241 RETURN_STRING(kStarted
);
242 RETURN_STRING(kStopping
);
243 RETURN_STRING(kStopped
);
251 Pipeline::State
Pipeline::GetNextState() const {
252 DCHECK(task_runner_
->BelongsToCurrentThread());
253 DCHECK(stop_cb_
.is_null())
254 << "State transitions don't happen when stopping";
255 DCHECK_EQ(status_
, PIPELINE_OK
)
256 << "State transitions don't happen when there's an error: " << status_
;
263 if (demuxer_
->GetStream(DemuxerStream::AUDIO
))
264 return kInitAudioRenderer
;
265 if (demuxer_
->GetStream(DemuxerStream::VIDEO
))
266 return kInitVideoRenderer
;
267 return kInitPrerolling
;
269 case kInitAudioRenderer
:
270 if (demuxer_
->GetStream(DemuxerStream::VIDEO
))
271 return kInitVideoRenderer
;
272 return kInitPrerolling
;
274 case kInitVideoRenderer
:
275 return kInitPrerolling
;
277 case kInitPrerolling
:
291 NOTREACHED() << "State has no transition: " << state_
;
295 void Pipeline::OnDemuxerError(PipelineStatus error
) {
299 void Pipeline::AddTextStream(DemuxerStream
* text_stream
,
300 const TextTrackConfig
& config
) {
301 task_runner_
->PostTask(FROM_HERE
, base::Bind(
302 &Pipeline::AddTextStreamTask
, base::Unretained(this),
303 text_stream
, config
));
306 void Pipeline::RemoveTextStream(DemuxerStream
* text_stream
) {
307 task_runner_
->PostTask(FROM_HERE
, base::Bind(
308 &Pipeline::RemoveTextStreamTask
, base::Unretained(this),
312 void Pipeline::SetError(PipelineStatus error
) {
314 DCHECK_NE(PIPELINE_OK
, error
);
315 VLOG(1) << "Media pipeline error: " << error
;
317 task_runner_
->PostTask(FROM_HERE
, base::Bind(
318 &Pipeline::ErrorChangedTask
, base::Unretained(this), error
));
320 media_log_
->AddEvent(media_log_
->CreatePipelineErrorEvent(error
));
323 void Pipeline::OnAudioDisabled() {
325 task_runner_
->PostTask(FROM_HERE
, base::Bind(
326 &Pipeline::AudioDisabledTask
, base::Unretained(this)));
327 media_log_
->AddEvent(
328 media_log_
->CreateEvent(MediaLogEvent::AUDIO_RENDERER_DISABLED
));
331 void Pipeline::OnAudioTimeUpdate(TimeDelta time
, TimeDelta max_time
) {
332 DCHECK_LE(time
.InMicroseconds(), max_time
.InMicroseconds());
334 base::AutoLock
auto_lock(lock_
);
338 if (waiting_for_clock_update_
&& time
< clock_
->Elapsed())
341 // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
342 // http://crbug.com/137973
343 if (state_
== kSeeking
)
346 clock_
->SetTime(time
, max_time
);
347 StartClockIfWaitingForTimeUpdate_Locked();
350 void Pipeline::OnVideoTimeUpdate(TimeDelta max_time
) {
352 base::AutoLock
auto_lock(lock_
);
357 // TODO(scherkus): |state_| should only be accessed on pipeline thread, see
358 // http://crbug.com/137973
359 if (state_
== kSeeking
)
362 DCHECK(!waiting_for_clock_update_
);
363 clock_
->SetMaxTime(max_time
);
366 void Pipeline::SetDuration(TimeDelta duration
) {
368 media_log_
->AddEvent(
369 media_log_
->CreateTimeEvent(
370 MediaLogEvent::DURATION_SET
, "duration", duration
));
371 UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration
);
373 base::AutoLock
auto_lock(lock_
);
374 clock_
->SetDuration(duration
);
375 if (!duration_change_cb_
.is_null())
376 duration_change_cb_
.Run();
379 void Pipeline::SetTotalBytes(int64 total_bytes
) {
381 media_log_
->AddEvent(
382 media_log_
->CreateStringEvent(
383 MediaLogEvent::TOTAL_BYTES_SET
, "total_bytes",
384 base::Int64ToString(total_bytes
)));
385 int64 total_mbytes
= total_bytes
>> 20;
386 if (total_mbytes
> kint32max
)
387 total_mbytes
= kint32max
;
388 UMA_HISTOGRAM_CUSTOM_COUNTS(
389 "Media.TotalMBytes", static_cast<int32
>(total_mbytes
), 1, kint32max
, 50);
391 base::AutoLock
auto_lock(lock_
);
392 total_bytes_
= total_bytes
;
395 TimeDelta
Pipeline::TimeForByteOffset_Locked(int64 byte_offset
) const {
396 lock_
.AssertAcquired();
397 // Use floating point to avoid potential overflow when using 64 bit integers.
398 double time_offset_in_ms
= clock_
->Duration().InMilliseconds() *
399 (static_cast<double>(byte_offset
) / total_bytes_
);
400 TimeDelta
time_offset(TimeDelta::FromMilliseconds(
401 static_cast<int64
>(time_offset_in_ms
)));
402 // Since the byte->time calculation is approximate, fudge the beginning &
403 // ending areas to look better.
404 TimeDelta epsilon
= clock_
->Duration() / 100;
405 if (time_offset
< epsilon
)
407 if (time_offset
+ epsilon
> clock_
->Duration())
408 return clock_
->Duration();
412 void Pipeline::OnStateTransition(PipelineStatus status
) {
413 // Force post to process state transitions after current execution frame.
414 task_runner_
->PostTask(FROM_HERE
, base::Bind(
415 &Pipeline::StateTransitionTask
, base::Unretained(this), status
));
418 void Pipeline::StateTransitionTask(PipelineStatus status
) {
419 DCHECK(task_runner_
->BelongsToCurrentThread());
421 // No-op any state transitions if we're stopping.
422 if (state_
== kStopping
|| state_
== kStopped
)
425 // Preserve existing abnormal status, otherwise update based on the result of
426 // the previous operation.
427 status_
= (status_
!= PIPELINE_OK
? status_
: status
);
429 if (status_
!= PIPELINE_OK
) {
430 ErrorChangedTask(status_
);
434 // Guard against accidentally clearing |pending_callbacks_| for states that
435 // use it as well as states that should not be using it.
437 // TODO(scherkus): Make every state transition use |pending_callbacks_|.
438 DCHECK_EQ(pending_callbacks_
.get() != NULL
,
439 (state_
== kInitPrerolling
|| state_
== kStarting
||
440 state_
== kSeeking
));
441 pending_callbacks_
.reset();
443 PipelineStatusCB done_cb
= base::Bind(
444 &Pipeline::OnStateTransition
, base::Unretained(this));
446 // Switch states, performing any entrance actions for the new state as well.
447 SetState(GetNextState());
450 return InitializeDemuxer(done_cb
);
452 case kInitAudioRenderer
:
453 return InitializeAudioRenderer(done_cb
);
455 case kInitVideoRenderer
:
456 return InitializeVideoRenderer(done_cb
);
458 case kInitPrerolling
:
459 filter_collection_
.reset();
461 base::AutoLock
l(lock_
);
462 // We do not want to start the clock running. We only want to set the
463 // base media time so our timestamp calculations will be correct.
464 clock_
->SetTime(demuxer_
->GetStartTime(), demuxer_
->GetStartTime());
466 // TODO(scherkus): |has_audio_| should be true no matter what --
467 // otherwise people with muted/disabled sound cards will make our
468 // default controls look as if every video doesn't contain an audio
470 has_audio_
= audio_renderer_
!= NULL
&& !audio_disabled_
;
471 has_video_
= video_renderer_
!= NULL
;
473 if (!audio_renderer_
&& !video_renderer_
) {
474 done_cb
.Run(PIPELINE_ERROR_COULD_NOT_RENDER
);
478 buffering_state_cb_
.Run(kHaveMetadata
);
480 return DoInitialPreroll(done_cb
);
483 return DoPlay(done_cb
);
487 base::AutoLock
l(lock_
);
488 // We use audio stream to update the clock. So if there is such a
489 // stream, we pause the clock until we receive a valid timestamp.
490 waiting_for_clock_update_
= true;
492 clock_
->SetMaxTime(clock_
->Duration());
493 StartClockIfWaitingForTimeUpdate_Locked();
497 DCHECK(!seek_cb_
.is_null());
498 DCHECK_EQ(status_
, PIPELINE_OK
);
500 // Fire canplaythrough immediately after playback begins because of
502 // TODO(vrk): set ready state to HaveFutureData when bug above is fixed.
503 buffering_state_cb_
.Run(kPrerollCompleted
);
504 return base::ResetAndReturn(&seek_cb_
).Run(PIPELINE_OK
);
510 NOTREACHED() << "State has no transition: " << state_
;
515 // Note that the usage of base::Unretained() with the audio/video renderers
516 // in the following DoXXX() functions is considered safe as they are owned by
517 // |pending_callbacks_| and share the same lifetime.
519 // That being said, deleting the renderers while keeping |pending_callbacks_|
520 // running on the media thread would result in crashes.
521 void Pipeline::DoInitialPreroll(const PipelineStatusCB
& done_cb
) {
522 DCHECK(task_runner_
->BelongsToCurrentThread());
523 DCHECK(!pending_callbacks_
.get());
524 SerialRunner::Queue bound_fns
;
526 base::TimeDelta seek_timestamp
= demuxer_
->GetStartTime();
528 // Preroll renderers.
529 if (audio_renderer_
) {
530 bound_fns
.Push(base::Bind(
531 &AudioRenderer::Preroll
, base::Unretained(audio_renderer_
.get()),
535 if (video_renderer_
) {
536 bound_fns
.Push(base::Bind(
537 &VideoRenderer::Preroll
, base::Unretained(video_renderer_
.get()),
541 pending_callbacks_
= SerialRunner::Run(bound_fns
, done_cb
);
544 void Pipeline::DoSeek(
545 base::TimeDelta seek_timestamp
,
546 const PipelineStatusCB
& done_cb
) {
547 DCHECK(task_runner_
->BelongsToCurrentThread());
548 DCHECK(!pending_callbacks_
.get());
549 SerialRunner::Queue bound_fns
;
552 if (audio_renderer_
) {
553 bound_fns
.Push(base::Bind(
554 &AudioRenderer::Pause
, base::Unretained(audio_renderer_
.get())));
556 if (video_renderer_
) {
557 bound_fns
.Push(base::Bind(
558 &VideoRenderer::Pause
, base::Unretained(video_renderer_
.get())));
560 if (text_renderer_
) {
561 bound_fns
.Push(base::Bind(
562 &TextRenderer::Pause
, base::Unretained(text_renderer_
.get())));
566 if (audio_renderer_
) {
567 bound_fns
.Push(base::Bind(
568 &AudioRenderer::Flush
, base::Unretained(audio_renderer_
.get())));
570 if (video_renderer_
) {
571 bound_fns
.Push(base::Bind(
572 &VideoRenderer::Flush
, base::Unretained(video_renderer_
.get())));
574 if (text_renderer_
) {
575 bound_fns
.Push(base::Bind(
576 &TextRenderer::Flush
, base::Unretained(text_renderer_
.get())));
580 bound_fns
.Push(base::Bind(
581 &Demuxer::Seek
, base::Unretained(demuxer_
), seek_timestamp
));
583 // Preroll renderers.
584 if (audio_renderer_
) {
585 bound_fns
.Push(base::Bind(
586 &AudioRenderer::Preroll
, base::Unretained(audio_renderer_
.get()),
590 if (video_renderer_
) {
591 bound_fns
.Push(base::Bind(
592 &VideoRenderer::Preroll
, base::Unretained(video_renderer_
.get()),
596 pending_callbacks_
= SerialRunner::Run(bound_fns
, done_cb
);
599 void Pipeline::DoPlay(const PipelineStatusCB
& done_cb
) {
600 DCHECK(task_runner_
->BelongsToCurrentThread());
601 DCHECK(!pending_callbacks_
.get());
602 SerialRunner::Queue bound_fns
;
604 PlaybackRateChangedTask(GetPlaybackRate());
605 VolumeChangedTask(GetVolume());
607 if (audio_renderer_
) {
608 bound_fns
.Push(base::Bind(
609 &AudioRenderer::Play
, base::Unretained(audio_renderer_
.get())));
612 if (video_renderer_
) {
613 bound_fns
.Push(base::Bind(
614 &VideoRenderer::Play
, base::Unretained(video_renderer_
.get())));
617 if (text_renderer_
) {
618 bound_fns
.Push(base::Bind(
619 &TextRenderer::Play
, base::Unretained(text_renderer_
.get())));
622 pending_callbacks_
= SerialRunner::Run(bound_fns
, done_cb
);
625 void Pipeline::DoStop(const PipelineStatusCB
& done_cb
) {
626 DCHECK(task_runner_
->BelongsToCurrentThread());
627 DCHECK(!pending_callbacks_
.get());
628 SerialRunner::Queue bound_fns
;
631 bound_fns
.Push(base::Bind(
632 &Demuxer::Stop
, base::Unretained(demuxer_
)));
635 if (audio_renderer_
) {
636 bound_fns
.Push(base::Bind(
637 &AudioRenderer::Stop
, base::Unretained(audio_renderer_
.get())));
640 if (video_renderer_
) {
641 bound_fns
.Push(base::Bind(
642 &VideoRenderer::Stop
, base::Unretained(video_renderer_
.get())));
645 if (text_renderer_
) {
646 bound_fns
.Push(base::Bind(
647 &TextRenderer::Stop
, base::Unretained(text_renderer_
.get())));
650 pending_callbacks_
= SerialRunner::Run(bound_fns
, done_cb
);
653 void Pipeline::OnStopCompleted(PipelineStatus status
) {
654 DCHECK(task_runner_
->BelongsToCurrentThread());
655 DCHECK_EQ(state_
, kStopping
);
657 base::AutoLock
l(lock_
);
662 pending_callbacks_
.reset();
663 filter_collection_
.reset();
664 audio_renderer_
.reset();
665 video_renderer_
.reset();
666 text_renderer_
.reset();
669 // If we stop during initialization/seeking we want to run |seek_cb_|
670 // followed by |stop_cb_| so we don't leave outstanding callbacks around.
671 if (!seek_cb_
.is_null()) {
672 base::ResetAndReturn(&seek_cb_
).Run(status_
);
675 if (!stop_cb_
.is_null()) {
677 base::ResetAndReturn(&stop_cb_
).Run();
679 // NOTE: pipeline may be deleted at this point in time as a result of
680 // executing |stop_cb_|.
683 if (!error_cb_
.is_null()) {
684 DCHECK_NE(status_
, PIPELINE_OK
);
685 base::ResetAndReturn(&error_cb_
).Run(status_
);
689 void Pipeline::AddBufferedByteRange(int64 start
, int64 end
) {
691 base::AutoLock
auto_lock(lock_
);
692 buffered_byte_ranges_
.Add(start
, end
);
693 did_loading_progress_
= true;
696 void Pipeline::AddBufferedTimeRange(base::TimeDelta start
,
697 base::TimeDelta end
) {
699 base::AutoLock
auto_lock(lock_
);
700 buffered_time_ranges_
.Add(start
, end
);
701 did_loading_progress_
= true;
704 void Pipeline::OnAudioRendererEnded() {
705 // Force post to process ended tasks after current execution frame.
706 task_runner_
->PostTask(FROM_HERE
, base::Bind(
707 &Pipeline::DoAudioRendererEnded
, base::Unretained(this)));
708 media_log_
->AddEvent(media_log_
->CreateEvent(MediaLogEvent::AUDIO_ENDED
));
711 void Pipeline::OnVideoRendererEnded() {
712 // Force post to process ended tasks after current execution frame.
713 task_runner_
->PostTask(FROM_HERE
, base::Bind(
714 &Pipeline::DoVideoRendererEnded
, base::Unretained(this)));
715 media_log_
->AddEvent(media_log_
->CreateEvent(MediaLogEvent::VIDEO_ENDED
));
718 void Pipeline::OnTextRendererEnded() {
719 // Force post to process ended messages after current execution frame.
720 task_runner_
->PostTask(FROM_HERE
, base::Bind(
721 &Pipeline::DoTextRendererEnded
, base::Unretained(this)));
722 media_log_
->AddEvent(media_log_
->CreateEvent(MediaLogEvent::TEXT_ENDED
));
725 // Called from any thread.
726 void Pipeline::OnUpdateStatistics(const PipelineStatistics
& stats
) {
727 base::AutoLock
auto_lock(lock_
);
728 statistics_
.audio_bytes_decoded
+= stats
.audio_bytes_decoded
;
729 statistics_
.video_bytes_decoded
+= stats
.video_bytes_decoded
;
730 statistics_
.video_frames_decoded
+= stats
.video_frames_decoded
;
731 statistics_
.video_frames_dropped
+= stats
.video_frames_dropped
;
734 void Pipeline::StartTask(scoped_ptr
<FilterCollection
> filter_collection
,
735 const base::Closure
& ended_cb
,
736 const PipelineStatusCB
& error_cb
,
737 const PipelineStatusCB
& seek_cb
,
738 const BufferingStateCB
& buffering_state_cb
,
739 const base::Closure
& duration_change_cb
) {
740 DCHECK(task_runner_
->BelongsToCurrentThread());
741 CHECK_EQ(kCreated
, state_
)
742 << "Media pipeline cannot be started more than once";
744 filter_collection_
= filter_collection
.Pass();
745 ended_cb_
= ended_cb
;
746 error_cb_
= error_cb
;
748 buffering_state_cb_
= buffering_state_cb
;
749 duration_change_cb_
= duration_change_cb
;
751 text_renderer_
= filter_collection_
->GetTextRenderer();
753 if (text_renderer_
) {
754 text_renderer_
->Initialize(
755 base::Bind(&Pipeline::OnTextRendererEnded
, base::Unretained(this)));
758 StateTransitionTask(PIPELINE_OK
);
761 void Pipeline::StopTask(const base::Closure
& stop_cb
) {
762 DCHECK(task_runner_
->BelongsToCurrentThread());
763 DCHECK(stop_cb_
.is_null());
765 if (state_
== kStopped
) {
772 // We may already be stopping due to a runtime error.
773 if (state_
== kStopping
)
777 pending_callbacks_
.reset();
778 DoStop(base::Bind(&Pipeline::OnStopCompleted
, base::Unretained(this)));
781 void Pipeline::ErrorChangedTask(PipelineStatus error
) {
782 DCHECK(task_runner_
->BelongsToCurrentThread());
783 DCHECK_NE(PIPELINE_OK
, error
) << "PIPELINE_OK isn't an error!";
785 if (state_
== kStopping
|| state_
== kStopped
)
789 pending_callbacks_
.reset();
792 DoStop(base::Bind(&Pipeline::OnStopCompleted
, base::Unretained(this)));
795 void Pipeline::PlaybackRateChangedTask(float playback_rate
) {
796 DCHECK(task_runner_
->BelongsToCurrentThread());
798 // Playback rate changes are only carried out while playing.
799 if (state_
!= kStarting
&& state_
!= kStarted
)
803 base::AutoLock
auto_lock(lock_
);
804 clock_
->SetPlaybackRate(playback_rate
);
808 audio_renderer_
->SetPlaybackRate(playback_rate_
);
810 video_renderer_
->SetPlaybackRate(playback_rate_
);
813 void Pipeline::VolumeChangedTask(float volume
) {
814 DCHECK(task_runner_
->BelongsToCurrentThread());
816 // Volume changes are only carried out while playing.
817 if (state_
!= kStarting
&& state_
!= kStarted
)
821 audio_renderer_
->SetVolume(volume
);
824 void Pipeline::SeekTask(TimeDelta time
, const PipelineStatusCB
& seek_cb
) {
825 DCHECK(task_runner_
->BelongsToCurrentThread());
826 DCHECK(stop_cb_
.is_null());
828 // Suppress seeking if we're not fully started.
829 if (state_
!= kStarted
) {
830 DCHECK(state_
== kStopping
|| state_
== kStopped
)
831 << "Receive extra seek in unexpected state: " << state_
;
833 // TODO(scherkus): should we run the callback? I'm tempted to say the API
834 // will only execute the first Seek() request.
835 DVLOG(1) << "Media pipeline has not started, ignoring seek to "
836 << time
.InMicroseconds() << " (current state: " << state_
<< ")";
840 DCHECK(seek_cb_
.is_null());
843 base::TimeDelta seek_timestamp
= std::max(time
, demuxer_
->GetStartTime());
845 audio_ended_
= false;
846 video_ended_
= false;
851 base::AutoLock
auto_lock(lock_
);
852 if (clock_
->IsPlaying())
854 clock_
->SetTime(seek_timestamp
, seek_timestamp
);
856 DoSeek(seek_timestamp
, base::Bind(
857 &Pipeline::OnStateTransition
, base::Unretained(this)));
860 void Pipeline::DoAudioRendererEnded() {
861 DCHECK(task_runner_
->BelongsToCurrentThread());
863 if (state_
!= kStarted
)
866 DCHECK(!audio_ended_
);
869 // Start clock since there is no more audio to trigger clock updates.
870 if (!audio_disabled_
) {
871 base::AutoLock
auto_lock(lock_
);
872 clock_
->SetMaxTime(clock_
->Duration());
873 StartClockIfWaitingForTimeUpdate_Locked();
876 RunEndedCallbackIfNeeded();
879 void Pipeline::DoVideoRendererEnded() {
880 DCHECK(task_runner_
->BelongsToCurrentThread());
882 if (state_
!= kStarted
)
885 DCHECK(!video_ended_
);
888 RunEndedCallbackIfNeeded();
891 void Pipeline::DoTextRendererEnded() {
892 DCHECK(task_runner_
->BelongsToCurrentThread());
894 if (state_
!= kStarted
)
897 DCHECK(!text_ended_
);
900 RunEndedCallbackIfNeeded();
903 void Pipeline::RunEndedCallbackIfNeeded() {
904 DCHECK(task_runner_
->BelongsToCurrentThread());
906 if (audio_renderer_
&& !audio_ended_
&& !audio_disabled_
)
909 if (video_renderer_
&& !video_ended_
)
912 if (text_renderer_
&& text_renderer_
->HasTracks() && !text_ended_
)
916 base::AutoLock
auto_lock(lock_
);
917 clock_
->EndOfStream();
920 DCHECK_EQ(status_
, PIPELINE_OK
);
924 void Pipeline::AudioDisabledTask() {
925 DCHECK(task_runner_
->BelongsToCurrentThread());
927 base::AutoLock
auto_lock(lock_
);
929 audio_disabled_
= true;
931 // Notify our demuxer that we're no longer rendering audio.
932 demuxer_
->OnAudioRendererDisabled();
934 // Start clock since there is no more audio to trigger clock updates.
935 clock_
->SetMaxTime(clock_
->Duration());
936 StartClockIfWaitingForTimeUpdate_Locked();
939 void Pipeline::AddTextStreamTask(DemuxerStream
* text_stream
,
940 const TextTrackConfig
& config
) {
941 DCHECK(task_runner_
->BelongsToCurrentThread());
942 // TODO(matthewjheaney): fix up text_ended_ when text stream
943 // is added (http://crbug.com/321446).
944 text_renderer_
->AddTextStream(text_stream
, config
);
947 void Pipeline::RemoveTextStreamTask(DemuxerStream
* text_stream
) {
948 DCHECK(task_runner_
->BelongsToCurrentThread());
949 text_renderer_
->RemoveTextStream(text_stream
);
952 void Pipeline::InitializeDemuxer(const PipelineStatusCB
& done_cb
) {
953 DCHECK(task_runner_
->BelongsToCurrentThread());
955 demuxer_
= filter_collection_
->GetDemuxer();
956 demuxer_
->Initialize(this, done_cb
, text_renderer_
);
959 void Pipeline::InitializeAudioRenderer(const PipelineStatusCB
& done_cb
) {
960 DCHECK(task_runner_
->BelongsToCurrentThread());
962 audio_renderer_
= filter_collection_
->GetAudioRenderer();
963 audio_renderer_
->Initialize(
964 demuxer_
->GetStream(DemuxerStream::AUDIO
),
966 base::Bind(&Pipeline::OnUpdateStatistics
, base::Unretained(this)),
967 base::Bind(&Pipeline::OnAudioUnderflow
, base::Unretained(this)),
968 base::Bind(&Pipeline::OnAudioTimeUpdate
, base::Unretained(this)),
969 base::Bind(&Pipeline::OnAudioRendererEnded
, base::Unretained(this)),
970 base::Bind(&Pipeline::OnAudioDisabled
, base::Unretained(this)),
971 base::Bind(&Pipeline::SetError
, base::Unretained(this)));
974 void Pipeline::InitializeVideoRenderer(const PipelineStatusCB
& done_cb
) {
975 DCHECK(task_runner_
->BelongsToCurrentThread());
977 // Get an initial natural size so we have something when we signal
978 // the kHaveMetadata buffering state.
980 // TODO(acolwell): We have to query demuxer outside of the lock to prevent a
981 // deadlock between ChunkDemuxer and Pipeline. See http://crbug.com/334325 for
982 // ideas on removing locking from ChunkDemuxer.
983 DemuxerStream
* stream
= demuxer_
->GetStream(DemuxerStream::VIDEO
);
984 gfx::Size initial_natural_size
=
985 stream
->video_decoder_config().natural_size();
987 base::AutoLock
l(lock_
);
988 initial_natural_size_
= initial_natural_size
;
991 video_renderer_
= filter_collection_
->GetVideoRenderer();
992 video_renderer_
->Initialize(
995 base::Bind(&Pipeline::OnUpdateStatistics
, base::Unretained(this)),
996 base::Bind(&Pipeline::OnVideoTimeUpdate
, base::Unretained(this)),
997 base::Bind(&Pipeline::OnVideoRendererEnded
, base::Unretained(this)),
998 base::Bind(&Pipeline::SetError
, base::Unretained(this)),
999 base::Bind(&Pipeline::GetMediaTime
, base::Unretained(this)),
1000 base::Bind(&Pipeline::GetMediaDuration
, base::Unretained(this)));
1003 void Pipeline::OnAudioUnderflow() {
1004 if (!task_runner_
->BelongsToCurrentThread()) {
1005 task_runner_
->PostTask(FROM_HERE
, base::Bind(
1006 &Pipeline::OnAudioUnderflow
, base::Unretained(this)));
1010 if (state_
!= kStarted
)
1013 if (audio_renderer_
)
1014 audio_renderer_
->ResumeAfterUnderflow();
1017 void Pipeline::StartClockIfWaitingForTimeUpdate_Locked() {
1018 lock_
.AssertAcquired();
1019 if (!waiting_for_clock_update_
)
1022 waiting_for_clock_update_
= false;
1026 } // namespace media