diff options
author | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-23 19:55:10 +0000 |
---|---|---|
committer | sergeyu@chromium.org <sergeyu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-23 19:55:10 +0000 |
commit | 7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68 (patch) | |
tree | 77175349dccbb9fe5fb9b3bdecdb8b92e2c32605 /remoting | |
parent | 53869dbba36edf76eec07499a5bb7b00f70ac509 (diff) | |
download | chromium_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/DEPS | 1 | ||||
-rw-r--r-- | remoting/codec/audio_decoder.cc | 3 | ||||
-rw-r--r-- | remoting/codec/audio_decoder_opus.cc | 132 | ||||
-rw-r--r-- | remoting/codec/audio_decoder_opus.h | 41 | ||||
-rw-r--r-- | remoting/codec/audio_encoder_opus.cc | 240 | ||||
-rw-r--r-- | remoting/codec/audio_encoder_opus.h | 63 | ||||
-rw-r--r-- | remoting/codec/audio_encoder_opus_unittest.cc | 189 | ||||
-rw-r--r-- | remoting/host/client_session.cc | 3 | ||||
-rw-r--r-- | remoting/protocol/session_config.cc | 4 | ||||
-rw-r--r-- | remoting/remoting.gyp | 8 |
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', |