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));
|
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
|
} // namespace webrtc
|
||||||
|
|
||||||
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_MOCK_MOCK_AUDIO_ENCODER_H_
|
#endif // WEBRTC_MODULES_AUDIO_CODING_CODECS_MOCK_MOCK_AUDIO_ENCODER_H_
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "testing/gtest/include/gtest/gtest.h"
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
#include "webrtc/base/checks.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/main/interface/audio_coding_module.h"
|
||||||
#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h"
|
#include "webrtc/modules/audio_coding/neteq/tools/input_audio_file.h"
|
||||||
#include "webrtc/modules/audio_coding/neteq/tools/packet.h"
|
#include "webrtc/modules/audio_coding/neteq/tools/packet.h"
|
||||||
@@ -50,18 +51,27 @@ bool AcmSendTestOldApi::RegisterCodec(const char* payload_name,
|
|||||||
int channels,
|
int channels,
|
||||||
int payload_type,
|
int payload_type,
|
||||||
int frame_size_samples) {
|
int frame_size_samples) {
|
||||||
CHECK_EQ(0,
|
CodecInst codec;
|
||||||
AudioCodingModule::Codec(
|
CHECK_EQ(0, AudioCodingModule::Codec(payload_name, &codec, sampling_freq_hz,
|
||||||
payload_name, &codec_, sampling_freq_hz, channels));
|
channels));
|
||||||
codec_.pltype = payload_type;
|
codec.pltype = payload_type;
|
||||||
codec_.pacsize = frame_size_samples;
|
codec.pacsize = frame_size_samples;
|
||||||
codec_registered_ = (acm_->RegisterSendCodec(codec_) == 0);
|
codec_registered_ = (acm_->RegisterSendCodec(codec) == 0);
|
||||||
input_frame_.num_channels_ = channels;
|
input_frame_.num_channels_ = channels;
|
||||||
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
|
assert(input_block_size_samples_ * input_frame_.num_channels_ <=
|
||||||
AudioFrame::kMaxDataSizeSamples);
|
AudioFrame::kMaxDataSizeSamples);
|
||||||
return codec_registered_;
|
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() {
|
Packet* AcmSendTestOldApi::NextPacket() {
|
||||||
assert(codec_registered_);
|
assert(codec_registered_);
|
||||||
if (filter_.test(static_cast<size_t>(payload_type_))) {
|
if (filter_.test(static_cast<size_t>(payload_type_))) {
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
#include "webrtc/system_wrappers/interface/clock.h"
|
#include "webrtc/system_wrappers/interface/clock.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
class AudioEncoderMutable;
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
class InputAudioFile;
|
class InputAudioFile;
|
||||||
@@ -40,6 +41,9 @@ class AcmSendTestOldApi : public AudioPacketizationCallback,
|
|||||||
int payload_type,
|
int payload_type,
|
||||||
int frame_size_samples);
|
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
|
// Returns the next encoded packet. Returns NULL if the test duration was
|
||||||
// exceeded. Ownership of the packet is handed over to the caller.
|
// exceeded. Ownership of the packet is handed over to the caller.
|
||||||
// Inherited from PacketSource.
|
// Inherited from PacketSource.
|
||||||
@@ -69,7 +73,6 @@ class AcmSendTestOldApi : public AudioPacketizationCallback,
|
|||||||
int source_rate_hz_;
|
int source_rate_hz_;
|
||||||
const int input_block_size_samples_;
|
const int input_block_size_samples_;
|
||||||
AudioFrame input_frame_;
|
AudioFrame input_frame_;
|
||||||
CodecInst codec_;
|
|
||||||
bool codec_registered_;
|
bool codec_registered_;
|
||||||
int test_duration_ms_;
|
int test_duration_ms_;
|
||||||
// The following member variables are set whenever SendData() is called.
|
// 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.
|
// Can be called multiple times for Codec, CNG, RED.
|
||||||
int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) {
|
int AudioCodingModuleImpl::RegisterSendCodec(const CodecInst& send_codec) {
|
||||||
CriticalSectionScoped lock(acm_crit_sect_);
|
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.
|
// Get current send codec.
|
||||||
int AudioCodingModuleImpl::SendCodec(CodecInst* current_codec) const {
|
int AudioCodingModuleImpl::SendCodec(CodecInst* current_codec) const {
|
||||||
CriticalSectionScoped lock(acm_crit_sect_);
|
CriticalSectionScoped lock(acm_crit_sect_);
|
||||||
return codec_manager_.SendCodec(current_codec);
|
return codec_manager_.GetCodecInst(current_codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current send frequency.
|
// Get current send frequency.
|
||||||
|
@@ -48,6 +48,9 @@ class AudioCodingModuleImpl : public AudioCodingModule {
|
|||||||
// Can be called multiple times for Codec, CNG, RED.
|
// Can be called multiple times for Codec, CNG, RED.
|
||||||
int RegisterSendCodec(const CodecInst& send_codec) override;
|
int RegisterSendCodec(const CodecInst& send_codec) override;
|
||||||
|
|
||||||
|
void RegisterExternalSendCodec(
|
||||||
|
AudioEncoderMutable* external_speech_encoder) override;
|
||||||
|
|
||||||
// Get current send codec.
|
// Get current send codec.
|
||||||
int SendCodec(CodecInst* current_codec) const override;
|
int SendCodec(CodecInst* current_codec) const override;
|
||||||
|
|
||||||
|
@@ -15,6 +15,9 @@
|
|||||||
#include "webrtc/base/md5digest.h"
|
#include "webrtc/base/md5digest.h"
|
||||||
#include "webrtc/base/scoped_ptr.h"
|
#include "webrtc/base/scoped_ptr.h"
|
||||||
#include "webrtc/base/thread_annotations.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_receive_test_oldapi.h"
|
||||||
#include "webrtc/modules/audio_coding/main/acm2/acm_send_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"
|
#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/fileutils.h"
|
||||||
#include "webrtc/test/testsupport/gtest_disable.h"
|
#include "webrtc/test/testsupport/gtest_disable.h"
|
||||||
|
|
||||||
|
using ::testing::AtLeast;
|
||||||
|
using ::testing::Invoke;
|
||||||
|
using ::testing::_;
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -849,6 +856,15 @@ class AcmSenderBitExactnessOldApi : public ::testing::Test,
|
|||||||
frame_size_samples);
|
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
|
// Runs the test. SetUpSender() and RegisterSendCodec() must have been called
|
||||||
// before calling this method.
|
// before calling this method.
|
||||||
void Run(const std::string& audio_checksum_ref,
|
void Run(const std::string& audio_checksum_ref,
|
||||||
@@ -942,6 +958,13 @@ class AcmSenderBitExactnessOldApi : public ::testing::Test,
|
|||||||
codec_frame_size_rtp_timestamps));
|
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::AcmSendTestOldApi> send_test_;
|
||||||
rtc::scoped_ptr<test::InputAudioFile> audio_source_;
|
rtc::scoped_ptr<test::InputAudioFile> audio_source_;
|
||||||
uint32_t frame_size_rtp_timestamps_;
|
uint32_t frame_size_rtp_timestamps_;
|
||||||
@@ -1355,6 +1378,39 @@ TEST_F(AcmChangeBitRateOldApi, Pcm16_8khz_10ms_32kbps) {
|
|||||||
Run(32000, 64000, 64000);
|
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
|
// 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
|
// frequency during the call. The input packets are simply PCM16b-wb encoded
|
||||||
// payloads with a constant value of |kSampleValue|. The test fixture itself
|
// payloads with a constant value of |kSampleValue|. The test fixture itself
|
||||||
|
@@ -187,7 +187,7 @@ CodecManager::CodecManager()
|
|||||||
|
|
||||||
CodecManager::~CodecManager() = default;
|
CodecManager::~CodecManager() = default;
|
||||||
|
|
||||||
int CodecManager::RegisterSendCodec(const CodecInst& send_codec) {
|
int CodecManager::RegisterEncoder(const CodecInst& send_codec) {
|
||||||
DCHECK(thread_checker_.CalledOnValidThread());
|
DCHECK(thread_checker_.CalledOnValidThread());
|
||||||
int codec_id = IsValidSendCodec(send_codec, true);
|
int codec_id = IsValidSendCodec(send_codec, true);
|
||||||
|
|
||||||
@@ -321,7 +321,32 @@ int CodecManager::RegisterSendCodec(const CodecInst& send_codec) {
|
|||||||
return 0;
|
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;
|
int dummy_id = 0;
|
||||||
WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, dummy_id,
|
WEBRTC_TRACE(webrtc::kTraceStream, webrtc::kTraceAudioCoding, dummy_id,
|
||||||
"SendCodec()");
|
"SendCodec()");
|
||||||
@@ -348,12 +373,11 @@ bool CodecManager::SetCopyRed(bool enable) {
|
|||||||
}
|
}
|
||||||
if (red_enabled_ != enable) {
|
if (red_enabled_ != enable) {
|
||||||
red_enabled_ = enable;
|
red_enabled_ = enable;
|
||||||
if (codec_owner_.Encoder())
|
if (codec_owner_.Encoder()) {
|
||||||
codec_owner_.SetEncoders(
|
int cng_pt = dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1;
|
||||||
send_codec_inst_,
|
int red_pt = red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1;
|
||||||
dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1,
|
codec_owner_.ChangeCngAndRed(cng_pt, vad_mode_, red_pt);
|
||||||
vad_mode_,
|
}
|
||||||
red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -382,12 +406,11 @@ int CodecManager::SetVAD(bool enable, ACMVADMode mode) {
|
|||||||
if (dtx_enabled_ != enable || vad_mode_ != mode) {
|
if (dtx_enabled_ != enable || vad_mode_ != mode) {
|
||||||
dtx_enabled_ = enable;
|
dtx_enabled_ = enable;
|
||||||
vad_mode_ = mode;
|
vad_mode_ = mode;
|
||||||
if (codec_owner_.Encoder())
|
if (codec_owner_.Encoder()) {
|
||||||
codec_owner_.SetEncoders(
|
int cng_pt = dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1;
|
||||||
send_codec_inst_,
|
int red_pt = red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1;
|
||||||
dtx_enabled_ ? CngPayloadType(send_codec_inst_.plfreq) : -1,
|
codec_owner_.ChangeCngAndRed(cng_pt, vad_mode_, red_pt);
|
||||||
vad_mode_,
|
}
|
||||||
red_enabled_ ? RedPayloadType(send_codec_inst_.plfreq) : -1);
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -31,9 +31,11 @@ class CodecManager final {
|
|||||||
CodecManager();
|
CodecManager();
|
||||||
~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);
|
bool SetCopyRed(bool enable);
|
||||||
|
|
||||||
|
@@ -75,7 +75,8 @@ bool IsG722(const CodecInst& codec) {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
CodecOwner::CodecOwner() : isac_is_encoder_(false) {
|
CodecOwner::CodecOwner()
|
||||||
|
: isac_is_encoder_(false), external_speech_encoder_(nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CodecOwner::~CodecOwner() = default;
|
CodecOwner::~CodecOwner() = default;
|
||||||
@@ -92,7 +93,7 @@ AudioEncoderDecoderMutableIsac* CreateIsacCodec(const CodecInst& speech_inst) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEncoder* CreateSpeechEncoder(
|
void CreateSpeechEncoder(
|
||||||
const CodecInst& speech_inst,
|
const CodecInst& speech_inst,
|
||||||
rtc::scoped_ptr<AudioEncoderMutable>* speech_encoder,
|
rtc::scoped_ptr<AudioEncoderMutable>* speech_encoder,
|
||||||
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac>* isac_codec,
|
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac>* isac_codec,
|
||||||
@@ -105,7 +106,7 @@ AudioEncoder* CreateSpeechEncoder(
|
|||||||
}
|
}
|
||||||
*isac_is_encoder = true;
|
*isac_is_encoder = true;
|
||||||
speech_encoder->reset();
|
speech_encoder->reset();
|
||||||
return isac_codec->get();
|
return;
|
||||||
}
|
}
|
||||||
if (IsOpus(speech_inst)) {
|
if (IsOpus(speech_inst)) {
|
||||||
speech_encoder->reset(new AudioEncoderMutableOpus(speech_inst));
|
speech_encoder->reset(new AudioEncoderMutableOpus(speech_inst));
|
||||||
@@ -123,7 +124,6 @@ AudioEncoder* CreateSpeechEncoder(
|
|||||||
FATAL();
|
FATAL();
|
||||||
}
|
}
|
||||||
*isac_is_encoder = false;
|
*isac_is_encoder = false;
|
||||||
return speech_encoder->get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEncoder* CreateRedEncoder(int red_payload_type,
|
AudioEncoder* CreateRedEncoder(int red_payload_type,
|
||||||
@@ -140,13 +140,13 @@ AudioEncoder* CreateRedEncoder(int red_payload_type,
|
|||||||
return red_encoder->get();
|
return red_encoder->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioEncoder* CreateCngEncoder(int cng_payload_type,
|
void CreateCngEncoder(int cng_payload_type,
|
||||||
ACMVADMode vad_mode,
|
ACMVADMode vad_mode,
|
||||||
AudioEncoder* encoder,
|
AudioEncoder* encoder,
|
||||||
rtc::scoped_ptr<AudioEncoder>* cng_encoder) {
|
rtc::scoped_ptr<AudioEncoder>* cng_encoder) {
|
||||||
if (cng_payload_type == -1) {
|
if (cng_payload_type == -1) {
|
||||||
cng_encoder->reset();
|
cng_encoder->reset();
|
||||||
return encoder;
|
return;
|
||||||
}
|
}
|
||||||
AudioEncoderCng::Config config;
|
AudioEncoderCng::Config config;
|
||||||
config.num_channels = encoder->NumChannels();
|
config.num_channels = encoder->NumChannels();
|
||||||
@@ -169,7 +169,6 @@ AudioEncoder* CreateCngEncoder(int cng_payload_type,
|
|||||||
FATAL();
|
FATAL();
|
||||||
}
|
}
|
||||||
cng_encoder->reset(new AudioEncoderCng(config));
|
cng_encoder->reset(new AudioEncoderCng(config));
|
||||||
return cng_encoder->get();
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -177,12 +176,31 @@ void CodecOwner::SetEncoders(const CodecInst& speech_inst,
|
|||||||
int cng_payload_type,
|
int cng_payload_type,
|
||||||
ACMVADMode vad_mode,
|
ACMVADMode vad_mode,
|
||||||
int red_payload_type) {
|
int red_payload_type) {
|
||||||
AudioEncoder* encoder = CreateSpeechEncoder(speech_inst, &speech_encoder_,
|
CreateSpeechEncoder(speech_inst, &speech_encoder_, &isac_codec_,
|
||||||
&isac_codec_, &isac_is_encoder_);
|
&isac_is_encoder_);
|
||||||
encoder = CreateRedEncoder(red_payload_type, encoder, &red_encoder_);
|
external_speech_encoder_ = nullptr;
|
||||||
encoder =
|
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_);
|
CreateCngEncoder(cng_payload_type, vad_mode, encoder, &cng_encoder_);
|
||||||
DCHECK(!speech_encoder_ || !isac_is_encoder_);
|
int num_true =
|
||||||
|
!!speech_encoder_ + !!external_speech_encoder_ + isac_is_encoder_;
|
||||||
|
DCHECK_EQ(num_true, 1);
|
||||||
DCHECK(!isac_is_encoder_ || isac_codec_);
|
DCHECK(!isac_is_encoder_ || isac_codec_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,8 +237,12 @@ AudioEncoderMutable* CodecOwner::SpeechEncoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AudioEncoderMutable* CodecOwner::SpeechEncoder() const {
|
const AudioEncoderMutable* CodecOwner::SpeechEncoder() const {
|
||||||
DCHECK(!speech_encoder_ || !isac_is_encoder_);
|
int num_true =
|
||||||
DCHECK(!isac_is_encoder_ || isac_codec_);
|
!!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_)
|
if (speech_encoder_)
|
||||||
return speech_encoder_.get();
|
return speech_encoder_.get();
|
||||||
return isac_is_encoder_ ? isac_codec_.get() : nullptr;
|
return isac_is_encoder_ ? isac_codec_.get() : nullptr;
|
||||||
|
@@ -34,6 +34,17 @@ class CodecOwner {
|
|||||||
ACMVADMode vad_mode,
|
ACMVADMode vad_mode,
|
||||||
int red_payload_type);
|
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();
|
AudioDecoder* GetIsacDecoder();
|
||||||
|
|
||||||
AudioEncoder* Encoder();
|
AudioEncoder* Encoder();
|
||||||
@@ -42,13 +53,20 @@ class CodecOwner {
|
|||||||
const AudioEncoderMutable* SpeechEncoder() const;
|
const AudioEncoderMutable* SpeechEncoder() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// If iSAC is registered as an encoder, |isac_is_encoder_| is true,
|
// There are three main cases for the state of the encoder members below:
|
||||||
// |isac_codec_| is valid and |speech_encoder_| is null. If another encoder
|
// 1. An external encoder is used. |external_speech_encoder_| points to it.
|
||||||
// is registered, |isac_is_encoder_| is false, |speech_encoder_| is valid
|
// |speech_encoder_| is null, and |isac_is_encoder_| is false.
|
||||||
// and |isac_codec_| is valid iff iSAC has been registered as a decoder.
|
// 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<AudioEncoderMutable> speech_encoder_;
|
||||||
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac> isac_codec_;
|
rtc::scoped_ptr<AudioEncoderDecoderMutableIsac> isac_codec_;
|
||||||
bool isac_is_encoder_;
|
bool isac_is_encoder_;
|
||||||
|
AudioEncoderMutable* external_speech_encoder_;
|
||||||
|
|
||||||
// |cng_encoder_| and |red_encoder_| are valid iff CNG or RED, respectively,
|
// |cng_encoder_| and |red_encoder_| are valid iff CNG or RED, respectively,
|
||||||
// are active.
|
// are active.
|
||||||
|
@@ -9,12 +9,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "testing/gtest/include/gtest/gtest.h"
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
#include "webrtc/base/arraysize.h"
|
||||||
#include "webrtc/base/safe_conversions.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/codec_owner.h"
|
||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
namespace acm2 {
|
namespace acm2 {
|
||||||
|
|
||||||
|
using ::testing::Return;
|
||||||
|
using ::testing::InSequence;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
const int kDataLengthSamples = 80;
|
const int kDataLengthSamples = 80;
|
||||||
const int kPacketSizeSamples = 2 * kDataLengthSamples;
|
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 acm2
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
@@ -28,6 +28,7 @@ struct CodecInst;
|
|||||||
struct WebRtcRTPHeader;
|
struct WebRtcRTPHeader;
|
||||||
class AudioFrame;
|
class AudioFrame;
|
||||||
class RTPFragmentationHeader;
|
class RTPFragmentationHeader;
|
||||||
|
class AudioEncoderMutable;
|
||||||
|
|
||||||
#define WEBRTC_10MS_PCM_AUDIO 960 // 16 bits super wideband 48 kHz
|
#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;
|
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()
|
// int32_t SendCodec()
|
||||||
// Get parameters for the codec currently registered as send codec.
|
// Get parameters for the codec currently registered as send codec.
|
||||||
|
Reference in New Issue
Block a user