/******************************************************************************
 *
 *  Copyright (C) 2004-2012 Broadcom Corporation
 *
 *  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.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *  This module contains utility functions for dealing with SBC data frames
 *  and codec capabilities.
 *
 ******************************************************************************/

#include "a2d_api.h"
#include "a2d_sbc.h"
#include "bta_av_sbc.h"

typedef int (tBTA_AV_SBC_ACT)(void *p_src, void *p_dst,
                               UINT32 src_samples, UINT32 dst_samples,
                               UINT32 *p_ret);

typedef struct
{
    INT32               cur_pos;    /* current position */
    UINT32              src_sps;    /* samples per second (source audio data) */
    UINT32              dst_sps;    /* samples per second (converted audio data) */
    tBTA_AV_SBC_ACT     *p_act;     /* the action function to do the conversion */
    UINT16              bits;       /* number of bits per pcm sample */
    UINT16              n_channels; /* number of channels (i.e. mono(1), stereo(2)...) */
    INT16               worker1;
    INT16               worker2;
    UINT8               div;
} tBTA_AV_SBC_UPS_CB;

tBTA_AV_SBC_UPS_CB bta_av_sbc_ups_cb;

/*******************************************************************************
**
** Function         bta_av_sbc_init_up_sample
**
** Description      initialize the up sample
**
**                  src_sps: samples per second (source audio data)
**                  dst_sps: samples per second (converted audio data)
**                  bits: number of bits per pcm sample
**                  n_channels: number of channels (i.e. mono(1), stereo(2)...)
**
** Returns          none
**
*******************************************************************************/
void bta_av_sbc_init_up_sample (UINT32 src_sps, UINT32 dst_sps, UINT16 bits, UINT16 n_channels)
{
    bta_av_sbc_ups_cb.cur_pos   = -1;
    bta_av_sbc_ups_cb.src_sps   = src_sps;
    bta_av_sbc_ups_cb.dst_sps   = dst_sps;
    bta_av_sbc_ups_cb.bits      = bits;
    bta_av_sbc_ups_cb.n_channels= n_channels;

    if(n_channels == 1)
    {
        /* mono */
        if(bits == 8)
        {
            bta_av_sbc_ups_cb.p_act = bta_av_sbc_up_sample_8m;
            bta_av_sbc_ups_cb.div   = 1;
        }
        else
        {
            bta_av_sbc_ups_cb.p_act = bta_av_sbc_up_sample_16m;
            bta_av_sbc_ups_cb.div   = 2;
        }
    }
    else
    {
        /* stereo */
        if(bits == 8)
        {
            bta_av_sbc_ups_cb.p_act = bta_av_sbc_up_sample_8s;
            bta_av_sbc_ups_cb.div   = 2;
        }
        else
        {
            bta_av_sbc_ups_cb.p_act = bta_av_sbc_up_sample_16s;
            bta_av_sbc_ups_cb.div   = 4;
        }
    }
}

/*******************************************************************************
**
** Function         bta_av_sbc_up_sample
**
** Description      Given the source (p_src) audio data and
**                  source speed (src_sps, samples per second),
**                  This function converts it to audio data in the desired format
**
**                  p_src: the data buffer that holds the source audio data
**                  p_dst: the data buffer to hold the converted audio data
**                  src_samples: The number of source samples (number of bytes)
**                  dst_samples: The size of p_dst (number of bytes)
**
** Note:            An AE reported an issue with this function.
**                  When called with bta_av_sbc_up_sample(src, uint8_array_dst..)
**                  the byte before uint8_array_dst may get overwritten.
**                  Using uint16_array_dst avoids the problem.
**                  This issue is related to endian-ness and is hard to resolve
**                  in a generic manner.
** **************** Please use uint16 array as dst.
**
** Returns          The number of bytes used in p_dst
**                  The number of bytes used in p_src (in *p_ret)
**
*******************************************************************************/
int bta_av_sbc_up_sample (void *p_src, void *p_dst,
                         UINT32 src_samples, UINT32 dst_samples,
                         UINT32 *p_ret)
{
    UINT32 src;
    UINT32 dst;

    if(bta_av_sbc_ups_cb.p_act)
    {
        src = src_samples/bta_av_sbc_ups_cb.div;
        dst = dst_samples/bta_av_sbc_ups_cb.div;
        return (*bta_av_sbc_ups_cb.p_act)(p_src, p_dst, src, dst, p_ret);
    }
    else
    {
        *p_ret = 0;
        return 0;
    }
}

