/*******************************************************
 
 bdimad_dev.c

 Author: bdSound Development Team (techsupport@bdsound.com)
 
 Date: 12/03/2015
 Version 2.0.0 rev.1618

 Copyright (c) 2015 bdSound s.r.l. (www.bdsound.com)
 All Rights Reserved.
 
 *******************************************************/

#include <pjmedia-audiodev/audiodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/string.h>
#include <pj/unicode.h>
#include <pjmedia/errno.h>

#if defined(PJMEDIA_AUDIO_DEV_HAS_BDIMAD) && PJMEDIA_AUDIO_DEV_HAS_BDIMAD != 0

#include <math.h>
#include <wchar.h>

/**************************
*           bdIMAD
***************************/
#include  "../../third_party/bdsound/include/bdimad.h"

/* Maximum supported audio devices */
#define BD_IMAD_MAX_DEV_COUNT               100                                
/* Maximum supported device name */
#define BD_IMAD_MAX_DEV_LENGTH_NAME         256                                
/* Only mono mode */
#define BD_IMAD_MAX_CHANNELS                1                                  
/* Frequency default value (admitted 8000 Hz, 16000 Hz, 32000 Hz, 44100 Hz and 48000 Hz) */
#define BD_IMAD_DEFAULT_FREQ                48000                              
/* Default milliseconds per buffer */
#define BD_IMAD_MSECOND_PER_BUFFER          16                                 
/* Only for supported systems */
#define BD_IMAD_STARTING_INPUT_VOLUME       50                                 
/* Only for supported systems */
#define BD_IMAD_STARTING_OUTPUT_VOLUME      100                                
/* Diagnostic Enable/Disable */
#define BD_IMAD_DIAGNOSTIC                  BD_IMAD_DIAGNOSTIC_DISABLE

/* Diagnostic folder path */ 
#define BD_IMAD_DIAGNOSTIC_PATH  "/mnt/sdcard/MUSIC/"

static wchar_t bdImadPjDiagnosticFolderPath[200];                          

#define THIS_FILE            "bdimad_dev.c"

/* BD device info */
struct bddev_info
{
    pjmedia_aud_dev_info    info;
    unsigned            deviceId;
};

/* BD factory */
struct bd_factory
{
    pjmedia_aud_dev_factory  base;
    pj_pool_t                *base_pool;
    pj_pool_t                *pool;
    pj_pool_factory          *pf;
    unsigned                 dev_count;
    struct bddev_info        *dev_info;
};

/* Sound stream. */
struct bd_stream
{
    /** Base stream.                    */
    pjmedia_aud_stream   base;                
    /** Settings.                       */
    pjmedia_aud_param    param;               
    /** Memory pool.                    */
    pj_pool_t           *pool;                
                                        
    /** Capture callback.               */
    pjmedia_aud_rec_cb   rec_cb;              
    /** Playback callback.              */
    pjmedia_aud_play_cb  play_cb;             
    /** Application data.               */
    void                *user_data;           
                                        
    /** Frame format                    */
    pjmedia_format_id    fmt_id;              
    /** Silence pattern                 */
    pj_uint8_t           silence_char;    
    /** Bytes per frame                 */
    unsigned             bytes_per_frame;
    /** Samples per frame               */
    unsigned             samples_per_frame;
    /** Channel count                    */
    int                  channel_count;
                                        
    /** Extended frame buffer           */
    pjmedia_frame_ext   *xfrm;                
    /** Total ext frm size              */
    unsigned             xfrm_size;           
       
    /** Check running variable          */
    int                  go;                

    /** Timestamp iterator for capture  */
    pj_timestamp         timestampCapture;    
    /** Timestamp iterator for playback */
    pj_timestamp         timestampPlayback;   

    /** bdIMAD current session instance */
    bdIMADpj             bdIMADpjInstance;    
    /** bdIMAD current session settings */
    bdIMADpj_Setting_t  *bdIMADpjSettingsPtr; 
    /** bdIMAD current session warnings */
    bdIMADpj_Warnings_t *bdIMADpjWarningPtr;

    pj_bool_t            quit_flag;

    pj_bool_t            rec_thread_exited;
    pj_bool_t            rec_thread_initialized;
    pj_thread_desc       rec_thread_desc;
    pj_thread_t         *rec_thread;

    pj_bool_t            play_thread_exited;
    pj_bool_t            play_thread_initialized;
    pj_thread_desc       play_thread_desc;
    pj_thread_t         *play_thread;

    /* Sometime the record callback does not return framesize as configured
     * (e.g: in OSS), while this module must guarantee returning framesize
     * as configured in the creation settings. In this case, we need a buffer 
     * for the recorded samples.
     */
    pj_int16_t          *rec_buf;
    unsigned             rec_buf_count;

    /* Sometime the player callback does not request framesize as configured
     * (e.g: in Linux OSS) while sound device will always get samples from 
     * the other component as many as configured samples_per_frame. 
     */
    pj_int16_t          *play_buf;
    unsigned             play_buf_count;
};

/* Prototypes */

