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

Unified Diff: chromecast/media/audio/audio_output_stream.cc

Issue 1308153005: [Chromecast] Plumbs raw audio through CMA backend. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 4 months 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
Index: chromecast/media/audio/audio_output_stream.cc
diff --git a/chromecast/media/audio/audio_output_stream.cc b/chromecast/media/audio/audio_output_stream.cc
new file mode 100644
index 0000000000000000000000000000000000000000..73a6153c3a87aa5c80d1949e343e5675c51c10b3
--- /dev/null
+++ b/chromecast/media/audio/audio_output_stream.cc
@@ -0,0 +1,242 @@
+// Copyright 2015 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/audio/audio_output_stream.h"
+
+#include "base/bind.h"
+#include "base/thread_task_runner_handle.h"
+#include "chromecast/media/audio/audio_manager.h"
+#include "chromecast/media/cma/base/cast_decoder_buffer_impl.h"
+#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
+#include "chromecast/media/cma/pipeline/frame_status_cb_impl.h"
+#include "chromecast/public/media/audio_pipeline_device.h"
+#include "chromecast/public/media/decoder_config.h"
+#include "chromecast/public/media/decrypt_context.h"
+#include "chromecast/public/media/media_clock_device.h"
+#include "chromecast/public/media/media_pipeline_backend.h"
+#include "media/audio/fake_audio_worker.h"
+#include "media/base/decoder_buffer.h"
+
+namespace chromecast {
+namespace media {
+
+namespace {
+bool InitClockDevice(MediaClockDevice* clock_device) {
+ DCHECK(clock_device);
+ DCHECK_EQ(clock_device->GetState(), MediaClockDevice::kStateUninitialized);
+
+ if (!clock_device->SetState(media::MediaClockDevice::kStateIdle))
+ return false;
+
+ if (!clock_device->ResetTimeline(0))
+ return false;
+
+ if (!clock_device->SetRate(1.0))
+ return false;
+
+ return true;
+}
halliwell 2015/08/28 15:14:18 It seems unfortunate that we have to do this. The
alokp 2015/09/01 00:23:12 Right. Although removing the requirement of a cloc
+
+bool InitAudioDevice(const ::media::AudioParameters& audio_params,
+ AudioPipelineDevice* audio_device) {
+ DCHECK(audio_device);
+ DCHECK_EQ(audio_device->GetState(), AudioPipelineDevice::kStateUninitialized);
+
+ AudioConfig audio_config;
+ audio_config.codec = kCodecPCM;
+ audio_config.sample_format = kSampleFormatPlanarS16;
+ audio_config.bytes_per_channel = audio_params.bits_per_sample() / 8;
+ audio_config.channel_number = audio_params.channels();
+ audio_config.samples_per_second = audio_params.sample_rate();
+ audio_config.extra_data = nullptr;
+ audio_config.extra_data_size = 0;
+ audio_config.is_encrypted = false;
+ if (!audio_device->SetConfig(audio_config))
+ return false;
+
+ if (!audio_device->SetState(AudioPipelineDevice::kStateIdle))
+ return false;
+
+ return true;
+}
+} // namespace
+
+AudioOutputStream::AudioOutputStream(
+ const ::media::AudioParameters& audio_params,
+ AudioManager* audio_manager)
+ : audio_params_(audio_params),
+ audio_manager_(audio_manager),
+ volume_(1.0),
+ audio_device_busy_(false),
+ weak_factory_(this) {
+ LOG(INFO) << "New AudioOutputStream with: "
+ << audio_params_.AsHumanReadableString();
+}
+
+AudioOutputStream::~AudioOutputStream() {}
+
+bool AudioOutputStream::Open() {
+ LOG(INFO) << __FUNCTION__;
halliwell 2015/08/28 15:14:18 do we really need all these info logs?
alokp 2015/09/01 00:23:12 We did have this in one backend implementation and
halliwell 2015/09/01 01:10:32 Frequency seems fine, but it doesn't seem like muc
alokp 2015/09/01 17:34:44 Good point about adding 'this' to the logs. I have
+
+ ::media::AudioParameters::Format format = audio_params_.format();
+ if ((format != ::media::AudioParameters::AUDIO_PCM_LINEAR) &&
+ (format != ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY)) {
+ LOG(WARNING) << "Unsupported audio format: " << format;
+ return false;
+ }
+
+ ::media::ChannelLayout channel_layout = audio_params_.channel_layout();
+ if ((channel_layout != ::media::CHANNEL_LAYOUT_MONO) &&
+ (channel_layout != ::media::CHANNEL_LAYOUT_STEREO)) {
+ LOG(WARNING) << "Unsupported channel layout: " << channel_layout;
+ return false;
+ }
+ DCHECK_GE(audio_params_.channels(), 1);
+ DCHECK_LE(audio_params_.channels(), 2);
+
+ media_pipeline_backend_ = audio_manager_->CreateMediaPipelineBackend();
+ if (!media_pipeline_backend_) {
+ LOG(WARNING) << "Failed to create media pipeline backend.";
+ return false;
+ }
+
+ if (!InitClockDevice(media_pipeline_backend_->GetClock())) {
+ LOG(WARNING) << "Failed to initialize clock device.";
+ return false;
+ }
+
+ if (!InitAudioDevice(audio_params_, media_pipeline_backend_->GetAudio())) {
+ LOG(WARNING) << "Failed to initialize audio device.";
+ return false;
+ }
+
+ audio_bus_ = ::media::AudioBus::Create(audio_params_);
+ audio_worker_.reset(new ::media::FakeAudioWorker(
+ base::ThreadTaskRunnerHandle::Get(), audio_params_));
+ return true;
+}
+
+void AudioOutputStream::Close() {
+ LOG(INFO) << __FUNCTION__;
+
+ if (media_pipeline_backend_) {
+ media_pipeline_backend_->GetClock()->SetState(MediaClockDevice::kStateIdle);
+ media_pipeline_backend_->GetAudio()->SetState(
+ AudioPipelineDevice::kStateIdle);
+ }
+
+ audio_worker_.reset();
slan 2015/08/31 16:59:56 This class relies on Start(), Stop() , and dtor al
alokp 2015/09/01 00:23:11 AudioManager already has this CHECK. In addition F
+ audio_bus_.reset();
+ media_pipeline_backend_.reset();
+
+ // Signal to the manager that we're closed and can be removed.
+ // This should be the last call in the function as it deletes "this".
+ audio_manager_->ReleaseOutputStream(this);
+}
+
+void AudioOutputStream::Start(AudioSourceCallback* source_callback) {
+ LOG(INFO) << __FUNCTION__;
+
+ MediaClockDevice* clock_device = media_pipeline_backend_->GetClock();
+ DCHECK(clock_device);
+ if (!clock_device->SetState(MediaClockDevice::kStateRunning)) {
+ LOG(WARNING) << "Failed to run clock device.";
+ return;
+ }
+ clock_device->SetRate(1.0f);
+
+ AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio();
+ DCHECK(audio_device);
+ if (!audio_device->SetState(AudioPipelineDevice::kStateRunning)) {
+ LOG(WARNING) << "Failed to run audio device.";
+ return;
+ }
+ audio_worker_->Start(base::Bind(&AudioOutputStream::PushFrame,
+ weak_factory_.GetWeakPtr(), source_callback));
+}
+
+void AudioOutputStream::Stop() {
+ LOG(INFO) << __FUNCTION__;
+
+ MediaClockDevice* clock_device = media_pipeline_backend_->GetClock();
+ DCHECK(clock_device);
+ clock_device->SetState(MediaClockDevice::kStateIdle);
+ clock_device->SetRate(0.0f);
+
+ AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio();
+ DCHECK(audio_device);
+ audio_device->SetState(AudioPipelineDevice::kStatePaused);
+ audio_worker_->Stop();
+ audio_device_busy_ = false;
+}
+
+void AudioOutputStream::SetVolume(double volume) {
+ AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio();
+ DCHECK(audio_device);
+ audio_device->SetStreamVolumeMultiplier(volume);
+ volume_ = volume;
+}
+
+void AudioOutputStream::GetVolume(double* volume) {
+ *volume = volume_;
+}
+
+void AudioOutputStream::PushFrame(AudioSourceCallback* source_callback) {
+ DCHECK(source_callback);
+ if (audio_device_busy_) {
slan 2015/08/31 16:59:56 In what situation does this occur? audio_worker_ w
alokp 2015/09/01 00:23:12 This happens when the backend buffer is full - opp
+ // Skip pulling data if audio device is still busy.
+ LOG(WARNING) << "Skipping frame because audio pipeline device is busy.";
+ return;
+ }
+
+ int frame_count = source_callback->OnMoreData(audio_bus_.get(), 0);
+ DCHECK_EQ(frame_count, audio_bus_->frames());
+ int buffer_size = frame_count * audio_params_.GetBytesPerFrame();
+ scoped_refptr<::media::DecoderBuffer> decoder_buffer(
+ new ::media::DecoderBuffer(buffer_size));
+ audio_bus_->ToInterleaved(frame_count, audio_params_.bits_per_sample() / 8,
+ decoder_buffer->writable_data());
+
+ AudioPipelineDevice* audio_device = media_pipeline_backend_->GetAudio();
+ DCHECK(audio_device);
+ MediaComponentDevice::FrameStatus status = audio_device->PushFrame(
+ nullptr, // decrypt_context
+ new CastDecoderBufferImpl(new DecoderBufferAdapter(decoder_buffer)),
+ new media::FrameStatusCBImpl(
+ base::Bind(&AudioOutputStream::OnPushFrameStatus,
slan 2015/08/31 16:59:56 Is there somewhere in the documentation that makes
alokp 2015/09/01 00:23:11 This guarantee should be provided by AudioPipeline
halliwell 2015/09/01 01:10:32 AudioPipelineDevice is a platform backend, we didn
+ weak_factory_.GetWeakPtr(), source_callback)));
+
+ if (status == MediaComponentDevice::kFrameFailed) {
+ // Note: We cannot call OnPushFrameError directly because it will lead to
+ // a recursive lock, which is not supported by base::Lock.
+ // This callback is called with a lock held inside FakeAudioWorker.
+ // Calling FakeAudioWorker::Stop will try to acquire the lock again.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&AudioOutputStream::OnPushFrameError,
+ weak_factory_.GetWeakPtr(), source_callback));
+ } else if (status == MediaComponentDevice::kFramePending) {
+ audio_device_busy_ = true;
+ }
+}
+
+void AudioOutputStream::OnPushFrameStatus(
+ AudioSourceCallback* source_callback,
+ MediaComponentDevice::FrameStatus status) {
+ DCHECK(audio_device_busy_);
slan 2015/08/31 16:59:56 Is it possible that Stop() is called, while this c
alokp 2015/09/01 00:23:11 You are right. Luke: Do we ignore the callback if
halliwell 2015/09/01 01:10:32 Comment on PushFrame says "Pushing a pending frame
+ audio_device_busy_ = false;
+
+ DCHECK_NE(status, MediaComponentDevice::kFramePending);
+ if (status == MediaComponentDevice::kFrameFailed)
+ OnPushFrameError(source_callback);
+}
+
+void AudioOutputStream::OnPushFrameError(AudioSourceCallback* source_callback) {
+ LOG(WARNING) << "Failed to push frame to audio pipeline device.";
+ // Inform audio source about the error and stop pulling data.
+ audio_worker_->Stop();
+ source_callback->OnError(this);
+}
+
+} // namespace media
+} // namespace chromecast

Powered by Google App Engine
This is Rietveld 408576698