/*******************************************************************************
**
** Function         bta_av_sbc_up_sample_16s (16bits-stereo)
**
** Description      Given the source (p_src) audio data and
**                  source speed (src_sps, samples per second),
**                  This function converts it to audio data in the desired format
**
**                  p_src: the data buffer that holds the source audio data
**                  p_dst: the data buffer to hold the converted audio data
**                  src_samples: The number of source samples (in uint of 4 bytes)
**                  dst_samples: The size of p_dst (in uint of 4 bytes)
**
** Returns          The number of bytes used in p_dst
**                  The number of bytes used in p_src (in *p_ret)
**
*******************************************************************************/
int bta_av_sbc_up_sample_16s (void *p_src, void *p_dst,
                         UINT32 src_samples, UINT32 dst_samples,
                         UINT32 *p_ret)
{
    INT16   *p_src_tmp = (INT16 *)p_src;
    INT16   *p_dst_tmp = (INT16 *)p_dst;
    INT16   *p_worker1 = &bta_av_sbc_ups_cb.worker1;
    INT16   *p_worker2 = &bta_av_sbc_ups_cb.worker2;
    UINT32  src_sps = bta_av_sbc_ups_cb.src_sps;
    UINT32  dst_sps = bta_av_sbc_ups_cb.dst_sps;

    while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples)
    {
        *p_dst_tmp++    = *p_worker1;
        *p_dst_tmp++    = *p_worker2;

        bta_av_sbc_ups_cb.cur_pos -= src_sps;
        dst_samples--;
    }

    bta_av_sbc_ups_cb.cur_pos = dst_sps;

    while (src_samples-- && dst_samples)
    {
        *p_worker1 = *p_src_tmp++;
        *p_worker2 = *p_src_tmp++;

        do
        {
            *p_dst_tmp++    = *p_worker1;
            *p_dst_tmp++    = *p_worker2;

            bta_av_sbc_ups_cb.cur_pos -= src_sps;
            dst_samples--;
        } while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples);

        bta_av_sbc_ups_cb.cur_pos += dst_sps;
    }

    if (bta_av_sbc_ups_cb.cur_pos == (INT32)dst_sps)
        bta_av_sbc_ups_cb.cur_pos = 0;

    *p_ret = ((char *)p_src_tmp - (char *)p_src);
    return ((char *)p_dst_tmp - (char *)p_dst);
}

/*******************************************************************************
**
** Function         bta_av_sbc_up_sample_16m (16bits-mono)
**
** Description      Given the source (p_src) audio data and
**                  source speed (src_sps, samples per second),
**                  This function converts it to audio data in the desired format
**
**                  p_src: the data buffer that holds the source audio data
**                  p_dst: the data buffer to hold the converted audio data
**                  src_samples: The number of source samples (in uint of 2 bytes)
**                  dst_samples: The size of p_dst (in uint of 2 bytes)
**
** Returns          The number of bytes used in p_dst
**                  The number of bytes used in p_src (in *p_ret)
**
*******************************************************************************/
int bta_av_sbc_up_sample_16m (void *p_src, void *p_dst,
                              UINT32 src_samples, UINT32 dst_samples,
                              UINT32 *p_ret)
{
    INT16   *p_src_tmp = (INT16 *)p_src;
    INT16   *p_dst_tmp = (INT16 *)p_dst;
    INT16   *p_worker = &bta_av_sbc_ups_cb.worker1;
    UINT32  src_sps = bta_av_sbc_ups_cb.src_sps;
    UINT32  dst_sps = bta_av_sbc_ups_cb.dst_sps;

    while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples)
    {
        *p_dst_tmp++ = *p_worker;
        *p_dst_tmp++ = *p_worker;

        bta_av_sbc_ups_cb.cur_pos -= src_sps;
        dst_samples--;
        dst_samples--;
    }


    bta_av_sbc_ups_cb.cur_pos = dst_sps;

    while (src_samples-- && dst_samples)
    {
        *p_worker = *p_src_tmp++;

        do
        {
            *p_dst_tmp++ = *p_worker;
            *p_dst_tmp++ = *p_worker;

            bta_av_sbc_ups_cb.cur_pos -= src_sps;
            dst_samples--;
            dst_samples--;

        } while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples);

        bta_av_sbc_ups_cb.cur_pos += dst_sps;
    }

    if (bta_av_sbc_ups_cb.cur_pos == (INT32)dst_sps)
        bta_av_sbc_ups_cb.cur_pos = 0;

    *p_ret = ((char *)p_src_tmp - (char *)p_src);
    return ((char *)p_dst_tmp - (char *)p_dst);
}

