summaryrefslogtreecommitdiffstats
path: root/remoting/codec/audio_encoder_opus.cc
diff options
context:
space:
mode:
authorsergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-23 19:55:10 +0000
committersergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-23 19:55:10 +0000
commit7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68 (patch)
tree77175349dccbb9fe5fb9b3bdecdb8b92e2c32605 /remoting/codec/audio_encoder_opus.cc
parent53869dbba36edf76eec07499a5bb7b00f70ac509 (diff)
downloadchromium_src-7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68.zip
chromium_src-7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68.tar.gz
chromium_src-7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68.tar.bz2
Add opus audio codec support in remoting
BUG=154714 Review URL: https://chromiumcodereview.appspot.com/11189047 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@163650 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/codec/audio_encoder_opus.cc')
-rw-r--r--remoting/codec/audio_encoder_opus.cc240
1 files changed, 240 insertions, 0 deletions
diff --git a/remoting/codec/audio_encoder_opus.cc b/remoting/codec/audio_encoder_opus.cc
new file mode 100644
index 0000000..6ff3056
--- /dev/null
+++ b/remoting/codec/audio_encoder_opus.cc
@@ -0,0 +1,240 @@
+// 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.h"
+#include "media/base/audio_bus.h"
+#include "media/base/multi_channel_resampler.h"
+#include "third_party/opus/opus.h"
+
+namespace remoting {
+
+namespace {
+
+// Output 160 kb/s bitrate.
+const int kOutputBitrateBps = 160 * 1024;
+
+// Encoded buffer size.
+const int kFrameDefaultBufferSize = 4096;
+
+// Maximum buffer size we'll allocate when encoding before giving up.
+const int kMaxBufferSize = 65536;
+
+// 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_]);
+ resampler_.reset(new media::MultiChannelResampler(
+ channels_,
+ static_cast<double>(sampling_rate_) / kOpusSamplingRate,
+ 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::kMaximumLookAheadSize;
+ 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(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::kMaximumLookAheadSize : 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(resampler_bus_.get(), kFrameSamples);
+ 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