diff --git a/webrtc/modules/audio_coding/BUILD.gn b/webrtc/modules/audio_coding/BUILD.gn index 13f9a5d306..94717f1229 100644 --- a/webrtc/modules/audio_coding/BUILD.gn +++ b/webrtc/modules/audio_coding/BUILD.gn @@ -63,8 +63,6 @@ source_set("audio_coding") { "main/acm2/call_statistics.h", "main/acm2/codec_manager.cc", "main/acm2/codec_manager.h", - "main/acm2/codec_owner.cc", - "main/acm2/codec_owner.h", "main/acm2/initial_delay_manager.cc", "main/acm2/initial_delay_manager.h", "main/include/audio_coding_module.h", diff --git a/webrtc/modules/audio_coding/main/acm2/codec_manager.cc b/webrtc/modules/audio_coding/main/acm2/codec_manager.cc index ac21020dc2..47bbbde1d5 100644 --- a/webrtc/modules/audio_coding/main/acm2/codec_manager.cc +++ b/webrtc/modules/audio_coding/main/acm2/codec_manager.cc @@ -240,7 +240,7 @@ int CodecManager::RegisterEncoder(const CodecInst& send_codec) { // Check if the codec is already registered as send codec. bool new_codec = true; - if (codec_owner_.Encoder()) { + if (CurrentEncoder()) { auto new_codec_id = RentACodec::CodecIdByInst(send_codec_inst_); RTC_DCHECK(new_codec_id); auto old_codec_id = RentACodec::CodecIdFromIndex(codec_id); @@ -263,10 +263,8 @@ int CodecManager::RegisterEncoder(const CodecInst& send_codec) { AudioEncoder* enc = rent_a_codec_.RentEncoder(send_codec); if (!enc) return -1; - codec_owner_.SetEncoders( - enc, dtx_enabled_ ? CngPayloadType(send_codec.plfreq) : -1, - vad_mode_, red_enabled_ ? RedPayloadType(send_codec.plfreq) : -1); - RTC_DCHECK(codec_owner_.Encoder()); + RentEncoderStack(enc, send_codec.plfreq); + RTC_DCHECK(CurrentEncoder()); codec_fec_enabled_ = codec_fec_enabled_ && enc->SetFec(codec_fec_enabled_); @@ -282,10 +280,8 @@ int CodecManager::RegisterEncoder(const CodecInst& send_codec) { AudioEncoder* enc = rent_a_codec_.RentEncoder(send_codec); if (!enc) return -1; - codec_owner_.SetEncoders( - enc, dtx_enabled_ ? CngPayloadType(send_codec.plfreq) : -1, - vad_mode_, red_enabled_ ? RedPayloadType(send_codec.plfreq) : -1); - RTC_DCHECK(codec_owner_.Encoder()); + RentEncoderStack(enc, send_codec.plfreq); + RTC_DCHECK(CurrentEncoder()); } send_codec_inst_.plfreq = send_codec.plfreq; send_codec_inst_.pacsize = send_codec.pacsize; @@ -294,12 +290,12 @@ int CodecManager::RegisterEncoder(const CodecInst& send_codec) { // Check if a change in Rate is required. if (send_codec.rate != send_codec_inst_.rate) { - codec_owner_.Encoder()->SetTargetBitrate(send_codec.rate); + CurrentEncoder()->SetTargetBitrate(send_codec.rate); send_codec_inst_.rate = send_codec.rate; } codec_fec_enabled_ = - codec_fec_enabled_ && codec_owner_.Encoder()->SetFec(codec_fec_enabled_); + codec_fec_enabled_ && CurrentEncoder()->SetFec(codec_fec_enabled_); return 0; } @@ -328,11 +324,9 @@ void CodecManager::RegisterEncoder(AudioEncoder* external_speech_encoder) { const bool success = external_speech_encoder->SetFec(false); RTC_DCHECK(success); } - int cng_pt = dtx_enabled_ - ? CngPayloadType(external_speech_encoder->SampleRateHz()) - : -1; - int red_pt = red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1; - codec_owner_.SetEncoders(external_speech_encoder, cng_pt, vad_mode_, red_pt); + + RentEncoderStack(external_speech_encoder, + external_speech_encoder->SampleRateHz()); } rtc::Optional CodecManager::GetCodecInst() const { @@ -340,7 +334,7 @@ rtc::Optional CodecManager::GetCodecInst() const { WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, dummy_id, "SendCodec()"); - if (!codec_owner_.Encoder()) { + if (!CurrentEncoder()) { WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, dummy_id, "SendCodec Failed, no codec is registered"); return rtc::Optional(); @@ -361,11 +355,8 @@ bool CodecManager::SetCopyRed(bool enable) { } if (red_enabled_ != enable) { red_enabled_ = enable; - if (codec_owner_.Encoder()) { - int cng_pt = dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1; - int red_pt = red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1; - codec_owner_.ChangeCngAndRed(cng_pt, vad_mode_, red_pt); - } + if (CurrentEncoder()) + RentEncoderStack(rent_a_codec_.GetEncoder(), send_codec_inst_.plfreq); } return true; } @@ -377,7 +368,7 @@ int CodecManager::SetVAD(bool enable, ACMVADMode mode) { // Check that the send codec is mono. We don't support VAD/DTX for stereo // sending. - const auto* enc = codec_owner_.Encoder(); + auto* enc = rent_a_codec_.GetEncoder(); const bool stereo_send = enc ? (enc->NumChannels() != 1) : false; if (enable && stereo_send) { WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, 0, @@ -396,11 +387,8 @@ int CodecManager::SetVAD(bool enable, ACMVADMode mode) { if (dtx_enabled_ != enable || vad_mode_ != mode) { dtx_enabled_ = enable; vad_mode_ = mode; - if (enc) { - int cng_pt = dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1; - int red_pt = red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1; - codec_owner_.ChangeCngAndRed(cng_pt, vad_mode_, red_pt); - } + if (enc) + RentEncoderStack(enc, send_codec_inst_.plfreq); } return 0; } @@ -420,9 +408,9 @@ int CodecManager::SetCodecFEC(bool enable_codec_fec) { return -1; } - RTC_CHECK(codec_owner_.Encoder()); + RTC_CHECK(CurrentEncoder()); codec_fec_enabled_ = - codec_owner_.Encoder()->SetFec(enable_codec_fec) && enable_codec_fec; + CurrentEncoder()->SetFec(enable_codec_fec) && enable_codec_fec; return codec_fec_enabled_ == enable_codec_fec ? 0 : -1; } @@ -460,5 +448,17 @@ int CodecManager::RedPayloadType(int sample_rate_hz) const { } } +void CodecManager::RentEncoderStack(AudioEncoder* speech_encoder, + int sample_rate_hz) { + auto cng_config = + dtx_enabled_ ? rtc::Optional(RentACodec::CngConfig{ + CngPayloadType(sample_rate_hz), vad_mode_}) + : rtc::Optional(); + auto red_pt = red_enabled_ + ? rtc::Optional(RedPayloadType(sample_rate_hz)) + : rtc::Optional(); + rent_a_codec_.RentEncoderStack(speech_encoder, cng_config, red_pt); +} + } // namespace acm2 } // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/codec_manager.h b/webrtc/modules/audio_coding/main/acm2/codec_manager.h index 37018d345b..584b7c4dd9 100644 --- a/webrtc/modules/audio_coding/main/acm2/codec_manager.h +++ b/webrtc/modules/audio_coding/main/acm2/codec_manager.h @@ -15,7 +15,6 @@ #include "webrtc/base/optional.h" #include "webrtc/base/scoped_ptr.h" #include "webrtc/base/thread_checker.h" -#include "webrtc/modules/audio_coding/main/acm2/codec_owner.h" #include "webrtc/modules/audio_coding/main/acm2/rent_a_codec.h" #include "webrtc/modules/audio_coding/main/include/audio_coding_module_typedefs.h" #include "webrtc/common_types.h" @@ -57,15 +56,17 @@ class CodecManager final { bool codec_fec_enabled() const { return codec_fec_enabled_; } - AudioEncoder* CurrentEncoder() { return codec_owner_.Encoder(); } - const AudioEncoder* CurrentEncoder() const { return codec_owner_.Encoder(); } + AudioEncoder* CurrentEncoder() { return rent_a_codec_.GetEncoderStack(); } + const AudioEncoder* CurrentEncoder() const { + return rent_a_codec_.GetEncoderStack(); + } bool CurrentEncoderIsOpus() const { return encoder_is_opus_; } private: int CngPayloadType(int sample_rate_hz) const; - int RedPayloadType(int sample_rate_hz) const; + void RentEncoderStack(AudioEncoder* speech_encoder, int sample_rate_hz); rtc::ThreadChecker thread_checker_; uint8_t cng_nb_pltype_; @@ -78,7 +79,6 @@ class CodecManager final { CodecInst send_codec_inst_; bool red_enabled_; bool codec_fec_enabled_; - CodecOwner codec_owner_; RentACodec rent_a_codec_; bool encoder_is_opus_; diff --git a/webrtc/modules/audio_coding/main/acm2/codec_owner.cc b/webrtc/modules/audio_coding/main/acm2/codec_owner.cc deleted file mode 100644 index 6b9809f836..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/codec_owner.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2015 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/main/acm2/codec_owner.h" - -#include "webrtc/base/checks.h" -#include "webrtc/base/logging.h" -#include "webrtc/engine_configurations.h" -#include "webrtc/modules/audio_coding/codecs/cng/include/audio_encoder_cng.h" -#ifdef WEBRTC_CODEC_RED -#include "webrtc/modules/audio_coding/codecs/red/audio_encoder_copy_red.h" -#endif - -namespace webrtc { -namespace acm2 { - -CodecOwner::CodecOwner() : speech_encoder_(nullptr) { -} - -CodecOwner::~CodecOwner() = default; - -namespace { - -AudioEncoder* CreateRedEncoder(int red_payload_type, - AudioEncoder* encoder, - rtc::scoped_ptr* red_encoder) { -#ifdef WEBRTC_CODEC_RED - if (red_payload_type != -1) { - AudioEncoderCopyRed::Config config; - config.payload_type = red_payload_type; - config.speech_encoder = encoder; - red_encoder->reset(new AudioEncoderCopyRed(config)); - return red_encoder->get(); - } -#endif - - red_encoder->reset(); - return encoder; -} - -void CreateCngEncoder(int cng_payload_type, - ACMVADMode vad_mode, - AudioEncoder* encoder, - rtc::scoped_ptr* cng_encoder) { - if (cng_payload_type == -1) { - cng_encoder->reset(); - return; - } - AudioEncoderCng::Config config; - config.num_channels = encoder->NumChannels(); - config.payload_type = cng_payload_type; - config.speech_encoder = encoder; - switch (vad_mode) { - case VADNormal: - config.vad_mode = Vad::kVadNormal; - break; - case VADLowBitrate: - config.vad_mode = Vad::kVadLowBitrate; - break; - case VADAggr: - config.vad_mode = Vad::kVadAggressive; - break; - case VADVeryAggr: - config.vad_mode = Vad::kVadVeryAggressive; - break; - default: - FATAL(); - } - cng_encoder->reset(new AudioEncoderCng(config)); -} -} // namespace - -void CodecOwner::SetEncoders(AudioEncoder* external_speech_encoder, - int cng_payload_type, - ACMVADMode vad_mode, - int red_payload_type) { - speech_encoder_ = external_speech_encoder; - ChangeCngAndRed(cng_payload_type, vad_mode, red_payload_type); -} - -void CodecOwner::ChangeCngAndRed(int cng_payload_type, - ACMVADMode vad_mode, - int red_payload_type) { - RTC_DCHECK(speech_encoder_); - if (cng_payload_type != -1 || red_payload_type != -1) { - // The RED and CNG encoders need to be in sync with the speech encoder, so - // reset the latter to ensure its buffer is empty. - speech_encoder_->Reset(); - } - AudioEncoder* encoder = CreateRedEncoder( - red_payload_type, speech_encoder_, &red_encoder_); - CreateCngEncoder(cng_payload_type, vad_mode, encoder, &cng_encoder_); -} - -AudioEncoder* CodecOwner::Encoder() { - const auto& const_this = *this; - return const_cast(const_this.Encoder()); -} - -const AudioEncoder* CodecOwner::Encoder() const { - if (cng_encoder_) - return cng_encoder_.get(); - if (red_encoder_) - return red_encoder_.get(); - return speech_encoder_; -} - -} // namespace acm2 -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/codec_owner.h b/webrtc/modules/audio_coding/main/acm2/codec_owner.h deleted file mode 100644 index bfeaee6ddc..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/codec_owner.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2015 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_MAIN_ACM2_CODEC_OWNER_H_ -#define WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CODEC_OWNER_H_ - -#include "webrtc/base/constructormagic.h" -#include "webrtc/base/scoped_ptr.h" -#include "webrtc/common_types.h" -#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" -#include "webrtc/modules/audio_coding/codecs/audio_decoder.h" -#include "webrtc/modules/audio_coding/main/include/audio_coding_module_typedefs.h" - -namespace webrtc { -namespace acm2 { - -class CodecOwner { - public: - CodecOwner(); - ~CodecOwner(); - - void SetEncoders(AudioEncoder* external_speech_encoder, - int cng_payload_type, - ACMVADMode vad_mode, - int red_payload_type); - - void ChangeCngAndRed(int cng_payload_type, - ACMVADMode vad_mode, - int red_payload_type); - - AudioEncoder* Encoder(); - const AudioEncoder* Encoder() const; - - private: - AudioEncoder* speech_encoder_; - - // |cng_encoder_| and |red_encoder_| are valid iff CNG or RED, respectively, - // are active. - rtc::scoped_ptr cng_encoder_; - rtc::scoped_ptr red_encoder_; - - RTC_DISALLOW_COPY_AND_ASSIGN(CodecOwner); -}; - -} // namespace acm2 -} // namespace webrtc -#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CODEC_OWNER_H_ diff --git a/webrtc/modules/audio_coding/main/acm2/codec_owner_unittest.cc b/webrtc/modules/audio_coding/main/acm2/codec_owner_unittest.cc deleted file mode 100644 index 317c6bd7c0..0000000000 --- a/webrtc/modules/audio_coding/main/acm2/codec_owner_unittest.cc +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2015 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 - -#include "testing/gtest/include/gtest/gtest.h" -#include "webrtc/base/arraysize.h" -#include "webrtc/base/safe_conversions.h" -#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" -#include "webrtc/modules/audio_coding/main/acm2/codec_owner.h" -#include "webrtc/modules/audio_coding/main/acm2/rent_a_codec.h" - -namespace webrtc { -namespace acm2 { - -using ::testing::Return; -using ::testing::InSequence; - -namespace { -const int kDataLengthSamples = 80; -const int kPacketSizeSamples = 2 * kDataLengthSamples; -const int16_t kZeroData[kDataLengthSamples] = {0}; -const CodecInst kDefaultCodecInst = - {0, "pcmu", 8000, kPacketSizeSamples, 1, 64000}; -const int kCngPt = 13; -} // namespace - -class CodecOwnerTest : public ::testing::Test { - protected: - CodecOwnerTest() : timestamp_(0) {} - - void CreateCodec() { - AudioEncoder *enc = rent_a_codec_.RentEncoder(kDefaultCodecInst); - ASSERT_TRUE(enc); - codec_owner_.SetEncoders(enc, kCngPt, VADNormal, -1); - } - - void EncodeAndVerify(size_t expected_out_length, - uint32_t expected_timestamp, - int expected_payload_type, - int expected_send_even_if_empty) { - uint8_t out[kPacketSizeSamples]; - AudioEncoder::EncodedInfo encoded_info; - encoded_info = codec_owner_.Encoder()->Encode(timestamp_, kZeroData, - kPacketSizeSamples, out); - timestamp_ += kDataLengthSamples; - EXPECT_TRUE(encoded_info.redundant.empty()); - EXPECT_EQ(expected_out_length, encoded_info.encoded_bytes); - EXPECT_EQ(expected_timestamp, encoded_info.encoded_timestamp); - if (expected_payload_type >= 0) - EXPECT_EQ(expected_payload_type, encoded_info.payload_type); - if (expected_send_even_if_empty >= 0) - EXPECT_EQ(static_cast(expected_send_even_if_empty), - encoded_info.send_even_if_empty); - } - - // Verify that the speech encoder's Reset method is called when CNG or RED - // (or both) are switched on, but not when they're switched off. - void TestCngAndRedResetSpeechEncoder(bool use_cng, bool use_red) { - MockAudioEncoder speech_encoder; - EXPECT_CALL(speech_encoder, NumChannels()) - .WillRepeatedly(Return(1)); - EXPECT_CALL(speech_encoder, Max10MsFramesInAPacket()) - .WillRepeatedly(Return(2)); - EXPECT_CALL(speech_encoder, SampleRateHz()) - .WillRepeatedly(Return(8000)); - { - InSequence s; - EXPECT_CALL(speech_encoder, Mark("start off")); - EXPECT_CALL(speech_encoder, Mark("switch on")); - if (use_cng || use_red) - EXPECT_CALL(speech_encoder, Reset()); - EXPECT_CALL(speech_encoder, Mark("start on")); - if (use_cng || use_red) - EXPECT_CALL(speech_encoder, Reset()); - EXPECT_CALL(speech_encoder, Mark("switch off")); - EXPECT_CALL(speech_encoder, Die()); - } - - int cng_pt = use_cng ? 17 : -1; - int red_pt = use_red ? 19 : -1; - speech_encoder.Mark("start off"); - codec_owner_.SetEncoders(&speech_encoder, -1, VADNormal, -1); - speech_encoder.Mark("switch on"); - codec_owner_.ChangeCngAndRed(cng_pt, VADNormal, red_pt); - speech_encoder.Mark("start on"); - codec_owner_.SetEncoders(&speech_encoder, cng_pt, VADNormal, red_pt); - speech_encoder.Mark("switch off"); - codec_owner_.ChangeCngAndRed(-1, VADNormal, -1); - } - - CodecOwner codec_owner_; - RentACodec rent_a_codec_; - uint32_t timestamp_; -}; - -// This test verifies that CNG frames are delivered as expected. Since the frame -// size is set to 20 ms, we expect the first encode call to produce no output -// (which is signaled as 0 bytes output of type kNoEncoding). The next encode -// call should produce one SID frame of 9 bytes. The third call should not -// result in any output (just like the first one). The fourth and final encode -// call should produce an "empty frame", which is like no output, but with -// AudioEncoder::EncodedInfo::send_even_if_empty set to true. (The reason to -// produce an empty frame is to drive sending of DTMF packets in the RTP/RTCP -// module.) -TEST_F(CodecOwnerTest, VerifyCngFrames) { - CreateCodec(); - uint32_t expected_timestamp = timestamp_; - // Verify no frame. - { - SCOPED_TRACE("First encoding"); - EncodeAndVerify(0, expected_timestamp, -1, -1); - } - - // Verify SID frame delivered. - { - SCOPED_TRACE("Second encoding"); - EncodeAndVerify(9, expected_timestamp, kCngPt, 1); - } - - // Verify no frame. - { - SCOPED_TRACE("Third encoding"); - EncodeAndVerify(0, expected_timestamp, -1, -1); - } - - // Verify NoEncoding. - expected_timestamp += 2 * kDataLengthSamples; - { - SCOPED_TRACE("Fourth encoding"); - EncodeAndVerify(0, expected_timestamp, kCngPt, 1); - } -} - -TEST_F(CodecOwnerTest, ExternalEncoder) { - MockAudioEncoder external_encoder; - codec_owner_.SetEncoders(&external_encoder, -1, VADNormal, -1); - const int kSampleRateHz = 8000; - const int kPacketSizeSamples = kSampleRateHz / 100; - int16_t audio[kPacketSizeSamples] = {0}; - uint8_t encoded[kPacketSizeSamples]; - AudioEncoder::EncodedInfo info; - EXPECT_CALL(external_encoder, SampleRateHz()) - .WillRepeatedly(Return(kSampleRateHz)); - EXPECT_CALL(external_encoder, NumChannels()).WillRepeatedly(Return(1)); - - { - InSequence s; - info.encoded_timestamp = 0; - EXPECT_CALL(external_encoder, - EncodeInternal(0, rtc::ArrayView(audio), - arraysize(encoded), encoded)) - .WillOnce(Return(info)); - EXPECT_CALL(external_encoder, Mark("A")); - EXPECT_CALL(external_encoder, Mark("B")); - info.encoded_timestamp = 2; - EXPECT_CALL(external_encoder, - EncodeInternal(2, rtc::ArrayView(audio), - arraysize(encoded), encoded)) - .WillOnce(Return(info)); - EXPECT_CALL(external_encoder, Die()); - } - - info = codec_owner_.Encoder()->Encode(0, audio, arraysize(encoded), encoded); - EXPECT_EQ(0u, info.encoded_timestamp); - external_encoder.Mark("A"); - - // Change to internal encoder. - CodecInst codec_inst = kDefaultCodecInst; - codec_inst.pacsize = kPacketSizeSamples; - AudioEncoder* enc = rent_a_codec_.RentEncoder(codec_inst); - ASSERT_TRUE(enc); - codec_owner_.SetEncoders(enc, -1, VADNormal, -1); - // Don't expect any more calls to the external encoder. - info = codec_owner_.Encoder()->Encode(1, audio, arraysize(encoded), encoded); - external_encoder.Mark("B"); - - // Change back to external encoder again. - codec_owner_.SetEncoders(&external_encoder, -1, VADNormal, -1); - info = codec_owner_.Encoder()->Encode(2, audio, arraysize(encoded), encoded); - EXPECT_EQ(2u, info.encoded_timestamp); -} - -TEST_F(CodecOwnerTest, CngResetsSpeechEncoder) { - TestCngAndRedResetSpeechEncoder(true, false); -} - -TEST_F(CodecOwnerTest, RedResetsSpeechEncoder) { - TestCngAndRedResetSpeechEncoder(false, true); -} - -TEST_F(CodecOwnerTest, CngAndRedResetsSpeechEncoder) { - TestCngAndRedResetSpeechEncoder(true, true); -} - -TEST_F(CodecOwnerTest, NoCngAndRedNoSpeechEncoderReset) { - TestCngAndRedResetSpeechEncoder(false, false); -} - -} // namespace acm2 -} // namespace webrtc diff --git a/webrtc/modules/audio_coding/main/acm2/rent_a_codec.cc b/webrtc/modules/audio_coding/main/acm2/rent_a_codec.cc index 2ee953c1dd..e3310ad94a 100644 --- a/webrtc/modules/audio_coding/main/acm2/rent_a_codec.cc +++ b/webrtc/modules/audio_coding/main/acm2/rent_a_codec.cc @@ -11,6 +11,7 @@ #include "webrtc/modules/audio_coding/main/acm2/rent_a_codec.h" #include "webrtc/base/logging.h" +#include "webrtc/modules/audio_coding/codecs/cng/include/audio_encoder_cng.h" #include "webrtc/modules/audio_coding/codecs/g711/include/audio_encoder_pcm.h" #ifdef WEBRTC_CODEC_G722 #include "webrtc/modules/audio_coding/codecs/g722/include/audio_encoder_g722.h" @@ -30,6 +31,9 @@ #include "webrtc/modules/audio_coding/codecs/opus/include/audio_encoder_opus.h" #endif #include "webrtc/modules/audio_coding/codecs/pcm16b/include/audio_encoder_pcm16b.h" +#ifdef WEBRTC_CODEC_RED +#include "webrtc/modules/audio_coding/codecs/red/audio_encoder_copy_red.h" +#endif #include "webrtc/modules/audio_coding/main/acm2/acm_codec_database.h" #include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h" @@ -140,6 +144,44 @@ rtc::scoped_ptr CreateEncoder( return rtc::scoped_ptr(); } +rtc::scoped_ptr CreateRedEncoder(AudioEncoder* encoder, + int red_payload_type) { +#ifdef WEBRTC_CODEC_RED + AudioEncoderCopyRed::Config config; + config.payload_type = red_payload_type; + config.speech_encoder = encoder; + return rtc::scoped_ptr(new AudioEncoderCopyRed(config)); +#else + return rtc::scoped_ptr(); +#endif +} + +rtc::scoped_ptr CreateCngEncoder( + AudioEncoder* encoder, + RentACodec::CngConfig cng_config) { + AudioEncoderCng::Config config; + config.num_channels = encoder->NumChannels(); + config.payload_type = cng_config.cng_payload_type; + config.speech_encoder = encoder; + switch (cng_config.vad_mode) { + case VADNormal: + config.vad_mode = Vad::kVadNormal; + break; + case VADLowBitrate: + config.vad_mode = Vad::kVadLowBitrate; + break; + case VADAggr: + config.vad_mode = Vad::kVadAggressive; + break; + case VADVeryAggr: + config.vad_mode = Vad::kVadVeryAggressive; + break; + default: + FATAL(); + } + return rtc::scoped_ptr(new AudioEncoderCng(config)); +} + rtc::scoped_ptr CreateIsacDecoder( LockedIsacBandwidthInfo* bwinfo) { #if defined(WEBRTC_CODEC_ISACFX) @@ -162,8 +204,35 @@ AudioEncoder* RentACodec::RentEncoder(const CodecInst& codec_inst) { CreateEncoder(codec_inst, &isac_bandwidth_info_); if (!enc) return nullptr; - encoder_ = enc.Pass(); - return encoder_.get(); + speech_encoder_ = enc.Pass(); + return speech_encoder_.get(); +} + +AudioEncoder* RentACodec::RentEncoderStack( + AudioEncoder* speech_encoder, + rtc::Optional cng_config, + rtc::Optional red_payload_type) { + RTC_DCHECK(speech_encoder); + if (cng_config || red_payload_type) { + // The RED and CNG encoders need to be in sync with the speech encoder, so + // reset the latter to ensure its buffer is empty. + speech_encoder->Reset(); + } + encoder_stack_ = speech_encoder; + if (red_payload_type) { + red_encoder_ = CreateRedEncoder(encoder_stack_, *red_payload_type); + if (red_encoder_) + encoder_stack_ = red_encoder_.get(); + } else { + red_encoder_.reset(); + } + if (cng_config) { + cng_encoder_ = CreateCngEncoder(encoder_stack_, *cng_config); + encoder_stack_ = cng_encoder_.get(); + } else { + cng_encoder_.reset(); + } + return encoder_stack_; } AudioDecoder* RentACodec::RentIsacDecoder() { diff --git a/webrtc/modules/audio_coding/main/acm2/rent_a_codec.h b/webrtc/modules/audio_coding/main/acm2/rent_a_codec.h index 4daac6ad7e..45715607aa 100644 --- a/webrtc/modules/audio_coding/main/acm2/rent_a_codec.h +++ b/webrtc/modules/audio_coding/main/acm2/rent_a_codec.h @@ -17,9 +17,10 @@ #include "webrtc/base/constructormagic.h" #include "webrtc/base/optional.h" #include "webrtc/base/scoped_ptr.h" -#include "webrtc/typedefs.h" -#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" #include "webrtc/modules/audio_coding/codecs/audio_decoder.h" +#include "webrtc/modules/audio_coding/codecs/audio_encoder.h" +#include "webrtc/modules/audio_coding/main/include/audio_coding_module_typedefs.h" +#include "webrtc/typedefs.h" #if defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX) #include "webrtc/modules/audio_coding/codecs/isac/locked_bandwidth_info.h" @@ -188,14 +189,35 @@ class RentACodec { // successful call to this function, or until the Rent-A-Codec is destroyed. AudioEncoder* RentEncoder(const CodecInst& codec_inst); + // Creates and returns an audio encoder stack where the given speech encoder + // is augmented with the specified CNG/VAD and RED encoders. Leave either + // optional field blank if you don't want the corresponding gizmo in the + // stack. The returned encoder is live until the next successful call to this + // function, or until the Rent-A-Codec is destroyed. + struct CngConfig { + int cng_payload_type; + ACMVADMode vad_mode; + }; + AudioEncoder* RentEncoderStack(AudioEncoder* speech_encoder, + rtc::Optional cng_config, + rtc::Optional red_payload_type); + + // Get the last return values of RentEncoder and RentEncoderStack, or null if + // they haven't been called. + AudioEncoder* GetEncoder() const { return speech_encoder_.get(); } + AudioEncoder* GetEncoderStack() const { return encoder_stack_; } + // Creates and returns an iSAC decoder, which will remain live until the // Rent-A-Codec is destroyed. Subsequent calls will simply return the same // object. AudioDecoder* RentIsacDecoder(); private: - rtc::scoped_ptr encoder_; + rtc::scoped_ptr speech_encoder_; + rtc::scoped_ptr cng_encoder_; + rtc::scoped_ptr red_encoder_; rtc::scoped_ptr isac_decoder_; + AudioEncoder* encoder_stack_ = nullptr; LockedIsacBandwidthInfo isac_bandwidth_info_; RTC_DISALLOW_COPY_AND_ASSIGN(RentACodec); diff --git a/webrtc/modules/audio_coding/main/acm2/rent_a_codec_unittest.cc b/webrtc/modules/audio_coding/main/acm2/rent_a_codec_unittest.cc index 01ba02457a..6938051644 100644 --- a/webrtc/modules/audio_coding/main/acm2/rent_a_codec_unittest.cc +++ b/webrtc/modules/audio_coding/main/acm2/rent_a_codec_unittest.cc @@ -9,11 +9,202 @@ */ #include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/base/arraysize.h" +#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h" #include "webrtc/modules/audio_coding/main/acm2/rent_a_codec.h" namespace webrtc { namespace acm2 { +using ::testing::Return; + +namespace { +const int kDataLengthSamples = 80; +const int kPacketSizeSamples = 2 * kDataLengthSamples; +const int16_t kZeroData[kDataLengthSamples] = {0}; +const CodecInst kDefaultCodecInst = {0, "pcmu", 8000, kPacketSizeSamples, + 1, 64000}; +const int kCngPt = 13; +} // namespace + +class RentACodecTestF : public ::testing::Test { + protected: + void CreateCodec() { + speech_encoder_ = rent_a_codec_.RentEncoder(kDefaultCodecInst); + ASSERT_TRUE(speech_encoder_); + encoder_ = rent_a_codec_.RentEncoderStack( + speech_encoder_, rtc::Optional( + RentACodec::CngConfig{kCngPt, VADNormal}), + rtc::Optional()); + } + + void EncodeAndVerify(size_t expected_out_length, + uint32_t expected_timestamp, + int expected_payload_type, + int expected_send_even_if_empty) { + uint8_t out[kPacketSizeSamples]; + AudioEncoder::EncodedInfo encoded_info; + encoded_info = + encoder_->Encode(timestamp_, kZeroData, kPacketSizeSamples, out); + timestamp_ += kDataLengthSamples; + EXPECT_TRUE(encoded_info.redundant.empty()); + EXPECT_EQ(expected_out_length, encoded_info.encoded_bytes); + EXPECT_EQ(expected_timestamp, encoded_info.encoded_timestamp); + if (expected_payload_type >= 0) + EXPECT_EQ(expected_payload_type, encoded_info.payload_type); + if (expected_send_even_if_empty >= 0) + EXPECT_EQ(static_cast(expected_send_even_if_empty), + encoded_info.send_even_if_empty); + } + + RentACodec rent_a_codec_; + AudioEncoder* speech_encoder_ = nullptr; + AudioEncoder* encoder_ = nullptr; + uint32_t timestamp_ = 0; +}; + +// This test verifies that CNG frames are delivered as expected. Since the frame +// size is set to 20 ms, we expect the first encode call to produce no output +// (which is signaled as 0 bytes output of type kNoEncoding). The next encode +// call should produce one SID frame of 9 bytes. The third call should not +// result in any output (just like the first one). The fourth and final encode +// call should produce an "empty frame", which is like no output, but with +// AudioEncoder::EncodedInfo::send_even_if_empty set to true. (The reason to +// produce an empty frame is to drive sending of DTMF packets in the RTP/RTCP +// module.) +TEST_F(RentACodecTestF, VerifyCngFrames) { + CreateCodec(); + uint32_t expected_timestamp = timestamp_; + // Verify no frame. + { + SCOPED_TRACE("First encoding"); + EncodeAndVerify(0, expected_timestamp, -1, -1); + } + + // Verify SID frame delivered. + { + SCOPED_TRACE("Second encoding"); + EncodeAndVerify(9, expected_timestamp, kCngPt, 1); + } + + // Verify no frame. + { + SCOPED_TRACE("Third encoding"); + EncodeAndVerify(0, expected_timestamp, -1, -1); + } + + // Verify NoEncoding. + expected_timestamp += 2 * kDataLengthSamples; + { + SCOPED_TRACE("Fourth encoding"); + EncodeAndVerify(0, expected_timestamp, kCngPt, 1); + } +} + +TEST(RentACodecTest, ExternalEncoder) { + MockAudioEncoder external_encoder; + RentACodec rac; + EXPECT_EQ(&external_encoder, + rac.RentEncoderStack(&external_encoder, + rtc::Optional(), + rtc::Optional())); + const int kSampleRateHz = 8000; + const int kPacketSizeSamples = kSampleRateHz / 100; + int16_t audio[kPacketSizeSamples] = {0}; + uint8_t encoded[kPacketSizeSamples]; + AudioEncoder::EncodedInfo info; + EXPECT_CALL(external_encoder, SampleRateHz()) + .WillRepeatedly(Return(kSampleRateHz)); + EXPECT_CALL(external_encoder, NumChannels()).WillRepeatedly(Return(1)); + + { + ::testing::InSequence s; + info.encoded_timestamp = 0; + EXPECT_CALL(external_encoder, + EncodeInternal(0, rtc::ArrayView(audio), + arraysize(encoded), encoded)) + .WillOnce(Return(info)); + EXPECT_CALL(external_encoder, Mark("A")); + EXPECT_CALL(external_encoder, Mark("B")); + info.encoded_timestamp = 2; + EXPECT_CALL(external_encoder, + EncodeInternal(2, rtc::ArrayView(audio), + arraysize(encoded), encoded)) + .WillOnce(Return(info)); + EXPECT_CALL(external_encoder, Die()); + } + + info = rac.GetEncoderStack()->Encode(0, audio, arraysize(encoded), encoded); + EXPECT_EQ(0u, info.encoded_timestamp); + external_encoder.Mark("A"); + + // Change to internal encoder. + CodecInst codec_inst = kDefaultCodecInst; + codec_inst.pacsize = kPacketSizeSamples; + AudioEncoder* enc = rac.RentEncoder(codec_inst); + ASSERT_TRUE(enc); + EXPECT_EQ(enc, + rac.RentEncoderStack(enc, rtc::Optional(), + rtc::Optional())); + + // Don't expect any more calls to the external encoder. + info = rac.GetEncoderStack()->Encode(1, audio, arraysize(encoded), encoded); + external_encoder.Mark("B"); + + // Change back to external encoder again. + EXPECT_EQ(&external_encoder, + rac.RentEncoderStack(&external_encoder, + rtc::Optional(), + rtc::Optional())); + info = rac.GetEncoderStack()->Encode(2, audio, arraysize(encoded), encoded); + EXPECT_EQ(2u, info.encoded_timestamp); +} + +// Verify that the speech encoder's Reset method is called when CNG or RED +// (or both) are switched on, but not when they're switched off. +void TestCngAndRedResetSpeechEncoder(bool use_cng, bool use_red) { + MockAudioEncoder speech_encoder; + EXPECT_CALL(speech_encoder, NumChannels()).WillRepeatedly(Return(1)); + EXPECT_CALL(speech_encoder, Max10MsFramesInAPacket()) + .WillRepeatedly(Return(2)); + EXPECT_CALL(speech_encoder, SampleRateHz()).WillRepeatedly(Return(8000)); + { + ::testing::InSequence s; + EXPECT_CALL(speech_encoder, Mark("disabled")); + EXPECT_CALL(speech_encoder, Mark("enabled")); + if (use_cng || use_red) + EXPECT_CALL(speech_encoder, Reset()); + EXPECT_CALL(speech_encoder, Die()); + } + + auto cng_cfg = use_cng ? rtc::Optional( + RentACodec::CngConfig{17, VADNormal}) + : rtc::Optional(); + auto red_pt = use_red ? rtc::Optional(19) : rtc::Optional(); + speech_encoder.Mark("disabled"); + RentACodec rac; + rac.RentEncoderStack(&speech_encoder, rtc::Optional(), + rtc::Optional()); + speech_encoder.Mark("enabled"); + rac.RentEncoderStack(&speech_encoder, cng_cfg, red_pt); +} + +TEST(RentACodecTest, CngResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(true, false); +} + +TEST(RentACodecTest, RedResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(false, true); +} + +TEST(RentACodecTest, CngAndRedResetsSpeechEncoder) { + TestCngAndRedResetSpeechEncoder(true, true); +} + +TEST(RentACodecTest, NoCngAndRedNoSpeechEncoderReset) { + TestCngAndRedResetSpeechEncoder(false, false); +} + TEST(RentACodecTest, RentEncoderError) { const CodecInst codec_inst = { 0, "Robert'); DROP TABLE Students;", 8000, 160, 1, 64000}; diff --git a/webrtc/modules/audio_coding/main/audio_coding_module.gypi b/webrtc/modules/audio_coding/main/audio_coding_module.gypi index 09ac0a815e..061ffaa212 100644 --- a/webrtc/modules/audio_coding/main/audio_coding_module.gypi +++ b/webrtc/modules/audio_coding/main/audio_coding_module.gypi @@ -109,8 +109,6 @@ 'acm2/call_statistics.h', 'acm2/codec_manager.cc', 'acm2/codec_manager.h', - 'acm2/codec_owner.cc', - 'acm2/codec_owner.h', 'acm2/initial_delay_manager.cc', 'acm2/initial_delay_manager.h', 'include/audio_coding_module.h', diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index 22dbff2f04..7cac61971d 100644 --- a/webrtc/modules/modules.gyp +++ b/webrtc/modules/modules.gyp @@ -102,7 +102,6 @@ 'audio_coding/main/acm2/audio_coding_module_unittest_oldapi.cc', 'audio_coding/main/acm2/call_statistics_unittest.cc', 'audio_coding/main/acm2/codec_manager_unittest.cc', - 'audio_coding/main/acm2/codec_owner_unittest.cc', 'audio_coding/main/acm2/initial_delay_manager_unittest.cc', 'audio_coding/main/acm2/rent_a_codec_unittest.cc', 'audio_coding/codecs/cng/cng_unittest.cc',