Index: remoting/codec/audio_encoder_opus_unittest.cc |
diff --git a/remoting/codec/audio_encoder_opus_unittest.cc b/remoting/codec/audio_encoder_opus_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d05dc905db5f9e99fa56064b7dc77824bea88f3b |
--- /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 choose such that all the tests pass normally, but fail with |
Wez
2012/10/23 05:10:10
typo: choose -> chosen
Sergey Ulanov
2012/10/23 17:36:31
Done.
|
+// small small change (e.g. one sample shift between signals). |
Wez
2012/10/23 05:10:10
typo: small small change -> small changes?
Sergey Ulanov
2012/10/23 17:36:31
Done.
|
+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 raw data. This |
Wez
2012/10/23 05:10:10
nit: by raw data do you mean the original data?
Sergey Ulanov
2012/10/23 17:36:31
Done.
|
+ // 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 decoded. It estimates |
Wez
2012/10/23 05:10:10
typo: test signal that was encoded
Sergey Ulanov
2012/10/23 17:36:31
Done.
|
+ // phase shift from the original signal, then calculates standard deviation |
+ // of the difference between original and decoded signal. |
Wez
2012/10/23 05:10:10
typo: signal -> signals
Sergey Ulanov
2012/10/23 17:36:31
Done.
|
+ 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 |