// pjmedia_aud_dev_factory_op
static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
static unsigned    factory_get_dev_count(pjmedia_aud_dev_factory *f);
static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
                                        unsigned index,    
                                        pjmedia_aud_dev_info *info);
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, 
                                         unsigned index, 
                                         pjmedia_aud_param *param);
static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f, 
                                         const pjmedia_aud_param *param, 
                                         pjmedia_aud_rec_cb rec_cb, 
                                         pjmedia_aud_play_cb play_cb, 
                                         void *user_data, 
                                         pjmedia_aud_stream **p_aud_strm);
static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);

// pjmedia_aud_stream_op
static pj_status_t stream_get_param(pjmedia_aud_stream *strm, 
                                    pjmedia_aud_param *param);
static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, 
                                  pjmedia_aud_dev_cap cap, 
                                  void *value);
static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, 
                                  pjmedia_aud_dev_cap cap, 
                                  const void *value);
static pj_status_t stream_start(pjmedia_aud_stream *strm);
static pj_status_t stream_stop(pjmedia_aud_stream *strm);
static pj_status_t stream_destroy(pjmedia_aud_stream *strm);

/* End Prototypes */

/* Operations */
static pjmedia_aud_dev_factory_op factory_op =
{
    &factory_init,
    &factory_destroy,
    &factory_get_dev_count,
    &factory_get_dev_info,
    &factory_default_param,
    &factory_create_stream,
    &factory_refresh
};

static pjmedia_aud_stream_op stream_op = 
{
    &stream_get_param,
    &stream_get_cap,
    &stream_set_cap,
    &stream_start,
    &stream_stop,
    &stream_destroy
};

/* End Operations */

/* Utility functions */

char* BD_IMAD_PJ_WCHARtoCHAR(wchar_t *orig)
{
    size_t origsize = wcslen(orig)+1;
    const size_t newsize = origsize*sizeof(wchar_t);
    char *nstring = (char*)calloc(newsize, sizeof(char));
    wcstombs(nstring, orig, newsize);
    return nstring;
}

#ifdef __cplusplus
extern "C" {
#endif
void manage_code(const unsigned char * pCode, unsigned char *pMsg1, 
                 unsigned char * pMsg2);
#ifdef __cplusplus
}
#endif

/* End Utility functions */

/****************************************************************************
* Factory operations
*/

/* Init BDIMAD audio driver */
pjmedia_aud_dev_factory* pjmedia_bdimad_factory(pj_pool_factory *pf)
{
    struct bd_factory *f;
    pj_pool_t *pool;

    pool = pj_pool_create(pf, "BDIMAD_DRIVER", 1000, 1000, NULL);
    f = PJ_POOL_ZALLOC_T(pool, struct bd_factory);
    f->pf = pf;
    f->base_pool = pool;
    f->base.op = &factory_op;
    f->pool = NULL;
    f->dev_count = 0;
    f->dev_info = NULL;

    return &f->base;
}

/* API: init factory */
static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
{
    pj_status_t ret = factory_refresh(f);
    if (ret != PJ_SUCCESS) return ret;

    PJ_LOG(4, (THIS_FILE, "BDIMAD initialized"));

    return PJ_SUCCESS;
}

