Add support for external encoders in ACM

Also introduce tests using external (mock) encoders, both for
CodecOwner and for AudioCodingModule.

Support for external decoders is still missing.

COAUTHOR=henrik.lundin@webrtc.org
BUG=4474
R=jmarusic@webrtc.org, minyue@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/49939004

Cr-Commit-Position: refs/heads/master@{#9206}
This commit is contained in:
Karl Wiberg
2015-05-18 14:52:29 +02:00
parent ea14f0ac11
commit 7e0c7d49ea
12 changed files with 274 additions and 47 deletions

View File

@@ -36,6 +36,31 @@ class MockAudioEncoder : public AudioEncoder {
uint8_t* encoded));
};
class MockAudioEncoderMutable : public AudioEncoderMutable {
public:
MOCK_CONST_METHOD0(SampleRateHz, int());
MOCK_CONST_METHOD0(NumChannels, int());
MOCK_CONST_METHOD0(MaxEncodedBytes, size_t());
MOCK_CONST_METHOD0(Num10MsFramesInNextPacket, int());
MOCK_CONST_METHOD0(Max10MsFramesInAPacket, int());
MOCK_METHOD1(SetTargetBitrate, void(int));
MOCK_METHOD1(SetProjectedPacketLossRate, void(double));
// Note, we explicitly chose not to create a mock for the Encode method.
MOCK_METHOD4(EncodeInternal,
EncodedInfo(uint32_t timestamp,
const int16_t* audio,
size_t max_encoded_bytes,
uint8_t* encoded));
MOCK_METHOD0(Reset, void());
MOCK_METHOD1(SetFec, bool(bool enable));
MOCK_METHOD1(SetDtx, bool(bool enable));
MOCK_METHOD1(SetApplication, bool(Application application));
MOCK_METHOD1(SetMaxPayloadSize, void(int max_payload_size_bytes));
MOCK_METHOD1(SetMaxRate, void(int max_rate_bps));
MOCK_METHOD1(SetMaxPlaybackRate, bool(int frequency_hz));
};
} // namespace webrtc
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_MOCK_MOCK_AUDIO_ENCODER_H_

View File

