/***************************************************************************************
 *
 *  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
 *
 *  By downloading, copying, installing or using the software you agree to this license.
 *  If you do not agree to this license, do not download, install, 
 *  copy or use the software.
 *
 *  Copyright (C) 2014-2024, Happytimesoft Corporation, all rights reserved.
 *
 *  Redistribution and use in binary forms, with or without modification, are permitted.
 *
 *  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.
 *
****************************************************************************************/

#include "sys_inc.h"
#include "audio_capture_avf.h"
#include <CoreAudio/CoreAudio.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>

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

typedef struct
{
    BOOL                        capture_flag;
    pthread_t                   capture_thread;
    AudioQueueRef               audio_queue;
    int                         num_audio_buffers;
    AudioQueueBufferRef       * audio_buffer;
    void                      * buffer;
    uint32                      buffer_offset;
    uint32                      buffer_size;
    AudioStreamBasicDescription strdesc;
    AudioDeviceID               device_id;
    int                         device_index;
    int                         samplerate;
    uint16                      channels;
    uint16                      samples;
    int                         samplefmt;
    avf_audio_callback          callback;
    void                      * userdata;
    void                      * mutex_cb;
} AVFAudioContext;

static const AudioObjectPropertyAddress devlist_address = 
{
    kAudioHardwarePropertyDevices,
    kAudioObjectPropertyScopeGlobal,
    kAudioObjectPropertyElementMain
};

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

int avf_audio_device_nums()
{
    int count = 0;
    OSStatus result = noErr;
    uint32 size = 0;
    AudioDeviceID *devs = NULL;
    uint32 i = 0;
    uint32 max = 0;

    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                            &devlist_address, 0, NULL, &size);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    devs = (AudioDeviceID *) alloca(size);
    if (devs == NULL)
    {
        return 0;
    }
    
    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                        &devlist_address, 0, NULL, &size, devs);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    max = size / sizeof (AudioDeviceID);
    
    for (i = 0; i < max; i++) 
    {
        CFStringRef cfstr = NULL;
        char *ptr = NULL;
        AudioDeviceID dev = devs[i];
        AudioBufferList *buflist = NULL;
        int usable = 0;
        CFIndex len = 0;
        const AudioObjectPropertyAddress addr = {
            kAudioDevicePropertyStreamConfiguration,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        const AudioObjectPropertyAddress nameaddr = {
            kAudioObjectPropertyName,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
        if (result != noErr)
        {
            continue;
        }
        
        buflist = (AudioBufferList *) malloc(size);
        if (buflist == NULL)
        {
            continue;
        }
        
        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
        if (result == noErr) 
        {
            uint32 j;
            
            for (j = 0; j < buflist->mNumberBuffers; j++) 
            {
                if (buflist->mBuffers[j].mNumberChannels > 0) 
                {
                    usable = 1;
                    break;
                }
            }
        }

        free(buflist);

        if (!usable)
        {
            continue;
        }

        size = sizeof (CFStringRef);
        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
        if (result != kAudioHardwareNoError)
        {
            continue;
        }
        
        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
                                                kCFStringEncodingUTF8);

        ptr = (char *) malloc(len + 1);
        usable = ((ptr != NULL) &&
                  (CFStringGetCString
                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));

        CFRelease(cfstr);

        if (usable) 
        {
            len = strlen(ptr);
            
            /* Some devices have whitespace at the end...trim it. */
            while ((len > 0) && (ptr[len - 1] == ' ')) 
            {
                len--;
            }
            
            usable = (len > 0);
        }

        if (usable) 
        {
            count++;
        }

        free(ptr);
    }
    
    return count;
}