/* API: refresh the device list */
static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
{    
    struct bd_factory *wf = (struct bd_factory*)f;
    unsigned int i = 0;
    wchar_t *deviceNamep=NULL;
    wchar_t captureDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME];
    unsigned int captureDeviceCount = 0;
    wchar_t playbackDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME];
    unsigned int playbackDeviceCount = 0;


    if(wf->pool != NULL) {
        pj_pool_release(wf->pool);
        wf->pool = NULL;
    }

    // Enumerate capture sound devices
    while(bdIMADpj_getDeviceName(BD_IMAD_CAPTURE_DEVICES, &deviceNamep) != 
          BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY)
    {
        wcscpy(captureDevName[captureDeviceCount], deviceNamep);
        captureDeviceCount++;
    }

    // Enumerate playback sound devices
    while(bdIMADpj_getDeviceName(BD_IMAD_PLAYBACK_DEVICES, &deviceNamep) != 
          BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY)
    {
        wcscpy(playbackDevName[playbackDeviceCount], deviceNamep);
        playbackDeviceCount++;
    }
    
    // Set devices info
    wf->dev_count = captureDeviceCount + playbackDeviceCount;
    wf->pool = pj_pool_create(wf->pf, "BD_IMAD_DEVICES", 1000, 1000, NULL);
    wf->dev_info = (struct bddev_info*)pj_pool_calloc(wf->pool, wf->dev_count, 
                                                     sizeof(struct bddev_info));
    
    // Capture device properties
    for(i=0;i<captureDeviceCount;i++) {
        wf->dev_info[i].deviceId = i;
                bdIMADpj_getDeviceCapabilities(BD_IMAD_CAPTURE_DEVICES,&wf->dev_info[i].info.caps);
        wf->dev_info[i].info.default_samples_per_sec = BD_IMAD_DEFAULT_FREQ;
        pj_ansi_strxcpy(wf->dev_info[i].info.driver, "BD_IMAD",
                        sizeof(wf->dev_info[i].info.driver));
        wf->dev_info[i].info.ext_fmt_cnt = 0;
        wf->dev_info[i].info.input_count = BD_IMAD_MAX_CHANNELS;
        wf->dev_info[i].info.output_count = 0;
        pj_ansi_strxcpy(wf->dev_info[i].info.name, 
                        BD_IMAD_PJ_WCHARtoCHAR(captureDevName[i]),
                        sizeof(wf->dev_info[i].info.name));
        wf->dev_info[i].info.routes = 0;
    }

    // Playback device properties
    for(i=0;i<playbackDeviceCount;i++) {
        wf->dev_info[captureDeviceCount+i].deviceId = captureDeviceCount+i;
                bdIMADpj_getDeviceCapabilities(BD_IMAD_PLAYBACK_DEVICES,&wf->dev_info[captureDeviceCount+i].info.caps);
        wf->dev_info[captureDeviceCount+i].info.default_samples_per_sec = 
                                BD_IMAD_DEFAULT_FREQ;
        pj_ansi_strxcpy(wf->dev_info[captureDeviceCount+i].info.driver, "BD_IMAD",
                        sizeof(wf->dev_info[captureDeviceCount+i].info.driver));
        wf->dev_info[captureDeviceCount+i].info.ext_fmt_cnt = 0;
        wf->dev_info[captureDeviceCount+i].info.input_count = 0;
        wf->dev_info[captureDeviceCount+i].info.output_count = 
                                BD_IMAD_MAX_CHANNELS;
        pj_ansi_strxcpy(wf->dev_info[captureDeviceCount+i].info.name, 
                        BD_IMAD_PJ_WCHARtoCHAR(playbackDevName[i]),
                        sizeof(wf->dev_info[captureDeviceCount+i].info.name));
        wf->dev_info[captureDeviceCount+i].info.routes = 0;
    }

    PJ_LOG(4, (THIS_FILE, "BDIMAD found %d devices:", wf->dev_count));
    for(i=0; i<wf->dev_count; i++) {
        PJ_LOG(4,   
               (THIS_FILE, " dev_id %d: %s  (in=%d, out=%d)", 
               i,
               wf->dev_info[i].info.name,
               wf->dev_info[i].info.input_count,
               wf->dev_info[i].info.output_count));
    }    
    return PJ_SUCCESS;
}

/* API: destroy factory */
static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
{
    struct bd_factory *wf = (struct bd_factory*)f;
    pj_pool_t *pool = wf->base_pool;

    pj_pool_release(wf->pool);
    wf->base_pool = NULL;
    pj_pool_release(pool);

    return PJ_SUCCESS;
}

/* API: get number of devices */
static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
{
    struct bd_factory *wf = (struct bd_factory*)f;
    return wf->dev_count;
}

/* API: get device info */
static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, 
                                        unsigned index,
                                        pjmedia_aud_dev_info *info)
{
    struct bd_factory *wf = (struct bd_factory*)f;

    PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV);

    pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info));

    return PJ_SUCCESS;
}

/* API: create default device parameter */
static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
                                         unsigned index,
                                         pjmedia_aud_param *param)
{
    struct bd_factory *wf = (struct bd_factory*)f;
    struct bddev_info *di = &wf->dev_info[index];

    pj_bzero(param, sizeof(*param));
    if (di->info.input_count && di->info.output_count) {
        param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
        param->rec_id = index;
        param->play_id = index;
        param->channel_count = di->info.output_count;
    } else if(di->info.input_count) {
        param->dir = PJMEDIA_DIR_CAPTURE;
        param->rec_id = index;
        param->play_id = PJMEDIA_AUD_INVALID_DEV;
        param->channel_count = di->info.input_count;
    } else if(di->info.output_count) {
        param->dir = PJMEDIA_DIR_PLAYBACK;
        param->play_id = index;
        param->rec_id = PJMEDIA_AUD_INVALID_DEV;
        param->channel_count = di->info.output_count;
    } else {
        return PJMEDIA_EAUD_INVDEV;
    }

    param->bits_per_sample = BD_IMAD_BITS_X_SAMPLE;
    param->clock_rate = di->info.default_samples_per_sec;
    param->flags = di->info.caps;
    param->samples_per_frame = di->info.default_samples_per_sec * 
                               param->channel_count * 
                               BD_IMAD_MSECOND_PER_BUFFER / 
                               1000;    

    if(di->info.caps & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) {
        param->input_vol = BD_IMAD_STARTING_INPUT_VOLUME;
    }

    if(di->info.caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
        param->output_vol = BD_IMAD_STARTING_OUTPUT_VOLUME;
    }

    if(di->info.caps & PJMEDIA_AUD_DEV_CAP_EC) {
        param->ec_enabled = PJ_TRUE;
    }

        if(di->info.caps & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
                param->output_route = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
    }

    return PJ_SUCCESS;
}

