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:
@@ -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_
|
||||
|
@@ -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_))) {
|
||||
|
@@ -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.
|
||||
|
@@ -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.
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user