
/* -----------------------------------------------------------------------------------------------------------
Software License for The Fraunhofer FDK AAC Codec Library for Android

© Copyright  1995 - 2013 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
----------------------------------------------------------------------------------------------------------- */

/**********************  Fraunhofer IIS FDK AAC Encoder lib  ******************

   Author(s): V. Bacigalupo
   Description: Metadata Encoder library interface functions

******************************************************************************/


#include "metadata_main.h"
#include "metadata_compressor.h"
#include "FDK_bitstream.h"
#include "FDK_audio.h"
#include "genericStds.h"

/*----------------- defines ----------------------*/
#define MAX_DRC_BANDS        (1<<4)
#define MAX_DRC_CHANNELS        (8)
#define MAX_DRC_FRAMELEN   (2*1024)

/*--------------- structure definitions --------------------*/

typedef struct AAC_METADATA
{
  /* MPEG: Dynamic Range Control */
  struct {
    UCHAR                         prog_ref_level_present;
    SCHAR                         prog_ref_level;

    UCHAR                         dyn_rng_sgn[MAX_DRC_BANDS];
    UCHAR                         dyn_rng_ctl[MAX_DRC_BANDS];

    UCHAR                         drc_bands_present;
    UCHAR                         drc_band_incr;
    UCHAR                         drc_band_top[MAX_DRC_BANDS];
    UCHAR                         drc_interpolation_scheme;
    AACENC_METADATA_DRC_PROFILE   drc_profile;
    INT                           drc_TargetRefLevel;    /* used for Limiter */

    /* excluded channels */
    UCHAR                         excluded_chns_present;
    UCHAR                         exclude_mask[2];       /* MAX_NUMBER_CHANNELS/8 */
  } mpegDrc;

  /* ETSI: addtl ancillary data */
  struct {
    /* Heavy Compression */
    UCHAR                         compression_on;        /* flag, if compression value should be written */
    UCHAR                         compression_value;     /* compression value */
    AACENC_METADATA_DRC_PROFILE   comp_profile;
    INT                           comp_TargetRefLevel;   /* used for Limiter */
    INT                           timecode_coarse_status;
    INT                           timecode_fine_status;
  } etsiAncData;

  SCHAR                         centerMixLevel;          /* center downmix level (0...7, according to table) */
  SCHAR                         surroundMixLevel;        /* surround downmix level (0...7, according to table) */
  UCHAR                         WritePCEMixDwnIdx;       /* flag */
  UCHAR                         DmxLvl_On;               /* flag */

  UCHAR                         dolbySurroundMode;

  UCHAR                         metadataMode;            /* indicate meta data mode in current frame (delay line) */

} AAC_METADATA;

struct FDK_METADATA_ENCODER
{
  INT                metadataMode;
  HDRC_COMP          hDrcComp;
  AACENC_MetaData    submittedMetaData;

  INT                nAudioDataDelay;
  INT                nMetaDataDelay;
  INT                nChannels;

  INT_PCM            audioDelayBuffer[MAX_DRC_CHANNELS*MAX_DRC_FRAMELEN];
  int                audioDelayIdx;

  AAC_METADATA       metaDataBuffer[3];
  int                metaDataDelayIdx;

  UCHAR              drcInfoPayload[12];
  UCHAR              drcDsePayload[8];

  INT                matrix_mixdown_idx;
  AACENC_EXT_PAYLOAD exPayload[2];
  INT                nExtensions;

  INT                finalizeMetaData;                   /* Delay switch off by one frame and write default configuration to
                                                            finalize the metadata setup. */
};


/*---------------- constants -----------------------*/
static const AACENC_MetaData defaultMetaDataSetup = {
    AACENC_METADATA_DRC_NONE,
    AACENC_METADATA_DRC_NONE,
    -(31<<16),
    -(31<<16),
    0,
    -(31<<16),
    0,
    0,
    0,
    0,
    0
};