/* callbacks to set data */
void bdimad_CaptureCallback(void *buffer, int samples, void *user_data)
{
    pj_status_t status = PJ_SUCCESS;
    pjmedia_frame frame;
    unsigned nsamples;    

    struct bd_stream *strm = (struct bd_stream*)user_data;    

    if(!strm->go) 
        goto on_break;

    /* Known cases of callback's thread:
     * - The thread may be changed in the middle of a session, e.g: in MacOS 
     *   it happens when plugging/unplugging headphone.
     * - The same thread may be reused in consecutive sessions. The first
     *   session will leave TLS set, but release the TLS data address,
     *   so the second session must re-register the callback's thread.
     */
    if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered()) 
    {
        pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc));
        status = pj_thread_register("bd_CaptureCallback", 
                                    strm->rec_thread_desc, 
                                    &strm->rec_thread);
        if (status != PJ_SUCCESS)
            goto on_break;

        strm->rec_thread_initialized = 1;
        PJ_LOG(5,(THIS_FILE, "Recorder thread started"));
    }
    
    /* Calculate number of samples we've got */
    nsamples = samples * strm->channel_count + strm->rec_buf_count;

    /*
    RECORD
    */
    if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
        if (nsamples >= strm->samples_per_frame) {
            /* If buffer is not empty, combine the buffer with the just incoming
             * samples, then call put_frame.
             */
            if (strm->rec_buf_count) {
                unsigned chunk_count = 0;               

                chunk_count = strm->samples_per_frame - strm->rec_buf_count;
                pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
                                     (pj_int16_t*)buffer, chunk_count);

                frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
                frame.buf = (void*) strm->rec_buf;
                frame.size = strm->bytes_per_frame;
                frame.timestamp.u64 = strm->timestampCapture.u64;
                frame.bit_info = 0;

                status = (*strm->rec_cb)(strm->user_data, &frame);

                buffer = (pj_int16_t*) buffer + chunk_count;
                nsamples -= strm->samples_per_frame;
                strm->rec_buf_count = 0;
                strm->timestampCapture.u64 += strm->samples_per_frame /
                                              strm->channel_count;
            }

            /* Give all frames we have */
            while (nsamples >= strm->samples_per_frame && status == 0) {                
                frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
                frame.buf = (void*) buffer;
                frame.size = strm->bytes_per_frame;
                frame.timestamp.u64 = strm->timestampCapture.u64;
                frame.bit_info = 0;

                status = (*strm->rec_cb)(strm->user_data, &frame);

                buffer = (pj_int16_t*) buffer + strm->samples_per_frame;
                nsamples -= strm->samples_per_frame;
                strm->timestampCapture.u64 += strm->samples_per_frame /
                                              strm->channel_count;
            }

            /* Store the remaining samples into the buffer */
            if (nsamples && status == 0) {
                strm->rec_buf_count = nsamples;
                pjmedia_copy_samples(strm->rec_buf, (pj_int16_t*)buffer, 
                                     nsamples);
            }

        } else {
            /* Not enough samples, let's just store them in the buffer */
            pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count,
                                 (pj_int16_t*)buffer, 
                                 samples * strm->channel_count);
            strm->rec_buf_count += samples * strm->channel_count;       
        }
    }  else {
        pj_assert(!"Frame type not supported");
    }
    
    strm->timestampCapture.u64 += strm->param.samples_per_frame / 
                                  strm->param.channel_count;

    if (status==0)
        return;

on_break:
    strm->rec_thread_exited = 1;    
}

