Chromium Code Reviews| Index: chromecast/media/cma/pipeline/av_pipeline_impl.cc |
| diff --git a/chromecast/media/cma/pipeline/av_pipeline_impl.cc b/chromecast/media/cma/pipeline/av_pipeline_impl.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..08a03b7060c059064204707e598407b9e0ac7e0f |
| --- /dev/null |
| +++ b/chromecast/media/cma/pipeline/av_pipeline_impl.cc |
| @@ -0,0 +1,392 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chromecast/media/cma/pipeline/av_pipeline_impl.h" |
| + |
| +#include "base/bind.h" |
| +#include "base/location.h" |
| +#include "base/message_loop/message_loop_proxy.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "chromecast/media/base/decrypt_context.h" |
| +#include "chromecast/media/cdm/browser_cdm_cast.h" |
| +#include "chromecast/media/cma/backend/media_clock_device.h" |
| +#include "chromecast/media/cma/backend/media_component_device.h" |
| +#include "chromecast/media/cma/base/buffering_frame_provider.h" |
| +#include "chromecast/media/cma/base/buffering_state.h" |
| +#include "chromecast/media/cma/base/cma_logging.h" |
| +#include "chromecast/media/cma/base/coded_frame_provider.h" |
| +#include "chromecast/media/cma/base/decoder_buffer_base.h" |
| +#include "chromecast/media/cma/pipeline/decrypt_util.h" |
| +#include "media/base/audio_decoder_config.h" |
| +#include "media/base/decrypt_config.h" |
| + |
| +namespace chromecast { |
| +namespace media { |
| + |
| +namespace { |
| + |
| +const int kNoCallbackId = -1; |
| + |
| +} // namespace |
| + |
| +AvPipelineImpl::AvPipelineImpl( |
| + MediaComponentDevice* media_component_device, |
| + const UpdateConfigCB& update_config_cb) |
| + : update_config_cb_(update_config_cb), |
| + media_component_device_(media_component_device), |
| + state_(kUninitialized), |
| + buffered_time_(::media::kNoTimestamp()), |
| + playable_buffered_time_(::media::kNoTimestamp()), |
| + enable_feeding_(false), |
| + pending_read_(false), |
| + pending_push_(false), |
| + enable_time_update_(false), |
| + pending_time_update_task_(false), |
| + media_keys_(NULL), |
| + media_keys_callback_id_(kNoCallbackId), |
| + weak_factory_(this) { |
| + DCHECK(media_component_device); |
| + weak_this_ = weak_factory_.GetWeakPtr(); |
| + thread_checker_.DetachFromThread(); |
| +} |
| + |
| +AvPipelineImpl::~AvPipelineImpl() { |
| + // If there are weak pointers in the wild, they must be invalidated |
| + // on the right thread. |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + media_component_device_->SetClient(MediaComponentDevice::Client()); |
| + |
| + if (media_keys_ && media_keys_callback_id_ != kNoCallbackId) { |
|
damienv1
2014/11/20 17:08:26
nit: remove curly brace
gunsch
2014/11/20 19:50:27
Done.
|
| + media_keys_->UnregisterPlayer(media_keys_callback_id_); |
| + } |
| +} |
| + |
| +void AvPipelineImpl::TransitionToState(State state) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + state_ = state; |
| +} |
| + |
| +void AvPipelineImpl::SetCodedFrameProvider( |
| + scoped_ptr<CodedFrameProvider> frame_provider, |
| + size_t max_buffer_size, |
| + size_t max_frame_size) { |
| + DCHECK_EQ(state_, kUninitialized); |
| + DCHECK(frame_provider); |
| + |
| + // Wrap the incoming frame provider to add some buffering capabilities. |
| + frame_provider_.reset( |
| + new BufferingFrameProvider( |
| + frame_provider.Pass(), |
| + max_buffer_size, |
| + max_frame_size, |
| + base::Bind(&AvPipelineImpl::OnFrameBuffered, weak_this_))); |
| +} |
| + |
| +void AvPipelineImpl::SetClient(const AvPipelineClient& client) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK_EQ(state_, kUninitialized); |
| + client_ = client; |
| +} |
| + |
| +bool AvPipelineImpl::Initialize() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK_EQ(state_, kUninitialized); |
| + |
| + MediaComponentDevice::Client client; |
| + client.eos_cb = base::Bind(&AvPipelineImpl::OnEos, weak_this_); |
| + media_component_device_->SetClient(client); |
| + if (!media_component_device_->SetState(MediaComponentDevice::kStateIdle)) |
| + return false; |
| + |
| + return true; |
| +} |
| + |
| +bool AvPipelineImpl::StartPlayingFrom( |
| + base::TimeDelta time, |
| + const scoped_refptr<BufferingState>& buffering_state) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK_EQ(state_, kFlushed); |
| + |
| + // Media time where rendering should start |
| + // and switch to a state where the audio device accepts incoming buffers. |
| + if (!media_component_device_->SetStartPts(time) || |
| + !media_component_device_->SetState(MediaComponentDevice::kStatePaused)) { |
| + return false; |
| + } |
| + |
| + // Buffering related initialization. |
| + DCHECK(frame_provider_); |
| + buffering_state_ = buffering_state; |
| + if (buffering_state_.get()) |
| + buffering_state_->SetMediaTime(time); |
| + |
| + // Start feeding the pipeline. |
| + enable_feeding_ = true; |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); |
| + |
| + if (!media_component_device_->SetState(MediaComponentDevice::kStateRunning)) |
| + return false; |
|
damienv1
2014/11/20 17:08:26
StartPlayingFrom used to be 2 different actions (P
gunsch
2014/11/20 19:50:27
Strictly speaking, this code is in order from when
|
| + |
| + return true; |
| +} |
| + |
| +bool AvPipelineImpl::Pause() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK_EQ(state_, kPlaying); |
| + |
| + DCHECK_EQ( |
| + media_component_device_->GetState(), MediaComponentDevice::kStateRunning); |
|
damienv1
2014/11/20 17:08:26
Can you add a comment for this block of code to me
gunsch
2014/11/20 19:50:27
Done.
|
| + media_component_device_->SetState(MediaComponentDevice::kStateIdle); |
| + pending_push_ = false; |
| + |
| + // Break the feeding loop. |
| + enable_feeding_ = false; |
| + |
| + return true; |
| +} |
| + |
| +void AvPipelineImpl::Flush(const base::Closure& done_cb) { |
|
damienv1
2014/11/20 17:33:39
Could you merge Pause and Flush (always used toget
gunsch
2014/11/20 19:50:27
Done.
|
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK_EQ(state_, kFlushing); |
| + |
| + // Remove any pending buffer. |
| + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); |
| + |
| + // Finally, remove any frames left in the frame provider. |
| + pending_read_ = false; |
| + buffered_time_ = ::media::kNoTimestamp(); |
| + playable_buffered_time_ = ::media::kNoTimestamp(); |
| + non_playable_frames_.clear(); |
| + frame_provider_->Flush(done_cb); |
| +} |
| + |
| +void AvPipelineImpl::Stop() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + // Stop can be called from any state. |
| + if (state_ == kUninitialized || state_ == kStopped) |
| + return; |
| + |
| + // Stop feeding the pipeline. |
| + enable_feeding_ = false; |
| + |
| + // Release hardware resources on Stop. |
| + if (media_component_device_->GetState() == |
| + MediaComponentDevice::kStatePaused || |
| + media_component_device_->GetState() == |
| + MediaComponentDevice::kStateRunning) { |
| + media_component_device_->SetState(MediaComponentDevice::kStateIdle); |
| + } |
| + if (media_component_device_->GetState() == MediaComponentDevice::kStateIdle) { |
| + media_component_device_->SetState( |
| + MediaComponentDevice::kStateUninitialized); |
| + } |
| +} |
| + |
| +void AvPipelineImpl::SetCdm(BrowserCdmCast* media_keys) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + DCHECK(media_keys); |
| + |
| + if (media_keys_ && media_keys_callback_id_ != kNoCallbackId) { |
|
damienv1
2014/11/20 17:08:26
nit: remove curly braces.
gunsch
2014/11/20 19:50:27
Done.
|
| + media_keys_->UnregisterPlayer(media_keys_callback_id_); |
| + } |
| + |
| + media_keys_ = media_keys; |
| + media_keys_callback_id_ = media_keys_->RegisterPlayer( |
| + base::Bind(&AvPipelineImpl::OnCdmStateChanged, weak_this_), |
| + base::Bind(&AvPipelineImpl::OnCdmDestroyed, weak_this_)); |
| +} |
| + |
| +void AvPipelineImpl::OnEos() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + CMALOG(kLogControl) << __FUNCTION__; |
| + if (state_ != kPlaying) |
| + return; |
| + |
| + if (!client_.eos_cb.is_null()) |
| + client_.eos_cb.Run(); |
| +} |
| + |
| +void AvPipelineImpl::FetchBufferIfNeeded() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + if (!enable_feeding_) |
| + return; |
| + |
| + if (pending_read_ || pending_buffer_.get()) |
| + return; |
| + |
| + pending_read_ = true; |
| + frame_provider_->Read( |
| + base::Bind(&AvPipelineImpl::OnNewFrame, weak_this_)); |
| +} |
| + |
| +void AvPipelineImpl::OnNewFrame( |
| + const scoped_refptr<DecoderBufferBase>& buffer, |
| + const ::media::AudioDecoderConfig& audio_config, |
| + const ::media::VideoDecoderConfig& video_config) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + pending_read_ = false; |
| + |
| + if (audio_config.IsValidConfig() || video_config.IsValidConfig()) { |
|
damienv1
2014/11/20 17:08:26
nit: remove curly braces.
gunsch
2014/11/20 19:50:27
Done.
|
| + update_config_cb_.Run(audio_config, video_config); |
| + } |
| + |
| + pending_buffer_ = buffer; |
| + ProcessPendingBuffer(); |
| + |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); |
| +} |
| + |
| +void AvPipelineImpl::ProcessPendingBuffer() { |
| + if (!enable_feeding_) |
| + return; |
| + |
| + // Initiate a read if there isn't already one. |
| + if (!pending_buffer_.get() && !pending_read_) { |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_)); |
| + return; |
| + } |
| + |
| + if (!pending_buffer_.get() || pending_push_) |
| + return; |
| + |
| + // Break the feeding loop when the end of stream is reached. |
| + if (pending_buffer_->end_of_stream()) { |
| + CMALOG(kLogControl) << __FUNCTION__ << ": EOS reached, stopped feeding"; |
| + enable_feeding_ = false; |
| + } |
| + |
| + scoped_refptr<DecryptContext> decrypt_context; |
| + if (!pending_buffer_->end_of_stream() && |
| + pending_buffer_->decrypt_config()) { |
| + // Verify that CDM has the key ID. |
| + // Should not send the frame if the key ID is not available yet. |
| + std::string key_id(pending_buffer_->decrypt_config()->key_id()); |
| + if (!media_keys_) { |
| + CMALOG(kLogControl) << "No CDM for frame: pts=" |
| + << pending_buffer_->timestamp().InMilliseconds(); |
| + return; |
| + } |
| + decrypt_context = media_keys_->GetDecryptContext(key_id); |
| + if (!decrypt_context.get()) { |
| + CMALOG(kLogControl) << "frame(pts=" |
| + << pending_buffer_->timestamp().InMilliseconds() |
| + << "): waiting for key id " |
| + << base::HexEncode(&key_id[0], key_id.size()); |
| + return; |
| + } |
| + |
| + // If we do have the clear key, decrypt the pending buffer |
| + // and reset the decryption context (not needed anymore). |
| + crypto::SymmetricKey* key = decrypt_context->GetKey(); |
| + if (key != NULL) { |
| + pending_buffer_ = DecryptDecoderBuffer(pending_buffer_, key); |
| + decrypt_context = scoped_refptr<DecryptContext>(); |
| + } |
| + } |
| + |
| + MediaComponentDevice::FrameStatus status = media_component_device_->PushFrame( |
| + decrypt_context, |
| + pending_buffer_, |
| + base::Bind(&AvPipelineImpl::OnFramePushed, weak_this_)); |
| + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); |
| + |
| + pending_push_ = (status == MediaComponentDevice::kFramePending); |
| + if (!pending_push_) |
| + OnFramePushed(status); |
| +} |
| + |
| +void AvPipelineImpl::OnFramePushed(MediaComponentDevice::FrameStatus status) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + pending_push_ = false; |
| + if (status == MediaComponentDevice::kFrameFailed) { |
| + LOG(WARNING) << "AvPipelineImpl: PushFrame failed"; |
| + enable_feeding_ = false; |
| + state_ = kError; |
| + return; |
| + } |
| + base::MessageLoopProxy::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&AvPipelineImpl::ProcessPendingBuffer, weak_this_)); |
| +} |
| + |
| +void AvPipelineImpl::OnCdmStateChanged() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + // Update the buffering state if needed. |
| + if (buffering_state_.get()) |
| + UpdatePlayableFrames(); |
| + |
| + // Process the pending buffer in case the CDM now has the frame key id. |
| + ProcessPendingBuffer(); |
| +} |
| + |
| +void AvPipelineImpl::OnCdmDestroyed() { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + media_keys_ = NULL; |
| +} |
| + |
| +void AvPipelineImpl::OnFrameBuffered( |
| + const scoped_refptr<DecoderBufferBase>& buffer, |
| + bool is_at_max_capacity) { |
| + DCHECK(thread_checker_.CalledOnValidThread()); |
| + |
| + if (!buffering_state_.get()) |
| + return; |
| + |
| + if (!buffer->end_of_stream() && |
| + (buffered_time_ == ::media::kNoTimestamp() || |
| + buffered_time_ < buffer->timestamp())) { |
| + buffered_time_ = buffer->timestamp(); |
| + } |
| + |
| + if (is_at_max_capacity) |
| + buffering_state_->NotifyMaxCapacity(buffered_time_); |
| + |
| + // No need to update the list of playable frames, |
| + // if we are already blocking on a frame. |
| + bool update_playable_frames = non_playable_frames_.empty(); |
| + non_playable_frames_.push_back(buffer); |
| + if (update_playable_frames) |
| + UpdatePlayableFrames(); |
| +} |
| + |
| +void AvPipelineImpl::UpdatePlayableFrames() { |
| + while (!non_playable_frames_.empty()) { |
| + const scoped_refptr<DecoderBufferBase>& non_playable_frame = |
| + non_playable_frames_.front(); |
| + |
| + if (non_playable_frame->end_of_stream()) { |
| + buffering_state_->NotifyEos(); |
| + } else { |
| + const ::media::DecryptConfig* decrypt_config = |
| + non_playable_frame->decrypt_config(); |
| + if (decrypt_config && |
| + !(media_keys_ && |
| + media_keys_->GetDecryptContext(decrypt_config->key_id()).get())) { |
| + // The frame is still not playable. All the following are thus not |
| + // playable. |
| + break; |
| + } |
| + |
| + if (playable_buffered_time_ == ::media::kNoTimestamp() || |
| + playable_buffered_time_ < non_playable_frame->timestamp()) { |
| + playable_buffered_time_ = non_playable_frame->timestamp(); |
| + buffering_state_->SetBufferedTime(playable_buffered_time_); |
| + } |
| + } |
| + |
| + // The frame is playable: remove it from the list of non playable frames. |
| + non_playable_frames_.pop_front(); |
| + } |
| +} |
| + |
| +} // namespace media |
| +} // namespace chromecast |