/**************************************************************************//**
 * @file     uac_core.c
 * @version  V1.00
 * @brief    USB Host library audio class core driver.
 *
 * @copyright SPDX-License-Identifier: Apache-2.0
 * @copyright Copyright (C) 2021 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/

#include <stdio.h>
#include <string.h>

#include "NuMicro.h"

#include "usb.h"
#include "usbh_lib.h"
#include "usbh_uac.h"
#include "uac.h"

/** @addtogroup LIBRARY Library
  @{
*/

/** @addtogroup USBH_Library USB Host Library
  @{
*/

/** @addtogroup USBH_EXPORTED_FUNCTIONS USB Host Exported Functions
  @{
*/

/**
 *  @brief  Obtain Audio Class device's channel number.
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @return   Channel number or error code.
 *  @retval   < 0      Failed. UAC device may not present or function not supported.
 *  @retval   Otherwise  The channel number.
 */
int  usbh_uac_get_channel_number(UAC_DEV_T *uac, uint8_t target, uint32_t *ch_list,
                                int max_cnt, uint8_t *type)
{
    AS_FT1_T   *ft;
    AS_IF_T      asif;
    int i;
    if(uac->version == 0x0200)
    {
        if(target == UAC_SPEAKER)
            asif = uac->asif_out;
        else
            asif = uac->asif_in;

        if(asif.iface->num_alt < max_cnt)
            *type = asif.iface->num_alt;
        else               
            *type = max_cnt;

        for(i=0;i<*type;i++)
        {
            if(target == UAC_SPEAKER)
                ch_list[i] = uac->acif.speaker_ch[i];
            else
                ch_list[i] = uac->acif.mic_ch[i];
        }
        return 0;
    }
    else
    {
        if(target == UAC_SPEAKER)
            ft = uac->asif_out.ft;
        else
            ft = uac->asif_in.ft;

        if(!ft)
            return UAC_RET_DEV_NOT_SUPPORTED;

        if(asif.iface->num_alt < max_cnt)
            *type = asif.iface->num_alt;
        else               
            *type = max_cnt;

        for(i=0;i<*type;i++)
        {
            if(target == UAC_SPEAKER)
                ch_list[i] = uac->acif.speaker_ch[i];
            else
                ch_list[i] = uac->acif.mic_ch[i];
        }
        return 0;
    }
}


/**
 *  @brief  Obtain Audio Class device subframe bit resolution..
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[out] byte_cnt  The number of bytes occupied by one audio subframe. Can be 1, 2, 3 or 4.
 *  @return   Bit resolution or error code.
 *  @retval   < 0        Failed. UAC device may not present or function not supported.
 *  @retval   Otherwise  The number of effectively used bits from the available bits in an audio subframe.
 */
int  usbh_uac_get_bit_resolution(UAC_DEV_T *uac, uint8_t target, uint32_t *bit_list,
                                int max_cnt, uint8_t *type)
{
    AS_FT1_T   *ft;     
    AS_IF_T      asif;
    int i;

    if(target == UAC_SPEAKER)
        ft = uac->asif_out.ft;
    else
        ft = uac->asif_in.ft;

    if(!ft)
        return UAC_RET_DEV_NOT_SUPPORTED;

    if(uac->version == 0x0200)
    {
        if(target == UAC_SPEAKER)
            asif = uac->asif_out;
        else
            asif = uac->asif_in;

        if(asif.iface->num_alt < max_cnt)
            *type = asif.iface->num_alt;
        else               
            *type = max_cnt;

        for(i=0;i<*type;i++)
        {
            if(target == UAC_SPEAKER)
                bit_list[i] = uac->acif.speaker_SubslotSize[i];
            else
                bit_list[i] = uac->acif.mic_SubslotSize[i];
        }
        return 0;
    }
    else
    {                         

        if(asif.iface->num_alt < max_cnt)
            *type = asif.iface->num_alt;
        else               
            *type = max_cnt;

        for(i=0;i<*type;i++)
        {
            if(target == UAC_SPEAKER)
                bit_list[i] = uac->acif.speaker_SubslotSize[i];
            else
                bit_list[i] = uac->acif.mic_SubslotSize[i];
        }
        return 0;
    }
}

/// @cond HIDDEN_SYMBOLS

uint32_t  srate_to_u32(uint8_t *srate)
{
    return (srate[2] << 16) | (srate[1] << 8) | srate[0];
}

/// @endcond HIDDEN_SYMBOLS

/**
 *  @brief  Get a list of sampling rate frequencies supported by the UAC device.
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[out] srate_list  A word array provided by user application to hold the sampling rate list.
 *  @param[in]  max_cnt  Available number of entries of srate_list[]. Must be > 2.
 *  @param[out] type   Indicates how the sampling frequency can be programmed.
 *                     0:  Continuous sampling frequency. srate_list[0] is the lower bound
 *                          in Hz of the sampling frequency and srate_list[1] is the upper bound.
 *                     1~255:  The number of discrete sampling frequencies supported. They are
 *                             listed in srate_list[].
 *  @return   Success or not.
 *  @retval   0        Success
 *  @retval   Otherwise  Failed
 */