void avf_audio_device_list()
{
    int count = 0;
    OSStatus result = noErr;
    uint32 size = 0;
    AudioDeviceID *devs = NULL;
    uint32 i = 0;
    uint32 max = 0;

    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                            &devlist_address, 0, NULL, &size);
    if (result != kAudioHardwareNoError)
    {
        return;
    }
    
    devs = (AudioDeviceID *) alloca(size);
    if (devs == NULL)
    {
        return;
    }
    
    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                        &devlist_address, 0, NULL, &size, devs);
    if (result != kAudioHardwareNoError)
    {
        return;
    }

    printf("\r\nAvailable audio capture device : \r\n\r\n");
    
    max = size / sizeof (AudioDeviceID);
    
    for (i = 0; i < max; i++) 
    {
        CFStringRef cfstr = NULL;
        char *ptr = NULL;
        AudioDeviceID dev = devs[i];
        AudioBufferList *buflist = NULL;
        int usable = 0;
        CFIndex len = 0;
        const AudioObjectPropertyAddress addr = {
            kAudioDevicePropertyStreamConfiguration,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        const AudioObjectPropertyAddress nameaddr = {
            kAudioObjectPropertyName,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
        if (result != noErr)
        {
            continue;
        }
        
        buflist = (AudioBufferList *) malloc(size);
        if (buflist == NULL)
        {
            continue;
        }
        
        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
        if (result == noErr) 
        {
            uint32 j;
            
            for (j = 0; j < buflist->mNumberBuffers; j++) 
            {
                if (buflist->mBuffers[j].mNumberChannels > 0) 
                {
                    usable = 1;
                    break;
                }
            }
        }

        free(buflist);

        if (!usable)
        {
            continue;
        }

        size = sizeof (CFStringRef);
        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
        if (result != kAudioHardwareNoError)
        {
            continue;
        }
        
        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
                                                kCFStringEncodingUTF8);

        ptr = (char *) malloc(len + 1);
        usable = ((ptr != NULL) &&
                  (CFStringGetCString
                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));

        CFRelease(cfstr);

        if (usable) 
        {
            len = strlen(ptr);
            
            /* Some devices have whitespace at the end...trim it. */
            while ((len > 0) && (ptr[len - 1] == ' ')) 
            {
                len--;
            }
            
            usable = (len > 0);
        }

        if (usable) 
        {
            ptr[len] = '\0';

            printf("index : %d, name : %s\r\n", count++, ptr);
        }

        free(ptr);
    }
}

int avf_audio_device_get_index(const char * name)
{
    int count = 0;
    OSStatus result = noErr;
    uint32 size = 0;
    AudioDeviceID *devs = NULL;
    uint32 i = 0;
    uint32 max = 0;

    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                            &devlist_address, 0, NULL, &size);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    devs = (AudioDeviceID *) alloca(size);
    if (devs == NULL)
    {
        return 0;
    }
    
    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                        &devlist_address, 0, NULL, &size, devs);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    max = size / sizeof (AudioDeviceID);
    
    for (i = 0; i < max; i++) 
    {
        CFStringRef cfstr = NULL;
        char *ptr = NULL;
        AudioDeviceID dev = devs[i];
        AudioBufferList *buflist = NULL;
        int usable = 0;
        CFIndex len = 0;
        const AudioObjectPropertyAddress addr = {
            kAudioDevicePropertyStreamConfiguration,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        const AudioObjectPropertyAddress nameaddr = {
            kAudioObjectPropertyName,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
        if (result != noErr)
        {
            continue;
        }
        
        buflist = (AudioBufferList *) malloc(size);
        if (buflist == NULL)
        {
            continue;
        }
        
        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
        if (result == noErr) 
        {
            uint32 j;
            
            for (j = 0; j < buflist->mNumberBuffers; j++) 
            {
                if (buflist->mBuffers[j].mNumberChannels > 0) 
                {
                    usable = 1;
                    break;
                }
            }
        }

        free(buflist);

        if (!usable)
        {
            continue;
        }

        size = sizeof (CFStringRef);
        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
        if (result != kAudioHardwareNoError)
        {
            continue;
        }
        
        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
                                                kCFStringEncodingUTF8);

        ptr = (char *) malloc(len + 1);
        usable = ((ptr != NULL) &&
                  (CFStringGetCString
                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));

        CFRelease(cfstr);

        if (usable) 
        {
            len = strlen(ptr);
            
            /* Some devices have whitespace at the end...trim it. */
            while ((len > 0) && (ptr[len - 1] == ' ')) 
            {
                len--;
            }
            
            usable = (len > 0);
        }

        if (usable) 
        {
            ptr[len] = '\0';

            if (strcasecmp(ptr, name) == 0)
            {
                free(ptr);
                break;
            }

            count++;
        }

        free(ptr);
    }

    return count;
}