static const FIXP_DBL dmxTable[8] = {
    ((FIXP_DBL)MAXVAL_DBL), FL2FXCONST_DBL(0.841f), FL2FXCONST_DBL(0.707f), FL2FXCONST_DBL(0.596f),
    FL2FXCONST_DBL(0.500f), FL2FXCONST_DBL(0.422f), FL2FXCONST_DBL(0.355f), FL2FXCONST_DBL(0.000f)
};

static const UCHAR surmix2matrix_mixdown_idx[8] = {
    0, 0, 0, 1, 1, 2, 2, 3
};


/*--------------- function declarations --------------------*/
static FDK_METADATA_ERROR WriteMetadataPayload(
        const HANDLE_FDK_METADATA_ENCODER hMetaData,
        const AAC_METADATA * const  pMetadata
        );

static INT WriteDynamicRangeInfoPayload(
        const AAC_METADATA* const pMetadata,
        UCHAR* const              pExtensionPayload
        );

static INT WriteEtsiAncillaryDataPayload(
        const AAC_METADATA* const pMetadata,
        UCHAR* const              pExtensionPayload
        );

static FDK_METADATA_ERROR CompensateAudioDelay(
        HANDLE_FDK_METADATA_ENCODER hMetaDataEnc,
        INT_PCM * const             pAudioSamples,
        const INT                   nAudioSamples
        );

static FDK_METADATA_ERROR LoadSubmittedMetadata(
        const AACENC_MetaData *   const hMetadata,
        const INT                       nChannels,
        const INT                       metadataMode,
        AAC_METADATA * const            pAacMetaData
        );

static FDK_METADATA_ERROR ProcessCompressor(
        AAC_METADATA                    *pMetadata,
        HDRC_COMP                        hDrcComp,
        const INT_PCM * const            pSamples,
        const INT                        nSamples
        );

/*------------- function definitions ----------------*/

static DRC_PROFILE convertProfile(AACENC_METADATA_DRC_PROFILE aacProfile)
{
    DRC_PROFILE drcProfile = DRC_NONE;

    switch(aacProfile) {
      case AACENC_METADATA_DRC_NONE:          drcProfile = DRC_NONE;          break;
      case AACENC_METADATA_DRC_FILMSTANDARD:  drcProfile = DRC_FILMSTANDARD;  break;
      case AACENC_METADATA_DRC_FILMLIGHT:     drcProfile = DRC_FILMLIGHT;     break;
      case AACENC_METADATA_DRC_MUSICSTANDARD: drcProfile = DRC_MUSICSTANDARD; break;
      case AACENC_METADATA_DRC_MUSICLIGHT:    drcProfile = DRC_MUSICLIGHT;    break;
      case AACENC_METADATA_DRC_SPEECH:        drcProfile = DRC_SPEECH;        break;
      default:                                drcProfile = DRC_NONE;          break;
    }
    return drcProfile;
}


/* convert dialog normalization to program reference level */
/* NOTE: this only is correct, if the decoder target level is set to -31dB for line mode / -20dB for RF mode */
static UCHAR dialnorm2progreflvl(const INT d)
{
    return ((UCHAR)FDKmax(0, FDKmin((-d + (1<<13)) >> 14, 127)));
}

/* convert program reference level to dialog normalization */
static INT progreflvl2dialnorm(const UCHAR p)
{
    return -((INT)(p<<(16-2)));
}

/* encode downmix levels to Downmixing_levels_MPEG4 */
static SCHAR encodeDmxLvls(const SCHAR cmixlev, const SCHAR surmixlev)
{
    SCHAR dmxLvls = 0;
    dmxLvls |= 0x80 | (cmixlev << 4); /* center_mix_level_on */
    dmxLvls |= 0x08 | surmixlev;      /* surround_mix_level_on */

    return dmxLvls;
}

/* encode AAC DRC gain (ISO/IEC 14496-3:2005  4.5.2.7) */
static void encodeDynrng(INT gain, UCHAR* const dyn_rng_ctl, UCHAR* const dyn_rng_sgn )
{
    if(gain < 0)
    {
      *dyn_rng_sgn = 1;
      gain = -gain;
    }
    else
    {
      *dyn_rng_sgn = 0;
    }
    gain = FDKmin(gain,(127<<14));

    *dyn_rng_ctl = (UCHAR)((gain + (1<<13)) >> 14);
}

