Adding Opus DTX support in ACM.

This solution does not use the existing VAD/DTX logic of ACM, since Opus DTX is codec feature, while ACM VAD/DTX is mainly for setting the WebRTC VAD/DTX.

During the development of this CL, two old bugs were found and are fixed in this CL too.

They are in
webrtc/modules/audio_coding/test/Channels.cc
and webrtc/modules/audio_coding/main/acm2/acm_opus_unittest.cc
respectively.

BUG=webrtc:1014
R=henrik.lundin@webrtc.org, tina.legrand@webrtc.org

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

Cr-Commit-Position: refs/heads/master@{#8573}
git-svn-id: http://webrtc.googlecode.com/svn/trunk@8573 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
minyue@webrtc.org 2015-03-03 12:02:30 +00:00
parent a1c9803e32
commit 0561716ae2
13 changed files with 482 additions and 398 deletions

View File

@ -53,7 +53,8 @@ AudioEncoderOpus::Config::Config()
bitrate_bps(64000),
fec_enabled(false),
max_playback_rate_hz(48000),
complexity(kDefaultComplexity) {
complexity(kDefaultComplexity),
dtx_enabled(false) {
}
bool AudioEncoderOpus::Config::IsOk() const {
@ -65,6 +66,8 @@ bool AudioEncoderOpus::Config::IsOk() const {
return false;
if (complexity < 0 || complexity > 10)
return false;
if (dtx_enabled && application != kVoip)
return false;
return true;
}
@ -89,7 +92,11 @@ AudioEncoderOpus::AudioEncoderOpus(const Config& config)
CHECK_EQ(0,
WebRtcOpus_SetMaxPlaybackRate(inst_, config.max_playback_rate_hz));
CHECK_EQ(0, WebRtcOpus_SetComplexity(inst_, config.complexity));
if (config.dtx_enabled) {
CHECK_EQ(0, WebRtcOpus_EnableDtx(inst_));
} else {
CHECK_EQ(0, WebRtcOpus_DisableDtx(inst_));
}
}
AudioEncoderOpus::~AudioEncoderOpus() {
@ -193,6 +200,8 @@ void AudioEncoderOpus::EncodeInternal(uint32_t rtp_timestamp,
info->encoded_bytes = r;
info->encoded_timestamp = first_timestamp_in_buffer_;
info->payload_type = payload_type_;
// Allows Opus to send empty packets.
info->send_even_if_empty = true;
}
} // namespace webrtc

View File

@ -39,6 +39,7 @@ class AudioEncoderOpus final : public AudioEncoder {
bool fec_enabled;
int max_playback_rate_hz;
int complexity;
bool dtx_enabled;
};
explicit AudioEncoderOpus(const Config& config);

View File

@ -115,6 +115,7 @@ ACMGenericCodec::ACMGenericCodec(const CodecInst& codec_inst,
max_playback_rate_hz_(48000),
max_payload_size_bytes_(-1),
max_rate_bps_(-1),
opus_dtx_enabled_(false),
is_opus_(false),
is_isac_(false),
first_frame_(true),
@ -275,10 +276,12 @@ int16_t ACMGenericCodec::InitEncoder(WebRtcACMCodecParams* codec_params,
WriteLockScoped wl(codec_wrapper_lock_);
bitrate_bps_ = 0;
loss_rate_ = 0;
opus_dtx_enabled_ = false;
acm_codec_params_ = *codec_params;
if (force_initialization)
opus_application_set_ = false;
opus_application_ = GetOpusApplication(codec_params->codec_inst.channels);
opus_application_ = GetOpusApplication(codec_params->codec_inst.channels,
opus_dtx_enabled_);
opus_application_set_ = true;
ResetAudioEncoder();
return 0;
@ -325,8 +328,9 @@ void ACMGenericCodec::ResetAudioEncoder() {
config.fec_enabled = fec_enabled_;
config.bitrate_bps = codec_inst.rate;
config.max_playback_rate_hz = max_playback_rate_hz_;
config.dtx_enabled = opus_dtx_enabled_;
config.payload_type = codec_inst.pltype;
switch (GetOpusApplication(config.num_channels)) {
switch (GetOpusApplication(config.num_channels, config.dtx_enabled)) {
case kVoip:
config.application = AudioEncoderOpus::ApplicationMode::kVoip;
break;
@ -478,10 +482,10 @@ void ACMGenericCodec::ResetAudioEncoder() {
}
OpusApplicationMode ACMGenericCodec::GetOpusApplication(
int num_channels) const {
int num_channels, bool enable_dtx) const {
if (opus_application_set_)
return opus_application_;
return num_channels == 1 ? kVoip : kAudio;
return num_channels == 1 || enable_dtx ? kVoip : kAudio;
}
int32_t ACMGenericCodec::Add10MsData(const uint32_t timestamp,
@ -586,6 +590,28 @@ AudioDecoder* ACMGenericCodec::Decoder() {
return decoder_proxy_.IsSet() ? &decoder_proxy_ : nullptr;
}
int ACMGenericCodec::EnableOpusDtx() {
WriteLockScoped wl(codec_wrapper_lock_);
if (!is_opus_)
return -1; // Needed for tests to pass.
if (GetOpusApplication(encoder_->NumChannels(), true) != kVoip) {
// Opus DTX can only be enabled when application mode is KVoip.
return -1;
}
opus_dtx_enabled_ = true;
ResetAudioEncoder();
return 0;
}
int ACMGenericCodec::DisableOpusDtx() {
WriteLockScoped wl(codec_wrapper_lock_);
if (!is_opus_)
return -1; // Needed for tests to pass.
opus_dtx_enabled_ = false;
ResetAudioEncoder();
return 0;
}
int ACMGenericCodec::SetFEC(bool enable_fec) {
if (!HasInternalFEC())
return enable_fec ? -1 : 0;
@ -599,6 +625,10 @@ int ACMGenericCodec::SetFEC(bool enable_fec) {
int ACMGenericCodec::SetOpusApplication(OpusApplicationMode application) {
WriteLockScoped wl(codec_wrapper_lock_);
if (opus_dtx_enabled_ && application == kAudio) {
// Opus can only be set to kAudio when DTX is off.
return -1;
}
opus_application_ = application;
opus_application_set_ = true;
ResetAudioEncoder();

View File

@ -393,6 +393,23 @@ class ACMGenericCodec {
//
int SetOpusMaxPlaybackRate(int /* frequency_hz */);
///////////////////////////////////////////////////////////////////////////
// EnableOpusDtx()
// Enable the DTX, if the codec is Opus. If current Opus application mode is
// audio, a failure will be triggered.
// Return value:
// -1 if failed or on codecs other than Opus.
// 0 if succeeded.
int EnableOpusDtx();
///////////////////////////////////////////////////////////////////////////
// DisbleOpusDtx()
// Disable the DTX, if the codec is Opus.
// Return value:
// -1 if failed or on codecs other than Opus.
// 0 if succeeded.
int DisableOpusDtx();
///////////////////////////////////////////////////////////////////////////
// HasFrameToEncode()
// Returns true if there is enough audio buffered for encoding, such that
@ -469,7 +486,8 @@ class ACMGenericCodec {
void ResetAudioEncoder() EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_);
OpusApplicationMode GetOpusApplication(int num_channels) const
OpusApplicationMode GetOpusApplication(int num_channels,
bool enable_dtx) const
EXCLUSIVE_LOCKS_REQUIRED(codec_wrapper_lock_);
rtc::scoped_ptr<AudioEncoder> audio_encoder_ GUARDED_BY(codec_wrapper_lock_);
@ -485,6 +503,7 @@ class ACMGenericCodec {
int max_playback_rate_hz_ GUARDED_BY(codec_wrapper_lock_);
int max_payload_size_bytes_ GUARDED_BY(codec_wrapper_lock_);
int max_rate_bps_ GUARDED_BY(codec_wrapper_lock_);
bool opus_dtx_enabled_ GUARDED_BY(codec_wrapper_lock_);
bool is_opus_ GUARDED_BY(codec_wrapper_lock_);
bool is_isac_ GUARDED_BY(codec_wrapper_lock_);
bool first_frame_ GUARDED_BY(codec_wrapper_lock_);

View File

@ -100,6 +100,27 @@ TEST_F(AcmGenericCodecOpusTest, ResetWontChangeApplicationMode) {
// Verify that the mode is still kVoip.
EXPECT_EQ(AudioEncoderOpus::kVoip, GetAudioEncoderOpus()->application());
}
TEST_F(AcmGenericCodecOpusTest, ToggleDtx) {
// Create a stereo encoder.
acm_codec_params_.codec_inst.channels = 2;
CreateCodec();
// Verify that the mode is still kAudio.
EXPECT_EQ(AudioEncoderOpus::kAudio, GetAudioEncoderOpus()->application());
// DTX is not allowed in audio mode.
EXPECT_EQ(-1, codec_wrapper_->EnableOpusDtx());
EXPECT_EQ(0, codec_wrapper_->SetOpusApplication(kVoip));
EXPECT_EQ(0, codec_wrapper_->EnableOpusDtx());
// Audio mode is not allowed when DTX is on.
EXPECT_EQ(-1, codec_wrapper_->SetOpusApplication(kAudio));
EXPECT_EQ(AudioEncoderOpus::kVoip, GetAudioEncoderOpus()->application());
EXPECT_EQ(0, codec_wrapper_->DisableOpusDtx());
EXPECT_EQ(0, codec_wrapper_->SetOpusApplication(kAudio));
}
#endif // WEBRTC_CODEC_OPUS
} // namespace acm2

View File

@ -1374,6 +1374,22 @@ int AudioCodingModuleImpl::SetOpusMaxPlaybackRate(int frequency_hz) {
return codecs_[current_send_codec_idx_]->SetOpusMaxPlaybackRate(frequency_hz);
}
int AudioCodingModuleImpl::EnableOpusDtx() {
CriticalSectionScoped lock(acm_crit_sect_);
if (!HaveValidEncoder("EnableOpusDtx")) {
return -1;
}
return codecs_[current_send_codec_idx_]->EnableOpusDtx();
}
int AudioCodingModuleImpl::DisableOpusDtx() {
CriticalSectionScoped lock(acm_crit_sect_);
if (!HaveValidEncoder("DisableOpusDtx")) {
return -1;
}
return codecs_[current_send_codec_idx_]->DisableOpusDtx();
}
int AudioCodingModuleImpl::PlayoutTimestamp(uint32_t* timestamp) {
return receiver_.GetPlayoutTimestamp(timestamp) ? 0 : -1;
}

View File

@ -223,6 +223,10 @@ class AudioCodingModuleImpl : public AudioCodingModule {
// the receiver will render.
virtual int SetOpusMaxPlaybackRate(int frequency_hz) OVERRIDE;
int EnableOpusDtx() override;
int DisableOpusDtx() override;
virtual int UnregisterReceiveCodec(uint8_t payload_type) OVERRIDE;
virtual int EnableNack(size_t max_nack_list_size) OVERRIDE;

View File

@ -905,6 +905,30 @@ class AudioCodingModule {
//
virtual int SetOpusMaxPlaybackRate(int frequency_hz) = 0;
///////////////////////////////////////////////////////////////////////////
// int EnableOpusDtx()
// If current send codec is Opus, enables its internal DTX.
// Currently, this can be only called when Opus application mode is VOIP.
// Use SetOpusApplication() to switch to VOIP mode when necessary.
//
// Return value:
// -1 if current send codec is not Opus or
// error occurred in enabling DTX.
// 0 Opus DTX is enabled successfully.
//
virtual int EnableOpusDtx() = 0;
///////////////////////////////////////////////////////////////////////////
// int DisableOpusDtx()
// If current send codec is Opus, disables its internal DTX.
//
// Return value:
// -1 if current send codec is not Opus or
// error occurred in disabling DTX.
// 0 Opus DTX is disabled successfully.
//
virtual int DisableOpusDtx() = 0;
///////////////////////////////////////////////////////////////////////////
// statistics
//

View File

@ -43,7 +43,9 @@ int32_t Channel::SendData(FrameType frameType,
rtpInfo.type.Audio.isCNG = false;
}
if (frameType == kFrameEmpty) {
// Skip this frame
// When frame is empty, we should not transmit it. The frame size of the
// next non-empty frame will be based on the previous frame size.
_useLastFrameSize = _lastFrameSizeSample > 0;
return 0;
}
@ -101,6 +103,7 @@ int32_t Channel::SendData(FrameType frameType,
if (!_isStereo) {
CalcStatistics(rtpInfo, payloadSize);
}
_useLastFrameSize = false;
_lastInTimestamp = timeStamp;
_totalBytes += payloadDataSize;
_channelCritSect->Leave();
@ -153,22 +156,31 @@ void Channel::CalcStatistics(WebRtcRTPHeader& rtpInfo, size_t payloadSize) {
if (!newPayload) {
if (!currentPayloadStr->newPacket) {
uint32_t lastFrameSizeSample = (uint32_t)(
(uint32_t) rtpInfo.header.timestamp
- (uint32_t) currentPayloadStr->lastTimestamp);
assert(lastFrameSizeSample > 0);
if (!_useLastFrameSize) {
_lastFrameSizeSample = (uint32_t) ((uint32_t) rtpInfo.header.timestamp -
(uint32_t) currentPayloadStr->lastTimestamp);
}
assert(_lastFrameSizeSample > 0);
int k = 0;
while ((currentPayloadStr->frameSizeStats[k].frameSizeSample
!= lastFrameSizeSample)
&& (currentPayloadStr->frameSizeStats[k].frameSizeSample != 0)) {
k++;
for (; k < MAX_NUM_FRAMESIZES; ++k) {
if ((currentPayloadStr->frameSizeStats[k].frameSizeSample ==
_lastFrameSizeSample) ||
(currentPayloadStr->frameSizeStats[k].frameSizeSample == 0)) {
break;
}
}
if (k == MAX_NUM_FRAMESIZES) {
// New frame size found but no space to count statistics on it. Skip it.
printf("No memory to store statistics for payload %d : frame size %d\n",
_lastPayloadType, _lastFrameSizeSample);
return;
}
ACMTestFrameSizeStats* currentFrameSizeStats = &(currentPayloadStr
->frameSizeStats[k]);
currentFrameSizeStats->frameSizeSample = (int16_t) lastFrameSizeSample;
currentFrameSizeStats->frameSizeSample = (int16_t) _lastFrameSizeSample;
// increment the number of encoded samples.
currentFrameSizeStats->totalEncodedSamples += lastFrameSizeSample;
currentFrameSizeStats->totalEncodedSamples += _lastFrameSizeSample;
// increment the number of recveived packets
currentFrameSizeStats->numPackets++;
// increment the total number of bytes (this is based on
@ -220,6 +232,8 @@ Channel::Channel(int16_t chID)
_isStereo(false),
_leftChannel(true),
_lastInTimestamp(0),
_useLastFrameSize(false),
_lastFrameSizeSample(0),
_packetLoss(0),
_useFECTestWithPacketLoss(false),
_beginTime(TickTime::MillisecondTimestamp()),

View File

@ -111,6 +111,8 @@ class Channel : public AudioPacketizationCallback {
WebRtcRTPHeader _rtpInfo;
bool _leftChannel;
uint32_t _lastInTimestamp;
bool _useLastFrameSize;
uint32_t _lastFrameSizeSample;
// FEC Test variables
int16_t _packetLoss;
bool _useFECTestWithPacketLoss;

View File

@ -10,367 +10,279 @@
#include "webrtc/modules/audio_coding/main/test/TestVADDTX.h"
#include <iostream>
#include <string>
#include "webrtc/common_types.h"
#include "webrtc/engine_configurations.h"
#include "webrtc/modules/audio_coding/main/acm2/acm_common_defs.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h"
#include "webrtc/modules/audio_coding/main/test/PCMFile.h"
#include "webrtc/modules/audio_coding/main/test/utility.h"
#include "webrtc/system_wrappers/interface/trace.h"
#include "webrtc/test/testsupport/fileutils.h"
namespace webrtc {
TestVADDTX::TestVADDTX()
: _acmA(AudioCodingModule::Create(0)),
_acmB(AudioCodingModule::Create(1)),
_channelA2B(NULL) {}
TestVADDTX::~TestVADDTX() {
if (_channelA2B != NULL) {
delete _channelA2B;
_channelA2B = NULL;
}
}
void TestVADDTX::Perform() {
const std::string file_name = webrtc::test::ResourcePath(
"audio_coding/testfile32kHz", "pcm");
_inFileA.Open(file_name, 32000, "rb");
EXPECT_EQ(0, _acmA->InitializeReceiver());
EXPECT_EQ(0, _acmB->InitializeReceiver());
uint8_t numEncoders = _acmA->NumberOfCodecs();
CodecInst myCodecParam;
for (uint8_t n = 0; n < numEncoders; n++) {
EXPECT_EQ(0, _acmB->Codec(n, &myCodecParam));
if (!strcmp(myCodecParam.plname, "opus")) {
// Register Opus as mono.
myCodecParam.channels = 1;
}
EXPECT_EQ(0, _acmB->RegisterReceiveCodec(myCodecParam));
}
// Create and connect the channel
_channelA2B = new Channel;
_acmA->RegisterTransportCallback(_channelA2B);
_channelA2B->RegisterReceiverACM(_acmB.get());
_acmA->RegisterVADCallback(&_monitor);
int16_t testCntr = 1;
#ifdef WEBRTC_CODEC_ISAC
// Open outputfile
OpenOutFile(testCntr++);
// Register iSAC WB as send codec
char nameISAC[] = "ISAC";
RegisterSendCodec('A', nameISAC, 16000);
// Run the five test cased
runTestCases();
// Close file
_outFileB.Close();
// Open outputfile
OpenOutFile(testCntr++);
// Register iSAC SWB as send codec
RegisterSendCodec('A', nameISAC, 32000);
// Run the five test cased
runTestCases();
// Close file
_outFileB.Close();
const CodecInst kIsacWb = {103, "ISAC", 16000, 480, 1, 32000};
const CodecInst kIsacSwb = {104, "ISAC", 32000, 960, 1, 56000};
#endif
#ifdef WEBRTC_CODEC_ILBC
// Open outputfile
OpenOutFile(testCntr++);
// Register iLBC as send codec
char nameILBC[] = "ilbc";
RegisterSendCodec('A', nameILBC);
// Run the five test cased
runTestCases();
// Close file
_outFileB.Close();
const CodecInst kIlbc = {102, "ILBC", 8000, 240, 1, 13300};
#endif
#ifdef WEBRTC_CODEC_OPUS
// Open outputfile
OpenOutFile(testCntr++);
// Register Opus as send codec
char nameOPUS[] = "opus";
RegisterSendCodec('A', nameOPUS);
// Run the five test cased
runTestCases();
// Close file
_outFileB.Close();
const CodecInst kOpus = {120, "opus", 48000, 960, 1, 64000};
const CodecInst kOpusStereo = {120, "opus", 48000, 960, 2, 64000};
#endif
}
void TestVADDTX::runTestCases() {
// #1 DTX = OFF, VAD = OFF, VADNormal
SetVAD(false, false, VADNormal);
Run();
VerifyTest();
// #2 DTX = ON, VAD = ON, VADAggr
SetVAD(true, true, VADAggr);
Run();
VerifyTest();
// #3 DTX = ON, VAD = ON, VADLowBitrate
SetVAD(true, true, VADLowBitrate);
Run();
VerifyTest();
// #4 DTX = ON, VAD = ON, VADVeryAggr
SetVAD(true, true, VADVeryAggr);
Run();
VerifyTest();
// #5 DTX = ON, VAD = ON, VADNormal
SetVAD(true, true, VADNormal);
Run();
VerifyTest();
}
void TestVADDTX::runTestInternalDTX(int expected_result) {
// #6 DTX = ON, VAD = ON, VADNormal
SetVAD(true, true, VADNormal);
EXPECT_EQ(expected_result, _acmA->ReplaceInternalDTXWithWebRtc(true));
if (expected_result == 0) {
Run();
VerifyTest();
}
}
void TestVADDTX::SetVAD(bool statusDTX, bool statusVAD, int16_t vadMode) {
bool dtxEnabled, vadEnabled;
ACMVADMode vadModeSet;
EXPECT_EQ(0, _acmA->SetVAD(statusDTX, statusVAD, (ACMVADMode) vadMode));
EXPECT_EQ(0, _acmA->VAD(&dtxEnabled, &vadEnabled, &vadModeSet));
// Requested VAD/DTX settings
_setStruct.statusDTX = statusDTX;
_setStruct.statusVAD = statusVAD;
_setStruct.vadMode = (ACMVADMode) vadMode;
// VAD settings after setting VAD in ACM
_getStruct.statusDTX = dtxEnabled;
_getStruct.statusVAD = vadEnabled;
_getStruct.vadMode = vadModeSet;
}
VADDTXstruct TestVADDTX::GetVAD() {
VADDTXstruct retStruct;
bool dtxEnabled, vadEnabled;
ACMVADMode vadModeSet;
EXPECT_EQ(0, _acmA->VAD(&dtxEnabled, &vadEnabled, &vadModeSet));
retStruct.statusDTX = dtxEnabled;
retStruct.statusVAD = vadEnabled;
retStruct.vadMode = vadModeSet;
return retStruct;
}
int16_t TestVADDTX::RegisterSendCodec(char side, char* codecName,
int32_t samplingFreqHz,
int32_t rateKbps) {
std::cout << std::flush;
AudioCodingModule* myACM;
switch (side) {
case 'A': {
myACM = _acmA.get();
break;
}
case 'B': {
myACM = _acmB.get();
break;
}
default:
return -1;
}
if (myACM == NULL) {
return -1;
}
CodecInst myCodecParam;
for (int16_t codecCntr = 0; codecCntr < myACM->NumberOfCodecs();
codecCntr++) {
EXPECT_EQ(0, myACM->Codec((uint8_t) codecCntr, &myCodecParam));
if (!STR_CASE_CMP(myCodecParam.plname, codecName)) {
if ((samplingFreqHz == -1) || (myCodecParam.plfreq == samplingFreqHz)) {
if ((rateKbps == -1) || (myCodecParam.rate == rateKbps)) {
break;
}
}
}
}
// We only allow VAD/DTX when sending mono.
myCodecParam.channels = 1;
EXPECT_EQ(0, myACM->RegisterSendCodec(myCodecParam));
// initialization was succesful
return 0;
}
void TestVADDTX::Run() {
AudioFrame audioFrame;
uint16_t SamplesIn10MsecA = _inFileA.PayloadLength10Ms();
uint32_t timestampA = 1;
int32_t outFreqHzB = _outFileB.SamplingFrequency();
while (!_inFileA.EndOfFile()) {
_inFileA.Read10MsData(audioFrame);
audioFrame.timestamp_ = timestampA;
timestampA += SamplesIn10MsecA;
EXPECT_GE(_acmA->Add10MsData(audioFrame), 0);
EXPECT_EQ(0, _acmB->PlayoutData10Ms(outFreqHzB, &audioFrame));
_outFileB.Write10MsData(audioFrame.data_, audioFrame.samples_per_channel_);
}
#ifdef PRINT_STAT
_monitor.PrintStatistics();
#endif
_inFileA.Rewind();
_monitor.GetStatistics(_statCounter);
_monitor.ResetStatistics();
}
void TestVADDTX::OpenOutFile(int16_t test_number) {
std::string file_name;
std::stringstream file_stream;
file_stream << webrtc::test::OutputPath();
file_stream << "testVADDTX_outFile_";
file_stream << test_number << ".pcm";
file_name = file_stream.str();
_outFileB.Open(file_name, 16000, "wb");
}
int16_t TestVADDTX::VerifyTest() {
// Verify empty frame result
uint8_t statusEF = 0;
uint8_t vadPattern = 0;
uint8_t emptyFramePattern[6];
CodecInst myCodecParam;
_acmA->SendCodec(&myCodecParam);
// TODO(minyue): Remove these treatment on Opus when DTX is properly handled
// by ACMOpus.
if (STR_CASE_CMP(myCodecParam.plname, "opus") == 0) {
_setStruct.statusDTX = false;
_setStruct.statusVAD = false;
}
bool isReplaced = false;
_acmA->IsInternalDTXReplacedWithWebRtc(&isReplaced);
bool webRtcDtxInUse = _getStruct.statusDTX && isReplaced;
bool codecDtxInUse = _getStruct.statusDTX && !isReplaced;
// Check for error in VAD/DTX settings
if (_getStruct.statusDTX != _setStruct.statusDTX) {
// DTX status doesn't match expected.
vadPattern |= 1;
}
if (!_getStruct.statusVAD && webRtcDtxInUse) {
// WebRTC DTX cannot run without WebRTC VAD.
vadPattern |= 2;
}
if ((!_getStruct.statusDTX || codecDtxInUse) &&
(_getStruct.statusVAD != _setStruct.statusVAD)) {
// Using no DTX or codec Internal DTX should not affect setting of VAD.
vadPattern |= 4;
}
if (_getStruct.vadMode != _setStruct.vadMode) {
// VAD Mode doesn't match expected.
vadPattern |= 8;
}
// Set expected empty frame pattern
int ii;
for (ii = 0; ii < 6; ii++) {
emptyFramePattern[ii] = 0;
}
// 0 - "kNoEncoding", not important to check.
// Codecs with packetsize != 80 samples will get this output.
// 1 - "kActiveNormalEncoded", expect to receive some frames with this label .
// 2 - "kPassiveNormalEncoded".
// 3 - "kPassiveDTXNB".
// 4 - "kPassiveDTXWB".
// 5 - "kPassiveDTXSWB".
emptyFramePattern[0] = 1;
emptyFramePattern[1] = 1;
emptyFramePattern[2] = _getStruct.statusVAD && !webRtcDtxInUse;
emptyFramePattern[3] = webRtcDtxInUse && (_acmA->SendFrequency() == 8000);
emptyFramePattern[4] = webRtcDtxInUse && (_acmA->SendFrequency() == 16000);
emptyFramePattern[5] = webRtcDtxInUse && (_acmA->SendFrequency() == 32000);
// Check pattern 1-5 (skip 0)
for (int ii = 1; ii < 6; ii++) {
if (emptyFramePattern[ii]) {
statusEF |= (_statCounter[ii] == 0);
} else {
statusEF |= (_statCounter[ii] > 0);
}
}
EXPECT_EQ(0, statusEF);
EXPECT_EQ(0, vadPattern);
return 0;
}
ActivityMonitor::ActivityMonitor() {
_counter[0] = _counter[1] = _counter[2] = _counter[3] = _counter[4] =
_counter[5] = 0;
ResetStatistics();
}
ActivityMonitor::~ActivityMonitor() {
}
int32_t ActivityMonitor::InFrameType(int16_t frameType) {
_counter[frameType]++;
int32_t ActivityMonitor::InFrameType(int16_t frame_type) {
counter_[frame_type]++;
return 0;
}
void ActivityMonitor::PrintStatistics() {
printf("\n");
printf("kActiveNormalEncoded kPassiveNormalEncoded kPassiveDTXWB ");
printf("kPassiveDTXNB kPassiveDTXSWB kFrameEmpty\n");
printf("%19u", _counter[1]);
printf("%22u", _counter[2]);
printf("%14u", _counter[3]);
printf("%14u", _counter[4]);
printf("%14u", _counter[5]);
printf("%11u", _counter[0]);
printf("kPassiveDTXNB kPassiveDTXSWB kNoEncoding\n");
printf("%19u", counter_[1]);
printf("%22u", counter_[2]);
printf("%14u", counter_[3]);
printf("%14u", counter_[4]);
printf("%14u", counter_[5]);
printf("%11u", counter_[0]);
printf("\n\n");
}
void ActivityMonitor::ResetStatistics() {
_counter[0] = _counter[1] = _counter[2] = _counter[3] = _counter[4] =
_counter[5] = 0;
memset(counter_, 0, sizeof(counter_));
}
void ActivityMonitor::GetStatistics(uint32_t* getCounter) {
for (int ii = 0; ii < 6; ii++) {
getCounter[ii] = _counter[ii];
void ActivityMonitor::GetStatistics(uint32_t* counter) {
memcpy(counter, counter_, sizeof(counter_));
}
TestVadDtx::TestVadDtx()
: acm_send_(AudioCodingModule::Create(0)),
acm_receive_(AudioCodingModule::Create(1)),
channel_(new Channel),
monitor_(new ActivityMonitor) {
EXPECT_EQ(0, acm_send_->RegisterTransportCallback(channel_.get()));
channel_->RegisterReceiverACM(acm_receive_.get());
EXPECT_EQ(0, acm_send_->RegisterVADCallback(monitor_.get()));
assert(monitor_->kPacketTypes == this->kPacketTypes);
}
void TestVadDtx::RegisterCodec(CodecInst codec_param) {
// Set the codec for sending and receiving.
EXPECT_EQ(0, acm_send_->RegisterSendCodec(codec_param));
EXPECT_EQ(0, acm_receive_->RegisterReceiveCodec(codec_param));
channel_->SetIsStereo(codec_param.channels > 1);
}
// Encoding a file and see if the numbers that various packets occur follow
// the expectation.
void TestVadDtx::Run(std::string in_filename, int frequency, int channels,
std::string out_filename, bool append,
const int* expects) {
monitor_->ResetStatistics();
PCMFile in_file;
in_file.Open(in_filename, frequency, "rb");
in_file.ReadStereo(channels > 1);
PCMFile out_file;
if (append) {
out_file.Open(out_filename, kOutputFreqHz, "ab");
} else {
out_file.Open(out_filename, kOutputFreqHz, "wb");
}
uint16_t frame_size_samples = in_file.PayloadLength10Ms();
uint32_t time_stamp = 0x12345678;
AudioFrame audio_frame;
while (!in_file.EndOfFile()) {
in_file.Read10MsData(audio_frame);
audio_frame.timestamp_ = time_stamp;
time_stamp += frame_size_samples;
EXPECT_GE(acm_send_->Add10MsData(audio_frame), 0);
acm_receive_->PlayoutData10Ms(kOutputFreqHz, &audio_frame);
out_file.Write10MsData(audio_frame);
}
in_file.Close();
out_file.Close();
#ifdef PRINT_STAT
monitor_->PrintStatistics();
#endif
uint32_t stats[kPacketTypes];
monitor_->GetStatistics(stats);
monitor_->ResetStatistics();
for (int i = 0; i < kPacketTypes; i++) {
switch (expects[i]) {
case 0: {
EXPECT_EQ(static_cast<uint32_t>(0), stats[i]) << "stats["
<< i
<< "] error.";
break;
}
case 1: {
EXPECT_GT(stats[i], static_cast<uint32_t>(0)) << "stats["
<< i
<< "] error.";
break;
}
}
}
}
// Following is the implementation of TestWebRtcVadDtx.
TestWebRtcVadDtx::TestWebRtcVadDtx()
: vad_enabled_(false),
dtx_enabled_(false),
use_webrtc_dtx_(false),
output_file_num_(0) {
}
void TestWebRtcVadDtx::Perform() {
// Go through various test cases.
#ifdef WEBRTC_CODEC_ISAC
// Register iSAC WB as send codec
RegisterCodec(kIsacWb);
RunTestCases();
// Register iSAC SWB as send codec
RegisterCodec(kIsacSwb);
RunTestCases();
#endif
#ifdef WEBRTC_CODEC_ILBC
// Register iLBC as send codec
RegisterCodec(kIlbc);
RunTestCases();
#endif
#ifdef WEBRTC_CODEC_OPUS
// Register Opus as send codec
RegisterCodec(kOpus);
RunTestCases();
#endif
}
// Test various configurations on VAD/DTX.
void TestWebRtcVadDtx::RunTestCases() {
// #1 DTX = OFF, VAD = OFF, VADNormal
SetVAD(false, false, VADNormal);
Test(true);
// #2 DTX = ON, VAD = ON, VADAggr
SetVAD(true, true, VADAggr);
Test(false);
// #3 DTX = ON, VAD = ON, VADLowBitrate
SetVAD(true, true, VADLowBitrate);
Test(false);
// #4 DTX = ON, VAD = ON, VADVeryAggr
SetVAD(true, true, VADVeryAggr);
Test(false);
// #5 DTX = ON, VAD = ON, VADNormal
SetVAD(true, true, VADNormal);
Test(false);
}
// Set the expectation and run the test.
void TestWebRtcVadDtx::Test(bool new_outfile) {
int expects[kPacketTypes];
int frequency = acm_send_->SendFrequency();
expects[0] = -1; // Do not care.
expects[1] = 1;
expects[2] = vad_enabled_ && !use_webrtc_dtx_;
expects[3] = use_webrtc_dtx_ && (frequency == 8000);
expects[4] = use_webrtc_dtx_ && (frequency == 16000);
expects[5] = use_webrtc_dtx_ && (frequency == 32000);
if (new_outfile) {
output_file_num_++;
}
std::stringstream out_filename;
out_filename << webrtc::test::OutputPath()
<< "testWebRtcVadDtx_outFile_"
<< output_file_num_
<< ".pcm";
Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"),
32000, 1, out_filename.str(), !new_outfile, expects);
}
void TestWebRtcVadDtx::SetVAD(bool enable_dtx, bool enable_vad,
ACMVADMode vad_mode) {
ACMVADMode mode;
EXPECT_EQ(0, acm_send_->SetVAD(enable_dtx, enable_vad, vad_mode));
EXPECT_EQ(0, acm_send_->VAD(&dtx_enabled_, &vad_enabled_, &mode));
CodecInst codec_param;
acm_send_->SendCodec(&codec_param);
if (STR_CASE_CMP(codec_param.plname, "opus") == 0) {
// If send codec is Opus, WebRTC VAD/DTX cannot be used.
enable_dtx = enable_vad = false;
}
EXPECT_EQ(dtx_enabled_ , enable_dtx); // DTX should be set as expected.
bool replaced = false;
acm_send_->IsInternalDTXReplacedWithWebRtc(&replaced);
use_webrtc_dtx_ = dtx_enabled_ && replaced;
if (use_webrtc_dtx_) {
EXPECT_TRUE(vad_enabled_); // WebRTC DTX cannot run without WebRTC VAD.
}
if (!dtx_enabled_ || !use_webrtc_dtx_) {
// Using no DTX or codec Internal DTX should not affect setting of VAD.
EXPECT_EQ(enable_vad, vad_enabled_);
}
}
// Following is the implementation of TestOpusDtx.
void TestOpusDtx::Perform() {
#ifdef WEBRTC_CODEC_OPUS
int expects[kPacketTypes] = {0, 1, 0, 0, 0, 0};
// Register Opus as send codec
std::string out_filename = webrtc::test::OutputPath() +
"testOpusDtx_outFile_mono.pcm";
RegisterCodec(kOpus);
EXPECT_EQ(0, acm_send_->DisableOpusDtx());
Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"),
32000, 1, out_filename, false, expects);
EXPECT_EQ(0, acm_send_->EnableOpusDtx());
expects[0] = 1;
Run(webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"),
32000, 1, out_filename, true, expects);
// Register stereo Opus as send codec
out_filename = webrtc::test::OutputPath() + "testOpusDtx_outFile_stereo.pcm";
RegisterCodec(kOpusStereo);
EXPECT_EQ(0, acm_send_->DisableOpusDtx());
expects[0] = 0;
Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"),
32000, 2, out_filename, false, expects);
// Opus DTX should only work in Voip mode.
EXPECT_EQ(0, acm_send_->SetOpusApplication(kVoip));
EXPECT_EQ(0, acm_send_->EnableOpusDtx());
expects[0] = 1;
Run(webrtc::test::ResourcePath("audio_coding/teststereo32kHz", "pcm"),
32000, 2, out_filename, true, expects);
#endif
}
} // namespace webrtc

View File

@ -11,73 +11,97 @@
#ifndef WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_TESTVADDTX_H_
#define WEBRTC_MODULES_AUDIO_CODING_MAIN_TEST_TESTVADDTX_H_
#include "webrtc/base/scoped_ptr.h"
#include "webrtc/common_types.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module.h"
#include "webrtc/modules/audio_coding/main/interface/audio_coding_module_typedefs.h"
#include "webrtc/modules/audio_coding/main/test/ACMTest.h"
#include "webrtc/modules/audio_coding/main/test/Channel.h"
#include "webrtc/modules/audio_coding/main/test/PCMFile.h"
namespace webrtc {
typedef struct {
bool statusDTX;
bool statusVAD;
ACMVADMode vadMode;
} VADDTXstruct;
class ActivityMonitor : public ACMVADCallback {
public:
static const int kPacketTypes = 6;
ActivityMonitor();
~ActivityMonitor();
int32_t InFrameType(int16_t frameType);
int32_t InFrameType(int16_t frame_type);
void PrintStatistics();
void ResetStatistics();
void GetStatistics(uint32_t* getCounter);
void GetStatistics(uint32_t* stats);
private:
// Counting according to
// enum WebRtcACMEncodingType {
// kNoEncoding,
// kActiveNormalEncoded,
// kPassiveNormalEncoded,
// kPassiveDTXNB,
// kPassiveDTXWB,
// kPassiveDTXSWB
// };
uint32_t _counter[6];
// counter_[0] - kNoEncoding,
// counter_[1] - kActiveNormalEncoded,
// counter_[2] - kPassiveNormalEncoded,
// counter_[3] - kPassiveDTXNB,
// counter_[4] - kPassiveDTXWB,
// counter_[5] - kPassiveDTXSWB
uint32_t counter_[kPacketTypes];
};
class TestVADDTX : public ACMTest {
// TestVadDtx is to verify that VAD/DTX perform as they should. It runs through
// an audio file and check if the occurrence of various packet types follows
// expectation. TestVadDtx needs its derived class to implement the Perform()
// to put the test together.
class TestVadDtx : public ACMTest {
public:
TestVADDTX();
~TestVADDTX();
static const int kOutputFreqHz = 16000;
static const int kPacketTypes = 6;
TestVadDtx();
virtual void Perform() = 0;
protected:
void RegisterCodec(CodecInst codec_param);
// Encoding a file and see if the numbers that various packets occur follow
// the expectation. Saves result to a file.
// expects[x] means
// -1 : do not care,
// 0 : there have been no packets of type |x|,
// 1 : there have been packets of type |x|,
// with |x| indicates the following packet types
// 0 - kNoEncoding
// 1 - kActiveNormalEncoded
// 2 - kPassiveNormalEncoded
// 3 - kPassiveDTXNB
// 4 - kPassiveDTXWB
// 5 - kPassiveDTXSWB
void Run(std::string in_filename, int frequency, int channels,
std::string out_filename, bool append, const int* expects);
rtc::scoped_ptr<AudioCodingModule> acm_send_;
rtc::scoped_ptr<AudioCodingModule> acm_receive_;
rtc::scoped_ptr<Channel> channel_;
rtc::scoped_ptr<ActivityMonitor> monitor_;
};
// TestWebRtcVadDtx is to verify that the WebRTC VAD/DTX perform as they should.
class TestWebRtcVadDtx final : public TestVadDtx {
public:
TestWebRtcVadDtx();
void Perform() override;
void Perform();
private:
// Registration can be based on codec name only, codec name and sampling
// frequency, or codec name, sampling frequency and rate.
int16_t RegisterSendCodec(char side,
char* codecName,
int32_t samplingFreqHz = -1,
int32_t rateKhz = -1);
void Run();
void OpenOutFile(int16_t testNumber);
void runTestCases();
void runTestInternalDTX(int expected_result);
void SetVAD(bool statusDTX, bool statusVAD, int16_t vadMode);
VADDTXstruct GetVAD();
int16_t VerifyTest();
rtc::scoped_ptr<AudioCodingModule> _acmA;
rtc::scoped_ptr<AudioCodingModule> _acmB;
void RunTestCases();
void Test(bool new_outfile);
void SetVAD(bool enable_dtx, bool enable_vad, ACMVADMode vad_mode);
Channel* _channelA2B;
bool vad_enabled_;
bool dtx_enabled_;
bool use_webrtc_dtx_;
int output_file_num_;
};
PCMFile _inFileA;
PCMFile _outFileB;
ActivityMonitor _monitor;
uint32_t _statCounter[6];
VADDTXstruct _setStruct;
VADDTXstruct _getStruct;
// TestOpusDtx is to verify that the Opus DTX performs as it should.
class TestOpusDtx final : public TestVadDtx {
public:
void Perform() override;
};
} // namespace webrtc

View File

@ -82,11 +82,19 @@ TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestStereo)) {
Trace::ReturnTrace();
}
TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestVADDTX)) {
TEST(AudioCodingModuleTest, DISABLED_ON_ANDROID(TestWebRtcVadDtx)) {
Trace::CreateTrace();
Trace::SetTraceFile((webrtc::test::OutputPath() +
"acm_vaddtx_trace.txt").c_str());
webrtc::TestVADDTX().Perform();
webrtc::TestWebRtcVadDtx().Perform();
Trace::ReturnTrace();
}
TEST(AudioCodingModuleTest, TestOpusDtx) {
Trace::CreateTrace();
Trace::SetTraceFile((webrtc::test::OutputPath() +
"acm_opusdtx_trace.txt").c_str());
webrtc::TestOpusDtx().Perform();
Trace::ReturnTrace();
}