mirror of
https://github.com/mstorsjo/fdk-aac.git
synced 2025-02-10 16:40:42 +01:00
* AAC-Encoder - AAC-ELD core encoder audio quality tuning. Update tuning tables, configure bitreservoir size and adapt afterburner iteration value. Modified file(s): libAACenc/src/aacenc.h libAACenc/src/aacenc_lib.cpp libAACenc/src/adj_thr.cpp libAACenc/src/adj_thr.h libAACenc/src/adj_thr_data.h libAACenc/src/bandwidth.cpp libAACenc/src/pnsparam.cpp libAACenc/src/qc_main.cpp - Introduze dead zone quantizer for ELD to improve audio quality at certain configurations. Modified file(s): libAACenc/src/aacenc_lib.cpp libAACenc/src/adj_thr.cpp libAACenc/src/adj_thr.h libAACenc/src/qc_data.h libAACenc/src/qc_main.cpp libAACenc/src/quantize.cpp libAACenc/src/quantize.h libAACenc/src/sf_estim.cpp libAACenc/src/sf_estim.h - Revise TNS module to improve ELD audio quality. - Use new window function and separate prediction gain according TNS filters. - Add missing memory initilization to TNS configuration. Modified file(s): libAACenc/src/aacenc_lib.cpp libAACenc/src/aacenc_tns.cpp libAACenc/src/aacenc_tns.h libAACenc/src/psy_main.cpp libAACenc/src/tns_func.h * SBR-Encoder - Revise frequency resolution calculation and handle differently depending on number of envelopes and split frames decision. - Add and adjust ELD SBR tuning tables. Modified file(s): libSBRenc/include/sbr_encoder.h libSBRenc/src/bit_sbr.h libSBRenc/src/env_est.cpp libSBRenc/src/fram_gen.cpp libSBRenc/src/fram_gen.h libSBRenc/src/mh_det.cpp libSBRenc/src/sbr_def.h libSBRenc/src/sbr_encoder.cpp libSBRenc/src/sbr_rom.cpp libSBRenc/src/tran_det.cpp - Replace ELD transient detector with fast implementation. Modified file(s): libSBRenc/src/env_est.cpp libSBRenc/src/env_est.h libSBRenc/src/fram_gen.cpp libSBRenc/src/sbr_def.h libSBRenc/src/sbr_encoder.cpp libSBRenc/src/tran_det.cpp libSBRenc/src/tran_det.h * FDK-Library - Introduce generic compare function in tools library. Modified file(s): libFDK/include/fixpoint_math.h libFDK/src/FDK_core.cpp * SBR-Encoder - Revise ELD frame splitter to improve bit distribution. Modified file(s): libSBRenc/include/sbr_encoder.h libSBRenc/src/bit_sbr.h libSBRenc/src/env_est.cpp libSBRenc/src/fram_gen.cpp libSBRenc/src/fram_gen.h libSBRenc/src/sbr_encoder.cpp libSBRenc/src/tran_det.cpp libSBRenc/src/tran_det.h - Configure amplitude resolution according the tonality of the audio signal. Modified file(s): libSBRenc/include/sbr_encoder.h libSBRenc/src/bit_sbr.h libSBRenc/src/env_est.cpp libSBRenc/src/nf_est.cpp libSBRenc/src/nf_est.h libSBRenc/src/sbr_def.h libSBRenc/src/sbr_encoder.cpp libSBRenc/src/ton_corr.cpp libSBRenc/src/ton_corr.h libSBRenc/src/tran_det.cpp libSBRenc/src/tran_det.h Change-Id: Ie0672b989a06ee63b50240616b8d1d4b790b6cb2
1388 lines
58 KiB
C++
1388 lines
58 KiB
C++
|
|
/* -----------------------------------------------------------------------------------------------------------
|
|
Software License for The Fraunhofer FDK AAC Codec Library for Android
|
|
|
|
© Copyright 1995 - 2015 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V.
|
|
All rights reserved.
|
|
|
|
1. INTRODUCTION
|
|
The Fraunhofer FDK AAC Codec Library for Android ("FDK AAC Codec") is software that implements
|
|
the MPEG Advanced Audio Coding ("AAC") encoding and decoding scheme for digital audio.
|
|
This FDK AAC Codec software is intended to be used on a wide variety of Android devices.
|
|
|
|
AAC's HE-AAC and HE-AAC v2 versions are regarded as today's most efficient general perceptual
|
|
audio codecs. AAC-ELD is considered the best-performing full-bandwidth communications codec by
|
|
independent studies and is widely deployed. AAC has been standardized by ISO and IEC as part
|
|
of the MPEG specifications.
|
|
|
|
Patent licenses for necessary patent claims for the FDK AAC Codec (including those of Fraunhofer)
|
|
may be obtained through Via Licensing (www.vialicensing.com) or through the respective patent owners
|
|
individually for the purpose of encoding or decoding bit streams in products that are compliant with
|
|
the ISO/IEC MPEG audio standards. Please note that most manufacturers of Android devices already license
|
|
these patent claims through Via Licensing or directly from the patent owners, and therefore FDK AAC Codec
|
|
software may already be covered under those patent licenses when it is used for those licensed purposes only.
|
|
|
|
Commercially-licensed AAC software libraries, including floating-point versions with enhanced sound quality,
|
|
are also available from Fraunhofer. Users are encouraged to check the Fraunhofer website for additional
|
|
applications information and documentation.
|
|
|
|
2. COPYRIGHT LICENSE
|
|
|
|
Redistribution and use in source and binary forms, with or without modification, are permitted without
|
|
payment of copyright license fees provided that you satisfy the following conditions:
|
|
|
|
You must retain the complete text of this software license in redistributions of the FDK AAC Codec or
|
|
your modifications thereto in source code form.
|
|
|
|
You must retain the complete text of this software license in the documentation and/or other materials
|
|
provided with redistributions of the FDK AAC Codec or your modifications thereto in binary form.
|
|
You must make available free of charge copies of the complete source code of the FDK AAC Codec and your
|
|
modifications thereto to recipients of copies in binary form.
|
|
|
|
The name of Fraunhofer may not be used to endorse or promote products derived from this library without
|
|
prior written permission.
|
|
|
|
You may not charge copyright license fees for anyone to use, copy or distribute the FDK AAC Codec
|
|
software or your modifications thereto.
|
|
|
|
Your modified versions of the FDK AAC Codec must carry prominent notices stating that you changed the software
|
|
and the date of any change. For modified versions of the FDK AAC Codec, the term
|
|
"Fraunhofer FDK AAC Codec Library for Android" must be replaced by the term
|
|
"Third-Party Modified Version of the Fraunhofer FDK AAC Codec Library for Android."
|
|
|
|
3. NO PATENT LICENSE
|
|
|
|
NO EXPRESS OR IMPLIED LICENSES TO ANY PATENT CLAIMS, including without limitation the patents of Fraunhofer,
|
|
ARE GRANTED BY THIS SOFTWARE LICENSE. Fraunhofer provides no warranty of patent non-infringement with
|
|
respect to this software.
|
|
|
|
You may use this FDK AAC Codec software or modifications thereto only for purposes that are authorized
|
|
by appropriate patent licenses.
|
|
|
|
4. DISCLAIMER
|
|
|
|
This FDK AAC Codec software is provided by Fraunhofer on behalf of the copyright holders and contributors
|
|
"AS IS" and WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, including but not limited to the implied warranties
|
|
of merchantability and fitness for a particular purpose. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
|
CONTRIBUTORS BE LIABLE for any direct, indirect, incidental, special, exemplary, or consequential damages,
|
|
including but not limited to procurement of substitute goods or services; loss of use, data, or profits,
|
|
or business interruption, however caused and on any theory of liability, whether in contract, strict
|
|
liability, or tort (including negligence), arising in any way out of the use of this software, even if
|
|
advised of the possibility of such damage.
|
|
|
|
5. CONTACT INFORMATION
|
|
|
|
Fraunhofer Institute for Integrated Circuits IIS
|
|
Attention: Audio and Multimedia Departments - FDK AAC LL
|
|
Am Wolfsmantel 33
|
|
91058 Erlangen, Germany
|
|
|
|
www.iis.fraunhofer.de/amm
|
|
amm-info@iis.fraunhofer.de
|
|
----------------------------------------------------------------------------------------------------------- */
|
|
|
|
/******************************** MPEG Audio Encoder **************************
|
|
|
|
Initial author: M.Werner
|
|
contents/description: Psychoaccoustic major function block
|
|
|
|
******************************************************************************/
|
|
|
|
#include "psy_const.h"
|
|
|
|
#include "block_switch.h"
|
|
#include "transform.h"
|
|
#include "spreading.h"
|
|
#include "pre_echo_control.h"
|
|
#include "band_nrg.h"
|
|
#include "psy_configuration.h"
|
|
#include "psy_data.h"
|
|
#include "ms_stereo.h"
|
|
#include "interface.h"
|
|
#include "psy_main.h"
|
|
#include "grp_data.h"
|
|
#include "tns_func.h"
|
|
#include "pns_func.h"
|
|
#include "tonality.h"
|
|
#include "aacEnc_ram.h"
|
|
#include "intensity.h"
|
|
|
|
|
|
|
|
/* blending to reduce gibbs artifacts */
|
|
#define FADE_OUT_LEN 6
|
|
static const FIXP_DBL fadeOutFactor[FADE_OUT_LEN] = {1840644096, 1533870080, 1227096064, 920322048, 613548032, 306774016};
|
|
|
|
/* forward definitions */
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
functionname: FDKaacEnc_PsyNew
|
|
description: allocates memory for psychoacoustic
|
|
returns: an error code
|
|
input: pointer to a psych handle
|
|
|
|
*****************************************************************************/
|
|
AAC_ENCODER_ERROR FDKaacEnc_PsyNew(PSY_INTERNAL **phpsy,
|
|
const INT nElements,
|
|
const INT nChannels
|
|
,UCHAR *dynamic_RAM
|
|
)
|
|
{
|
|
AAC_ENCODER_ERROR ErrorStatus;
|
|
PSY_INTERNAL *hPsy;
|
|
INT i;
|
|
|
|
hPsy = GetRam_aacEnc_PsyInternal();
|
|
*phpsy = hPsy;
|
|
if (hPsy == NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
|
|
for (i=0; i<nElements; i++) {
|
|
/* PSY_ELEMENT */
|
|
hPsy->psyElement[i] = GetRam_aacEnc_PsyElement(i);
|
|
if (hPsy->psyElement[i] == NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
for (i=0; i<nChannels; i++) {
|
|
/* PSY_STATIC */
|
|
hPsy->pStaticChannels[i] = GetRam_aacEnc_PsyStatic(i);
|
|
if (hPsy->pStaticChannels[i]==NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
/* AUDIO INPUT BUFFER */
|
|
hPsy->pStaticChannels[i]->psyInputBuffer = GetRam_aacEnc_PsyInputBuffer(i);
|
|
if (hPsy->pStaticChannels[i]->psyInputBuffer==NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* reusable psych memory */
|
|
hPsy->psyDynamic = GetRam_aacEnc_PsyDynamic(0, dynamic_RAM);
|
|
|
|
return AAC_ENC_OK;
|
|
|
|
bail:
|
|
FDKaacEnc_PsyClose(phpsy, NULL);
|
|
|
|
return ErrorStatus;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
|
|
functionname: FDKaacEnc_PsyOutNew
|
|
description: allocates memory for psyOut struc
|
|
returns: an error code
|
|
input: pointer to a psych handle
|
|
|
|
*****************************************************************************/
|
|
AAC_ENCODER_ERROR FDKaacEnc_PsyOutNew(PSY_OUT **phpsyOut,
|
|
const INT nElements,
|
|
const INT nChannels,
|
|
const INT nSubFrames
|
|
,UCHAR *dynamic_RAM
|
|
)
|
|
{
|
|
AAC_ENCODER_ERROR ErrorStatus;
|
|
int n, i;
|
|
int elInc = 0, chInc = 0;
|
|
|
|
for (n=0; n<nSubFrames; n++) {
|
|
phpsyOut[n] = GetRam_aacEnc_PsyOut(n);
|
|
|
|
if (phpsyOut[n] == NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
|
|
for (i=0; i<nChannels; i++) {
|
|
phpsyOut[n]->pPsyOutChannels[i] = GetRam_aacEnc_PsyOutChannel(chInc++);
|
|
}
|
|
|
|
for (i=0; i<nElements; i++) {
|
|
phpsyOut[n]->psyOutElement[i] = GetRam_aacEnc_PsyOutElements(elInc++);
|
|
if (phpsyOut[n]->psyOutElement[i] == NULL) {
|
|
ErrorStatus = AAC_ENC_NO_MEMORY;
|
|
goto bail;
|
|
}
|
|
}
|
|
} /* nSubFrames */
|
|
|
|
return AAC_ENC_OK;
|
|
|
|
bail:
|
|
FDKaacEnc_PsyClose(NULL, phpsyOut);
|
|
return ErrorStatus;
|
|
}
|
|
|
|
|
|
AAC_ENCODER_ERROR FDKaacEnc_psyInitStates(PSY_INTERNAL *hPsy,
|
|
PSY_STATIC* psyStatic,
|
|
AUDIO_OBJECT_TYPE audioObjectType)
|
|
{
|
|
/* init input buffer */
|
|
FDKmemclear(psyStatic->psyInputBuffer, MAX_INPUT_BUFFER_SIZE*sizeof(INT_PCM));
|
|
|
|
FDKaacEnc_InitBlockSwitching(&psyStatic->blockSwitchingControl,
|
|
isLowDelay(audioObjectType)
|
|
);
|
|
|
|
return AAC_ENC_OK;
|
|
}
|
|
|
|
|
|
AAC_ENCODER_ERROR FDKaacEnc_psyInit(PSY_INTERNAL *hPsy,
|
|
PSY_OUT **phpsyOut,
|
|
const INT nSubFrames,
|
|
const INT nMaxChannels,
|
|
const AUDIO_OBJECT_TYPE audioObjectType,
|
|
CHANNEL_MAPPING *cm)
|
|
{
|
|
AAC_ENCODER_ERROR ErrorStatus = AAC_ENC_OK;
|
|
int i, ch, n, chInc = 0, resetChannels = 3;
|
|
|
|
if ( (nMaxChannels>2) && (cm->nChannels==2) ) {
|
|
chInc = 1;
|
|
FDKaacEnc_psyInitStates(hPsy, hPsy->pStaticChannels[0], audioObjectType);
|
|
}
|
|
|
|
if ( (nMaxChannels==2) ) {
|
|
resetChannels = 0;
|
|
}
|
|
|
|
for (i=0; i<cm->nElements; i++) {
|
|
for (ch=0; ch<cm->elInfo[i].nChannelsInEl; ch++) {
|
|
if (cm->elInfo[i].elType!=ID_LFE) {
|
|
hPsy->psyElement[i]->psyStatic[ch] = hPsy->pStaticChannels[chInc];
|
|
if (chInc>=resetChannels) {
|
|
FDKaacEnc_psyInitStates(hPsy, hPsy->psyElement[i]->psyStatic[ch], audioObjectType);
|
|
}
|
|
hPsy->psyElement[i]->psyStatic[ch]->isLFE = 0;
|
|
}
|
|
else {
|
|
hPsy->psyElement[i]->psyStatic[ch] = hPsy->pStaticChannels[nMaxChannels-1];
|
|
hPsy->psyElement[i]->psyStatic[ch]->isLFE = 1;
|
|
}
|
|
chInc++;
|
|
}
|
|
}
|
|
|
|
for (n=0; n<nSubFrames; n++) {
|
|
chInc = 0;
|
|
for (i=0; i<cm->nElements; i++) {
|
|
for (ch=0; ch<cm->elInfo[i].nChannelsInEl; ch++) {
|
|
phpsyOut[n]->psyOutElement[i]->psyOutChannel[ch] = phpsyOut[n]->pPsyOutChannels[chInc++];
|
|
}
|
|
}
|
|
}
|
|
|
|
return ErrorStatus;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
functionname: FDKaacEnc_psyMainInit
|
|
description: initializes psychoacoustic
|
|
returns: an error code
|
|
|
|
*****************************************************************************/
|
|
|
|
AAC_ENCODER_ERROR FDKaacEnc_psyMainInit(PSY_INTERNAL *hPsy,
|
|
AUDIO_OBJECT_TYPE audioObjectType,
|
|
CHANNEL_MAPPING *cm,
|
|
INT sampleRate,
|
|
INT granuleLength,
|
|
INT bitRate,
|
|
INT tnsMask,
|
|
INT bandwidth,
|
|
INT usePns,
|
|
INT useIS,
|
|
UINT syntaxFlags,
|
|
ULONG initFlags)
|
|
{
|
|
AAC_ENCODER_ERROR ErrorStatus;
|
|
int i, ch;
|
|
int channelsEff = cm->nChannelsEff;
|
|
int tnsChannels = 0;
|
|
FB_TYPE filterBank;
|
|
|
|
|
|
switch(FDKaacEnc_GetMonoStereoMode(cm->encMode)) {
|
|
/* ... and map to tnsChannels */
|
|
case EL_MODE_MONO: tnsChannels = 1; break;
|
|
case EL_MODE_STEREO: tnsChannels = 2; break;
|
|
default: tnsChannels = 0;
|
|
}
|
|
|
|
switch (audioObjectType)
|
|
{
|
|
default: filterBank = FB_LC; break;
|
|
case AOT_ER_AAC_LD: filterBank = FB_LD; break;
|
|
case AOT_ER_AAC_ELD: filterBank = FB_ELD; break;
|
|
}
|
|
|
|
hPsy->granuleLength = granuleLength;
|
|
|
|
ErrorStatus = FDKaacEnc_InitPsyConfiguration(bitRate/channelsEff, sampleRate, bandwidth, LONG_WINDOW, hPsy->granuleLength, useIS, &(hPsy->psyConf[0]), filterBank);
|
|
if (ErrorStatus != AAC_ENC_OK)
|
|
return ErrorStatus;
|
|
|
|
ErrorStatus = FDKaacEnc_InitTnsConfiguration(
|
|
(bitRate*tnsChannels)/channelsEff,
|
|
sampleRate,
|
|
tnsChannels,
|
|
LONG_WINDOW,
|
|
hPsy->granuleLength,
|
|
isLowDelay(audioObjectType),
|
|
(syntaxFlags&AC_SBR_PRESENT)?1:0,
|
|
&(hPsy->psyConf[0].tnsConf),
|
|
&hPsy->psyConf[0],
|
|
(INT)(tnsMask&2),
|
|
(INT)(tnsMask&8) );
|
|
|
|
if (ErrorStatus != AAC_ENC_OK)
|
|
return ErrorStatus;
|
|
|
|
if (granuleLength > 512) {
|
|
ErrorStatus = FDKaacEnc_InitPsyConfiguration(bitRate/channelsEff, sampleRate, bandwidth, SHORT_WINDOW, hPsy->granuleLength, useIS, &hPsy->psyConf[1], filterBank);
|
|
if (ErrorStatus != AAC_ENC_OK)
|
|
return ErrorStatus;
|
|
|
|
ErrorStatus = FDKaacEnc_InitTnsConfiguration(
|
|
(bitRate*tnsChannels)/channelsEff,
|
|
sampleRate,
|
|
tnsChannels,
|
|
SHORT_WINDOW,
|
|
hPsy->granuleLength,
|
|
isLowDelay(audioObjectType),
|
|
(syntaxFlags&AC_SBR_PRESENT)?1:0,
|
|
&hPsy->psyConf[1].tnsConf,
|
|
&hPsy->psyConf[1],
|
|
(INT)(tnsMask&1),
|
|
(INT)(tnsMask&4) );
|
|
|
|
if (ErrorStatus != AAC_ENC_OK)
|
|
return ErrorStatus;
|
|
|
|
}
|
|
|
|
|
|
for (i=0; i<cm->nElements; i++) {
|
|
for (ch=0; ch<cm->elInfo[i].nChannelsInEl; ch++) {
|
|
if (initFlags) {
|
|
/* reset states */
|
|
FDKaacEnc_psyInitStates(hPsy, hPsy->psyElement[i]->psyStatic[ch], audioObjectType);
|
|
}
|
|
|
|
FDKaacEnc_InitPreEchoControl(hPsy->psyElement[i]->psyStatic[ch]->sfbThresholdnm1,
|
|
&hPsy->psyElement[i]->psyStatic[ch]->calcPreEcho,
|
|
hPsy->psyConf[0].sfbCnt,
|
|
hPsy->psyConf[0].sfbPcmQuantThreshold,
|
|
&hPsy->psyElement[i]->psyStatic[ch]->mdctScalenm1);
|
|
}
|
|
}
|
|
|
|
ErrorStatus = FDKaacEnc_InitPnsConfiguration(&hPsy->psyConf[0].pnsConf,
|
|
bitRate/channelsEff,
|
|
sampleRate,
|
|
usePns,
|
|
hPsy->psyConf[0].sfbCnt,
|
|
hPsy->psyConf[0].sfbOffset,
|
|
cm->elInfo[0].nChannelsInEl,
|
|
(hPsy->psyConf[0].filterbank == FB_LC));
|
|
if (ErrorStatus != AAC_ENC_OK)
|
|
return ErrorStatus;
|
|
|
|
ErrorStatus = FDKaacEnc_InitPnsConfiguration(&hPsy->psyConf[1].pnsConf,
|
|
bitRate/channelsEff,
|
|
sampleRate,
|
|
usePns,
|
|
hPsy->psyConf[1].sfbCnt,
|
|
hPsy->psyConf[1].sfbOffset,
|
|
cm->elInfo[1].nChannelsInEl,
|
|
(hPsy->psyConf[1].filterbank == FB_LC));
|
|
return ErrorStatus;
|
|
}
|
|
|
|
|
|
static
|
|
void FDKaacEnc_deinterleaveInputBuffer(INT_PCM *pOutputSamples,
|
|
INT_PCM *pInputSamples,
|
|
INT nSamples,
|
|
INT nChannels)
|
|
{
|
|
INT k;
|
|
/* deinterlave input samples and write to output buffer */
|
|
for (k=0; k<nSamples; k++) {
|
|
pOutputSamples[k] = pInputSamples[k*nChannels];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
functionname: FDKaacEnc_psyMain
|
|
description: psychoacoustic
|
|
returns: an error code
|
|
|
|
This function assumes that enough input data is in the modulo buffer.
|
|
|
|
*****************************************************************************/
|
|
|
|
AAC_ENCODER_ERROR FDKaacEnc_psyMain(INT channels,
|
|
PSY_ELEMENT *psyElement,
|
|
PSY_DYNAMIC *psyDynamic,
|
|
PSY_CONFIGURATION *psyConf,
|
|
PSY_OUT_ELEMENT *RESTRICT psyOutElement,
|
|
INT_PCM *pInput,
|
|
INT *chIdx,
|
|
INT totalChannels
|
|
)
|
|
{
|
|
const INT commonWindow = 1;
|
|
INT maxSfbPerGroup[(2)];
|
|
INT mdctSpectrum_e;
|
|
INT ch; /* counts through channels */
|
|
INT w; /* counts through windows */
|
|
INT sfb; /* counts through scalefactor bands */
|
|
INT line; /* counts through lines */
|
|
|
|
PSY_CONFIGURATION *RESTRICT hPsyConfLong = &psyConf[0];
|
|
PSY_CONFIGURATION *RESTRICT hPsyConfShort = &psyConf[1];
|
|
PSY_OUT_CHANNEL **RESTRICT psyOutChannel = psyOutElement->psyOutChannel;
|
|
FIXP_SGL sfbTonality[(2)][MAX_SFB_LONG];
|
|
|
|
PSY_STATIC **RESTRICT psyStatic = psyElement->psyStatic;
|
|
|
|
PSY_DATA *RESTRICT psyData[(2)];
|
|
TNS_DATA *RESTRICT tnsData[(2)];
|
|
PNS_DATA *RESTRICT pnsData[(2)];
|
|
|
|
INT zeroSpec = TRUE; /* means all spectral lines are zero */
|
|
|
|
INT blockSwitchingOffset;
|
|
|
|
PSY_CONFIGURATION *RESTRICT hThisPsyConf[(2)];
|
|
INT windowLength[(2)];
|
|
INT nWindows[(2)];
|
|
INT wOffset;
|
|
|
|
INT maxSfb[(2)];
|
|
INT *pSfbMaxScaleSpec[(2)];
|
|
FIXP_DBL *pSfbEnergy[(2)];
|
|
FIXP_DBL *pSfbSpreadEnergy[(2)];
|
|
FIXP_DBL *pSfbEnergyLdData[(2)];
|
|
FIXP_DBL *pSfbEnergyMS[(2)];
|
|
FIXP_DBL *pSfbThreshold[(2)];
|
|
|
|
INT isShortWindow[(2)];
|
|
|
|
|
|
if (hPsyConfLong->filterbank == FB_LC) {
|
|
blockSwitchingOffset = psyConf->granuleLength + (9*psyConf->granuleLength/(2*TRANS_FAC));
|
|
} else {
|
|
blockSwitchingOffset = psyConf->granuleLength;
|
|
}
|
|
|
|
for(ch = 0; ch < channels; ch++)
|
|
{
|
|
psyData[ch] = &psyDynamic->psyData[ch];
|
|
tnsData[ch] = &psyDynamic->tnsData[ch];
|
|
pnsData[ch] = &psyDynamic->pnsData[ch];
|
|
|
|
psyData[ch]->mdctSpectrum = psyOutChannel[ch]->mdctSpectrum;
|
|
}
|
|
|
|
/* block switching */
|
|
if (hPsyConfLong->filterbank != FB_ELD)
|
|
{
|
|
int err;
|
|
|
|
for(ch = 0; ch < channels; ch++)
|
|
{
|
|
C_ALLOC_SCRATCH_START(pTimeSignal, INT_PCM, (1024))
|
|
|
|
/* deinterleave input data and use for block switching */
|
|
FDKaacEnc_deinterleaveInputBuffer( pTimeSignal,
|
|
&pInput[chIdx[ch]],
|
|
psyConf->granuleLength,
|
|
totalChannels);
|
|
|
|
|
|
FDKaacEnc_BlockSwitching (&psyStatic[ch]->blockSwitchingControl,
|
|
psyConf->granuleLength,
|
|
psyStatic[ch]->isLFE,
|
|
pTimeSignal
|
|
);
|
|
|
|
|
|
/* fill up internal input buffer, to 2xframelength samples */
|
|
FDKmemcpy(psyStatic[ch]->psyInputBuffer+blockSwitchingOffset,
|
|
pTimeSignal,
|
|
(2*psyConf->granuleLength-blockSwitchingOffset)*sizeof(INT_PCM));
|
|
|
|
C_ALLOC_SCRATCH_END(pTimeSignal, INT_PCM, (1024))
|
|
}
|
|
|
|
/* synch left and right block type */
|
|
err = FDKaacEnc_SyncBlockSwitching(&psyStatic[0]->blockSwitchingControl,
|
|
&psyStatic[1]->blockSwitchingControl,
|
|
channels,
|
|
commonWindow);
|
|
|
|
if (err) {
|
|
return AAC_ENC_UNSUPPORTED_AOT; /* mixed up LC and LD */
|
|
}
|
|
|
|
}
|
|
else {
|
|
for(ch = 0; ch < channels; ch++)
|
|
{
|
|
/* deinterleave input data and use for block switching */
|
|
FDKaacEnc_deinterleaveInputBuffer( psyStatic[ch]->psyInputBuffer + blockSwitchingOffset,
|
|
&pInput[chIdx[ch]],
|
|
psyConf->granuleLength,
|
|
totalChannels);
|
|
}
|
|
}
|
|
|
|
for(ch = 0; ch < channels; ch++)
|
|
isShortWindow[ch]=(psyStatic[ch]->blockSwitchingControl.lastWindowSequence == SHORT_WINDOW);
|
|
|
|
/* set parameters according to window length */
|
|
for(ch = 0; ch < channels; ch++)
|
|
{
|
|
if(isShortWindow[ch]) {
|
|
hThisPsyConf[ch] = hPsyConfShort;
|
|
windowLength[ch] = psyConf->granuleLength/TRANS_FAC;
|
|
nWindows[ch] = TRANS_FAC;
|
|
maxSfb[ch] = MAX_SFB_SHORT;
|
|
|
|
pSfbMaxScaleSpec[ch] = psyData[ch]->sfbMaxScaleSpec.Short[0];
|
|
pSfbEnergy[ch] = psyData[ch]->sfbEnergy.Short[0];
|
|
pSfbSpreadEnergy[ch] = psyData[ch]->sfbSpreadEnergy.Short[0];
|
|
pSfbEnergyLdData[ch] = psyData[ch]->sfbEnergyLdData.Short[0];
|
|
pSfbEnergyMS[ch] = psyData[ch]->sfbEnergyMS.Short[0];
|
|
pSfbThreshold[ch] = psyData[ch]->sfbThreshold.Short[0];
|
|
|
|
} else
|
|
{
|
|
hThisPsyConf[ch] = hPsyConfLong;
|
|
windowLength[ch] = psyConf->granuleLength;
|
|
nWindows[ch] = 1;
|
|
maxSfb[ch] = MAX_GROUPED_SFB;
|
|
|
|
pSfbMaxScaleSpec[ch] = psyData[ch]->sfbMaxScaleSpec.Long;
|
|
pSfbEnergy[ch] = psyData[ch]->sfbEnergy.Long;
|
|
pSfbSpreadEnergy[ch] = psyData[ch]->sfbSpreadEnergy.Long;
|
|
pSfbEnergyLdData[ch] = psyData[ch]->sfbEnergyLdData.Long;
|
|
pSfbEnergyMS[ch] = psyData[ch]->sfbEnergyMS.Long;
|
|
pSfbThreshold[ch] = psyData[ch]->sfbThreshold.Long;
|
|
}
|
|
}
|
|
|
|
/* Transform and get mdctScaling for all channels and windows. */
|
|
for(ch = 0; ch < channels; ch++)
|
|
{
|
|
/* update number of active bands */
|
|
if (psyStatic[ch]->isLFE) {
|
|
psyData[ch]->sfbActive = hThisPsyConf[ch]->sfbActiveLFE;
|
|
psyData[ch]->lowpassLine = hThisPsyConf[ch]->lowpassLineLFE;
|
|
} else
|
|
{
|
|
psyData[ch]->sfbActive = hThisPsyConf[ch]->sfbActive;
|
|
psyData[ch]->lowpassLine = hThisPsyConf[ch]->lowpassLine;
|
|
}
|
|
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
|
|
wOffset = w*windowLength[ch];
|
|
|
|
FDKaacEnc_Transform_Real( psyStatic[ch]->psyInputBuffer + wOffset,
|
|
psyData[ch]->mdctSpectrum+wOffset,
|
|
psyStatic[ch]->blockSwitchingControl.lastWindowSequence,
|
|
psyStatic[ch]->blockSwitchingControl.windowShape,
|
|
&psyStatic[ch]->blockSwitchingControl.lastWindowShape,
|
|
psyConf->granuleLength,
|
|
&mdctSpectrum_e,
|
|
hThisPsyConf[ch]->filterbank
|
|
,psyStatic[ch]->overlapAddBuffer
|
|
);
|
|
|
|
/* Low pass / highest sfb */
|
|
FDKmemclear(&psyData[ch]->mdctSpectrum[psyData[ch]->lowpassLine+wOffset],
|
|
(windowLength[ch]-psyData[ch]->lowpassLine)*sizeof(FIXP_DBL));
|
|
|
|
if ( (hPsyConfLong->filterbank != FB_LC) && (psyData[ch]->lowpassLine >= FADE_OUT_LEN) ) {
|
|
/* Do blending to reduce gibbs artifacts */
|
|
for (int i=0; i<FADE_OUT_LEN; i++) {
|
|
psyData[ch]->mdctSpectrum[psyData[ch]->lowpassLine+wOffset - FADE_OUT_LEN + i] = fMult(psyData[ch]->mdctSpectrum[psyData[ch]->lowpassLine+wOffset - FADE_OUT_LEN + i], fadeOutFactor[i]);
|
|
}
|
|
}
|
|
|
|
|
|
/* Check for zero spectrum. These loops will usually terminate very, very early. */
|
|
for(line=0; (line<psyData[ch]->lowpassLine) && (zeroSpec==TRUE); line++) {
|
|
if (psyData[ch]->mdctSpectrum[line+wOffset] != (FIXP_DBL)0) {
|
|
zeroSpec = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} /* w loop */
|
|
|
|
psyData[ch]->mdctScale = mdctSpectrum_e;
|
|
|
|
/* rotate internal time samples */
|
|
FDKmemmove(psyStatic[ch]->psyInputBuffer,
|
|
psyStatic[ch]->psyInputBuffer+psyConf->granuleLength,
|
|
psyConf->granuleLength*sizeof(INT_PCM));
|
|
|
|
|
|
/* ... and get remaining samples from input buffer */
|
|
FDKaacEnc_deinterleaveInputBuffer( psyStatic[ch]->psyInputBuffer+psyConf->granuleLength,
|
|
&pInput[ (2*psyConf->granuleLength-blockSwitchingOffset)*totalChannels + chIdx[ch] ],
|
|
blockSwitchingOffset-psyConf->granuleLength,
|
|
totalChannels);
|
|
|
|
} /* ch */
|
|
|
|
/* Do some rescaling to get maximum possible accuracy for energies */
|
|
if ( zeroSpec == FALSE) {
|
|
|
|
/* Calc possible spectrum leftshift for each sfb (1 means: 1 bit left shift is possible without overflow) */
|
|
INT minSpecShift = MAX_SHIFT_DBL;
|
|
INT nrgShift = MAX_SHIFT_DBL;
|
|
INT finalShift = MAX_SHIFT_DBL;
|
|
FIXP_DBL currNrg = 0;
|
|
FIXP_DBL maxNrg = 0;
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
wOffset = w*windowLength[ch];
|
|
FDKaacEnc_CalcSfbMaxScaleSpec(psyData[ch]->mdctSpectrum+wOffset,
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
pSfbMaxScaleSpec[ch]+w*maxSfb[ch],
|
|
psyData[ch]->sfbActive);
|
|
|
|
for (sfb = 0; sfb<psyData[ch]->sfbActive; sfb++)
|
|
minSpecShift = fixMin(minSpecShift, (pSfbMaxScaleSpec[ch]+w*maxSfb[ch])[sfb]);
|
|
}
|
|
|
|
}
|
|
|
|
/* Calc possible energy leftshift for each sfb (1 means: 1 bit left shift is possible without overflow) */
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
wOffset = w*windowLength[ch];
|
|
currNrg = FDKaacEnc_CheckBandEnergyOptim(psyData[ch]->mdctSpectrum+wOffset,
|
|
pSfbMaxScaleSpec[ch]+w*maxSfb[ch],
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
psyData[ch]->sfbActive,
|
|
pSfbEnergy[ch]+w*maxSfb[ch],
|
|
pSfbEnergyLdData[ch]+w*maxSfb[ch],
|
|
minSpecShift-4);
|
|
|
|
maxNrg = fixMax(maxNrg, currNrg);
|
|
}
|
|
}
|
|
|
|
if ( maxNrg != (FIXP_DBL)0 ) {
|
|
nrgShift = (CountLeadingBits(maxNrg)>>1) + (minSpecShift-4);
|
|
}
|
|
|
|
/* 2check: Hasn't this decision to be made for both channels? */
|
|
/* For short windows 1 additional bit headroom is necessary to prevent overflows when summing up energies in FDKaacEnc_groupShortData() */
|
|
if(isShortWindow[0]) nrgShift--;
|
|
|
|
/* both spectrum and energies mustn't overflow */
|
|
finalShift = fixMin(minSpecShift, nrgShift);
|
|
|
|
/* do not shift more than 3 bits more to the left than signal without blockfloating point
|
|
* would be to avoid overflow of scaled PCM quantization thresholds */
|
|
if (finalShift > psyData[0]->mdctScale + 3 )
|
|
finalShift = psyData[0]->mdctScale + 3;
|
|
|
|
FDK_ASSERT(finalShift >= 0); /* right shift is not allowed */
|
|
|
|
/* correct sfbEnergy and sfbEnergyLdData with new finalShift */
|
|
FIXP_DBL ldShift = finalShift * FL2FXCONST_DBL(2.0/64);
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
for(sfb=0; sfb<psyData[ch]->sfbActive; sfb++) {
|
|
INT scale = fixMax(0, (pSfbMaxScaleSpec[ch]+w*maxSfb[ch])[sfb]-4);
|
|
scale = fixMin((scale-finalShift)<<1, DFRACT_BITS-1);
|
|
if (scale >= 0) (pSfbEnergy[ch]+w*maxSfb[ch])[sfb] >>= (scale);
|
|
else (pSfbEnergy[ch]+w*maxSfb[ch])[sfb] <<= (-scale);
|
|
(pSfbThreshold[ch]+w*maxSfb[ch])[sfb] = fMult((pSfbEnergy[ch]+w*maxSfb[ch])[sfb], C_RATIO);
|
|
(pSfbEnergyLdData[ch]+w*maxSfb[ch])[sfb] += ldShift;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( finalShift != 0 ) {
|
|
for (ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
wOffset = w*windowLength[ch];
|
|
for(line=0; line<psyData[ch]->lowpassLine; line++) {
|
|
psyData[ch]->mdctSpectrum[line+wOffset] <<= finalShift;
|
|
}
|
|
/* update sfbMaxScaleSpec */
|
|
for (sfb = 0; sfb<psyData[ch]->sfbActive; sfb++)
|
|
(pSfbMaxScaleSpec[ch]+w*maxSfb[ch])[sfb] -= finalShift;
|
|
}
|
|
/* update mdctScale */
|
|
psyData[ch]->mdctScale -= finalShift;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/* all spectral lines are zero */
|
|
for (ch = 0; ch < channels; ch++) {
|
|
psyData[ch]->mdctScale = 0; /* otherwise mdctScale would be for example 7 and PCM quantization thresholds would be shifted
|
|
* 14 bits to the right causing some of them to become 0 (which causes problems later) */
|
|
/* clear sfbMaxScaleSpec */
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
for (sfb = 0; sfb<psyData[ch]->sfbActive; sfb++) {
|
|
(pSfbMaxScaleSpec[ch]+w*maxSfb[ch])[sfb] = 0;
|
|
(pSfbEnergy[ch]+w*maxSfb[ch])[sfb] = (FIXP_DBL)0;
|
|
(pSfbEnergyLdData[ch]+w*maxSfb[ch])[sfb] = FL2FXCONST_DBL(-1.0f);
|
|
(pSfbThreshold[ch]+w*maxSfb[ch])[sfb] = (FIXP_DBL)0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Advance psychoacoustics: Tonality and TNS */
|
|
if (psyStatic[0]->isLFE) {
|
|
tnsData[0]->dataRaw.Long.subBlockInfo.tnsActive[HIFILT] = 0;
|
|
tnsData[0]->dataRaw.Long.subBlockInfo.tnsActive[LOFILT] = 0;
|
|
}
|
|
else
|
|
{
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
if (!isShortWindow[ch]) {
|
|
/* tonality */
|
|
FDKaacEnc_CalculateFullTonality( psyData[ch]->mdctSpectrum,
|
|
pSfbMaxScaleSpec[ch],
|
|
pSfbEnergyLdData[ch],
|
|
sfbTonality[ch],
|
|
psyData[ch]->sfbActive,
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
hThisPsyConf[ch]->pnsConf.usePns);
|
|
}
|
|
}
|
|
|
|
if (hPsyConfLong->tnsConf.tnsActive || hPsyConfShort->tnsConf.tnsActive) {
|
|
INT tnsActive[TRANS_FAC];
|
|
INT nrgScaling[2] = {0,0};
|
|
INT tnsSpecShift = 0;
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
|
|
wOffset = w*windowLength[ch];
|
|
/* TNS */
|
|
FDKaacEnc_TnsDetect(
|
|
tnsData[ch],
|
|
&hThisPsyConf[ch]->tnsConf,
|
|
&psyOutChannel[ch]->tnsInfo,
|
|
hThisPsyConf[ch]->sfbCnt,
|
|
psyData[ch]->mdctSpectrum+wOffset,
|
|
w,
|
|
psyStatic[ch]->blockSwitchingControl.lastWindowSequence
|
|
);
|
|
}
|
|
}
|
|
|
|
if (channels == 2) {
|
|
FDKaacEnc_TnsSync(
|
|
tnsData[1],
|
|
tnsData[0],
|
|
&psyOutChannel[1]->tnsInfo,
|
|
&psyOutChannel[0]->tnsInfo,
|
|
|
|
psyStatic[1]->blockSwitchingControl.lastWindowSequence,
|
|
psyStatic[0]->blockSwitchingControl.lastWindowSequence,
|
|
&hThisPsyConf[1]->tnsConf);
|
|
}
|
|
|
|
FDK_ASSERT(1==commonWindow); /* all checks for TNS do only work for common windows (which is always set)*/
|
|
for(w = 0; w < nWindows[0]; w++)
|
|
{
|
|
if (isShortWindow[0])
|
|
tnsActive[w] = tnsData[0]->dataRaw.Short.subBlockInfo[w].tnsActive[HIFILT] ||
|
|
tnsData[0]->dataRaw.Short.subBlockInfo[w].tnsActive[LOFILT] ||
|
|
tnsData[channels-1]->dataRaw.Short.subBlockInfo[w].tnsActive[HIFILT] ||
|
|
tnsData[channels-1]->dataRaw.Short.subBlockInfo[w].tnsActive[LOFILT];
|
|
else
|
|
tnsActive[w] = tnsData[0]->dataRaw.Long.subBlockInfo.tnsActive[HIFILT] ||
|
|
tnsData[0]->dataRaw.Long.subBlockInfo.tnsActive[LOFILT] ||
|
|
tnsData[channels-1]->dataRaw.Long.subBlockInfo.tnsActive[HIFILT] ||
|
|
tnsData[channels-1]->dataRaw.Long.subBlockInfo.tnsActive[LOFILT];
|
|
}
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
if (tnsActive[0] && !isShortWindow[ch]) {
|
|
/* Scale down spectrum if tns is active in one of the two channels with same lastWindowSequence */
|
|
/* first part of threshold calculation; it's not necessary to update sfbMaxScaleSpec */
|
|
INT shift = 1;
|
|
for(sfb=0; sfb<hThisPsyConf[ch]->lowpassLine; sfb++) {
|
|
psyData[ch]->mdctSpectrum[sfb] = psyData[ch]->mdctSpectrum[sfb] >> shift;
|
|
}
|
|
|
|
/* update thresholds */
|
|
for (sfb=0; sfb<psyData[ch]->sfbActive; sfb++) {
|
|
pSfbThreshold[ch][sfb] >>= (2*shift);
|
|
}
|
|
|
|
psyData[ch]->mdctScale += shift; /* update mdctScale */
|
|
|
|
/* calc sfbEnergies after tnsEncode again ! */
|
|
|
|
}
|
|
}
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++)
|
|
{
|
|
wOffset = w*windowLength[ch];
|
|
FDKaacEnc_TnsEncode(
|
|
&psyOutChannel[ch]->tnsInfo,
|
|
tnsData[ch],
|
|
hThisPsyConf[ch]->sfbCnt,
|
|
&hThisPsyConf[ch]->tnsConf,
|
|
hThisPsyConf[ch]->sfbOffset[psyData[ch]->sfbActive],/*hThisPsyConf[ch]->lowpassLine*/ /* filter stops before that line ! */
|
|
psyData[ch]->mdctSpectrum+wOffset,
|
|
w,
|
|
psyStatic[ch]->blockSwitchingControl.lastWindowSequence);
|
|
|
|
if(tnsActive[w]) {
|
|
/* Calc sfb-bandwise mdct-energies for left and right channel again, */
|
|
/* if tns active in current channel or in one channel with same lastWindowSequence left and right */
|
|
FDKaacEnc_CalcSfbMaxScaleSpec(psyData[ch]->mdctSpectrum+wOffset,
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
pSfbMaxScaleSpec[ch]+w*maxSfb[ch],
|
|
psyData[ch]->sfbActive);
|
|
}
|
|
}
|
|
}
|
|
|
|
for(ch = 0; ch < channels; ch++) {
|
|
for(w = 0; w < nWindows[ch]; w++) {
|
|
|
|
if (tnsActive[w]) {
|
|
|
|
if (isShortWindow[ch]) {
|
|
FDKaacEnc_CalcBandEnergyOptimShort(psyData[ch]->mdctSpectrum+w*windowLength[ch],
|
|
pSfbMaxScaleSpec[ch]+w*maxSfb[ch],
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
psyData[ch]->sfbActive,
|
|
pSfbEnergy[ch]+w*maxSfb[ch]);
|
|
}
|
|
else {
|
|
nrgScaling[ch] = /* with tns, energy calculation can overflow; -> scaling */
|
|
FDKaacEnc_CalcBandEnergyOptimLong(psyData[ch]->mdctSpectrum,
|
|
pSfbMaxScaleSpec[ch],
|
|
hThisPsyConf[ch]->sfbOffset,
|
|
psyData[ch]->sfbActive,
|
|
pSfbEnergy[ch],
|
|
pSfbEnergyLdData[ch]);
|
|
tnsSpecShift = fixMax(tnsSpecShift, nrgScaling[ch]); /* nrgScaling is set only if nrg would have an overflow */
|
|
}
|
|
} /* if tnsActive */
|
|
}
|
|
} /* end channel loop */
|
|
|
|
/* adapt scaling to prevent nrg overflow, only for long blocks */
|
|
for(ch = 0; ch < channels; ch++) {
|
|
if ( (tnsSpecShift!=0) && !isShortWindow[ch] ) {
|
|
/* scale down spectrum, nrg's and thresholds, if there was an overflow in sfbNrg calculation after tns */
|
|
for(line=0; line<hThisPsyConf[ch]->lowpassLine; line++) {
|
|
psyData[ch]->mdctSpectrum[line] >>= tnsSpecShift;
|
|
}
|
|
INT scale = (tnsSpecShift-nrgScaling[ch])<<1;
|
|
for(sfb=0; sfb<psyData[ch]->sfbActive; sfb++) {
|
|
pSfbEnergyLdData[ch][sfb] -= scale*FL2FXCONST_DBL(1.0/LD_DATA_SCALING);
|
|
pSfbEnergy[ch][sfb] >>= scale;
|
|
pSfbThreshold[ch][sfb] >>= (tnsSpecShift<<1);
|
|
}
|
|
psyData[ch]->mdctScale += tnsSpecShift; /* update mdctScale; not necessary to update sfbMaxScaleSpec */
|
|
|
|
}
|
|
} /* end channel loop */
|
|
|
|
} /* TNS active */
|
|
} /* !isLFE */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Advance thresholds */
|
|
for(ch = 0; ch < channels; ch++) {
|
|
INT headroom;
|
|
|
|
FIXP_DBL clipEnergy;
|
|
INT energyShift = psyData[ch]->mdctScale*2 ;
|
|
INT clipNrgShift = energyShift - THR_SHIFTBITS ;
|
|
|
|
if(isShortWindow[ch])
|
|
headroom = 6;
|
|
else
|
|
headroom = 0;
|
|
|
|
if (clipNrgShift >= 0)
|
|
clipEnergy = hThisPsyConf[ch]->clipEnergy >> clipNrgShift ;
|
|
else if (clipNrgShift>=-headroom)
|
|
clipEnergy = hThisPsyConf[ch]->clipEnergy << -clipNrgShift ;
|
|
else
|
|
clipEnergy = (FIXP_DBL)MAXVAL_DBL ;
|
|
|
|
for(w = 0; w < nWindows[ch]; w++)
|
|
{
|
|
INT i;
|
|
/* limit threshold to avoid clipping */
|
|
for (i=0; i<psyData[ch]->sfbActive; i++) {
|
|
*(pSfbThreshold[ch]+w*maxSfb[ch]+i) = fixMin(*(pSfbThreshold[ch]+w*maxSfb[ch]+i), clipEnergy);
|
|
}
|
|
|
|
/* spreading */
|
|
FDKaacEnc_SpreadingMax(psyData[ch]->sfbActive,
|
|
hThisPsyConf[ch]->sfbMaskLowFactor,
|
|
hThisPsyConf[ch]->sfbMaskHighFactor,
|
|
pSfbThreshold[ch]+w*maxSfb[ch]);
|
|
|
|
|
|
/* PCM quantization threshold */
|
|
energyShift += PCM_QUANT_THR_SCALE;
|
|
if (energyShift>=0) {
|
|
energyShift = fixMin(DFRACT_BITS-1,energyShift);
|
|
for (i=0; i<psyData[ch]->sfbActive;i++) {
|
|
*(pSfbThreshold[ch]+w*maxSfb[ch]+i) = fixMax(*(pSfbThreshold[ch]+w*maxSfb[ch]+i) >> THR_SHIFTBITS,
|
|
(hThisPsyConf[ch]->sfbPcmQuantThreshold[i] >> energyShift));
|
|
}
|
|
} else {
|
|
energyShift = fixMin(DFRACT_BITS-1,-energyShift);
|
|
for (i=0; i<psyData[ch]->sfbActive;i++) {
|
|
*(pSfbThreshold[ch]+w*maxSfb[ch]+i) = fixMax(*(pSfbThreshold[ch]+w*maxSfb[ch]+i) >> THR_SHIFTBITS,
|
|
(hThisPsyConf[ch]->sfbPcmQuantThreshold[i] << energyShift));
|
|
}
|
|
}
|
|
|
|
if (!psyStatic[ch]->isLFE)
|
|
{
|
|
/* preecho control */
|
|
if(psyStatic[ch]->blockSwitchingControl.lastWindowSequence == STOP_WINDOW) {
|
|
/* prevent FDKaacEnc_PreEchoControl from comparing stop
|
|
thresholds with short thresholds */
|
|
for (i=0; i<psyData[ch]->sfbActive;i++) {
|
|
psyStatic[ch]->sfbThresholdnm1[i] = (FIXP_DBL)MAXVAL_DBL;
|
|
}
|
|
|
|
psyStatic[ch]->mdctScalenm1 = 0;
|
|
psyStatic[ch]->calcPreEcho = 0;
|
|
}
|
|
|
|
FDKaacEnc_PreEchoControl( psyStatic[ch]->sfbThresholdnm1,
|
|
psyStatic[ch]->calcPreEcho,
|
|
psyData[ch]->sfbActive,
|
|
hThisPsyConf[ch]->maxAllowedIncreaseFactor,
|
|
hThisPsyConf[ch]->minRemainingThresholdFactor,
|
|
pSfbThreshold[ch]+w*maxSfb[ch],
|
|
psyData[ch]->mdctScale,
|
|
&psyStatic[ch]->mdctScalenm1);
|
|
|
|
psyStatic[ch]->calcPreEcho = 1;
|
|
|
|
if(psyStatic[ch]->blockSwitchingControl.lastWindowSequence == START_WINDOW)
|
|
{
|
|
/* prevent FDKaacEnc_PreEchoControl in next frame to compare start
|
|
thresholds with short thresholds */
|
|
for (i=0; i<psyData[ch]->sfbActive;i++) {
|
|
psyStatic[ch]->sfbThresholdnm1[i] = (FIXP_DBL)MAXVAL_DBL;
|
|
}
|
|
|
|
psyStatic[ch]->mdctScalenm1 = 0;
|
|
psyStatic[ch]->calcPreEcho = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/* spread energy to avoid hole detection */
|
|
FDKmemcpy(pSfbSpreadEnergy[ch]+w*maxSfb[ch], pSfbEnergy[ch]+w*maxSfb[ch], psyData[ch]->sfbActive*sizeof(FIXP_DBL));
|
|
|
|
FDKaacEnc_SpreadingMax(psyData[ch]->sfbActive,
|
|
hThisPsyConf[ch]->sfbMaskLowFactorSprEn,
|
|
hThisPsyConf[ch]->sfbMaskHighFactorSprEn,
|
|
pSfbSpreadEnergy[ch]+w*maxSfb[ch]);
|
|
}
|
|
}
|
|
|
|
/* Calc bandwise energies for mid and side channel. Do it only if 2 channels exist */
|
|
if (channels==2) {
|
|
for(w = 0; w < nWindows[1]; w++) {
|
|
wOffset = w*windowLength[1];
|
|
FDKaacEnc_CalcBandNrgMSOpt(psyData[0]->mdctSpectrum+wOffset,
|
|
psyData[1]->mdctSpectrum+wOffset,
|
|
pSfbMaxScaleSpec[0]+w*maxSfb[0],
|
|
pSfbMaxScaleSpec[1]+w*maxSfb[1],
|
|
hThisPsyConf[1]->sfbOffset,
|
|
psyData[0]->sfbActive,
|
|
pSfbEnergyMS[0]+w*maxSfb[0],
|
|
pSfbEnergyMS[1]+w*maxSfb[1],
|
|
(psyStatic[1]->blockSwitchingControl.lastWindowSequence != SHORT_WINDOW),
|
|
psyData[0]->sfbEnergyMSLdData,
|
|
psyData[1]->sfbEnergyMSLdData);
|
|
}
|
|
}
|
|
|
|
/* group short data (maxSfb[ch] for short blocks is determined here) */
|
|
for(ch=0;ch<channels;ch++)
|
|
{
|
|
INT noSfb, i;
|
|
if(isShortWindow[ch])
|
|
{
|
|
int sfbGrp;
|
|
noSfb = psyStatic[ch]->blockSwitchingControl.noOfGroups * hPsyConfShort->sfbCnt;
|
|
/* At this point, energies and thresholds are copied/regrouped from the ".Short" to the ".Long" arrays */
|
|
FDKaacEnc_groupShortData( psyData[ch]->mdctSpectrum,
|
|
&psyData[ch]->sfbThreshold,
|
|
&psyData[ch]->sfbEnergy,
|
|
&psyData[ch]->sfbEnergyMS,
|
|
&psyData[ch]->sfbSpreadEnergy,
|
|
hPsyConfShort->sfbCnt,
|
|
psyData[ch]->sfbActive,
|
|
hPsyConfShort->sfbOffset,
|
|
hPsyConfShort->sfbMinSnrLdData,
|
|
psyData[ch]->groupedSfbOffset,
|
|
&maxSfbPerGroup[ch],
|
|
psyOutChannel[ch]->sfbMinSnrLdData,
|
|
psyStatic[ch]->blockSwitchingControl.noOfGroups,
|
|
psyStatic[ch]->blockSwitchingControl.groupLen,
|
|
psyConf[1].granuleLength);
|
|
|
|
|
|
/* calculate ldData arrays (short values are in .Long-arrays after FDKaacEnc_groupShortData) */
|
|
for (sfbGrp = 0; sfbGrp < noSfb; sfbGrp += hPsyConfShort->sfbCnt) {
|
|
LdDataVector(&psyData[ch]->sfbEnergy.Long[sfbGrp], &psyOutChannel[ch]->sfbEnergyLdData[sfbGrp], psyData[ch]->sfbActive);
|
|
}
|
|
|
|
/* calc sfbThrld and set Values smaller 2^-31 to 2^-33*/
|
|
for (sfbGrp = 0; sfbGrp < noSfb; sfbGrp += hPsyConfShort->sfbCnt) {
|
|
LdDataVector(&psyData[ch]->sfbThreshold.Long[sfbGrp], &psyOutChannel[ch]->sfbThresholdLdData[sfbGrp], psyData[ch]->sfbActive);
|
|
for (sfb=0;sfb<psyData[ch]->sfbActive;sfb++) {
|
|
psyOutChannel[ch]->sfbThresholdLdData[sfbGrp+sfb] =
|
|
fixMax(psyOutChannel[ch]->sfbThresholdLdData[sfbGrp+sfb], FL2FXCONST_DBL(-0.515625f));
|
|
}
|
|
}
|
|
|
|
if ( channels==2 ) {
|
|
for (sfbGrp = 0; sfbGrp < noSfb; sfbGrp += hPsyConfShort->sfbCnt) {
|
|
LdDataVector(&psyData[ch]->sfbEnergyMS.Long[sfbGrp], &psyData[ch]->sfbEnergyMSLdData[sfbGrp], psyData[ch]->sfbActive);
|
|
}
|
|
}
|
|
|
|
FDKmemcpy(psyOutChannel[ch]->sfbOffsets, psyData[ch]->groupedSfbOffset, (MAX_GROUPED_SFB+1)*sizeof(INT));
|
|
|
|
} else {
|
|
/* maxSfb[ch] for long blocks */
|
|
for (sfb = psyData[ch]->sfbActive-1; sfb >= 0; sfb--) {
|
|
for (line = hPsyConfLong->sfbOffset[sfb+1]-1; line >= hPsyConfLong->sfbOffset[sfb]; line--) {
|
|
if (psyData[ch]->mdctSpectrum[line] != FL2FXCONST_SGL(0.0f)) break;
|
|
}
|
|
if (line > hPsyConfLong->sfbOffset[sfb]) break;
|
|
}
|
|
maxSfbPerGroup[ch] = sfb + 1;
|
|
/* ensure at least one section in ICS; workaround for existing decoder crc implementation */
|
|
maxSfbPerGroup[ch] = fixMax(fixMin(5,psyData[ch]->sfbActive),maxSfbPerGroup[ch]);
|
|
|
|
/* sfbNrgLdData is calculated in FDKaacEnc_advancePsychLong, copy in psyOut structure */
|
|
FDKmemcpy(psyOutChannel[ch]->sfbEnergyLdData, psyData[ch]->sfbEnergyLdData.Long, psyData[ch]->sfbActive*sizeof(FIXP_DBL));
|
|
|
|
FDKmemcpy(psyOutChannel[ch]->sfbOffsets, hPsyConfLong->sfbOffset, (MAX_GROUPED_SFB+1)*sizeof(INT));
|
|
|
|
/* sfbMinSnrLdData modified in adjust threshold, copy necessary */
|
|
FDKmemcpy(psyOutChannel[ch]->sfbMinSnrLdData, hPsyConfLong->sfbMinSnrLdData, psyData[ch]->sfbActive*sizeof(FIXP_DBL));
|
|
|
|
/* sfbEnergyMSLdData ist already calculated in FDKaacEnc_CalcBandNrgMSOpt; only in long case */
|
|
|
|
/* calc sfbThrld and set Values smaller 2^-31 to 2^-33*/
|
|
LdDataVector(psyData[ch]->sfbThreshold.Long, psyOutChannel[ch]->sfbThresholdLdData, psyData[ch]->sfbActive);
|
|
for (i=0;i<psyData[ch]->sfbActive;i++) {
|
|
psyOutChannel[ch]->sfbThresholdLdData[i] =
|
|
fixMax(psyOutChannel[ch]->sfbThresholdLdData[i], FL2FXCONST_DBL(-0.515625f));
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
Intensity parameter intialization.
|
|
*/
|
|
for(ch=0;ch<channels;ch++) {
|
|
FDKmemclear(psyOutChannel[ch]->isBook, MAX_GROUPED_SFB*sizeof(INT));
|
|
FDKmemclear(psyOutChannel[ch]->isScale, MAX_GROUPED_SFB*sizeof(INT));
|
|
}
|
|
|
|
for(ch=0;ch<channels;ch++) {
|
|
INT win = (isShortWindow[ch]?1:0);
|
|
if (!psyStatic[ch]->isLFE)
|
|
{
|
|
/* PNS Decision */
|
|
FDKaacEnc_PnsDetect( &(psyConf[0].pnsConf),
|
|
pnsData[ch],
|
|
psyStatic[ch]->blockSwitchingControl.lastWindowSequence,
|
|
psyData[ch]->sfbActive,
|
|
maxSfbPerGroup[ch], /* count of Sfb which are not zero. */
|
|
psyOutChannel[ch]->sfbThresholdLdData,
|
|
psyConf[win].sfbOffset,
|
|
psyData[ch]->mdctSpectrum,
|
|
psyData[ch]->sfbMaxScaleSpec.Long,
|
|
sfbTonality[ch],
|
|
psyOutChannel[ch]->tnsInfo.order[0][0],
|
|
tnsData[ch]->dataRaw.Long.subBlockInfo.predictionGain[HIFILT],
|
|
tnsData[ch]->dataRaw.Long.subBlockInfo.tnsActive[HIFILT],
|
|
psyOutChannel[ch]->sfbEnergyLdData,
|
|
psyOutChannel[ch]->noiseNrg );
|
|
} /* !isLFE */
|
|
}
|
|
|
|
/*
|
|
stereo Processing
|
|
*/
|
|
if(channels == 2)
|
|
{
|
|
psyOutElement->toolsInfo.msDigest = MS_NONE;
|
|
psyOutElement->commonWindow = commonWindow;
|
|
if (psyOutElement->commonWindow)
|
|
maxSfbPerGroup[0] = maxSfbPerGroup[1] =
|
|
fixMax(maxSfbPerGroup[0], maxSfbPerGroup[1]);
|
|
|
|
if(psyStatic[0]->blockSwitchingControl.lastWindowSequence != SHORT_WINDOW)
|
|
{
|
|
/* PNS preprocessing depending on ms processing: PNS not in Short Window! */
|
|
FDKaacEnc_PreProcessPnsChannelPair(
|
|
psyData[0]->sfbActive,
|
|
(&psyData[0]->sfbEnergy)->Long,
|
|
(&psyData[1]->sfbEnergy)->Long,
|
|
psyOutChannel[0]->sfbEnergyLdData,
|
|
psyOutChannel[1]->sfbEnergyLdData,
|
|
psyData[0]->sfbEnergyMS.Long,
|
|
&(psyConf[0].pnsConf),
|
|
pnsData[0],
|
|
pnsData[1]);
|
|
|
|
FDKaacEnc_IntensityStereoProcessing(
|
|
psyData[0]->sfbEnergy.Long,
|
|
psyData[1]->sfbEnergy.Long,
|
|
psyData[0]->mdctSpectrum,
|
|
psyData[1]->mdctSpectrum,
|
|
psyData[0]->sfbThreshold.Long,
|
|
psyData[1]->sfbThreshold.Long,
|
|
psyOutChannel[1]->sfbThresholdLdData,
|
|
psyData[0]->sfbSpreadEnergy.Long,
|
|
psyData[1]->sfbSpreadEnergy.Long,
|
|
psyOutChannel[0]->sfbEnergyLdData,
|
|
psyOutChannel[1]->sfbEnergyLdData,
|
|
&psyOutElement->toolsInfo.msDigest,
|
|
psyOutElement->toolsInfo.msMask,
|
|
psyConf[0].sfbCnt,
|
|
psyConf[0].sfbCnt,
|
|
maxSfbPerGroup[0],
|
|
psyConf[0].sfbOffset,
|
|
psyConf[0].allowIS && commonWindow,
|
|
psyOutChannel[1]->isBook,
|
|
psyOutChannel[1]->isScale,
|
|
pnsData);
|
|
|
|
FDKaacEnc_MsStereoProcessing(
|
|
psyData,
|
|
psyOutChannel,
|
|
psyOutChannel[1]->isBook,
|
|
&psyOutElement->toolsInfo.msDigest,
|
|
psyOutElement->toolsInfo.msMask,
|
|
psyData[0]->sfbActive,
|
|
psyData[0]->sfbActive,
|
|
maxSfbPerGroup[0],
|
|
psyOutChannel[0]->sfbOffsets);
|
|
|
|
/* PNS postprocessing */
|
|
FDKaacEnc_PostProcessPnsChannelPair(psyData[0]->sfbActive,
|
|
&(psyConf[0].pnsConf),
|
|
pnsData[0],
|
|
pnsData[1],
|
|
psyOutElement->toolsInfo.msMask,
|
|
&psyOutElement->toolsInfo.msDigest);
|
|
|
|
} else {
|
|
FDKaacEnc_IntensityStereoProcessing(
|
|
psyData[0]->sfbEnergy.Long,
|
|
psyData[1]->sfbEnergy.Long,
|
|
psyData[0]->mdctSpectrum,
|
|
psyData[1]->mdctSpectrum,
|
|
psyData[0]->sfbThreshold.Long,
|
|
psyData[1]->sfbThreshold.Long,
|
|
psyOutChannel[1]->sfbThresholdLdData,
|
|
psyData[0]->sfbSpreadEnergy.Long,
|
|
psyData[1]->sfbSpreadEnergy.Long,
|
|
psyOutChannel[0]->sfbEnergyLdData,
|
|
psyOutChannel[1]->sfbEnergyLdData,
|
|
&psyOutElement->toolsInfo.msDigest,
|
|
psyOutElement->toolsInfo.msMask,
|
|
psyStatic[0]->blockSwitchingControl.noOfGroups*hPsyConfShort->sfbCnt,
|
|
psyConf[1].sfbCnt,
|
|
maxSfbPerGroup[0],
|
|
psyData[0]->groupedSfbOffset,
|
|
psyConf[0].allowIS && commonWindow,
|
|
psyOutChannel[1]->isBook,
|
|
psyOutChannel[1]->isScale,
|
|
pnsData);
|
|
|
|
/* it's OK to pass the ".Long" arrays here. They contain grouped short data since FDKaacEnc_groupShortData() */
|
|
FDKaacEnc_MsStereoProcessing( psyData,
|
|
psyOutChannel,
|
|
psyOutChannel[1]->isBook,
|
|
&psyOutElement->toolsInfo.msDigest,
|
|
psyOutElement->toolsInfo.msMask,
|
|
psyStatic[0]->blockSwitchingControl.noOfGroups*hPsyConfShort->sfbCnt,
|
|
hPsyConfShort->sfbCnt,
|
|
maxSfbPerGroup[0],
|
|
psyOutChannel[0]->sfbOffsets);
|
|
}
|
|
}
|
|
|
|
/*
|
|
PNS Coding
|
|
*/
|
|
for(ch=0;ch<channels;ch++) {
|
|
if (psyStatic[ch]->isLFE) {
|
|
/* no PNS coding */
|
|
for(sfb = 0; sfb < psyData[ch]->sfbActive; sfb++) {
|
|
psyOutChannel[ch]->noiseNrg[sfb] = NO_NOISE_PNS;
|
|
}
|
|
} else
|
|
{
|
|
FDKaacEnc_CodePnsChannel(psyData[ch]->sfbActive,
|
|
&(psyConf[ch].pnsConf),
|
|
pnsData[ch]->pnsFlag,
|
|
psyData[ch]->sfbEnergyLdData.Long,
|
|
psyOutChannel[ch]->noiseNrg, /* this is the energy that will be written to the bitstream */
|
|
psyOutChannel[ch]->sfbThresholdLdData);
|
|
}
|
|
}
|
|
|
|
/*
|
|
build output
|
|
*/
|
|
for(ch=0;ch<channels;ch++)
|
|
{
|
|
INT j, grp, mask;
|
|
|
|
psyOutChannel[ch]->maxSfbPerGroup = maxSfbPerGroup[ch];
|
|
psyOutChannel[ch]->mdctScale = psyData[ch]->mdctScale;
|
|
|
|
if(isShortWindow[ch]==0) {
|
|
|
|
psyOutChannel[ch]->sfbCnt = hPsyConfLong->sfbActive;
|
|
psyOutChannel[ch]->sfbPerGroup = hPsyConfLong->sfbActive;
|
|
psyOutChannel[ch]->lastWindowSequence = psyStatic[ch]->blockSwitchingControl.lastWindowSequence;
|
|
psyOutChannel[ch]->windowShape = psyStatic[ch]->blockSwitchingControl.windowShape;
|
|
}
|
|
else {
|
|
INT sfbCnt = psyStatic[ch]->blockSwitchingControl.noOfGroups*hPsyConfShort->sfbCnt;
|
|
|
|
psyOutChannel[ch]->sfbCnt = sfbCnt;
|
|
psyOutChannel[ch]->sfbPerGroup = hPsyConfShort->sfbCnt;
|
|
psyOutChannel[ch]->lastWindowSequence = SHORT_WINDOW;
|
|
psyOutChannel[ch]->windowShape = SINE_WINDOW;
|
|
}
|
|
|
|
/* generate grouping mask */
|
|
mask = 0;
|
|
for (grp = 0; grp < psyStatic[ch]->blockSwitchingControl.noOfGroups; grp++)
|
|
{
|
|
mask <<= 1;
|
|
for (j=1; j<psyStatic[ch]->blockSwitchingControl.groupLen[grp]; j++) {
|
|
mask = (mask<<1) | 1 ;
|
|
}
|
|
}
|
|
psyOutChannel[ch]->groupingMask = mask;
|
|
|
|
/* build interface */
|
|
FDKmemcpy(psyOutChannel[ch]->groupLen,psyStatic[ch]->blockSwitchingControl.groupLen,MAX_NO_OF_GROUPS*sizeof(INT));
|
|
FDKmemcpy(psyOutChannel[ch]->sfbEnergy,(&psyData[ch]->sfbEnergy)->Long, MAX_GROUPED_SFB*sizeof(FIXP_DBL));
|
|
FDKmemcpy(psyOutChannel[ch]->sfbSpreadEnergy,(&psyData[ch]->sfbSpreadEnergy)->Long, MAX_GROUPED_SFB*sizeof(FIXP_DBL));
|
|
// FDKmemcpy(psyOutChannel[ch]->mdctSpectrum, psyData[ch]->mdctSpectrum, (1024)*sizeof(FIXP_DBL));
|
|
}
|
|
|
|
return AAC_ENC_OK;
|
|
}
|
|
|
|
|
|
void FDKaacEnc_PsyClose(PSY_INTERNAL **phPsyInternal,
|
|
PSY_OUT **phPsyOut)
|
|
{
|
|
int n, i;
|
|
|
|
|
|
if(phPsyInternal!=NULL) {
|
|
PSY_INTERNAL *hPsyInternal = *phPsyInternal;
|
|
|
|
if (hPsyInternal)
|
|
{
|
|
for (i=0; i<(8); i++) {
|
|
if (hPsyInternal->pStaticChannels[i]) {
|
|
if (hPsyInternal->pStaticChannels[i]->psyInputBuffer)
|
|
FreeRam_aacEnc_PsyInputBuffer(&hPsyInternal->pStaticChannels[i]->psyInputBuffer); /* AUDIO INPUT BUFFER */
|
|
|
|
FreeRam_aacEnc_PsyStatic(&hPsyInternal->pStaticChannels[i]); /* PSY_STATIC */
|
|
}
|
|
}
|
|
|
|
for (i=0; i<(8); i++) {
|
|
if (hPsyInternal->psyElement[i])
|
|
FreeRam_aacEnc_PsyElement(&hPsyInternal->psyElement[i]); /* PSY_ELEMENT */
|
|
}
|
|
|
|
|
|
FreeRam_aacEnc_PsyInternal(phPsyInternal);
|
|
}
|
|
}
|
|
|
|
if (phPsyOut!=NULL) {
|
|
for (n=0; n<(1); n++) {
|
|
if (phPsyOut[n])
|
|
{
|
|
for (i=0; i<(8); i++) {
|
|
if (phPsyOut[n]->pPsyOutChannels[i])
|
|
FreeRam_aacEnc_PsyOutChannel(&phPsyOut[n]->pPsyOutChannels[i]); /* PSY_OUT_CHANNEL */
|
|
}
|
|
|
|
for (i=0; i<(8); i++) {
|
|
if (phPsyOut[n]->psyOutElement[i])
|
|
FreeRam_aacEnc_PsyOutElements(&phPsyOut[n]->psyOutElement[i]); /* PSY_OUT_ELEMENTS */
|
|
}
|
|
|
|
FreeRam_aacEnc_PsyOut(&phPsyOut[n]);
|
|
}
|
|
}
|
|
}
|
|
}
|