/* decode AAC DRC gain (ISO/IEC 14496-3:2005  4.5.2.7) */
static INT decodeDynrng(const UCHAR dyn_rng_ctl, const UCHAR dyn_rng_sgn)
{
    INT tmp = ((INT)dyn_rng_ctl << (16-2));
    if (dyn_rng_sgn) tmp = -tmp;

    return tmp;
}

/* encode AAC compression value (ETSI TS 101 154 page 99) */
static UCHAR encodeCompr(INT gain)
{
    UCHAR x, y;
    INT tmp;

    /* tmp = (int)((48.164f - gain) / 6.0206f * 15 + 0.5f); */
    tmp = ((3156476 - gain) * 15 + 197283) / 394566;

    if (tmp >= 240) {
        return 0xFF;
    }
    else if (tmp < 0) {
        return 0;
    }
    else {
        x = tmp / 15;
        y = tmp % 15;
    }

    return (x << 4) | y;
}

/* decode AAC compression value (ETSI TS 101 154 page 99) */
static INT decodeCompr(const UCHAR compr)
{
    INT gain;
    SCHAR x = compr >> 4;     /* 4 MSB of compr */
    UCHAR y = (compr & 0x0F); /* 4 LSB of compr */

    /* gain = (INT)((48.164f - 6.0206f * x - 0.4014f * y) ); */
    gain = (INT)( scaleValue(((LONG)FL2FXCONST_DBL(6.0206f/128.f)*(8-x) - (LONG)FL2FXCONST_DBL(0.4014f/128.f)*y), -(DFRACT_BITS-1-7-16)) );

    return gain;
}


FDK_METADATA_ERROR FDK_MetadataEnc_Open(
        HANDLE_FDK_METADATA_ENCODER *phMetaData
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;
    HANDLE_FDK_METADATA_ENCODER hMetaData = NULL;

    if (phMetaData == NULL) {
      err = METADATA_INVALID_HANDLE;
      goto bail;
    }

    /* allocate memory */
    hMetaData = (HANDLE_FDK_METADATA_ENCODER) FDKcalloc(1, sizeof(FDK_METADATA_ENCODER) );

    if (hMetaData == NULL) {
      err = METADATA_MEMORY_ERROR;
      goto bail;
    }

    FDKmemclear(hMetaData, sizeof(FDK_METADATA_ENCODER));

    /* Allocate DRC Compressor. */
    if (FDK_DRC_Generator_Open(&hMetaData->hDrcComp)!=0) {
      err = METADATA_MEMORY_ERROR;
      goto bail;
    }

    /* Return metadata instance */
    *phMetaData = hMetaData;

    return err;

bail:
    FDK_MetadataEnc_Close(&hMetaData);
    return err;
}

FDK_METADATA_ERROR FDK_MetadataEnc_Close(
        HANDLE_FDK_METADATA_ENCODER *phMetaData
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;

    if (phMetaData == NULL) {
      err = METADATA_INVALID_HANDLE;
      goto bail;
    }

    if (*phMetaData != NULL) {
      FDK_DRC_Generator_Close(&(*phMetaData)->hDrcComp);
      FDKfree(*phMetaData);
      *phMetaData = NULL;
    }
bail:
    return err;
}

