blob: d5afde3e4912c637563e0bda71893dc843e01339 [file] [log] [blame]
// 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/cast_audio_output_stream.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/base/task_runner_impl.h"
#include "chromecast/media/audio/cast_audio_manager.h"
#include "chromecast/media/cma/backend/cma_backend.h"
#include "chromecast/media/cma/backend/cma_backend_factory.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/public/media/decoder_config.h"
#include "chromecast/public/media/media_pipeline_device_params.h"
#include "chromecast/public/volume_control.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/decoder_buffer.h"
namespace {
const int kMaxQueuedDataMs = 1000;
void SignalWaitableEvent(bool* success,
base::WaitableEvent* waitable_event,
bool result) {
*success = result;
waitable_event->Signal();
}
} // namespace
namespace chromecast {
namespace media {
// Backend represents a CmaBackend adapter.
// It can be created and destroyed on any thread,
// but all other member functions must be called on a single thread.
class CastAudioOutputStream::Backend : public CmaBackend::Decoder::Delegate {
public:
using OpenCompletionCallback = base::OnceCallback<void(bool)>;
explicit Backend(const ::media::AudioParameters& audio_params)
: audio_params_(audio_params),
timestamp_helper_(audio_params_.sample_rate()),
buffer_duration_(audio_params.GetBufferDuration()),
first_start_(true),
push_in_progress_(false),
encountered_error_(false),
decoder_(nullptr),
source_callback_(nullptr),
weak_factory_(this) {
DETACH_FROM_THREAD(thread_checker_);
}
~Backend() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (backend_ && !first_start_) // Only stop the backend if it was started.
backend_->Stop();
}
void Open(CastAudioManager* audio_manager,
OpenCompletionCallback completion_cb) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(audio_manager);
DCHECK(backend_ == nullptr);
CmaBackendFactory* backend_factory = audio_manager->backend_factory();
DCHECK(backend_factory);
backend_task_runner_.reset(new TaskRunnerImpl());
MediaPipelineDeviceParams device_params(
MediaPipelineDeviceParams::kModeIgnorePts,
MediaPipelineDeviceParams::kAudioStreamSoundEffects,
backend_task_runner_.get(), AudioContentType::kMedia,
::media::AudioDeviceDescription::kDefaultDeviceId);
backend_ = backend_factory->CreateBackend(device_params);
if (!backend_) {
std::move(completion_cb).Run(false);
return;
}
decoder_ = backend_->CreateAudioDecoder();
if (!decoder_) {
std::move(completion_cb).Run(false);
return;
}
decoder_->SetDelegate(this);
AudioConfig audio_config;
audio_config.codec = kCodecPCM;
audio_config.sample_format = kSampleFormatS16;
audio_config.bytes_per_channel = 2;
audio_config.channel_number = audio_params_.channels();
audio_config.samples_per_second = audio_params_.sample_rate();
if (!decoder_->SetConfig(audio_config)) {
std::move(completion_cb).Run(false);
return;
}
if (!backend_->Initialize()) {
std::move(completion_cb).Run(false);
return;
}
audio_bus_ = ::media::AudioBus::Create(audio_params_);
decoder_buffer_ = new DecoderBufferAdapter(new ::media::DecoderBuffer(
audio_params_.GetBytesPerBuffer(::media::kSampleFormatS16)));
timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
std::move(completion_cb).Run(true);
}
void Start(AudioSourceCallback* source_callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(backend_);
if (first_start_) {
first_start_ = false;
backend_->Start(0);
} else {
backend_->Resume();
}
source_callback_ = source_callback;
next_push_time_ = base::TimeTicks::Now();
if (!push_in_progress_) {
push_in_progress_ = true;
PushBuffer();
}
}
void Stop(base::OnceClosure completion_cb) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(backend_);
backend_->Pause();
source_callback_ = nullptr;
std::move(completion_cb).Run();
}
void SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(decoder_);
decoder_->SetVolume(volume);
}
private:
void PushBuffer() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(push_in_progress_);
if (!source_callback_ || encountered_error_) {
push_in_progress_ = false;
return;
}
CmaBackend::AudioDecoder::RenderingDelay rendering_delay =
decoder_->GetRenderingDelay();
base::TimeDelta delay =
base::TimeDelta::FromMicroseconds(rendering_delay.delay_microseconds);
base::TimeTicks delay_timestamp =
base::TimeTicks() + base::TimeDelta::FromMicroseconds(
rendering_delay.timestamp_microseconds);
int frame_count = source_callback_->OnMoreData(delay, delay_timestamp, 0,
audio_bus_.get());
VLOG(3) << "frames_filled=" << frame_count << " with latency=" << delay;
DCHECK_EQ(frame_count, audio_bus_->frames());
DCHECK_EQ(static_cast<int>(decoder_buffer_->data_size()),
audio_params_.GetBytesPerBuffer(::media::kSampleFormatS16));
audio_bus_->ToInterleaved<::media::SignedInt16SampleTypeTraits>(
frame_count,
reinterpret_cast<int16_t*>(decoder_buffer_->writable_data()));
decoder_buffer_->set_timestamp(timestamp_helper_.GetTimestamp());
timestamp_helper_.AddFrames(frame_count);
BufferStatus status = decoder_->PushBuffer(decoder_buffer_.get());
if (status != CmaBackend::BufferStatus::kBufferPending)
OnPushBufferComplete(status);
}
// CmaBackend::Decoder::Delegate implementation
void OnPushBufferComplete(BufferStatus status) override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_NE(status, CmaBackend::BufferStatus::kBufferPending);
DCHECK(push_in_progress_);
push_in_progress_ = false;
if (!source_callback_ || encountered_error_)
return;
if (status != CmaBackend::BufferStatus::kBufferSuccess) {
source_callback_->OnError();
return;
}
// Schedule next push buffer. We don't want to allow more than
// kMaxQueuedDataMs of queued audio.
const base::TimeTicks now = base::TimeTicks::Now();
next_push_time_ = std::max(now, next_push_time_ + buffer_duration_);
base::TimeDelta delay = (next_push_time_ - now) -
base::TimeDelta::FromMilliseconds(kMaxQueuedDataMs);
delay = std::max(delay, base::TimeDelta());
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Backend::PushBuffer, weak_factory_.GetWeakPtr()),
delay);
push_in_progress_ = true;
}
void OnEndOfStream() override {}
void OnDecoderError() override {
VLOG(1) << this << ": " << __func__;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
encountered_error_ = true;
if (source_callback_)
source_callback_->OnError();
}
void OnKeyStatusChanged(const std::string& key_id,
CastKeyStatus key_status,
uint32_t system_code) override {}
void OnVideoResolutionChanged(const Size& size) override {}
const ::media::AudioParameters audio_params_;
std::unique_ptr<::media::AudioBus> audio_bus_;
scoped_refptr<media::DecoderBufferBase> decoder_buffer_;
::media::AudioTimestampHelper timestamp_helper_;
const base::TimeDelta buffer_duration_;
bool first_start_;
bool push_in_progress_;
bool encountered_error_;
base::TimeTicks next_push_time_;
std::unique_ptr<TaskRunnerImpl> backend_task_runner_;
std::unique_ptr<CmaBackend> backend_;
CmaBackend::AudioDecoder* decoder_;
AudioSourceCallback* source_callback_;
THREAD_CHECKER(thread_checker_);
base::WeakPtrFactory<Backend> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Backend);
};
// CastAudioOutputStream runs on audio thread (AudioManager::GetTaskRunner).
CastAudioOutputStream::CastAudioOutputStream(
const ::media::AudioParameters& audio_params,
CastAudioManager* audio_manager)
: audio_params_(audio_params), audio_manager_(audio_manager), volume_(1.0) {
VLOG(1) << "CastAudioOutputStream " << this << " created with "
<< audio_params_.AsHumanReadableString();
}
CastAudioOutputStream::~CastAudioOutputStream() {
DCHECK(!backend_);
}
bool CastAudioOutputStream::Open() {
VLOG(1) << this << ": " << __func__;
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
::media::AudioParameters::Format format = audio_params_.format();
DCHECK((format == ::media::AudioParameters::AUDIO_PCM_LINEAR) ||
(format == ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY));
::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);
bool success = false;
DCHECK(!backend_);
backend_ = std::make_unique<Backend>(audio_params_);
{
base::WaitableEvent completion_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
audio_manager_->backend_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&Backend::Open, base::Unretained(backend_.get()), audio_manager_,
base::BindOnce(&SignalWaitableEvent, &success, &completion_event)));
completion_event.Wait();
}
if (!success)
LOG(WARNING) << "Failed to open audio output stream.";
return success;
}
void CastAudioOutputStream::Close() {
VLOG(1) << this << ": " << __func__;
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(backend_);
audio_manager_->backend_task_runner()->DeleteSoon(FROM_HERE,
backend_.release());
// 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 CastAudioOutputStream::Start(AudioSourceCallback* source_callback) {
VLOG(2) << this << ": " << __func__;
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(source_callback);
DCHECK(backend_);
audio_manager_->backend_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Backend::Start, base::Unretained(backend_.get()),
source_callback));
metrics::CastMetricsHelper::GetInstance()->LogTimeToFirstAudio();
}
void CastAudioOutputStream::Stop() {
VLOG(2) << this << ": " << __func__;
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
DCHECK(backend_);
base::WaitableEvent completion_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
audio_manager_->backend_task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Backend::Stop, base::Unretained(backend_.get()),
base::BindOnce(&base::WaitableEvent::Signal,
base::Unretained(&completion_event))));
completion_event.Wait();
}
void CastAudioOutputStream::SetVolume(double volume) {
VLOG(2) << this << ": " << __func__ << "(" << volume << ")";
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
volume_ = volume;
if (backend_) {
audio_manager_->backend_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Backend::SetVolume,
base::Unretained(backend_.get()), volume_));
}
}
void CastAudioOutputStream::GetVolume(double* volume) {
DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
*volume = volume_;
}
} // namespace media
} // namespace chromecast