| /* |
| * 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. |
| */ |
| |
| /* |
| * This file contains the function where the main decision logic for buffer level |
| * adaptation happens. |
| */ |
| |
| #include "buffer_stats.h" |
| |
| #include <assert.h> |
| |
| #include "signal_processing_library.h" |
| |
| #include "automode.h" |
| #include "neteq_defines.h" |
| #include "neteq_error_codes.h" |
| #include "webrtc_neteq.h" |
| |
| #define NETEQ_BUFSTAT_20MS_Q7 2560 /* = 20 ms in Q7 */ |
| |
| WebRtc_UWord16 WebRtcNetEQ_BufstatsDecision(BufstatsInst_t *inst, WebRtc_Word16 frameSize, |
| WebRtc_Word32 cur_size, WebRtc_UWord32 targetTS, |
| WebRtc_UWord32 availableTS, int noPacket, |
| int cngPacket, int prevPlayMode, |
| enum WebRtcNetEQPlayoutMode playoutMode, |
| int timestampsPerCall, int NoOfExpandCalls, |
| WebRtc_Word16 fs_mult, |
| WebRtc_Word16 lastModeBGNonly, int playDtmf) |
| { |
| |
| int currentDelayMs; |
| WebRtc_Word32 currSizeSamples = cur_size; |
| WebRtc_Word16 extraDelayPacketsQ8 = 0; |
| |
| /* Avoid overflow if the buffer size should be really large (cur_size is limited 256ms) */ |
| WebRtc_Word32 curr_sizeQ7 = WEBRTC_SPL_LSHIFT_W32(cur_size, 4); |
| WebRtc_UWord16 level_limit_hi, level_limit_lo; |
| |
| inst->Automode_inst.prevTimeScale &= (prevPlayMode == MODE_SUCCESS_ACCELERATE |
| || prevPlayMode == MODE_LOWEN_ACCELERATE || prevPlayMode == MODE_SUCCESS_PREEMPTIVE |
| || prevPlayMode == MODE_LOWEN_PREEMPTIVE); |
| |
| if ((prevPlayMode != MODE_RFC3389CNG) && (prevPlayMode != MODE_CODEC_INTERNAL_CNG)) |
| { |
| /* |
| * Do not update buffer history if currently playing CNG |
| * since it will bias the filtered buffer level. |
| */ |
| WebRtcNetEQ_BufferLevelFilter(cur_size, &(inst->Automode_inst), timestampsPerCall, |
| fs_mult); |
| } |
| else |
| { |
| /* only update time counters */ |
| inst->Automode_inst.packetIatCountSamp += timestampsPerCall; /* packet inter-arrival time */ |
| inst->Automode_inst.peakIatCountSamp += timestampsPerCall; /* peak inter-arrival time */ |
| inst->Automode_inst.timescaleHoldOff >>= 1; /* time-scaling limiter */ |
| } |
| cur_size = WEBRTC_SPL_MIN(curr_sizeQ7, WEBRTC_SPL_WORD16_MAX); |
| |
| /* Calculate VQmon related variables */ |
| /* avgDelay = avgDelay*(511/512) + currentDelay*(1/512) (sample ms delay in Q8) */ |
| inst->avgDelayMsQ8 = (WebRtc_Word16) (WEBRTC_SPL_MUL_16_16_RSFT(inst->avgDelayMsQ8,511,9) |
| + (cur_size >> 9)); |
| |
| /* Update maximum delay if needed */ |
| currentDelayMs = (curr_sizeQ7 >> 7); |
| if (currentDelayMs > inst->maxDelayMs) |
| { |
| inst->maxDelayMs = currentDelayMs; |
| } |
| |
| /* NetEQ is on with normal or steaming mode */ |
| if (playoutMode == kPlayoutOn || playoutMode == kPlayoutStreaming) |
| { |
| /* Guard for errors, so that it should not get stuck in error mode */ |
| if (prevPlayMode == MODE_ERROR) |
| { |
| if (noPacket) |
| { |
| return BUFSTATS_DO_EXPAND; |
| } |
| else |
| { |
| return BUFSTAT_REINIT; |
| } |
| } |
| |
| if (prevPlayMode != MODE_EXPAND && prevPlayMode != MODE_FADE_TO_BGN) |
| { |
| inst->w16_noExpand = 1; |
| } |
| else |
| { |
| inst->w16_noExpand = 0; |
| } |
| |
| if (cngPacket) |
| { |
| /* signed difference between wanted and available TS */ |
| WebRtc_Word32 diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS; |
| |
| if ((diffTS) < 0 && (prevPlayMode == MODE_RFC3389CNG)) |
| { |
| /* Not time to play this packet yet. Wait another round before using this |
| * packet. Keep on playing CNG from previous CNG parameters. */ |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| |
| /* otherwise, go for the CNG packet now */ |
| return BUFSTATS_DO_RFC3389CNG_PACKET; |
| } |
| |
| /*Check for expand/cng */ |
| if (noPacket) |
| { |
| if (inst->w16_cngOn == CNG_RFC3389_ON) |
| { |
| /* keep on playing CNG */ |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| else if (inst->w16_cngOn == CNG_INTERNAL_ON) |
| { |
| /* keep on playing internal CNG */ |
| return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; |
| } |
| else if (playDtmf == 1) |
| { |
| /* we have not audio data, but can play DTMF */ |
| return BUFSTATS_DO_DTMF_ONLY; |
| } |
| else |
| { |
| /* nothing to play => do Expand */ |
| return BUFSTATS_DO_EXPAND; |
| } |
| } |
| |
| /* |
| * If the expand period was very long, reset NetEQ since it is likely that the |
| * sender was restarted. |
| */ |
| if (NoOfExpandCalls > REINIT_AFTER_EXPANDS) return BUFSTAT_REINIT_DECODER; |
| |
| /* Calculate extra delay in Q8 packets */ |
| if (inst->Automode_inst.extraDelayMs > 0 && inst->Automode_inst.packetSpeechLenSamp |
| > 0) |
| { |
| extraDelayPacketsQ8 = WebRtcSpl_DivW32W16ResW16( |
| (WEBRTC_SPL_MUL(inst->Automode_inst.extraDelayMs, 8 * fs_mult) << 8), |
| inst->Automode_inst.packetSpeechLenSamp); |
| /* (extra delay in samples in Q8) */ |
| } |
| |
| /* Check if needed packet is available */ |
| if (targetTS == availableTS) |
| { |
| |
| /* If last mode was not expand, and there is no DTMF to play */ |
| if (inst->w16_noExpand == 1 && playDtmf == 0) |
| { |
| /* If so check for accelerate */ |
| |
| level_limit_lo = ((inst->Automode_inst.optBufLevel) >> 1) /* 50 % */ |
| + ((inst->Automode_inst.optBufLevel) >> 2); /* ... + 25% = 75% */ |
| |
| /* set upper limit to optBufLevel, but make sure that window is at least 20ms */ |
| level_limit_hi = WEBRTC_SPL_MAX(inst->Automode_inst.optBufLevel, |
| level_limit_lo + |
| WebRtcSpl_DivW32W16ResW16((WEBRTC_SPL_MUL(20*8, fs_mult) << 8), |
| inst->Automode_inst.packetSpeechLenSamp)); |
| |
| /* if extra delay is non-zero, add it */ |
| if (extraDelayPacketsQ8 > 0) |
| { |
| level_limit_hi += extraDelayPacketsQ8; |
| level_limit_lo += extraDelayPacketsQ8; |
| } |
| |
| if (((inst->Automode_inst.buffLevelFilt >= level_limit_hi) && |
| (inst->Automode_inst.timescaleHoldOff == 0)) || |
| (inst->Automode_inst.buffLevelFilt >= level_limit_hi << 2)) |
| { |
| /* |
| * Buffer level higher than limit and time-scaling allowed, |
| * OR buffer level _really_ high. |
| */ |
| return BUFSTATS_DO_ACCELERATE; |
| } |
| else if ((inst->Automode_inst.buffLevelFilt < level_limit_lo) |
| && (inst->Automode_inst.timescaleHoldOff == 0)) |
| { |
| return BUFSTATS_DO_PREEMPTIVE_EXPAND; |
| } |
| } |
| return BUFSTATS_DO_NORMAL; |
| } |
| |
| /* Check for Merge */ |
| else if (availableTS > targetTS) |
| { |
| |
| /* Check that we do not play a packet "too early" */ |
| if ((prevPlayMode == MODE_EXPAND) |
| && (availableTS - targetTS |
| < (WebRtc_UWord32) WEBRTC_SPL_MUL_16_16((WebRtc_Word16)timestampsPerCall, |
| (WebRtc_Word16)REINIT_AFTER_EXPANDS)) |
| && (NoOfExpandCalls < MAX_WAIT_FOR_PACKET) |
| && (availableTS |
| > targetTS |
| + WEBRTC_SPL_MUL_16_16((WebRtc_Word16)timestampsPerCall, |
| (WebRtc_Word16)NoOfExpandCalls)) |
| && (inst->Automode_inst.buffLevelFilt <= inst->Automode_inst.optBufLevel |
| + extraDelayPacketsQ8)) |
| { |
| if (playDtmf == 1) |
| { |
| /* we still have DTMF to play, so do not perform expand */ |
| return BUFSTATS_DO_DTMF_ONLY; |
| } |
| else |
| { |
| /* nothing to play */ |
| return BUFSTATS_DO_EXPAND; |
| } |
| } |
| |
| /* If previous was CNG period or BGNonly then no merge is needed */ |
| if ((prevPlayMode == MODE_RFC3389CNG) || (prevPlayMode == MODE_CODEC_INTERNAL_CNG) |
| || lastModeBGNonly) |
| { |
| /* |
| * Keep the same delay as before the CNG (or maximum 70 ms in buffer as safety |
| * precaution), but make sure that the number of samples in buffer is no |
| * higher than 4 times the optimal level. |
| */ |
| WebRtc_Word32 diffTS = (inst->uw32_CNGplayedTS + targetTS) - availableTS; |
| if (diffTS >= 0 |
| || (WEBRTC_SPL_MUL_16_16_RSFT( inst->Automode_inst.optBufLevel |
| + extraDelayPacketsQ8, |
| inst->Automode_inst.packetSpeechLenSamp, 6) < currSizeSamples)) |
| { |
| /* it is time to play this new packet */ |
| return BUFSTATS_DO_NORMAL; |
| } |
| else |
| { |
| /* it is too early to play this new packet => keep on playing CNG */ |
| if (prevPlayMode == MODE_RFC3389CNG) |
| { |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| else if (prevPlayMode == MODE_CODEC_INTERNAL_CNG) |
| { |
| return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; |
| } |
| else if (playDtmf == 1) |
| { |
| /* we have not audio data, but can play DTMF */ |
| return BUFSTATS_DO_DTMF_ONLY; |
| } |
| else /* lastModeBGNonly */ |
| { |
| /* signal expand, but this will result in BGN again */ |
| return BUFSTATS_DO_EXPAND; |
| } |
| } |
| } |
| |
| /* Do not merge unless we have done a Expand before (for complexity reasons) */ |
| if ((inst->w16_noExpand == 0) || ((frameSize < timestampsPerCall) && (cur_size |
| > NETEQ_BUFSTAT_20MS_Q7))) |
| { |
| return BUFSTATS_DO_MERGE; |
| } |
| else if (playDtmf == 1) |
| { |
| /* play DTMF instead of expand */ |
| return BUFSTATS_DO_DTMF_ONLY; |
| } |
| else |
| { |
| return BUFSTATS_DO_EXPAND; |
| } |
| } |
| } |
| else |
| { /* kPlayoutOff or kPlayoutFax */ |
| if (cngPacket) |
| { |
| if (((WebRtc_Word32) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0) |
| { |
| /* time to play this packet now */ |
| return BUFSTATS_DO_RFC3389CNG_PACKET; |
| } |
| else |
| { |
| /* wait before playing this packet */ |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| } |
| if (noPacket) |
| { |
| /* |
| * No packet => |
| * 1. If in CNG mode play as usual |
| * 2. Otherwise use other method to generate data and hold TS value |
| */ |
| if (inst->w16_cngOn == CNG_RFC3389_ON) |
| { |
| /* keep on playing CNG */ |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| else if (inst->w16_cngOn == CNG_INTERNAL_ON) |
| { |
| /* keep on playing internal CNG */ |
| return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; |
| } |
| else |
| { |
| /* nothing to play => invent some data to play out */ |
| if (playoutMode == kPlayoutOff) |
| { |
| return BUFSTATS_DO_ALTERNATIVE_PLC; |
| } |
| else if (playoutMode == kPlayoutFax) |
| { |
| return BUFSTATS_DO_AUDIO_REPETITION; |
| } |
| else |
| { |
| /* UNDEFINED, should not get here... */ |
| assert(0); |
| return BUFSTAT_REINIT; |
| } |
| } |
| } |
| else if (targetTS == availableTS) |
| { |
| return BUFSTATS_DO_NORMAL; |
| } |
| else |
| { |
| if (((WebRtc_Word32) ((inst->uw32_CNGplayedTS + targetTS) - availableTS)) >= 0) |
| { |
| return BUFSTATS_DO_NORMAL; |
| } |
| else if (playoutMode == kPlayoutOff) |
| { |
| /* |
| * If currently playing CNG, continue with that. Don't increase TS |
| * since uw32_CNGplayedTS will be increased. |
| */ |
| if (inst->w16_cngOn == CNG_RFC3389_ON) |
| { |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| else if (inst->w16_cngOn == CNG_INTERNAL_ON) |
| { |
| return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; |
| } |
| else |
| { |
| /* |
| * Otherwise, do PLC and increase TS while waiting for the time to |
| * play this packet. |
| */ |
| return BUFSTATS_DO_ALTERNATIVE_PLC_INC_TS; |
| } |
| } |
| else if (playoutMode == kPlayoutFax) |
| { |
| /* |
| * If currently playing CNG, continue with that don't increase TS since |
| * uw32_CNGplayedTS will be increased. |
| */ |
| if (inst->w16_cngOn == CNG_RFC3389_ON) |
| { |
| return BUFSTATS_DO_RFC3389CNG_NOPACKET; |
| } |
| else if (inst->w16_cngOn == CNG_INTERNAL_ON) |
| { |
| return BUFSTATS_DO_INTERNAL_CNG_NOPACKET; |
| } |
| else |
| { |
| /* |
| * Otherwise, do audio repetition and increase TS while waiting for the |
| * time to play this packet. |
| */ |
| return BUFSTATS_DO_AUDIO_REPETITION_INC_TS; |
| } |
| } |
| else |
| { |
| /* UNDEFINED, should not get here... */ |
| assert(0); |
| return BUFSTAT_REINIT; |
| } |
| } |
| } |
| /* We should not get here (but sometimes we do anyway...) */ |
| return BUFSTAT_REINIT; |
| } |
| |