FDK_METADATA_ERROR FDK_MetadataEnc_Init(
        HANDLE_FDK_METADATA_ENCODER hMetaData,
        const INT                   resetStates,
        const INT                   metadataMode,
        const INT                   audioDelay,
        const UINT                  frameLength,
        const UINT                  sampleRate,
        const UINT                  nChannels,
        const CHANNEL_MODE          channelMode,
        const CHANNEL_ORDER         channelOrder
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;
    int i, nFrames, delay;

    if (hMetaData==NULL) {
      err = METADATA_INVALID_HANDLE;
      goto bail;
    }

    /* Determine values for delay compensation. */
    for (nFrames=0, delay=audioDelay-frameLength; delay>0; delay-=frameLength, nFrames++);

    if ( (hMetaData->nChannels>MAX_DRC_CHANNELS) || ((-delay)>MAX_DRC_FRAMELEN) ) {
      err = METADATA_INIT_ERROR;
      goto bail;
    }

    /* Initialize with default setup. */
    FDKmemcpy(&hMetaData->submittedMetaData, &defaultMetaDataSetup,  sizeof(AACENC_MetaData));

    hMetaData->finalizeMetaData = 0; /* finalize meta data only while on/off switching, else disabled */

    /* Reset delay lines. */
    if ( resetStates || (hMetaData->nAudioDataDelay!=-delay) || (hMetaData->nChannels!=(INT)nChannels) )
    {
      FDKmemclear(hMetaData->audioDelayBuffer, sizeof(hMetaData->audioDelayBuffer));
      FDKmemclear(hMetaData->metaDataBuffer, sizeof(hMetaData->metaDataBuffer));
      hMetaData->audioDelayIdx = 0;
      hMetaData->metaDataDelayIdx = 0;
    }
    else {
      /* Enable meta data. */
      if ( (hMetaData->metadataMode==0) && (metadataMode!=0) ) {
        /* disable meta data in all delay lines */
        for (i=0; i<(int)(sizeof(hMetaData->metaDataBuffer)/sizeof(AAC_METADATA)); i++) {
          LoadSubmittedMetadata(&hMetaData->submittedMetaData, nChannels, 0, &hMetaData->metaDataBuffer[i]);
        }
      }

      /* Disable meta data.*/
      if ( (hMetaData->metadataMode!=0) && (metadataMode==0) ) {
        hMetaData->finalizeMetaData = hMetaData->metadataMode;
      }
    }

    /* Initialize delay. */
    hMetaData->nAudioDataDelay = -delay;
    hMetaData->nMetaDataDelay  = nFrames;
    hMetaData->nChannels       = nChannels;
    hMetaData->metadataMode    = metadataMode;

    /* Initialize compressor. */
    if (metadataMode != 0) {
        if ( FDK_DRC_Generator_Initialize(
                         hMetaData->hDrcComp,
                         DRC_NONE,
                         DRC_NONE,
                         frameLength,
                         sampleRate,
                         channelMode,
                         channelOrder,
                         1) != 0)
        {
          err = METADATA_INIT_ERROR;
        }
    }
bail:
    return err;
}

static FDK_METADATA_ERROR ProcessCompressor(
        AAC_METADATA                    *pMetadata,
        HDRC_COMP                        hDrcComp,
        const INT_PCM * const            pSamples,
        const INT                        nSamples
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;

    if ( (pMetadata==NULL) || (hDrcComp==NULL) ) {
      err = METADATA_INVALID_HANDLE;
      return err;
    }
    DRC_PROFILE profileDrc  = convertProfile(pMetadata->mpegDrc.drc_profile);
    DRC_PROFILE profileComp = convertProfile(pMetadata->etsiAncData.comp_profile);

    /* first, check if profile is same as last frame
     * otherwise, update setup */
    if ( (profileDrc != FDK_DRC_Generator_getDrcProfile(hDrcComp))
      || (profileComp != FDK_DRC_Generator_getCompProfile(hDrcComp)) )
    {
      FDK_DRC_Generator_setDrcProfile(hDrcComp, profileDrc, profileComp);
    }

    /* Sanity check */
    if (profileComp == DRC_NONE) {
      pMetadata->etsiAncData.compression_value = 0x80;  /* to ensure no external values will be written if not configured */
    }

    /* in case of embedding external values, copy this now (limiter may overwrite them) */
    INT dynrng = decodeDynrng(pMetadata->mpegDrc.dyn_rng_ctl[0], pMetadata->mpegDrc.dyn_rng_sgn[0]);
    INT compr  = decodeCompr(pMetadata->etsiAncData.compression_value);

    /* Call compressor */
    if (FDK_DRC_Generator_Calc(hDrcComp,
                           pSamples,
                           progreflvl2dialnorm(pMetadata->mpegDrc.prog_ref_level),
                           pMetadata->mpegDrc.drc_TargetRefLevel,
                           pMetadata->etsiAncData.comp_TargetRefLevel,
                           dmxTable[pMetadata->centerMixLevel],
                           dmxTable[pMetadata->surroundMixLevel],
                           &dynrng,
                           &compr) != 0)
    {
      err = METADATA_ENCODE_ERROR;
      goto bail;
    }

    /* Write DRC values */
    pMetadata->mpegDrc.drc_band_incr = 0;
    encodeDynrng(dynrng, pMetadata->mpegDrc.dyn_rng_ctl, pMetadata->mpegDrc.dyn_rng_sgn);
    pMetadata->etsiAncData.compression_value = encodeCompr(compr);

bail:
    return err;
}