BOOL avf_audio_device_get_name(int index, char * name, int namesize)
{
    BOOL ret = FALSE;
    OSStatus result = noErr;
    uint32 size = 0;
    AudioDeviceID *devs = NULL;
    uint32 max = 0;

    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                            &devlist_address, 0, NULL, &size);
    if (result != kAudioHardwareNoError)
    {
        return FALSE;
    }
    
    devs = (AudioDeviceID *) alloca(size);
    if (devs == NULL)
    {
        return FALSE;
    }
    
    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                        &devlist_address, 0, NULL, &size, devs);
    if (result != kAudioHardwareNoError)
    {
        return FALSE;
    }
    
    max = size / sizeof (AudioDeviceID);

    if (index < 0 || index >= max)
    {
        return FALSE;
    }

    {
        CFStringRef cfstr = NULL;
        char *ptr = NULL;
        AudioDeviceID dev = devs[index];
        AudioBufferList *buflist = NULL;
        int usable = 0;
        CFIndex len = 0;
        const AudioObjectPropertyAddress addr = {
            kAudioDevicePropertyStreamConfiguration,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        const AudioObjectPropertyAddress nameaddr = {
            kAudioObjectPropertyName,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
        if (result != noErr)
        {
            return FALSE;
        }
        
        buflist = (AudioBufferList *) malloc(size);
        if (buflist == NULL)
        {
            return FALSE;
        }
        
        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
        if (result == noErr) 
        {
            uint32 j;
            
            for (j = 0; j < buflist->mNumberBuffers; j++) 
            {
                if (buflist->mBuffers[j].mNumberChannels > 0) 
                {
                    usable = 1;
                    break;
                }
            }
        }

        free(buflist);

        if (!usable)
        {
            return FALSE;
        }

        size = sizeof (CFStringRef);
        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
        if (result != kAudioHardwareNoError)
        {
            return FALSE;
        }
        
        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
                                                kCFStringEncodingUTF8);

        ptr = (char *) malloc(len + 1);
        usable = ((ptr != NULL) &&
                  (CFStringGetCString
                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));

        CFRelease(cfstr);

        if (usable) 
        {
            len = strlen(ptr);
            
            /* Some devices have whitespace at the end...trim it. */
            while ((len > 0) && (ptr[len - 1] == ' ')) 
            {
                len--;
            }
            
            usable = (len > 0);
        }

        if (usable) 
        {
            ptr[len] = '\0';

            ret = TRUE;
            strncpy(name, ptr, namesize);
        }

        free(ptr);
    }
    
    return ret;
}