int  usbh_uac_get_sampling_rate(UAC_DEV_T *uac, uint8_t target, uint32_t *srate_list,
                                int max_cnt, uint8_t *type)
{
    AS_FT1_T   *ft;
    int        i;
    uint32_t clock_id; 
    int         ret;
    if(target == UAC_SPEAKER)  
    {
        clock_id = uac->acif.speaker_clock_id;
        ft = uac->asif_out.ft;
    }
    else
    {
        clock_id = uac->acif.mic_clock_id;   
        ft = uac->asif_in.ft;
    }

    if(uac->version == 0x0200)
    {
        uint8_t range_buf[256];
        uint32_t u32Len;      
        uint16_t wNumSubRanges, chn = 0; 

        ret = usbh_ctrl_xfer(uac->udev,
                             REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE,
                             UAC_GET_RANGE,
                             (CLOCK_FREQUENCY_CONTROL << 8) | chn,
                             (clock_id << 8) | uac->acif.iface->if_num,
                             256,
                             range_buf,
                             &u32Len,
                             UAC_REQ_TIMEOUT);

        if (ret < 0)
            return ret;

        wNumSubRanges = range_buf[0] | (range_buf[1] << 8);

        *type = wNumSubRanges; 

        UAC_DBGMSG("GET_RANGE: wNumSubRanges = %d\n", wNumSubRanges);

        for (int i = 0; i < wNumSubRanges; i++)
        {
            int offset = 2 + i * 12;
#if 0
            uint32_t min = range_buf[offset] |
                           (range_buf[offset + 1] << 8) |
                           (range_buf[offset + 2] << 16) |
                           (range_buf[offset + 3] << 24);
            uint32_t res = range_buf[offset + 8] |
                           (range_buf[offset + 9] << 8) |
                           (range_buf[offset + 10] << 16) |
                           (range_buf[offset + 11] << 24);
#endif
            uint32_t max = range_buf[offset + 4] |
                           (range_buf[offset + 5] << 8) |
                           (range_buf[offset + 6] << 16) |
                           (range_buf[offset + 7] << 24);
            UAC_DBGMSG("  Range %d: MIN=%u, MAX=%u, RES=%u\n", i, min, max, res);
            if(i < max_cnt)
                srate_list[i] = max;
        }
        return 0;
    }
    else
    {
        if(!ft)
            return UAC_RET_DEV_NOT_SUPPORTED;
        *type = ft->bSamFreqType;

        if(*type == 0)
        {
            if(max_cnt < 2)
                return UAC_RET_OUT_OF_MEMORY;

            srate_list[0] = srate_to_u32(&ft->tSamFreq[0][0]);
            srate_list[1] = srate_to_u32(&ft->tSamFreq[1][0]);
        }
        else
        {
            for(i = 0; i < *type; i++)
                srate_list[i] = srate_to_u32(&ft->tSamFreq[i][0]);
        }
    }
    return 0;
}

/**
 *  @brief  Set sampling rate frequency.
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[in]  req    Control request. This UAC driver supports the following request:
 *                     - \ref UAC_SET_CUR
 *                     - \ref UAC_GET_CUR
 *                     - \ref UAC_SET_MIN
 *                     - \ref UAC_GET_MIN
 *                     - \ref UAC_SET_MAX
 *                     - \ref UAC_GET_MAX
 *                     - \ref UAC_SET_RES
 *                     - \ref UAC_GET_RES
 *  @param[in]  srate  Sampling rate frequency to be set or get.
 *  @return   Success or failed.
 *  @retval   0        Success
 *  @retval   Otherwise  Error occurred
 */
int  usbh_uac_sampling_rate_control(UAC_DEV_T *uac, uint8_t target, uint8_t req, uint32_t *srate)
{
    EP_INFO_T   *ep;
    uint8_t     bmRequestType, set_cur = 0;
    uint8_t     tSampleFreq[4];
    uint32_t    xfer_len;
    int         ret;
    uint32_t   clock_id, chn = 0x0; //Master Channel
 
    if(uac->version == 0x0200)
    {
        if(target == UAC_SPEAKER)  
            clock_id = uac->acif.speaker_clock_id;
        else
            clock_id = uac->acif.mic_clock_id;    

        tSampleFreq[0] = *srate & 0xff;
        tSampleFreq[1] = (*srate >> 8) & 0xff;
        tSampleFreq[2] = (*srate >> 16) & 0xff;
        tSampleFreq[3] = (*srate >> 24) & 0xff;

        if(req & 0x80)
            bmRequestType = REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;
        else
        {
            set_cur = 1;
            bmRequestType = REQ_TYPE_OUT | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;
        }
        req = req & 0x7F;
        /* Audio Class Request - Endpoint Control Requests (5.2.3.2) */
        ret = usbh_ctrl_xfer(uac->udev, bmRequestType, req,
                             (CLOCK_FREQUENCY_CONTROL << 8) | chn,
                             (clock_id << 8) | uac->acif.iface->if_num,
                             4,                            /* wLength - parameter block length */
                             tSampleFreq,                  /* parameter block                */
                             &xfer_len, UAC_REQ_TIMEOUT);

        if(ret < 0)
            return ret;

        if(xfer_len != 4)
           return UAC_RET_DATA_LEN;

        *srate = (tSampleFreq[3] << 24) |(tSampleFreq[2] << 16) | (tSampleFreq[1] << 8) | tSampleFreq[0];

        if(set_cur)
        {
            if(target == UAC_SPEAKER)  
                uac->acif.speaker_samplingrate = *srate; 
            else
                uac->acif.mic_samplingrate = *srate; 
        }
    }
    else
    {           
        if(target == UAC_SPEAKER)
            ep = uac->asif_out.ep;
        else
            ep = uac->asif_in.ep;

        if(ep == NULL)
            return UAC_RET_DEV_NOT_SUPPORTED;

        tSampleFreq[0] = *srate & 0xff;
        tSampleFreq[1] = (*srate >> 8) & 0xff;
        tSampleFreq[2] = (*srate >> 16) & 0xff;

        if(req & 0x80)
            bmRequestType = REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_EP;
        else
        {
            set_cur = 1;
            bmRequestType = REQ_TYPE_OUT | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_EP;
        }
        /* Audio Class Request - Endpoint Control Requests (5.2.3.2) */
        ret = usbh_ctrl_xfer(uac->udev, bmRequestType, req,
                             (SAMPLING_FREQ_CONTROL << 8),  /* wValue - Control Selector (CS) */
                             ep->bEndpointAddress,         /* wIndex - endpoint              */
                             3,                            /* wLength - parameter block length */
                             tSampleFreq,                  /* parameter block                */
                             &xfer_len, UAC_REQ_TIMEOUT);
        if(ret < 0)
            return ret;

        if(xfer_len != 3)
           return UAC_RET_DATA_LEN;

        *srate = srate_to_u32(tSampleFreq);
        if(set_cur)
        {
            if(target == UAC_SPEAKER)  
                uac->acif.speaker_samplingrate = *srate; 
            else
                uac->acif.mic_samplingrate = *srate; 
        }
    }

    return 0;
}

