// Copyright (c) 2012 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 "remoting/codec/audio_encoder_opus.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "media/base/audio_bus.h"
#include "media/base/multi_channel_resampler.h"
#include "third_party/opus/src/include/opus.h"
namespace remoting {
namespace {
// Output 160 kb/s bitrate.
const int kOutputBitrateBps = 160 * 1024;
// Opus doesn't support 44100 sampling rate so we always resample to 48kHz.
const AudioPacket::SamplingRate kOpusSamplingRate =
AudioPacket::SAMPLING_RATE_48000;
// Opus supports frame sizes of 2.5, 5, 10, 20, 40 and 60 ms. We use 20 ms
// frames to balance latency and efficiency.
const int kFrameSizeMs = 20;
// Number of samples per frame when using default sampling rate.
const int kFrameSamples =
kOpusSamplingRate * kFrameSizeMs / base::Time::kMillisecondsPerSecond;
const AudioPacket::BytesPerSample kBytesPerSample =
AudioPacket::BYTES_PER_SAMPLE_2;
bool IsSupportedSampleRate(int rate) {
return rate == 44100 || rate == 48000;
}
} // namespace
AudioEncoderOpus::AudioEncoderOpus()
: sampling_rate_(0),
channels_(AudioPacket::CHANNELS_STEREO),
encoder_(NULL),
frame_size_(0),
resampling_data_(NULL),
resampling_data_size_(0),
resampling_data_pos_(0) {
}
AudioEncoderOpus::~AudioEncoderOpus() {
DestroyEncoder();
}
void AudioEncoderOpus::InitEncoder() {
DCHECK(!encoder_);
int error;
encoder_ = opus_encoder_create(kOpusSamplingRate, channels_,
OPUS_APPLICATION_AUDIO, &error);
if (!encoder_) {
LOG(ERROR) << "Failed to create OPUS encoder. Error code: " << error;
return;
}
opus_encoder_ctl(encoder_, OPUS_SET_BITRATE(kOutputBitrateBps));
frame_size_ = sampling_rate_ * kFrameSizeMs /
base::Time::kMillisecondsPerSecond;
if (sampling_rate_ != kOpusSamplingRate) {
resample_buffer_.reset(
new char[kFrameSamples * kBytesPerSample * channels_]);
// TODO(sergeyu): Figure out the right buffer size to use per packet instead
// of using media::SincResampler::kDefaultRequestSize.
resampler_.reset(new media::MultiChannelResampler(
channels_,
static_cast<double>(sampling_rate_) / kOpusSamplingRate,
media::SincResampler::kDefaultRequestSize,
base::Bind(&AudioEncoderOpus::FetchBytesToResample,
base::Unretained(this))));
resampler_bus_ = media::AudioBus::Create(channels_, kFrameSamples);
}
// Drop leftover data because it's for different sampling rate.
leftover_samples_ = 0;
leftover_buffer_size_ =
frame_size_ + media::SincResampler::kDefaultRequestSize;
leftover_buffer_.reset(
new int16[leftover_buffer_size_ * channels_]);
}
void AudioEncoderOpus::DestroyEncoder() {
if (encoder_) {
opus_encoder_destroy(encoder_);
encoder_ = NULL;
}
resampler_.reset();
}
bool AudioEncoderOpus::ResetForPacket(AudioPacket* packet) {
if (packet->channels() != channels_ ||
packet->sampling_rate() != sampling_rate_) {
DestroyEncoder();
channels_ = packet->channels();
sampling_rate_ = packet->sampling_rate();
if (channels_ <= 0 || channels_ > 2 ||
!IsSupportedSampleRate(sampling_rate_)) {
LOG(WARNING) << "Unsupported OPUS parameters: "
<< channels_ << " channels with "
<< sampling_rate_ << " samples per second.";
return false;
}
InitEncoder();
}
return encoder_ != NULL;
}
void AudioEncoderOpus::FetchBytesToResample(int resampler_frame_delay,
media::AudioBus* audio_bus) {
DCHECK(resampling_data_);
int samples_left = (resampling_data_size_ - resampling_data_pos_) /
kBytesPerSample / channels_;
DCHECK_LE(audio_bus->frames(), samples_left);
audio_bus->FromInterleaved(
resampling_data_ + resampling_data_pos_,
audio_bus->frames(), kBytesPerSample);
resampling_data_pos_ += audio_bus->frames() * kBytesPerSample * channels_;
DCHECK_LE(resampling_data_pos_, static_cast<int>(resampling_data_size_));
}
scoped_ptr<AudioPacket> AudioEncoderOpus::Encode(
scoped_ptr<AudioPacket> packet) {
DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding());
DCHECK_EQ(1, packet->data_size());
DCHECK_EQ(kBytesPerSample, packet->bytes_per_sample());
if (!ResetForPacket(packet.get())) {
LOG(ERROR) << "Encoder initialization failed";
return scoped_ptr<AudioPacket>();
}
int samples_in_packet = packet->data(0).size() / kBytesPerSample / channels_;
const int16* next_sample =
reinterpret_cast<const int16*>(packet->data(0).data());
// Create a new packet of encoded data.
scoped_ptr<AudioPacket> encoded_packet(new AudioPacket());
encoded_packet->set_encoding(AudioPacket::ENCODING_OPUS);
encoded_packet->set_sampling_rate(kOpusSamplingRate);
encoded_packet->set_channels(channels_);
int prefetch_samples =
resampler_.get() ? media::SincResampler::kDefaultRequestSize : 0;
int samples_wanted = frame_size_ + prefetch_samples;
while (leftover_samples_ + samples_in_packet >= samples_wanted) {
const int16* pcm_buffer = NULL;
// Combine the packet with the leftover samples, if any.
if (leftover_samples_ > 0) {
pcm_buffer = leftover_buffer_.get();
int samples_to_copy = samples_wanted - leftover_samples_;
memcpy(leftover_buffer_.get() + leftover_samples_ * channels_,
next_sample, samples_to_copy * kBytesPerSample * channels_);
} else {
pcm_buffer = next_sample;
}
// Resample data if necessary.
int samples_consumed = 0;
if (resampler_.get()) {
resampling_data_ = reinterpret_cast<const char*>(pcm_buffer);
resampling_data_pos_ = 0;
resampling_data_size_ = samples_wanted * channels_ * kBytesPerSample;
resampler_->Resample(kFrameSamples, resampler_bus_.get());
resampling_data_ = NULL;
samples_consumed = resampling_data_pos_ / channels_ / kBytesPerSample;
resampler_bus_->ToInterleaved(kFrameSamples, kBytesPerSample,
resample_buffer_.get());
pcm_buffer = reinterpret_cast<int16*>(resample_buffer_.get());
} else {
samples_consumed = frame_size_;
}
// Initialize output buffer.
std::string* data = encoded_packet->add_data();
data->resize(kFrameSamples * kBytesPerSample * channels_);
// Encode.
unsigned char* buffer =
reinterpret_cast<unsigned char*>(string_as_array(data));
int result = opus_encode(encoder_, pcm_buffer, kFrameSamples,
buffer, data->length());
if (result < 0) {
LOG(ERROR) << "opus_encode() failed with error code: " << result;
return scoped_ptr<AudioPacket>();
}
DCHECK_LE(result, static_cast<int>(data->length()));
data->resize(result);
// Cleanup leftover buffer.
if (samples_consumed >= leftover_samples_) {
samples_consumed -= leftover_samples_;
leftover_samples_ = 0;
next_sample += samples_consumed * channels_;
samples_in_packet -= samples_consumed;
} else {
leftover_samples_ -= samples_consumed;
memmove(leftover_buffer_.get(),
leftover_buffer_.get() + samples_consumed * channels_,
leftover_samples_ * channels_ * kBytesPerSample);
}
}
// Store the leftover samples.
if (samples_in_packet > 0) {
DCHECK_LE(leftover_samples_ + samples_in_packet, leftover_buffer_size_);
memmove(leftover_buffer_.get() + leftover_samples_ * channels_,
next_sample, samples_in_packet * kBytesPerSample * channels_);
leftover_samples_ += samples_in_packet;
}
// Return NULL if there's nothing in the packet.
if (encoded_packet->data_size() == 0)
return scoped_ptr<AudioPacket>();
return encoded_packet.Pass();
}
} // namespace remoting