// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/media/audio/cma_audio_output.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "base/logging.h"
#include "chromecast/base/task_runner_impl.h"
#include "chromecast/media/api/cma_backend_factory.h"
#include "chromecast/media/base/cast_decoder_buffer_impl.h"
#include "chromecast/media/cma/base/decoder_config_adapter.h"
#include "chromecast/public/volume_control.h"
#include "media/audio/audio_device_description.h"
namespace chromecast {
namespace media {
namespace {
AudioContentType GetContentType(const std::string& device_id) {
if (::media::AudioDeviceDescription::IsCommunicationsDevice(device_id)) {
return AudioContentType::kCommunication;
}
return AudioContentType::kMedia;
}
int GetSampleSize(SampleFormat sample_format) {
switch (sample_format) {
case kUnknownSampleFormat:
return 0;
case kSampleFormatU8:
case kSampleFormatPlanarU8:
return sizeof(uint8_t);
case kSampleFormatS16:
case kSampleFormatPlanarS16:
return sizeof(int16_t);
case kSampleFormatS32:
case kSampleFormatPlanarS32:
return sizeof(int32_t);
case kSampleFormatF32:
case kSampleFormatPlanarF32:
return sizeof(float);
case kSampleFormatS24:
return 3;
}
}
} // namespace
CmaAudioOutput::CmaAudioOutput(
const ::media::AudioParameters& audio_params,
SampleFormat sample_format,
const std::string& device_id,
const std::string& application_session_id,
MediaPipelineDeviceParams::MediaSyncType sync_type,
bool use_hw_av_sync,
int audio_track_session_id,
CmaBackendFactory* cma_backend_factory,
CmaBackend::Decoder::Delegate* delegate)
: audio_params_(audio_params),
sample_size_(GetSampleSize(sample_format)),
use_hw_av_sync_(use_hw_av_sync),
delegate_(delegate),
timestamp_helper_(audio_params_.sample_rate()) {
DCHECK(delegate_);
Initialize(sample_format, device_id, application_session_id, sync_type,
audio_track_session_id, cma_backend_factory);
}
CmaAudioOutput::~CmaAudioOutput() = default;
void CmaAudioOutput::Initialize(
SampleFormat sample_format,
const std::string& device_id,
const std::string& application_session_id,
MediaPipelineDeviceParams::MediaSyncType sync_type,
int audio_track_session_id,
CmaBackendFactory* cma_backend_factory) {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
DCHECK(cma_backend_factory);
auto cma_backend_task_runner = std::make_unique<TaskRunnerImpl>();
MediaPipelineDeviceParams device_params(
sync_type, MediaPipelineDeviceParams::kAudioStreamNormal,
cma_backend_task_runner.get(), GetContentType(device_id), device_id);
device_params.session_id = application_session_id;
auto cma_backend = cma_backend_factory->CreateBackend(device_params);
if (!cma_backend) {
return;
}
auto* audio_decoder = cma_backend->CreateAudioDecoder();
if (!audio_decoder) {
return;
}
audio_decoder->SetDelegate(delegate_);
AudioConfig audio_config;
audio_config.codec = kCodecPCM;
audio_config.channel_layout =
DecoderConfigAdapter::ToChannelLayout(audio_params_.channel_layout());
audio_config.sample_format = sample_format;
audio_config.bytes_per_channel = sample_size_;
audio_config.channel_number = audio_params_.channels();
audio_config.samples_per_second = audio_params_.sample_rate();
audio_config.use_hw_av_sync = use_hw_av_sync_;
audio_config.audio_track_session_id = audio_track_session_id;
DCHECK(IsValidConfig(audio_config));
// Need to first set the config of the audio decoder then initialize the cma
// backend if succeed.
if (!audio_decoder->SetConfig(audio_config) || !cma_backend->Initialize()) {
return;
}
cma_backend_task_runner_ = std::move(cma_backend_task_runner);
cma_backend_ = std::move(cma_backend);
audio_decoder_ = audio_decoder;
timestamp_helper_.SetBaseTimestamp(base::TimeDelta());
}
bool CmaAudioOutput::Start(int64_t start_pts) {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
if (!cma_backend_ || !cma_backend_->Start(start_pts)) {
return false;
}
return true;
}
void CmaAudioOutput::Stop() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
if (cma_backend_) {
cma_backend_->Stop();
}
}
bool CmaAudioOutput::Pause() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
if (!cma_backend_ || !cma_backend_->Pause()) {
return false;
}
return true;
}
bool CmaAudioOutput::Resume() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
if (!cma_backend_ || !cma_backend_->Resume()) {
return false;
}
return true;
}
bool CmaAudioOutput::SetVolume(double volume) {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
if (!audio_decoder_) {
return false;
}
return audio_decoder_->SetVolume(volume);
}
void CmaAudioOutput::PushBuffer(
scoped_refptr<CastDecoderBufferImpl> decoder_buffer,
bool is_silence) {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
DCHECK_GT(decoder_buffer->data_size(), 0u);
DCHECK_EQ(
decoder_buffer->data_size() % (sample_size_ * audio_params_.channels()),
0u);
DCHECK(audio_decoder_);
if (!is_silence) {
if (!use_hw_av_sync_) {
// Keep the timestamp of the buffer if the stream is on hardware av sync
// mode.
decoder_buffer->set_timestamp(timestamp_helper_.GetTimestamp());
}
int frame_count =
decoder_buffer->data_size() / (sample_size_ * audio_params_.channels());
timestamp_helper_.AddFrames(frame_count);
}
CmaBackend::BufferStatus status =
audio_decoder_->PushBuffer(std::move(decoder_buffer));
if (status != CmaBackend::BufferStatus::kBufferPending)
delegate_->OnPushBufferComplete(status);
}
CmaBackend::AudioDecoder::RenderingDelay CmaAudioOutput::GetRenderingDelay() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
DCHECK(audio_decoder_);
return audio_decoder_->GetRenderingDelay();
}
CmaBackend::AudioDecoder::AudioTrackTimestamp
CmaAudioOutput::GetAudioTrackTimestamp() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
DCHECK(audio_decoder_);
return audio_decoder_->GetAudioTrackTimestamp();
}
int64_t CmaAudioOutput::GetTotalFrames() {
DCHECK_CALLED_ON_VALID_THREAD(media_thread_checker_);
return timestamp_helper_.frame_count();
}
} // namespace media
} // namespace chromecast