| /* |
| * Copyright (c) 2011 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 <algorithm> // sort |
| #include <stdlib.h> // malloc |
| #include <vector> |
| |
| #include "acm_neteq.h" |
| #include "common_types.h" |
| #include "critical_section_wrapper.h" |
| #include "rw_lock_wrapper.h" |
| #include "signal_processing_library.h" |
| #include "tick_util.h" |
| #include "trace.h" |
| #include "webrtc_neteq.h" |
| #include "webrtc_neteq_internal.h" |
| |
| namespace webrtc |
| { |
| |
| #define RTP_HEADER_SIZE 12 |
| #define NETEQ_INIT_FREQ 8000 |
| #define NETEQ_INIT_FREQ_KHZ (NETEQ_INIT_FREQ/1000) |
| #define NETEQ_ERR_MSG_LEN_BYTE (WEBRTC_NETEQ_MAX_ERROR_NAME + 1) |
| |
| |
| ACMNetEQ::ACMNetEQ() |
| : |
| _id(0), |
| _currentSampFreqKHz(NETEQ_INIT_FREQ_KHZ), |
| _avtPlayout(false), |
| _playoutMode(voice), |
| _netEqCritSect(CriticalSectionWrapper::CreateCriticalSection()), |
| _vadStatus(false), |
| _vadMode(VADNormal), |
| _decodeLock(RWLockWrapper::CreateRWLock()), |
| _numSlaves(0), |
| _receivedStereo(false), |
| _masterSlaveInfo(NULL), |
| _previousAudioActivity(AudioFrame::kVadUnknown), |
| _extraDelay(0), |
| _callbackCritSect(CriticalSectionWrapper::CreateCriticalSection()) |
| { |
| for(int n = 0; n < MAX_NUM_SLAVE_NETEQ + 1; n++) |
| { |
| _isInitialized[n] = false; |
| _ptrVADInst[n] = NULL; |
| _inst[n] = NULL; |
| _instMem[n] = NULL; |
| _netEqPacketBuffer[n] = NULL; |
| } |
| } |
| |
| ACMNetEQ::~ACMNetEQ() |
| { |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if (_instMem[idx] != NULL) |
| { |
| free(_instMem[idx]); |
| _instMem[idx] = NULL; |
| } |
| if (_netEqPacketBuffer[idx] != NULL) |
| { |
| free(_netEqPacketBuffer[idx]); |
| _netEqPacketBuffer[idx] = NULL; |
| } |
| if(_ptrVADInst[idx] != NULL) |
| { |
| WebRtcVad_Free(_ptrVADInst[idx]); |
| _ptrVADInst[idx] = NULL; |
| } |
| } |
| if(_masterSlaveInfo != NULL) |
| { |
| free(_masterSlaveInfo); |
| _masterSlaveInfo = NULL; |
| } |
| } |
| if(_netEqCritSect != NULL) |
| { |
| delete _netEqCritSect; |
| } |
| |
| if(_decodeLock != NULL) |
| { |
| delete _decodeLock; |
| } |
| |
| if(_callbackCritSect != NULL) |
| { |
| delete _callbackCritSect; |
| } |
| } |
| |
| WebRtc_Word32 |
| ACMNetEQ::Init() |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(InitByIdxSafe(idx) < 0) |
| { |
| return -1; |
| } |
| // delete VAD instance and start fresh if required. |
| if(_ptrVADInst[idx] != NULL) |
| { |
| WebRtcVad_Free(_ptrVADInst[idx]); |
| _ptrVADInst[idx] = NULL; |
| } |
| if(_vadStatus) |
| { |
| // Has to enable VAD |
| if(EnableVADByIdxSafe(idx) < 0) |
| { |
| // Failed to enable VAD. |
| // Delete VAD instance, if it is created |
| if(_ptrVADInst[idx] != NULL) |
| { |
| WebRtcVad_Free(_ptrVADInst[idx]); |
| _ptrVADInst[idx] = NULL; |
| } |
| // We are at initialization of NetEq, if failed to |
| // enable VAD, we delete the NetEq instance. |
| if (_instMem[idx] != NULL) { |
| free(_instMem[idx]); |
| _instMem[idx] = NULL; |
| _inst[idx] = NULL; |
| } |
| _isInitialized[idx] = false; |
| return -1; |
| } |
| } |
| _isInitialized[idx] = true; |
| } |
| if (EnableVAD() == -1) |
| { |
| return -1; |
| } |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::InitByIdxSafe( |
| const WebRtc_Word16 idx) |
| { |
| int memorySizeBytes; |
| if (WebRtcNetEQ_AssignSize(&memorySizeBytes) != 0) |
| { |
| LogError("AssignSize", idx); |
| return -1; |
| } |
| |
| if(_instMem[idx] != NULL) |
| { |
| free(_instMem[idx]); |
| _instMem[idx] = NULL; |
| } |
| _instMem[idx] = malloc(memorySizeBytes); |
| if (_instMem[idx] == NULL) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "InitByIdxSafe: NetEq Initialization error: could not allocate memory for NetEq"); |
| _isInitialized[idx] = false; |
| return -1; |
| } |
| if (WebRtcNetEQ_Assign(&_inst[idx], _instMem[idx]) != 0) |
| { |
| if (_instMem[idx] != NULL) { |
| free(_instMem[idx]); |
| _instMem[idx] = NULL; |
| } |
| LogError("Assign", idx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "InitByIdxSafe: NetEq Initialization error: could not Assign"); |
| _isInitialized[idx] = false; |
| return -1; |
| } |
| if (WebRtcNetEQ_Init(_inst[idx], NETEQ_INIT_FREQ) != 0) |
| { |
| if (_instMem[idx] != NULL) { |
| free(_instMem[idx]); |
| _instMem[idx] = NULL; |
| } |
| LogError("Init", idx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "InitByIdxSafe: NetEq Initialization error: could not initialize NetEq"); |
| _isInitialized[idx] = false; |
| return -1; |
| } |
| _isInitialized[idx] = true; |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::EnableVADByIdxSafe( |
| const WebRtc_Word16 idx) |
| { |
| if(_ptrVADInst[idx] == NULL) |
| { |
| if(WebRtcVad_Create(&_ptrVADInst[idx]) < 0) |
| { |
| _ptrVADInst[idx] = NULL; |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "EnableVADByIdxSafe: NetEq Initialization error: could not create VAD"); |
| return -1; |
| } |
| } |
| |
| if(WebRtcNetEQ_SetVADInstance(_inst[idx], _ptrVADInst[idx], |
| (WebRtcNetEQ_VADInitFunction) WebRtcVad_Init, |
| (WebRtcNetEQ_VADSetmodeFunction) WebRtcVad_set_mode, |
| (WebRtcNetEQ_VADFunction) WebRtcVad_Process) < 0) |
| { |
| LogError("setVADinstance", idx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "EnableVADByIdxSafe: NetEq Initialization error: could not set VAD instance"); |
| return -1; |
| } |
| |
| if(WebRtcNetEQ_SetVADMode(_inst[idx], _vadMode) < 0) |
| { |
| LogError("setVADmode", idx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "EnableVADByIdxSafe: NetEq Initialization error: could not set VAD mode"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::AllocatePacketBuffer( |
| const WebRtcNetEQDecoder* usedCodecs, |
| WebRtc_Word16 noOfCodecs) |
| { |
| // Due to WebRtcNetEQ_GetRecommendedBufferSize |
| // the following has to be int otherwise we will have compiler error |
| // if not casted |
| |
| CriticalSectionScoped lock(*_netEqCritSect); |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(AllocatePacketBufferByIdxSafe(usedCodecs, noOfCodecs, idx) < 0) |
| { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::AllocatePacketBufferByIdxSafe( |
| const WebRtcNetEQDecoder* usedCodecs, |
| WebRtc_Word16 noOfCodecs, |
| const WebRtc_Word16 idx) |
| { |
| int maxNoPackets; |
| int bufferSizeInBytes; |
| |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AllocatePacketBufferByIdxSafe: NetEq is not initialized."); |
| return -1; |
| } |
| if (WebRtcNetEQ_GetRecommendedBufferSize(_inst[idx], usedCodecs, noOfCodecs, |
| kTCPLargeJitter , &maxNoPackets, &bufferSizeInBytes) |
| != 0) |
| { |
| LogError("GetRecommendedBufferSize", idx); |
| return -1; |
| } |
| if(_netEqPacketBuffer[idx] != NULL) |
| { |
| free(_netEqPacketBuffer[idx]); |
| _netEqPacketBuffer[idx] = NULL; |
| } |
| |
| _netEqPacketBuffer[idx] = (WebRtc_Word16 *)malloc(bufferSizeInBytes); |
| if (_netEqPacketBuffer[idx] == NULL) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AllocatePacketBufferByIdxSafe: NetEq Initialization error: could not allocate " |
| "memory for NetEq Packet Buffer"); |
| return -1; |
| |
| } |
| if (WebRtcNetEQ_AssignBuffer(_inst[idx], maxNoPackets, _netEqPacketBuffer[idx], |
| bufferSizeInBytes) != 0) |
| { |
| if (_netEqPacketBuffer[idx] != NULL) { |
| free(_netEqPacketBuffer[idx]); |
| _netEqPacketBuffer[idx] = NULL; |
| } |
| LogError("AssignBuffer", idx); |
| return -1; |
| } |
| return 0; |
| } |
| |
| |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::SetExtraDelay( |
| const WebRtc_Word32 delayInMS) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetExtraDelay: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetExtraDelay(_inst[idx], delayInMS) < 0) |
| { |
| LogError("SetExtraDelay", idx); |
| return -1; |
| } |
| } |
| _extraDelay = delayInMS; |
| return 0; |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::SetAVTPlayout( |
| const bool enable) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if (_avtPlayout != enable) |
| { |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetAVTPlayout: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetAVTPlayout(_inst[idx], (enable) ? 1 : 0) < 0) |
| { |
| LogError("SetAVTPlayout", idx); |
| return -1; |
| } |
| } |
| } |
| _avtPlayout = enable; |
| return 0; |
| } |
| |
| |
| bool |
| ACMNetEQ::AVTPlayout() const |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| return _avtPlayout; |
| } |
| |
| WebRtc_Word32 |
| ACMNetEQ::CurrentSampFreqHz() const |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "CurrentSampFreqHz: NetEq is not initialized."); |
| return -1; |
| } |
| return (WebRtc_Word32)(1000*_currentSampFreqKHz); |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::SetPlayoutMode( |
| const AudioPlayoutMode mode) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(_playoutMode != mode) |
| { |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetPlayoutMode: NetEq is not initialized."); |
| return -1; |
| } |
| |
| enum WebRtcNetEQPlayoutMode playoutMode; |
| switch(mode) |
| { |
| case voice: |
| playoutMode = kPlayoutOn; |
| break; |
| case fax: |
| playoutMode = kPlayoutFax; |
| break; |
| case streaming: |
| playoutMode = kPlayoutStreaming; |
| break; |
| default: |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetPlayoutMode: NetEq Error playout mode not recognized"); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetPlayoutMode(_inst[idx], playoutMode) < 0) |
| { |
| LogError("SetPlayoutMode", idx); |
| return -1; |
| } |
| } |
| _playoutMode = mode; |
| } |
| |
| return 0; |
| } |
| |
| AudioPlayoutMode |
| ACMNetEQ::PlayoutMode() const |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| return _playoutMode; |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::NetworkStatistics( |
| ACMNetworkStatistics* statistics) const |
| { |
| WebRtcNetEQ_NetworkStatistics stats; |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "NetworkStatistics: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_GetNetworkStatistics(_inst[0], &stats) == 0) |
| { |
| statistics->currentAccelerateRate = stats.currentAccelerateRate; |
| statistics->currentBufferSize = stats.currentBufferSize; |
| statistics->jitterPeaksFound = (stats.jitterPeaksFound > 0); |
| statistics->currentDiscardRate = stats.currentDiscardRate; |
| statistics->currentExpandRate = stats.currentExpandRate; |
| statistics->currentPacketLossRate = stats.currentPacketLossRate; |
| statistics->currentPreemptiveRate = stats.currentPreemptiveRate; |
| statistics->preferredBufferSize = stats.preferredBufferSize; |
| statistics->clockDriftPPM = stats.clockDriftPPM; |
| } |
| else |
| { |
| LogError("getNetworkStatistics", 0); |
| return -1; |
| } |
| const int kArrayLen = 100; |
| int waiting_times[kArrayLen]; |
| int waiting_times_len = WebRtcNetEQ_GetRawFrameWaitingTimes( |
| _inst[0], kArrayLen, waiting_times); |
| if (waiting_times_len > 0) |
| { |
| std::vector<int> waiting_times_vec(waiting_times, |
| waiting_times + waiting_times_len); |
| std::sort(waiting_times_vec.begin(), waiting_times_vec.end()); |
| size_t size = waiting_times_vec.size(); |
| assert(size == static_cast<size_t>(waiting_times_len)); |
| if (size % 2 == 0) |
| { |
| statistics->medianWaitingTimeMs = |
| (waiting_times_vec[size / 2 - 1] + |
| waiting_times_vec[size / 2]) / 2; |
| } |
| else |
| { |
| statistics->medianWaitingTimeMs = waiting_times_vec[size / 2]; |
| } |
| statistics->minWaitingTimeMs = waiting_times_vec.front(); |
| statistics->maxWaitingTimeMs = waiting_times_vec.back(); |
| double sum = 0; |
| for (size_t i = 0; i < size; ++i) { |
| sum += waiting_times_vec[i]; |
| } |
| statistics->meanWaitingTimeMs = static_cast<int>(sum / size); |
| } |
| else if (waiting_times_len == 0) |
| { |
| statistics->meanWaitingTimeMs = -1; |
| statistics->medianWaitingTimeMs = -1; |
| statistics->minWaitingTimeMs = -1; |
| statistics->maxWaitingTimeMs = -1; |
| } |
| else |
| { |
| LogError("getRawFrameWaitingTimes", 0); |
| return -1; |
| } |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| ACMNetEQ::RecIn( |
| const WebRtc_Word8* incomingPayload, |
| const WebRtc_Word32 payloadLength, |
| const WebRtcRTPHeader& rtpInfo) |
| { |
| // translate to NetEq struct |
| WebRtcNetEQ_RTPInfo netEqRTPInfo; |
| netEqRTPInfo.payloadType = rtpInfo.header.payloadType; |
| netEqRTPInfo.sequenceNumber = rtpInfo.header.sequenceNumber; |
| netEqRTPInfo.timeStamp = rtpInfo.header.timestamp; |
| netEqRTPInfo.SSRC = rtpInfo.header.ssrc; |
| netEqRTPInfo.markerBit = rtpInfo.header.markerBit; |
| |
| CriticalSectionScoped lock(*_netEqCritSect); |
| // Down-cast the time to (32-6)-bit since we only care about |
| // the least significant bits. (32-6) bits cover 2^(32-6) = 67108864 ms. |
| // we masked 6 most significant bits of 32-bit so we don't loose resolution |
| // when do the following multiplication. |
| const WebRtc_UWord32 nowInMs = static_cast<WebRtc_UWord32>( |
| TickTime::MillisecondTimestamp() & 0x03ffffff); |
| WebRtc_UWord32 recvTimestamp = static_cast<WebRtc_UWord32> |
| (_currentSampFreqKHz * nowInMs); |
| |
| int status; |
| |
| if(rtpInfo.type.Audio.channel == 1) |
| { |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecIn: NetEq is not initialized."); |
| return -1; |
| } |
| // PUSH into Master |
| status = WebRtcNetEQ_RecInRTPStruct(_inst[0], &netEqRTPInfo, |
| (WebRtc_UWord8 *)incomingPayload, (WebRtc_Word16)payloadLength, |
| recvTimestamp); |
| if(status < 0) |
| { |
| LogError("RecInRTPStruct", 0); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecIn: NetEq, error in pushing in Master"); |
| return -1; |
| } |
| } |
| else if(rtpInfo.type.Audio.channel == 2) |
| { |
| if(!_isInitialized[1]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecIn: NetEq is not initialized."); |
| return -1; |
| } |
| // PUSH into Slave |
| status = WebRtcNetEQ_RecInRTPStruct(_inst[1], &netEqRTPInfo, |
| (WebRtc_UWord8 *)incomingPayload, (WebRtc_Word16)payloadLength, |
| recvTimestamp); |
| if(status < 0) |
| { |
| LogError("RecInRTPStruct", 1); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecIn: NetEq, error in pushing in Slave"); |
| return -1; |
| } |
| } |
| else |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecIn: NetEq, error invalid numbe of channels %d \ |
| (1, for Master stream, and 2, for slave stream, are valid values)", |
| rtpInfo.type.Audio.channel); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word32 |
| ACMNetEQ::RecOut( |
| AudioFrame& audioFrame) |
| { |
| enum WebRtcNetEQOutputType type; |
| WebRtc_Word16 payloadLenSample; |
| enum WebRtcNetEQOutputType typeMaster; |
| enum WebRtcNetEQOutputType typeSlave; |
| |
| WebRtc_Word16 payloadLenSampleSlave; |
| |
| CriticalSectionScoped lockNetEq(*_netEqCritSect); |
| |
| if(!_receivedStereo) |
| { |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq is not initialized."); |
| return -1; |
| } |
| { |
| WriteLockScoped lockCodec(*_decodeLock); |
| if(WebRtcNetEQ_RecOut(_inst[0], &(audioFrame._payloadData[0]), |
| &payloadLenSample) != 0) |
| { |
| LogError("RecOut", 0); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq, error in pulling out for mono case"); |
| |
| // Check for errors that can be recovered from: |
| // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 |
| int errorCode = WebRtcNetEQ_GetErrorCode(_inst[0]); |
| if(errorCode != 2003) |
| { |
| // Cannot recover; return an error |
| return -1; |
| } |
| } |
| } |
| WebRtcNetEQ_GetSpeechOutputType(_inst[0], &type); |
| audioFrame._audioChannel = 1; |
| } |
| else |
| { |
| if(!_isInitialized[0] || !_isInitialized[1]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq is not initialized."); |
| return -1; |
| } |
| WebRtc_Word16 payloadMaster[480]; |
| WebRtc_Word16 payloadSlave[480]; |
| { |
| WriteLockScoped lockCodec(*_decodeLock); |
| if(WebRtcNetEQ_RecOutMasterSlave(_inst[0], payloadMaster, |
| &payloadLenSample, _masterSlaveInfo, 1) != 0) |
| { |
| LogError("RecOutMasterSlave", 0); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq, error in pulling out for master"); |
| |
| // Check for errors that can be recovered from: |
| // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 |
| int errorCode = WebRtcNetEQ_GetErrorCode(_inst[0]); |
| if(errorCode != 2003) |
| { |
| // Cannot recover; return an error |
| return -1; |
| } |
| } |
| if(WebRtcNetEQ_RecOutMasterSlave(_inst[1], payloadSlave, |
| &payloadLenSampleSlave, _masterSlaveInfo, 0) != 0) |
| { |
| LogError("RecOutMasterSlave", 1); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq, error in pulling out for slave"); |
| |
| // Check for errors that can be recovered from: |
| // RECOUT_ERROR_SAMPLEUNDERRUN = 2003 |
| int errorCode = WebRtcNetEQ_GetErrorCode(_inst[0]); |
| if(errorCode != 2003) |
| { |
| // Cannot recover; return an error |
| return -1; |
| } |
| } |
| } |
| if(payloadLenSample != payloadLenSampleSlave) |
| { |
| WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, _id, |
| "RecOut: mismatch between the lenght of the decoded \ |
| audio by Master (%d samples) and Slave (%d samples).", |
| payloadLenSample, payloadLenSampleSlave); |
| if(payloadLenSample > payloadLenSampleSlave) |
| { |
| memset(&payloadSlave[payloadLenSampleSlave], 0, |
| (payloadLenSample - payloadLenSampleSlave) * sizeof(WebRtc_Word16)); |
| } |
| } |
| |
| for(WebRtc_Word16 n = 0; n < payloadLenSample; n++) |
| { |
| audioFrame._payloadData[n<<1] = payloadMaster[n]; |
| audioFrame._payloadData[(n<<1)+1] = payloadSlave[n]; |
| } |
| audioFrame._audioChannel = 2; |
| |
| WebRtcNetEQ_GetSpeechOutputType(_inst[0], &typeMaster); |
| WebRtcNetEQ_GetSpeechOutputType(_inst[1], &typeSlave); |
| if((typeMaster == kOutputNormal) || |
| (typeSlave == kOutputNormal)) |
| { |
| type = kOutputNormal; |
| } |
| else |
| { |
| type = typeMaster; |
| } |
| } |
| |
| audioFrame._payloadDataLengthInSamples = static_cast<WebRtc_UWord16>(payloadLenSample); |
| // NetEq always returns 10 ms of audio. |
| _currentSampFreqKHz = static_cast<float>(audioFrame._payloadDataLengthInSamples) / 10.0f; |
| audioFrame._frequencyInHz = audioFrame._payloadDataLengthInSamples * 100; |
| if(_vadStatus) |
| { |
| if(type == kOutputVADPassive) |
| { |
| audioFrame._vadActivity = AudioFrame::kVadPassive; |
| audioFrame._speechType = AudioFrame::kNormalSpeech; |
| } |
| else if(type == kOutputNormal) |
| { |
| audioFrame._vadActivity = AudioFrame::kVadActive; |
| audioFrame._speechType = AudioFrame::kNormalSpeech; |
| } |
| else if(type == kOutputPLC) |
| { |
| audioFrame._vadActivity = _previousAudioActivity; |
| audioFrame._speechType = AudioFrame::kPLC; |
| } |
| else if(type == kOutputCNG) |
| { |
| audioFrame._vadActivity = AudioFrame::kVadPassive; |
| audioFrame._speechType = AudioFrame::kCNG; |
| } |
| else |
| { |
| audioFrame._vadActivity = AudioFrame::kVadPassive; |
| audioFrame._speechType = AudioFrame::kPLCCNG; |
| } |
| } |
| else |
| { |
| // Always return kVadUnknown when receive VAD is inactive |
| audioFrame._vadActivity = AudioFrame::kVadUnknown; |
| |
| if(type == kOutputNormal) |
| { |
| audioFrame._speechType = AudioFrame::kNormalSpeech; |
| } |
| else if(type == kOutputPLC) |
| { |
| audioFrame._speechType = AudioFrame::kPLC; |
| } |
| else if(type == kOutputPLCtoCNG) |
| { |
| audioFrame._speechType = AudioFrame::kPLCCNG; |
| } |
| else if(type == kOutputCNG) |
| { |
| audioFrame._speechType = AudioFrame::kCNG; |
| } |
| else |
| { |
| // type is kOutputVADPassive which |
| // we don't expect to get if _vadStatus is false |
| WEBRTC_TRACE(webrtc::kTraceWarning, webrtc::kTraceAudioCoding, _id, |
| "RecOut: NetEq returned kVadPassive while _vadStatus is false."); |
| audioFrame._vadActivity = AudioFrame::kVadUnknown; |
| audioFrame._speechType = AudioFrame::kNormalSpeech; |
| } |
| } |
| _previousAudioActivity = audioFrame._vadActivity; |
| |
| return 0; |
| } |
| |
| // When ACMGenericCodec has set the codec specific parameters in codecDef |
| // it calls AddCodec() to add the new codec to the NetEQ database. |
| WebRtc_Word32 |
| ACMNetEQ::AddCodec( |
| WebRtcNetEQ_CodecDef* codecDef, |
| bool toMaster) |
| { |
| if (codecDef == NULL) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "ACMNetEQ::AddCodec: error, codecDef is NULL"); |
| return -1; |
| } |
| CriticalSectionScoped lock(*_netEqCritSect); |
| |
| WebRtc_Word16 idx; |
| if(toMaster) |
| { |
| idx = 0; |
| } |
| else |
| { |
| idx = 1; |
| } |
| |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "ACMNetEQ::AddCodec: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_CodecDbAdd(_inst[idx], codecDef) < 0) |
| { |
| LogError("CodecDB_Add", idx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "ACMNetEQ::AddCodec: NetEq, error in adding codec"); |
| return -1; |
| } |
| else |
| { |
| return 0; |
| } |
| } |
| |
| // Creates a Word16 RTP packet out of a Word8 payload and an rtp info struct. |
| // Must be byte order safe. |
| void |
| ACMNetEQ::RTPPack( |
| WebRtc_Word16* rtpPacket, |
| const WebRtc_Word8* payload, |
| const WebRtc_Word32 payloadLengthW8, |
| const WebRtcRTPHeader& rtpInfo) |
| { |
| WebRtc_Word32 idx = 0; |
| WEBRTC_SPL_SET_BYTE(rtpPacket, (WebRtc_Word8)0x80, idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, rtpInfo.header.payloadType, idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.sequenceNumber), 1), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.sequenceNumber), 0), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.timestamp), 3), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.timestamp), 2), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.timestamp), 1), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.timestamp), 0), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.ssrc), 3), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.ssrc), 2), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.ssrc), 1), idx); |
| idx++; |
| |
| WEBRTC_SPL_SET_BYTE(rtpPacket, WEBRTC_SPL_GET_BYTE( |
| &(rtpInfo.header.ssrc), 0), idx); |
| idx++; |
| |
| for (WebRtc_Word16 i=0; i < payloadLengthW8; i++) |
| { |
| WEBRTC_SPL_SET_BYTE(rtpPacket, payload[i], idx); |
| idx++; |
| } |
| if (payloadLengthW8 & 1) |
| { |
| // Our 16 bits buffer is one byte too large, set that |
| // last byte to zero. |
| WEBRTC_SPL_SET_BYTE(rtpPacket, 0x0, idx); |
| } |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::EnableVAD() |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if (_vadStatus) |
| { |
| return 0; |
| } |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetVADStatus: NetEq is not initialized."); |
| return -1; |
| } |
| // VAD was off and we have to turn it on |
| if(EnableVADByIdxSafe(idx) < 0) |
| { |
| return -1; |
| } |
| |
| // Set previous VAD status to PASSIVE |
| _previousAudioActivity = AudioFrame::kVadPassive; |
| } |
| _vadStatus = true; |
| return 0; |
| } |
| |
| |
| ACMVADMode |
| ACMNetEQ::VADMode() const |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| return _vadMode; |
| } |
| |
| |
| WebRtc_Word16 |
| ACMNetEQ::SetVADMode( |
| const ACMVADMode mode) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if((mode < VADNormal) || (mode > VADVeryAggr)) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetVADMode: NetEq error: could not set VAD mode, mode is not supported"); |
| return -1; |
| } |
| else |
| { |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetVADMode: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetVADMode(_inst[idx], mode) < 0) |
| { |
| LogError("SetVADmode", idx); |
| return -1; |
| } |
| } |
| _vadMode = mode; |
| return 0; |
| } |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::FlushBuffers() |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "FlushBuffers: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_FlushBuffers(_inst[idx]) < 0) |
| { |
| LogError("FlushBuffers", idx); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::GetVersion( |
| WebRtc_Word8* version, |
| WebRtc_UWord32& remainingBufferInBytes, |
| WebRtc_UWord32& position) |
| { |
| WebRtc_UWord32 len = position; |
| strncpy(&version[position], "NetEq\t\t", remainingBufferInBytes); |
| position = (WebRtc_UWord32)strlen(version); |
| remainingBufferInBytes -= (position - len); |
| len = position; |
| |
| WebRtc_Word8 myVersion[100]; |
| if(WebRtcNetEQ_GetVersion(myVersion) < 0) |
| { |
| return -1; |
| } |
| |
| strncpy(&version[position], myVersion, remainingBufferInBytes); |
| position = (WebRtc_UWord32)strlen(version); |
| remainingBufferInBytes -= (position - len); |
| len = position; |
| |
| strncpy(&version[position], "\n", remainingBufferInBytes); |
| position = (WebRtc_UWord32)strlen(version); |
| remainingBufferInBytes -= (position - len); |
| len = position; |
| |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::RemoveCodec( |
| WebRtcNetEQDecoder codecIdx, |
| bool isStereo) |
| { |
| // sanity check |
| if((codecIdx <= kDecoderReservedStart) || |
| (codecIdx >= kDecoderReservedEnd)) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RemoveCodec: NetEq error: could not Remove Codec, codec index out of range"); |
| return -1; |
| } |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "RemoveCodec: NetEq is not initialized."); |
| return -1; |
| } |
| |
| if(WebRtcNetEQ_CodecDbRemove(_inst[0], codecIdx) < 0) |
| { |
| LogError("CodecDB_Remove", 0); |
| return -1; |
| } |
| |
| if(isStereo) |
| { |
| if(WebRtcNetEQ_CodecDbRemove(_inst[1], codecIdx) < 0) |
| { |
| LogError("CodecDB_Remove", 1); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::SetBackgroundNoiseMode( |
| const ACMBackgroundNoiseMode mode) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| for(WebRtc_Word16 idx = 0; idx < _numSlaves + 1; idx++) |
| { |
| if(!_isInitialized[idx]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "SetBackgroundNoiseMode: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetBGNMode(_inst[idx], (WebRtcNetEQBGNMode)mode) < 0) |
| { |
| LogError("SetBGNMode", idx); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::BackgroundNoiseMode( |
| ACMBackgroundNoiseMode& mode) |
| { |
| WebRtcNetEQBGNMode myMode; |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(!_isInitialized[0]) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "BackgroundNoiseMode: NetEq is not initialized."); |
| return -1; |
| } |
| if(WebRtcNetEQ_GetBGNMode(_inst[0], &myMode) < 0) |
| { |
| LogError("WebRtcNetEQ_GetBGNMode", 0); |
| return -1; |
| } |
| else |
| { |
| mode = (ACMBackgroundNoiseMode)myMode; |
| } |
| return 0; |
| } |
| |
| void |
| ACMNetEQ::SetUniqueId( |
| WebRtc_Word32 id) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| _id = id; |
| } |
| |
| |
| void |
| ACMNetEQ::LogError( |
| const WebRtc_Word8* neteqFuncName, |
| const WebRtc_Word16 idx) const |
| { |
| WebRtc_Word8 errorName[NETEQ_ERR_MSG_LEN_BYTE]; |
| WebRtc_Word8 myFuncName[50]; |
| int neteqErrorCode = WebRtcNetEQ_GetErrorCode(_inst[idx]); |
| WebRtcNetEQ_GetErrorName(neteqErrorCode, errorName, NETEQ_ERR_MSG_LEN_BYTE - 1); |
| strncpy(myFuncName, neteqFuncName, 49); |
| errorName[NETEQ_ERR_MSG_LEN_BYTE - 1] = '\0'; |
| myFuncName[49] = '\0'; |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "NetEq-%d Error in function %s, error-code: %d, error-string: %s", |
| idx, |
| myFuncName, |
| neteqErrorCode, |
| errorName); |
| } |
| |
| |
| WebRtc_Word32 |
| ACMNetEQ::PlayoutTimestamp( |
| WebRtc_UWord32& timestamp) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| if(WebRtcNetEQ_GetSpeechTimeStamp(_inst[0], ×tamp) < 0) |
| { |
| LogError("GetSpeechTimeStamp", 0); |
| return -1; |
| } |
| else |
| { |
| return 0; |
| } |
| } |
| |
| WebRtc_Word16 |
| ACMNetEQ::AddSlave( |
| const WebRtcNetEQDecoder* usedCodecs, |
| WebRtc_Word16 noOfCodecs) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| const WebRtc_Word16 slaveIdx = 1; |
| if(_numSlaves < 1) |
| { |
| // initialize the receiver, this also sets up VAD. |
| if(InitByIdxSafe(slaveIdx) < 0) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not Initialize"); |
| return -1; |
| } |
| |
| // Allocate buffer. |
| if(AllocatePacketBufferByIdxSafe(usedCodecs, noOfCodecs, slaveIdx) < 0) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not Allocate Packet Buffer"); |
| return -1; |
| } |
| |
| if(_masterSlaveInfo != NULL) |
| { |
| free(_masterSlaveInfo); |
| _masterSlaveInfo = NULL; |
| } |
| int msInfoSize = WebRtcNetEQ_GetMasterSlaveInfoSize(); |
| _masterSlaveInfo = malloc(msInfoSize); |
| |
| if(_masterSlaveInfo == NULL) |
| { |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not Allocate memory for Master-Slave Info"); |
| return -1; |
| } |
| |
| // We accept this as initialized NetEQ, the rest is to synchronize |
| // Slave with Master. |
| _numSlaves = 1; |
| _isInitialized[slaveIdx] = true; |
| |
| // Set Slave delay as all other instances. |
| if(WebRtcNetEQ_SetExtraDelay(_inst[slaveIdx], _extraDelay) < 0) |
| { |
| LogError("SetExtraDelay", slaveIdx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not set delay"); |
| return -1; |
| } |
| |
| // Set AVT |
| if(WebRtcNetEQ_SetAVTPlayout(_inst[slaveIdx], (_avtPlayout) ? 1 : 0) < 0) |
| { |
| LogError("SetAVTPlayout", slaveIdx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not set AVT playout."); |
| return -1; |
| } |
| |
| // Set Background Noise |
| WebRtcNetEQBGNMode currentMode; |
| if(WebRtcNetEQ_GetBGNMode(_inst[0], ¤tMode) < 0) |
| { |
| LogError("GetBGNMode", 0); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AAddSlave: AddSlave Failed, Could not Get BGN form Master."); |
| return -1; |
| } |
| |
| if(WebRtcNetEQ_SetBGNMode(_inst[slaveIdx], (WebRtcNetEQBGNMode)currentMode) < 0) |
| { |
| LogError("SetBGNMode", slaveIdx); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not set BGN mode."); |
| return -1; |
| } |
| |
| enum WebRtcNetEQPlayoutMode playoutMode; |
| switch(_playoutMode) |
| { |
| case voice: |
| playoutMode = kPlayoutOn; |
| break; |
| case fax: |
| playoutMode = kPlayoutFax; |
| break; |
| case streaming: |
| playoutMode = kPlayoutStreaming; |
| break; |
| default: |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: NetEq Error, playout mode not recognized"); |
| return -1; |
| } |
| if(WebRtcNetEQ_SetPlayoutMode(_inst[slaveIdx], playoutMode) < 0) |
| { |
| LogError("SetPlayoutMode", 1); |
| WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceAudioCoding, _id, |
| "AddSlave: AddSlave Failed, Could not Set Playout Mode."); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void |
| ACMNetEQ::SetReceivedStereo( |
| bool receivedStereo) |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| _receivedStereo = receivedStereo; |
| } |
| |
| WebRtc_UWord8 |
| ACMNetEQ::NumSlaves() |
| { |
| CriticalSectionScoped lock(*_netEqCritSect); |
| return _numSlaves; |
| } |
| |
| } // namespace webrtc |