blob: 700ea5c3f95e5691258ac02512fcbcf55f9bd7c3 [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/cma/decoder/cast_audio_decoder.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/queue.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/media/cma/base/decoder_buffer_base.h"
#include "chromecast/media/cma/base/decoder_config_adapter.h"
#include "chromecast/media/cma/base/decoder_config_logging.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/cdm_context.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_util.h"
#include "media/base/sample_format.h"
#include "media/filters/ffmpeg_audio_decoder.h"
namespace chromecast {
namespace media {
namespace {
class CastAudioDecoderImpl : public CastAudioDecoder {
public:
CastAudioDecoderImpl(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
InitializedCallback initialized_callback,
OutputFormat output_format)
: task_runner_(std::move(task_runner)),
initialized_callback_(std::move(initialized_callback)),
output_format_(output_format),
initialized_(false),
decode_pending_(false),
weak_factory_(this) {
weak_this_ = weak_factory_.GetWeakPtr();
DCHECK(task_runner_);
DCHECK(initialized_callback_);
}
void Initialize(const media::AudioConfig& config) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(!initialized_);
config_ = config;
if (config_.is_encrypted()) {
LOG(ERROR) << "Cannot decode encrypted audio";
// TODO(kmackay) Should call OnInitialized(false) here, but that generally
// causes the browsertests to crash since it happens during the render
// pipeline initialization.
config_.encryption_scheme = EncryptionScheme::kUnencrypted;
}
decoder_ = std::make_unique<::media::FFmpegAudioDecoder>(task_runner_,
&media_log_);
decoder_->Initialize(
media::DecoderConfigAdapter::ToMediaAudioDecoderConfig(config_),
nullptr,
base::BindRepeating(&CastAudioDecoderImpl::OnInitialized, weak_this_),
base::BindRepeating(&CastAudioDecoderImpl::OnDecoderOutput, weak_this_),
base::NullCallback());
// Unfortunately there is no result from decoder_->Initialize() until later
// (the pipeline status callback is posted to the task runner).
}
// CastAudioDecoder implementation:
void Decode(scoped_refptr<media::DecoderBufferBase> data,
DecodeCallback decode_callback) override {
DCHECK(decode_callback);
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (data->decrypt_context() != nullptr) {
LOG(ERROR) << "Audio decoder doesn't support encrypted stream";
// Post the task to ensure that |decode_callback| is not called from
// within a call to Decode().
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CastAudioDecoderImpl::CallDecodeCallback,
weak_this_, std::move(decode_callback),
kDecodeError, std::move(data)));
} else if (!initialized_ || decode_pending_) {
decode_queue_.push(
std::make_pair(std::move(data), std::move(decode_callback)));
} else {
DecodeNow(std::move(data), std::move(decode_callback));
}
}
private:
typedef std::pair<scoped_refptr<media::DecoderBufferBase>, DecodeCallback>
DecodeBufferCallbackPair;
void CallDecodeCallback(DecodeCallback decode_callback,
Status status,
scoped_refptr<media::DecoderBufferBase> data) {
std::move(decode_callback).Run(status, config_, std::move(data));
}
void DecodeNow(scoped_refptr<media::DecoderBufferBase> data,
DecodeCallback decode_callback) {
if (data->end_of_stream()) {
// Post the task to ensure that |decode_callback| is not called from
// within a call to Decode().
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CastAudioDecoderImpl::CallDecodeCallback,
weak_this_, std::move(decode_callback),
kDecodeOk, std::move(data)));
return;
}
// FFmpegAudioDecoder requires a timestamp to be set.
base::TimeDelta timestamp =
base::TimeDelta::FromMicroseconds(data->timestamp());
if (timestamp == ::media::kNoTimestamp)
data->set_timestamp(base::TimeDelta());
decode_pending_ = true;
pending_decode_callback_ = std::move(decode_callback);
decoder_->Decode(data->ToMediaBuffer(),
base::BindRepeating(&CastAudioDecoderImpl::OnDecodeStatus,
weak_this_, timestamp));
}
void OnInitialized(bool success) {
DCHECK(!initialized_);
DCHECK(initialized_callback_);
if (success) {
initialized_ = true;
if (!decode_queue_.empty()) {
auto& d = decode_queue_.front();
DecodeNow(std::move(d.first), std::move(d.second));
decode_queue_.pop();
}
} else {
LOG(ERROR) << "Failed to initialize FFmpegAudioDecoder";
LOG(INFO) << "Config:";
LOG(INFO) << "\tEncrypted: "
<< (config_.is_encrypted() ? "true" : "false");
LOG(INFO) << "\tCodec: " << config_.codec;
LOG(INFO) << "\tSample format: " << config_.sample_format;
LOG(INFO) << "\tChannels: " << config_.channel_number;
LOG(INFO) << "\tSample rate: " << config_.samples_per_second;
}
std::move(initialized_callback_).Run(initialized_);
}
void OnDecodeStatus(base::TimeDelta buffer_timestamp,
::media::DecodeStatus status) {
DCHECK(pending_decode_callback_);
Status result_status = kDecodeOk;
scoped_refptr<media::DecoderBufferBase> decoded;
if (status == ::media::DecodeStatus::OK && !decoded_chunks_.empty()) {
decoded = ConvertDecoded();
} else {
if (status != ::media::DecodeStatus::OK)
result_status = kDecodeError;
decoded = base::MakeRefCounted<media::DecoderBufferAdapter>(
config_.id, base::MakeRefCounted<::media::DecoderBuffer>(0));
}
decoded_chunks_.clear();
decoded->set_timestamp(buffer_timestamp);
base::WeakPtr<CastAudioDecoderImpl> self = weak_factory_.GetWeakPtr();
std::move(pending_decode_callback_)
.Run(result_status, config_, std::move(decoded));
if (!self)
return; // Return immediately if the decode callback deleted this.
// Do not reset decode_pending_ to false until after the callback has
// finished running because the callback may call Decode().
decode_pending_ = false;
if (decode_queue_.empty())
return;
auto& d = decode_queue_.front();
// Calling DecodeNow() here does not result in a loop, because
// OnDecodeStatus() is always called asynchronously (guaranteed by the
// AudioDecoder interface).
DecodeNow(std::move(d.first), std::move(d.second));
decode_queue_.pop();
}
void OnDecoderOutput(scoped_refptr<::media::AudioBuffer> decoded) {
if (decoded->sample_rate() != config_.samples_per_second) {
LOG(WARNING) << "sample_rate changed to " << decoded->sample_rate()
<< " from " << config_.samples_per_second;
config_.samples_per_second = decoded->sample_rate();
}
if (decoded->channel_count() != config_.channel_number) {
config_.channel_number = decoded->channel_count();
decoded_bus_.reset();
}
decoded_chunks_.push_back(std::move(decoded));
}
scoped_refptr<media::DecoderBufferBase> ConvertDecoded() {
DCHECK(!decoded_chunks_.empty());
int num_frames = 0;
for (auto& chunk : decoded_chunks_)
num_frames += chunk->frame_count();
// Copy decoded data into an AudioBus for conversion.
if (!decoded_bus_ || decoded_bus_->frames() < num_frames) {
decoded_bus_ =
::media::AudioBus::Create(config_.channel_number, num_frames * 2);
}
int bus_frame_offset = 0;
for (auto& chunk : decoded_chunks_) {
chunk->ReadFrames(chunk->frame_count(), 0, bus_frame_offset,
decoded_bus_.get());
bus_frame_offset += chunk->frame_count();
}
return FinishConversion(decoded_bus_.get(), bus_frame_offset);
}
scoped_refptr<media::DecoderBufferBase> FinishConversion(
::media::AudioBus* bus,
int num_frames) {
int size =
num_frames * bus->channels() * OutputFormatSizeInBytes(output_format_);
auto result = base::MakeRefCounted<::media::DecoderBuffer>(size);
if (output_format_ == kOutputSigned16) {
bus->ToInterleaved(num_frames, OutputFormatSizeInBytes(output_format_),
result->writable_data());
} else if (output_format_ == kOutputPlanarFloat) {
// Data in an AudioBus is already in planar float format; just copy each
// channel into the result buffer in order.
float* ptr = reinterpret_cast<float*>(result->writable_data());
for (int c = 0; c < bus->channels(); ++c) {
std::copy_n(bus->channel(c), num_frames, ptr);
ptr += num_frames;
}
} else {
NOTREACHED();
}
result->set_duration(base::TimeDelta::FromMicroseconds(
num_frames * base::Time::kMicrosecondsPerSecond /
config_.samples_per_second));
return base::MakeRefCounted<media::DecoderBufferAdapter>(config_.id,
result);
}
::media::NullMediaLog media_log_;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
InitializedCallback initialized_callback_;
OutputFormat output_format_;
bool initialized_;
media::AudioConfig config_;
std::unique_ptr<::media::AudioDecoder> decoder_;
base::queue<DecodeBufferCallbackPair> decode_queue_;
bool decode_pending_;
DecodeCallback pending_decode_callback_;
std::vector<scoped_refptr<::media::AudioBuffer>> decoded_chunks_;
std::unique_ptr<::media::AudioBus> decoded_bus_;
base::WeakPtr<CastAudioDecoderImpl> weak_this_;
base::WeakPtrFactory<CastAudioDecoderImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CastAudioDecoderImpl);
};
} // namespace
// static
std::unique_ptr<CastAudioDecoder> CastAudioDecoder::Create(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
const media::AudioConfig& config,
OutputFormat output_format,
InitializedCallback initialized_callback) {
std::unique_ptr<CastAudioDecoderImpl> decoder(new CastAudioDecoderImpl(
std::move(task_runner), std::move(initialized_callback), output_format));
decoder->Initialize(config);
return std::move(decoder);
}
// static
int CastAudioDecoder::OutputFormatSizeInBytes(
CastAudioDecoder::OutputFormat format) {
switch (format) {
case CastAudioDecoder::OutputFormat::kOutputSigned16:
return 2;
case CastAudioDecoder::OutputFormat::kOutputPlanarFloat:
return 4;
}
NOTREACHED();
return 1;
}
} // namespace media
} // namespace chromecast