AudioDeviceID avf_audio_get_deviceid(int index)
{
    int count = 0;
    OSStatus result = noErr;
    uint32 size = 0;
    AudioDeviceID *devs = NULL;
    uint32 i = 0;
    uint32 max = 0;

    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
                                            &devlist_address, 0, NULL, &size);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    devs = (AudioDeviceID *) alloca(size);
    if (devs == NULL)
    {
        return 0;
    }
    
    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
                                        &devlist_address, 0, NULL, &size, devs);
    if (result != kAudioHardwareNoError)
    {
        return 0;
    }
    
    max = size / sizeof (AudioDeviceID);
    
    for (i = 0; i < max; i++) 
    {
        CFStringRef cfstr = NULL;
        char *ptr = NULL;
        AudioDeviceID dev = devs[i];
        AudioBufferList *buflist = NULL;
        int usable = 0;
        CFIndex len = 0;
        const AudioObjectPropertyAddress addr = {
            kAudioDevicePropertyStreamConfiguration,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        const AudioObjectPropertyAddress nameaddr = {
            kAudioObjectPropertyName,
            kAudioDevicePropertyScopeInput,
            kAudioObjectPropertyElementMain
        };

        result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
        if (result != noErr)
        {
            continue;
        }
        
        buflist = (AudioBufferList *) malloc(size);
        if (buflist == NULL)
        {
            continue;
        }
        
        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
        if (result == noErr) 
        {
            uint32 j;
            
            for (j = 0; j < buflist->mNumberBuffers; j++) 
            {
                if (buflist->mBuffers[j].mNumberChannels > 0) 
                {
                    usable = 1;
                    break;
                }
            }
        }

        free(buflist);

        if (!usable)
        {
            continue;
        }

        size = sizeof (CFStringRef);
        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
        if (result != kAudioHardwareNoError)
        {
            continue;
        }
        
        len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr),
                                                kCFStringEncodingUTF8);

        ptr = (char *) malloc(len + 1);
        usable = ((ptr != NULL) &&
                  (CFStringGetCString
                   (cfstr, ptr, len + 1, kCFStringEncodingUTF8)));

        CFRelease(cfstr);

        if (usable) 
        {
            len = strlen(ptr);
            
            /* Some devices have whitespace at the end...trim it. */
            while ((len > 0) && (ptr[len - 1] == ' ')) 
            {
                len--;
            }
            
            usable = (len > 0);
        }

        if (usable) 
        {
            ptr[len] = '\0';

            if (count == index)
            {
                free(ptr);
                return dev;
            }

            count++;
        }

        free(ptr);
    }

    return 0;
}

BOOL avf_audio_prepare_device(AVFAudioContext * context)
{
    AudioDeviceID devid = (AudioDeviceID) avf_audio_get_deviceid(context->device_index);
    OSStatus result = noErr;
    uint32 size = 0;
    uint32 alive = 0;
    pid_t pid = 0;

    AudioObjectPropertyAddress addr = {
        0,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMain
    };

    addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
    addr.mScope = kAudioDevicePropertyScopeInput;

    size = sizeof (alive);
    result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioObjectGetPropertyData failed\r\n", __FUNCTION__);
        return FALSE;
    }
    
    if (!alive) 
    {
        log_print(HT_LOG_ERR, "%s, requested device exists, but isn't alive\r\n", __FUNCTION__);
        return FALSE;
    }

    addr.mSelector = kAudioDevicePropertyHogMode;
    size = sizeof (pid);
    result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);

    /* some devices don't support this property, so errors are fine here. */
    if ((result == noErr) && (pid != -1)) 
    {
        log_print(HT_LOG_ERR, "%s, requested device is being hogged\r\n", __FUNCTION__);
        return FALSE;
    }

    context->device_id = devid;
    
    return TRUE;
}

int avf_audio_assign_device(AVFAudioContext * context)
{
    const AudioObjectPropertyAddress prop = {
        kAudioDevicePropertyDeviceUID,
        kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMain
    };

    OSStatus result;
    CFStringRef devuid;
    uint32 devuidsize = sizeof (devuid);
    
    result = AudioObjectGetPropertyData(context->device_id, &prop, 0, NULL, &devuidsize, &devuid);
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioObjectGetPropertyData failed\r\n", __FUNCTION__);
        return 0;
    }
    
    result = AudioQueueSetProperty(context->audio_queue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioQueueSetProperty failed\r\n", __FUNCTION__);
        return 0;
    }

    return 1;
}