/*******************************************************************************
**
** Function         bta_av_sbc_up_sample_8s (8bits-stereo)
**
** Description      Given the source (p_src) audio data and
**                  source speed (src_sps, samples per second),
**                  This function converts it to audio data in the desired format
**
**                  p_src: the data buffer that holds the source audio data
**                  p_dst: the data buffer to hold the converted audio data
**                  src_samples: The number of source samples (in uint of 2 bytes)
**                  dst_samples: The size of p_dst (in uint of 2 bytes)
**
** Returns          The number of bytes used in p_dst
**                  The number of bytes used in p_src (in *p_ret)
**
*******************************************************************************/
int bta_av_sbc_up_sample_8s (void *p_src, void *p_dst,
                             UINT32 src_samples, UINT32 dst_samples,
                             UINT32 *p_ret)
{
    UINT8   *p_src_tmp = (UINT8 *)p_src;
    INT16   *p_dst_tmp = (INT16 *)p_dst;
    INT16   *p_worker1 = &bta_av_sbc_ups_cb.worker1;
    INT16   *p_worker2 = &bta_av_sbc_ups_cb.worker2;
    UINT32  src_sps = bta_av_sbc_ups_cb.src_sps;
    UINT32  dst_sps = bta_av_sbc_ups_cb.dst_sps;

    while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples)
    {
        *p_dst_tmp++    = *p_worker1;
        *p_dst_tmp++    = *p_worker2;

        bta_av_sbc_ups_cb.cur_pos -= src_sps;
        dst_samples--;
        dst_samples--;
    }

    bta_av_sbc_ups_cb.cur_pos = dst_sps;

    while (src_samples -- && dst_samples)
    {
        *p_worker1 = *(UINT8 *)p_src_tmp++;
        *p_worker1 -= 0x80;
        *p_worker1 <<= 8;
        *p_worker2 = *(UINT8 *)p_src_tmp++;
        *p_worker2 -= 0x80;
        *p_worker2 <<= 8;

        do
        {
            *p_dst_tmp++    = *p_worker1;
            *p_dst_tmp++    = *p_worker2;

            bta_av_sbc_ups_cb.cur_pos -= src_sps;
            dst_samples--;
            dst_samples--;
        } while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples);

        bta_av_sbc_ups_cb.cur_pos += dst_sps;
    }

    if (bta_av_sbc_ups_cb.cur_pos == (INT32)dst_sps)
        bta_av_sbc_ups_cb.cur_pos = 0;

    *p_ret = ((char *)p_src_tmp - (char *)p_src);
    return ((char *)p_dst_tmp - (char *)p_dst);
}

