Move CNG and RED management into the Rent-A-Codec

This leaves CodecOwner without a job, so we eliminate it.

BUG=webrtc:5028

Review URL: https://codereview.webrtc.org/1443653004

Cr-Commit-Position: refs/heads/master@{#10650}
This commit is contained in:
kwiberg
2015-11-16 04:49:54 -08:00
committed by Commit bot
parent 54e92326af
commit e155ae671c
11 changed files with 322 additions and 423 deletions

View File

@ -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",

View File

@ -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<CodecInst> CodecManager::GetCodecInst() const {
@ -340,7 +334,7 @@ rtc::Optional<CodecInst> 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<CodecInst>();
@ -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>(RentACodec::CngConfig{
CngPayloadType(sample_rate_hz), vad_mode_})
: rtc::Optional<RentACodec::CngConfig>();
auto red_pt = red_enabled_
? rtc::Optional<int>(RedPayloadType(sample_rate_hz))
: rtc::Optional<int>();
rent_a_codec_.RentEncoderStack(speech_encoder, cng_config, red_pt);
}
} // namespace acm2
} // namespace webrtc

View File

@ -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_;

View File

@ -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<AudioEncoder>* 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<AudioEncoder>* 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<AudioEncoder*>(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

View File

@ -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<AudioEncoder> cng_encoder_;
rtc::scoped_ptr<AudioEncoder> red_encoder_;
RTC_DISALLOW_COPY_AND_ASSIGN(CodecOwner);
};
} // namespace acm2
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_CODING_MAIN_ACM2_CODEC_OWNER_H_

View File

@ -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 <cstring>
#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<bool>(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<const int16_t>(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<const int16_t>(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

View File

@ -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<AudioEncoder> CreateEncoder(
return rtc::scoped_ptr<AudioEncoder>();
}
rtc::scoped_ptr<AudioEncoder> 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<AudioEncoder>(new AudioEncoderCopyRed(config));
#else
return rtc::scoped_ptr<AudioEncoder>();
#endif
}
rtc::scoped_ptr<AudioEncoder> 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<AudioEncoder>(new AudioEncoderCng(config));
}
rtc::scoped_ptr<AudioDecoder> 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<CngConfig> cng_config,
rtc::Optional<int> 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() {

View File

@ -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<CngConfig> cng_config,
rtc::Optional<int> 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<AudioEncoder> encoder_;
rtc::scoped_ptr<AudioEncoder> speech_encoder_;
rtc::scoped_ptr<AudioEncoder> cng_encoder_;
rtc::scoped_ptr<AudioEncoder> red_encoder_;
rtc::scoped_ptr<AudioDecoder> isac_decoder_;
AudioEncoder* encoder_stack_ = nullptr;
LockedIsacBandwidthInfo isac_bandwidth_info_;
RTC_DISALLOW_COPY_AND_ASSIGN(RentACodec);

View File

@ -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>(
RentACodec::CngConfig{kCngPt, VADNormal}),
rtc::Optional<int>());
}
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<bool>(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<RentACodec::CngConfig>(),
rtc::Optional<int>()));
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<const int16_t>(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<const int16_t>(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<RentACodec::CngConfig>(),
rtc::Optional<int>()));
// 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<RentACodec::CngConfig>(),
rtc::Optional<int>()));
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>(
RentACodec::CngConfig{17, VADNormal})
: rtc::Optional<RentACodec::CngConfig>();
auto red_pt = use_red ? rtc::Optional<int>(19) : rtc::Optional<int>();
speech_encoder.Mark("disabled");
RentACodec rac;
rac.RentEncoderStack(&speech_encoder, rtc::Optional<RentACodec::CngConfig>(),
rtc::Optional<int>());
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};

View File

@ -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',

View File

@ -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',