/* callbacks to get data */
int bdimad_PlaybackCallback(void *buffer, int samples, void *user_data)
{
    pj_status_t status = PJ_SUCCESS;
    pjmedia_frame frame;
    struct bd_stream *strm = (struct bd_stream*)user_data;
    unsigned nsamples_req = samples * strm->channel_count;

    if(!strm->go) 
        goto on_break;

    /* Known cases of callback's thread:
     * - The thread may be changed in the middle of a session, e.g: in MacOS 
     *   it happens when plugging/unplugging headphone.
     * - The same thread may be reused in consecutive sessions. The first
     *   session will leave TLS set, but release the TLS data address,
     *   so the second session must re-register the callback's thread.
     */
    if (strm->play_thread_initialized == 0 || !pj_thread_is_registered()) 
    {
        pj_bzero(strm->play_thread_desc, sizeof(pj_thread_desc));
        status = pj_thread_register("bd_PlaybackCallback", 
                                    strm->play_thread_desc,
                                    &strm->play_thread);
        if (status != PJ_SUCCESS)
            goto on_break;

        strm->play_thread_initialized = 1;
        PJ_LOG(5,(THIS_FILE, "Player thread started"));
    }

    /*
    PLAY
    */
    if(strm->fmt_id == PJMEDIA_FORMAT_L16) {
        /* Check if any buffered samples */
        if (strm->play_buf_count) {
            /* samples buffered >= requested by sound device */
            if (strm->play_buf_count >= nsamples_req) {
                pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, 
                                     nsamples_req);
                strm->play_buf_count -= nsamples_req;
                pjmedia_move_samples(strm->play_buf, 
                                     strm->play_buf + nsamples_req,
                                     strm->play_buf_count);             

                return nsamples_req;
            }

            /* samples buffered < requested by sound device */
            pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, 
                                 strm->play_buf_count);
            nsamples_req -= strm->play_buf_count;
            buffer = (pj_int16_t*)buffer + strm->play_buf_count;
            strm->play_buf_count = 0;
        }

        /* Fill output buffer as requested */
        while (nsamples_req && status == 0) {
            if (nsamples_req >= strm->samples_per_frame) {              
                frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
                frame.buf = buffer;
                frame.size = strm->bytes_per_frame;
                frame.timestamp.u64 = strm->timestampPlayback.u64;
                frame.bit_info = 0;

                status = (*strm->play_cb)(strm->user_data, &frame);
                if (status != PJ_SUCCESS)
                    return 0;

                if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
                    pj_bzero(frame.buf, frame.size);

                nsamples_req -= strm->samples_per_frame;
                buffer = (pj_int16_t*)buffer + strm->samples_per_frame;
            } else {            
                frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
                frame.buf = strm->play_buf;
                frame.size = strm->bytes_per_frame;
                frame.timestamp.u64 = strm->timestampPlayback.u64;
                frame.bit_info = 0;

                status = (*strm->play_cb)(strm->user_data, &frame);
                if (status != PJ_SUCCESS)
                    return 0;

                if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
                    pj_bzero(frame.buf, frame.size);

                pjmedia_copy_samples((pj_int16_t*)buffer, strm->play_buf, 
                                     nsamples_req);
                strm->play_buf_count = strm->samples_per_frame - 
                                       nsamples_req;
                pjmedia_move_samples(strm->play_buf, 
                                     strm->play_buf+nsamples_req,
                                     strm->play_buf_count);
                nsamples_req = 0;
            }

            strm->timestampPlayback.u64 += strm->samples_per_frame /
                                           strm->channel_count;
        }
    } else {
        pj_assert(!"Frame type not supported");
    }

    if(status != PJ_SUCCESS) {
        return 0;
    }

    strm->timestampPlayback.u64 += strm->param.samples_per_frame / 
                                   strm->param.channel_count;

    if (status == 0)
        return samples;

on_break:
    strm->play_thread_exited = 1;
    return 0;
}

/* Internal: Get format name */
static const char *get_fmt_name(pj_uint32_t id)
{
    static char name[8];

    if (id == PJMEDIA_FORMAT_L16)
        return "PCM";
    pj_memcpy(name, &id, 4);
    name[4] = '\0';
    return name;
}