void avf_audio_input_cb(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
              const AudioTimeStamp *inStartTime, uint32 inNumberPacketDescriptions,
              const AudioStreamPacketDescription *inPacketDescs)
{
    AVFAudioContext * context = (AVFAudioContext *) inUserData;

    const uint8 * ptr = (const uint8 *) inBuffer->mAudioData;
    uint32 remaining = inBuffer->mAudioDataByteSize;
    while (remaining > 0) 
    {
        uint32 len = context->buffer_size - context->buffer_offset;
        if (len > remaining) 
        {
            len = remaining;
        }

        memcpy((char *)context->buffer + context->buffer_offset, ptr, len);
        ptr += len;
        remaining -= len;
        context->buffer_offset += len;

        if (context->buffer_offset >= context->buffer_size) 
        {
            if (context->callback)
            {
                avf_audio_data data;
                memset(&data, 0, sizeof(data));

                data.data[0] = (uint8 *)context->buffer;
                data.linesize[0] = context->buffer_size;
                data.samplerate = context->samplerate;
                data.channels = context->channels;
                data.samples = context->samples;
                data.format = context->samplefmt;
                
                context->callback(&data, context->userdata);
            }

            context->buffer_offset = 0;
        }
    }
    
    AudioQueueEnqueueBuffer(context->audio_queue, inBuffer, 0, NULL);
}

int avf_audio_prepare_queue(AVFAudioContext * context)
{
    int i;
    OSStatus result;
    const AudioStreamBasicDescription *strdesc = &context->strdesc;

    result = AudioQueueNewInput(strdesc, avf_audio_input_cb, context, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &context->audio_queue);
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioQueueNewInput failed\r\n", __FUNCTION__);
        return 0;
    }

    if (!avf_audio_assign_device(context)) 
    {
        log_print(HT_LOG_ERR, "%s, avf_audio_assign_device failed\r\n", __FUNCTION__);
        return 0;
    }

    AudioChannelLayout layout;
    memset(&layout, 0, sizeof(layout));
    
    layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

    result = AudioQueueSetProperty(context->audio_queue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioQueueSetProperty failed\r\n", __FUNCTION__);
        return 0;
    }

    uint32 size = strdesc->mBitsPerChannel / 8;
    size *= context->channels;
    size *= context->samples;
    
    /* Allocate a sample buffer */
    context->buffer_size = size;
    context->buffer_offset = 0;

    context->buffer = malloc(context->buffer_size);
    if (context->buffer == NULL) 
    {
        log_print(HT_LOG_ERR, "%s, malloc failed\r\n", __FUNCTION__);
        return 0;
    }

    /* Make sure we can feed the device a minimum amount of time */
    double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;

    const double msecs = (context->samples / ((double) context->samplerate)) * 1000.0;
    int num_audio_buffers = 2;
    if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) 
    {  
        /* use more buffers if we have a VERY small sample set. */
        num_audio_buffers = ((int)ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
    }

    context->num_audio_buffers = num_audio_buffers;
    context->audio_buffer = (AudioQueueBufferRef *) calloc(1, sizeof(AudioQueueBufferRef) * num_audio_buffers);
    if (context->audio_buffer == NULL) 
    {
        log_print(HT_LOG_ERR, "%s, calloc failed\r\n", __FUNCTION__);
        return 0;
    }

    for (i = 0; i < num_audio_buffers; i++) 
    {
        result = AudioQueueAllocateBuffer(context->audio_queue, size, &context->audio_buffer[i]);
        if (result != noErr)
        {
            log_print(HT_LOG_ERR, "%s, AudioQueueAllocateBuffer failed\r\n", __FUNCTION__);
            return 0;
        }
        
        memset(context->audio_buffer[i]->mAudioData, 0, context->audio_buffer[i]->mAudioDataBytesCapacity);
        context->audio_buffer[i]->mAudioDataByteSize = context->audio_buffer[i]->mAudioDataBytesCapacity;

        result = AudioQueueEnqueueBuffer(context->audio_queue, context->audio_buffer[i], 0, NULL);
        if (result != noErr)
        {
            log_print(HT_LOG_ERR, "%s, AudioQueueEnqueueBuffer failed\r\n", __FUNCTION__);
            return 0;
        }
    }

    result = AudioQueueStart(context->audio_queue, NULL);
    if (result != noErr)
    {
        log_print(HT_LOG_ERR, "%s, AudioQueueStart failed\r\n", __FUNCTION__);
        return 0;
    }

    /* We're running! */
    return 1;
}