FDK_METADATA_ERROR FDK_MetadataEnc_Process(
        HANDLE_FDK_METADATA_ENCODER      hMetaDataEnc,
        INT_PCM * const                  pAudioSamples,
        const INT                        nAudioSamples,
        const AACENC_MetaData * const    pMetadata,
        AACENC_EXT_PAYLOAD **            ppMetaDataExtPayload,
        UINT *                           nMetaDataExtensions,
        INT *                            matrix_mixdown_idx
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;
    int metaDataDelayWriteIdx, metaDataDelayReadIdx, metadataMode;

    /* Where to write new meta data info */
    metaDataDelayWriteIdx = hMetaDataEnc->metaDataDelayIdx;

    /* How to write the data */
    metadataMode = hMetaDataEnc->metadataMode;

    /* Compensate meta data delay. */
    hMetaDataEnc->metaDataDelayIdx++;
    if (hMetaDataEnc->metaDataDelayIdx > hMetaDataEnc->nMetaDataDelay) hMetaDataEnc->metaDataDelayIdx = 0;

    /* Where to read pending meta data info from. */
    metaDataDelayReadIdx = hMetaDataEnc->metaDataDelayIdx;

    /* Submit new data if available. */
    if (pMetadata!=NULL) {
        FDKmemcpy(&hMetaDataEnc->submittedMetaData, pMetadata, sizeof(AACENC_MetaData));
    }

    /* Write one additional frame with default configuration of meta data. Ensure defined behaviour on decoder side. */
    if ( (hMetaDataEnc->finalizeMetaData!=0) && (hMetaDataEnc->metadataMode==0)) {
      FDKmemcpy(&hMetaDataEnc->submittedMetaData, &defaultMetaDataSetup,  sizeof(AACENC_MetaData));
      metadataMode = hMetaDataEnc->finalizeMetaData;
      hMetaDataEnc->finalizeMetaData = 0;
    }

    /* Get last submitted data. */
    if ( (err = LoadSubmittedMetadata(
                        &hMetaDataEnc->submittedMetaData,
                         hMetaDataEnc->nChannels,
                         metadataMode,
                        &hMetaDataEnc->metaDataBuffer[metaDataDelayWriteIdx])) != METADATA_OK )
    {
        goto bail;
    }

    /* Calculate compressor if necessary and updata meta data info */
    if (hMetaDataEnc->metaDataBuffer[metaDataDelayWriteIdx].metadataMode != 0) {
      if ( (err = ProcessCompressor(
                        &hMetaDataEnc->metaDataBuffer[metaDataDelayWriteIdx],
                         hMetaDataEnc->hDrcComp,
                         pAudioSamples,
                         nAudioSamples)) != METADATA_OK)
      {
        /* Get last submitted data again. */
        LoadSubmittedMetadata(
                        &hMetaDataEnc->submittedMetaData,
                         hMetaDataEnc->nChannels,
                         metadataMode,
                        &hMetaDataEnc->metaDataBuffer[metaDataDelayWriteIdx]);
      }
    }

    /* Convert Meta Data side info to bitstream data. */
    if ( (err = WriteMetadataPayload(hMetaDataEnc, &hMetaDataEnc->metaDataBuffer[metaDataDelayReadIdx])) != METADATA_OK ) {
      goto bail;
    }

    /* Assign meta data to output */
    *ppMetaDataExtPayload = hMetaDataEnc->exPayload;
    *nMetaDataExtensions  = hMetaDataEnc->nExtensions;
    *matrix_mixdown_idx   = hMetaDataEnc->matrix_mixdown_idx;

bail:
    /* Compensate audio delay, reset err status. */
    err = CompensateAudioDelay(hMetaDataEnc, pAudioSamples, nAudioSamples);

    return err;
}