/* Internal: create BD device. */
static pj_status_t init_streams(struct bd_factory *wf,
                                struct bd_stream *strm,
                                const pjmedia_aud_param *prm)
{
    unsigned ptime;
    enum bdIMADpj_Status errorInitAEC;
    wchar_t *deviceNamep=NULL;
    wchar_t captureDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME];
    int captureDeviceCount = 0;
    wchar_t playbackDevName[BD_IMAD_MAX_DEV_COUNT][BD_IMAD_MAX_DEV_LENGTH_NAME];
    int playbackDeviceCount = 0;

    PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL);
    PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL);

    ptime = prm->samples_per_frame * 
            1000 / 
            (prm->clock_rate * prm->channel_count);
    strm->bytes_per_frame = (prm->clock_rate * 
                            ((prm->channel_count * prm->bits_per_sample) / 8)) * 
                             ptime / 
                             1000;
    strm->timestampCapture.u64 = 0;
    strm->timestampPlayback.u64 = 0;

    //BD_IMAD_PJ
    bdIMADpj_CreateStructures(&strm->bdIMADpjSettingsPtr, 
                              &strm->bdIMADpjWarningPtr);
    
    strm->bdIMADpjSettingsPtr->FrameSize_ms = ptime;
    strm->bdIMADpjSettingsPtr->DiagnosticEnable = BD_IMAD_DIAGNOSTIC;
    mbstowcs(bdImadPjDiagnosticFolderPath, BD_IMAD_DIAGNOSTIC_PATH, strlen(BD_IMAD_DIAGNOSTIC_PATH));
    strm->bdIMADpjSettingsPtr->DiagnosticFolderPath = bdImadPjDiagnosticFolderPath;
    strm->bdIMADpjSettingsPtr->validate = (void *)manage_code;

    if(prm->clock_rate != 8000 && prm->clock_rate != 16000 
           && prm->clock_rate != 32000 && prm->clock_rate != 44100 && prm->clock_rate != 48000) {
        PJ_LOG(4, (THIS_FILE, 
                   "BDIMAD support 8000 Hz, 16000 Hz, 32000 Hz, 44100 Hz and 48000 Hz "
                   "frequency."));
    }
    strm->bdIMADpjSettingsPtr->SamplingFrequency = prm->clock_rate;

    if(prm->channel_count > BD_IMAD_MAX_CHANNELS) {
        PJ_LOG(4, (THIS_FILE, 
                   "BDIMAD doesn't support a number of channels upper than %d.", 
                   BD_IMAD_MAX_CHANNELS));
    }
    
    // Enumerate capture sound devices
    while(bdIMADpj_getDeviceName(BD_IMAD_CAPTURE_DEVICES, &deviceNamep) != 
          BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY)
    {
        wcscpy(captureDevName[captureDeviceCount], deviceNamep);
        captureDeviceCount++;
    }
    strm->bdIMADpjSettingsPtr->CaptureDevice = captureDevName[(int)prm->rec_id];

    // Enumerate playback sound devices
    while(bdIMADpj_getDeviceName(BD_IMAD_PLAYBACK_DEVICES, &deviceNamep) != 
          BD_PJ_ERROR_IMAD_DEVICE_LIST_EMPTY)
    {
        wcscpy(playbackDevName[playbackDeviceCount], deviceNamep);
        playbackDeviceCount++;
    }
    strm->bdIMADpjSettingsPtr->PlayDevice = 
                    playbackDevName[(int)(prm->play_id-captureDeviceCount)];

    strm->bdIMADpjSettingsPtr->cb_emptyCaptureBuffer = &bdimad_CaptureCallback;
    strm->bdIMADpjSettingsPtr->cb_emptyCaptureBuffer_user_data = (void*)strm;
    strm->bdIMADpjSettingsPtr->cb_fillPlayBackBuffer = &bdimad_PlaybackCallback;
    strm->bdIMADpjSettingsPtr->cb_fillPlayBackBuffer_user_data = (void*)strm;

    if(strm->bdIMADpjInstance != NULL) 
        bdIMADpj_FreeAEC(&strm->bdIMADpjInstance);
    strm->bdIMADpjInstance = NULL;

    errorInitAEC = bdIMADpj_InitAEC(&strm->bdIMADpjInstance, 
                                    &strm->bdIMADpjSettingsPtr, 
                                    &strm->bdIMADpjWarningPtr);

    {
        int auxInt = (prm->ec_enabled == PJ_TRUE ? 1 : 0);
        bdIMADpj_setParameter(strm->bdIMADpjInstance, 
                              BD_PARAM_IMAD_PJ_AEC_ENABLE, 
                              &auxInt);
        auxInt = 1;
        //Mic control On by default
        bdIMADpj_setParameter(strm->bdIMADpjInstance, 
                              BD_PARAM_IMAD_PJ_MIC_CONTROL_ENABLE, 
                              &auxInt);
                
                // Enable GUI Socket Communication [default->disabled]
                bdIMADpj_enableGuiSocketCommunication(strm->bdIMADpjInstance,27000,0);
    }

    if(errorInitAEC != BD_PJ_OK && 
       errorInitAEC != BD_PJ_WARN_BDIMAD_WARNING_ASSERTED) 
    {
        return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(errorInitAEC);
    }
    
    return PJ_SUCCESS;
}

/**************************************** 
            API: create stream 
*****************************************/
static pj_status_t stream_stopBDIMAD(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;
    pj_status_t status = PJ_SUCCESS;
    int i, err = 0;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    strm->go = 0;    
    
    for (i=0; !strm->rec_thread_exited && i<100; ++i)
        pj_thread_sleep(10);
    for (i=0; !strm->play_thread_exited && i<100; ++i)
        pj_thread_sleep(10);

    pj_thread_sleep(1);

    PJ_LOG(5,(THIS_FILE, "Stopping stream.."));

    strm->play_thread_initialized = 0;
    strm->rec_thread_initialized = 0;

    PJ_LOG(5,(THIS_FILE, "Done, status=%d", err));

    return status;
}

static pj_status_t stream_stop(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if(strm->bdIMADpjInstance != NULL) {
        return stream_stopBDIMAD(s);
    } else {
        return PJMEDIA_EAUD_ERR;
    }
}

static pj_status_t stream_destroyBDIMAD(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;
    int i = 0;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    stream_stopBDIMAD(s);

    // DeInit BDIMAD
    bdIMADpj_FreeAEC(&strm->bdIMADpjInstance); 
        PJ_LOG(4, (THIS_FILE, "Free AEC"));

    bdIMADpj_FreeStructures(&strm->bdIMADpjSettingsPtr, 
                            &strm->bdIMADpjWarningPtr);
    PJ_LOG(4, (THIS_FILE, "Free AEC Structure"));

    strm->bdIMADpjInstance = NULL;
    strm->bdIMADpjSettingsPtr = NULL;
    strm->bdIMADpjWarningPtr = NULL;    

    strm->quit_flag = 1;
    for (i=0; !strm->rec_thread_exited && i<100; ++i) {
        pj_thread_sleep(1);
    }
    for (i=0; !strm->play_thread_exited && i<100; ++i) {
        pj_thread_sleep(1);
    }

    PJ_LOG(5,(THIS_FILE, "Destroying stream.."));

    pj_pool_release(strm->pool);
    return PJ_SUCCESS;
}