/**
 *  @brief  Control Audio Class device mute on/off.
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[in]  req    Control request. This UAC driver supports the following request:
 *                     - \ref UAC_SET_CUR
 *                     - \ref UAC_GET_CUR
 *  @param[in]  chn    The requested channel. It can be one of the followings:
 *                     - \ref UAC_CH_LEFT_FRONT
 *                     - \ref UAC_CH_RIGHT_FRONT
 *                     - \ref UAC_CH_CENTER_FRONT
 *                     - \ref UAC_CH_LOW_FREQ_EN
 *                     - \ref UAC_CH_LEFT_SRN
 *                     - \ref UAC_CH_RIGHT_SRN
 *                     - \ref UAC_CH_LEFT_OF_CENTER
 *                     - \ref UAC_CH_RIGHT_OF_CENTER
 *                     - \ref UAC_CH_SURROUND
 *                     - \ref UAC_CH_SIDE_LEFT
 *                     - \ref UAC_CH_SIDE_RIGHT
 *                     - \ref UAC_CH_TOP
 *  @param[in]  mute   One byte data. If the channel is muted, then the value is 1. Otherwise, it's 0.
 *  @return   Success or failed.
 *  @retval   0        Success
 *  @retval   UAC_RET_DEV_NOT_SUPPORTED  This UAC device does not support this function.
 *  @retval   Otherwise  Error occurred
 */
int usbh_uac_mute_control(UAC_DEV_T *uac, uint8_t target, uint8_t req, uint16_t chn, uint8_t *mute)
{
    uint8_t     bmRequestType;
    uint8_t     bUnitID;
    uint32_t    xfer_len;
    int         ret;
 
    if(target == UAC_MICROPHONE)
        bUnitID = uac->acif.mic_fuid;
    else
        bUnitID = uac->acif.speaker_fuid;

    if(req & 0x80)
        bmRequestType = REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;
    else
        bmRequestType = REQ_TYPE_OUT | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;

    if(uac->version == 0x0200)
        req = req & 0x7F;

    /* Audio Class Request - Feature Unit Control Request (5.2.2.4) */
    ret = usbh_ctrl_xfer(uac->udev, bmRequestType, req,
                         (MUTE_CONTROL << 8) | chn,  /* wValue - Control Selector (CS)    */
                         (bUnitID << 8) | (uac->acif.iface->if_num),  /* wIndex - unit ID and interface number */
                         1,                          /* wLength - parameter block length  */
                         mute,                       /* parameter block                   */
                         &xfer_len, UAC_REQ_TIMEOUT);
    if(ret < 0)
        return ret;

    if(xfer_len != 1)
        return UAC_RET_DATA_LEN;

    return 0;
}

/**
 *  @brief  Audio Class device volume control.
 *  @param[in]  uac    UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[in]  req    Control request. This UAC driver supports the following request:
 *                     - \ref UAC_SET_CUR
 *                     - \ref UAC_GET_CUR
 *                     - \ref UAC_SET_MIN
 *                     - \ref UAC_GET_MIN
 *                     - \ref UAC_SET_MAX
 *                     - \ref UAC_GET_MAX
 *                     - \ref UAC_SET_RES
 *                     - \ref UAC_GET_RES
 *                     - \ref UAC_GET_STAT
 *                     - \ref UAC_RANGE
 *  @param[in]  chn    The requested channel. It can be one of the followings:
 *                     - \ref UAC_CH_LEFT_FRONT
 *                     - \ref UAC_CH_RIGHT_FRONT
 *                     - \ref UAC_CH_CENTER_FRONT
 *                     - \ref UAC_CH_LOW_FREQ_EN
 *                     - \ref UAC_CH_LEFT_SRN
 *                     - \ref UAC_CH_RIGHT_SRN
 *                     - \ref UAC_CH_LEFT_OF_CENTER
 *                     - \ref UAC_CH_RIGHT_OF_CENTER
 *                     - \ref UAC_CH_SURROUND
 *                     - \ref UAC_CH_SIDE_LEFT
 *                     - \ref UAC_CH_SIDE_RIGHT
 *                     - \ref UAC_CH_TOP
 *  @param[in]  volume   Audio Class device volume value, which is interpreted as the following:
 *                       0x7FFF:    127.9961 dB
 *                       . . .
 *                       0x0100:      1.0000 dB
 *                       . . .
 *                       0x0002:      0.0078 dB
 *                       0x0001:      0.0039 dB
 *                       0x0000:      0.0000 dB
 *                       0xFFFF:     -0.0039 dB
 *                       0xFFFE:     -0.0078 dB
 *                       . . .
 *                       0xFE00:     -1.0000 dB
 *                       . . .
 *                       0x8002:   -127.9922 dB
 *                       0x8001:   -127.9961 dB
 *  @return   Success or failed.
 *  @retval   0        Success
 *  @retval   UAC_RET_DEV_NOT_SUPPORTED  This UAC device does not support this function.
 *  @retval   Otherwise  Error occurred
 */