static FDK_METADATA_ERROR CompensateAudioDelay(
        HANDLE_FDK_METADATA_ENCODER hMetaDataEnc,
        INT_PCM * const             pAudioSamples,
        const INT                   nAudioSamples
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;

    if (hMetaDataEnc->nAudioDataDelay) {
      int i, delaySamples = hMetaDataEnc->nAudioDataDelay*hMetaDataEnc->nChannels;

      for (i = 0; i < nAudioSamples; i++) {
        INT_PCM tmp = pAudioSamples[i];
        pAudioSamples[i] = hMetaDataEnc->audioDelayBuffer[hMetaDataEnc->audioDelayIdx];
        hMetaDataEnc->audioDelayBuffer[hMetaDataEnc->audioDelayIdx] = tmp;

        hMetaDataEnc->audioDelayIdx++;
        if (hMetaDataEnc->audioDelayIdx >= delaySamples) hMetaDataEnc->audioDelayIdx = 0;
      }
    }

    return err;
}

/*-----------------------------------------------------------------------------

  functionname: WriteMetadataPayload
  description:  fills anc data and extension payload
  returns:      Error status

 ------------------------------------------------------------------------------*/
static FDK_METADATA_ERROR WriteMetadataPayload(
        const HANDLE_FDK_METADATA_ENCODER hMetaData,
        const AAC_METADATA * const        pMetadata
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;

    if ( (hMetaData==NULL) || (pMetadata==NULL) ) {
        err = METADATA_INVALID_HANDLE;
        goto bail;
    }

    hMetaData->nExtensions = 0;
    hMetaData->matrix_mixdown_idx = -1;

    /* AAC-DRC */
    if (pMetadata->metadataMode != 0)
    {
        hMetaData->exPayload[hMetaData->nExtensions].pData               = hMetaData->drcInfoPayload;
        hMetaData->exPayload[hMetaData->nExtensions].dataType            = EXT_DYNAMIC_RANGE;
        hMetaData->exPayload[hMetaData->nExtensions].associatedChElement = -1;

        hMetaData->exPayload[hMetaData->nExtensions].dataSize =
              WriteDynamicRangeInfoPayload(pMetadata, hMetaData->exPayload[hMetaData->nExtensions].pData);

        hMetaData->nExtensions++;

        /* Matrix Mixdown Coefficient in PCE */
        if (pMetadata->WritePCEMixDwnIdx) {
            hMetaData->matrix_mixdown_idx = surmix2matrix_mixdown_idx[pMetadata->surroundMixLevel];
        }

        /* ETSI TS 101 154 (DVB) - MPEG4 ancillary_data() */
        if (pMetadata->metadataMode == 2) /* MP4_METADATA_MPEG_ETSI */
        {
            hMetaData->exPayload[hMetaData->nExtensions].pData               = hMetaData->drcDsePayload;
            hMetaData->exPayload[hMetaData->nExtensions].dataType            = EXT_DATA_ELEMENT;
            hMetaData->exPayload[hMetaData->nExtensions].associatedChElement = -1;

            hMetaData->exPayload[hMetaData->nExtensions].dataSize =
                 WriteEtsiAncillaryDataPayload(pMetadata,hMetaData->exPayload[hMetaData->nExtensions].pData);

            hMetaData->nExtensions++;
        } /* metadataMode == 2 */

    } /* metadataMode != 0 */

bail:
    return err;
}

