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

Side by Side Diff: chromecast/media/cma/filters/cma_renderer.cc

Issue 750683003: Chromecast: adds media::Renderer implementation for CMA pipeline. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address comments Created 6 years 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
« no previous file with comments | « chromecast/media/cma/filters/cma_renderer.h ('k') | chromecast/media/media.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chromecast/media/cma/filters/cma_renderer.h"
6
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/location.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
12 #include "chromecast/media/cma/base/cma_logging.h"
13 #include "chromecast/media/cma/filters/demuxer_stream_adapter.h"
14 #include "chromecast/media/cma/pipeline/audio_pipeline.h"
15 #include "chromecast/media/cma/pipeline/av_pipeline_client.h"
16 #include "chromecast/media/cma/pipeline/media_pipeline.h"
17 #include "chromecast/media/cma/pipeline/media_pipeline_client.h"
18 #include "chromecast/media/cma/pipeline/video_pipeline.h"
19 #include "chromecast/media/cma/pipeline/video_pipeline_client.h"
20 #include "media/base/bind_to_current_loop.h"
21 #include "media/base/demuxer_stream_provider.h"
22 #include "media/base/pipeline_status.h"
23 #include "media/base/time_delta_interpolator.h"
24 #include "media/base/video_frame.h"
25 #include "ui/gfx/geometry/size.h"
26
27 namespace chromecast {
28 namespace media {
29
30 namespace {
31
32 // Maximum difference between audio frame PTS and video frame PTS
33 // for frames read from the DemuxerStream.
34 const base::TimeDelta kMaxDeltaFetcher(
35 base::TimeDelta::FromMilliseconds(2000));
36
37 } // namespace
38
39 CmaRenderer::CmaRenderer(scoped_ptr<MediaPipeline> media_pipeline)
40 : media_task_runner_factory_(
41 new BalancedMediaTaskRunnerFactory(kMaxDeltaFetcher)),
42 media_pipeline_(media_pipeline.Pass()),
43 audio_pipeline_(media_pipeline_->GetAudioPipeline()),
44 video_pipeline_(media_pipeline_->GetVideoPipeline()),
45 state_(kUninitialized),
46 is_pending_transition_(false),
47 has_audio_(false),
48 has_video_(false),
49 received_audio_eos_(false),
50 received_video_eos_(false),
51 initial_natural_size_(gfx::Size()),
52 initial_video_hole_created_(false),
53 time_interpolator_(
54 new ::media::TimeDeltaInterpolator(&default_tick_clock_)),
55 playback_rate_(1.0f),
56 weak_factory_(this) {
57 weak_this_ = weak_factory_.GetWeakPtr();
58 thread_checker_.DetachFromThread();
59
60 time_interpolator_->SetUpperBound(base::TimeDelta());
61 }
62
63 CmaRenderer::~CmaRenderer() {
64 DCHECK(thread_checker_.CalledOnValidThread());
65 FireAllPendingCallbacks();
66 }
67
68 void CmaRenderer::Initialize(
69 ::media::DemuxerStreamProvider* demuxer_stream_provider,
70 const base::Closure& init_cb,
71 const ::media::StatisticsCB& statistics_cb,
72 const ::media::BufferingStateCB& buffering_state_cb,
73 const PaintCB& paint_cb,
74 const base::Closure& ended_cb,
75 const ::media::PipelineStatusCB& error_cb) {
76 CMALOG(kLogControl) << __FUNCTION__;
77 DCHECK(thread_checker_.CalledOnValidThread());
78 DCHECK_EQ(state_, kUninitialized) << state_;
79 DCHECK(!init_cb.is_null());
80 DCHECK(!statistics_cb.is_null());
81 DCHECK(!ended_cb.is_null());
82 DCHECK(!error_cb.is_null());
83 DCHECK(!buffering_state_cb.is_null());
84 DCHECK(demuxer_stream_provider_->GetStream(::media::DemuxerStream::AUDIO) ||
85 demuxer_stream_provider_->GetStream(::media::DemuxerStream::VIDEO));
86
87 BeginStateTransition();
88
89 demuxer_stream_provider_ = demuxer_stream_provider;
90 statistics_cb_ = statistics_cb;
91 buffering_state_cb_ = buffering_state_cb;
92 paint_cb_ = paint_cb;
93 ended_cb_ = ended_cb;
94 error_cb_ = error_cb;
95
96 MediaPipelineClient media_pipeline_client;
97 media_pipeline_client.error_cb = error_cb_;
98 media_pipeline_client.buffering_state_cb = ::media::BindToCurrentLoop(
99 base::Bind(&CmaRenderer::OnBufferingNotification, weak_this_));
100 media_pipeline_client.time_update_cb = ::media::BindToCurrentLoop(
101 base::Bind(&CmaRenderer::OnPlaybackTimeUpdated, weak_this_));
102 media_pipeline_->SetClient(media_pipeline_client);
103
104 init_cb_ = init_cb;
105 InitializeAudioPipeline();
106 }
107
108 void CmaRenderer::Flush(const base::Closure& flush_cb) {
109 CMALOG(kLogControl) << __FUNCTION__;
110 DCHECK(thread_checker_.CalledOnValidThread());
111 BeginStateTransition();
112
113 DCHECK(flush_cb_.is_null());
114 flush_cb_ = flush_cb;
115
116 if (state_ == kError) {
117 OnError(::media::PIPELINE_ERROR_ABORT);
118 return;
119 }
120
121 DCHECK_EQ(state_, kPlaying) << state_;
122 media_pipeline_->Flush(
123 ::media::BindToCurrentLoop(
124 base::Bind(&CmaRenderer::OnFlushDone, weak_this_)));
125
126 {
127 base::AutoLock auto_lock(time_interpolator_lock_);
128 time_interpolator_->StopInterpolating();
129 }
130 }
131
132 void CmaRenderer::StartPlayingFrom(base::TimeDelta time) {
133 CMALOG(kLogControl) << __FUNCTION__ << ": " << time.InMilliseconds();
134 DCHECK(thread_checker_.CalledOnValidThread());
135 BeginStateTransition();
136
137 if (state_ == kError) {
138 error_cb_.Run(::media::PIPELINE_ERROR_ABORT);
139 CompleteStateTransition(kError);
140 return;
141 }
142
143 #if defined(VIDEO_HOLE)
144 // Create a video hole frame just before starting playback.
145 // Note that instead of creating the video hole frame in Initialize(), we do
146 // it here because paint_cb_ (which eventually calls OnOpacityChanged)
147 // expects the current state to not be HaveNothing. And the place where
148 // the ready state is changed to HaveMetadata (OnPipelineMetadata) is
149 // right before the pipeline calls StartPlayingFrom (in
150 // Pipeline::StateTransitionTask).
151 if (!initial_video_hole_created_) {
152 initial_video_hole_created_ = true;
153 paint_cb_.Run(::media::VideoFrame::CreateHoleFrame(initial_natural_size_));
154 }
155 #endif
156
157 {
158 base::AutoLock auto_lock(time_interpolator_lock_);
159 time_interpolator_.reset(
160 new ::media::TimeDeltaInterpolator(&default_tick_clock_));
161 time_interpolator_->SetPlaybackRate(playback_rate_);
162 time_interpolator_->SetBounds(time, time);
163 time_interpolator_->StartInterpolating();
164 }
165
166 received_audio_eos_ = false;
167 received_video_eos_ = false;
168
169 DCHECK_EQ(state_, kFlushed) << state_;
170 // Immediately update transition to playing.
171 // Error case will be handled on response from host.
172 media_pipeline_->StartPlayingFrom(time);
173 CompleteStateTransition(kPlaying);
174 }
175
176 void CmaRenderer::SetPlaybackRate(float playback_rate) {
177 CMALOG(kLogControl) << __FUNCTION__ << ": " << playback_rate;
178 DCHECK(thread_checker_.CalledOnValidThread());
179 media_pipeline_->SetPlaybackRate(playback_rate);
180 playback_rate_ = playback_rate;
181
182 {
183 base::AutoLock auto_lock(time_interpolator_lock_);
184 time_interpolator_->SetPlaybackRate(playback_rate);
185 }
186 }
187
188 void CmaRenderer::SetVolume(float volume) {
189 CMALOG(kLogControl) << __FUNCTION__ << ": " << volume;
190 DCHECK(thread_checker_.CalledOnValidThread());
191 audio_pipeline_->SetVolume(volume);
192 }
193
194 base::TimeDelta CmaRenderer::GetMediaTime() {
195 base::AutoLock auto_lock(time_interpolator_lock_);
196 return time_interpolator_->GetInterpolatedTime();
197 }
198
199 bool CmaRenderer::HasAudio() {
200 DCHECK(thread_checker_.CalledOnValidThread());
201 return has_audio_;
202 }
203
204 bool CmaRenderer::HasVideo() {
205 DCHECK(thread_checker_.CalledOnValidThread());
206 return has_video_;
207 }
208
209 void CmaRenderer::SetCdm(::media::CdmContext* cdm_context,
210 const ::media::CdmAttachedCB& cdm_attached_cb) {
211 DCHECK(thread_checker_.CalledOnValidThread());
212 #if defined(ENABLE_BROWSER_CDMS)
213 media_pipeline_->SetCdm(cdm_context->GetCdmId());
214 #endif
215 cdm_attached_cb.Run(true);
216 }
217
218 void CmaRenderer::InitializeAudioPipeline() {
219 DCHECK(thread_checker_.CalledOnValidThread());
220 DCHECK_EQ(state_, kUninitialized) << state_;
221 DCHECK(!init_cb_.is_null());
222
223 ::media::PipelineStatusCB audio_initialization_done_cb =
224 ::media::BindToCurrentLoop(
225 base::Bind(&CmaRenderer::OnAudioPipelineInitializeDone, weak_this_));
226
227 ::media::DemuxerStream* stream =
228 demuxer_stream_provider_->GetStream(::media::DemuxerStream::AUDIO);
229 if (!stream) {
230 audio_initialization_done_cb.Run(::media::PIPELINE_OK);
231 return;
232 }
233
234 // Receive events from the audio pipeline.
235 AvPipelineClient av_pipeline_client;
236 av_pipeline_client.eos_cb = ::media::BindToCurrentLoop(
237 base::Bind(&CmaRenderer::OnEosReached, weak_this_, true));
238 av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop(
239 base::Bind(&CmaRenderer::OnError, weak_this_));
240 av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop(
241 base::Bind(&CmaRenderer::OnStatisticsUpdated, weak_this_));
242 audio_pipeline_->SetClient(av_pipeline_client);
243
244 scoped_ptr<CodedFrameProvider> frame_provider(
245 new DemuxerStreamAdapter(
246 base::MessageLoopProxy::current(),
247 media_task_runner_factory_,
248 stream));
249
250 const ::media::AudioDecoderConfig& config = stream->audio_decoder_config();
251 if (config.codec() == ::media::kCodecAAC)
252 stream->EnableBitstreamConverter();
253
254 has_audio_ = true;
255 media_pipeline_->InitializeAudio(
256 config, frame_provider.Pass(), audio_initialization_done_cb);
257 }
258
259 void CmaRenderer::OnAudioPipelineInitializeDone(
260 ::media::PipelineStatus status) {
261 DCHECK(thread_checker_.CalledOnValidThread());
262 DCHECK_EQ(state_, kUninitialized) << state_;
263 DCHECK(!init_cb_.is_null());
264
265 if (status != ::media::PIPELINE_OK) {
266 has_audio_ = false;
267 OnError(status);
268 return;
269 }
270
271 InitializeVideoPipeline();
272 }
273
274 void CmaRenderer::InitializeVideoPipeline() {
275 DCHECK(thread_checker_.CalledOnValidThread());
276 DCHECK_EQ(state_, kUninitialized) << state_;
277 DCHECK(!init_cb_.is_null());
278
279 ::media::PipelineStatusCB video_initialization_done_cb =
280 ::media::BindToCurrentLoop(
281 base::Bind(&CmaRenderer::OnVideoPipelineInitializeDone, weak_this_));
282
283 ::media::DemuxerStream* stream =
284 demuxer_stream_provider_->GetStream(::media::DemuxerStream::VIDEO);
285 if (!stream) {
286 video_initialization_done_cb.Run(::media::PIPELINE_OK);
287 return;
288 }
289
290 // Receive events from the video pipeline.
291 VideoPipelineClient client;
292 client.av_pipeline_client.eos_cb = ::media::BindToCurrentLoop(
293 base::Bind(&CmaRenderer::OnEosReached, weak_this_, false));
294 client.av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop(
295 base::Bind(&CmaRenderer::OnError, weak_this_));
296 client.av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop(
297 base::Bind(&CmaRenderer::OnStatisticsUpdated, weak_this_));
298 client.natural_size_changed_cb = ::media::BindToCurrentLoop(
299 base::Bind(&CmaRenderer::OnNaturalSizeChanged, weak_this_));
300 video_pipeline_->SetClient(client);
301
302 scoped_ptr<CodedFrameProvider> frame_provider(
303 new DemuxerStreamAdapter(
304 base::MessageLoopProxy::current(),
305 media_task_runner_factory_,
306 stream));
307
308 const ::media::VideoDecoderConfig& config = stream->video_decoder_config();
309 if (config.codec() == ::media::kCodecH264)
310 stream->EnableBitstreamConverter();
311
312 initial_natural_size_ = config.natural_size();
313
314 has_video_ = true;
315 media_pipeline_->InitializeVideo(
316 config,
317 frame_provider.Pass(),
318 video_initialization_done_cb);
319 }
320
321 void CmaRenderer::OnVideoPipelineInitializeDone(
322 ::media::PipelineStatus status) {
323 DCHECK(thread_checker_.CalledOnValidThread());
324 DCHECK_EQ(state_, kUninitialized) << state_;
325 DCHECK(!init_cb_.is_null());
326
327 if (status != ::media::PIPELINE_OK) {
328 has_video_ = false;
329 OnError(status);
330 return;
331 }
332
333 CompleteStateTransition(kFlushed);
334 base::ResetAndReturn(&init_cb_).Run();
335 }
336
337 void CmaRenderer::OnEosReached(bool is_audio) {
338 DCHECK(thread_checker_.CalledOnValidThread());
339 CMALOG(kLogControl) << __FUNCTION__;
340 if (state_ != kPlaying) {
341 LOG(WARNING) << "Ignoring a late EOS event";
342 return;
343 }
344
345 if (is_audio) {
346 DCHECK(!received_audio_eos_);
347 received_audio_eos_ = true;
348 } else {
349 DCHECK(!received_video_eos_);
350 received_video_eos_ = true;
351 }
352
353 bool audio_finished = !has_audio_ || received_audio_eos_;
354 bool video_finished = !has_video_ || received_video_eos_;
355 if (audio_finished && video_finished)
356 ended_cb_.Run();
357 }
358
359 void CmaRenderer::OnStatisticsUpdated(
360 const ::media::PipelineStatistics& stats) {
361 DCHECK(thread_checker_.CalledOnValidThread());
362 statistics_cb_.Run(stats);
363 }
364
365 void CmaRenderer::OnNaturalSizeChanged(const gfx::Size& size) {
366 DCHECK(thread_checker_.CalledOnValidThread());
367 #if defined(VIDEO_HOLE)
368 paint_cb_.Run(::media::VideoFrame::CreateHoleFrame(size));
369 #endif
370 }
371
372 void CmaRenderer::OnPlaybackTimeUpdated(
373 base::TimeDelta time,
374 base::TimeDelta max_time,
375 base::TimeTicks capture_time) {
376 DCHECK(thread_checker_.CalledOnValidThread());
377 if (state_ != kPlaying) {
378 LOG(WARNING) << "Ignoring a late time update";
379 return;
380 }
381
382 // TODO(halliwell): arguably, TimeDeltaInterpolator::SetBounds should perform
383 // this calculation to avoid calling TimeTicks::Now twice (it's slower and has
384 // potential accuracy problems).
385 base::TimeDelta lower_bound =
386 std::min(max_time, time + base::TimeTicks::Now() - capture_time);
387
388 base::AutoLock auto_lock(time_interpolator_lock_);
389 time_interpolator_->SetBounds(lower_bound, max_time);
390 }
391
392 void CmaRenderer::OnBufferingNotification(
393 ::media::BufferingState buffering_state) {
394 CMALOG(kLogControl) << __FUNCTION__ << ": state=" << state_
395 << ", buffering=" << buffering_state;
396 // TODO(gunsch): WebMediaPlayerImpl currently only handles HAVE_ENOUGH while
397 // playing. See OnPipelineBufferingStateChanged, http://crbug.com/144683.
398 if (state_ != kPlaying) {
399 LOG(WARNING) << "Ignoring buffering notification in state: " << state_;
400 return;
401 }
402 if (buffering_state != ::media::BUFFERING_HAVE_ENOUGH) {
403 LOG(WARNING) << "Ignoring buffering notification during playing: "
404 << buffering_state;
405 return;
406 }
407 buffering_state_cb_.Run(buffering_state);
408 }
409
410 void CmaRenderer::OnFlushDone(::media::PipelineStatus status) {
411 DCHECK(thread_checker_.CalledOnValidThread());
412 if (status != ::media::PIPELINE_OK) {
413 OnError(status);
414 return;
415 }
416
417 CompleteStateTransition(kFlushed);
418 base::ResetAndReturn(&flush_cb_).Run();
419 }
420
421 void CmaRenderer::OnError(::media::PipelineStatus error) {
422 DCHECK(thread_checker_.CalledOnValidThread());
423 DCHECK_NE(::media::PIPELINE_OK, error) << "PIPELINE_OK isn't an error!";
424 LOG(ERROR) << "CMA error encountered: " << error;
425
426 // Pipeline will destroy |this| as the result of error.
427 if (state_ != kError)
428 error_cb_.Run(error);
429
430 CompleteStateTransition(kError);
431 FireAllPendingCallbacks();
432 }
433
434 void CmaRenderer::FireAllPendingCallbacks() {
435 DCHECK(thread_checker_.CalledOnValidThread());
436 if (!init_cb_.is_null())
437 base::ResetAndReturn(&init_cb_).Run();
438 if (!flush_cb_.is_null())
439 base::ResetAndReturn(&flush_cb_).Run();
440 }
441
442 void CmaRenderer::BeginStateTransition() {
443 DCHECK(!is_pending_transition_) << state_;
444 is_pending_transition_ = true;
445 }
446
447 void CmaRenderer::CompleteStateTransition(State new_state) {
448 state_ = new_state;
449 is_pending_transition_ = false;
450 }
451
452 } // namespace media
453 } // namespace chromecast
OLDNEW
« no previous file with comments | « chromecast/media/cma/filters/cma_renderer.h ('k') | chromecast/media/media.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698