int usbh_uac_vol_control(UAC_DEV_T *uac, uint8_t target, uint8_t req, uint16_t chn, uint16_t *volume)
{
    uint8_t     bmRequestType;    
    uint8_t     reqBackup;
    uint8_t     bUnitID, u8Range = 0;
    uint32_t    xfer_len, len = 2;
    int         ret;
    uint16_t    u16Range[4];
    uint8_t     *puPtr = (uint8_t *)volume;

    if(target == UAC_MICROPHONE)
        bUnitID = uac->acif.mic_fuid;
    else
        bUnitID = uac->acif.speaker_fuid;

    if(req & 0x80)
        bmRequestType = REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;
    else
        bmRequestType = REQ_TYPE_OUT | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;

    if(uac->version == 0x0200)
    {
        if((req == UAC_GET_MAX) || (req == UAC_GET_MIN) || (req == UAC_GET_RES))
        {
            reqBackup = req;
            u8Range = 1;
            req = UAC_RANGE;
            len = 8;
            puPtr =  (uint8_t *)u16Range;
        }
        else
            req = req & 0x7F;
    }
 
    /* Audio Class Request - Feature Unit Control Request (5.2.2.4) */
    ret = usbh_ctrl_xfer(uac->udev, bmRequestType, req,
                         (VOLUME_CONTROL << 8) | chn,/* wValue - Control Selector (CS)    */
                         (bUnitID << 8) | (uac->acif.iface->if_num),  /* wIndex - unit ID and interface number */
                         len,                          /* wLength - parameter block length  */
                         puPtr,          /* parameter block                   */
                         &xfer_len, UAC_REQ_TIMEOUT);
    if(ret < 0)
        return ret;

    if(xfer_len != len)
        return UAC_RET_DATA_LEN;
    if(u8Range)
    {
        switch(reqBackup)
        {
            case UAC_GET_MIN:
                *volume = u16Range[1]; 
                break;
            case UAC_GET_MAX:
                *volume = u16Range[2]; 
                break;
            case UAC_GET_RES:
                *volume = u16Range[3]; 
                break;
        }
    }
    return 0;
}

/**
 *  @brief  Audio Class device automatic gain control.
 *  @param[in]  uac  UAC device
 *  @param[in]  target Select the control target.
 *                     - \ref UAC_SPEAKER
 *                     - \ref UAC_MICROPHONE
 *  @param[in]  req    Control request. This UAC driver supports the following request:
 *                     - \ref UAC_SET_CUR
 *                     - \ref UAC_GET_CUR
 *  @param[in]  chn    The requested channel. It can be one of the followings:
 *                     - \ref UAC_CH_LEFT_FRONT
 *                     - \ref UAC_CH_RIGHT_FRONT
 *                     - \ref UAC_CH_CENTER_FRONT
 *                     - \ref UAC_CH_LOW_FREQ_EN
 *                     - \ref UAC_CH_LEFT_SRN
 *                     - \ref UAC_CH_RIGHT_SRN
 *                     - \ref UAC_CH_LEFT_OF_CENTER
 *                     - \ref UAC_CH_RIGHT_OF_CENTER
 *                     - \ref UAC_CH_SURROUND
 *                     - \ref UAC_CH_SIDE_LEFT
 *                     - \ref UAC_CH_SIDE_RIGHT
 *                     - \ref UAC_CH_TOP
 *  @param[in]  bAGC   One byte data. If the channel's automatic gain control is on, then the value is 1. Otherwise, it's 0.
 *  @return   Success or failed.
 *  @retval   0        Success
 *  @retval   UAC_RET_DEV_NOT_SUPPORTED  This UAC device does not support this function.
 *  @retval   Otherwise  Error occurred
 */
int  usbh_uac_auto_gain_control(UAC_DEV_T *uac, uint8_t target, uint8_t req, uint16_t chn, uint8_t *bAGC)
{
    uint8_t     bmRequestType;
    uint8_t     bUnitID;
    uint32_t    xfer_len;
    int         ret;

    if(target == UAC_MICROPHONE)
        bUnitID = uac->acif.mic_fuid;
    else
        bUnitID = uac->acif.speaker_fuid;

    if(req & 0x80)
        bmRequestType = REQ_TYPE_IN | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;
    else
        bmRequestType = REQ_TYPE_OUT | REQ_TYPE_CLASS_DEV | REQ_TYPE_TO_IFACE;

    if(uac->version == 0x0200)
    {
        req = req & 0x7F;
    }
    /* Audio Class Request - Feature Unit Control Request (5.2.2.4) */
    ret = usbh_ctrl_xfer(uac->udev, bmRequestType, req,
                         (AUTOMATIC_GAIN_CONTROL << 8) | chn,  /* wValue - Control Selector (CS)    */
                         (bUnitID << 8) | (uac->acif.iface->if_num),  /* wIndex - unit ID and interface number */
                         1,                          /* wLength - parameter block length  */
                         bAGC,                       /* parameter block                   */
                         &xfer_len, UAC_REQ_TIMEOUT);
    if(ret < 0)
        return ret;

    if(xfer_len != 1)
        return UAC_RET_DATA_LEN;

    return 0;
}

/// @cond HIDDEN_SYMBOLS

/**
 *  @brief    Find the alternative interface whose endpoint has the maximum packet size.
 *  @param[in]  ifcae     USB device interface
 *  @param[in]  dir       Endpoint bEndpointAddress[7] direction
 *  @param[in]  attr      Endpoint bmAttributes[1:0] transfer type
 *  @param[out] bAlternateSetting   The alternative interface number if found.
 *  @return   Success or not.
 *  @retval     0         Success.
 *  @retval    Otherwise  Failed
 */
