// Copyright 2017 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/cma/backend/android/audio_decoder_android.h"
#include <time.h>
#include <algorithm>
#include <limits>
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "chromecast/base/task_runner_impl.h"
#include "chromecast/media/api/decoder_buffer_base.h"
#include "chromecast/media/cma/backend/android/media_pipeline_backend_android.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/media/cma/base/decoder_config_adapter.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_buffer.h"
#include "media/base/sample_format.h"
#include "media/filters/audio_renderer_algorithm.h"
#define TRACE_FUNCTION_ENTRY0() TRACE_EVENT0("cma", __FUNCTION__)
#define TRACE_FUNCTION_ENTRY1(arg1) \
TRACE_EVENT1("cma", __FUNCTION__, #arg1, arg1)
#define TRACE_FUNCTION_ENTRY2(arg1, arg2) \
TRACE_EVENT2("cma", __FUNCTION__, #arg1, arg1, #arg2, arg2)
namespace chromecast {
namespace media {
namespace {
const int kDefaultFramesPerBuffer = 1024;
const int kSilenceBufferFrames = 2048;
const int kMaxOutputMs = 20;
const int kMillisecondsPerSecond = 1000;
const double kPlaybackRateEpsilon = 0.001;
const CastAudioDecoder::OutputFormat kDecoderSampleFormat =
CastAudioDecoder::kOutputPlanarFloat;
const int64_t kInvalidTimestamp = std::numeric_limits<int64_t>::min();
const int64_t kNoPendingOutput = -1;
bool IsValidChannelNumber(int channel_number) {
// Currently, we only support following channel numbers.
return (channel_number == 1) || (channel_number == 2) ||
(channel_number == 4) || (channel_number == 6) ||
(channel_number == 8);
}
} // namespace
AudioDecoderAndroid::RateShifterInfo::RateShifterInfo(float playback_rate)
: rate(playback_rate), input_frames(0), output_frames(0) {}
// static
int64_t MediaPipelineBackend::AudioDecoder::GetMinimumBufferedTime(
const AudioConfig& config) {
return AudioSinkAndroid::GetMinimumBufferedTime(config);
}
AudioDecoderAndroid::AudioDecoderAndroid(MediaPipelineBackendAndroid* backend,
bool is_apk_audio)
: backend_(backend),
is_apk_audio_(is_apk_audio),
task_runner_(backend->GetTaskRunner()),
delegate_(nullptr),
pending_buffer_complete_(false),
got_eos_(false),
pushed_eos_(false),
sink_error_(false),
current_pts_(kInvalidTimestamp),
pending_output_frames_(kNoPendingOutput),
volume_multiplier_(1.0f),
pool_(new ::media::AudioBufferMemoryPool()),
weak_factory_(this) {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
DCHECK(backend_);
DCHECK(task_runner_.get());
DCHECK(task_runner_->BelongsToCurrentThread());
}
AudioDecoderAndroid::~AudioDecoderAndroid() {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
}
void AudioDecoderAndroid::SetDelegate(
MediaPipelineBackend::Decoder::Delegate* delegate) {
LOG(INFO) << __func__ << ":";
DCHECK(delegate);
delegate_ = delegate;
}
void AudioDecoderAndroid::Initialize() {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
DCHECK(delegate_);
stats_ = Statistics();
pending_buffer_complete_ = false;
got_eos_ = false;
pushed_eos_ = false;
current_pts_ = kInvalidTimestamp;
pending_output_frames_ = kNoPendingOutput;
}
bool AudioDecoderAndroid::Start(int64_t start_pts) {
LOG(INFO) << __func__ << ": start_pts=" << start_pts;
TRACE_FUNCTION_ENTRY0();
current_pts_ = start_pts;
DCHECK(IsValidConfig(config_));
DCHECK(IsValidChannelNumber(config_.channel_number));
if (!sink_.Create(this, config_.channel_number, config_.samples_per_second,
config_.audio_track_session_id, backend_->Primary(),
is_apk_audio_, config_.use_hw_av_sync, backend_->DeviceId(),
backend_->ContentType())) {
return false;
}
sink_->SetStreamVolumeMultiplier(volume_multiplier_);
// Create decoder_ if necessary. This can happen if Stop() was called, and
// SetConfig() was not called since then.
if (!decoder_) {
CreateDecoder();
}
if (!rate_shifter_) {
CreateRateShifter(config_);
}
sink_->SetPaused(false);
return true;
}
void AudioDecoderAndroid::Stop() {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
decoder_.reset();
sink_.Reset();
rate_shifter_.reset();
weak_factory_.InvalidateWeakPtrs();
Initialize();
}
bool AudioDecoderAndroid::Pause() {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
DCHECK(sink_);
sink_->SetPaused(true);
return true;
}
bool AudioDecoderAndroid::Resume() {
LOG(INFO) << __func__ << ":";
TRACE_FUNCTION_ENTRY0();
DCHECK(sink_);
sink_->SetPaused(false);
return true;
}
bool AudioDecoderAndroid::SetPlaybackRate(float rate) {
LOG(INFO) << __func__ << ": rate=" << rate;
if (std::abs(rate - 1.0) < kPlaybackRateEpsilon) {
// AudioRendererAlgorithm treats values close to 1 as exactly 1.
rate = 1.0f;
}
LOG(INFO) << "SetPlaybackRate to " << rate;
// Remove info for rates that have no pending output left.
while (!rate_shifter_info_.empty()) {
RateShifterInfo* rate_info = &rate_shifter_info_.back();
int64_t possible_output_frames = rate_info->input_frames / rate_info->rate;
DCHECK_GE(possible_output_frames, rate_info->output_frames);
if (rate_info->output_frames == possible_output_frames) {
rate_shifter_info_.pop_back();
} else {
break;
}
}
rate_shifter_info_.push_back(RateShifterInfo(rate));
return true;
}
AudioDecoderAndroid::BufferStatus AudioDecoderAndroid::PushBuffer(
CastDecoderBuffer* buffer) {
if (buffer->end_of_stream()) {
DVLOG(3) << __func__ << ": EOS";
} else {
DVLOG(3) << __func__ << ":"
<< " size=" << buffer->data_size()
<< " pts=" << buffer->timestamp();
}
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(buffer);
DCHECK(!got_eos_);
DCHECK(!sink_error_);
DCHECK(!pending_buffer_complete_);
uint64_t input_bytes = buffer->end_of_stream() ? 0 : buffer->data_size();
scoped_refptr<DecoderBufferBase> buffer_base(
static_cast<DecoderBufferBase*>(buffer));
if (!buffer->end_of_stream()) {
current_pts_ = buffer->timestamp();
}
// If the buffer is already decoded, do not attempt to decode. Call
// OnBufferDecoded asynchronously on the main thread.
if (BypassDecoder()) {
DCHECK(!decoder_);
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AudioDecoderAndroid::OnBufferDecoded,
weak_factory_.GetWeakPtr(), input_bytes,
CastAudioDecoder::Status::kDecodeOk, config_,
std::move(buffer_base)));
return MediaPipelineBackendAndroid::kBufferPending;
}
DCHECK(decoder_);
// Decode the buffer.
decoder_->Decode(std::move(buffer_base),
base::BindOnce(&AudioDecoderAndroid::OnBufferDecoded,
base::Unretained(this), input_bytes));
return MediaPipelineBackendAndroid::kBufferPending;
}
void AudioDecoderAndroid::UpdateStatistics(Statistics delta) {
DCHECK(task_runner_->BelongsToCurrentThread());
stats_.decoded_bytes += delta.decoded_bytes;
}
void AudioDecoderAndroid::GetStatistics(Statistics* stats) {
TRACE_FUNCTION_ENTRY0();
DCHECK(stats);
DCHECK(task_runner_->BelongsToCurrentThread());
*stats = stats_;
LOG(INFO) << __func__ << ": decoded_bytes=" << stats->decoded_bytes;
}
bool AudioDecoderAndroid::SetConfig(const AudioConfig& config) {
LOG(INFO) << __func__ << ":"
<< " id=" << config.id << " codec=" << config.codec
<< " sample_format=" << config.sample_format
<< " bytes_per_channel=" << config.bytes_per_channel
<< " channel_number=" << config.channel_number
<< " samples_per_second=" << config.samples_per_second
<< " is_encrypted=" << config.is_encrypted();
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
if (!IsValidConfig(config) || !IsValidChannelNumber(config.channel_number)) {
LOG(ERROR) << "Invalid audio config passed to SetConfig";
return false;
}
bool changed_config =
(config.samples_per_second != config_.samples_per_second ||
config.channel_number != config_.channel_number);
if (!rate_shifter_ || changed_config) {
CreateRateShifter(config);
}
if (sink_ && changed_config) {
if (!ResetSinkForNewConfig(config)) {
return false;
}
}
config_ = config;
decoder_.reset();
CreateDecoder();
if (pending_buffer_complete_ && changed_config) {
pending_buffer_complete_ = false;
delegate_->OnPushBufferComplete(
MediaPipelineBackendAndroid::kBufferSuccess);
}
return true;
}
bool AudioDecoderAndroid::ResetSinkForNewConfig(const AudioConfig& config) {
if (!sink_.Create(this, config.channel_number, config.samples_per_second,
config.audio_track_session_id, backend_->Primary(),
is_apk_audio_, config.use_hw_av_sync, backend_->DeviceId(),
backend_->ContentType())) {
return false;
}
sink_->SetStreamVolumeMultiplier(volume_multiplier_);
pending_output_frames_ = kNoPendingOutput;
return true;
}
void AudioDecoderAndroid::CreateDecoder() {
LOG(INFO) << __func__ << ":";
DCHECK(!decoder_);
DCHECK(IsValidConfig(config_));
DCHECK(IsValidChannelNumber(config_.channel_number));
// No need to create a decoder if the samples are already decoded.
if (BypassDecoder()) {
LOG(INFO) << "Data is not coded. Decoder will not be used.";
return;
}
// Create a decoder.
decoder_ =
CastAudioDecoder::Create(task_runner_, config_, kDecoderSampleFormat);
if (!decoder_) {
LOG(INFO) << __func__ << ": Decoder initialization was unsuccessful";
delegate_->OnDecoderError();
}
}
void AudioDecoderAndroid::CreateRateShifter(const AudioConfig& config) {
LOG(INFO) << __func__ << ": channel_number=" << config.channel_number
<< " samples_per_second=" << config.samples_per_second;
rate_shifter_info_.clear();
rate_shifter_info_.push_back(RateShifterInfo(1.0f));
rate_shifter_output_.reset();
rate_shifter_.reset(new ::media::AudioRendererAlgorithm(&media_log_));
bool is_encrypted = false;
::media::ChannelLayout channel_layout =
DecoderConfigAdapter::ToMediaChannelLayout(config.channel_layout);
rate_shifter_->Initialize(
::media::AudioParameters(::media::AudioParameters::AUDIO_PCM_LINEAR,
{channel_layout, config.channel_number},
config.samples_per_second,
kDefaultFramesPerBuffer),
is_encrypted);
}
bool AudioDecoderAndroid::SetVolume(float multiplier) {
LOG(INFO) << __func__ << ": multiplier=" << multiplier;
TRACE_FUNCTION_ENTRY1(multiplier);
DCHECK(task_runner_->BelongsToCurrentThread());
volume_multiplier_ = multiplier;
if (sink_)
sink_->SetStreamVolumeMultiplier(volume_multiplier_);
return true;
}
AudioDecoderAndroid::RenderingDelay AudioDecoderAndroid::GetRenderingDelay() {
TRACE_FUNCTION_ENTRY0();
if (!sink_) {
return AudioDecoderAndroid::RenderingDelay();
}
AudioDecoderAndroid::RenderingDelay delay = sink_->GetRenderingDelay();
if (delay.timestamp_microseconds != kInvalidTimestamp) {
double usec_per_sample = 1000000.0 / config_.samples_per_second;
// Account for data that has been queued in the rate shifters.
for (const RateShifterInfo& info : rate_shifter_info_) {
double queued_output_frames =
(info.input_frames / info.rate) - info.output_frames;
delay.delay_microseconds += queued_output_frames * usec_per_sample;
}
// Account for data that is in the process of being pushed to the sink.
if (pending_output_frames_ != kNoPendingOutput) {
delay.delay_microseconds += pending_output_frames_ * usec_per_sample;
}
}
DVLOG(2) << __func__ << ":"
<< " delay=" << delay.delay_microseconds
<< " ts=" << delay.timestamp_microseconds;
return delay;
}
AudioDecoderAndroid::AudioTrackTimestamp
AudioDecoderAndroid::GetAudioTrackTimestamp() {
TRACE_FUNCTION_ENTRY0();
return (sink_ ? sink_->GetAudioTrackTimestamp()
: AudioDecoderAndroid::AudioTrackTimestamp());
}
int AudioDecoderAndroid::GetStartThresholdInFrames() {
TRACE_FUNCTION_ENTRY0();
return (sink_ ? sink_->GetStartThresholdInFrames() : 0);
}
void AudioDecoderAndroid::OnBufferDecoded(
uint64_t input_bytes,
CastAudioDecoder::Status status,
const AudioConfig& config,
scoped_refptr<DecoderBufferBase> decoded) {
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!got_eos_);
DCHECK(!pending_buffer_complete_);
DCHECK(rate_shifter_);
if (decoded->end_of_stream()) {
DVLOG(3) << __func__ << ": EOS";
} else {
DVLOG(3) << __func__ << ":"
<< " input_bytes=" << input_bytes
<< " decoded.size=" << decoded->data_size();
}
if (status == CastAudioDecoder::Status::kDecodeError) {
LOG(ERROR) << "Decode error";
delegate_->OnPushBufferComplete(MediaPipelineBackendAndroid::kBufferFailed);
return;
}
if (sink_error_) {
delegate_->OnPushBufferComplete(MediaPipelineBackendAndroid::kBufferFailed);
return;
}
if (!IsValidChannelNumber(config.channel_number)) {
LOG(ERROR) << "Channel number changes to be invalid.";
delegate_->OnPushBufferComplete(MediaPipelineBackendAndroid::kBufferFailed);
return;
}
Statistics delta;
delta.decoded_bytes = input_bytes;
UpdateStatistics(delta);
bool changed_config = false;
if (config.samples_per_second != config_.samples_per_second) {
LOG(INFO) << "Input sample rate changed from " << config_.samples_per_second
<< " to " << config.samples_per_second;
config_.samples_per_second = config.samples_per_second;
changed_config = true;
}
if (config.channel_number != config_.channel_number) {
LOG(INFO) << "Input channel count changed from " << config_.channel_number
<< " to " << config.channel_number;
config_.channel_number = config.channel_number;
changed_config = true;
}
if (changed_config) {
// Config from actual stream doesn't match supposed config from the
// container. Update the sink and rate shifter. Note that for now we
// assume that this can only happen at start of stream (ie, on the first
// decoded buffer).
CreateRateShifter(config_);
if (!ResetSinkForNewConfig(config_)) {
OnSinkError(SinkError::kInternalError);
return;
}
}
pending_buffer_complete_ = true;
if (decoded->end_of_stream()) {
got_eos_ = true;
LOG(INFO) << __func__ << ": decoded buffer marked EOS";
} else {
int64_t input_frames =
decoded->data_size() / (config_.channel_number * sizeof(float));
DCHECK(!rate_shifter_info_.empty());
// If not AudioChannel::kAll, wipe all other channels for stereo sound.
if (backend_->AudioChannel() != AudioChannel::kAll) {
// There is an assumption hardcoded for playout_channel to be left
// or right. Adding a check here in case this changes.
DCHECK(backend_->AudioChannel() == AudioChannel::kLeft ||
backend_->AudioChannel() == AudioChannel::kRight);
const int playout_channel =
backend_->AudioChannel() == AudioChannel::kLeft ? 0 : 1;
for (int c = 0; c < config_.channel_number; ++c) {
if (c != playout_channel) {
const size_t channel_size =
decoded->data_size() / config_.channel_number;
std::memcpy(decoded->writable_data() + c * channel_size,
decoded->writable_data() + playout_channel * channel_size,
channel_size);
}
}
}
RateShifterInfo* rate_info = &rate_shifter_info_.front();
// Bypass rate shifter if the rate is 1.0, and there are no frames queued
// in the rate shifter.
if (rate_info->rate == 1.0 && rate_shifter_->BufferedFrames() == 0 &&
pending_output_frames_ == kNoPendingOutput &&
rate_shifter_info_.size() == 1) {
DCHECK_EQ(rate_info->output_frames, rate_info->input_frames);
pending_output_frames_ = input_frames;
if (got_eos_) {
DCHECK(!pushed_eos_);
pushed_eos_ = true;
}
sink_->WritePcm(std::move(decoded));
return;
}
// Otherwise, queue data into the rate shifter, and then try to push the
// rate-shifted data.
scoped_refptr<::media::AudioBuffer> buffer =
::media::AudioBuffer::CreateBuffer(
::media::kSampleFormatPlanarF32,
DecoderConfigAdapter::ToMediaChannelLayout(config_.channel_layout),
config_.channel_number, config_.samples_per_second, input_frames,
pool_);
buffer->set_timestamp(base::TimeDelta());
const int channel_data_size = input_frames * sizeof(float);
for (int c = 0; c < config_.channel_number; ++c) {
memcpy(buffer->channel_data()[c], decoded->data() + c * channel_data_size,
channel_data_size);
}
rate_shifter_->EnqueueBuffer(buffer);
rate_shifter_info_.back().input_frames += input_frames;
}
PushRateShifted();
DCHECK(!rate_shifter_info_.empty());
CheckBufferComplete();
}
void AudioDecoderAndroid::CheckBufferComplete() {
DVLOG(3) << __func__
<< ": pending_buffer_complete_=" << pending_buffer_complete_;
if (!pending_buffer_complete_) {
return;
}
bool rate_shifter_queue_full = rate_shifter_->IsQueueFull();
DCHECK(!rate_shifter_info_.empty());
if (rate_shifter_info_.front().rate == 1.0) {
// If the current rate is 1.0, drain any data in the rate shifter before
// calling PushBufferComplete, so that the next PushBuffer call can skip the
// rate shifter entirely.
rate_shifter_queue_full = (rate_shifter_->BufferedFrames() > 0 ||
pending_output_frames_ != kNoPendingOutput);
}
if (pushed_eos_ || !rate_shifter_queue_full) {
pending_buffer_complete_ = false;
delegate_->OnPushBufferComplete(
MediaPipelineBackendAndroid::kBufferSuccess);
}
}
void AudioDecoderAndroid::PushRateShifted() {
DVLOG(3) << __func__ << ":"
<< " pushed_eos_=" << pushed_eos_
<< " pending_output_frames_=" << pending_output_frames_
<< " got_eos_=" << got_eos_;
DCHECK(sink_);
if (pushed_eos_ || pending_output_frames_ != kNoPendingOutput) {
return;
}
if (got_eos_) {
// Push some silence into the rate shifter so we can get out any remaining
// rate-shifted data.
rate_shifter_->EnqueueBuffer(::media::AudioBuffer::CreateEmptyBuffer(
DecoderConfigAdapter::ToMediaChannelLayout(config_.channel_layout),
config_.channel_number, config_.samples_per_second,
kSilenceBufferFrames, base::TimeDelta()));
}
DCHECK(!rate_shifter_info_.empty());
RateShifterInfo* rate_info = &rate_shifter_info_.front();
int64_t possible_output_frames = rate_info->input_frames / rate_info->rate;
DCHECK_GE(possible_output_frames, rate_info->output_frames);
int desired_output_frames = possible_output_frames - rate_info->output_frames;
if (desired_output_frames == 0) {
if (got_eos_) {
DCHECK(!pushed_eos_);
pending_output_frames_ = 0;
pushed_eos_ = true;
scoped_refptr<DecoderBufferBase> eos_buffer(
new DecoderBufferAdapter(::media::DecoderBuffer::CreateEOSBuffer()));
DVLOG(3) << __func__ << ": WritePcm(eos_buffer)";
sink_->WritePcm(eos_buffer);
}
return;
}
// Don't push too many frames at a time.
desired_output_frames = std::min(
desired_output_frames,
config_.samples_per_second * kMaxOutputMs / kMillisecondsPerSecond);
if (!rate_shifter_output_ ||
desired_output_frames > rate_shifter_output_->frames()) {
rate_shifter_output_ = ::media::AudioBus::Create(config_.channel_number,
desired_output_frames);
}
int out_frames = rate_shifter_->FillBuffer(
rate_shifter_output_.get(), 0, desired_output_frames, rate_info->rate);
if (out_frames <= 0) {
return;
}
rate_info->output_frames += out_frames;
DCHECK_GE(possible_output_frames, rate_info->output_frames);
int channel_data_size = out_frames * sizeof(float);
scoped_refptr<DecoderBufferBase> output_buffer(new DecoderBufferAdapter(
new ::media::DecoderBuffer(channel_data_size * config_.channel_number)));
for (int c = 0; c < config_.channel_number; ++c) {
memcpy(output_buffer->writable_data() + c * channel_data_size,
rate_shifter_output_->channel(c), channel_data_size);
}
pending_output_frames_ = out_frames;
sink_->WritePcm(output_buffer);
if (rate_shifter_info_.size() > 1 &&
rate_info->output_frames == possible_output_frames) {
double remaining_input_frames =
rate_info->input_frames - (rate_info->output_frames * rate_info->rate);
rate_shifter_info_.pop_front();
rate_info = &rate_shifter_info_.front();
LOG(INFO) << "New playback rate in effect: " << rate_info->rate;
rate_info->input_frames += remaining_input_frames;
DCHECK_EQ(0, rate_info->output_frames);
// If new playback rate is 1.0, clear out 'extra' data in the rate shifter.
// When doing rate shifting, the rate shifter queue holds data after it has
// been logically played; once we switch to passthrough mode (rate == 1.0),
// that old data needs to be cleared out.
if (rate_info->rate == 1.0) {
int extra_frames = rate_shifter_->BufferedFrames() -
static_cast<int>(rate_info->input_frames);
if (extra_frames > 0) {
// Clear out extra buffered data.
std::unique_ptr<::media::AudioBus> dropped =
::media::AudioBus::Create(config_.channel_number, extra_frames);
int cleared_frames =
rate_shifter_->FillBuffer(dropped.get(), 0, extra_frames, 1.0f);
DCHECK_EQ(extra_frames, cleared_frames);
}
rate_info->input_frames = rate_shifter_->BufferedFrames();
}
}
}
bool AudioDecoderAndroid::BypassDecoder() const {
DCHECK(task_runner_->BelongsToCurrentThread());
// The sink input requires planar float PCM data.
return (config_.codec == kCodecPCM &&
config_.sample_format == kSampleFormatPlanarF32);
}
void AudioDecoderAndroid::OnWritePcmCompletion(BufferStatus status) {
DVLOG(3) << __func__ << ": status=" << status;
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(MediaPipelineBackendAndroid::kBufferSuccess, status);
pending_output_frames_ = kNoPendingOutput;
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&AudioDecoderAndroid::PushMorePcm,
weak_factory_.GetWeakPtr()));
}
void AudioDecoderAndroid::PushMorePcm() {
DVLOG(3) << __func__ << ":";
PushRateShifted();
DCHECK(!rate_shifter_info_.empty());
CheckBufferComplete();
if (pushed_eos_) {
LOG(INFO) << __func__ << ": OnEndOfStream()";
delegate_->OnEndOfStream();
}
}
void AudioDecoderAndroid::OnSinkError(SinkError error) {
TRACE_FUNCTION_ENTRY0();
DCHECK(task_runner_->BelongsToCurrentThread());
if (error != SinkError::kInputIgnored)
LOG(ERROR) << "Sink error occurred.";
sink_error_ = true;
delegate_->OnDecoderError();
}
} // namespace media
} // namespace chromecast