| /* | |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/audio_coding/test/APITest.h" | |
| #include <ctype.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <iostream> | |
| #include <ostream> | |
| #include <string> | |
| #include "api/audio_codecs/builtin_audio_decoder_factory.h" | |
| #include "common_types.h" // NOLINT(build/include) | |
| #include "modules/audio_coding/codecs/audio_format_conversion.h" | |
| #include "modules/audio_coding/test/utility.h" | |
| #include "rtc_base/platform_thread.h" | |
| #include "rtc_base/timeutils.h" | |
| #include "system_wrappers/include/event_wrapper.h" | |
| #include "test/gtest.h" | |
| #include "test/testsupport/fileutils.h" | |
| #include "typedefs.h" // NOLINT(build/include) | |
| namespace webrtc { | |
| #define TEST_DURATION_SEC 600 | |
| #define NUMBER_OF_SENDER_TESTS 6 | |
| #define MAX_FILE_NAME_LENGTH_BYTE 500 | |
| void APITest::Wait(uint32_t waitLengthMs) { | |
| if (_randomTest) { | |
| return; | |
| } else { | |
| EventWrapper* myEvent = EventWrapper::Create(); | |
| myEvent->Wait(waitLengthMs); | |
| delete myEvent; | |
| return; | |
| } | |
| } | |
| APITest::APITest() | |
| : _acmA(AudioCodingModule::Create( | |
| AudioCodingModule::Config(CreateBuiltinAudioDecoderFactory()))), | |
| _acmB(AudioCodingModule::Create( | |
| AudioCodingModule::Config(CreateBuiltinAudioDecoderFactory()))), | |
| _channel_A2B(NULL), | |
| _channel_B2A(NULL), | |
| _writeToFile(true), | |
| _pullEventA(NULL), | |
| _pushEventA(NULL), | |
| _processEventA(NULL), | |
| _apiEventA(NULL), | |
| _pullEventB(NULL), | |
| _pushEventB(NULL), | |
| _processEventB(NULL), | |
| _apiEventB(NULL), | |
| _codecCntrA(0), | |
| _codecCntrB(0), | |
| _thereIsEncoderA(false), | |
| _thereIsEncoderB(false), | |
| _thereIsDecoderA(false), | |
| _thereIsDecoderB(false), | |
| _sendVADA(false), | |
| _sendDTXA(false), | |
| _sendVADModeA(VADNormal), | |
| _sendVADB(false), | |
| _sendDTXB(false), | |
| _sendVADModeB(VADNormal), | |
| _minDelayA(0), | |
| _minDelayB(0), | |
| _dotPositionA(0), | |
| _dotMoveDirectionA(1), | |
| _dotPositionB(39), | |
| _dotMoveDirectionB(-1), | |
| _vadCallbackA(NULL), | |
| _vadCallbackB(NULL), | |
| _apiTestRWLock(*RWLockWrapper::CreateRWLock()), | |
| _randomTest(false), | |
| _testNumA(0), | |
| _testNumB(1) { | |
| int n; | |
| for (n = 0; n < 32; n++) { | |
| _payloadUsed[n] = false; | |
| } | |
| _movingDot[40] = '\0'; | |
| for (int n = 0; n < 40; n++) { | |
| _movingDot[n] = ' '; | |
| } | |
| } | |
| APITest::~APITest() { | |
| DELETE_POINTER(_channel_A2B); | |
| DELETE_POINTER(_channel_B2A); | |
| DELETE_POINTER(_pushEventA); | |
| DELETE_POINTER(_pullEventA); | |
| DELETE_POINTER(_processEventA); | |
| DELETE_POINTER(_apiEventA); | |
| DELETE_POINTER(_pushEventB); | |
| DELETE_POINTER(_pullEventB); | |
| DELETE_POINTER(_processEventB); | |
| DELETE_POINTER(_apiEventB); | |
| _inFileA.Close(); | |
| _outFileA.Close(); | |
| _inFileB.Close(); | |
| _outFileB.Close(); | |
| DELETE_POINTER(_vadCallbackA); | |
| DELETE_POINTER(_vadCallbackB); | |
| delete &_apiTestRWLock; | |
| } | |
| int16_t APITest::SetUp() { | |
| CodecInst dummyCodec; | |
| int lastPayloadType = 0; | |
| int16_t numCodecs = _acmA->NumberOfCodecs(); | |
| for (uint8_t n = 0; n < numCodecs; n++) { | |
| AudioCodingModule::Codec(n, &dummyCodec); | |
| if ((STR_CASE_CMP(dummyCodec.plname, "CN") == 0) | |
| && (dummyCodec.plfreq == 32000)) { | |
| continue; | |
| } | |
| printf("Register Receive Codec %s ", dummyCodec.plname); | |
| if ((n != 0) && !FixedPayloadTypeCodec(dummyCodec.plname)) { | |
| // Check registration with an already occupied payload type | |
| int currentPayloadType = dummyCodec.pltype; | |
| dummyCodec.pltype = 97; //lastPayloadType; | |
| EXPECT_EQ(true, _acmB->RegisterReceiveCodec(dummyCodec.pltype, | |
| CodecInstToSdp(dummyCodec))); | |
| dummyCodec.pltype = currentPayloadType; | |
| } | |
| if ((n < numCodecs - 1) && !FixedPayloadTypeCodec(dummyCodec.plname)) { | |
| // test if re-registration works; | |
| CodecInst nextCodec; | |
| int currentPayloadType = dummyCodec.pltype; | |
| AudioCodingModule::Codec(n + 1, &nextCodec); | |
| dummyCodec.pltype = nextCodec.pltype; | |
| if (!FixedPayloadTypeCodec(nextCodec.plname)) { | |
| _acmB->RegisterReceiveCodec(dummyCodec.pltype, | |
| CodecInstToSdp(dummyCodec)); | |
| } | |
| dummyCodec.pltype = currentPayloadType; | |
| } | |
| if ((n < numCodecs - 1) && !FixedPayloadTypeCodec(dummyCodec.plname)) { | |
| // test if un-registration works; | |
| CodecInst nextCodec; | |
| AudioCodingModule::Codec(n + 1, &nextCodec); | |
| nextCodec.pltype = dummyCodec.pltype; | |
| if (!FixedPayloadTypeCodec(nextCodec.plname)) { | |
| EXPECT_EQ(true, _acmA->RegisterReceiveCodec(nextCodec.pltype, | |
| CodecInstToSdp(nextCodec))); | |
| CHECK_ERROR_MT(_acmA->UnregisterReceiveCodec(nextCodec.pltype)); | |
| } | |
| } | |
| EXPECT_EQ(true, _acmA->RegisterReceiveCodec(dummyCodec.pltype, | |
| CodecInstToSdp(dummyCodec))); | |
| printf(" side A done!"); | |
| EXPECT_EQ(true, _acmB->RegisterReceiveCodec(dummyCodec.pltype, | |
| CodecInstToSdp(dummyCodec))); | |
| printf(" side B done!\n"); | |
| if (!strcmp(dummyCodec.plname, "CN")) { | |
| CHECK_ERROR_MT(_acmA->RegisterSendCodec(dummyCodec)); | |
| CHECK_ERROR_MT(_acmB->RegisterSendCodec(dummyCodec)); | |
| } | |
| lastPayloadType = dummyCodec.pltype; | |
| if ((lastPayloadType >= 96) && (lastPayloadType <= 127)) { | |
| _payloadUsed[lastPayloadType - 96] = true; | |
| } | |
| } | |
| _thereIsDecoderA = true; | |
| _thereIsDecoderB = true; | |
| // Register Send Codec | |
| AudioCodingModule::Codec((uint8_t) _codecCntrA, &dummyCodec); | |
| CHECK_ERROR_MT(_acmA->RegisterSendCodec(dummyCodec)); | |
| _thereIsEncoderA = true; | |
| // | |
| AudioCodingModule::Codec((uint8_t) _codecCntrB, &dummyCodec); | |
| CHECK_ERROR_MT(_acmB->RegisterSendCodec(dummyCodec)); | |
| _thereIsEncoderB = true; | |
| uint16_t frequencyHz; | |
| printf("\n\nAPI Test\n"); | |
| printf("========\n"); | |
| printf("Hit enter to accept the default values indicated in []\n\n"); | |
| //--- Input A | |
| std::string file_name = webrtc::test::ResourcePath( | |
| "audio_coding/testfile32kHz", "pcm"); | |
| frequencyHz = 32000; | |
| printf("Enter input file at side A [%s]: ", file_name.c_str()); | |
| PCMFile::ChooseFile(&file_name, 499, &frequencyHz); | |
| _inFileA.Open(file_name, frequencyHz, "rb", true); | |
| //--- Output A | |
| std::string out_file_a = webrtc::test::OutputPath() + "outA.pcm"; | |
| printf("Enter output file at side A [%s]: ", out_file_a.c_str()); | |
| PCMFile::ChooseFile(&out_file_a, 499, &frequencyHz); | |
| _outFileA.Open(out_file_a, frequencyHz, "wb"); | |
| //--- Input B | |
| file_name = webrtc::test::ResourcePath("audio_coding/testfile32kHz", "pcm"); | |
| printf("\n\nEnter input file at side B [%s]: ", file_name.c_str()); | |
| PCMFile::ChooseFile(&file_name, 499, &frequencyHz); | |
| _inFileB.Open(file_name, frequencyHz, "rb", true); | |
| //--- Output B | |
| std::string out_file_b = webrtc::test::OutputPath() + "outB.pcm"; | |
| printf("Enter output file at side B [%s]: ", out_file_b.c_str()); | |
| PCMFile::ChooseFile(&out_file_b, 499, &frequencyHz); | |
| _outFileB.Open(out_file_b, frequencyHz, "wb"); | |
| //--- Set A-to-B channel | |
| _channel_A2B = new Channel(2); | |
| CHECK_ERROR_MT(_acmA->RegisterTransportCallback(_channel_A2B)); | |
| _channel_A2B->RegisterReceiverACM(_acmB.get()); | |
| //--- Set B-to-A channel | |
| _channel_B2A = new Channel(1); | |
| CHECK_ERROR_MT(_acmB->RegisterTransportCallback(_channel_B2A)); | |
| _channel_B2A->RegisterReceiverACM(_acmA.get()); | |
| //--- EVENT TIMERS | |
| // A | |
| _pullEventA = EventTimerWrapper::Create(); | |
| _pushEventA = EventTimerWrapper::Create(); | |
| _processEventA = EventTimerWrapper::Create(); | |
| _apiEventA = EventWrapper::Create(); | |
| // B | |
| _pullEventB = EventTimerWrapper::Create(); | |
| _pushEventB = EventTimerWrapper::Create(); | |
| _processEventB = EventTimerWrapper::Create(); | |
| _apiEventB = EventWrapper::Create(); | |
| //--- I/O params | |
| // A | |
| _outFreqHzA = _outFileA.SamplingFrequency(); | |
| // B | |
| _outFreqHzB = _outFileB.SamplingFrequency(); | |
| char print[11]; | |
| printf("\nRandom Test (y/n)?"); | |
| EXPECT_TRUE(fgets(print, 10, stdin) != NULL); | |
| print[10] = '\0'; | |
| if (strstr(print, "y") != NULL) { | |
| _randomTest = true; | |
| _verbose = false; | |
| _writeToFile = false; | |
| } else { | |
| _randomTest = false; | |
| printf("\nPrint Tests (y/n)? "); | |
| EXPECT_TRUE(fgets(print, 10, stdin) != NULL); | |
| print[10] = '\0'; | |
| if (strstr(print, "y") == NULL) { | |
| EXPECT_TRUE(freopen("APITest_log.txt", "w", stdout) != 0); | |
| _verbose = false; | |
| } | |
| } | |
| _vadCallbackA = new VADCallback; | |
| _vadCallbackB = new VADCallback; | |
| return 0; | |
| } | |
| bool APITest::PushAudioThreadA(void* obj) { | |
| return static_cast<APITest*>(obj)->PushAudioRunA(); | |
| } | |
| bool APITest::PushAudioThreadB(void* obj) { | |
| return static_cast<APITest*>(obj)->PushAudioRunB(); | |
| } | |
| bool APITest::PullAudioThreadA(void* obj) { | |
| return static_cast<APITest*>(obj)->PullAudioRunA(); | |
| } | |
| bool APITest::PullAudioThreadB(void* obj) { | |
| return static_cast<APITest*>(obj)->PullAudioRunB(); | |
| } | |
| bool APITest::ProcessThreadA(void* obj) { | |
| return static_cast<APITest*>(obj)->ProcessRunA(); | |
| } | |
| bool APITest::ProcessThreadB(void* obj) { | |
| return static_cast<APITest*>(obj)->ProcessRunB(); | |
| } | |
| bool APITest::APIThreadA(void* obj) { | |
| return static_cast<APITest*>(obj)->APIRunA(); | |
| } | |
| bool APITest::APIThreadB(void* obj) { | |
| return static_cast<APITest*>(obj)->APIRunB(); | |
| } | |
| bool APITest::PullAudioRunA() { | |
| _pullEventA->Wait(100); | |
| AudioFrame audioFrame; | |
| bool muted; | |
| if (_acmA->PlayoutData10Ms(_outFreqHzA, &audioFrame, &muted) < 0) { | |
| bool thereIsDecoder; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| thereIsDecoder = _thereIsDecoderA; | |
| } | |
| if (thereIsDecoder) { | |
| fprintf(stderr, "\n>>>>>> cannot pull audio A <<<<<<<< \n"); | |
| } | |
| } else { | |
| if (_writeToFile) { | |
| _outFileA.Write10MsData(audioFrame); | |
| } | |
| } | |
| return true; | |
| } | |
| bool APITest::PullAudioRunB() { | |
| _pullEventB->Wait(100); | |
| AudioFrame audioFrame; | |
| bool muted; | |
| if (_acmB->PlayoutData10Ms(_outFreqHzB, &audioFrame, &muted) < 0) { | |
| bool thereIsDecoder; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| thereIsDecoder = _thereIsDecoderB; | |
| } | |
| if (thereIsDecoder) { | |
| fprintf(stderr, "\n>>>>>> cannot pull audio B <<<<<<<< \n"); | |
| fprintf(stderr, "%d %d\n", _testNumA, _testNumB); | |
| } | |
| } else { | |
| if (_writeToFile) { | |
| _outFileB.Write10MsData(audioFrame); | |
| } | |
| } | |
| return true; | |
| } | |
| bool APITest::PushAudioRunA() { | |
| _pushEventA->Wait(100); | |
| AudioFrame audioFrame; | |
| _inFileA.Read10MsData(audioFrame); | |
| if (_acmA->Add10MsData(audioFrame) < 0) { | |
| bool thereIsEncoder; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| thereIsEncoder = _thereIsEncoderA; | |
| } | |
| if (thereIsEncoder) { | |
| fprintf(stderr, "\n>>>> add10MsData at A failed <<<<\n"); | |
| } | |
| } | |
| return true; | |
| } | |
| bool APITest::PushAudioRunB() { | |
| _pushEventB->Wait(100); | |
| AudioFrame audioFrame; | |
| _inFileB.Read10MsData(audioFrame); | |
| if (_acmB->Add10MsData(audioFrame) < 0) { | |
| bool thereIsEncoder; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| thereIsEncoder = _thereIsEncoderB; | |
| } | |
| if (thereIsEncoder) { | |
| fprintf(stderr, "\n>>>> cannot add audio to B <<<<"); | |
| } | |
| } | |
| return true; | |
| } | |
| bool APITest::ProcessRunA() { | |
| _processEventA->Wait(100); | |
| return true; | |
| } | |
| bool APITest::ProcessRunB() { | |
| _processEventB->Wait(100); | |
| return true; | |
| } | |
| /*/ | |
| * | |
| * In side A we test the APIs which are related to sender Side. | |
| * | |
| /*/ | |
| void APITest::RunTest(char thread) { | |
| int testNum; | |
| { | |
| WriteLockScoped cs(_apiTestRWLock); | |
| if (thread == 'A') { | |
| _testNumA = (_testNumB + 1 + (rand() % 3)) % 4; | |
| testNum = _testNumA; | |
| _movingDot[_dotPositionA] = ' '; | |
| if (_dotPositionA == 0) { | |
| _dotMoveDirectionA = 1; | |
| } | |
| if (_dotPositionA == 19) { | |
| _dotMoveDirectionA = -1; | |
| } | |
| _dotPositionA += _dotMoveDirectionA; | |
| _movingDot[_dotPositionA] = (_dotMoveDirectionA > 0) ? '>' : '<'; | |
| } else { | |
| _testNumB = (_testNumA + 1 + (rand() % 3)) % 4; | |
| testNum = _testNumB; | |
| _movingDot[_dotPositionB] = ' '; | |
| if (_dotPositionB == 20) { | |
| _dotMoveDirectionB = 1; | |
| } | |
| if (_dotPositionB == 39) { | |
| _dotMoveDirectionB = -1; | |
| } | |
| _dotPositionB += _dotMoveDirectionB; | |
| _movingDot[_dotPositionB] = (_dotMoveDirectionB > 0) ? '>' : '<'; | |
| } | |
| //fprintf(stderr, "%c: %d \n", thread, testNum); | |
| //fflush(stderr); | |
| } | |
| switch (testNum) { | |
| case 0: | |
| CurrentCodec('A'); | |
| ChangeCodec('A'); | |
| break; | |
| case 1: | |
| if (!_randomTest) { | |
| fprintf(stdout, "\nTesting Delay ...\n"); | |
| } | |
| TestDelay('A'); | |
| break; | |
| case 2: | |
| TestSendVAD('A'); | |
| break; | |
| case 3: | |
| TestRegisteration('A'); | |
| break; | |
| default: | |
| fprintf(stderr, "Wrong Test Number\n"); | |
| getc(stdin); | |
| exit(1); | |
| } | |
| } | |
| bool APITest::APIRunA() { | |
| _apiEventA->Wait(50); | |
| bool randomTest; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| randomTest = _randomTest; | |
| } | |
| if (randomTest) { | |
| RunTest('A'); | |
| } else { | |
| CurrentCodec('A'); | |
| ChangeCodec('A'); | |
| if (_codecCntrA == 0) { | |
| fprintf(stdout, "\nTesting Delay ...\n"); | |
| TestDelay('A'); | |
| } | |
| // VAD TEST | |
| TestSendVAD('A'); | |
| TestRegisteration('A'); | |
| } | |
| return true; | |
| } | |
| bool APITest::APIRunB() { | |
| _apiEventB->Wait(50); | |
| bool randomTest; | |
| { | |
| ReadLockScoped rl(_apiTestRWLock); | |
| randomTest = _randomTest; | |
| } | |
| //_apiEventB->Wait(2000); | |
| if (randomTest) { | |
| RunTest('B'); | |
| } | |
| return true; | |
| } | |
| void APITest::Perform() { | |
| SetUp(); | |
| //--- THREADS | |
| // A | |
| // PUSH | |
| rtc::PlatformThread myPushAudioThreadA(PushAudioThreadA, this, | |
| "PushAudioThreadA"); | |
| myPushAudioThreadA.Start(); | |
| // PULL | |
| rtc::PlatformThread myPullAudioThreadA(PullAudioThreadA, this, | |
| "PullAudioThreadA"); | |
| myPullAudioThreadA.Start(); | |
| // Process | |
| rtc::PlatformThread myProcessThreadA(ProcessThreadA, this, "ProcessThreadA"); | |
| myProcessThreadA.Start(); | |
| // API | |
| rtc::PlatformThread myAPIThreadA(APIThreadA, this, "APIThreadA"); | |
| myAPIThreadA.Start(); | |
| // B | |
| // PUSH | |
| rtc::PlatformThread myPushAudioThreadB(PushAudioThreadB, this, | |
| "PushAudioThreadB"); | |
| myPushAudioThreadB.Start(); | |
| // PULL | |
| rtc::PlatformThread myPullAudioThreadB(PullAudioThreadB, this, | |
| "PullAudioThreadB"); | |
| myPullAudioThreadB.Start(); | |
| // Process | |
| rtc::PlatformThread myProcessThreadB(ProcessThreadB, this, "ProcessThreadB"); | |
| myProcessThreadB.Start(); | |
| // API | |
| rtc::PlatformThread myAPIThreadB(APIThreadB, this, "APIThreadB"); | |
| myAPIThreadB.Start(); | |
| //_apiEventA->StartTimer(true, 5000); | |
| //_apiEventB->StartTimer(true, 5000); | |
| _processEventA->StartTimer(true, 10); | |
| _processEventB->StartTimer(true, 10); | |
| _pullEventA->StartTimer(true, 10); | |
| _pullEventB->StartTimer(true, 10); | |
| _pushEventA->StartTimer(true, 10); | |
| _pushEventB->StartTimer(true, 10); | |
| // Keep main thread waiting for sender/receiver | |
| // threads to complete | |
| EventWrapper* completeEvent = EventWrapper::Create(); | |
| uint64_t startTime = rtc::TimeMillis(); | |
| uint64_t currentTime; | |
| // Run test in 2 minutes (120000 ms). | |
| do { | |
| { | |
| //ReadLockScoped rl(_apiTestRWLock); | |
| //fprintf(stderr, "\r%s", _movingDot); | |
| } | |
| //fflush(stderr); | |
| completeEvent->Wait(50); | |
| currentTime = rtc::TimeMillis(); | |
| } while ((currentTime - startTime) < 120000); | |
| //completeEvent->Wait(0xFFFFFFFF); | |
| //(unsigned long)((unsigned long)TEST_DURATION_SEC * (unsigned long)1000)); | |
| delete completeEvent; | |
| myPushAudioThreadA.Stop(); | |
| myPullAudioThreadA.Stop(); | |
| myProcessThreadA.Stop(); | |
| myAPIThreadA.Stop(); | |
| myPushAudioThreadB.Stop(); | |
| myPullAudioThreadB.Stop(); | |
| myProcessThreadB.Stop(); | |
| myAPIThreadB.Stop(); | |
| } | |
| void APITest::CheckVADStatus(char side) { | |
| bool dtxEnabled; | |
| bool vadEnabled; | |
| ACMVADMode vadMode; | |
| if (side == 'A') { | |
| _acmA->VAD(&dtxEnabled, &vadEnabled, &vadMode); | |
| _acmA->RegisterVADCallback(NULL); | |
| _vadCallbackA->Reset(); | |
| _acmA->RegisterVADCallback(_vadCallbackA); | |
| if (!_randomTest) { | |
| if (_verbose) { | |
| fprintf(stdout, "DTX %3s, VAD %3s, Mode %d", dtxEnabled ? "ON" : "OFF", | |
| vadEnabled ? "ON" : "OFF", (int) vadMode); | |
| Wait(5000); | |
| fprintf(stdout, " => bit-rate %3.0f kbps\n", _channel_A2B->BitRate()); | |
| } else { | |
| Wait(5000); | |
| fprintf(stdout, "DTX %3s, VAD %3s, Mode %d => bit-rate %3.0f kbps\n", | |
| dtxEnabled ? "ON" : "OFF", vadEnabled ? "ON" : "OFF", | |
| (int) vadMode, _channel_A2B->BitRate()); | |
| } | |
| _vadCallbackA->PrintFrameTypes(); | |
| } | |
| if (dtxEnabled != _sendDTXA) { | |
| fprintf(stderr, ">>> Error Enabling DTX <<<\n"); | |
| } | |
| if ((vadEnabled != _sendVADA) && (!dtxEnabled)) { | |
| fprintf(stderr, ">>> Error Enabling VAD <<<\n"); | |
| } | |
| if ((vadMode != _sendVADModeA) && vadEnabled) { | |
| fprintf(stderr, ">>> Error setting VAD-mode <<<\n"); | |
| } | |
| } else { | |
| _acmB->VAD(&dtxEnabled, &vadEnabled, &vadMode); | |
| _acmB->RegisterVADCallback(NULL); | |
| _vadCallbackB->Reset(); | |
| _acmB->RegisterVADCallback(_vadCallbackB); | |
| if (!_randomTest) { | |
| if (_verbose) { | |
| fprintf(stdout, "DTX %3s, VAD %3s, Mode %d", dtxEnabled ? "ON" : "OFF", | |
| vadEnabled ? "ON" : "OFF", (int) vadMode); | |
| Wait(5000); | |
| fprintf(stdout, " => bit-rate %3.0f kbps\n", _channel_B2A->BitRate()); | |
| } else { | |
| Wait(5000); | |
| fprintf(stdout, "DTX %3s, VAD %3s, Mode %d => bit-rate %3.0f kbps\n", | |
| dtxEnabled ? "ON" : "OFF", vadEnabled ? "ON" : "OFF", | |
| (int) vadMode, _channel_B2A->BitRate()); | |
| } | |
| _vadCallbackB->PrintFrameTypes(); | |
| } | |
| if (dtxEnabled != _sendDTXB) { | |
| fprintf(stderr, ">>> Error Enabling DTX <<<\n"); | |
| } | |
| if ((vadEnabled != _sendVADB) && (!dtxEnabled)) { | |
| fprintf(stderr, ">>> Error Enabling VAD <<<\n"); | |
| } | |
| if ((vadMode != _sendVADModeB) && vadEnabled) { | |
| fprintf(stderr, ">>> Error setting VAD-mode <<<\n"); | |
| } | |
| } | |
| } | |
| // Set Min delay, get delay, playout timestamp | |
| void APITest::TestDelay(char side) { | |
| AudioCodingModule* myACM; | |
| Channel* myChannel; | |
| int32_t* myMinDelay; | |
| EventTimerWrapper* myEvent = EventTimerWrapper::Create(); | |
| uint32_t inTimestamp = 0; | |
| double estimDelay = 0; | |
| double averageEstimDelay = 0; | |
| double averageDelay = 0; | |
| test::CircularBuffer estimDelayCB(100); | |
| estimDelayCB.SetArithMean(true); | |
| if (side == 'A') { | |
| myACM = _acmA.get(); | |
| myChannel = _channel_B2A; | |
| myMinDelay = &_minDelayA; | |
| } else { | |
| myACM = _acmB.get(); | |
| myChannel = _channel_A2B; | |
| myMinDelay = &_minDelayB; | |
| } | |
| CHECK_ERROR_MT(myACM->SetMinimumPlayoutDelay(*myMinDelay)); | |
| inTimestamp = myChannel->LastInTimestamp(); | |
| rtc::Optional<uint32_t> outTimestamp = myACM->PlayoutTimestamp(); | |
| CHECK_ERROR_MT(outTimestamp ? 0 : -1); | |
| if (!_randomTest) { | |
| myEvent->StartTimer(true, 30); | |
| int n = 0; | |
| int settlePoint = 5000; | |
| while (n < settlePoint + 400) { | |
| myEvent->Wait(1000); | |
| inTimestamp = myChannel->LastInTimestamp(); | |
| outTimestamp = myACM->PlayoutTimestamp(); | |
| CHECK_ERROR_MT(outTimestamp ? 0 : -1); | |
| //std::cout << outTimestamp << std::endl << std::flush; | |
| estimDelay = (double)((uint32_t)(inTimestamp - *outTimestamp)) / | |
| ((double)myACM->ReceiveFrequency() / 1000.0); | |
| estimDelayCB.Update(estimDelay); | |
| estimDelayCB.ArithMean(averageEstimDelay); | |
| //printf("\n %6.1f \n", estimDelay); | |
| //std::cout << " " << std::flush; | |
| if (_verbose) { | |
| fprintf(stdout, | |
| "\rExpected: %4d, retreived: %6.1f, measured: %6.1f", | |
| *myMinDelay, averageDelay, averageEstimDelay); | |
| std::cout << " " << std::flush; | |
| } | |
| if ((averageDelay > *myMinDelay) && (n < settlePoint)) { | |
| settlePoint = n; | |
| } | |
| n++; | |
| } | |
| myEvent->StopTimer(); | |
| } | |
| if ((!_verbose) && (!_randomTest)) { | |
| fprintf(stdout, "\nExpected: %4d, retreived: %6.1f, measured: %6.1f", | |
| *myMinDelay, averageDelay, averageEstimDelay); | |
| } | |
| *myMinDelay = (rand() % 1000) + 1; | |
| NetworkStatistics networkStat; | |
| CHECK_ERROR_MT(myACM->GetNetworkStatistics(&networkStat)); | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n\nJitter Statistics at Side %c\n", side); | |
| fprintf(stdout, "--------------------------------------\n"); | |
| fprintf(stdout, "buffer-size............. %d\n", | |
| networkStat.currentBufferSize); | |
| fprintf(stdout, "Preferred buffer-size... %d\n", | |
| networkStat.preferredBufferSize); | |
| fprintf(stdout, "Peaky jitter mode........%d\n", | |
| networkStat.jitterPeaksFound); | |
| fprintf(stdout, "packet-size rate........ %d\n", | |
| networkStat.currentPacketLossRate); | |
| fprintf(stdout, "expand rate............. %d\n", | |
| networkStat.currentExpandRate); | |
| fprintf(stdout, "speech expand rate...... %d\n", | |
| networkStat.currentSpeechExpandRate); | |
| fprintf(stdout, "Preemptive rate......... %d\n", | |
| networkStat.currentPreemptiveRate); | |
| fprintf(stdout, "Accelerate rate......... %d\n", | |
| networkStat.currentAccelerateRate); | |
| fprintf(stdout, "Secondary decoded rate.. %d\n", | |
| networkStat.currentSecondaryDecodedRate); | |
| fprintf(stdout, "Secondary discarded rate.%d\n", | |
| networkStat.currentSecondaryDiscardedRate); | |
| fprintf(stdout, "Clock-drift............. %d\n", networkStat.clockDriftPPM); | |
| fprintf(stdout, "Mean waiting time....... %d\n", | |
| networkStat.meanWaitingTimeMs); | |
| fprintf(stdout, "Median waiting time..... %d\n", | |
| networkStat.medianWaitingTimeMs); | |
| fprintf(stdout, "Min waiting time........ %d\n", | |
| networkStat.minWaitingTimeMs); | |
| fprintf(stdout, "Max waiting time........ %d\n", | |
| networkStat.maxWaitingTimeMs); | |
| } | |
| CHECK_ERROR_MT(myACM->SetMinimumPlayoutDelay(*myMinDelay)); | |
| if (!_randomTest) { | |
| myEvent->Wait(500); | |
| fprintf(stdout, "\n"); | |
| fprintf(stdout, "\n"); | |
| } | |
| delete myEvent; | |
| } | |
| // Unregister a codec & register again. | |
| void APITest::TestRegisteration(char sendSide) { | |
| AudioCodingModule* sendACM; | |
| AudioCodingModule* receiveACM; | |
| bool* thereIsDecoder; | |
| EventWrapper* myEvent = EventWrapper::Create(); | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n\n"); | |
| fprintf(stdout, | |
| "---------------------------------------------------------\n"); | |
| fprintf(stdout, " Unregister/register Receive Codec\n"); | |
| fprintf(stdout, | |
| "---------------------------------------------------------\n"); | |
| } | |
| switch (sendSide) { | |
| case 'A': { | |
| sendACM = _acmA.get(); | |
| receiveACM = _acmB.get(); | |
| thereIsDecoder = &_thereIsDecoderB; | |
| break; | |
| } | |
| case 'B': { | |
| sendACM = _acmB.get(); | |
| receiveACM = _acmA.get(); | |
| thereIsDecoder = &_thereIsDecoderA; | |
| break; | |
| } | |
| default: | |
| fprintf(stderr, "Invalid sender-side in TestRegistration(%c)\n", | |
| sendSide); | |
| exit(-1); | |
| } | |
| auto myCodec = sendACM->SendCodec(); | |
| if (!myCodec) { | |
| CodecInst ci; | |
| AudioCodingModule::Codec(_codecCntrA, &ci); | |
| myCodec = ci; | |
| } | |
| if (!_randomTest) { | |
| fprintf(stdout, "Unregistering reveive codec, NO AUDIO.\n"); | |
| fflush (stdout); | |
| } | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsDecoder = false; | |
| } | |
| //myEvent->Wait(20); | |
| CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec->pltype)); | |
| Wait(1000); | |
| int currentPayload = myCodec->pltype; | |
| if (!FixedPayloadTypeCodec(myCodec->plname)) { | |
| int32_t i; | |
| for (i = 0; i < 32; i++) { | |
| if (!_payloadUsed[i]) { | |
| if (!_randomTest) { | |
| fprintf(stdout, | |
| "Register receive codec with new Payload, AUDIO BACK.\n"); | |
| } | |
| //myCodec->pltype = i + 96; | |
| //CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(*myCodec)); | |
| //CHECK_ERROR_MT(sendACM->RegisterSendCodec(*myCodec)); | |
| //myEvent->Wait(20); | |
| //{ | |
| // WriteLockScoped wl(_apiTestRWLock); | |
| // *thereIsDecoder = true; | |
| //} | |
| Wait(1000); | |
| if (!_randomTest) { | |
| fprintf(stdout, "Unregistering reveive codec, NO AUDIO.\n"); | |
| } | |
| //{ | |
| // WriteLockScoped wl(_apiTestRWLock); | |
| // *thereIsDecoder = false; | |
| //} | |
| //myEvent->Wait(20); | |
| //CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec->pltype)); | |
| Wait(1000); | |
| myCodec->pltype = currentPayload; | |
| if (!_randomTest) { | |
| fprintf(stdout, | |
| "Register receive codec with default Payload, AUDIO BACK.\n"); | |
| fflush (stdout); | |
| } | |
| EXPECT_EQ(true, receiveACM->RegisterReceiveCodec( | |
| myCodec->pltype, CodecInstToSdp(*myCodec))); | |
| //CHECK_ERROR_MT(sendACM->RegisterSendCodec(*myCodec)); | |
| myEvent->Wait(20); | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsDecoder = true; | |
| } | |
| Wait(1000); | |
| break; | |
| } | |
| } | |
| if (i == 32) { | |
| EXPECT_EQ(true, receiveACM->RegisterReceiveCodec( | |
| myCodec->pltype, CodecInstToSdp(*myCodec))); | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsDecoder = true; | |
| } | |
| } | |
| } else { | |
| if (!_randomTest) { | |
| fprintf(stdout, | |
| "Register receive codec with fixed Payload, AUDIO BACK.\n"); | |
| fflush (stdout); | |
| } | |
| EXPECT_EQ(true, receiveACM->RegisterReceiveCodec(myCodec->pltype, | |
| CodecInstToSdp(*myCodec))); | |
| //CHECK_ERROR_MT(receiveACM->UnregisterReceiveCodec(myCodec->pltype)); | |
| //CHECK_ERROR_MT(receiveACM->RegisterReceiveCodec(*myCodec)); | |
| myEvent->Wait(20); | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsDecoder = true; | |
| } | |
| } | |
| delete myEvent; | |
| if (!_randomTest) { | |
| fprintf(stdout, | |
| "---------------------------------------------------------\n"); | |
| } | |
| } | |
| void APITest::TestSendVAD(char side) { | |
| if (_randomTest) { | |
| return; | |
| } | |
| bool* vad; | |
| bool* dtx; | |
| ACMVADMode* mode; | |
| Channel* myChannel; | |
| AudioCodingModule* myACM; | |
| CodecInst myCodec; | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n\n"); | |
| fprintf(stdout, "-----------------------------------------------\n"); | |
| fprintf(stdout, " Test VAD API\n"); | |
| fprintf(stdout, "-----------------------------------------------\n"); | |
| } | |
| if (side == 'A') { | |
| AudioCodingModule::Codec(_codecCntrA, &myCodec); | |
| vad = &_sendVADA; | |
| dtx = &_sendDTXA; | |
| mode = &_sendVADModeA; | |
| myChannel = _channel_A2B; | |
| myACM = _acmA.get(); | |
| } else { | |
| AudioCodingModule::Codec(_codecCntrB, &myCodec); | |
| vad = &_sendVADB; | |
| dtx = &_sendDTXB; | |
| mode = &_sendVADModeB; | |
| myChannel = _channel_B2A; | |
| myACM = _acmB.get(); | |
| } | |
| CheckVADStatus(side); | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n\n"); | |
| } | |
| switch (*mode) { | |
| case VADNormal: | |
| *vad = true; | |
| *dtx = true; | |
| *mode = VADAggr; | |
| break; | |
| case VADLowBitrate: | |
| *vad = true; | |
| *dtx = true; | |
| *mode = VADVeryAggr; | |
| break; | |
| case VADAggr: | |
| *vad = true; | |
| *dtx = true; | |
| *mode = VADLowBitrate; | |
| break; | |
| case VADVeryAggr: | |
| *vad = false; | |
| *dtx = false; | |
| *mode = VADNormal; | |
| break; | |
| default: | |
| *mode = VADNormal; | |
| } | |
| *dtx = (myCodec.plfreq == 32000) ? false : *dtx; | |
| CHECK_ERROR_MT(myACM->SetVAD(*dtx, *vad, *mode)); | |
| myChannel->ResetStats(); | |
| CheckVADStatus(side); | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n"); | |
| fprintf(stdout, "-----------------------------------------------\n"); | |
| } | |
| // Fault Test | |
| CHECK_PROTECTED_MT(myACM->SetVAD(false, true, (ACMVADMode) - 1)); | |
| CHECK_PROTECTED_MT(myACM->SetVAD(false, true, (ACMVADMode) 4)); | |
| } | |
| void APITest::CurrentCodec(char side) { | |
| auto myCodec = (side == 'A' ? _acmA : _acmB)->SendCodec(); | |
| if (!_randomTest) { | |
| fprintf(stdout, "\n\n"); | |
| fprintf(stdout, "Send codec in Side A\n"); | |
| fprintf(stdout, "----------------------------\n"); | |
| fprintf(stdout, "Name................. %s\n", myCodec->plname); | |
| fprintf(stdout, "Sampling Frequency... %d\n", myCodec->plfreq); | |
| fprintf(stdout, "Rate................. %d\n", myCodec->rate); | |
| fprintf(stdout, "Payload-type......... %d\n", myCodec->pltype); | |
| fprintf(stdout, "Packet-size.......... %d\n", myCodec->pacsize); | |
| } | |
| Wait(100); | |
| } | |
| void APITest::ChangeCodec(char side) { | |
| CodecInst myCodec; | |
| AudioCodingModule* myACM; | |
| uint8_t* codecCntr; | |
| bool* thereIsEncoder; | |
| bool* vad; | |
| bool* dtx; | |
| ACMVADMode* mode; | |
| Channel* myChannel; | |
| // Reset and Wait | |
| if (!_randomTest) { | |
| fprintf(stdout, "Reset Encoder Side A \n"); | |
| } | |
| if (side == 'A') { | |
| myACM = _acmA.get(); | |
| codecCntr = &_codecCntrA; | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| thereIsEncoder = &_thereIsEncoderA; | |
| } | |
| vad = &_sendVADA; | |
| dtx = &_sendDTXA; | |
| mode = &_sendVADModeA; | |
| myChannel = _channel_A2B; | |
| } else { | |
| myACM = _acmB.get(); | |
| codecCntr = &_codecCntrB; | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| thereIsEncoder = &_thereIsEncoderB; | |
| } | |
| vad = &_sendVADB; | |
| dtx = &_sendDTXB; | |
| mode = &_sendVADModeB; | |
| myChannel = _channel_B2A; | |
| } | |
| Wait(100); | |
| // Register the next codec | |
| do { | |
| *codecCntr = | |
| (*codecCntr < AudioCodingModule::NumberOfCodecs() - 1) ? | |
| (*codecCntr + 1) : 0; | |
| if (*codecCntr == 0) { | |
| //printf("Initialize Sender Side A \n"); | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsEncoder = false; | |
| } | |
| // After Initialization CN is lost, re-register them | |
| if (AudioCodingModule::Codec("CN", &myCodec, 8000, 1) >= 0) { | |
| CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec)); | |
| } | |
| if (AudioCodingModule::Codec("CN", &myCodec, 16000, 1) >= 0) { | |
| CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec)); | |
| } | |
| // VAD & DTX are disabled after initialization | |
| *vad = false; | |
| *dtx = false; | |
| _writeToFile = false; | |
| } | |
| AudioCodingModule::Codec(*codecCntr, &myCodec); | |
| } while (!STR_CASE_CMP(myCodec.plname, "CN") | |
| || !STR_CASE_CMP(myCodec.plname, "telephone-event") | |
| || !STR_CASE_CMP(myCodec.plname, "RED")); | |
| if (!_randomTest) { | |
| fprintf(stdout,"\n=====================================================\n"); | |
| fprintf(stdout, " Registering New Codec %s, %d kHz, %d kbps\n", | |
| myCodec.plname, myCodec.plfreq / 1000, myCodec.rate / 1000); | |
| } | |
| //std::cout<< std::flush; | |
| // NO DTX for supe-wideband codec at this point | |
| if (myCodec.plfreq == 32000) { | |
| *dtx = false; | |
| CHECK_ERROR_MT(myACM->SetVAD(*dtx, *vad, *mode)); | |
| } | |
| CHECK_ERROR_MT(myACM->RegisterSendCodec(myCodec)); | |
| myChannel->ResetStats(); | |
| { | |
| WriteLockScoped wl(_apiTestRWLock); | |
| *thereIsEncoder = true; | |
| } | |
| Wait(500); | |
| } | |
| } // namespace webrtc |