From 0561716ae262461eaa3fe5291f4626c76822108a Mon Sep 17 00:00:00 2001 From: "minyue@webrtc.org" Date: Tue, 3 Mar 2015 12:02:30 +0000 Subject: [PATCH] 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 --- .../codecs/opus/audio_encoder_opus.cc | 13 +- .../opus/interface/audio_encoder_opus.h | 1 + .../main/acm2/acm_generic_codec.cc | 38 +- .../main/acm2/acm_generic_codec.h | 21 +- .../main/acm2/acm_generic_codec_opus_test.cc | 21 + .../main/acm2/audio_coding_module_impl.cc | 16 + .../main/acm2/audio_coding_module_impl.h | 4 + .../main/interface/audio_coding_module.h | 24 + .../modules/audio_coding/main/test/Channel.cc | 36 +- .../modules/audio_coding/main/test/Channel.h | 2 + .../audio_coding/main/test/TestVADDTX.cc | 574 ++++++++---------- .../audio_coding/main/test/TestVADDTX.h | 118 ++-- .../modules/audio_coding/main/test/Tester.cc | 12 +- 13 files changed, 482 insertions(+), 398 deletions(-) diff --git a/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc index 688fb6405..4739018f7 100644 --- a/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc +++ b/webrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc @@ -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 diff --git a/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h b/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h index e2ad601e6..93aca6c6c 100644 --- a/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h +++ b/webrtc/modules/audio_coding/codecs/opus/interface/audio_encoder_opus.h @@ -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); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc index d40615bc2..071244bbc 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.cc @@ -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(); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h index d264b0111..2fe440138 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h +++ b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec.h @@ -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 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_); diff --git a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec_opus_test.cc b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec_opus_test.cc index c19413a1a..29ca5260b 100644 --- a/webrtc/modules/audio_coding/main/acm2/acm_generic_codec_opus_test.cc +++ b/webrtc/modules/audio_coding/main/acm2/acm_generic_codec_opus_test.cc @@ -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 diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc index 62287a4ab..dd0ac702a 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.cc @@ -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; } diff --git a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h index 76d1e363a..49c815955 100644 --- a/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h +++ b/webrtc/modules/audio_coding/main/acm2/audio_coding_module_impl.h @@ -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; diff --git a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h index 72e00cdfa..bcce3a2f4 100644 --- a/webrtc/modules/audio_coding/main/interface/audio_coding_module.h +++ b/webrtc/modules/audio_coding/main/interface/audio_coding_module.h @@ -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 // diff --git a/webrtc/modules/audio_coding/main/test/Channel.cc b/webrtc/modules/audio_coding/main/test/Channel.cc index 919208a76..779718dd5 100644 --- a/webrtc/modules/audio_coding/main/test/Channel.cc +++ b/webrtc/modules/audio_coding/main/test/Channel.cc @@ -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()), diff --git a/webrtc/modules/audio_coding/main/test/Channel.h b/webrtc/modules/audio_coding/main/test/Channel.h index 5ec78b2c7..55167d359 100644 --- a/webrtc/modules/audio_coding/main/test/Channel.h +++ b/webrtc/modules/audio_coding/main/test/Channel.h @@ -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; diff --git a/webrtc/modules/audio_coding/main/test/TestVADDTX.cc b/webrtc/modules/audio_coding/main/test/TestVADDTX.cc index 20ccde6e6..0aeaf2003 100644 --- a/webrtc/modules/audio_coding/main/test/TestVADDTX.cc +++ b/webrtc/modules/audio_coding/main/test/TestVADDTX.cc @@ -10,367 +10,279 @@ #include "webrtc/modules/audio_coding/main/test/TestVADDTX.h" -#include +#include -#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(0), stats[i]) << "stats[" + << i + << "] error."; + break; + } + case 1: { + EXPECT_GT(stats[i], static_cast(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 diff --git a/webrtc/modules/audio_coding/main/test/TestVADDTX.h b/webrtc/modules/audio_coding/main/test/TestVADDTX.h index 5e832e476..773d3795e 100644 --- a/webrtc/modules/audio_coding/main/test/TestVADDTX.h +++ b/webrtc/modules/audio_coding/main/test/TestVADDTX.h @@ -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 acm_send_; + rtc::scoped_ptr acm_receive_; + rtc::scoped_ptr channel_; + rtc::scoped_ptr 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 _acmA; - rtc::scoped_ptr _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 diff --git a/webrtc/modules/audio_coding/main/test/Tester.cc b/webrtc/modules/audio_coding/main/test/Tester.cc index b6a554b9f..22510f341 100644 --- a/webrtc/modules/audio_coding/main/test/Tester.cc +++ b/webrtc/modules/audio_coding/main/test/Tester.cc @@ -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(); }