Chromium Code Reviews| 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 |