@@ -16,6 +16,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "webrtc/base/checks.h"
#include "webrtc/modules/audio_coding/codecs/audio_encoder.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h"
#include "webrtc/modules/audio_coding/neteq/tools/packet.h"
@@ -50,18 +51,27 @@ bool AcmSendTestOldApi::RegisterCodec(const char* payload_name,
int channels,
int payload_type,
int frame_size_samples) {
CHECK_EQ(0,
AudioCodingModule::Codec(
payload_name, &codec_, sampling_freq_hz, channels));
codec_.pltype = payload_type;
codec_.pacsize = frame_size_samples;
codec_registered_ = (acm_->RegisterSendCodec(codec_) == 0);
CodecInst codec;
CHECK_EQ(0, AudioCodingModule::Codec(payload_name, &codec, sampling_freq_hz,
channels));
codec.pltype = payload_type;
codec.pacsize = frame_size_samples;
codec_registered_ = (acm_->RegisterSendCodec(codec) == 0);
input_frame_.num_channels_ = channels;
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
AudioFrame::kMaxDataSizeSamples);
return codec_registered_;
}
bool AcmSendTestOldApi::RegisterExternalCodec(
AudioEncoderMutable* external_speech_encoder) {
acm_->RegisterExternalSendCodec(external_speech_encoder);
input_frame_.num_channels_ = external_speech_encoder->NumChannels();
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
AudioFrame::kMaxDataSizeSamples);
return codec_registered_ = true;
}
Packet* AcmSendTestOldApi::NextPacket() {
assert(codec_registered_);
if (filter_.test(static_cast<size_t>(payload_type_))) {

View File

@@ -20,6 +20,7 @@
#include "webrtc/system_wrappers/interface/clock.h"
namespace webrtc {
class AudioEncoderMutable;
namespace test {
class InputAudioFile;
@@ -40,6 +41,9 @@ class AcmSendTestOldApi : public AudioPacketizationCallback,
int payload_type,
int frame_size_samples);
// Registers an external send codec. Returns true on success, false otherwise.
bool RegisterExternalCodec(AudioEncoderMutable* external_speech_encoder);
// Returns the next encoded packet. Returns NULL if the test duration was
// exceeded. Ownership of the packet is handed over to the caller.
// Inherited from PacketSource.
@@ -69,7 +73,6 @@ class AcmSendTestOldApi : public AudioPacketizationCallback,
int source_rate_hz_;
const int input_block_size_samples_;
AudioFrame input_frame_;
CodecInst codec_;
bool codec_registered_;
int test_duration_ms_;
// The following member variables are set whenever SendData() is called.

View File

@@ -234,13 +234,19 @@ int AudioCodingModuleImpl::ResetEncoder() {
// Can be called multiple times for Codec, CNG, RED.
int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) {
CriticalSectionScoped lock(acm_crit_sect_);
return codec_manager_.RegisterSendCodec(send_codec);
return codec_manager_.RegisterEncoder(send_codec);
}
void AudioCodingModuleImpl::RegisterExternalSendCodec(
AudioEncoderMutable* external_speech_encoder) {
CriticalSectionScoped lock(acm_crit_sect_);
codec_manager_.RegisterEncoder(external_speech_encoder);
}
// Get current send codec.
int AudioCodingModuleImpl::SendCodec(CodecInst* current_codec) const {
CriticalSectionScoped lock(acm_crit_sect_);
return codec_manager_.SendCodec(current_codec);
return codec_manager_.GetCodecInst(current_codec);
}
// Get current send frequency.

View File

@@ -48,6 +48,9 @@ class AudioCodingModuleImpl : public AudioCodingModule {
// Can be called multiple times for Codec, CNG, RED.
int RegisterSendCodec(const CodecInst& send_codec) override;
void RegisterExternalSendCodec(
AudioEncoderMutable* external_speech_encoder) override;
// Get current send codec.
int SendCodec(CodecInst* current_codec) const override;

View File

@@ -15,6 +15,9 @@
#include "webrtc/base/md5digest.h"
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/base/thread_annotations.h"
#include "webrtc/modules/audio_coding/codecs/audio_encoder.h"
#include "webrtc/modules/audio_coding/codecs/g711/include/audio_encoder_pcm.h"
#include "webrtc/modules/audio_coding/codecs/mock/mock_audio_encoder.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_receive_test_oldapi.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_send_test_oldapi.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
@@ -35,6 +38,10 @@
#include "webrtc/test/testsupport/fileutils.h"
#include "webrtc/test/testsupport/gtest_disable.h"
using ::testing::AtLeast;
using ::testing::Invoke;
using ::testing::_;
namespace webrtc {
namespace {
@@ -849,6 +856,15 @@ class AcmSenderBitExactnessOldApi : public ::testing::Test,
frame_size_samples);
}
bool RegisterExternalSendCodec(AudioEncoderMutable* external_speech_encoder,
int payload_type) {
payload_type_ = payload_type;
frame_size_rtp_timestamps_ =
external_speech_encoder->Num10MsFramesInNextPacket() *
external_speech_encoder->RtpTimestampRateHz() / 100;
return send_test_->RegisterExternalCodec(external_speech_encoder);
}
// Runs the test. SetUpSender() and RegisterSendCodec() must have been called
// before calling this method.
void Run(const std::string& audio_checksum_ref,
@@ -942,6 +958,13 @@ class AcmSenderBitExactnessOldApi : public ::testing::Test,
codec_frame_size_rtp_timestamps));
}
void SetUpTestExternalEncoder(AudioEncoderMutable* external_speech_encoder,
int payload_type) {
ASSERT_TRUE(SetUpSender());
ASSERT_TRUE(
RegisterExternalSendCodec(external_speech_encoder, payload_type));
}
rtc::scoped_ptr<test::AcmSendTestOldApi> send_test_;
rtc::scoped_ptr<test::InputAudioFile> audio_source_;
uint32_t frame_size_rtp_timestamps_;
@@ -1355,6 +1378,39 @@ TEST_F(AcmChangeBitRateOldApi, Pcm16_8khz_10ms_32kbps) {
Run(32000, 64000, 64000);
}
TEST_F(AcmSenderBitExactnessOldApi, External_Pcmu_20ms) {
CodecInst codec_inst;
codec_inst.channels = 1;
codec_inst.pacsize = 160;
codec_inst.pltype = 0;
AudioEncoderMutablePcmU encoder(codec_inst);
MockAudioEncoderMutable mock_encoder;
// Set expectations on the mock encoder and also delegate the calls to the
// real encoder.
EXPECT_CALL(mock_encoder, Num10MsFramesInNextPacket())
.Times(AtLeast(1))
.WillRepeatedly(Invoke(
&encoder, &AudioEncoderMutablePcmU::Num10MsFramesInNextPacket));
EXPECT_CALL(mock_encoder, Max10MsFramesInAPacket())
.Times(AtLeast(1))
.WillRepeatedly(
Invoke(&encoder, &AudioEncoderMutablePcmU::Max10MsFramesInAPacket));
EXPECT_CALL(mock_encoder, SampleRateHz())
.Times(AtLeast(1))
.WillRepeatedly(Invoke(&encoder, &AudioEncoderMutablePcmU::SampleRateHz));
EXPECT_CALL(mock_encoder, NumChannels())
.Times(AtLeast(1))
.WillRepeatedly(Invoke(&encoder, &AudioEncoderMutablePcmU::NumChannels));
EXPECT_CALL(mock_encoder, EncodeInternal(_, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(
Invoke(&encoder, &AudioEncoderMutablePcmU::EncodeInternal));
ASSERT_NO_FATAL_FAILURE(
SetUpTestExternalEncoder(&mock_encoder, codec_inst.pltype));
Run("81a9d4c0bb72e9becc43aef124c981e9", "8f9b8750bd80fe26b6cbf6659b89f0f9",
50, test::AcmReceiveTestOldApi::kMonoOutput);
}
// This test fixture is implemented to run ACM and change the desired output
// frequency during the call. The input packets are simply PCM16b-wb encoded
// payloads with a constant value of |kSampleValue|. The test fixture itself

View File

@@ -187,7 +187,7 @@ CodecManager::CodecManager()
CodecManager::~CodecManager() = default;
int CodecManager::RegisterSendCodec(const CodecInst& send_codec) {
int CodecManager::RegisterEncoder(const CodecInst& send_codec) {
DCHECK(thread_checker_.CalledOnValidThread());
int codec_id = IsValidSendCodec(send_codec, true);
@@ -321,7 +321,32 @@ int CodecManager::RegisterSendCodec(const CodecInst& send_codec) {
return 0;
}
int CodecManager::SendCodec(CodecInst* current_codec) const {
void CodecManager::RegisterEncoder(
AudioEncoderMutable* external_speech_encoder) {
// Make up a CodecInst.
send_codec_inst_.channels = external_speech_encoder->NumChannels();
send_codec_inst_.plfreq = external_speech_encoder->SampleRateHz();
send_codec_inst_.pacsize =
rtc::CheckedDivExact(external_speech_encoder->Max10MsFramesInAPacket() *
send_codec_inst_.plfreq,
100);
send_codec_inst_.pltype = -1; // Not valid.
send_codec_inst_.rate = -1; // Not valid.
static const char kName[] = "external";
memcpy(send_codec_inst_.plname, kName, sizeof(kName));
if (stereo_send_)
dtx_enabled_ = false;
codec_fec_enabled_ = codec_fec_enabled_ &&
codec_owner_.SpeechEncoder()->SetFec(codec_fec_enabled_);
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);
}
int CodecManager::GetCodecInst(CodecInst* current_codec) const {
int dummy_id = 0;
WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, dummy_id,
"SendCodec()");
@@ -348,12 +373,11 @@ bool CodecManager::SetCopyRed(bool enable) {
}
if (red_enabled_ != enable) {
red_enabled_ = enable;
if (codec_owner_.Encoder())
codec_owner_.SetEncoders(
send_codec_inst_,
dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1,
vad_mode_,
red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1);
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);
}
}
return true;
}
@@ -382,12 +406,11 @@ int CodecManager::SetVAD(bool enable, ACMVADMode mode) {
if (dtx_enabled_ != enable || vad_mode_ != mode) {
dtx_enabled_ = enable;
vad_mode_ = mode;
if (codec_owner_.Encoder())
codec_owner_.SetEncoders(
send_codec_inst_,
dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1,
vad_mode_,
red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1);
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);
}
}
return 0;
}

View File

@@ -31,9 +31,11 @@ class CodecManager final {
CodecManager();
~CodecManager();
int RegisterSendCodec(const CodecInst& send_codec);
int RegisterEncoder(const CodecInst& send_codec);
int SendCodec(CodecInst* current_codec) const;
void RegisterEncoder(AudioEncoderMutable* external_speech_encoder);
int GetCodecInst(CodecInst* current_codec) const;
bool SetCopyRed(bool enable);

View File

@@ -75,7 +75,8 @@ bool IsG722(const CodecInst& codec) {
}
} // namespace
CodecOwner::CodecOwner() : isac_is_encoder_(false) {
CodecOwner::CodecOwner()
: isac_is_encoder_(false), external_speech_encoder_(nullptr) {
}
CodecOwner::~CodecOwner() = default;
@@ -92,7 +93,7 @@ AudioEncoderDecoderMutableIsac* CreateIsacCodec(const CodecInst& speech_inst) {
#endif
}
AudioEncoder* CreateSpeechEncoder(
void CreateSpeechEncoder(
const CodecInst& speech_inst,
rtc::scoped_ptr<AudioEncoderMutable>* speech_encoder,
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac>* isac_codec,
@@ -105,7 +106,7 @@ AudioEncoder* CreateSpeechEncoder(
}
*isac_is_encoder = true;
speech_encoder->reset();
return isac_codec->get();
return;
}
if (IsOpus(speech_inst)) {
speech_encoder->reset(new AudioEncoderMutableOpus(speech_inst));
@@ -123,7 +124,6 @@ AudioEncoder* CreateSpeechEncoder(
FATAL();
}
*isac_is_encoder = false;
return speech_encoder->get();
}
AudioEncoder* CreateRedEncoder(int red_payload_type,
@@ -140,13 +140,13 @@ AudioEncoder* CreateRedEncoder(int red_payload_type,
return red_encoder->get();
}
AudioEncoder* CreateCngEncoder(int cng_payload_type,
ACMVADMode vad_mode,
AudioEncoder* encoder,
rtc::scoped_ptr<AudioEncoder>* cng_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 encoder;
return;
}
AudioEncoderCng::Config config;
config.num_channels = encoder->NumChannels();
@@ -169,7 +169,6 @@ AudioEncoder* CreateCngEncoder(int cng_payload_type,
FATAL();
}
cng_encoder->reset(new AudioEncoderCng(config));
return cng_encoder->get();
}
} // namespace
@@ -177,12 +176,31 @@ void CodecOwner::SetEncoders(const CodecInst& speech_inst,
int cng_payload_type,
ACMVADMode vad_mode,
int red_payload_type) {
AudioEncoder* encoder = CreateSpeechEncoder(speech_inst, &speech_encoder_,
&isac_codec_, &isac_is_encoder_);
encoder = CreateRedEncoder(red_payload_type, encoder, &red_encoder_);
encoder =
CreateCngEncoder(cng_payload_type, vad_mode, encoder, &cng_encoder_);
DCHECK(!speech_encoder_ || !isac_is_encoder_);
CreateSpeechEncoder(speech_inst, &speech_encoder_, &isac_codec_,
&isac_is_encoder_);
external_speech_encoder_ = nullptr;
ChangeCngAndRed(cng_payload_type, vad_mode, red_payload_type);
}
void CodecOwner::SetEncoders(AudioEncoderMutable* external_speech_encoder,
int cng_payload_type,
ACMVADMode vad_mode,
int red_payload_type) {
external_speech_encoder_ = external_speech_encoder;
speech_encoder_.reset();
isac_is_encoder_ = false;
ChangeCngAndRed(cng_payload_type, vad_mode, red_payload_type);
}
void CodecOwner::ChangeCngAndRed(int cng_payload_type,
ACMVADMode vad_mode,
int red_payload_type) {
AudioEncoder* encoder =
CreateRedEncoder(red_payload_type, SpeechEncoder(), &red_encoder_);
CreateCngEncoder(cng_payload_type, vad_mode, encoder, &cng_encoder_);
int num_true =
!!speech_encoder_ + !!external_speech_encoder_ + isac_is_encoder_;
DCHECK_EQ(num_true, 1);
DCHECK(!isac_is_encoder_ || isac_codec_);
}
@@ -219,8 +237,12 @@ AudioEncoderMutable* CodecOwner::SpeechEncoder() {
}
const AudioEncoderMutable* CodecOwner::SpeechEncoder() const {
DCHECK(!speech_encoder_ || !isac_is_encoder_);
DCHECK(!isac_is_encoder_ || isac_codec_);
int num_true =
!!speech_encoder_ + !!external_speech_encoder_ + isac_is_encoder_;
DCHECK_GE(num_true, 0);
DCHECK_LE(num_true, 1);
if (external_speech_encoder_)
return external_speech_encoder_;
if (speech_encoder_)
return speech_encoder_.get();
return isac_is_encoder_ ? isac_codec_.get() : nullptr;

View File

@@ -34,6 +34,17 @@ class CodecOwner {
ACMVADMode vad_mode,
int red_payload_type);
void SetEncoders(AudioEncoderMutable* 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);
// Returns a pointer to an iSAC decoder owned by the CodecOwner. The decoder
// will live as long as the CodecOwner exists.
AudioDecoder* GetIsacDecoder();
AudioEncoder* Encoder();
@@ -42,13 +53,20 @@ class CodecOwner {
const AudioEncoderMutable* SpeechEncoder() const;
private:
// If iSAC is registered as an encoder, |isac_is_encoder_| is true,
// |isac_codec_| is valid and |speech_encoder_| is null. If another encoder
// is registered, |isac_is_encoder_| is false, |speech_encoder_| is valid
// and |isac_codec_| is valid iff iSAC has been registered as a decoder.
// There are three main cases for the state of the encoder members below:
// 1. An external encoder is used. |external_speech_encoder_| points to it.
// |speech_encoder_| is null, and |isac_is_encoder_| is false.
// 2. The internal iSAC codec is used as encoder. |isac_codec_| points to it
// and |isac_is_encoder_| is true. |external_speech_encoder_| and
// |speech_encoder_| are null.
// 3. Another internal encoder is used. |speech_encoder_| points to it.
// |external_speech_encoder_| is null, and |isac_is_encoder_| is false.
// In addition to case 2, |isac_codec_| is valid when GetIsacDecoder has been
// called.
rtc::scoped_ptr<AudioEncoderMutable> speech_encoder_;
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac> isac_codec_;
bool isac_is_encoder_;
AudioEncoderMutable* external_speech_encoder_;
// |cng_encoder_| and |red_encoder_| are valid iff CNG or RED, respectively,
// are active.

View File

@@ -9,12 +9,17 @@
*/
#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"
namespace webrtc {
namespace acm2 {
using ::testing::Return;
using ::testing::InSequence;
namespace {
const int kDataLengthSamples = 80;
const int kPacketSizeSamples = 2 * kDataLengthSamples;
@@ -93,5 +98,53 @@ TEST_F(CodecOwnerTest, VerifyCngFrames) {
}
}
TEST_F(CodecOwnerTest, ExternalEncoder) {
MockAudioEncoderMutable 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));
{
InSequence s;
info.encoded_timestamp = 0;
EXPECT_CALL(external_encoder,
EncodeInternal(0, audio, arraysize(encoded), encoded))
.WillOnce(Return(info));
EXPECT_CALL(external_encoder, Reset());
EXPECT_CALL(external_encoder, Reset());
info.encoded_timestamp = 2;
EXPECT_CALL(external_encoder,
EncodeInternal(2, audio, arraysize(encoded), encoded))
.WillOnce(Return(info));
EXPECT_CALL(external_encoder, Reset());
}
info = codec_owner_.Encoder()->Encode(0, audio, arraysize(audio),
arraysize(encoded), encoded);
EXPECT_EQ(0u, info.encoded_timestamp);
external_encoder.Reset(); // Dummy call to mark the sequence of expectations.
// Change to internal encoder.
CodecInst codec_inst = kDefaultCodecInst;
codec_inst.pacsize = kPacketSizeSamples;
codec_owner_.SetEncoders(codec_inst, -1, VADNormal, -1);
// Don't expect any more calls to the external encoder.
info = codec_owner_.Encoder()->Encode(1, audio, arraysize(audio),
arraysize(encoded), encoded);
external_encoder.Reset(); // Dummy call to mark the sequence of expectations.
// Change back to external encoder again.
codec_owner_.SetEncoders(&external_encoder, -1, VADNormal, -1);
info = codec_owner_.Encoder()->Encode(2, audio, arraysize(audio),
arraysize(encoded), encoded);
EXPECT_EQ(2u, info.encoded_timestamp);
external_encoder.Reset(); // Dummy call to mark the sequence of expectations.
}
} // namespace acm2
} // namespace webrtc

View File

@@ -28,6 +28,7 @@ struct CodecInst;
struct WebRtcRTPHeader;
class AudioFrame;
class RTPFragmentationHeader;
class AudioEncoderMutable;
#define WEBRTC_10MS_PCM_AUDIO 960 // 16 bits super wideband 48 kHz
@@ -231,6 +232,11 @@ class AudioCodingModule {
//
virtual int32_t RegisterSendCodec(const CodecInst& send_codec) = 0;
// Registers |external_speech_encoder| as encoder. The new encoder will
// replace any previously registered speech encoder (internal or external).
virtual void RegisterExternalSendCodec(
AudioEncoderMutable* external_speech_encoder) = 0;
///////////////////////////////////////////////////////////////////////////
// int32_t SendCodec()
// Get parameters for the codec currently registered as send codec.