From 663fdd02fde854b9765c500effd6b306681398f7 Mon Sep 17 00:00:00 2001 From: "kwiberg@webrtc.org" Date: Wed, 29 Oct 2014 07:28:36 +0000 Subject: [PATCH] Make an AudioEncoder subclass for Opus BUG=3926 R=henrik.lundin@webrtc.org, kjellander@webrtc.org Review URL: https://webrtc-codereview.appspot.com/23239004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7552 4adac7df-926f-26a2-2b94-8c16560cd09d --- webrtc/modules/audio_coding/BUILD.gn | 2 + .../audio_coding/codecs/audio_encoder.h | 6 +- .../codecs/opus/audio_encoder_opus.cc | 104 ++++++++++++++++++ .../opus/interface/audio_encoder_opus.h | 55 +++++++++ .../audio_coding/codecs/opus/opus.gypi | 2 + .../neteq/audio_decoder_unittest.cc | 69 ++++-------- 6 files changed, 185 insertions(+), 53 deletions(-) create mode 100644 webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc create mode 100644 webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn index d1f70fa1a5..547f15f0b1 100644 --- a/webrtc/modules/audio_coding/BUILD.gn +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -608,6 +608,8 @@ config("opus_config") { source_set("webrtc_opus") { sources = [ + "codecs/opus/audio_encoder_opus.cc", + "codecs/opus/interface/audio_encoder_opus.h", "codecs/opus/interface/opus_interface.h", "codecs/opus/opus_inst.h", "codecs/opus/opus_interface.c", diff --git a/webrtc/modules/audio_coding/codecs/audio_encoder.h b/webrtc/modules/audio_coding/codecs/audio_encoder.h index f8142e2b62..f9cbe212fa 100644 --- a/webrtc/modules/audio_coding/codecs/audio_encoder.h +++ b/webrtc/modules/audio_coding/codecs/audio_encoder.h @@ -33,13 +33,13 @@ class AudioEncoder { // output. bool Encode(uint32_t timestamp, const int16_t* audio, - size_t num_samples, + size_t num_samples_per_channel, size_t max_encoded_bytes, uint8_t* encoded, size_t* encoded_bytes, uint32_t* encoded_timestamp) { - CHECK_EQ(num_samples, - static_cast(sample_rate_hz() / 100 * num_channels())); + CHECK_EQ(num_samples_per_channel, + static_cast(sample_rate_hz() / 100)); bool ret = Encode(timestamp, audio, max_encoded_bytes, diff --git a/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc new file mode 100644 index 0000000000..0a3661f5bd --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h" + +#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" + +namespace webrtc { + +namespace { + +// We always encode at 48 kHz. +const int kSampleRateHz = 48000; + +int DivExact(int a, int b) { + CHECK_EQ(a % b, 0); + return a / b; +} + +int16_t ClampInt16(size_t x) { + return static_cast( + std::min(x, static_cast(std::numeric_limits::max()))); +} + +int16_t CastInt16(size_t x) { + DCHECK_LE(x, static_cast(std::numeric_limits::max())); + return static_cast(x); +} + +} // namespace + +AudioEncoderOpus::Config::Config() : frame_size_ms(20), num_channels(1) {} + +bool AudioEncoderOpus::Config::IsOk() const { + if (frame_size_ms <= 0 || frame_size_ms % 10 != 0) + return false; + if (num_channels <= 0) + return false; + return true; +} + +AudioEncoderOpus::AudioEncoderOpus(const Config& config) + : num_10ms_frames_per_packet_(DivExact(config.frame_size_ms, 10)), + num_channels_(config.num_channels), + samples_per_10ms_frame_(DivExact(kSampleRateHz, 100) * num_channels_) { + CHECK(config.IsOk()); + input_buffer_.reserve(num_10ms_frames_per_packet_ * samples_per_10ms_frame_); + CHECK_EQ(0, WebRtcOpus_EncoderCreate(&inst_, num_channels_)); +} + +AudioEncoderOpus::~AudioEncoderOpus() { + CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_)); +} + +int AudioEncoderOpus::sample_rate_hz() const { + return kSampleRateHz; +} + +int AudioEncoderOpus::num_channels() const { + return num_channels_; +} + +int AudioEncoderOpus::num_10ms_frames_per_packet() const { + return num_10ms_frames_per_packet_; +} + +bool AudioEncoderOpus::Encode(uint32_t timestamp, + const int16_t* audio, + size_t max_encoded_bytes, + uint8_t* encoded, + size_t* encoded_bytes, + uint32_t* encoded_timestamp) { + if (input_buffer_.empty()) + first_timestamp_in_buffer_ = timestamp; + input_buffer_.insert(input_buffer_.end(), audio, + audio + samples_per_10ms_frame_); + if (input_buffer_.size() < (static_cast(num_10ms_frames_per_packet_) * + samples_per_10ms_frame_)) { + *encoded_bytes = 0; + return true; + } + CHECK_EQ(input_buffer_.size(), + static_cast(num_10ms_frames_per_packet_) * + samples_per_10ms_frame_); + int16_t r = WebRtcOpus_Encode( + inst_, &input_buffer_[0], + DivExact(CastInt16(input_buffer_.size()), num_channels_), + ClampInt16(max_encoded_bytes), encoded); + input_buffer_.clear(); + if (r < 0) + return false; + *encoded_bytes = r; + *encoded_timestamp = first_timestamp_in_buffer_; + return true; +} + +} // namespace webrtc diff --git a/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h b/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h new file mode 100644 index 0000000000..7325b7e93c --- /dev/null +++ b/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_INTERFACE_AUDIO_ENCODER_OPUS_H_ +#define WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_INTERFACE_AUDIO_ENCODER_OPUS_H_ + +#include + +#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" +#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" + +namespace webrtc { + +class AudioEncoderOpus : public AudioEncoder { + public: + struct Config { + Config(); + bool IsOk() const; + int frame_size_ms; + int num_channels; + }; + + explicit AudioEncoderOpus(const Config& config); + virtual ~AudioEncoderOpus() OVERRIDE; + + virtual int sample_rate_hz() const OVERRIDE; + virtual int num_channels() const OVERRIDE; + virtual int num_10ms_frames_per_packet() const OVERRIDE; + + protected: + virtual bool Encode(uint32_t timestamp, + const int16_t* audio, + size_t max_encoded_bytes, + uint8_t* encoded, + size_t* encoded_bytes, + uint32_t* encoded_timestamp) OVERRIDE; + + private: + const int num_10ms_frames_per_packet_; + const int num_channels_; + const int samples_per_10ms_frame_; + std::vector input_buffer_; + OpusEncInst* inst_; + uint32_t first_timestamp_in_buffer_; +}; + +} // namespace webrtc +#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_OPUS_INTERFACE_AUDIO_ENCODER_OPUS_H_ diff --git a/webrtc/modules/audio_coding/codecs/opus/opus.gypi b/webrtc/modules/audio_coding/codecs/opus/opus.gypi index 89f0a54a76..b537285ae2 100644 --- a/webrtc/modules/audio_coding/codecs/opus/opus.gypi +++ b/webrtc/modules/audio_coding/codecs/opus/opus.gypi @@ -27,6 +27,8 @@ '<(webrtc_root)', ], 'sources': [ + 'audio_encoder_opus.cc', + 'interface/audio_encoder_opus.h', 'interface/opus_interface.h', 'opus_inst.h', 'opus_interface.c', diff --git a/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc b/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc index 3a5a13ff75..b6c6ba16d4 100644 --- a/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc +++ b/webrtc/modules/audio_coding/neteq/audio_decoder_unittest.cc @@ -26,7 +26,7 @@ #include "webrtc/modules/audio_coding/codecs/ilbc/interface/ilbc.h" #include "webrtc/modules/audio_coding/codecs/isac/fix/interface/isacfix.h" #include "webrtc/modules/audio_coding/codecs/isac/main/interface/isac.h" -#include "webrtc/modules/audio_coding/codecs/opus/interface/opus_interface.h" +#include "webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h" #include "webrtc/modules/audio_coding/codecs/pcm16b/include/pcm16b.h" #include "webrtc/modules/audio_coding/neteq/tools/resample_input_audio_file.h" #include "webrtc/system_wrappers/interface/data_log.h" @@ -140,17 +140,20 @@ class AudioDecoderTest : public ::testing::Test { size_t input_len_samples, uint8_t* output) { size_t enc_len_bytes = 0; + scoped_ptr interleaved_input( + new int16_t[channels_ * input_len_samples]); for (int i = 0; i < audio_encoder_->num_10ms_frames_per_packet(); ++i) { EXPECT_EQ(0u, enc_len_bytes); - EXPECT_TRUE(audio_encoder_->Encode(0, - input, - audio_encoder_->sample_rate_hz() / 100, - data_length_ * 2, - output, - &enc_len_bytes, - &output_timestamp_)); + + // Duplicate the mono input signal to however many channels the test + // wants. + test::InputAudioFile::DuplicateInterleaved( + input, input_len_samples, channels_, interleaved_input.get()); + + EXPECT_TRUE(audio_encoder_->Encode( + 0, interleaved_input.get(), audio_encoder_->sample_rate_hz() / 100, + data_length_ * 2, output, &enc_len_bytes, &output_timestamp_)); } - EXPECT_EQ(input_len_samples, enc_len_bytes); return static_cast(enc_len_bytes); } @@ -636,56 +639,22 @@ class AudioDecoderOpusTest : public AudioDecoderTest { frame_size_ = 480; data_length_ = 10 * frame_size_; decoder_ = new AudioDecoderOpus(kDecoderOpus); - assert(decoder_); - WebRtcOpus_EncoderCreate(&encoder_, 1); + AudioEncoderOpus::Config config; + config.frame_size_ms = static_cast(frame_size_) / 48; + audio_encoder_.reset(new AudioEncoderOpus(config)); } - - ~AudioDecoderOpusTest() { - WebRtcOpus_EncoderFree(encoder_); - } - - virtual void InitEncoder() {} - - virtual int EncodeFrame(const int16_t* input, size_t input_len_samples, - uint8_t* output) OVERRIDE { - int enc_len_bytes = WebRtcOpus_Encode(encoder_, const_cast(input), - static_cast(input_len_samples), - static_cast(data_length_), output); - EXPECT_GT(enc_len_bytes, 0); - return enc_len_bytes; - } - - OpusEncInst* encoder_; }; class AudioDecoderOpusStereoTest : public AudioDecoderOpusTest { protected: AudioDecoderOpusStereoTest() : AudioDecoderOpusTest() { channels_ = 2; - WebRtcOpus_EncoderFree(encoder_); delete decoder_; decoder_ = new AudioDecoderOpus(kDecoderOpus_2ch); - assert(decoder_); - WebRtcOpus_EncoderCreate(&encoder_, 2); - } - - virtual int EncodeFrame(const int16_t* input, size_t input_len_samples, - uint8_t* output) OVERRIDE { - // Create stereo by duplicating each sample in |input|. - const int input_stereo_samples = static_cast(input_len_samples) * 2; - scoped_ptr input_stereo(new int16_t[input_stereo_samples]); - test::InputAudioFile::DuplicateInterleaved( - input, input_len_samples, 2, input_stereo.get()); - - // Note that the input length is given as samples per channel. - int enc_len_bytes = - WebRtcOpus_Encode(encoder_, - input_stereo.get(), - static_cast(input_len_samples), - static_cast(data_length_), - output); - EXPECT_GT(enc_len_bytes, 0); - return enc_len_bytes; + AudioEncoderOpus::Config config; + config.frame_size_ms = static_cast(frame_size_) / 48; + config.num_channels = 2; + audio_encoder_.reset(new AudioEncoderOpus(config)); } };