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

Unified Diff: chromecast/media/cma/pipeline/av_pipeline_impl.cc

Issue 741863002: Chromecast: adds a media pipeline feeding data to CMA device backends. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cma-cdm
Patch Set: address nits, merge Pause/Flush Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chromecast/media/cma/pipeline/av_pipeline_impl.h ('k') | chromecast/media/cma/pipeline/decrypt_util.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « chromecast/media/cma/pipeline/av_pipeline_impl.h ('k') | chromecast/media/cma/pipeline/decrypt_util.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698