summaryrefslogtreecommitdiffstats
path: root/remoting
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
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')
-rw-r--r--remoting/codec/DEPS1
-rw-r--r--remoting/codec/audio_decoder.cc3
-rw-r--r--remoting/codec/audio_decoder_opus.cc132
-rw-r--r--remoting/codec/audio_decoder_opus.h41
-rw-r--r--remoting/codec/audio_encoder_opus.cc240
-rw-r--r--remoting/codec/audio_encoder_opus.h63
-rw-r--r--remoting/codec/audio_encoder_opus_unittest.cc189
-rw-r--r--remoting/host/client_session.cc3
-rw-r--r--remoting/protocol/session_config.cc4
-rw-r--r--remoting/remoting.gyp8
10 files changed, 684 insertions, 0 deletions
diff --git a/remoting/codec/DEPS b/remoting/codec/DEPS
index df4431a..53a9412 100644
--- a/remoting/codec/DEPS
+++ b/remoting/codec/DEPS
@@ -2,5 +2,6 @@ include_rules = [
"+remoting/protocol",
"+google/protobuf",
+ "+third_party/opus",
"+third_party/speex",
]
diff --git a/remoting/codec/audio_decoder.cc b/remoting/codec/audio_decoder.cc
index 6e6a686..3dc70c3 100644
--- a/remoting/codec/audio_decoder.cc
+++ b/remoting/codec/audio_decoder.cc
@@ -5,6 +5,7 @@
#include "remoting/codec/audio_decoder.h"
#include "base/logging.h"
+#include "remoting/codec/audio_decoder_opus.h"
#include "remoting/codec/audio_decoder_speex.h"
#include "remoting/codec/audio_decoder_verbatim.h"
#include "remoting/protocol/session_config.h"
@@ -17,6 +18,8 @@ scoped_ptr<AudioDecoder> AudioDecoder::CreateAudioDecoder(
if (audio_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) {
return scoped_ptr<AudioDecoder>(new AudioDecoderVerbatim());
+ } else if (audio_config.codec == protocol::ChannelConfig::CODEC_OPUS) {
+ return scoped_ptr<AudioDecoder>(new AudioDecoderOpus());
} else if (audio_config.codec == protocol::ChannelConfig::CODEC_SPEEX) {
return scoped_ptr<AudioDecoder>(new AudioDecoderSpeex());
}
diff --git a/remoting/codec/audio_decoder_opus.cc b/remoting/codec/audio_decoder_opus.cc
new file mode 100644
index 0000000..a2b601f
--- /dev/null
+++ b/remoting/codec/audio_decoder_opus.cc
@@ -0,0 +1,132 @@
+// 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_decoder_opus.h"
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "base/time.h"
+#include "remoting/proto/audio.pb.h"
+#include "third_party/opus/opus.h"
+
+namespace remoting {
+
+namespace {
+
+const int kMaxPacketSizeMs = 120;
+
+const AudioPacket::SamplingRate kSamplingRate =
+ AudioPacket::SAMPLING_RATE_48000;
+
+} // namespace
+
+AudioDecoderOpus::AudioDecoderOpus()
+ : sampling_rate_(0),
+ channels_(0),
+ decoder_(NULL) {
+}
+
+AudioDecoderOpus::~AudioDecoderOpus() {
+ DestroyDecoder();
+}
+
+void AudioDecoderOpus::InitDecoder() {
+ DCHECK(!decoder_);
+ int error;
+ decoder_ = opus_decoder_create(kSamplingRate, channels_, &error);
+ if (!decoder_) {
+ LOG(ERROR) << "Failed to create OPUS decoder; Error code: " << error;
+ }
+}
+
+void AudioDecoderOpus::DestroyDecoder() {
+ if (decoder_) {
+ opus_decoder_destroy(decoder_);
+ decoder_ = NULL;
+ }
+}
+
+bool AudioDecoderOpus::ResetForPacket(AudioPacket* packet) {
+ if (packet->channels() != channels_ ||
+ packet->sampling_rate() != sampling_rate_) {
+ DestroyDecoder();
+
+ channels_ = packet->channels();
+ sampling_rate_ = packet->sampling_rate();
+
+ if (channels_ <= 0 || channels_ > 2 ||
+ sampling_rate_ != kSamplingRate) {
+ LOG(WARNING) << "Unsupported OPUS parameters: "
+ << channels_ << " channels with "
+ << sampling_rate_ << " samples per second.";
+ return false;
+ }
+ }
+
+ if (!decoder_) {
+ InitDecoder();
+ }
+
+ return decoder_ != NULL;
+}
+
+
+scoped_ptr<AudioPacket> AudioDecoderOpus::Decode(
+ scoped_ptr<AudioPacket> packet) {
+ if (packet->encoding() != AudioPacket::ENCODING_OPUS) {
+ LOG(WARNING) << "Received a packet with encoding " << packet->encoding()
+ << "when an OPUS packet was expected.";
+ return scoped_ptr<AudioPacket>();
+ }
+
+ if (!ResetForPacket(packet.get())) {
+ return scoped_ptr<AudioPacket>();
+ }
+
+ // Create a new packet of decoded data.
+ scoped_ptr<AudioPacket> decoded_packet(new AudioPacket());
+ decoded_packet->set_encoding(AudioPacket::ENCODING_RAW);
+ decoded_packet->set_sampling_rate(kSamplingRate);
+ decoded_packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2);
+ decoded_packet->set_channels(packet->channels());
+
+ int max_frame_samples = kMaxPacketSizeMs * kSamplingRate /
+ base::Time::kMillisecondsPerSecond;
+ int max_frame_bytes = max_frame_samples * channels_ *
+ decoded_packet->bytes_per_sample();
+
+ std::string* decoded_data = decoded_packet->add_data();
+ decoded_data->resize(packet->data_size() * max_frame_bytes);
+ int buffer_pos = 0;
+
+ for (int i = 0; i < packet->data_size(); ++i) {
+ int16* pcm_buffer =
+ reinterpret_cast<int16*>(string_as_array(decoded_data) + buffer_pos);
+ CHECK_LE(buffer_pos + max_frame_bytes,
+ static_cast<int>(decoded_data->size()));
+ std::string* frame = packet->mutable_data(i);
+ unsigned char* frame_data =
+ reinterpret_cast<unsigned char*>(string_as_array(frame));
+ int result = opus_decode(decoder_, frame_data, frame->size(),
+ pcm_buffer, max_frame_samples, 0);
+ if (result < 0) {
+ LOG(ERROR) << "Failed decoding Opus frame. Error code: " << result;
+ DestroyDecoder();
+ return scoped_ptr<AudioPacket>();
+ }
+
+ buffer_pos += result * packet->channels() *
+ decoded_packet->bytes_per_sample();
+ }
+
+ if (!buffer_pos) {
+ return scoped_ptr<AudioPacket>();
+ }
+
+ decoded_data->resize(buffer_pos);
+
+ return decoded_packet.Pass();
+}
+
+} // namespace remoting
diff --git a/remoting/codec/audio_decoder_opus.h b/remoting/codec/audio_decoder_opus.h
new file mode 100644
index 0000000..35989ea
--- /dev/null
+++ b/remoting/codec/audio_decoder_opus.h
@@ -0,0 +1,41 @@
+// 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.
+
+#ifndef REMOTING_CODEC_AUDIO_DECODER_OPUS_H_
+#define REMOTING_CODEC_AUDIO_DECODER_OPUS_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "remoting/codec/audio_decoder.h"
+
+struct OpusDecoder;
+
+namespace remoting {
+
+class AudioPacket;
+
+class AudioDecoderOpus : public AudioDecoder {
+ public:
+ AudioDecoderOpus();
+ virtual ~AudioDecoderOpus();
+
+ // AudioDecoder interface.
+ virtual scoped_ptr<AudioPacket> Decode(
+ scoped_ptr<AudioPacket> packet) OVERRIDE;
+
+ private:
+ void InitDecoder();
+ void DestroyDecoder();
+ bool ResetForPacket(AudioPacket* packet);
+
+ int sampling_rate_;
+ int channels_;
+ OpusDecoder* decoder_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioDecoderOpus);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_CODEC_AUDIO_DECODER_OPUS_H_
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
diff --git a/remoting/codec/audio_encoder_opus.h b/remoting/codec/audio_encoder_opus.h
new file mode 100644
index 0000000..b172de2
--- /dev/null
+++ b/remoting/codec/audio_encoder_opus.h
@@ -0,0 +1,63 @@
+// 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.
+
+#ifndef REMOTING_CODEC_AUDIO_ENCODER_OPUS_H_
+#define REMOTING_CODEC_AUDIO_ENCODER_OPUS_H_
+
+#include "remoting/codec/audio_encoder.h"
+
+#include "remoting/proto/audio.pb.h"
+
+struct OpusEncoder;
+
+namespace media {
+class AudioBus;
+class MultiChannelResampler;
+} // namespace media
+
+namespace remoting {
+
+class AudioPacket;
+
+class AudioEncoderOpus : public AudioEncoder {
+ public:
+ AudioEncoderOpus();
+ virtual ~AudioEncoderOpus();
+
+ // AudioEncoder interface.
+ virtual scoped_ptr<AudioPacket> Encode(
+ scoped_ptr<AudioPacket> packet) OVERRIDE;
+
+ private:
+ void InitEncoder();
+ void DestroyEncoder();
+ bool ResetForPacket(AudioPacket* packet);
+
+ void FetchBytesToResample(media::AudioBus* audio_bus);
+
+ int sampling_rate_;
+ AudioPacket::Channels channels_;
+ OpusEncoder* encoder_;
+
+ int frame_size_;
+ scoped_ptr<media::MultiChannelResampler> resampler_;
+ scoped_array<char> resample_buffer_;
+ scoped_ptr<media::AudioBus> resampler_bus_;
+
+ // Used to pass packet to the FetchBytesToResampler() callback.
+ const char* resampling_data_;
+ int resampling_data_size_;
+ int resampling_data_pos_;
+
+ // Left-over unencoded samples from the previous AudioPacket.
+ scoped_array<int16> leftover_buffer_;
+ int leftover_buffer_size_;
+ int leftover_samples_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioEncoderOpus);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_CODEC_AUDIO_ENCODER_OPUS_H_
diff --git a/remoting/codec/audio_encoder_opus_unittest.cc b/remoting/codec/audio_encoder_opus_unittest.cc
new file mode 100644
index 0000000..b32bbec
--- /dev/null
+++ b/remoting/codec/audio_encoder_opus_unittest.cc
@@ -0,0 +1,189 @@
+// 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.
+
+// MSVC++ requires this to get M_PI.
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "remoting/codec/audio_encoder_opus.h"
+
+#include "base/logging.h"
+#include "remoting/codec/audio_decoder_opus.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+namespace {
+
+// Maximum value that can be encoded in a 16-bit signed sample.
+const int kMaxSampleValue = 32767;
+
+const int kChannels = 2;
+
+// Phase shift between left and right channels.
+const double kChannelPhaseShift = 2 * M_PI / 3;
+
+// The sampling rate that OPUS uses internally and that we expect to get
+// from the decoder.
+const AudioPacket_SamplingRate kDefaultSamplingRate =
+ AudioPacket::SAMPLING_RATE_48000;
+
+// Maximum latency expected from the encoder.
+const int kMaxLatencyMs = 40;
+
+// When verifying results ignore the first 1k samples. This is necessary because
+// it takes some time for the codec to adjust for the input signal.
+const int kSkippedFirstSamples = 1000;
+
+// Maximum standard deviation of the difference between original and decoded
+// signals as a proportion of kMaxSampleValue. For two unrelated signals this
+// difference will be close to 1.0, even for signals that differ only slightly.
+// The value is chosen such that all the tests pass normally, but fail with
+// small changes (e.g. one sample shift between signals).
+const double kMaxSignalDeviation = 0.1;
+
+} // namespace
+
+class OpusAudioEncoderTest : public testing::Test {
+ public:
+ // Return test signal value at the specified position |pos|. |frequency_hz|
+ // defines frequency of the signal. |channel| is used to calculate phase shift
+ // of the signal, so that different signals are generated for left and right
+ // channels.
+ static int16 GetSampleValue(
+ AudioPacket::SamplingRate rate,
+ double frequency_hz,
+ double pos,
+ int channel) {
+ double angle = pos * 2 * M_PI * frequency_hz / rate +
+ kChannelPhaseShift * channel;
+ return static_cast<int>(sin(angle) * kMaxSampleValue + 0.5);
+ }
+
+ // Creates audio packet filled with a test signal with the specified
+ // |frequency_hz|.
+ scoped_ptr<AudioPacket> CreatePacket(
+ int samples,
+ AudioPacket::SamplingRate rate,
+ double frequency_hz,
+ int pos) {
+ std::vector<int16> data(samples * kChannels);
+ for (int i = 0; i < samples; ++i) {
+ data[i * kChannels] = GetSampleValue(rate, frequency_hz, i + pos, 0);
+ data[i * kChannels + 1] = GetSampleValue(rate, frequency_hz, i + pos, 1);
+ }
+
+ scoped_ptr<AudioPacket> packet(new AudioPacket());
+ packet->add_data(reinterpret_cast<char*>(&(data[0])),
+ samples * kChannels * sizeof(int16));
+ packet->set_encoding(AudioPacket::ENCODING_RAW);
+ packet->set_sampling_rate(rate);
+ packet->set_bytes_per_sample(AudioPacket::BYTES_PER_SAMPLE_2);
+ packet->set_channels(AudioPacket::CHANNELS_STEREO);
+ return packet.Pass();
+ }
+
+ // Decoded data is normally shifted in phase relative to the original signal.
+ // This function returns the approximate shift in samples by finding the first
+ // point when signal goes from negative to positive.
+ double EstimateSignalShift(const std::vector<int16>& received_data) {
+ for (size_t i = kSkippedFirstSamples;
+ i < received_data.size() / kChannels - 1; i++) {
+ int16 this_sample = received_data[i * kChannels];
+ int16 next_sample = received_data[(i + 1) * kChannels];
+ if (this_sample < 0 && next_sample > 0) {
+ return
+ i + static_cast<double>(-this_sample) / (next_sample - this_sample);
+ }
+ }
+ return 0;
+ }
+
+ // Compares decoded signal with the test signal that was encoded. It estimates
+ // phase shift from the original signal, then calculates standard deviation of
+ // the difference between original and decoded signals.
+ void ValidateReceivedData(int samples,
+ AudioPacket::SamplingRate rate,
+ double frequency_hz,
+ const std::vector<int16>& received_data) {
+ double shift = EstimateSignalShift(received_data);
+ double diff_sqare_sum = 0;
+ for (size_t i = kSkippedFirstSamples;
+ i < received_data.size() / kChannels; i++) {
+ double d = received_data[i * kChannels] -
+ GetSampleValue(rate, frequency_hz, i - shift, 0);
+ diff_sqare_sum += d * d;
+ d = received_data[i * kChannels + 1] -
+ GetSampleValue(rate, frequency_hz, i - shift, 1);
+ diff_sqare_sum += d * d;
+ }
+ double deviation = sqrt(diff_sqare_sum / received_data.size())
+ / kMaxSampleValue;
+ EXPECT_LE(deviation, kMaxSignalDeviation);
+ }
+
+ void TestEncodeDecode(int packet_size,
+ double frequency_hz,
+ AudioPacket::SamplingRate rate) {
+ const int kTotalTestSamples = 24000;
+
+ encoder_.reset(new AudioEncoderOpus());
+ decoder_.reset(new AudioDecoderOpus());
+
+ std::vector<int16> received_data;
+ int pos = 0;
+ for (; pos < kTotalTestSamples; pos += packet_size) {
+ scoped_ptr<AudioPacket> source_packet =
+ CreatePacket(packet_size, rate, frequency_hz, pos);
+ scoped_ptr<AudioPacket> encoded =
+ encoder_->Encode(source_packet.Pass());
+ if (encoded.get()) {
+ scoped_ptr<AudioPacket> decoded = decoder_->Decode(encoded.Pass());
+ EXPECT_EQ(kDefaultSamplingRate, decoded->sampling_rate());
+ for (int i = 0; i < decoded->data_size(); ++i) {
+ const int16* data =
+ reinterpret_cast<const int16*>(decoded->data(i).data());
+ received_data.insert(
+ received_data.end(), data,
+ data + decoded->data(i).size() / sizeof(int16));
+ }
+ }
+ }
+
+ // Verify that at most kMaxLatencyMs worth of samples is buffered inside
+ // |encoder_| and |decoder_|.
+ EXPECT_GE(static_cast<int>(received_data.size()) / kChannels,
+ pos - rate * kMaxLatencyMs / 1000);
+
+ ValidateReceivedData(packet_size, kDefaultSamplingRate,
+ frequency_hz, received_data);
+ }
+
+ protected:
+ scoped_ptr<AudioEncoderOpus> encoder_;
+ scoped_ptr<AudioDecoderOpus> decoder_;
+};
+
+TEST_F(OpusAudioEncoderTest, CreateAndDestroy) {
+}
+
+TEST_F(OpusAudioEncoderTest, NoResampling) {
+ TestEncodeDecode(2000, 50, AudioPacket::SAMPLING_RATE_48000);
+ TestEncodeDecode(2000, 3000, AudioPacket::SAMPLING_RATE_48000);
+ TestEncodeDecode(2000, 10000, AudioPacket::SAMPLING_RATE_48000);
+}
+
+TEST_F(OpusAudioEncoderTest, Resampling) {
+ TestEncodeDecode(2000, 50, AudioPacket::SAMPLING_RATE_44100);
+ TestEncodeDecode(2000, 3000, AudioPacket::SAMPLING_RATE_44100);
+ TestEncodeDecode(2000, 10000, AudioPacket::SAMPLING_RATE_44100);
+}
+
+TEST_F(OpusAudioEncoderTest, BufferSizeAndResampling) {
+ TestEncodeDecode(500, 3000, AudioPacket::SAMPLING_RATE_44100);
+ TestEncodeDecode(1000, 3000, AudioPacket::SAMPLING_RATE_44100);
+ TestEncodeDecode(5000, 3000, AudioPacket::SAMPLING_RATE_44100);
+}
+
+} // namespace remoting
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index d311d46..6bc351f 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -8,6 +8,7 @@
#include "base/message_loop_proxy.h"
#include "remoting/codec/audio_encoder.h"
+#include "remoting/codec/audio_encoder_opus.h"
#include "remoting/codec/audio_encoder_speex.h"
#include "remoting/codec/audio_encoder_verbatim.h"
#include "remoting/codec/video_encoder.h"
@@ -302,6 +303,8 @@ scoped_ptr<AudioEncoder> ClientSession::CreateAudioEncoder(
return scoped_ptr<AudioEncoder>(new AudioEncoderVerbatim());
} else if (audio_config.codec == protocol::ChannelConfig::CODEC_SPEEX) {
return scoped_ptr<AudioEncoder>(new AudioEncoderSpeex());
+ } else if (audio_config.codec == protocol::ChannelConfig::CODEC_OPUS) {
+ return scoped_ptr<AudioEncoder>(new AudioEncoderOpus());
}
NOTIMPLEMENTED();
diff --git a/remoting/protocol/session_config.cc b/remoting/protocol/session_config.cc
index 28ffc19..f825302 100644
--- a/remoting/protocol/session_config.cc
+++ b/remoting/protocol/session_config.cc
@@ -198,6 +198,10 @@ scoped_ptr<CandidateSessionConfig> CandidateSessionConfig::CreateDefault() {
result->mutable_audio_configs()->push_back(
ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
kDefaultStreamVersion,
+ ChannelConfig::CODEC_OPUS));
+ result->mutable_audio_configs()->push_back(
+ ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
+ kDefaultStreamVersion,
ChannelConfig::CODEC_SPEEX));
result->mutable_audio_configs()->push_back(
ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 7338b55..fb695d5 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -1375,8 +1375,11 @@
'../net/net.gyp:net',
'../skia/skia.gyp:skia',
'../third_party/libvpx/libvpx.gyp:libvpx',
+ '../third_party/opus/opus.gyp:opus',
'../third_party/protobuf/protobuf.gyp:protobuf_lite',
'../third_party/speex/speex.gyp:libspeex',
+ '../media/media.gyp:media',
+ '../media/media.gyp:shared_memory_support',
'../media/media.gyp:yuv_convert',
'remoting_jingle_glue',
'remoting_resources',
@@ -1419,11 +1422,15 @@
'base/util.h',
'codec/audio_decoder.cc',
'codec/audio_decoder.h',
+ 'codec/audio_decoder_opus.cc',
+ 'codec/audio_decoder_opus.h',
'codec/audio_decoder_speex.cc',
'codec/audio_decoder_speex.h',
'codec/audio_decoder_verbatim.cc',
'codec/audio_decoder_verbatim.h',
'codec/audio_encoder.h',
+ 'codec/audio_encoder_opus.cc',
+ 'codec/audio_encoder_opus.h',
'codec/audio_encoder_speex.cc',
'codec/audio_encoder_speex.h',
'codec/audio_encoder_verbatim.cc',
@@ -2064,6 +2071,7 @@
'client/audio_player_unittest.cc',
'client/key_event_mapper_unittest.cc',
'client/plugin/mac_key_event_processor_unittest.cc',
+ 'codec/audio_encoder_opus_unittest.cc',
'codec/codec_test.cc',
'codec/codec_test.h',
'codec/video_decoder_vp8_unittest.cc',