/* API: Destroy stream. */
static pj_status_t stream_destroy(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if(strm->bdIMADpjInstance != NULL) {
        return stream_destroyBDIMAD(s);
    } else {
        return PJMEDIA_EAUD_ERR;
    }
}

/* API: set capability */
static pj_status_t stream_set_capBDIMAD(pjmedia_aud_stream *s,
                                        pjmedia_aud_dev_cap cap,
                                        const void *pval)
{
    struct bd_stream *strm = (struct bd_stream*)s;
    bdIMADpj_Status res = BD_PJ_OK;
    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);

    if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
        /* Output volume setting */
        float vol = (float)*(unsigned*)pval;

        if(vol > 100.0f) vol = 100.0f;
        if(vol < 0.0f) vol = 0.0f;

        vol = vol / 100.0f;
        res = bdIMADpj_setParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_SPK_VOLUME, &vol);


        if(res == BD_PJ_OK) {
            strm->param.output_vol = *(unsigned*)pval;
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    }

    if(cap == PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) {
        /* Input volume setting */
        float vol = (float)*(unsigned*)pval;

        if(vol > 100.0f) vol = 100.0f;
        if(vol < 0.0f) vol = 0.0f;

        vol = vol / 100.0f;
        res = bdIMADpj_setParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_MIC_VOLUME, &vol);
        if(res == BD_PJ_OK) {
            strm->param.input_vol = *(unsigned*)pval;
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    }

    if(cap == PJMEDIA_AUD_DEV_CAP_EC) {
        int aecOnOff = (*(pj_bool_t*)pval == PJ_TRUE ? 1 : 0);

        /* AEC setting */
        res = bdIMADpj_setParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_AEC_ENABLE, 
                                    &aecOnOff);
        if(res == BD_PJ_OK) {
            strm->param.ec_enabled = (aecOnOff == 1 ? PJ_TRUE : PJ_FALSE);
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    }

        if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
                pjmedia_aud_dev_route outputRoute = *(pjmedia_aud_dev_route*)pval;
                res = bdIMADpj_setRouteOutputDevice(strm->bdIMADpjInstance, (bdIMADpj_out_dev_route) outputRoute, &strm->bdIMADpjWarningPtr);
                if(res == BD_PJ_OK) {
                        strm->param.output_route = outputRoute;
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
        }

    return PJMEDIA_EAUD_INVCAP;
}

static pj_status_t factory_create_streamBDIMAD(pjmedia_aud_dev_factory *f,
                                    const pjmedia_aud_param *param,
                                    pjmedia_aud_rec_cb rec_cb,
                                    pjmedia_aud_play_cb play_cb,
                                    void *user_data,
                                    pjmedia_aud_stream **p_aud_strm)
{
    struct bd_factory *wf = (struct bd_factory*)f;
    pj_pool_t *pool;
    struct bd_stream *strm;
    pj_uint8_t silence_char;
    pj_status_t status;

    switch (param->ext_fmt.id) {
    case PJMEDIA_FORMAT_L16:
        silence_char = '\0';
        break;
    default:
        return PJMEDIA_EAUD_BADFORMAT;
    }

    /* Create and Initialize stream descriptor */
    pool = pj_pool_create(wf->pf, "BDIMAD_STREAM", 1000, 1000, NULL);
    PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);

    strm = PJ_POOL_ZALLOC_T(pool, struct bd_stream);
    pj_memcpy(&strm->param, param, sizeof(*param));
    strm->pool = pool;
    strm->rec_cb = rec_cb;
    strm->play_cb = play_cb;
    strm->user_data = user_data;
    strm->fmt_id = (pjmedia_format_id)param->ext_fmt.id;
    strm->silence_char = silence_char;
    strm->channel_count = param->channel_count;
    strm->samples_per_frame = param->samples_per_frame;

    if (param->dir & PJMEDIA_DIR_CAPTURE_PLAYBACK) {
        status = init_streams(wf, strm, param);

        if (status != PJ_SUCCESS) {
            stream_destroyBDIMAD(&strm->base);
            return status;
        }
    } else {
        stream_destroyBDIMAD(&strm->base);
        return PJMEDIA_EAUD_ERR;
    }

    strm->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, 
                    strm->bytes_per_frame);
    if (!strm->rec_buf) {
        pj_pool_release(pool);
        return PJ_ENOMEM;
    }
    strm->rec_buf_count = 0;

    strm->play_buf = (pj_int16_t*)pj_pool_alloc(pool, 
                     strm->bytes_per_frame);
    if (!strm->play_buf) {
        pj_pool_release(pool);
        return PJ_ENOMEM;
    }
    strm->play_buf_count = 0;

    /* Apply the remaining settings */
    if(param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
        stream_set_capBDIMAD(&strm->base, 
                             PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, 
                             &param->output_vol);
    }
    if(param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) {
        stream_set_capBDIMAD(&strm->base, 
                             PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, 
                             &param->input_vol);
    }
    if(param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
        stream_set_capBDIMAD(&strm->base, 
                             PJMEDIA_AUD_DEV_CAP_EC, 
                             &param->ec_enabled);
    }

    if(param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
                stream_set_capBDIMAD(&strm->base,
                                PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
                                 &param->output_route);
        }

    strm->base.op = &stream_op;
    *p_aud_strm = &strm->base;

    return PJ_SUCCESS;
}