int  usbh_uac_find_max_alt(IFACE_T *iface, uint8_t dir, uint8_t attr, uint8_t *bAlternateSetting)
{
    EP_INFO_T    *ep;
    uint8_t      i,  j;
    uint16_t     wMaxPacketSize = 0;

    for(i = 0; i < iface->num_alt; i++)
    {
        for(j = 0; j < iface->alt[i].ifd->bNumEndpoints; j++)
        {
            ep = &(iface->alt[i].ep[j]);    /* get endpoint                               */

            if(((ep->bEndpointAddress & EP_ADDR_DIR_MASK) != dir) ||
                    ((ep->bmAttributes & EP_ATTR_TT_MASK) != attr))
                continue;                   /* not interested endpoint                    */

            if(ep->wMaxPacketSize > wMaxPacketSize)
            {
                /* a better candidate endpoint found          */
                *bAlternateSetting = i;
                wMaxPacketSize = ep->wMaxPacketSize;
            }
        }
    }
    if(wMaxPacketSize == 0)
        return USBH_ERR_NOT_FOUND;

    return 0;
}

/**
 *  @brief    Find the alternative interface whose endpoint is the best choice.
 *  @param[in]  ifcae     USB device interface
 *  @param[in]  dir       Endpoint bEndpointAddress[7] direction
 *  @param[in]  attr      Endpoint bmAttributes[1:0] transfer type
 *  @param[in]  pkt_sz    Find the endpoint whose wMaxPacketSize is larger than <pkt_sz> and is most cross to <pkt_sz>.
 *  @param[out] bAlternateSetting   The alternative interface number if found.
 *  @return   Success or not.
 *  @retval     0         Success.
 *  @retval    Otherwise  Failed
 */
int  usbh_uac_find_best_alt(IFACE_T *iface, uint8_t dir, uint8_t attr, int pkt_sz, uint8_t *bAlternateSetting)
{
    EP_INFO_T    *ep;
    uint8_t      i,  j;
    uint16_t     wMaxPacketSize = 0xFFFF;

    for(i = 0; i < iface->num_alt; i++)
    {
        for(j = 0; j < iface->alt[i].ifd->bNumEndpoints; j++)
        {
            ep = &(iface->alt[i].ep[j]);    /* get endpoint                               */

            if(((ep->bEndpointAddress & EP_ADDR_DIR_MASK) != dir) ||
                    ((ep->bmAttributes & EP_ATTR_TT_MASK) != attr))
                continue;                   /* not interested endpoint                    */

            if((ep->wMaxPacketSize >= pkt_sz) && (ep->wMaxPacketSize < wMaxPacketSize))
            {
                /* a better candidate endpoint found          */
                *bAlternateSetting = i;
                wMaxPacketSize = ep->wMaxPacketSize;
            }
        }
    }
    if(wMaxPacketSize == 0xFFFF)
    {
        UAC_DBGMSG("Audio interface %d cannot find endpoint with wMaxPacketSize >= %d!\n", iface->if_num, pkt_sz);
        return USBH_ERR_NOT_FOUND;
    }
    return 0;
}

static void iso_in_irq(UTR_T *utr)
{
    UAC_DEV_T   *uac = (UAC_DEV_T *)utr->context;
    int         i, ret;

    /* We don't want to do anything if we are about to be removed! */
    if(!uac || !uac->udev)
        return;

    if(uac->asif_in.flag_streaming == 0)
        return;

    //UAC_DBGMSG("SF=%d, 0x%x\n", utr->iso_sf, (int)utr);

    utr->bIsoNewSched = 0;

    for(i = 0; i < IF_PER_UTR; i++)
    {
        if(utr->iso_status[i] == 0)
        {
            if((uac->func_au_in != NULL) && (utr->iso_xlen[i] > 0))
                uac->func_au_in(uac, utr->iso_buff[i], utr->iso_xlen[i]);
        }
        else
        {
//            UAC_DBGMSG("Iso %d err - %d\n", i, utr->iso_status[i]);
//            if((utr->iso_status[i] == USBH_ERR_NOT_ACCESS0) || (utr->iso_status[i] == USBH_ERR_NOT_ACCESS1))
//                utr->bIsoNewSched = 1;
        }
        utr->iso_xlen[i] = utr->ep->wMaxPacketSize;
    }

    /* schedule the following isochronous transfers */
    ret = usbh_iso_xfer(utr);
    if(ret < 0)
        UAC_DBGMSG("usbh_iso_xfer failed!\n");
}

/// @endcond HIDDEN_SYMBOLS

/**
 *  @brief  Start to receive audio data from UAC device. (Microphone)
 *  @param[in] uac        Audio Class device
 *  @param[in] func       Audio in callback function.
 *  @return   Success or not.
 *  @retval    0          Success
 *  @retval    Otherwise  Failed
 */