void * avf_audio_thread(void * argv)
{
    AVFAudioContext * context = (AVFAudioContext *) argv;

    const int rc = avf_audio_prepare_queue(context);
    if (!rc) 
    {
        log_print(HT_LOG_ERR, "%s, avf_audio_prepare_queue failed\r\n", __FUNCTION__);
        return NULL;
    }

    while (context->capture_flag)
    {
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
    }

    context->capture_thread = 0;
    
    return NULL;
}

void * avf_audio_init(int device_index, int samplerate, int channels)
{
    AVFAudioContext * context = (AVFAudioContext *)malloc(sizeof(AVFAudioContext));
    if (NULL == context)
    {
        return NULL;
    }

    memset(context, 0, sizeof(AVFAudioContext));

    context->device_index = device_index;
    context->samplerate = samplerate;
    context->channels = channels;
    context->samples = 1024;
    context->samplefmt = 1;     // AV_SAMPLE_FMT_S16

    AudioStreamBasicDescription *strdesc = &context->strdesc;

    memset(strdesc, 0, sizeof(AudioStreamBasicDescription));

    strdesc->mFormatID = kAudioFormatLinearPCM;
    strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
    strdesc->mChannelsPerFrame = channels;
    strdesc->mSampleRate = samplerate;
    strdesc->mFramesPerPacket = 1;
    strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
    strdesc->mBitsPerChannel = 16;
    strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
    strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;

    if (!avf_audio_prepare_device(context)) 
    {
        log_print(HT_LOG_ERR, "%s, avf_prepare_device failed!\r\n", __FUNCTION__);
        goto fail;
    }

    context->capture_flag = TRUE;
    context->capture_thread = sys_os_create_thread((void *)avf_audio_thread, (void *)context);
    if (!context->capture_thread) 
    {
        goto fail;
    }

    context->mutex_cb = sys_os_create_mutex();
    
    return context;
   	
fail:

    avf_audio_uninit((void *)context);
    
    return NULL;
}

void avf_audio_uninit(void * ctx)
{
    if (NULL == ctx)
    {
        return;
    }

    AVFAudioContext * context = (AVFAudioContext *)ctx;

    if (context->audio_queue) 
    {
        AudioQueueDispose(context->audio_queue, 1);
    }

    context->capture_flag = FALSE;
    
    while (context->capture_thread) 
    {
        usleep(200*1000);
    }

    free(context->audio_buffer);
    free(context->buffer);

    sys_os_destroy_sig_mutex(context->mutex_cb);
    
    free(context);
}

void avf_audio_set_callback(void * ctx, avf_audio_callback cb, void * userdata)
{
    if (NULL == ctx)
    {
        return;
    }

    AVFAudioContext * context = (AVFAudioContext *)ctx;

    sys_os_mutex_enter(context->mutex_cb);
    context->callback = cb;
    context->userdata = userdata;
    sys_os_mutex_leave(context->mutex_cb);
}

int avf_audio_get_samplerate(void * ctx)
{
    if (NULL == ctx)
    {
        return 0;
    }

    AVFAudioContext * context = (AVFAudioContext *)ctx;

    return context->samplerate;
}

int avf_audio_get_channels(void * ctx)
{
    if (NULL == ctx)
    {
        return 0;
    }

    AVFAudioContext * context = (AVFAudioContext *)ctx;

    return context->channels;
}

int avf_audio_get_samplefmt(void * ctx)
{
    if (NULL == ctx)
    {
        return 0;
    }

    AVFAudioContext * context = (AVFAudioContext *)ctx;

    return context->samplefmt;
}

BOOL avf_audio_read(void * ctx, int * samples)
{
    return FALSE;	
}



