blob: 3e805d1a0ceef43b40b2a683f8c67194d012d895 [file] [log] [blame]
// Copyright 2014 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 "media/cast/receiver/audio_decoder.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/sys_byteorder.h"
#include "build/build_config.h"
#include "starboard/types.h"
#include "third_party/opus/src/include/opus.h"
namespace cobalt {
namespace media {
namespace cast {
// Base class that handles the common problem of detecting dropped frames, and
// then invoking the Decode() method implemented by the subclasses to convert
// the encoded payload data into usable audio data.
class AudioDecoder::ImplBase
: public base::RefCountedThreadSafe<AudioDecoder::ImplBase> {
public:
ImplBase(const scoped_refptr<CastEnvironment>& cast_environment, Codec codec,
int num_channels, int sampling_rate)
: cast_environment_(cast_environment),
codec_(codec),
num_channels_(num_channels),
operational_status_(STATUS_UNINITIALIZED) {
if (num_channels_ <= 0 || sampling_rate <= 0 || sampling_rate % 100 != 0)
operational_status_ = STATUS_INVALID_CONFIGURATION;
}
OperationalStatus InitializationResult() const { return operational_status_; }
void DecodeFrame(std::unique_ptr<EncodedFrame> encoded_frame,
const DecodeFrameCallback& callback) {
DCHECK_EQ(operational_status_, STATUS_INITIALIZED);
bool is_continuous = true;
DCHECK(!encoded_frame->frame_id.is_null());
if (!last_frame_id_.is_null()) {
if (encoded_frame->frame_id > (last_frame_id_ + 1)) {
RecoverBecauseFramesWereDropped();
is_continuous = false;
}
}
last_frame_id_ = encoded_frame->frame_id;
std::unique_ptr<AudioBus> decoded_audio =
Decode(encoded_frame->mutable_bytes(),
static_cast<int>(encoded_frame->data.size()));
std::unique_ptr<FrameEvent> event(new FrameEvent());
event->timestamp = cast_environment_->Clock()->NowTicks();
event->type = FRAME_DECODED;
event->media_type = AUDIO_EVENT;
event->rtp_timestamp = encoded_frame->rtp_timestamp;
event->frame_id = encoded_frame->frame_id;
cast_environment_->logger()->DispatchFrameEvent(std::move(event));
cast_environment_->PostTask(
CastEnvironment::MAIN, FROM_HERE,
base::Bind(callback, base::Passed(&decoded_audio), is_continuous));
}
protected:
friend class base::RefCountedThreadSafe<ImplBase>;
virtual ~ImplBase() {}
virtual void RecoverBecauseFramesWereDropped() {}
// Note: Implementation of Decode() is allowed to mutate |data|.
virtual std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) = 0;
const scoped_refptr<CastEnvironment> cast_environment_;
const Codec codec_;
const int num_channels_;
// Subclass' ctor is expected to set this to STATUS_INITIALIZED.
OperationalStatus operational_status_;
private:
FrameId last_frame_id_;
DISALLOW_COPY_AND_ASSIGN(ImplBase);
};
class AudioDecoder::OpusImpl : public AudioDecoder::ImplBase {
public:
OpusImpl(const scoped_refptr<CastEnvironment>& cast_environment,
int num_channels, int sampling_rate)
: ImplBase(cast_environment, CODEC_AUDIO_OPUS, num_channels,
sampling_rate),
decoder_memory_(new uint8_t[opus_decoder_get_size(num_channels)]),
opus_decoder_(reinterpret_cast<OpusDecoder*>(decoder_memory_.get())),
max_samples_per_frame_(kOpusMaxFrameDurationMillis * sampling_rate /
1000),
buffer_(new float[max_samples_per_frame_ * num_channels]) {
if (ImplBase::operational_status_ != STATUS_UNINITIALIZED) return;
if (opus_decoder_init(opus_decoder_, sampling_rate, num_channels) !=
OPUS_OK) {
ImplBase::operational_status_ = STATUS_INVALID_CONFIGURATION;
return;
}
ImplBase::operational_status_ = STATUS_INITIALIZED;
}
private:
~OpusImpl() final {}
void RecoverBecauseFramesWereDropped() final {
// Passing NULL for the input data notifies the decoder of frame loss.
const opus_int32 result = opus_decode_float(
opus_decoder_, NULL, 0, buffer_.get(), max_samples_per_frame_, 0);
DCHECK_GE(result, 0);
}
std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) final {
std::unique_ptr<AudioBus> audio_bus;
const opus_int32 num_samples_decoded = opus_decode_float(
opus_decoder_, data, len, buffer_.get(), max_samples_per_frame_, 0);
if (num_samples_decoded <= 0) return audio_bus; // Decode error.
// Copy interleaved samples from |buffer_| into a new AudioBus (where
// samples are stored in planar format, for each channel).
audio_bus = AudioBus::Create(num_channels_, num_samples_decoded);
// TODO(miu): This should be moved into AudioBus::FromInterleaved().
for (int ch = 0; ch < num_channels_; ++ch) {
const float* src = buffer_.get() + ch;
const float* const src_end = src + num_samples_decoded * num_channels_;
float* dest = audio_bus->channel(ch);
for (; src < src_end; src += num_channels_, ++dest) *dest = *src;
}
return audio_bus;
}
const std::unique_ptr<uint8_t[]> decoder_memory_;
OpusDecoder* const opus_decoder_;
const int max_samples_per_frame_;
const std::unique_ptr<float[]> buffer_;
// According to documentation in third_party/opus/src/include/opus.h, we must
// provide enough space in |buffer_| to contain 120ms of samples. At 48 kHz,
// then, that means 5760 samples times the number of channels.
static const int kOpusMaxFrameDurationMillis = 120;
DISALLOW_COPY_AND_ASSIGN(OpusImpl);
};
class AudioDecoder::Pcm16Impl : public AudioDecoder::ImplBase {
public:
Pcm16Impl(const scoped_refptr<CastEnvironment>& cast_environment,
int num_channels, int sampling_rate)
: ImplBase(cast_environment, CODEC_AUDIO_PCM16, num_channels,
sampling_rate) {
if (ImplBase::operational_status_ != STATUS_UNINITIALIZED) return;
ImplBase::operational_status_ = STATUS_INITIALIZED;
}
private:
~Pcm16Impl() final {}
std::unique_ptr<AudioBus> Decode(uint8_t* data, int len) final {
std::unique_ptr<AudioBus> audio_bus;
const int num_samples = len / sizeof(int16_t) / num_channels_;
if (num_samples <= 0) return audio_bus;
int16_t* const pcm_data = reinterpret_cast<int16_t*>(data);
#if defined(ARCH_CPU_LITTLE_ENDIAN)
// Convert endianness.
const int num_elements = num_samples * num_channels_;
for (int i = 0; i < num_elements; ++i)
pcm_data[i] = static_cast<int16_t>(base::NetToHost16(pcm_data[i]));
#endif
audio_bus = AudioBus::Create(num_channels_, num_samples);
audio_bus->FromInterleaved(pcm_data, num_samples, sizeof(int16_t));
return audio_bus;
}
DISALLOW_COPY_AND_ASSIGN(Pcm16Impl);
};
AudioDecoder::AudioDecoder(
const scoped_refptr<CastEnvironment>& cast_environment, int channels,
int sampling_rate, Codec codec)
: cast_environment_(cast_environment) {
switch (codec) {
case CODEC_AUDIO_OPUS:
impl_ = new OpusImpl(cast_environment, channels, sampling_rate);
break;
case CODEC_AUDIO_PCM16:
impl_ = new Pcm16Impl(cast_environment, channels, sampling_rate);
break;
default:
NOTREACHED() << "Unknown or unspecified codec.";
break;
}
}
AudioDecoder::~AudioDecoder() {}
OperationalStatus AudioDecoder::InitializationResult() const {
if (impl_.get()) return impl_->InitializationResult();
return STATUS_UNSUPPORTED_CODEC;
}
void AudioDecoder::DecodeFrame(std::unique_ptr<EncodedFrame> encoded_frame,
const DecodeFrameCallback& callback) {
DCHECK(encoded_frame.get());
DCHECK(!callback.is_null());
if (!impl_.get() || impl_->InitializationResult() != STATUS_INITIALIZED) {
callback.Run(base::WrapUnique<AudioBus>(NULL), false);
return;
}
cast_environment_->PostTask(
CastEnvironment::AUDIO, FROM_HERE,
base::Bind(&AudioDecoder::ImplBase::DecodeFrame, impl_,
base::Passed(&encoded_frame), callback));
}
} // namespace cast
} // namespace media
} // namespace cobalt