static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
                                         const pjmedia_aud_param *param,
                                         pjmedia_aud_rec_cb rec_cb,
                                         pjmedia_aud_play_cb play_cb,
                                         void *user_data,
                                         pjmedia_aud_stream **p_aud_strm)
{
    return factory_create_streamBDIMAD(f, param, rec_cb, 
                                       play_cb, user_data, p_aud_strm);
}
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------
// ----------------------------------------------------------------------

/* API: Get stream info. */
static pj_status_t stream_get_param(pjmedia_aud_stream *s,
                                    pjmedia_aud_param *pi)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);

    pj_memcpy(pi, &strm->param, sizeof(*pi));

    // Get the output volume setting
    if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, 
                      &pi->output_vol) == PJ_SUCCESS)
    {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
    }

    // Get the input volume setting
    if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, 
                      &pi->input_vol) == PJ_SUCCESS)
    {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING;
    }

    // Get the AEC setting
    if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC, &pi->ec_enabled) == PJ_SUCCESS)
    {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
    }
        if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC, &pi->ec_enabled) == PJ_SUCCESS)
    {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_EC;
    }
    

    // Get the Route Output Device setting
    if(stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, &pi->output_route) == PJ_SUCCESS)
    {
        pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
    }

    return PJ_SUCCESS;
}

static pj_status_t stream_get_capBDIMAD(pjmedia_aud_stream *s,
                                        pjmedia_aud_dev_cap cap,
                                        void *pval)
{
    struct bd_stream *strm = (struct bd_stream*)s;
    bdIMADpj_Status res = BD_PJ_OK;

    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);

    if(cap == PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING)
    {
        /* Input volume setting */
        float vol;
        res = bdIMADpj_getParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_MIC_VOLUME, &vol);
        if(res == BD_PJ_OK) {
            vol = vol * 100;
            if(vol > 100.0f) vol = 100.0f;
            if(vol < 0.0f) vol = 0.0f;
            *(unsigned int *)pval = (unsigned int)vol;
            return PJ_SUCCESS;
        } else{
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    } else if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
        /* Output volume setting */
        float vol;
        res = bdIMADpj_getParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_SPK_VOLUME, &vol);
        if(res == BD_PJ_OK) {
                        vol = vol * 100;
            if(vol > 100.0f) vol = 100.0f;
            if(vol < 0.0f) vol = 0.0f;
            *(unsigned int *)pval = (unsigned int)vol;
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    }
    else if(cap == PJMEDIA_AUD_DEV_CAP_EC) {
        int aecIsOn;
        res = bdIMADpj_getParameter(strm->bdIMADpjInstance, 
                                    BD_PARAM_IMAD_PJ_AEC_ENABLE, &aecIsOn);
        if(res == BD_PJ_OK) {
            *(pj_bool_t*)pval = (aecIsOn == 1 ? PJ_TRUE : PJ_FALSE);
            return PJ_SUCCESS;
        } else {
            return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
        }
    }
    else if(cap == PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
                pjmedia_aud_dev_route routeOutDev;
                res = bdIMADpj_getRouteOutputDevice(strm->bdIMADpjInstance,(bdIMADpj_out_dev_route*)&routeOutDev);
            if(res == BD_PJ_OK) {
                *(pjmedia_aud_dev_route*)pval = routeOutDev;
                return PJ_SUCCESS;
            } else {
                return PJMEDIA_AUDIODEV_ERRNO_FROM_BDIMAD(res);
            }
    } else {
        return PJMEDIA_EAUD_INVCAP;
    }
}

static pj_status_t stream_startBDIMAD(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    strm->go = 1;

    return PJ_SUCCESS;
}

/* API: get capability */
static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
                                  pjmedia_aud_dev_cap cap,
                                  void *pval)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if(strm->bdIMADpjInstance != NULL) {
        return stream_get_capBDIMAD(s, cap, pval);
    } else {
        return PJMEDIA_EAUD_ERR;
    }
}

/* API: set capability */
static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
                                  pjmedia_aud_dev_cap cap,
                                  const void *pval)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if(strm->bdIMADpjInstance != NULL) {
        return stream_set_capBDIMAD(s, cap, pval);
    } else {
        return PJMEDIA_EAUD_ERR;
    }
}

/* API: Start stream. */
static pj_status_t stream_start(pjmedia_aud_stream *s)
{
    struct bd_stream *strm = (struct bd_stream*)s;

    PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL);

    if(strm->bdIMADpjInstance != NULL) {
        return stream_startBDIMAD(s);
    } else {
        return PJMEDIA_EAUD_ERR;
    }
}

#if defined (_MSC_VER)
#pragma comment ( lib, "bdClientValidation.lib" )
#pragma comment ( lib, "bdIMADpj.lib" )
#endif


#endif    /* PJMEDIA_AUDIO_DEV_HAS_BDIMAD */