/*******************************************************************************
**
** Function         bta_av_sbc_up_sample_8m (8bits-mono)
**
** Description      Given the source (p_src) audio data and
**                  source speed (src_sps, samples per second),
**                  This function converts it to audio data in the desired format
**
**                  p_src: the data buffer that holds the source audio data
**                  p_dst: the data buffer to hold the converted audio data
**                  src_samples: The number of source samples (number of bytes)
**                  dst_samples: The size of p_dst (number of bytes)
**
** Returns          The number of bytes used in p_dst
**                  The number of bytes used in p_src (in *p_ret)
**
*******************************************************************************/
int bta_av_sbc_up_sample_8m (void *p_src, void *p_dst,
                             UINT32 src_samples, UINT32 dst_samples,
                             UINT32 *p_ret)
{
    UINT8   *p_src_tmp = (UINT8 *)p_src;
    INT16   *p_dst_tmp = (INT16 *)p_dst;
    INT16   *p_worker = &bta_av_sbc_ups_cb.worker1;
    UINT32  src_sps = bta_av_sbc_ups_cb.src_sps;
    UINT32  dst_sps = bta_av_sbc_ups_cb.dst_sps;

    while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples)
    {
        *p_dst_tmp++ = *p_worker;
        *p_dst_tmp++ = *p_worker;

        bta_av_sbc_ups_cb.cur_pos -= src_sps;
        dst_samples -= 4;
    }


    bta_av_sbc_ups_cb.cur_pos = dst_sps;

    while (src_samples-- && dst_samples)
    {
        *p_worker = *(UINT8 *)p_src_tmp++;
        *p_worker -= 0x80;
        *p_worker <<= 8;

        do
        {
            *p_dst_tmp++ = *p_worker;
            *p_dst_tmp++ = *p_worker;

            bta_av_sbc_ups_cb.cur_pos -= src_sps;
            dst_samples -= 4;

        } while (bta_av_sbc_ups_cb.cur_pos > 0 && dst_samples);

        bta_av_sbc_ups_cb.cur_pos += dst_sps;
    }

    if (bta_av_sbc_ups_cb.cur_pos == (INT32)dst_sps)
        bta_av_sbc_ups_cb.cur_pos = 0;

    *p_ret = ((char *)p_src_tmp - (char *)p_src);
    return ((char *)p_dst_tmp - (char *)p_dst);
}

/*******************************************************************************
**
** Function         bta_av_sbc_cfg_for_cap
**
** Description      Determine the preferred SBC codec configuration for the
**                  given codec capabilities.  The function is passed the
**                  preferred codec configuration and the peer codec
**                  capabilities for the stream.  The function attempts to
**                  match the preferred capabilities with the configuration
**                  as best it can.  The resulting codec configuration is
**                  returned in the same memory used for the capabilities.
**
** Returns          0 if ok, nonzero if error.
**                  Codec configuration in p_cap.
**
*******************************************************************************/
UINT8 bta_av_sbc_cfg_for_cap(UINT8 *p_peer, tA2D_SBC_CIE *p_cap, tA2D_SBC_CIE *p_pref)
{
    UINT8           status = A2D_SUCCESS;
    tA2D_SBC_CIE    peer_cie;

    /* parse peer capabilities */
    if ((status = A2D_ParsSbcInfo(&peer_cie, p_peer, TRUE)) != 0)
    {
        return status;
    }

    /* Check if the peer supports our channel mode */
    if (peer_cie.ch_mode & p_pref->ch_mode)
    {
        peer_cie.ch_mode = p_pref->ch_mode;
    }
    else
    {
        APPL_TRACE_ERROR1("bta_av_sbc_cfg_for_cap: ch_mode(0x%02X) not supported", p_pref->ch_mode);
        return A2D_FAIL;
    }

    /* Check if the peer supports our sampling freq */
    if (peer_cie.samp_freq & p_pref->samp_freq)
    {
        peer_cie.samp_freq = p_pref->samp_freq;
    }
    else
    {
        APPL_TRACE_ERROR1("bta_av_sbc_cfg_for_cap: samp_freq(0x%02X) not supported", p_pref->samp_freq);
        return A2D_FAIL;
    }

    /* Check if the peer supports our block len */
    if (peer_cie.block_len & p_pref->block_len)
    {
        peer_cie.block_len = p_pref->block_len;
    }
    else
    {
        APPL_TRACE_ERROR1("bta_av_sbc_cfg_for_cap: block_len(0x%02X) not supported", p_pref->block_len);
        return A2D_FAIL;
    }

    /* Check if the peer supports our num subbands */
    if (peer_cie.num_subbands & p_pref->num_subbands)
    {
        peer_cie.num_subbands = p_pref->num_subbands;
    }
    else
    {
        APPL_TRACE_ERROR1("bta_av_sbc_cfg_for_cap: num_subbands(0x%02X) not supported", p_pref->num_subbands);
        return A2D_FAIL;
    }

    /* Check if the peer supports our alloc method */
    if (peer_cie.alloc_mthd & p_pref->alloc_mthd)
    {
        peer_cie.alloc_mthd = p_pref->alloc_mthd;
    }
    else
    {
        APPL_TRACE_ERROR1("bta_av_sbc_cfg_for_cap: alloc_mthd(0x%02X) not supported", p_pref->alloc_mthd);
        return A2D_FAIL;
    }

    /* max bitpool */
    if (p_pref->max_bitpool != 0 && p_pref->max_bitpool < peer_cie.max_bitpool)
    {
        peer_cie.max_bitpool = p_pref->max_bitpool;
    }

    /* min bitpool */
    if (p_pref->min_bitpool != 0 && p_pref->min_bitpool > peer_cie.min_bitpool)
    {
        peer_cie.min_bitpool = p_pref->min_bitpool;
    }

    if (status == A2D_SUCCESS)
    {
        /* build configuration */
        A2D_BldSbcInfo(A2D_MEDIA_TYPE_AUDIO, &peer_cie, p_peer);
    }
    return status;
}

