OLD | NEW |
(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 |
OLD | NEW |