| 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..dd80a0a564ce9497d41b24820b2aaa24110f27f8
|
| --- /dev/null
|
| +++ b/chromecast/media/cma/pipeline/av_pipeline_impl.cc
|
| @@ -0,0 +1,382 @@
|
| +// 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)
|
| + 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);
|
| +
|
| + if (!media_component_device_->SetState(MediaComponentDevice::kStateRunning))
|
| + return false;
|
| +
|
| + // Start feeding the pipeline.
|
| + enable_feeding_ = true;
|
| + base::MessageLoopProxy::current()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&AvPipelineImpl::FetchBufferIfNeeded, weak_this_));
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void AvPipelineImpl::Flush(const base::Closure& done_cb) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| + DCHECK_EQ(state_, kFlushing);
|
| + DCHECK_EQ(
|
| + media_component_device_->GetState(), MediaComponentDevice::kStateRunning);
|
| + // Note: returning to idle state aborts any pending frame push.
|
| + media_component_device_->SetState(MediaComponentDevice::kStateIdle);
|
| + pending_push_ = false;
|
| +
|
| + // Break the feeding loop.
|
| + enable_feeding_ = false;
|
| +
|
| + // 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)
|
| + 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())
|
| + 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
|
|
|