/*******************************************************************************
**
** Function         bta_av_sbc_cfg_in_cap
**
** Description      This function checks whether an SBC codec configuration
**                  is allowable for the given codec capabilities.
**
** Returns          0 if ok, nonzero if error.
**
*******************************************************************************/
UINT8 bta_av_sbc_cfg_in_cap(UINT8 *p_cfg, tA2D_SBC_CIE *p_cap)
{
    UINT8           status = 0;
    tA2D_SBC_CIE    cfg_cie;

    /* parse configuration */
    if ((status = A2D_ParsSbcInfo(&cfg_cie, p_cfg, FALSE)) != 0)
    {
        return status;
    }

    /* verify that each parameter is in range */

    /* sampling frequency */
    if ((cfg_cie.samp_freq & p_cap->samp_freq) == 0)
    {
        status = A2D_NS_SAMP_FREQ;
    }
    /* channel mode */
    else if ((cfg_cie.ch_mode & p_cap->ch_mode) == 0)
    {
        status = A2D_NS_CH_MODE;
    }
    /* block length */
    else if ((cfg_cie.block_len & p_cap->block_len) == 0)
    {
        status = A2D_BAD_BLOCK_LEN;
    }
    /* subbands */
    else if ((cfg_cie.num_subbands & p_cap->num_subbands) == 0)
    {
        status = A2D_NS_SUBBANDS;
    }
    /* allocation method */
    else if ((cfg_cie.alloc_mthd & p_cap->alloc_mthd) == 0)
    {
        status = A2D_NS_ALLOC_MTHD;
    }
    /* max bitpool */
    else if (cfg_cie.max_bitpool > p_cap->max_bitpool)
    {
        status = A2D_NS_MAX_BITPOOL;
    }
    /* min bitpool */
    else if (cfg_cie.min_bitpool < p_cap->min_bitpool)
    {
        status = A2D_NS_MIN_BITPOOL;
    }

    return status;
}

/*******************************************************************************
**
** Function         bta_av_sbc_bld_hdr
**
** Description      This function builds the packet header for MPF1.
**
** Returns          void
**
*******************************************************************************/
void bta_av_sbc_bld_hdr(BT_HDR *p_buf, UINT16 fr_per_pkt)
{
    UINT8   *p;

    p_buf->offset -= BTA_AV_SBC_HDR_SIZE;
    p = (UINT8 *) (p_buf + 1) + p_buf->offset;
    p_buf->len += BTA_AV_SBC_HDR_SIZE;
    A2D_BldSbcMplHdr(p, FALSE, FALSE, FALSE, (UINT8) fr_per_pkt);
}