static INT WriteDynamicRangeInfoPayload(
        const AAC_METADATA* const pMetadata,
        UCHAR* const              pExtensionPayload
        )
{
    const INT pce_tag_present = 0;        /* yet fixed setting! */
    const INT prog_ref_lev_res_bits = 0;
    INT i, drc_num_bands = 1;

    FDK_BITSTREAM bsWriter;
    FDKinitBitStream(&bsWriter, pExtensionPayload, 16, 0, BS_WRITER);

    /* dynamic_range_info() */
    FDKwriteBits(&bsWriter, pce_tag_present, 1);                                         /* pce_tag_present */
    if (pce_tag_present) {
      FDKwriteBits(&bsWriter, 0x0, 4);                                                   /* pce_instance_tag */
      FDKwriteBits(&bsWriter, 0x0, 4);                                                   /* drc_tag_reserved_bits */
   }

    /* Exclude channels */
    FDKwriteBits(&bsWriter, (pMetadata->mpegDrc.excluded_chns_present) ? 1 : 0, 1);      /* excluded_chns_present*/

    /* Multiband DRC */
    FDKwriteBits(&bsWriter, (pMetadata->mpegDrc.drc_bands_present) ? 1 : 0, 1);          /* drc_bands_present */
    if (pMetadata->mpegDrc.drc_bands_present)
    {
      FDKwriteBits(&bsWriter, pMetadata->mpegDrc.drc_band_incr, 4);                      /* drc_band_incr */
      FDKwriteBits(&bsWriter, pMetadata->mpegDrc.drc_interpolation_scheme, 4);           /* drc_interpolation_scheme */
      drc_num_bands += pMetadata->mpegDrc.drc_band_incr;
      for (i=0; i<drc_num_bands; i++) {
        FDKwriteBits(&bsWriter, pMetadata->mpegDrc.drc_band_top[i], 8);                  /* drc_band_top */
      }
    }

    /* Program Reference Level */
    FDKwriteBits(&bsWriter, pMetadata->mpegDrc.prog_ref_level_present, 1);               /* prog_ref_level_present */
    if (pMetadata->mpegDrc.prog_ref_level_present)
    {
      FDKwriteBits(&bsWriter, pMetadata->mpegDrc.prog_ref_level, 7);                     /* prog_ref_level */
      FDKwriteBits(&bsWriter, prog_ref_lev_res_bits, 1);                                 /* prog_ref_level_reserved_bits */
    }

    /* DRC Values */
    for (i=0; i<drc_num_bands; i++) {
      FDKwriteBits(&bsWriter, (pMetadata->mpegDrc.dyn_rng_sgn[i]) ? 1 : 0, 1);           /* dyn_rng_sgn[ */
      FDKwriteBits(&bsWriter, pMetadata->mpegDrc.dyn_rng_ctl[i], 7);                     /* dyn_rng_ctl */
    }

    /* return number of valid bits in extension payload. */
    return FDKgetValidBits(&bsWriter);
}

static INT WriteEtsiAncillaryDataPayload(
        const AAC_METADATA* const pMetadata,
        UCHAR* const              pExtensionPayload
        )
{
    FDK_BITSTREAM bsWriter;
    FDKinitBitStream(&bsWriter, pExtensionPayload, 16, 0, BS_WRITER);

    /* ancillary_data_sync */
    FDKwriteBits(&bsWriter, 0xBC, 8);

    /* bs_info */
    FDKwriteBits(&bsWriter, 0x3, 2);                                                     /* mpeg_audio_type */
    FDKwriteBits(&bsWriter, pMetadata->dolbySurroundMode, 2);                            /* dolby_surround_mode */
    FDKwriteBits(&bsWriter, 0x0, 4);                                                     /* reserved */

    /* ancillary_data_status */
    FDKwriteBits(&bsWriter, 0, 3);                                                       /* 3 bit Reserved, set to "0" */
    FDKwriteBits(&bsWriter, (pMetadata->DmxLvl_On) ? 1 : 0, 1);                          /* downmixing_levels_MPEG4_status */
    FDKwriteBits(&bsWriter, 0, 1);                                                       /* Reserved, set to "0" */
    FDKwriteBits(&bsWriter, (pMetadata->etsiAncData.compression_on) ? 1 : 0, 1);         /* audio_coding_mode_and_compression status */
    FDKwriteBits(&bsWriter, (pMetadata->etsiAncData.timecode_coarse_status) ? 1 : 0, 1); /* coarse_grain_timecode_status */
    FDKwriteBits(&bsWriter, (pMetadata->etsiAncData.timecode_fine_status) ? 1 : 0, 1);   /* fine_grain_timecode_status */

    /* downmixing_levels_MPEG4_status */
    if (pMetadata->DmxLvl_On) {
      FDKwriteBits(&bsWriter, encodeDmxLvls(pMetadata->centerMixLevel, pMetadata->surroundMixLevel), 8);
    }

    /* audio_coding_mode_and_compression_status */
    if (pMetadata->etsiAncData.compression_on) {
      FDKwriteBits(&bsWriter, 0x01, 8);                                                  /* audio coding mode */
      FDKwriteBits(&bsWriter, pMetadata->etsiAncData.compression_value, 8);              /* compression value */
    }

    /* grain-timecode coarse/fine */
    if (pMetadata->etsiAncData.timecode_coarse_status) {
      FDKwriteBits(&bsWriter, 0x0, 16);                                                  /* not yet supported */
    }

    if (pMetadata->etsiAncData.timecode_fine_status) {
      FDKwriteBits(&bsWriter, 0x0, 16);                                                  /* not yet supported */
    }

    return FDKgetValidBits(&bsWriter);
}


