// 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 "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/sys_byteorder.h"
#include "media/cast/cast_defines.h"
#include "third_party/opus/src/include/opus.h"
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,
transport::AudioCodec codec,
int num_channels,
int sampling_rate)
: cast_environment_(cast_environment),
codec_(codec),
num_channels_(num_channels),
cast_initialization_status_(STATUS_AUDIO_UNINITIALIZED),
seen_first_frame_(false) {
if (num_channels_ <= 0 || sampling_rate <= 0 || sampling_rate % 100 != 0)
cast_initialization_status_ = STATUS_INVALID_AUDIO_CONFIGURATION;
}
CastInitializationStatus InitializationResult() const {
return cast_initialization_status_;
}
void DecodeFrame(scoped_ptr<transport::EncodedFrame> encoded_frame,
const DecodeFrameCallback& callback) {
DCHECK_EQ(cast_initialization_status_, STATUS_AUDIO_INITIALIZED);
COMPILE_ASSERT(sizeof(encoded_frame->frame_id) == sizeof(last_frame_id_),
size_of_frame_id_types_do_not_match);
bool is_continuous = true;
if (seen_first_frame_) {
const uint32 frames_ahead = encoded_frame->frame_id - last_frame_id_;
if (frames_ahead > 1) {
RecoverBecauseFramesWereDropped();
is_continuous = false;
}
} else {
seen_first_frame_ = true;
}
last_frame_id_ = encoded_frame->frame_id;
scoped_ptr<AudioBus> decoded_audio = Decode(
encoded_frame->mutable_bytes(),
static_cast<int>(encoded_frame->data.size()));
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 scoped_ptr<AudioBus> Decode(uint8* data, int len) = 0;
const scoped_refptr<CastEnvironment> cast_environment_;
const transport::AudioCodec codec_;
const int num_channels_;
// Subclass' ctor is expected to set this to STATUS_AUDIO_INITIALIZED.
CastInitializationStatus cast_initialization_status_;
private:
bool seen_first_frame_;
uint32 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,
transport::kOpus,
num_channels,
sampling_rate),
decoder_memory_(new uint8[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::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED)
return;
if (opus_decoder_init(opus_decoder_, sampling_rate, num_channels) !=
OPUS_OK) {
ImplBase::cast_initialization_status_ =
STATUS_INVALID_AUDIO_CONFIGURATION;
return;
}
ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED;
}
private:
virtual ~OpusImpl() {}
virtual void RecoverBecauseFramesWereDropped() OVERRIDE {
// 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);
}
virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) OVERRIDE {
scoped_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.Pass(); // 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).Pass();
// 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.Pass();
}
const scoped_ptr<uint8[]> decoder_memory_;
OpusDecoder* const opus_decoder_;
const int max_samples_per_frame_;
const scoped_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,
transport::kPcm16,
num_channels,
sampling_rate) {
if (ImplBase::cast_initialization_status_ != STATUS_AUDIO_UNINITIALIZED)
return;
ImplBase::cast_initialization_status_ = STATUS_AUDIO_INITIALIZED;
}
private:
virtual ~Pcm16Impl() {}
virtual scoped_ptr<AudioBus> Decode(uint8* data, int len) OVERRIDE {
scoped_ptr<AudioBus> audio_bus;
const int num_samples = len / sizeof(int16) / num_channels_;
if (num_samples <= 0)
return audio_bus.Pass();
int16* const pcm_data = reinterpret_cast<int16*>(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>(base::NetToHost16(pcm_data[i]));
#endif
audio_bus = AudioBus::Create(num_channels_, num_samples).Pass();
audio_bus->FromInterleaved(pcm_data, num_samples, sizeof(int16));
return audio_bus.Pass();
}
DISALLOW_COPY_AND_ASSIGN(Pcm16Impl);
};
AudioDecoder::AudioDecoder(
const scoped_refptr<CastEnvironment>& cast_environment,
int channels,
int sampling_rate,
transport::AudioCodec codec)
: cast_environment_(cast_environment) {
switch (codec) {
case transport::kOpus:
impl_ = new OpusImpl(cast_environment, channels, sampling_rate);
break;
case transport::kPcm16:
impl_ = new Pcm16Impl(cast_environment, channels, sampling_rate);
break;
default:
NOTREACHED() << "Unknown or unspecified codec.";
break;
}
}
AudioDecoder::~AudioDecoder() {}
CastInitializationStatus AudioDecoder::InitializationResult() const {
if (impl_)
return impl_->InitializationResult();
return STATUS_UNSUPPORTED_AUDIO_CODEC;
}
void AudioDecoder::DecodeFrame(
scoped_ptr<transport::EncodedFrame> encoded_frame,
const DecodeFrameCallback& callback) {
DCHECK(encoded_frame.get());
DCHECK(!callback.is_null());
if (!impl_ || impl_->InitializationResult() != STATUS_AUDIO_INITIALIZED) {
callback.Run(make_scoped_ptr<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