blob: 8b8be0e4083eed435d97c981005d0394313aa27e [file] [log] [blame]
/*
** Copyright 2003-2010, VisualOn, Inc.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
/*******************************************************************************
File: adj_thr.c
Content: Threshold compensation functions
*******************************************************************************/
/* Include system headers before local headers - the local headers
* redefine __inline, which can mess up definitions in libc headers if
* they happen to use __inline. */
#include <string.h>
#include "basic_op.h"
#include "oper_32b.h"
#include "adj_thr_data.h"
#include "adj_thr.h"
#include "qc_data.h"
#include "line_pe.h"
#define minSnrLimit 0x6666 /* 1 dB */
#define PEBITS_COEF 0x170a /* 0.18*(1 << 15)*/
#define HOLE_THR_LONG 0x2873 /* 0.316*(1 << 15) */
#define HOLE_THR_SHORT 0x4000 /* 0.5 *(1 << 15) */
#define MS_THRSPREAD_COEF 0x7333 /* 0.9 * (1 << 15) */
#define MIN_SNR_COEF 0x651f /* 3.16* (1 << (15 - 2)) */
/* values for avoid hole flag */
enum _avoid_hole_state {
NO_AH =0,
AH_INACTIVE =1,
AH_ACTIVE =2
};
/********************************************************************************
*
* function name:bits2pe
* description: convert from bits to pe
* pe = 1.18*desiredBits
*
**********************************************************************************/
Word16 bits2pe(const Word16 bits) {
return (bits + ((PEBITS_COEF * bits) >> 15));
}
/********************************************************************************
*
* function name:calcThreshExp
* description: loudness calculation (threshold to the power of redExp)
* thr(n)^0.25
*
**********************************************************************************/
static void calcThreshExp(Word32 thrExp[MAX_CHANNELS][MAX_GROUPED_SFB],
PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
const Word16 nChannels)
{
Word16 ch, sfb, sfbGrp;
Word32 *pthrExp = NULL, *psfbThre;
for (ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
for(sfbGrp = 0; sfbGrp < psyOutChan->sfbCnt; sfbGrp+= psyOutChan->sfbPerGroup)
pthrExp = &(thrExp[ch][sfbGrp]);
psfbThre = psyOutChan->sfbThreshold + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
*pthrExp = rsqrt(rsqrt(*psfbThre,INT_BITS),INT_BITS);
pthrExp++; psfbThre++;
}
}
}
/********************************************************************************
*
* function name:adaptMinSnr
* description: reduce minSnr requirements for bands with relative low energies
*
**********************************************************************************/
static void adaptMinSnr(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
Word16 logSfbEnergy[MAX_CHANNELS][MAX_GROUPED_SFB],
MINSNR_ADAPT_PARAM *msaParam,
const Word16 nChannels)
{
Word16 ch, sfb, sfbOffs;
Word32 nSfb, avgEn;
Word16 log_avgEn = 0;
Word32 startRatio_x_avgEn = 0;
for (ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL* psyOutChan = &psyOutChannel[ch];
/* calc average energy per scalefactor band */
avgEn = 0;
nSfb = 0;
for (sfbOffs=0; sfbOffs<psyOutChan->sfbCnt; sfbOffs+=psyOutChan->sfbPerGroup) {
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
avgEn = L_add(avgEn, psyOutChan->sfbEnergy[sfbOffs+sfb]);
nSfb = nSfb + 1;
}
}
if (nSfb > 0) {
avgEn = avgEn / nSfb;
log_avgEn = iLog4(avgEn);
startRatio_x_avgEn = fixmul(msaParam->startRatio, avgEn);
}
/* reduce minSnr requirement by minSnr^minSnrRed dependent on avgEn/sfbEn */
for (sfbOffs=0; sfbOffs<psyOutChan->sfbCnt; sfbOffs+=psyOutChan->sfbPerGroup) {
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
if (psyOutChan->sfbEnergy[sfbOffs+sfb] < startRatio_x_avgEn) {
Word16 dbRatio, minSnrRed;
Word32 snrRed;
Word16 newMinSnr;
dbRatio = log_avgEn - logSfbEnergy[ch][sfbOffs+sfb];
dbRatio = dbRatio + (dbRatio << 1);
minSnrRed = 110 - ((dbRatio + (dbRatio << 1)) >> 2);
minSnrRed = max(minSnrRed, 20); /* 110: (0.375(redOffs)+1)*80,
3: 0.00375(redRatioFac)*80
20: 0.25(maxRed) * 80 */
snrRed = minSnrRed * iLog4((psyOutChan->sfbMinSnr[sfbOffs+sfb] << 16));
/*
snrRedI si now scaled by 80 (minSnrRed) and 4 (ffr_iLog4)
*/
newMinSnr = round16(pow2_xy(snrRed,80*4));
psyOutChan->sfbMinSnr[sfbOffs+sfb] = min(newMinSnr, minSnrLimit);
}
}
}
}
}
/********************************************************************************
*
* function name:initAvoidHoleFlag
* description: determine bands where avoid hole is not necessary resp. possible
*
**********************************************************************************/
static void initAvoidHoleFlag(Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
PSY_OUT_ELEMENT* psyOutElement,
const Word16 nChannels,
AH_PARAM *ahParam)
{
Word16 ch, sfb, sfbGrp, shift;
Word32 threshold;
Word32* psfbSpreadEn;
for (ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
if (psyOutChan->windowSequence != SHORT_WINDOW) {
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
psfbSpreadEn = psyOutChan->sfbSpreadedEnergy + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
*psfbSpreadEn = *psfbSpreadEn >> 1; /* 0.5 */
++psfbSpreadEn;
}
}
}
else {
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
psfbSpreadEn = psyOutChan->sfbSpreadedEnergy + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
*psfbSpreadEn = (*psfbSpreadEn >> 1) + (*psfbSpreadEn >> 3); /* 0.63 */
++psfbSpreadEn;
}
}
}
}
/* increase minSnr for local peaks, decrease it for valleys */
if (ahParam->modifyMinSnr) {
for(ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
if (psyOutChan->windowSequence != SHORT_WINDOW)
threshold = HOLE_THR_LONG;
else
threshold = HOLE_THR_SHORT;
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
Word16 *psfbMinSnr = psyOutChan->sfbMinSnr + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
Word32 sfbEn, sfbEnm1, sfbEnp1, avgEn;
if (sfb > 0)
sfbEnm1 = psyOutChan->sfbEnergy[sfbGrp+sfb-1];
else
sfbEnm1 = psyOutChan->sfbEnergy[sfbGrp];
if (sfb < (psyOutChan->maxSfbPerGroup-1))
sfbEnp1 = psyOutChan->sfbEnergy[sfbGrp+sfb+1];
else
sfbEnp1 = psyOutChan->sfbEnergy[sfbGrp+sfb];
avgEn = (sfbEnm1 + sfbEnp1) >> 1;
sfbEn = psyOutChan->sfbEnergy[sfbGrp+sfb];
if (sfbEn > avgEn && avgEn > 0) {
Word32 tmpMinSnr;
shift = norm_l(sfbEn);
tmpMinSnr = Div_32(L_mpy_ls(avgEn, minSnrLimit) << shift, sfbEn << shift );
tmpMinSnr = max(tmpMinSnr, HOLE_THR_LONG);
tmpMinSnr = max(tmpMinSnr, threshold);
*psfbMinSnr = min(*psfbMinSnr, tmpMinSnr);
}
/* valley ? */
if ((sfbEn < (avgEn >> 1)) && (sfbEn > 0)) {
Word32 tmpMinSnr;
Word32 minSnrEn = L_mpy_wx(avgEn, *psfbMinSnr);
if(minSnrEn < sfbEn) {
shift = norm_l(sfbEn);
tmpMinSnr = Div_32( minSnrEn << shift, sfbEn<<shift);
}
else {
tmpMinSnr = MAX_16;
}
tmpMinSnr = min(minSnrLimit, tmpMinSnr);
*psfbMinSnr =
(min((tmpMinSnr >> 2), mult(*psfbMinSnr, MIN_SNR_COEF)) << 2);
}
psfbMinSnr++;
}
}
}
}
/* stereo: adapt the minimum requirements sfbMinSnr of mid and
side channels */
if (nChannels == 2) {
PSY_OUT_CHANNEL *psyOutChanM = &psyOutChannel[0];
PSY_OUT_CHANNEL *psyOutChanS = &psyOutChannel[1];
for (sfb=0; sfb<psyOutChanM->sfbCnt; sfb++) {
if (psyOutElement->toolsInfo.msMask[sfb]) {
Word32 sfbEnM = psyOutChanM->sfbEnergy[sfb];
Word32 sfbEnS = psyOutChanS->sfbEnergy[sfb];
Word32 maxSfbEn = max(sfbEnM, sfbEnS);
Word32 maxThr = L_mpy_wx(maxSfbEn, psyOutChanM->sfbMinSnr[sfb]) >> 1;
if(maxThr >= sfbEnM) {
psyOutChanM->sfbMinSnr[sfb] = MAX_16;
}
else {
shift = norm_l(sfbEnM);
psyOutChanM->sfbMinSnr[sfb] = min(max(psyOutChanM->sfbMinSnr[sfb],
round16(Div_32(maxThr<<shift, sfbEnM << shift))), minSnrLimit);
}
if(maxThr >= sfbEnS) {
psyOutChanS->sfbMinSnr[sfb] = MAX_16;
}
else {
shift = norm_l(sfbEnS);
psyOutChanS->sfbMinSnr[sfb] = min(max(psyOutChanS->sfbMinSnr[sfb],
round16(Div_32(maxThr << shift, sfbEnS << shift))), minSnrLimit);
}
if (sfbEnM > psyOutChanM->sfbSpreadedEnergy[sfb])
psyOutChanS->sfbSpreadedEnergy[sfb] = L_mpy_ls(sfbEnS, MS_THRSPREAD_COEF);
if (sfbEnS > psyOutChanS->sfbSpreadedEnergy[sfb])
psyOutChanM->sfbSpreadedEnergy[sfb] = L_mpy_ls(sfbEnM, MS_THRSPREAD_COEF);
}
}
}
/* init ahFlag (0: no ah necessary, 1: ah possible, 2: ah active */
for(ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
Word16 *pahFlag = ahFlag[ch] + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
if ((psyOutChan->sfbSpreadedEnergy[sfbGrp+sfb] > psyOutChan->sfbEnergy[sfbGrp+sfb]) ||
(psyOutChan->sfbEnergy[sfbGrp+sfb] <= psyOutChan->sfbThreshold[sfbGrp+sfb]) ||
(psyOutChan->sfbMinSnr[sfbGrp+sfb] == MAX_16)) {
*pahFlag++ = NO_AH;
}
else {
*pahFlag++ = AH_INACTIVE;
}
}
for (sfb=psyOutChan->maxSfbPerGroup; sfb<psyOutChan->sfbPerGroup; sfb++) {
*pahFlag++ = NO_AH;
}
}
}
}
/********************************************************************************
*
* function name:calcPeNoAH
* description: sum the pe data only for bands where avoid hole is inactive
*
**********************************************************************************/
static void calcPeNoAH(Word16 *pe,
Word16 *constPart,
Word16 *nActiveLines,
PE_DATA *peData,
Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
const Word16 nChannels)
{
Word16 ch, sfb, sfbGrp;
int ipe, iconstPart, inActiveLines;
ipe = 0;
iconstPart = 0;
inActiveLines = 0;
for(ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
PE_CHANNEL_DATA *peChanData = &peData->peChannelData[ch];
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
if (ahFlag[ch][sfbGrp+sfb] < AH_ACTIVE) {
ipe = ipe + peChanData->sfbPe[sfbGrp+sfb];
iconstPart = iconstPart + peChanData->sfbConstPart[sfbGrp+sfb];
inActiveLines = inActiveLines + peChanData->sfbNActiveLines[sfbGrp+sfb];
}
}
}
}
*pe = saturate(ipe);
*constPart = saturate(iconstPart);
*nActiveLines = saturate(inActiveLines);
}
/********************************************************************************
*
* function name:reduceThresholds
* description: apply reduction formula
*
**********************************************************************************/
static void reduceThresholds(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
Word32 thrExp[MAX_CHANNELS][MAX_GROUPED_SFB],
const Word16 nChannels,
const Word32 redVal)
{
Word32 sfbThrReduced;
Word32 *psfbEn, *psfbThr;
Word16 ch, sfb, sfbGrp;
for(ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
for(sfbGrp=0; sfbGrp<psyOutChan->sfbCnt; sfbGrp+=psyOutChan->sfbPerGroup) {
psfbEn = psyOutChan->sfbEnergy + sfbGrp;
psfbThr = psyOutChan->sfbThreshold + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
if (*psfbEn > *psfbThr) {
/* threshold reduction formula */
Word32 tmp = thrExp[ch][sfbGrp+sfb] + redVal;
tmp = fixmul(tmp, tmp);
sfbThrReduced = fixmul(tmp, tmp);
/* avoid holes */
tmp = L_mpy_ls(*psfbEn, psyOutChan->sfbMinSnr[sfbGrp+sfb]);
if ((sfbThrReduced > tmp) &&
(ahFlag[ch][sfbGrp+sfb] != NO_AH)){
sfbThrReduced = max(tmp, *psfbThr);
ahFlag[ch][sfbGrp+sfb] = AH_ACTIVE;
}
*psfbThr = sfbThrReduced;
}
psfbEn++; psfbThr++;
}
}
}
}
/********************************************************************************
*
* function name:correctThresh
* description: if pe difference deltaPe between desired pe and real pe is small enough,
* the difference can be distributed among the scale factor bands.
*
**********************************************************************************/
static void correctThresh(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
PE_DATA *peData,
Word32 thrExp[MAX_CHANNELS][MAX_GROUPED_SFB],
const Word32 redVal,
const Word16 nChannels,
const Word32 deltaPe)
{
Word16 ch, sfb, sfbGrp,shift;
PSY_OUT_CHANNEL *psyOutChan;
PE_CHANNEL_DATA *peChanData;
Word32 deltaSfbPe;
Word32 normFactor;
Word32 *psfbPeFactors;
Word16 *psfbNActiveLines, *pahFlag;
Word32 sfbEn, sfbThr;
Word32 sfbThrReduced;
/* for each sfb calc relative factors for pe changes */
normFactor = 1;
for(ch=0; ch<nChannels; ch++) {
psyOutChan = &psyOutChannel[ch];
peChanData = &peData->peChannelData[ch];
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
psfbPeFactors = peData->sfbPeFactors[ch] + sfbGrp;
psfbNActiveLines = peChanData->sfbNActiveLines + sfbGrp;
pahFlag = ahFlag[ch] + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
Word32 redThrExp = thrExp[ch][sfbGrp+sfb] + redVal;
if (((*pahFlag < AH_ACTIVE) || (deltaPe > 0)) && (redThrExp > 0) && (redThrExp >= *psfbNActiveLines)) {
*psfbPeFactors = (*psfbNActiveLines) * (0x7fffffff / redThrExp);
normFactor = L_add(normFactor, *psfbPeFactors);
}
else {
*psfbPeFactors = 0;
}
psfbPeFactors++;
pahFlag++; psfbNActiveLines++;
}
}
}
/* calculate new thresholds */
for(ch=0; ch<nChannels; ch++) {
psyOutChan = &psyOutChannel[ch];
peChanData = &peData->peChannelData[ch];
for(sfbGrp = 0;sfbGrp < psyOutChan->sfbCnt;sfbGrp+= psyOutChan->sfbPerGroup){
psfbPeFactors = peData->sfbPeFactors[ch] + sfbGrp;
psfbNActiveLines = peChanData->sfbNActiveLines + sfbGrp;
pahFlag = ahFlag[ch] + sfbGrp;
for (sfb=0; sfb<psyOutChan->maxSfbPerGroup; sfb++) {
/* pe difference for this sfb */
deltaSfbPe = *psfbPeFactors * deltaPe;
/* thr3(n) = thr2(n)*2^deltaSfbPe/b(n) */
if (*psfbNActiveLines > 0 && (normFactor* (*psfbNActiveLines)) != 0) {
/* new threshold */
Word32 thrFactor;
sfbEn = psyOutChan->sfbEnergy[sfbGrp+sfb];
sfbThr = psyOutChan->sfbThreshold[sfbGrp+sfb];
if(deltaSfbPe >= 0){
/*
reduce threshold
*/
thrFactor = pow2_xy(L_negate(deltaSfbPe), (normFactor* (*psfbNActiveLines)));
sfbThrReduced = L_mpy_ls(sfbThr, round16(thrFactor));
}
else {
/*
increase threshold
*/
thrFactor = pow2_xy(deltaSfbPe, (normFactor * (*psfbNActiveLines)));
if(thrFactor > sfbThr) {
shift = norm_l(thrFactor);
sfbThrReduced = Div_32( sfbThr << shift, thrFactor<<shift );
}
else {
sfbThrReduced = MAX_32;
}
}
/* avoid hole */
sfbEn = L_mpy_ls(sfbEn, psyOutChan->sfbMinSnr[sfbGrp+sfb]);
if ((sfbThrReduced > sfbEn) &&
(*pahFlag == AH_INACTIVE)) {
sfbThrReduced = max(sfbEn, sfbThr);
*pahFlag = AH_ACTIVE;
}
psyOutChan->sfbThreshold[sfbGrp+sfb] = sfbThrReduced;
}
pahFlag++; psfbNActiveLines++; psfbPeFactors++;
}
}
}
}
/********************************************************************************
*
* function name:reduceMinSnr
* description: if the desired pe can not be reached, reduce pe by reducing minSnr
*
**********************************************************************************/
static void reduceMinSnr(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
PE_DATA *peData,
Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
const Word16 nChannels,
const Word16 desiredPe)
{
Word16 ch, sfb, sfbSubWin;
Word16 deltaPe;
/* start at highest freq down to 0 */
sfbSubWin = psyOutChannel[0].maxSfbPerGroup;
while (peData->pe > desiredPe && sfbSubWin > 0) {
sfbSubWin = sfbSubWin - 1;
/* loop over all subwindows */
for (sfb=sfbSubWin; sfb<psyOutChannel[0].sfbCnt;
sfb+=psyOutChannel[0].sfbPerGroup) {
/* loop over all channels */
PE_CHANNEL_DATA* peChan = peData->peChannelData;
PSY_OUT_CHANNEL* psyOutCh = psyOutChannel;
for (ch=0; ch<nChannels; ch++) {
if (ahFlag[ch][sfb] != NO_AH &&
psyOutCh->sfbMinSnr[sfb] < minSnrLimit) {
psyOutCh->sfbMinSnr[sfb] = minSnrLimit;
psyOutCh->sfbThreshold[sfb] =
L_mpy_ls(psyOutCh->sfbEnergy[sfb], psyOutCh->sfbMinSnr[sfb]);
/* calc new pe */
deltaPe = ((peChan->sfbNLines4[sfb] + (peChan->sfbNLines4[sfb] >> 1)) >> 2) -
peChan->sfbPe[sfb];
peData->pe = peData->pe + deltaPe;
peChan->pe = peChan->pe + deltaPe;
}
peChan += 1; psyOutCh += 1;
}
/* stop if enough has been saved */
if (peData->pe <= desiredPe)
break;
}
}
}
/********************************************************************************
*
* function name:allowMoreHoles
* description: if the desired pe can not be reached, some more scalefactor bands
* have to be quantized to zero
*
**********************************************************************************/
static void allowMoreHoles(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
PSY_OUT_ELEMENT *psyOutElement,
PE_DATA *peData,
Word16 ahFlag[MAX_CHANNELS][MAX_GROUPED_SFB],
const AH_PARAM *ahParam,
const Word16 nChannels,
const Word16 desiredPe)
{
Word16 ch, sfb;
Word16 actPe, shift;
actPe = peData->pe;
/* for MS allow hole in the channel with less energy */
if (nChannels==2 &&
psyOutChannel[0].windowSequence==psyOutChannel[1].windowSequence) {
PSY_OUT_CHANNEL *psyOutChanL = &psyOutChannel[0];
PSY_OUT_CHANNEL *psyOutChanR = &psyOutChannel[1];
for (sfb=0; sfb<psyOutChanL->sfbCnt; sfb++) {
Word32 minEn;
if (psyOutElement->toolsInfo.msMask[sfb]) {
/* allow hole in side channel ? */
minEn = L_mpy_ls(psyOutChanL->sfbEnergy[sfb], (minSnrLimit * psyOutChanL->sfbMinSnr[sfb]) >> 16);
if (ahFlag[1][sfb] != NO_AH &&
minEn > psyOutChanR->sfbEnergy[sfb]) {
ahFlag[1][sfb] = NO_AH;
psyOutChanR->sfbThreshold[sfb] = L_add(psyOutChanR->sfbEnergy[sfb], psyOutChanR->sfbEnergy[sfb]);
actPe = actPe - peData->peChannelData[1].sfbPe[sfb];
}
/* allow hole in mid channel ? */
else {
minEn = L_mpy_ls(psyOutChanR->sfbEnergy[sfb], (minSnrLimit * psyOutChanR->sfbMinSnr[sfb]) >> 16);
if (ahFlag[0][sfb]!= NO_AH &&
minEn > psyOutChanL->sfbEnergy[sfb]) {
ahFlag[0][sfb] = NO_AH;
psyOutChanL->sfbThreshold[sfb] = L_add(psyOutChanL->sfbEnergy[sfb], psyOutChanL->sfbEnergy[sfb]);
actPe = actPe - peData->peChannelData[0].sfbPe[sfb];
}
}
if (actPe < desiredPe)
break;
}
}
}
/* subsequently erase bands */
if (actPe > desiredPe) {
Word16 startSfb[2];
Word32 avgEn, minEn;
Word16 ahCnt;
Word16 enIdx;
Word16 enDiff;
Word32 en[4];
Word16 minSfb, maxSfb;
Flag done;
/* do not go below startSfb */
for (ch=0; ch<nChannels; ch++) {
if (psyOutChannel[ch].windowSequence != SHORT_WINDOW)
startSfb[ch] = ahParam->startSfbL;
else
startSfb[ch] = ahParam->startSfbS;
}
avgEn = 0;
minEn = MAX_32;
ahCnt = 0;
for (ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
for (sfb=startSfb[ch]; sfb<psyOutChan->sfbCnt; sfb++) {
if ((ahFlag[ch][sfb] != NO_AH) &&
(psyOutChan->sfbEnergy[sfb] > psyOutChan->sfbThreshold[sfb])) {
minEn = min(minEn, psyOutChan->sfbEnergy[sfb]);
avgEn = L_add(avgEn, psyOutChan->sfbEnergy[sfb]);
ahCnt++;
}
}
}
if(ahCnt) {
Word32 iahCnt;
shift = norm_l(ahCnt);
iahCnt = Div_32( 1 << shift, ahCnt << shift );
avgEn = fixmul(avgEn, iahCnt);
}
enDiff = iLog4(avgEn) - iLog4(minEn);
/* calc some energy borders between minEn and avgEn */
for (enIdx=0; enIdx<4; enIdx++) {
Word32 enFac;
enFac = ((6-(enIdx << 1)) * enDiff);
en[enIdx] = fixmul(avgEn, pow2_xy(L_negate(enFac),7*4));
}
/* start with lowest energy border at highest sfb */
maxSfb = psyOutChannel[0].sfbCnt - 1;
minSfb = startSfb[0];
if (nChannels == 2) {
maxSfb = max(maxSfb, (psyOutChannel[1].sfbCnt - 1));
minSfb = min(minSfb, startSfb[1]);
}
sfb = maxSfb;
enIdx = 0;
done = 0;
while (!done) {
for (ch=0; ch<nChannels; ch++) {
PSY_OUT_CHANNEL *psyOutChan = &psyOutChannel[ch];
if (sfb>=startSfb[ch] && sfb<psyOutChan->sfbCnt) {
/* sfb energy below border ? */
if (ahFlag[ch][sfb] != NO_AH && psyOutChan->sfbEnergy[sfb] < en[enIdx]){
/* allow hole */
ahFlag[ch][sfb] = NO_AH;
psyOutChan->sfbThreshold[sfb] = L_add(psyOutChan->sfbEnergy[sfb], psyOutChan->sfbEnergy[sfb]);
actPe = actPe - peData->peChannelData[ch].sfbPe[sfb];
}
if (actPe < desiredPe) {
done = 1;
break;
}
}
}
sfb = sfb - 1;
if (sfb < minSfb) {
/* restart with next energy border */
sfb = maxSfb;
enIdx = enIdx + 1;
if (enIdx - 4 >= 0)
done = 1;
}
}
}
}
/********************************************************************************
*
* function name:adaptThresholdsToPe
* description: two guesses for the reduction value and one final correction of the
* thresholds
*
**********************************************************************************/
static void adaptThresholdsToPe(PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
PSY_OUT_ELEMENT *psyOutElement,
Word16 logSfbEnergy[MAX_CHANNELS][MAX_GROUPED_SFB],
PE_DATA *peData,
const Word16 nChannels,
const Word16 desiredPe,
AH_PARAM *ahParam,
MINSNR_ADAPT_PARAM *msaParam)
{
Word16 noRedPe, redPe, redPeNoAH;
Word16 constPart, constPartNoAH;
Word16 nActiveLines, nActiveLinesNoAH;
Word16 desiredPeNoAH;
Word32 redVal, avgThrExp;
Word32 iter;
calcThreshExp(peData->thrExp, psyOutChannel, nChannels);
adaptMinSnr(psyOutChannel, logSfbEnergy, msaParam, nChannels);
initAvoidHoleFlag(peData->ahFlag, psyOutChannel, psyOutElement, nChannels, ahParam);
noRedPe = peData->pe;
constPart = peData->constPart;
nActiveLines = peData->nActiveLines;
/* first guess of reduction value t^0.25 = 2^((a-pen)/4*b) */
avgThrExp = pow2_xy((constPart - noRedPe), (nActiveLines << 2));
/* r1 = 2^((a-per)/4*b) - t^0.25 */
redVal = pow2_xy((constPart - desiredPe), (nActiveLines << 2)) - avgThrExp;
/* reduce thresholds */
reduceThresholds(psyOutChannel, peData->ahFlag, peData->thrExp, nChannels, redVal);
/* pe after first guess */
calcSfbPe(peData, psyOutChannel, nChannels);
redPe = peData->pe;
iter = 0;
do {
/* pe for bands where avoid hole is inactive */
calcPeNoAH(&redPeNoAH, &constPartNoAH, &nActiveLinesNoAH,
peData, peData->ahFlag, psyOutChannel, nChannels);
desiredPeNoAH = desiredPe -(redPe - redPeNoAH);
if (desiredPeNoAH < 0) {
desiredPeNoAH = 0;
}
/* second guess */
if (nActiveLinesNoAH > 0) {
avgThrExp = pow2_xy((constPartNoAH - redPeNoAH), (nActiveLinesNoAH << 2));
redVal = (redVal + pow2_xy((constPartNoAH - desiredPeNoAH), (nActiveLinesNoAH << 2))) - avgThrExp;
/* reduce thresholds */
reduceThresholds(psyOutChannel, peData->ahFlag, peData->thrExp, nChannels, redVal);
}
calcSfbPe(peData, psyOutChannel, nChannels);
redPe = peData->pe;
iter = iter+1;
} while ((20 * abs_s(redPe - desiredPe) > desiredPe) && (iter < 2));
if ((100 * redPe < 115 * desiredPe)) {
correctThresh(psyOutChannel, peData->ahFlag, peData, peData->thrExp, redVal,
nChannels, desiredPe - redPe);
}
else {
Word16 desiredPe105 = (105 * desiredPe) / 100;
reduceMinSnr(psyOutChannel, peData, peData->ahFlag,
nChannels, desiredPe105);
allowMoreHoles(psyOutChannel, psyOutElement, peData, peData->ahFlag,
ahParam, nChannels, desiredPe105);
}
}
/*****************************************************************************
*
* function name: calcBitSave
* description: Calculates percentage of bit save, see figure below
* returns:
* input: parameters and bitres-fullness
* output: percentage of bit save
*
*****************************************************************************/
static Word16 calcBitSave(Word16 fillLevel,
const Word16 clipLow,
const Word16 clipHigh,
const Word16 minBitSave,
const Word16 maxBitSave)
{
Word16 bitsave = 0;
fillLevel = max(fillLevel, clipLow);
fillLevel = min(fillLevel, clipHigh);
if(clipHigh-clipLow)
bitsave = (maxBitSave - (((maxBitSave-minBitSave)*(fillLevel-clipLow))/
(clipHigh-clipLow)));
return (bitsave);
}
/*****************************************************************************
*
* function name: calcBitSpend
* description: Calculates percentage of bit spend, see figure below
* returns:
* input: parameters and bitres-fullness
* output: percentage of bit spend
*
*****************************************************************************/
static Word16 calcBitSpend(Word16 fillLevel,
const Word16 clipLow,
const Word16 clipHigh,
const Word16 minBitSpend,
const Word16 maxBitSpend)
{
Word16 bitspend = 1;
fillLevel = max(fillLevel, clipLow);
fillLevel = min(fillLevel, clipHigh);
if(clipHigh-clipLow)
bitspend = (minBitSpend + ((maxBitSpend - minBitSpend)*(fillLevel - clipLow) /
(clipHigh-clipLow)));
return (bitspend);
}
/*****************************************************************************
*
* function name: adjustPeMinMax()
* description: adjusts peMin and peMax parameters over time
* returns:
* input: current pe, peMin, peMax
* output: adjusted peMin/peMax
*
*****************************************************************************/
static void adjustPeMinMax(const Word16 currPe,
Word16 *peMin,
Word16 *peMax)
{
Word16 minFacHi, maxFacHi, minFacLo, maxFacLo;
Word16 diff;
Word16 minDiff = extract_l(currPe / 6);
minFacHi = 30;
maxFacHi = 100;
minFacLo = 14;
maxFacLo = 7;
diff = currPe - *peMax ;
if (diff > 0) {
*peMin = *peMin + ((diff * minFacHi) / 100);
*peMax = *peMax + ((diff * maxFacHi) / 100);
} else {
diff = *peMin - currPe;
if (diff > 0) {
*peMin = *peMin - ((diff * minFacLo) / 100);
*peMax = *peMax - ((diff * maxFacLo) / 100);
} else {
*peMin = *peMin + ((currPe - *peMin) * minFacHi / 100);
*peMax = *peMax - ((*peMax - currPe) * maxFacLo / 100);
}
}
if ((*peMax - *peMin) < minDiff) {
Word16 partLo, partHi;
partLo = max(0, (currPe - *peMin));
partHi = max(0, (*peMax - currPe));
*peMax = currPe + ((partHi * minDiff) / (partLo + partHi));
*peMin = currPe - ((partLo * minDiff) / (partLo + partHi));
*peMin = max(0, *peMin);
}
}
/*****************************************************************************
*
* function name: BitresCalcBitFac
* description: calculates factor of spending bits for one frame
* 1.0 : take all frame dynpart bits
* >1.0 : take all frame dynpart bits + bitres
* <1.0 : put bits in bitreservoir
* returns: BitFac*100
* input: bitres-fullness, pe, blockType, parameter-settings
* output:
*
*****************************************************************************/
static Word16 bitresCalcBitFac( const Word16 bitresBits,
const Word16 maxBitresBits,
const Word16 pe,
const Word16 windowSequence,
const Word16 avgBits,
const Word16 maxBitFac,
ADJ_THR_STATE *AdjThr,
ATS_ELEMENT *adjThrChan)
{
BRES_PARAM *bresParam;
Word16 pex;
Word16 fillLevel;
Word16 bitSave, bitSpend, bitresFac;
fillLevel = extract_l((100* bitresBits) / maxBitresBits);
if (windowSequence != SHORT_WINDOW)
bresParam = &(AdjThr->bresParamLong);
else
bresParam = &(AdjThr->bresParamShort);
pex = max(pe, adjThrChan->peMin);
pex = min(pex,adjThrChan->peMax);
bitSave = calcBitSave(fillLevel,
bresParam->clipSaveLow, bresParam->clipSaveHigh,
bresParam->minBitSave, bresParam->maxBitSave);
bitSpend = calcBitSpend(fillLevel,
bresParam->clipSpendLow, bresParam->clipSpendHigh,
bresParam->minBitSpend, bresParam->maxBitSpend);
if(adjThrChan->peMax != adjThrChan->peMin)
bitresFac = (100 - bitSave) + extract_l(((bitSpend + bitSave) * (pex - adjThrChan->peMin)) /
(adjThrChan->peMax - adjThrChan->peMin));
else
bitresFac = 0x7fff;
bitresFac = min(bitresFac,
(100-30 + extract_l((100 * bitresBits) / avgBits)));
bitresFac = min(bitresFac, maxBitFac);
adjustPeMinMax(pe, &adjThrChan->peMin, &adjThrChan->peMax);
return bitresFac;
}
/*****************************************************************************
*
* function name: AdjThrInit
* description: init thresholds parameter
*
*****************************************************************************/
void AdjThrInit(ADJ_THR_STATE *hAdjThr,
const Word32 meanPe,
Word32 chBitrate)
{
ATS_ELEMENT* atsElem = &hAdjThr->adjThrStateElem;
MINSNR_ADAPT_PARAM *msaParam = &atsElem->minSnrAdaptParam;
/* common for all elements: */
/* parameters for bitres control */
hAdjThr->bresParamLong.clipSaveLow = 20;
hAdjThr->bresParamLong.clipSaveHigh = 95;
hAdjThr->bresParamLong.minBitSave = -5;
hAdjThr->bresParamLong.maxBitSave = 30;
hAdjThr->bresParamLong.clipSpendLow = 20;
hAdjThr->bresParamLong.clipSpendHigh = 95;
hAdjThr->bresParamLong.minBitSpend = -10;
hAdjThr->bresParamLong.maxBitSpend = 40;
hAdjThr->bresParamShort.clipSaveLow = 20;
hAdjThr->bresParamShort.clipSaveHigh = 75;
hAdjThr->bresParamShort.minBitSave = 0;
hAdjThr->bresParamShort.maxBitSave = 20;
hAdjThr->bresParamShort.clipSpendLow = 20;
hAdjThr->bresParamShort.clipSpendHigh = 75;
hAdjThr->bresParamShort.minBitSpend = -5;
hAdjThr->bresParamShort.maxBitSpend = 50;
/* specific for each element: */
/* parameters for bitres control */
atsElem->peMin = extract_l(((80*meanPe) / 100));
atsElem->peMax = extract_l(((120*meanPe) / 100));
/* additional pe offset to correct pe2bits for low bitrates */
atsElem->peOffset = 0;
if (chBitrate < 32000) {
atsElem->peOffset = max(50, (100 - extract_l((100 * chBitrate) / 32000)));
}
/* avoid hole parameters */
if (chBitrate > 20000) {
atsElem->ahParam.modifyMinSnr = TRUE;
atsElem->ahParam.startSfbL = 15;
atsElem->ahParam.startSfbS = 3;
}
else {
atsElem->ahParam.modifyMinSnr = FALSE;
atsElem->ahParam.startSfbL = 0;
atsElem->ahParam.startSfbS = 0;
}
/* minSnr adaptation */
/* maximum reduction of minSnr goes down to minSnr^maxRed */
msaParam->maxRed = 0x20000000; /* *0.25f */
/* start adaptation of minSnr for avgEn/sfbEn > startRatio */
msaParam->startRatio = 0x0ccccccd; /* 10 */
/* maximum minSnr reduction to minSnr^maxRed is reached for
avgEn/sfbEn >= maxRatio */
msaParam->maxRatio = 0x0020c49c; /* 1000 */
/* helper variables to interpolate minSnr reduction for
avgEn/sfbEn between startRatio and maxRatio */
msaParam->redRatioFac = 0xfb333333; /* -0.75/20 */
msaParam->redOffs = 0x30000000; /* msaParam->redRatioFac * 10*log10(msaParam->startRatio) */
/* pe correction */
atsElem->peLast = 0;
atsElem->dynBitsLast = 0;
atsElem->peCorrectionFactor = 100; /* 1.0 */
}
/*****************************************************************************
*
* function name: calcPeCorrection
* description: calculates the desired perceptual entropy factor
* It is between 0.85 and 1.15
*
*****************************************************************************/
static void calcPeCorrection(Word16 *correctionFac,
const Word16 peAct,
const Word16 peLast,
const Word16 bitsLast)
{
Word32 peAct100 = 100 * peAct;
Word32 peLast100 = 100 * peLast;
Word16 peBitsLast = bits2pe(bitsLast);
if ((bitsLast > 0) &&
(peAct100 < (150 * peLast)) && (peAct100 > (70 * peLast)) &&
((120 * peBitsLast) > peLast100 ) && (( 65 * peBitsLast) < peLast100))
{
Word16 newFac = (100 * peLast) / peBitsLast;
/* dead zone */
if (newFac < 100) {
newFac = min(((110 * newFac) / 100), 100);
newFac = max(newFac, 85);
}
else {
newFac = max(((90 * newFac) / 100), 100);
newFac = min(newFac, 115);
}
if ((newFac > 100 && *correctionFac < 100) ||
(newFac < 100 && *correctionFac > 100)) {
*correctionFac = 100;
}
/* faster adaptation towards 1.0, slower in the other direction */
if ((*correctionFac < 100 && newFac < *correctionFac) ||
(*correctionFac > 100 && newFac > *correctionFac))
*correctionFac = (85 * *correctionFac + 15 * newFac) / 100;
else
*correctionFac = (70 * *correctionFac + 30 * newFac) / 100;
*correctionFac = min(*correctionFac, 115);
*correctionFac = max(*correctionFac, 85);
}
else {
*correctionFac = 100;
}
}
/********************************************************************************
*
* function name: AdjustThresholds
* description: Adjust thresholds to the desired bitrate
*
**********************************************************************************/
void AdjustThresholds(ADJ_THR_STATE *adjThrState,
ATS_ELEMENT *AdjThrStateElement,
PSY_OUT_CHANNEL psyOutChannel[MAX_CHANNELS],
PSY_OUT_ELEMENT *psyOutElement,
Word16 *chBitDistribution,
Word16 logSfbEnergy[MAX_CHANNELS][MAX_GROUPED_SFB],
Word16 sfbNRelevantLines[MAX_CHANNELS][MAX_GROUPED_SFB],
QC_OUT_ELEMENT *qcOE,
ELEMENT_BITS *elBits,
const Word16 nChannels,
const Word16 maxBitFac)
{
PE_DATA peData;
Word16 noRedPe, grantedPe, grantedPeCorr;
Word16 curWindowSequence;
Word16 bitFactor;
Word16 avgBits = (elBits->averageBits - (qcOE->staticBitsUsed + qcOE->ancBitsUsed));
Word16 bitresBits = elBits->bitResLevel;
Word16 maxBitresBits = elBits->maxBits;
Word16 sideInfoBits = (qcOE->staticBitsUsed + qcOE->ancBitsUsed);
Word16 ch;
memset(&peData, 0, sizeof(peData));
prepareSfbPe(&peData, psyOutChannel, logSfbEnergy, sfbNRelevantLines, nChannels, AdjThrStateElement->peOffset);
/* pe without reduction */
calcSfbPe(&peData, psyOutChannel, nChannels);
noRedPe = peData.pe;
curWindowSequence = LONG_WINDOW;
if (nChannels == 2) {
if ((psyOutChannel[0].windowSequence == SHORT_WINDOW) ||
(psyOutChannel[1].windowSequence == SHORT_WINDOW)) {
curWindowSequence = SHORT_WINDOW;
}
}
else {
curWindowSequence = psyOutChannel[0].windowSequence;
}
/* bit factor */
bitFactor = bitresCalcBitFac(bitresBits, maxBitresBits, noRedPe+5*sideInfoBits,
curWindowSequence, avgBits, maxBitFac,
adjThrState,
AdjThrStateElement);
/* desired pe */
grantedPe = ((bitFactor * bits2pe(avgBits)) / 100);
/* correction of pe value */
calcPeCorrection(&(AdjThrStateElement->peCorrectionFactor),
min(grantedPe, noRedPe),
AdjThrStateElement->peLast,
AdjThrStateElement->dynBitsLast);
grantedPeCorr = (grantedPe * AdjThrStateElement->peCorrectionFactor) / 100;
if (grantedPeCorr < noRedPe && noRedPe > peData.offset) {
/* calc threshold necessary for desired pe */
adaptThresholdsToPe(psyOutChannel,
psyOutElement,
logSfbEnergy,
&peData,
nChannels,
grantedPeCorr,
&AdjThrStateElement->ahParam,
&AdjThrStateElement->minSnrAdaptParam);
}
/* calculate relative distribution */
for (ch=0; ch<nChannels; ch++) {
Word16 peOffsDiff = peData.pe - peData.offset;
chBitDistribution[ch] = 200;
if (peOffsDiff > 0) {
Word32 temp = 1000 - (nChannels * 200);
chBitDistribution[ch] = chBitDistribution[ch] +
(temp * peData.peChannelData[ch].pe) / peOffsDiff;
}
}
/* store pe */
qcOE->pe = noRedPe;
/* update last pe */
AdjThrStateElement->peLast = grantedPe;
}
/********************************************************************************
*
* function name: AdjThrUpdate
* description: save dynBitsUsed for correction of bits2pe relation
*
**********************************************************************************/
void AdjThrUpdate(ATS_ELEMENT *AdjThrStateElement,
const Word16 dynBitsUsed)
{
AdjThrStateElement->dynBitsLast = dynBitsUsed;
}