static FDK_METADATA_ERROR LoadSubmittedMetadata(
        const AACENC_MetaData * const   hMetadata,
        const INT                       nChannels,
        const INT                       metadataMode,
        AAC_METADATA * const            pAacMetaData
        )
{
    FDK_METADATA_ERROR err = METADATA_OK;

    if (pAacMetaData==NULL) {
      err = METADATA_INVALID_HANDLE;
    }
    else {
      /* init struct */
      FDKmemclear(pAacMetaData, sizeof(AAC_METADATA));

      if (hMetadata!=NULL) {
        /* convert data */
        pAacMetaData->mpegDrc.drc_profile            = hMetadata->drc_profile;
        pAacMetaData->etsiAncData.comp_profile       = hMetadata->comp_profile;
        pAacMetaData->mpegDrc.drc_TargetRefLevel     = hMetadata->drc_TargetRefLevel;
        pAacMetaData->etsiAncData.comp_TargetRefLevel= hMetadata->comp_TargetRefLevel;
        pAacMetaData->mpegDrc.prog_ref_level_present = hMetadata->prog_ref_level_present;
        pAacMetaData->mpegDrc.prog_ref_level         = dialnorm2progreflvl(hMetadata->prog_ref_level);

        pAacMetaData->centerMixLevel                 = hMetadata->centerMixLevel;
        pAacMetaData->surroundMixLevel               = hMetadata->surroundMixLevel;
        pAacMetaData->WritePCEMixDwnIdx              = hMetadata->PCE_mixdown_idx_present;
        pAacMetaData->DmxLvl_On                      = hMetadata->ETSI_DmxLvl_present;

        pAacMetaData->etsiAncData.compression_on = 1;


        if (nChannels == 2) {
          pAacMetaData->dolbySurroundMode = hMetadata->dolbySurroundMode;     /* dolby_surround_mode */
        } else {
          pAacMetaData->dolbySurroundMode = 0;
        }

        pAacMetaData->etsiAncData.timecode_coarse_status = 0; /* not yet supported - attention: Update GetEstMetadataBytesPerFrame() if enable this! */
        pAacMetaData->etsiAncData.timecode_fine_status   = 0; /* not yet supported - attention: Update GetEstMetadataBytesPerFrame() if enable this! */

        pAacMetaData->metadataMode = metadataMode;
      }
      else {
        pAacMetaData->metadataMode = 0;                      /* there is no configuration available */
      }
    }

    return err;
}

INT FDK_MetadataEnc_GetDelay(
        HANDLE_FDK_METADATA_ENCODER hMetadataEnc
        )
{
    INT delay = 0;

    if (hMetadataEnc!=NULL) {
        delay = hMetadataEnc->nAudioDataDelay;
    }

    return delay;
}