int usbh_uac_start_audio_in(UAC_DEV_T *uac, uint8_t u8Alt, UAC_CB_FUNC *func)
{
    UDEV_T       *udev = uac->udev;
    AS_IF_T      *asif = &uac->asif_in;
    IFACE_T      *iface = uac->asif_in.iface;
    ALT_IFACE_T  *aif;
    EP_INFO_T    *ep;
    UTR_T        *utr;
    uint8_t      *buff;
    int          i, j, ret;

    if(!uac || !iface)
        return UAC_RET_DEV_NOT_FOUND;

    if(asif->flag_streaming)
        return UAC_RET_IS_STREAMING;

    /*------------------------------------------------------------------------------------*/
    /*  Select the maximum packet size alternative interface                              */
    /*------------------------------------------------------------------------------------*/


    uac->func_au_in = func;

    ret = usbh_set_interface(iface, u8Alt);
    if(ret < 0)
    {
        UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", iface->if_num, u8Alt, ret);
        return ret;
    }

    ret = uac_parse_streaming_interface(uac, iface, u8Alt);
    if(ret < 0)
        return ret;

    /*------------------------------------------------------------------------------------*/
    /*  Find the endpoint                                                                 */
    /*------------------------------------------------------------------------------------*/
    asif->ep = NULL;
    aif = asif->iface->aif;
    for(i = 0; i < aif->ifd->bNumEndpoints; i++)
    {
        ep = &(aif->ep[i]);

        if(((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_IN) &&
                ((ep->bmAttributes & EP_ATTR_TT_MASK) == EP_ATTR_TT_ISO))
        {
            asif->ep = ep;
            UAC_DBGMSG("Audio in endpoint 0x%x found, size: %d\n", ep->bEndpointAddress, ep->wMaxPacketSize);
            break;
        }
    }
    if(asif->ep == NULL)
        return UAC_RET_FUNC_NOT_FOUND;
    ep = asif->ep;
    if(uac->version == 0x0200)
    {
        uac->xfer = uac->acif.mic_ch[u8Alt] * uac->acif.mic_samplingrate * uac->acif.mic_SubslotSize[u8Alt] / 8000;
//    printf("UAC_MICROPHONE xfer %d\n", uac->xfer);

        ep->wMaxPacketSize = uac->xfer;
		}
#ifdef UAC_DEBUG
    UAC_DBGMSG("Activated isochronous-in endpoint =>");
    usbh_dump_ep_info(ep);
#endif

    /*------------------------------------------------------------------------------------*/
    /*  Allocate isochronous in buffer                                                    */
    /*------------------------------------------------------------------------------------*/
    for(i = 0; i < NUM_UTR; i++)            /* allocate UTRs                              */
    {
        asif->utr[i] = alloc_utr(udev);     /* allocate UTR                               */
        if(asif->utr[i] == NULL)
        {
            ret = USBH_ERR_MEMORY_OUT;      /* memory allocate failed                     */
            goto err_out;                   /* abort                                      */
        }
    }

    buff = (uint8_t *)usbh_alloc_mem(ep->wMaxPacketSize * IF_PER_UTR * NUM_UTR);
    if(buff == NULL)
    {
        ret = USBH_ERR_MEMORY_OUT;          /* memory allocate failed                     */
        goto err_out;                       /* abort                                      */
    }

    for(i = 0; i < NUM_UTR; i++)            /* dispatch buffers                           */
    {
        /* divide buffer equally                      */
        utr = asif->utr[i];
        utr->buff = buff + (ep->wMaxPacketSize * IF_PER_UTR * i);
        utr->data_len = ep->wMaxPacketSize * IF_PER_UTR;
        for(j = 0; j < IF_PER_UTR; j++)
        {
            utr->iso_xlen[j] = ep->wMaxPacketSize;
            utr->iso_buff[j] = utr->buff + (ep->wMaxPacketSize * j);
        }
    }

    /*------------------------------------------------------------------------------------*/
    /*  Start UTRs                                                                        */
    /*------------------------------------------------------------------------------------*/

    asif->utr[0]->bIsoNewSched = 1;

    for(i = 0; i < NUM_UTR; i++)
    {
        utr = asif->utr[i];
        utr->context = uac;
        utr->ep = ep;
        utr->func = iso_in_irq;
        ret = usbh_iso_xfer(utr);
        if(ret < 0)
        {
            UAC_DBGMSG("Error - failed to start UTR %d isochronous-in transfer (%d)", i, ret);
            goto err_out;
        }
    }
    asif->flag_streaming = 1;
    uac->state = UAC_STATE_RUNNING;

    return UAC_RET_OK;

err_out:

    for(i = 0; i < NUM_UTR; i++)            /* quit all UTRs                              */
    {
        if(asif->utr[i])
            usbh_quit_utr(asif->utr[i]);
    }
    asif->flag_streaming = 0;
    /* free USB transfer buffer                   */
    if((asif->utr[0] != NULL) &&
            (asif->utr[0]->buff != NULL))
        usbh_free_mem(asif->utr[0]->buff, asif->utr[0]->data_len * NUM_UTR);

    for(i = 0; i < NUM_UTR; i++)            /* free all UTRs                              */
    {
        if(asif->utr[i])
            free_utr(asif->utr[i]);
        asif->utr[i] = NULL;
    }
    return ret;
}

/**
 *  @brief  Stop UAC device audio in data stream.
 *  @param[in] uac      Audio Class device
 *  @return   Success or not.
 *  @retval    0          Success
 *  @retval    Otherwise  Failed
 */
int usbh_uac_stop_audio_in(UAC_DEV_T *uac)
{
    AS_IF_T      *asif = &uac->asif_in;
    int          i, ret;

    asif->flag_streaming = 0;

    /* Set interface alternative settings */
    if(uac->state != UAC_STATE_DISCONNECTING)
    {
        ret = usbh_set_interface(asif->iface, 0);
        if(ret < 0)
        {
            UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", asif->iface->if_num, 0, ret);
        }
    }

    for(i = 0; i < NUM_UTR; i++)            /* stop all UTRs                              */
    {
        if(asif->utr[i])
            usbh_quit_utr(asif->utr[i]);
    }

    if((asif->utr[0] != NULL) &&
            (asif->utr[0]->buff != NULL))   /* free audio buffer                          */
        usbh_free_mem(asif->utr[0]->buff, asif->utr[0]->data_len * NUM_UTR);

    for(i = 0; i < NUM_UTR; i++)            /* free all UTRs                              */
    {
        if(asif->utr[i])
            free_utr(asif->utr[i]);
        asif->utr[i] = NULL;
    }

    if(uac->state != UAC_STATE_DISCONNECTING)
    {
        if((uac->asif_out.iface == NULL) || (uac->asif_out.flag_streaming == 0))
        {
            uac->state = UAC_STATE_READY;
        }
    }

    return UAC_RET_OK;
}

/// @cond HIDDEN_SYMBOLS

static void iso_out_irq(UTR_T *utr)
{
    UAC_DEV_T   *uac = (UAC_DEV_T *)utr->context;
    int         i, ret;

    /* We don't want to do anything if we are about to be removed! */
    if(!uac || !uac->udev)
        return;

    if(uac->asif_out.flag_streaming == 0)
        return;

    //UAC_DBGMSG("SF=%d, 0x%x\n", utr->iso_sf, (int)utr);

    utr->bIsoNewSched = 0;

    for(i = 0; i < IF_PER_UTR; i++)
    {
        if(utr->iso_status[i] != 0)
        {
//            UAC_DBGMSG("Iso %d err - %d\n", i, utr->iso_status[i]);
//            if((utr->iso_status[i] == USBH_ERR_NOT_ACCESS0) || (utr->iso_status[i] == USBH_ERR_NOT_ACCESS1))
//                utr->bIsoNewSched = 1;
        }
        utr->iso_xlen[i] = uac->func_au_out(uac, utr->iso_buff[i], utr->ep->wMaxPacketSize);
    }

    /* schedule the following isochronous transfers */
    ret = usbh_iso_xfer(utr);
    if(ret < 0)
        UAC_DBGMSG("usbh_iso_xfer failed!\n");
}

/// @endcond HIDDEN_SYMBOLS

/**
 *  @brief  Start to transmit audio data to UAC device. (Speaker)
 *  @param[in] uac      Audio Class device
 *  @param[in] func     Audio out call-back function. UAC driver call this function to get audio
 *                      out stream data from user application.
 *  @return   Success or not.
 *  @retval    0          Success
 *  @retval    Otherwise  Failed
 */
int usbh_uac_start_audio_out(UAC_DEV_T *uac, uint8_t u8Alt, UAC_CB_FUNC *func)
{
    UDEV_T       *udev = uac->udev;
    AS_IF_T      *asif = &uac->asif_out;
    IFACE_T      *iface = uac->asif_out.iface;
    ALT_IFACE_T  *aif;
    EP_INFO_T    *ep;
    UTR_T        *utr;
    uint8_t      *buff;
    int          i, j, ret;

    if(!uac || !func || !iface)
        return UAC_RET_DEV_NOT_FOUND;

    if(asif->flag_streaming)
        return UAC_RET_IS_STREAMING;

    uac->func_au_out = func;

    ret = usbh_set_interface(iface, u8Alt);
    if(ret < 0)
    {
        UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", iface->if_num, u8Alt, ret);
        return ret;
    }

    ret = uac_parse_streaming_interface(uac, iface, u8Alt);
    if(ret < 0)
        return ret;

    /*------------------------------------------------------------------------------------*/
    /*  Find the endpoint                                                                 */
    /*------------------------------------------------------------------------------------*/
    asif->ep = NULL;
    aif = asif->iface->aif;
    for(i = 0; i < aif->ifd->bNumEndpoints; i++)
    {
        ep = &(aif->ep[i]);

        if(((ep->bEndpointAddress & EP_ADDR_DIR_MASK) == EP_ADDR_DIR_OUT) &&
                ((ep->bmAttributes & EP_ATTR_TT_MASK) == EP_ATTR_TT_ISO))
        {
            asif->ep = ep;
            if(uac->version == 0x0200)
                ep->wMaxPacketSize = uac->xfer;

            UAC_DBGMSG("Audio out endpoint 0x%x found, size: %d\n", ep->bEndpointAddress, ep->wMaxPacketSize);
            break;
        }
    }
    if(asif->ep == NULL)
        return UAC_RET_FUNC_NOT_FOUND;
    ep = asif->ep;

    uac->xfer = uac->acif.speaker_ch[u8Alt] * uac->acif.speaker_samplingrate * uac->acif.speaker_SubslotSize[u8Alt] / 8000;
//    printf("UAC_SPEAKER xfer %d\n", uac->xfer);
#ifdef UAC_DEBUG
    UAC_DBGMSG("Activated isochronous-out endpoint =>");
    usbh_dump_ep_info(ep);
#endif

    /*------------------------------------------------------------------------------------*/
    /*  Allocate isochronous in buffer                                                    */
    /*------------------------------------------------------------------------------------*/
    for(i = 0; i < NUM_UTR; i++)            /* allocate UTRs                              */
    {
        asif->utr[i] = alloc_utr(udev);     /* allocate UTR                               */
        if(asif->utr[i] == NULL)
        {
            ret = USBH_ERR_MEMORY_OUT;      /* memory allocate failed                     */
            goto err_out;                   /* abort                                      */
        }
    }

    buff = (uint8_t *)usbh_alloc_mem(ep->wMaxPacketSize * IF_PER_UTR * NUM_UTR);
    if(buff == NULL)
    {
        ret = USBH_ERR_MEMORY_OUT;          /* memory allocate failed                     */
        goto err_out;                       /* abort                                      */
    }

    for(i = 0; i < NUM_UTR; i++)            /* dispatch buffers                           */
    {
        /* divide buffer equally                      */
        asif->utr[i]->buff = buff + (ep->wMaxPacketSize * IF_PER_UTR * i);
        asif->utr[i]->data_len = ep->wMaxPacketSize * IF_PER_UTR;
    }

    /*------------------------------------------------------------------------------------*/
    /*  Start UTRs                                                                        */
    /*------------------------------------------------------------------------------------*/

    asif->utr[0]->bIsoNewSched = 1;

    for(i = 0; i < NUM_UTR; i++)
    {
        utr = asif->utr[i];
        utr->context = uac;
        utr->ep = ep;
        utr->func = iso_out_irq;

        for(j = 0; j < IF_PER_UTR; j++)     /* get audio out data from user               */
        {
            utr->iso_buff[j] = utr->buff + (ep->wMaxPacketSize * j);
            utr->iso_xlen[j] = uac->func_au_out(uac, utr->iso_buff[j], ep->wMaxPacketSize);
        }

        ret = usbh_iso_xfer(utr);
        if(ret < 0)
        {
            UAC_DBGMSG("Error - failed to start UTR %d isochronous-in transfer (%d)", i, ret);
            goto err_out;
        }
    }
    asif->flag_streaming = 1;
    uac->state = UAC_STATE_RUNNING;

    return UAC_RET_OK;

err_out:

    for(i = 0; i < NUM_UTR; i++)            /* quit all UTRs                              */
    {
        if(asif->utr[i])
            usbh_quit_utr(asif->utr[i]);
    }
    asif->flag_streaming = 0;

    if((asif->utr[0] != NULL) &&            /* free USB transfer buffer                   */
            (asif->utr[0]->buff != NULL))
        usbh_free_mem(asif->utr[0]->buff, asif->utr[0]->data_len * NUM_UTR);

    for(i = 0; i < NUM_UTR; i++)            /* free all UTRs                              */
    {
        if(asif->utr[i])
            free_utr(asif->utr[i]);
        asif->utr[i] = NULL;
    }
    return ret;
}

/**
 *  @brief  Stop UAC device audio out data stream.
 *  @param[in] uac      Audio Class device
 *  @return   Success or not.
 *  @retval    0          Success
 *  @retval    Otherwise  Failed
 */
int usbh_uac_stop_audio_out(UAC_DEV_T *uac)
{
    AS_IF_T      *asif = &uac->asif_out;
    int          i, ret;

    /* Set interface alternative settings */
    if(uac->state != UAC_STATE_DISCONNECTING)
    {
        ret = usbh_set_interface(asif->iface, 0);
        if(ret < 0)
        {
            UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", asif->iface->if_num, 0, ret);
        }
    }

    for(i = 0; i < NUM_UTR; i++)            /* stop all UTRs                              */
    {
        if(asif->utr[i])
            usbh_quit_utr(asif->utr[i]);
    }

    if((asif->utr[0] != NULL) &&
            (asif->utr[0]->buff != NULL))   /* free audio buffer                          */
        usbh_free_mem(asif->utr[0]->buff, asif->utr[0]->data_len * NUM_UTR);

    for(i = 0; i < NUM_UTR; i++)            /* free all UTRs                              */
    {
        if(asif->utr[i])
            free_utr(asif->utr[i]);
        asif->utr[i] = NULL;
    }

    if(uac->state != UAC_STATE_DISCONNECTING)
    {
        if((uac->asif_in.iface == NULL) || (uac->asif_in.flag_streaming == 0))
        {
            uac->state = UAC_STATE_READY;
        }
    }
    asif->flag_streaming = 0;

    return UAC_RET_OK;
}

/**
 *  @brief   Open an connected UAC device.
 *  @param[in] uac        Audio Class device
 *  @return   Success or not.
 *  @retval    0          Success
 *  @retval    Otherwise  Failed
 */
int usbh_uac_open(UAC_DEV_T *uac)
{
    IFACE_T      *iface;
    uint8_t      bAlternateSetting;
    int          ret, i;

    /*------------------------------------------------------------------------------------*/
    /*  Select the maximum packet size alternative interface                              */
    /*------------------------------------------------------------------------------------*/
    iface = uac->asif_in.iface;

    if(iface != NULL)
    {
        for(i=0;i<iface->num_alt;i++)
        {
            ret = uac_parse_streaming_interface(uac, iface, i);
            if(ret < 0)
                return ret;
            uac->acif.mic_ch[i] = uac->asif_in.bNrChannels;  
            uac->acif.mic_SubslotSize[i] = uac->asif_in.bSubslotSize;  
        }
        if(usbh_uac_find_max_alt(iface, EP_ADDR_DIR_IN, EP_ATTR_TT_ISO, &bAlternateSetting) != 0)
            return UAC_RET_FUNC_NOT_FOUND;

        ret = usbh_set_interface(iface, bAlternateSetting);
        if(ret < 0)
        {
            UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", iface->if_num, bAlternateSetting, ret);
            return ret;
        }
    }

    /*------------------------------------------------------------------------------------*/
    /*  Select the maximum packet size alternative interface                              */
    /*------------------------------------------------------------------------------------*/
    iface = uac->asif_out.iface;

    if(iface != NULL)
    {
        for(i=0;i<iface->num_alt;i++)
        {
            ret = uac_parse_streaming_interface(uac, iface, i);
            if(ret < 0)
                return ret;
            uac->acif.speaker_ch[i] = uac->asif_out.bNrChannels;  
            uac->acif.speaker_SubslotSize[i] = uac->asif_out.bSubslotSize;  
        }
        if(usbh_uac_find_max_alt(iface, EP_ADDR_DIR_OUT, EP_ATTR_TT_ISO, &bAlternateSetting) != 0)
            return UAC_RET_FUNC_NOT_FOUND;

        ret = usbh_set_interface(iface, bAlternateSetting);
        if(ret < 0)
        {
            UAC_ERRMSG("Failed to set interface %d, %d! (%d)\n", iface->if_num, bAlternateSetting, ret);
            return ret;
        }
    }
    return 0;
}

/*@}*/ /* end of group USBH_EXPORTED_FUNCTIONS */

/*@}*/ /* end of group USBH_Library */

/*@}*/ /* end of group LIBRARY */
