/*---------------------------------------------------------------------------*/
/* ios_phone.cpp                                                             */
/* copyright (c) innovaphone 2012                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/

#define IOS_PHONE_CPP_

#include <AudioUnit/AudioUnit.h>
#if TARGET_OS_IPHONE
#else
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
#endif

#include <pthread.h>
#include <stdio.h>
#include <fcntl.h>

#include "platform/platform.h"
#include "common/os/iomux.h"
#include "common/ilib/str.h"
#include "common/ilib/xml.h"
#include "common/interface/time.h"
#include "common/interface/audio.h"
#include "common/interface/socket.h"
#include "common/lib/language.h"
#include "client/lang/myapps_languages.h"
#if TARGET_OS_IPHONE
#include "client/config/client_config.h"
#include "client/linux/client_main/appclient_config.h"
#else
#include "client/headset_provider/interface/headset_provider_if.h"
#include "client/headset_provider/jabra/jabra_headset.h"
#include "client/headset_provider/plantronics/plantronics_headset.h"
#include "client/headset_provider/sennheiser/sennheiser_headset.h"
#include "client/headset_provider/yealink/yealink_headset.h"
#include "client/headset_provider/common/headset_provider.h"
#endif
#include "ios_phone.h"

extern int phoneLanguageId;
extern ISocketProvider * clientSocketProviderTCP;
extern "C" void ns_log(const char *format, ...);
extern "C" void setMicrophoneMute(bool mute);

#define FILE_ "ios_phone.cpp"

USING_NAMESPACE;

void *ios_dsp_context;

#define RECV_FRAGMENTATION_WINDOW       3200
#define PLAYBACK_TIMER_INTERVAL_8KHZ    320
#define SEND_MAX_FRAME_SAMPLES          160
#define RECORD_PACKET_SAMPLES           160
#if((AUDIO_PLAY_BUFFER_SAMPLES % (AUDIO_PLAY_WRITE_SAMPLES * (MAX_SAMPLE_RATE / 8000))) != 0)
#error ((AUDIO_PLAY_BUFFER_SAMPLES % (AUDIO_PLAY_WRITE_SAMPLES * (MAX_SAMPLE_RATE / 8000))) != 0)
#endif
#define AUDIO_RECORD_READ_SAMPLES       160
#if((AUDIO_RECORD_BUFFER_SAMPLES % (AUDIO_RECORD_READ_SAMPLES * (MAX_SAMPLE_RATE / 8000))) != 0)
#error ((AUDIO_RECORD_BUFFER_SAMPLES % (AUDIO_RECORD_READ_SAMPLES * (MAX_SAMPLE_RATE / 8000))) != 0)
#endif
#define START_WATCHDOG_SAMPLES          4000

#define SAMPLE_RATE_ID_8000             0
#define SAMPLE_RATE_ID_16000            1
#define SAMPLE_RATE_COUNT               2

static unsigned int sample_rate_table[SAMPLE_RATE_COUNT] =
{
    8000,
    16000,
};

word sample_overrun_threshold = SAMPLE_OVERRUN_THRESHOLD_8KHZ;
word recv_stuffing_limit = RECV_STUFFING_LIMIT_8KHZ;

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

IAudioPhone * IAudioPhone::Create(class IIoMux * const iomux, class IInstanceLog * log, unsigned execInterval8khz, class MediaConfiguration * mediaConfiguration)
{
    return new ios_dsp(iomux, log, execInterval8khz, clientSocketProviderTCP);
}

/*---------------------------------------------------------------------------*/
/* ios_dsp members                                                           */
/*---------------------------------------------------------------------------*/

typedef struct SAudioDevices {
#if TARGET_OS_IPHONE
    char *id;
    enum AudioDeviceType type;
    unsigned capabilities;
    char *name;
    int nameId;
#else
    char *id;
    enum AudioDeviceType type;
    unsigned capabilities;
    char *name;
    AudioDeviceID inputDeviceID;
    AudioDeviceID outputDeviceID;
    unsigned match;
#endif
} TAudioDevices;

#if TARGET_OS_IPHONE

static void AudioDevicesChanged(void * context)
{
    ios_dsp * thiz = (ios_dsp *) context;

    byte buf[1] = {0};
    write(thiz->device_change_pipefd[1], buf, sizeof(buf));
}

static void AddAudioDevice(void * context, const char * id, const char * name, bool speakerphone, bool headset, bool voiceCommunication)
{
    ios_dsp * thiz = (ios_dsp *) context;
    TAudioDevices * d;

    if (thiz->AudioDeviceCount == thiz->AudioDevicesAllocated) {
        thiz->AudioDevicesAllocated *= 2;
        d = (TAudioDevices *)(malloc(thiz->AudioDevicesAllocated * sizeof(TAudioDevices)));
        memcpy(d, thiz->AudioDevices, thiz->AudioDeviceCount * sizeof(TAudioDevices));
        free(thiz->AudioDevices);
        thiz->AudioDevices = d;
    }
    d = &thiz->AudioDevices[thiz->AudioDeviceCount];
    d->id = _strdup(id);
    d->name = _strdup(name);
    d->nameId = speakerphone ? TXT_HANDSFREE :
        ((!headset && (strcmp(id, AUDIO_DEVICE_ID_DEFAULT) == 0)) ? TXT_DEFAULT :
        ((!headset && (strcmp(id, AUDIO_DEVICE_ID_HANDSET) == 0)) ? TXT_HANDSET :
        ((headset && (strcmp(id, AUDIO_DEVICE_ID_HEADSET) == 0)) ? TXT_HEADSET :
        MYAPPS_TXT_NULL)));
    d->type = speakerphone ? AudioDeviceType::HandsfreeDevice : (headset ? AudioDeviceType::HeadsetDevice : AudioDeviceType::HandsetDevice);
    d->capabilities = AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK | (voiceCommunication ? AUDIO_CAPABILITY_COMMUNICATION : 0) | (headset ? AUDIO_CAPABILITY_HOOK_DEVICE : 0);
    (thiz->AudioDeviceCount)++;
}

extern "C" OSStatus configureAudioSession(unsigned int buffer_interval_8khz, unsigned int sample_rate);
extern "C" void startListenAudioSessionDevices(void (* changed)(void * context), void * context);
extern "C" void stopListenAudioSessionDevices();
extern "C" OSStatus setAudioSessionDevice(const char * deviceId);
extern "C" void getAudioSessionDevices(void (* add)(void * context, const char * id, const char * name, bool speakerphone, bool headset, bool voiceCommunication), void * context);

void ios_dsp::UpdateDeviceList()
{
    unsigned i;

    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
    }
    AudioDevicesAllocated = 16;
    AudioDevices = (TAudioDevices *)(malloc(AudioDevicesAllocated * sizeof(TAudioDevices)));
    AudioDeviceCount = 0;
    getAudioSessionDevices(AddAudioDevice, this);
}

void ios_dsp::StartListenDeviceList()
{
    OSStatus os_status;
    long flags;
    unsigned int i;

    AudioDevices = NULL;
    AudioDeviceCount = 0;
    device_change_pipe_created = false;
    if(!device_change_pipe_created) {
        if(pipe(device_change_pipefd) < 0) {
            ERR(("%s cannot create device change pipe", tag));
        }
        else {
            device_change_pipe_created = true;
            for(i = 0; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
                flags = fcntl(device_change_pipefd[i], F_GETFL);
                if(flags == -1) {
                    ERR(("%s cannot fcntl(F_GETFL)", tag));
                    flags = 0;
                }
                flags |= O_NONBLOCK;
                if(fcntl(device_change_pipefd[i], F_SETFL, flags) == -1) {
                    ERR(("%s cannot fcntl(F_SETFL)", tag));
                }
            }
            iomux->FDUnblock(device_change_pipefd[0]);
            iomux->FDAdd(device_change_pipefd[0], this, false);
        }
    }
    startListenAudioSessionDevices(AudioDevicesChanged, this);
    os_status = configureAudioSession(RECV_FRAGMENTATION_SAMPLES, sample_rate_table[SAMPLE_RATE_ID_16000]);
    if (os_status != noErr) {
        ns_log("%s couldn't configure audio session %i", "AUDIO", os_status);
    }
}

void ios_dsp::StopListenDeviceList()
{
    unsigned i;

    stopListenAudioSessionDevices();
    if(device_change_pipe_created) {
        iomux->FDClose(this);
        for(i = 1; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
            close(device_change_pipefd[i]);
        }
        device_change_pipe_created = false;
    }
    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
        AudioDevices = NULL;
        AudioDeviceCount = 0;
    }
}

void ios_dsp::IoContextNotify(bool indRX, bool indTX)
{
    byte buf[1];

    while(read(device_change_pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {
        switch(*buf) {
        default:
            if(!shutDown) {
                execCnt++;
                SetExec(iomux, this, (ios_dsp_query_devices *)NULL);
            }
            break;
        }
    }
}

void ios_dsp::TimerOnTimeout(ios_dsp_polling_timer * timer)
{
}

void ios_dsp::TimerOnTimeout(ios_dsp_reconnect_timer * timer)
{
}

unsigned ios_dsp::FindHeadsetDevice(const char *deviceId)
{
    return 0;
}

void ios_dsp::HookKeyReceived(const char *deviceId, byte key)
{
}

void ios_dsp::HeadsetCloseComplete(class IHeadsetProvider *provider)
{
}

void ios_dsp::HeadsetDispatch(class IHeadsetProvider *provider, bool polling)
{
}

void ios_dsp::TcpSocketConnect(class IHeadsetProvider *provider, IPaddr addr, word port)
{
}

void ios_dsp::TcpSocketSend(class IHeadsetProvider *provider, const void * buf, size_t len)
{
}

void ios_dsp::TcpSocketShutdown(class IHeadsetProvider *provider)
{
}

void ios_dsp::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
}

void ios_dsp::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
}

void ios_dsp::StartHookDevice(const char *deviceId)
{
}

void ios_dsp::StopHookDevice(const char *deviceId)
{
}

void ios_dsp::SendHookKey(const char *deviceId, byte key, byte callId, const char * number, const char * name)
{
}

void ios_dsp::SetMicrophoneMute(bool mute)
{
    setMicrophoneMute(mute);
}

void ios_dsp::Close()
{
    shutDown = true;
    process_timer.Cancel();
    execCnt++;
    SetExec(iomux, this, (ios_dsp_close *)0);
}

#else

#define setAudioSessionDevice(deviceId)

void ios_dsp::UpdateDeviceList()
{
    OSStatus os_status;
    AudioObjectPropertyAddress ioProperties;
    UInt32 ioSize, transportType;
    AudioDeviceID deviceID;
    AudioObjectID *devices;
    AudioBufferList *bufferList;
    CFStringRef str, inputUID, outputUID, modelUID;
    CFIndex maxSize;
    TAudioDevices *ioDevices;
    char headsetModelId[1024];
    unsigned headsetDeviceCount;
    char **headsetDeviceIds;
    word *headsetVendorIds, *headsetProductIds;
    const char *p;
    unsigned ioDevice, ioDeviceCount, ioInputOutputs, ioInput, ioInputs, ioOutput, ioOutputs, inputChannels, outputChannels, inputLen, outputLen, i, j, l, m, n;

    ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    ioSize = 0;
    os_status = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &ioProperties, 0, NULL, &ioSize);
    if (os_status != noErr) {
        return;
    }
    // calculate the number of device available
    ioDeviceCount = ioSize / (UInt32)(sizeof(AudioObjectID));
    // make space for the devices we are about to get
    devices = (AudioObjectID *)(malloc(ioSize));
    memset(devices, 0, ioSize);
    os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, 0, NULL, &ioSize, devices);
    ioDevices = (TAudioDevices *)(malloc(ioDeviceCount * sizeof(TAudioDevices)));
    ioInputOutputs = 0;
    ioInputs = 0;
    ioOutputs = 0;

    // iterate over each device gathering information
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        deviceID = devices[ioDevice];

        // get device name
        ioDevices[ioDevice].name = NULL;
        ioProperties.mSelector = kAudioObjectPropertyName;
        ioProperties.mScope = kAudioObjectPropertyScopeGlobal;
        ioSize = sizeof(CFStringRef);
        os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, &str);
        if((os_status == noErr) && str) {
            maxSize = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8) + 1;
            ioDevices[ioDevice].name = (char *)(malloc(maxSize));
            p = CFStringGetCStringPtr(str, kCFStringEncodingUTF8);
            if (p) {
                strcpy(ioDevices[ioDevice].name, p);
            }
            else if (!CFStringGetCString(str, ioDevices[ioDevice].name, maxSize, kCFStringEncodingUTF8)) {
                ioDevices[ioDevice].name[0] = '\0';
            }
            CFRelease(str);
        }

        inputChannels = 0;
        outputChannels = 0;
        inputUID = NULL;
        outputUID = NULL;
        // get output device UID
        ioProperties.mSelector = kAudioDevicePropertyDeviceUID;
        ioProperties.mScope = kAudioObjectPropertyScopeOutput;
        ioSize = sizeof(CFStringRef);
        os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, &outputUID);
        if((os_status == noErr) && outputUID) {
            // get number of output channels
            ioProperties.mSelector = kAudioDevicePropertyStreamConfiguration;
            ioProperties.mScope = kAudioObjectPropertyScopeOutput;
            ioSize = 0;
            os_status = AudioObjectGetPropertyDataSize(deviceID, &ioProperties, 0, NULL, &ioSize);
            if((os_status == noErr) && (ioSize != 0)) {
                bufferList = (AudioBufferList *)(malloc(ioSize));
                if(bufferList != NULL) {
                    // get the output stream configuration
                    os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, bufferList);
                    if(os_status == noErr) {
                        // count the total number of output channels in the stream
                        for(i = 0; i < bufferList->mNumberBuffers; i++) {
                            outputChannels += bufferList->mBuffers[i].mNumberChannels;
                        }
                    }
                    free(bufferList);
                }
            }
            if (outputChannels == 0) {
                CFRelease(outputUID);
                outputUID = NULL;
            }
        }
        ioProperties.mSelector = kAudioDevicePropertyTransportType;
        ioProperties.mScope = kAudioObjectPropertyScopeGlobal;
        ioSize = sizeof(transportType);
        os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, &transportType);
        if ((outputChannels == 0) || (os_status != noErr) || (transportType != kAudioDeviceTransportTypeBuiltIn)) {
            // get input device UID
            ioProperties.mSelector = kAudioDevicePropertyDeviceUID;
            ioProperties.mScope = kAudioObjectPropertyScopeInput;
            ioSize = sizeof(CFStringRef);
            os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, &inputUID);
            if((os_status == noErr) && inputUID) {
                // get number of input channels
                ioProperties.mSelector = kAudioDevicePropertyStreamConfiguration;
                ioProperties.mScope = kAudioObjectPropertyScopeInput;
                ioSize = 0;
                os_status = AudioObjectGetPropertyDataSize(deviceID, &ioProperties, 0, NULL, &ioSize);
                if((os_status == noErr) && (ioSize != 0)) {
                    bufferList = (AudioBufferList *)(malloc(ioSize));
                    if(bufferList != NULL) {
                        // get the input stream configuration
                        os_status = AudioObjectGetPropertyData(deviceID, &ioProperties, 0, NULL, &ioSize, bufferList);
                        if(os_status == noErr) {
                            // count the total number of input channels in the stream
                            for(i = 0; i < bufferList->mNumberBuffers; i++) {
                                inputChannels += bufferList->mBuffers[i].mNumberChannels;
                            }
                        }
                        free(bufferList);
                    }
                }
                if (inputChannels == 0) {
                    CFRelease(inputUID);
                    inputUID = NULL;
                }
            }
        }
        maxSize = CFStringGetMaximumSizeForEncoding(((inputChannels != 0) ? CFStringGetLength(inputUID) : 0) + ((outputChannels != 0) ? CFStringGetLength(outputUID) : 0), kCFStringEncodingUTF8) + 2;
        ioDevices[ioDevice].id = (char *)(malloc(maxSize));
        i = 0;
        if (inputChannels != 0) {
            p = CFStringGetCStringPtr(inputUID, kCFStringEncodingUTF8);
            if (p) {
                strcpy(&ioDevices[ioDevice].id[i], p);
            }
            else if (!CFStringGetCString(inputUID, &ioDevices[ioDevice].id[i], maxSize - i - 2, kCFStringEncodingUTF8)) {
                ioDevices[ioDevice].id[i] = '\0';
            }
            CFRelease(inputUID);
            i += strlen(&ioDevices[ioDevice].id[i]);
        }
        if ((inputChannels != 0) && (outputChannels != 0)) {
            ioDevices[ioDevice].id[i++] = '\n';
        }
        if (outputChannels != 0) {
            p = CFStringGetCStringPtr(outputUID, kCFStringEncodingUTF8);
            if (p) {
                strcpy(&ioDevices[ioDevice].id[i], p);
            }
            else if (!CFStringGetCString(outputUID, &ioDevices[ioDevice].id[i], maxSize - i - 1, kCFStringEncodingUTF8)) {
                ioDevices[ioDevice].id[i] = '\0';
            }
            CFRelease(outputUID);
            i += strlen(&ioDevices[ioDevice].id[i]);
        }
        ioDevices[ioDevice].id[i] = '\0';

        ioDevices[ioDevice].capabilities = 0;
        ioDevices[ioDevice].inputDeviceID = 0;
        ioDevices[ioDevice].outputDeviceID = 0;
        if ((transportType != kAudioDeviceTransportTypeAggregate) && ioDevices[ioDevice].name) {
            ioDevices[ioDevice].type = ((inputChannels != 0) && ((transportType == kAudioDeviceTransportTypeUSB) || (transportType == kAudioDeviceTransportTypeBluetooth))) ?
                AudioDeviceType::HeadsetDevice : AudioDeviceType::HandsfreeDevice;
            if (inputChannels != 0) {
                if (outputChannels != 0) {
                    ioInputOutputs++;
                    ioDevices[ioDevice].capabilities = AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK | AUDIO_CAPABILITY_COMMUNICATION;
                    ioDevices[ioDevice].inputDeviceID = deviceID;
                    ioDevices[ioDevice].outputDeviceID = deviceID;
                }
                else {
                    ioInputs++;
                    ioDevices[ioDevice].capabilities = AUDIO_CAPABILITY_RECORD;
                    ioDevices[ioDevice].inputDeviceID = deviceID;
                }
            }
            else {
                if (outputChannels != 0) {
                    ioOutputs++;
                    ioDevices[ioDevice].capabilities = AUDIO_CAPABILITY_PLAYBACK;
                    ioDevices[ioDevice].outputDeviceID = deviceID;
                }
            }
            if (ioDevices[ioDevice].type == AudioDeviceType::HeadsetDevice) {
                ioDevices[ioDevice].capabilities |= AUDIO_CAPABILITY_HOOK_DEVICE;
            }
        }
//        ERR(("%s device[%u:%u] %u %u %u %s", tag, ioDevice, deviceID, transportType, inputChannels, outputChannels, ioDevices[ioDevice].id));
    }
    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
    }
    AudioDeviceCount = 1 + ioInputOutputs + ioInputs * ioOutputs;
    AudioDevices = (TAudioDevices *)(malloc(AudioDeviceCount * sizeof(TAudioDevices)));
    AudioDevices[0].id = _strdup("\n");
    AudioDevices[0].name = _strdup(MYAPPS_LANG_STRING(TXT_DEFAULT, phoneLanguageId));
    AudioDevices[0].type = AudioDeviceType::HandsfreeDevice;
    AudioDevices[0].capabilities = AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK | AUDIO_CAPABILITY_COMMUNICATION;
    AudioDevices[0].inputDeviceID = 0;
    AudioDevices[0].outputDeviceID = 0;
    AudioDeviceID inputDeviceID;
    UInt32 param = sizeof(AudioDeviceID);
    os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &param, &inputDeviceID);
    if(os_status != noErr) {
        ERR(("%s couldn't get default input device %i", tag, os_status));
    }
    AudioDeviceID outputDeviceID;
    param = sizeof(AudioDeviceID);
    os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &param, &outputDeviceID);
    if(os_status != noErr) {
        ERR(("%s couldn't get default output device %i", tag, os_status));
    }
    n = 1;
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        if ((ioDevices[ioDevice].capabilities & (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) == (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) {
            i = n;
            if ((ioDevices[ioDevice].inputDeviceID == inputDeviceID) && (ioDevices[ioDevice].outputDeviceID == outputDeviceID)) {
                while (i > 1) {
                    memcpy(&AudioDevices[i], &AudioDevices[i-1], sizeof(TAudioDevices));
                    i--;
                }
            }
            AudioDevices[i].id = _strdup(ioDevices[ioDevice].id);
            AudioDevices[i].name = _strdup(ioDevices[ioDevice].name);
            AudioDevices[i].type = ioDevices[ioDevice].type;
            AudioDevices[i].capabilities = ioDevices[ioDevice].capabilities;
            AudioDevices[i].inputDeviceID = ioDevices[ioDevice].inputDeviceID;
            AudioDevices[i].outputDeviceID = ioDevices[ioDevice].outputDeviceID;
            n++;
        }
    }
    for(ioInput = 0; ioInput < ioDeviceCount; ioInput++) {
        if ((ioDevices[ioInput].capabilities & (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) == AUDIO_CAPABILITY_RECORD) {
            for(ioOutput = 0; ioOutput < ioDeviceCount; ioOutput++) {
                if ((ioDevices[ioOutput].capabilities & (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) == AUDIO_CAPABILITY_PLAYBACK) {
                    inputLen = strlen(ioDevices[ioInput].name);
                    outputLen = strlen(ioDevices[ioOutput].name);
                    l = (inputLen <= outputLen) ? inputLen : outputLen;
                    j = 0;
                    while ((j < l) && (ioDevices[ioInput].name[j] == ioDevices[ioOutput].name[j])) {
                        j++;
                    }
                    if (j < 4) {
                        j = 0;
                    }
                    m = j;
                    j = 0;
                    while ((j < l) && (ioDevices[ioInput].name[inputLen - 1 - j] == ioDevices[ioOutput].name[outputLen - 1 - j])) {
                        j++;
                    }
                    if (j < 4) {
                        j = 0;
                    }
                    if (j > m) {
                        m = j;
                    }
                    i = n;
                    while ((i > 1 + ioInputOutputs) && (m > AudioDevices[i-1].match)) {
                        memcpy(&AudioDevices[i], &AudioDevices[i-1], sizeof(TAudioDevices));
                        i--;
                    }
                    AudioDevices[i].id = (char *)(malloc(strlen(ioDevices[ioInput].id) + strlen(ioDevices[ioOutput].id) + 2));
                    _sprintf(AudioDevices[i].id, "%s\n%s", ioDevices[ioInput].id, ioDevices[ioOutput].id);
                    AudioDevices[i].name = (char *)(malloc(inputLen + outputLen - m + 4));
                    if (inputLen == 0) {
                        memcpy(AudioDevices[i].name, ioDevices[ioOutput].name, outputLen + 1);
                    }
                    else if ((outputLen == 0) || ((inputLen == outputLen) && (m == inputLen))) {
                        memcpy(AudioDevices[i].name, ioDevices[ioInput].name, inputLen + 1);
                    }
                    else {
                        if (m >= j) {
                            while (ioDevices[ioOutput].name[m] == ' ') {
                                m++;
                            }
                            memcpy(AudioDevices[i].name, ioDevices[ioInput].name, inputLen);
                            AudioDevices[i].name[inputLen] = ' ';
                            AudioDevices[i].name[inputLen + 1] = '+';
                            AudioDevices[i].name[inputLen + 2] = ' ';
                            memcpy(&AudioDevices[i].name[inputLen + 3], &ioDevices[ioOutput].name[m], outputLen - m + 1);
                        }
                        else {
                            while ((j < inputLen) && (ioDevices[ioInput].name[inputLen - 1 - j] == ' ')) {
                                j++;
                            }
                            memcpy(AudioDevices[i].name, ioDevices[ioInput].name, inputLen - j);
                            AudioDevices[i].name[inputLen - j] = ' ';
                            AudioDevices[i].name[inputLen - j + 1] = '+';
                            AudioDevices[i].name[inputLen - j + 2] = ' ';
                            memcpy(&AudioDevices[i].name[inputLen - j + 3], ioDevices[ioOutput].name, outputLen + 1);
                        }
                    }
                    AudioDevices[i].type = ioDevices[ioInput].type;
                    AudioDevices[i].capabilities = ioDevices[ioInput].capabilities | ioDevices[ioOutput].capabilities;
                    AudioDevices[i].inputDeviceID = ioDevices[ioInput].inputDeviceID;
                    AudioDevices[i].outputDeviceID = ioDevices[ioOutput].outputDeviceID;
                    AudioDevices[i].match = m;
                    n++;
                }
            }
        }
    }
/*
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        if ((ioDevices[ioDevice].capabilities & (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) == AUDIO_CAPABILITY_RECORD) {
            AudioDevices[n].id = (char *)(malloc(strlen(ioDevices[ioDevice].id) + 2));
            _sprintf(AudioDevices[n].id, "%s\n", ioDevices[ioDevice].id);
            AudioDevices[n].name = _strdup(ioDevices[ioDevice].name);
            AudioDevices[n].type = ioDevices[ioDevice].type;
            AudioDevices[n].capabilities = ioDevices[ioDevice].capabilities;
            AudioDevices[n].inputDeviceID = ioDevices[ioDevice].inputDeviceID;
            AudioDevices[n].outputDeviceID = ioDevices[ioDevice].outputDeviceID;
            n++;
        }
    }
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        if ((ioDevices[ioDevice].capabilities & (AUDIO_CAPABILITY_RECORD | AUDIO_CAPABILITY_PLAYBACK)) == AUDIO_CAPABILITY_PLAYBACK) {
            AudioDevices[n].id = (char *)(malloc(strlen(ioDevices[ioDevice].id) + 2));
            _sprintf(AudioDevices[n].id, "\n%s", ioDevices[ioDevice].id);
            AudioDevices[n].name = _strdup(ioDevices[ioDevice].name);
            AudioDevices[n].type = ioDevices[ioDevice].type;
            AudioDevices[n].capabilities = ioDevices[ioDevice].capabilities;
            AudioDevices[n].inputDeviceID = ioDevices[ioDevice].inputDeviceID;
            AudioDevices[n].outputDeviceID = ioDevices[ioDevice].outputDeviceID;
            n++;
        }
    }
*/
    headsetDeviceCount = 0;
    for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
        if (ioDevices[ioDevice].type == AudioDeviceType::HeadsetDevice) {
            headsetDeviceCount++;
        }
    }
    if (headsetDeviceCount != 0) {
        headsetDeviceIds = (char **)(malloc(headsetDeviceCount * sizeof(char *)));
        headsetVendorIds = (word *)(malloc(headsetDeviceCount * sizeof(word)));
        headsetProductIds = (word *)(malloc(headsetDeviceCount * sizeof(word)));
        n = 0;
        for(ioDevice = 0; ioDevice < ioDeviceCount; ioDevice++) {
            if (ioDevices[ioDevice].type == AudioDeviceType::HeadsetDevice) {
                headsetDeviceIds[n] = ioDevices[ioDevice].name;
                headsetVendorIds[n] = 0;
                headsetProductIds[n] = 0;
                // get input model UID
                ioProperties.mSelector = kAudioDevicePropertyModelUID;
                ioProperties.mScope = kAudioObjectPropertyScopeInput;
                ioSize = sizeof(CFStringRef);
                os_status = AudioObjectGetPropertyData(devices[ioDevice], &ioProperties, 0, NULL, &ioSize, &modelUID);
                if((os_status == noErr) && modelUID) {
                    p = CFStringGetCStringPtr(modelUID, kCFStringEncodingUTF8);
                    if (!p) {
                        if (!CFStringGetCString(modelUID, headsetModelId, sizeof(headsetModelId) - 1, kCFStringEncodingUTF8)) {
                            headsetModelId[0] = '\0';
                        }
                        p = headsetModelId;
                    }
                    l = strlen(p);
                    if ((l >= 10)
                     && (p[l-10] == ':') && isxdigit(p[l-9]) && isxdigit(p[l-8]) && isxdigit(p[l-7]) && isxdigit(p[l-6])
                     && (p[l-5] == ':') && isxdigit(p[l-4]) && isxdigit(p[l-3]) && isxdigit(p[l-2]) && isxdigit(p[l-1])) {
                        headsetVendorIds[n] = ((isdigit(p[l-9]) ? p[l-9] - '0' : tolower(p[l-9]) - 'a' + 10) << 12) |
                            ((isdigit(p[l-8]) ? p[l-8] - '0' : tolower(p[l-8]) - 'a' + 10) << 8) |
                            ((isdigit(p[l-7]) ? p[l-7] - '0' : tolower(p[l-7]) - 'a' + 10) << 4) |
                            (isdigit(p[l-6]) ? p[l-6] - '0' : tolower(p[l-6]) - 'a' + 10);
                        headsetProductIds[n] = ((isdigit(p[l-4]) ? p[l-4] - '0' : tolower(p[l-4]) - 'a' + 10) << 12) |
                            ((isdigit(p[l-3]) ? p[l-3] - '0' : tolower(p[l-3]) - 'a' + 10) << 8) |
                            ((isdigit(p[l-2]) ? p[l-2] - '0' : tolower(p[l-2]) - 'a' + 10) << 4) |
                            (isdigit(p[l-1]) ? p[l-1] - '0' : tolower(p[l-1]) - 'a' + 10);
                    }
                    CFRelease(modelUID);
                }
//                ERR(("DevicesDetected[%u] %x %x %s", n, headsetVendorIds[n], headsetProductIds[n], headsetDeviceIds[n]));
                n++;
            }
        }
        if (headsetProvider) headsetProvider->DevicesDetected(headsetDeviceCount, headsetDeviceIds, headsetVendorIds, headsetProductIds);
        free(headsetDeviceIds);
        free(headsetVendorIds);
        free(headsetProductIds);
    }
    else {
        if (headsetProvider) headsetProvider->DevicesDetected(0, NULL, NULL, NULL);
    }
    for (i = 0; i < ioDeviceCount; i++) {
        free(ioDevices[i].id);
        if (ioDevices[i].name) free(ioDevices[i].name);
    }
    free(ioDevices);
    free(devices);
/*
    for (i = 0; i < AudioDeviceCount; i++) {
        ERR(("%s AudioDevices[%u] %x %u %u %s", tag, i, AudioDevices[i].capabilities, AudioDevices[i].inputDeviceID, AudioDevices[i].outputDeviceID, AudioDevices[i].name));
    }
*/
}

static OSStatus AOPropertyListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress inAddresses[], void *inClientData)
{
    class ios_dsp *thiz = (class ios_dsp *) inClientData;
    UInt32 x;

    for (x = 0; x < inNumberAddresses; x++) {
        switch (inAddresses[x].mSelector) {
    /*
     * These are the other types of notifications we might receive, however, they are beyond
     * the scope of this sample and we ignore them.
     *
        case kAudioHardwarePropertyDefaultInputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default input device changed\n");
            break;

        case kAudioHardwarePropertyDefaultOutputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default output device changed\n");
            break;

        case kAudioHardwarePropertyDefaultSystemOutputDevice:
            fprintf(stderr, "AOPropertyListenerProc: default system output device changed\n");
            break;
    */
        case kAudioHardwarePropertyDevices:
            {
                byte buf[1] = {0};
                write(thiz->device_change_pipefd[1], buf, sizeof(buf));
            }
            break;

        default:
            break;
        }
    }
    return noErr;
}

void ios_dsp::StartListenDeviceList()
{
    long flags;
    unsigned int i;

    AudioDevices = NULL;
    AudioDeviceCount = 0;
    device_change_pipe_created = false;
    if(!device_change_pipe_created) {
        if(pipe(device_change_pipefd) < 0) {
            ERR(("%s cannot create device change pipe", tag));
        }
        else {
            device_change_pipe_created = true;
            for(i = 0; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
                flags = fcntl(device_change_pipefd[i], F_GETFL);
                if(flags == -1) {
                    ERR(("%s cannot fcntl(F_GETFL)", tag));
                    flags = 0;
                }
                flags |= O_NONBLOCK;
                if(fcntl(device_change_pipefd[i], F_SETFL, flags) == -1) {
                    ERR(("%s cannot fcntl(F_SETFL)", tag));
                }
            }
            iomux->FDUnblock(device_change_pipefd[0]);
            iomux->FDAdd(device_change_pipefd[0], this, false);
        }
    }
    headsetProvider = new HeadsetProvider;
    headsetProvider->Initialize(this);
    // install kAudioHardwarePropertyDevices notification listener
    AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &ioProperties, AOPropertyListenerProc, this);
}

void ios_dsp::StopListenDeviceList()
{
    unsigned int i;

    // remove kAudioHardwarePropertyDevices notification listener
    AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &ioProperties, AOPropertyListenerProc, this);
    if (headsetProvider) {
        delete headsetProvider;
        headsetProvider = NULL;
    }
    if(device_change_pipe_created) {
        iomux->FDClose(this);
        for(i = 1; i < sizeof(device_change_pipefd) / sizeof(device_change_pipefd[0]); i++) {
            close(device_change_pipefd[i]);
        }
        device_change_pipe_created = false;
    }
    if (AudioDevices) {
        for (i = 0; i < AudioDeviceCount; i++) {
            free(AudioDevices[i].id);
            free(AudioDevices[i].name);
        }
        free(AudioDevices);
        AudioDevices = NULL;
        AudioDeviceCount = 0;
    }
}

void ios_dsp::IoContextNotify(bool indRX, bool indTX)
{
    byte buf[1];

    while(read(device_change_pipefd[0], buf, sizeof(buf)) == sizeof(buf)) {
        switch(*buf) {
        case 1:
            headsetProvider->OnDispatch();
            polling_timer.Cancel();
            break;
        case 2:
            if (!polling_timer.IsRunning()) {
                polling_timer.Start(100);
            }
            break;
        default:
            if(!shutDown) {
                execCnt++;
                SetExec(iomux, this, (ios_dsp_query_devices *)NULL);
            }
            break;
        }
    }
}

void ios_dsp::TimerOnTimeout(ios_dsp_polling_timer * timer)
{
    if (!shutDown) {
        headsetProvider->OnDispatch();
    }
}

void ios_dsp::TimerOnTimeout(ios_dsp_reconnect_timer * timer)
{
    if (!shutDown) {
        headsetSocketUser->TcpSocketShutdown();
    }
}

unsigned ios_dsp::FindHeadsetDevice(const char *deviceId)
{
    const char *p, *q;
    unsigned i;

    i = 0;
    while (i < AudioDeviceCount) {
        if (AudioDevices[i].capabilities & AUDIO_CAPABILITY_HOOK_DEVICE) {
            p = strchr(AudioDevices[i].id, '\n');
            q = strstr(AudioDevices[i].id, deviceId);
            if (q && (!p || (p >= q))) {
                return i;
            }
        }
        i++;
    }
    return i;
}

void ios_dsp::HookKeyReceived(const char *deviceId, byte key)
{
    unsigned i;

    i = FindHeadsetDevice(deviceId);
    ERR(("%s HookKeyReceived %u %02x %s", tag, (i < AudioDeviceCount), key, deviceId));
    if (i < AudioDeviceCount) {
        user->HookKeyReceived(AudioDevices[i].id, key);
    }
}

void ios_dsp::HeadsetCloseComplete(class IHeadsetProvider *provider)
{
    StopListenDeviceList();
    SetExec(iomux, this, (ios_dsp_close *)0);
}

void ios_dsp::HeadsetDispatch(class IHeadsetProvider *provider, bool polling)
{
    byte buf[1];
    buf[0] = polling ? 2 : 1;
    write(device_change_pipefd[1], buf, sizeof(buf));
}

void ios_dsp::TcpSocketConnect(class IHeadsetProvider *provider, IPaddr addr, word port)
{
    char address[128];
    headsetSocketUser = provider;
    headsetSocket = tcpSocketProvider->CreateSocket(iomux, this, log, false);
    _sprintf(address, "%u.%u.%u.%u:%u", addr.b[12], addr.b[13], addr.b[14], addr.b[15], port);
    headsetSocket->Connect(address);
    headsetSocket->Recv(headsetSocketBuf, sizeof(headsetSocketBuf), true);
}

void ios_dsp::TcpSocketSend(class IHeadsetProvider *provider, const void * buf, size_t len)
{
    headsetSocket->Send(buf, len);
}

void ios_dsp::TcpSocketShutdown(class IHeadsetProvider *provider)
{
    if (headsetSocket) {
        headsetSocket->Shutdown();
    }
    else {
        headsetSocketUser->TcpSocketShutdown();
    }
}

void ios_dsp::SocketRecvResult(ISocket * const socket, void * buf, size_t len)
{
    if (socket == headsetSocket) {
        headsetSocketUser->TcpSocketRecvResult(buf, len);
        headsetSocket->Recv(headsetSocketBuf, sizeof(headsetSocketBuf), true);
    }
}

void ios_dsp::SocketShutdown(ISocket * const socket, shutdownreason_t reason)
{
    delete socket;
    if (socket == headsetSocket) {
        headsetSocket = NULL;
        if (shutDown) {
            headsetSocketUser->TcpSocketShutdown();
        }
        else {
            reconnect_timer.Start(1000);
        }
    }
}

void ios_dsp::StartHookDevice(const char *deviceId)
{
    const char *p;
    char *q;

    if (!headsetProvider) return;
    p = strchr(deviceId, '\n');
    if (p) {
        q = (char *)(malloc((p - deviceId) + 1));
        memcpy(q, deviceId, p - deviceId);
        q[p - deviceId] = '\0';
        headsetProvider->StartHookDevice(q);
        free(q);
    }
    else {
        headsetProvider->StartHookDevice(deviceId);
    }
}

void ios_dsp::StopHookDevice(const char *deviceId)
{
    const char *p;
    char *q;

    if (!headsetProvider) return;
    p = strchr(deviceId, '\n');
    if (p) {
        q = (char *)(malloc((p - deviceId) + 1));
        memcpy(q, deviceId, p - deviceId);
        q[p - deviceId] = '\0';
        headsetProvider->StopHookDevice(q);
        free(q);
    }
    else {
        headsetProvider->StopHookDevice(deviceId);
    }
}

void ios_dsp::SendHookKey(const char *deviceId, byte key, byte callId, const char * number, const char * name)
{
    const char *p;
    char *q;

    if (!headsetProvider) return;
    p = strchr(deviceId, '\n');
    if (p) {
        q = (char *)(malloc((p - deviceId) + 1));
        memcpy(q, deviceId, p - deviceId);
        q[p - deviceId] = '\0';
        headsetProvider->SendHookKey(q, key, callId, number, name);
        free(q);
    }
    else {
        headsetProvider->SendHookKey(deviceId, key, callId, number, name);
    }
}

void ios_dsp::SetMicrophoneMute(bool mute)
{
    setMicrophoneMute(mute);
    if (!headsetProvider) return;
    headsetProvider->SetMicrophoneMute(mute);
}

void ios_dsp::Close()
{
    shutDown = true;
    process_timer.Cancel();
    if (headsetProvider) {
        execCnt++;
        headsetProvider->Close();
    }
    else
    {
        StopListenDeviceList();
        execCnt++;
        SetExec(iomux, this, (ios_dsp_close *)0);
    }
}

#endif

void ios_dsp::IoExec(ios_dsp_query_devices * context)
{
    UpdateDeviceList();
    user->AudioDeviceList(context);
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseAudioPhoneComplete();
    }
}

void ios_dsp::IoExec(ios_dsp_close * context)
{
    execCnt--;
    if(shutDown && !execCnt) {
        user->CloseAudioPhoneComplete();
    }
}

class ios_dsp *the_ios_dsp = 0;

#define IN_DOORBELL_PLAY_STOP           0x80000000
#define IN_DOORBELL_RECORD_STOP         0x40000000
#define OUT_DOORBELL_PLAY_STOP          0x80000000
#define OUT_DOORBELL_RECORD_STOP        0x40000000

static OSStatus	performInput  (void                         *inRefCon,
                               AudioUnitRenderActionFlags 	*ioActionFlags,
                               const AudioTimeStamp 		*inTimeStamp,
                               UInt32 						inBusNumber,
                               UInt32 						inNumberFrames,
                               AudioBufferList              *ioData)
{
    class ios_dsp *thiz = (class ios_dsp *) inRefCon;
    AudioTimeStamp ts;
    OSStatus os_status;
    AudioBuffer *b;
    unsigned int buffer_number, i, n, sample_ofs;

    // we are calling AudioUnitRender on the input bus of audio unit
    // this will store the audio data captured by the microphone in ioData
    memcpy(&ts, inTimeStamp, sizeof(ts));
    n = inNumberFrames;
    while (n > sizeof(thiz->recordSampleBuffer) / sizeof(thiz->recordSampleBuffer[0])) {
        i = n - sizeof(thiz->recordSampleBuffer) / sizeof(thiz->recordSampleBuffer[0]);
        if (i > sizeof(thiz->recordSampleBuffer) / sizeof(thiz->recordSampleBuffer[0])) {
            i = sizeof(thiz->recordSampleBuffer) / sizeof(thiz->recordSampleBuffer[0]);
        }
        thiz->recordIoDataList.mBuffers[0].mDataByteSize = i * sizeof(AudioUnitSampleType);
#if TARGET_OS_IPHONE
        os_status = AudioUnitRender(thiz->audio_unit, ioActionFlags, &ts, 1, i, &(thiz->recordIoDataList));
#else
        os_status = AudioUnitRender(thiz->record_audio_unit ? thiz->record_audio_unit : thiz->audio_unit, ioActionFlags, &ts, 1, i, &(thiz->recordIoDataList));
#endif
        n -= i;
        ts.mSampleTime += i;
    }
    thiz->recordIoDataList.mBuffers[0].mDataByteSize = n * sizeof(AudioUnitSampleType);
#if TARGET_OS_IPHONE
    os_status = AudioUnitRender(thiz->audio_unit, ioActionFlags, &ts, 1, n, &(thiz->recordIoDataList));
#else
    os_status = AudioUnitRender(thiz->record_audio_unit ? thiz->record_audio_unit : thiz->audio_unit, ioActionFlags, &ts, 1, n, &(thiz->recordIoDataList));
#endif
    thiz->recordIoDataList.mBuffers[0].mDataByteSize = n * sizeof(AudioUnitSampleType);
    if (os_status != noErr) {
        ns_log("%s couldn't record audio %i %u %u", thiz->tag, os_status, inBusNumber, inNumberFrames);
        for (buffer_number = 0; buffer_number < thiz->recordIoDataList.mNumberBuffers; buffer_number++) {
            b = &(thiz->recordIoDataList.mBuffers[buffer_number]);
            for (i = 0; i < b->mDataByteSize / sizeof(AudioUnitSampleType); i++) {
                ((AudioUnitSampleType *)(b->mData))[i] = 0;
            }
        }
    }
    // we are calling AudioUnitRender on the input bus of audio unit
    // this will store the audio data captured by the microphone in ioData
    buffer_number = 0;
    sample_ofs = 0;
    while (sample_ofs < inNumberFrames) {
        b = &(thiz->recordIoDataList.mBuffers[buffer_number]);
        i = 0;
        n = (b->mDataByteSize / sizeof(AudioUnitSampleType)) - i;
        if (n > inNumberFrames - (sample_ofs + i)) {
            n = inNumberFrames - (sample_ofs + i);
        }
        while (n != 0) {
            if (n > AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos) {
                n = AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos;
            }
            memcpy(&thiz->audio_record_buffer[thiz->audio_record_pos], &((AudioUnitSampleType *)(b->mData))[i], n * sizeof(AudioUnitSampleType));
            thiz->audio_record_pos = (n < AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos) ? thiz->audio_record_pos + n : 0;
            i += n;
            n = (b->mDataByteSize / sizeof(AudioUnitSampleType)) - i;
            if (n > inNumberFrames - (sample_ofs + i)) {
                n = inNumberFrames - (sample_ofs + i);
            }
        }
        sample_ofs += i;
        buffer_number++;
    }
    return noErr;
}

#if TARGET_OS_IPHONE
#else
OSStatus output_audio_converter_proc(AudioConverterRef inAudioConverter,
                                     UInt32* ioNumberDataPackets,
                                     AudioBufferList* ioData,
                                     AudioStreamPacketDescription** ioDataPacketDescription,
                                     void* inUserData)
{
    class ios_dsp *thiz = (class ios_dsp *) inUserData;
    unsigned n;

    n = *ioNumberDataPackets;
    if (n > AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos) {
        n = AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos;
        *ioNumberDataPackets = n;
    }
    ioData->mBuffers[0].mNumberChannels = 1;
    ioData->mBuffers[0].mData = &thiz->audio_play_buffer[thiz->audio_play_pos];
    ioData->mBuffers[0].mDataByteSize = n * sizeof(AudioUnitSampleType);
    ioData->mNumberBuffers = 1;
    thiz->audio_play_pos = ((n < AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos) ? thiz->audio_play_pos : thiz->audio_play_pos - AUDIO_PLAY_BUFFER_SAMPLES) + n;
    return noErr;
}
#endif

static OSStatus	performRender (void                         *inRefCon,
                               AudioUnitRenderActionFlags 	*ioActionFlags,
                               const AudioTimeStamp 		*inTimeStamp,
                               UInt32 						inBusNumber,
                               UInt32 						inNumberFrames,
                               AudioBufferList              *ioData)
{
    class ios_dsp *thiz = (class ios_dsp *) inRefCon;
    AudioBuffer *b;
    unsigned int i, n, sample_ofs, sample_count, buffer_number;

    sample_count = 0;
    for (buffer_number = 0; buffer_number < ioData->mNumberBuffers; buffer_number++) {
        b = &(ioData->mBuffers[buffer_number]);
        sample_count += b->mDataByteSize / sizeof(AudioUnitSampleType);
    }

    if((thiz->audio_io_in_doorbell ^ thiz->audio_io_in_doorbell_ack) & IN_DOORBELL_RECORD_STOP) {
        thiz->audio_io_in_doorbell_ack ^= IN_DOORBELL_RECORD_STOP;
        thiz->audio_io_out_doorbell ^= OUT_DOORBELL_RECORD_STOP;
    }

#if TARGET_OS_IPHONE
#else
    if (thiz->output_audio_converter) {
        OSStatus os_status;
        UInt32 converted_samples = sample_count;
        os_status = AudioConverterFillComplexBuffer(thiz->output_audio_converter,
                                                    output_audio_converter_proc,
                                                    thiz,
                                                    &converted_samples,
                                                    ioData,
                                                    NULL /* AudioStreamPacketDescription *outPacketDescription */);
        if ((os_status != noErr) || (converted_samples != sample_count)) {
            ns_log("%s couldn't convert output audio %i %u %u", thiz->tag, os_status, converted_samples, sample_count);
        }
    }
    else
#endif
    {
        buffer_number = 0;
        sample_ofs = 0;
        while (sample_ofs < sample_count) {
            b = &(ioData->mBuffers[buffer_number]);
            i = 0;
            n = (b->mDataByteSize / sizeof(AudioUnitSampleType)) - i;
            if (n > sample_count - (sample_ofs + i)) {
                n = sample_count - (sample_ofs + i);
            }
            while (n != 0) {
                if (n > AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos) {
                    n = AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos;
                }
                memcpy(&((AudioUnitSampleType *)(b->mData))[i], &thiz->audio_play_buffer[thiz->audio_play_pos], n * sizeof(AudioUnitSampleType));
                thiz->audio_play_pos = (n < AUDIO_PLAY_BUFFER_SAMPLES - thiz->audio_play_pos) ? thiz->audio_play_pos + n : 0;
                i += n;
                n = (b->mDataByteSize / sizeof(AudioUnitSampleType)) - i;
                if (n > sample_count - (sample_ofs + i)) {
                    n = sample_count - (sample_ofs + i);
                }
            }
            sample_ofs += i;
            buffer_number++;
        }
    }

    if((thiz->audio_io_in_doorbell ^ thiz->audio_io_in_doorbell_ack) & IN_DOORBELL_PLAY_STOP) {
        thiz->audio_io_in_doorbell_ack ^= IN_DOORBELL_PLAY_STOP;
        thiz->audio_io_out_doorbell ^= OUT_DOORBELL_PLAY_STOP;
    }
    return noErr;
}

bool ios_dsp::audio_session_preconfigured = false;

ios_dsp::ios_dsp(class IIoMux * const iomux, class IInstanceLog * log, unsigned execInterval8khz, class ISocketProvider * tcpSocketProvider) :
    delayed_off_timer(iomux, this),
    process_timer(iomux, this),
    polling_timer(iomux, this),
    reconnect_timer(iomux, this)
{
    the_ios_dsp = this;
    strcpy(tag, "AUDIO");
    this->iomux = iomux;
    this->log = log;
    this->execInterval8khz = execInterval8khz;
    this->tcpSocketProvider = tcpSocketProvider;

    headset_plugged = false;
    channel_attached = false;

    new_record_sample_rate_id = SAMPLE_RATE_ID_16000;
    new_playback_sample_rate_id = SAMPLE_RATE_ID_16000;
    new_device_id = _strdup("");
    new_handset = false;
    new_speaker = false;
    new_headset = false;
    new_earpiece_nearby = false;
    start_watchdog = START_WATCHDOG_SAMPLES;

    record_sample_rate_id = SAMPLE_RATE_ID_16000;
    playback_sample_rate_id = SAMPLE_RATE_ID_16000;
    mute = false;
    device_id = _strdup("");
    handset = false;
    speaker = false;
    headset = false;
    ringing = false;
    earpiece_nearby = false;

    audio_play_pos = 0;
    audio_record_pos = 0;

    start_thread_running = false;
    audio_activated = false;
    audio_session = false;
    componentSubType = kAudioUnitSubType_VoiceProcessingIO;
    audio_unit = 0;
    input_sample_rate = sample_rate_table[record_sample_rate_id];
    output_sample_rate = sample_rate_table[playback_sample_rate_id];
#if TARGET_OS_IPHONE
#else
    record_audio_unit = 0;
    input_audio_converter = 0;
    output_audio_converter = 0;
#endif

    ios_dsp_context = this;

    shutDown = false;
    execCnt = 0;

    StartListenDeviceList();
    UpdateDeviceList();
}

ios_dsp::~ios_dsp()
{
    finit();
    free(new_device_id);
    free(device_id);
}

extern "C" void onCallPresentationStart();
extern "C" void onCallPresentationEnd();
extern "C" void onHandsetStart();
extern "C" void onHandsetEnd();
extern "C" void onSpeakerphoneStart();
extern "C" void onSpeakerphoneEnd();
extern "C" void onHeadsetStart();
extern "C" void onHeadsetEnd();

void ios_dsp::Initialize(class UAudioPhone * const user)
{
    this->user = user;
    init();
}

unsigned ios_dsp::ExecJitter8khz()
{
    return sample_overrun_threshold;
}

unsigned ios_dsp::AudioDelay8khz()
{
    return sample_overrun_threshold;
}

void ios_dsp::QueryDevices(void * context)
{
    if(!shutDown) {
        execCnt++;
        SetExec(iomux, this, (ios_dsp_query_devices *)context);
    }
}

unsigned ios_dsp::DeviceCount()
{
#if TARGET_OS_IPHONE
    unsigned i, n;

	if (headset_plugged) {
        n = AudioDeviceCount;
    }
    else {
        n = 0;
        for (i = 0; i < AudioDeviceCount; i++) {
            if (AudioDevices[i].type != AudioDeviceType::HeadsetDevice) {
                n++;
            }
        }
    }
    return n;
#else
    return AudioDeviceCount;
#endif
}

const char * ios_dsp::DeviceId(unsigned deviceNumber)
{
#if TARGET_OS_IPHONE
    unsigned i, n;

    if (headset_plugged) {
        i = deviceNumber;
    }
    else {
        i = 0;
        n = 0;
        while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice)) {
            i++;
        }
        while (n < deviceNumber) {
            n++;
            do {
                i++;
            }
            while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice));
        }
    }
    return AudioDevices[i].id;
#else
    return AudioDevices[(deviceNumber < AudioDeviceCount) ? deviceNumber : 0].id;
#endif
}

enum AudioDeviceType ios_dsp::DeviceType(unsigned deviceNumber)
{
#if TARGET_OS_IPHONE
    unsigned i, n;

    if (headset_plugged) {
        i = deviceNumber;
    }
    else {
        i = 0;
        n = 0;
        while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice)) {
            i++;
        }
        while (n < deviceNumber) {
            n++;
            do {
                i++;
            }
            while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice));
        }
    }
    return AudioDevices[i].type;
#else
    return AudioDevices[(deviceNumber < AudioDeviceCount) ? deviceNumber : 0].type;
#endif
}

unsigned ios_dsp::DeviceCapabilities(unsigned deviceNumber)
{
#if TARGET_OS_IPHONE
    unsigned i, n;

    if (headset_plugged) {
        i = deviceNumber;
    }
    else {
        i = 0;
        n = 0;
        while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice)) {
            i++;
        }
        while (n < deviceNumber) {
            n++;
            do {
                i++;
            }
            while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice));
        }
    }
    return AudioDevices[i].capabilities;
#else
    return AudioDevices[(deviceNumber < AudioDeviceCount) ? deviceNumber : 0].capabilities;
#endif
}

const char * ios_dsp::DeviceName(unsigned deviceNumber)
{
#if TARGET_OS_IPHONE
    unsigned i, n;

    if (headset_plugged) {
        i = deviceNumber;
    }
    else {
        i = 0;
        n = 0;
        while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice)) {
            i++;
        }
        while (n < deviceNumber) {
            n++;
            do {
                i++;
            }
            while ((i < AudioDeviceCount) && (AudioDevices[i].type == AudioDeviceType::HeadsetDevice));
        }
    }
    return ((AudioDevices[i].nameId != MYAPPS_TXT_NULL) ? MYAPPS_LANG_STRING(AudioDevices[i].nameId, phoneLanguageId) : AudioDevices[i].name);
#else
    return AudioDevices[(deviceNumber < AudioDeviceCount) ? deviceNumber : 0].name;
#endif
}

void phone_audio_focus_loss(bool loss)
{
}

void ios_dsp::audio_devices_updated()
{
    UpdateDeviceList();
    user->AudioDeviceList(NULL);
}

void phone_audio_devices_updated()
{
    the_ios_dsp->audio_devices_updated();
}

void ios_dsp::headset_plugged_changed(bool plugged)
{
    if (plugged != headset_plugged) {
        headset_plugged = plugged;
        QueryDevices(NULL);
    }
}

void phone_headset_plugged(bool plugged)
{
    the_ios_dsp->headset_plugged_changed(plugged);
}

void ios_dsp::headset_hook()
{
    unsigned i;

    i = 0;
    while (AudioDevices[i].type != AudioDeviceType::HeadsetDevice) {
        i++;
    }
    user->HookKeyReceived(AudioDevices[i].id, KEY_EHS_TALK);
}

void phone_headset_hook()
{
    the_ios_dsp->headset_hook();
}

void ios_dsp::earpiece_proximity(bool nearby)
{
    new_earpiece_nearby = nearby;
    codec_mode_changed(ringing);
    update_dsp(false);
}

void phone_earpiece_proximity(bool nearby)
{
    the_ios_dsp->earpiece_proximity(nearby);
}

void ios_dsp::codec_mode_changed(bool new_ringing)
{
    delayed_off_timer.Cancel();
    if((new_ringing ^ ringing)
    || ((new_handset || (new_speaker && new_earpiece_nearby)) ^ (handset || (speaker && earpiece_nearby)))
    || ((new_speaker && !new_earpiece_nearby) ^ (speaker && !earpiece_nearby))
    || (new_headset ^ headset)) {
        ERR(("%s Codec mode change %i%i%i%i%i->%i%i%i%i%i %s->%s", tag,
            ringing, handset, speaker, headset, earpiece_nearby,
            new_ringing, new_handset, new_speaker, new_headset, new_earpiece_nearby,
            device_id, new_device_id));
    }
    if((new_ringing && !ringing)
    || (ringing && !new_ringing && !new_handset && !new_speaker && !new_headset)) {
        if (new_ringing) {
            onCallPresentationStart();
        }
        else {
            onCallPresentationEnd();
        }
    }
    if(((new_handset || (new_speaker && new_earpiece_nearby)) && !(handset || (speaker && earpiece_nearby)))
    || ((handset || (speaker && earpiece_nearby)) && !new_ringing && !new_handset && !new_speaker && !new_headset)) {
        if (new_handset || (new_speaker && new_earpiece_nearby)) {
            onHandsetStart();
        }
        else {
            onHandsetEnd();
        }
    }
    if(((new_speaker && !new_earpiece_nearby) && !(speaker && !earpiece_nearby))
    || ((speaker && !earpiece_nearby) && !new_ringing && !new_handset && !new_speaker && !new_headset)) {
        if (new_speaker && !new_earpiece_nearby) {
            onSpeakerphoneStart();
        }
        else {
            onSpeakerphoneEnd();
        }
    }
    if(((new_headset && !new_handset) && !(headset && !handset))
    || (headset && !handset && !new_ringing && !new_handset && !new_speaker && !new_headset)) {
        if (new_headset) {
            onHeadsetStart();
        }
        else {
            onHeadsetEnd();
        }
    }
/*
    if((!ringing && !handset && !speaker && !headset) && (new_ringing || new_handset || new_speaker || new_headset)) {
        AudioManager_requestAudioFocus(audio_manager, 0, AudioManager_STREAM_VOICE_CALL, AudioManager_AUDIOFOCUS_GAIN_TRANSIENT);
    }
    else if((ringing || handset || speaker || headset) && (!new_ringing && !new_handset && !new_speaker && !new_headset)) {
        AudioManager_abandonAudioFocus(audio_manager, 0);
    }
*/
    handset = new_handset;
    speaker = new_speaker;
    headset = new_headset;
    ringing = new_ringing;
    earpiece_nearby = new_earpiece_nearby;

    if (ringing) {
        headset_plugged_changed(headset_plugged);
    }
    else if ((handset && !headset) || (speaker && earpiece_nearby)) {
        setAudioSessionDevice(AUDIO_DEVICE_ID_HANDSET);
    }
    else if (headset) {
        setAudioSessionDevice((handset && (!new_device_id || (new_device_id[0] == '\0') || (strcmp(new_device_id, AUDIO_DEVICE_ID_HANDSET) == 0))) ? AUDIO_DEVICE_ID_DEFAULT : new_device_id);
    }
    else if (speaker && !earpiece_nearby) {
        setAudioSessionDevice(new_device_id);
    }
    else {
        setAudioSessionDevice(new_device_id);
    }
}


extern "C" OSStatus configureAudioSession(unsigned int buffer_interval_8khz, unsigned int sample_rate);
extern "C" OSStatus setupAudioSession(unsigned int buffer_interval_8khz, unsigned int sample_rate);
extern "C" OSStatus activateAudioSession();
extern "C" OSStatus releaseAudioSession();
extern "C" void audioSessionActivated(bool activated);
extern "C" void request_restart_dsp();
extern void ios_main_sleep(dword ms);

extern "C" void phone_configure_audio_session()
{
    OSStatus os_status;

    ios_dsp::audio_session_preconfigured = true;
    ns_log("%s configureAudioSession %i %i", "AUDIO", RECV_FRAGMENTATION_SAMPLES, sample_rate_table[SAMPLE_RATE_ID_16000]);
    os_status = configureAudioSession(RECV_FRAGMENTATION_SAMPLES, sample_rate_table[SAMPLE_RATE_ID_16000]);
    if (os_status != noErr) {
        ns_log("%s couldn't configure audio session %i", "AUDIO", os_status);
    }
}

extern "C" void phone_audio_session_will_deactivate()
{
    unsigned i;
    the_ios_dsp->audio_activated = false;
    ns_log("%s audioSessionWillDeactivate", "AUDIO");
    request_restart_dsp();
    i = 0;
    while ((i < 20) && the_ios_dsp->audio_session) {
        i++;
        ios_main_sleep(5);
    }
}

extern "C" void phone_audio_session_activated(bool activated)
{
    audioSessionActivated(activated);
    if (activated != the_ios_dsp->audio_activated) {
        if (activated) {
            ios_dsp::audio_session_preconfigured = true;
            the_ios_dsp->audio_activated = true;
            request_restart_dsp();
        }
        else {
            phone_audio_session_will_deactivate();
            ios_dsp::audio_session_preconfigured = false;
        }
    }
}

static int set_high_prio(const char *name)
{
    struct sched_param param;
	int policy=SCHED_RR;
	int result=0;

	memset(&param,0,sizeof(param));
    policy=SCHED_FIFO;
	param.sched_priority=sched_get_priority_max(policy);
	if((result=pthread_setschedparam(pthread_self(),policy, &param))) {
		if(result==EPERM){
			/*
				The linux kernel has 
				sched_get_priority_max(SCHED_OTHER)=sched_get_priority_max(SCHED_OTHER)=0.
				As long as we can't use SCHED_RR or SCHED_FIFO, the only way to increase priority of a calling thread
				is to use setpriority().
			*/
			if(setpriority(PRIO_PROCESS,0,-20)==-1){
				ns_log("%s setpriority() failed: %s, nevermind.",name,strerror(errno));
			}
            else {
//				ns_log("%s priority increased to maximum.",name);
			}
		}
        else {
            ns_log("%s: Set pthread_setschedparam failed: %s",name,strerror(result));
        }
	} else {
//		ns_log("%s priority set to %s and value (%i)",name,
//		           policy==SCHED_FIFO ? "SCHED_FIFO" : "SCHED_RR", param.sched_priority);
	}
    return result;
}

static void *audio_start_thread(void *arg)
{
    set_high_prio("audio_thread");
    ((class ios_dsp *) arg)->start_thread();
    return NULL;
}

void ios_dsp::update_dsp(bool media_config_changed)
{
    unsigned int i;
    bool stop_dsp, new_channel_attached, sample_rate_changed, device_id_changed;
    OSType newComponentSubType;
    OSStatus os_status;

    if (start_thread_running) {
        memset(audio_play_buffer, 0, AUDIO_PLAY_BUFFER_SAMPLES * sizeof(AudioUnitSampleType));
        int err = pthread_join(start_thread_id, NULL);
        if (err != 0) {
            ERR(("%s couldn't stop audio start thread %s", tag, strerror(err)));
        }
        start_thread_running = false;
    }
#if TARGET_OS_IPHONE
    newComponentSubType = kAudioUnitSubType_VoiceProcessingIO;
#else
    AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyTranslateUIDToDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
    UInt32 ioSize;
    CFStringRef inputUID, outputUID;
    AudioDeviceID defaultInputDeviceID, defaultOutputDeviceID;
    UInt32 param;
    char *p;
    const char *in, *out;

    newComponentSubType = kAudioUnitSubType_VoiceProcessingIO;
    ns_log("%s update_dsp DefaultDevice", tag);
    param = sizeof(AudioDeviceID);
    os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &param, &defaultInputDeviceID);
    if(os_status != noErr) {
        ERR(("%s couldn't get default input device %i", tag, os_status));
    }
    param = sizeof(AudioDeviceID);
    os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &param, &defaultOutputDeviceID);
    if(os_status != noErr) {
        ERR(("%s couldn't get default output device %i", tag, os_status));
    }

    // get input device UID
    ioProperties.mSelector = kAudioDevicePropertyDeviceUID;
    ioProperties.mScope = kAudioObjectPropertyScopeInput;
    ioSize = sizeof(CFStringRef);
    os_status = AudioObjectGetPropertyData(defaultInputDeviceID, &ioProperties, 0, NULL, &ioSize, &inputUID);
    if(os_status != noErr) {
        inputUID = NULL;
    }
    // get output device UID
    ioProperties.mSelector = kAudioDevicePropertyDeviceUID;
    ioProperties.mScope = kAudioObjectPropertyScopeOutput;
    ioSize = sizeof(CFStringRef);
    os_status = AudioObjectGetPropertyData(defaultOutputDeviceID, &ioProperties, 0, NULL, &ioSize, &outputUID);
    if(os_status != noErr) {
        outputUID = NULL;
    }
    p = strchr(new_device_id, '\n');
    if (p) {
        *p = '\0';
        p++;
    }
    in = ((new_device_id[0] != '\0') || !inputUID) ? new_device_id : CFStringGetCStringPtr(inputUID, kCFStringEncodingUTF8);
    out = ((p && (*p != '\0')) || !outputUID) ? (p ? p : new_device_id) : CFStringGetCStringPtr(outputUID, kCFStringEncodingUTF8);
    i = 0;
    while ((in[i] == out[i]) && (in[i] != '\0')) {
        i++;
    }
    if ((strlen(&in[i]) <= 3) || ((strcmp(&in[i], "input") == 0) && (strcmp(&out[i], "output") == 0))) {
        newComponentSubType = kAudioUnitSubType_HALOutput;
    }
    ERR(("%s update_dsp Device %u %s\n%s", tag, (newComponentSubType != kAudioUnitSubType_VoiceProcessingIO), in, out));
    if (p) {
        p--;
        *p = '\n';
    }
    if (inputUID) {
        CFRelease(inputUID);
    }
    if (outputUID) {
        CFRelease(outputUID);
    }
#endif
    stop_dsp = false;
    new_channel_attached = (((new_handset || new_speaker || new_headset) && !audio_session_preconfigured) || audio_activated);
    ERR(("%s update_dsp(%i) %i%i %i %i %i", tag, media_config_changed, channel_attached, new_channel_attached, stop_dsp,
        (new_record_sample_rate_id != record_sample_rate_id), (new_playback_sample_rate_id != playback_sample_rate_id)));
    sample_rate_changed = ((new_record_sample_rate_id != record_sample_rate_id) || (new_playback_sample_rate_id != playback_sample_rate_id));
    device_id_changed = (strcmp(new_device_id, device_id) != 0);
    if((new_channel_attached ^ channel_attached)
     || device_id_changed
     || sample_rate_changed
     || media_config_changed) {
        stop_dsp = true;
    }
    if(stop_dsp) {
        if(audio_unit) {
            memset(audio_play_buffer, 0, AUDIO_PLAY_BUFFER_SAMPLES * sizeof(AudioUnitSampleType));
            i = 0;
            audio_io_in_doorbell ^= (IN_DOORBELL_RECORD_STOP | IN_DOORBELL_PLAY_STOP);
            while(((i < AUDIO_RECORD_BUFFER_SAMPLES / ((MAX_RESAMPLE_RATE / 8000) * (5 * 8)))
                && !((audio_io_out_doorbell ^ audio_io_out_doorbell_ack) & OUT_DOORBELL_RECORD_STOP))
               || ((i < AUDIO_PLAY_BUFFER_SAMPLES / ((MAX_SAMPLE_RATE / 8000) * (5 * 8)))
                && !((audio_io_out_doorbell ^ audio_io_out_doorbell_ack) & OUT_DOORBELL_PLAY_STOP))) {
                i++;
                ios_main_sleep(5);
            }
            audio_io_out_doorbell_ack ^= (OUT_DOORBELL_RECORD_STOP | OUT_DOORBELL_PLAY_STOP);

            // Stop the audio unit instance
            ERR(("%s update_dsp AudioOutputUnitStop %u", tag, i * 5));
            os_status = AudioOutputUnitStop(audio_unit);
            if (os_status != noErr) {
                ERR(("%s couldn't stop audio unit %i", tag, os_status));
            }
#if TARGET_OS_IPHONE
#else
            if (record_audio_unit) {
                os_status = AudioOutputUnitStop(record_audio_unit);
                if (os_status != noErr) {
                    ERR(("%s couldn't stop record audio unit %i", tag, os_status));
                }
            }
#endif
            if (!new_channel_attached || sample_rate_changed || device_id_changed) {
                // Uninitialize the audio unit instance
                ERR(("%s update_dsp AudioUnitUninitialize", tag));
                os_status = AudioUnitUninitialize(audio_unit);
                if (os_status != noErr) {
                    ERR(("%s couldn't uninitialize audio unit %i", tag, os_status));
                }
#if TARGET_OS_IPHONE
#else
                if (record_audio_unit) {
                    os_status = AudioUnitUninitialize(record_audio_unit);
                    if (os_status != noErr) {
                        ERR(("%s couldn't uninitialize record audio unit %i", tag, os_status));
                    }
                }
#endif

                if (!new_channel_attached || (newComponentSubType != componentSubType)) {
                    ERR(("%s update_dsp AudioComponentInstanceDispose", tag));
                    os_status = AudioComponentInstanceDispose(audio_unit);
                    if (os_status != noErr) {
                        ERR(("%s couldn't dispose audio unit %i", tag, os_status));
                    }
                    audio_unit = 0;
#if TARGET_OS_IPHONE
#else
                    if (record_audio_unit) {
                        os_status = AudioComponentInstanceDispose(record_audio_unit);
                        if (os_status != noErr) {
                            ERR(("%s couldn't dispose record audio unit %i", tag, os_status));
                        }
                        record_audio_unit = 0;
                    }
                    if (input_audio_converter) {
                        os_status = AudioConverterDispose(input_audio_converter);
                        if (os_status != noErr) {
                            ERR(("%s couldn't dispose input audio converter %i", tag, os_status));
                        }
                        input_audio_converter = 0;
                    }
                    if (output_audio_converter) {
                        os_status = AudioConverterDispose(output_audio_converter);
                        if (os_status != noErr) {
                            ERR(("%s couldn't dispose output audio converter %i", tag, os_status));
                        }
                        output_audio_converter = 0;
                    }
#endif
                }
            }
            start_watchdog = START_WATCHDOG_SAMPLES;
        }
        if(audio_session && !new_channel_attached) {
            if(!audio_session_preconfigured) {
                ERR(("%s update_dsp releaseAudioSession", tag));
                os_status = releaseAudioSession();
                if (os_status != noErr) {
                    ERR(("%s couldn't release audio session %i", tag, os_status));
                }
            }
            audio_session = false;
        }
        ERR(("%s update_dsp done", tag));
    }
    free(device_id);
    device_id = _strdup(new_device_id);
    componentSubType = newComponentSubType;
    record_sample_rate_id = new_record_sample_rate_id;
    playback_sample_rate_id = new_playback_sample_rate_id;

    if(new_channel_attached) {
        if(!audio_session || !audio_unit || sample_rate_changed || device_id_changed) {
            if (pthread_create(&start_thread_id, NULL, (void*(*)(void*))audio_start_thread, this) != 0) {
                ERR(("%s Cannot start audio_start_thread", tag));
            }
            else {
                start_thread_running = true;
                channel_attached = true;
                return;
            }
        }
        if(!audio_unit || stop_dsp) {
            if(!audio_unit || sample_rate_changed || device_id_changed) {
                start_thread();
            }
            else {
                memset(audio_play_buffer, 0, AUDIO_PLAY_BUFFER_SAMPLES * sizeof(AudioUnitSampleType));
                ERR(("%s update_dsp AudioOutputUnitStart", tag));
                os_status = AudioOutputUnitStart(audio_unit);
                if (os_status != noErr) {
                    ERR(("%s couldn't start audio unit %i", tag, os_status));
                }
#if TARGET_OS_IPHONE
#else
                if (record_audio_unit) {
                    os_status = AudioOutputUnitStart(record_audio_unit);
                    if (os_status != noErr) {
                        ERR(("%s couldn't start record audio unit %i", tag, os_status));
                    }
                }
#endif
                start_watchdog = 0;
                ERR(("%s update_dsp done", tag));
            }
        }
    }
    if(channel_attached ^ new_channel_attached) {
        channel_attached = new_channel_attached;
    }
}

void ios_dsp::start_thread()
{
    OSStatus os_status;
    UInt32 propSize;

    if(!audio_session) {
        audio_io_in_doorbell = 0;
        audio_io_in_doorbell_ack = 0;
        audio_io_out_doorbell = 0;
        audio_io_out_doorbell_ack = 0;
        if(!audio_session_preconfigured) {
            ns_log("%s setupAudioSession %i %i", tag, RECV_FRAGMENTATION_SAMPLES, sample_rate_table[record_sample_rate_id]);
            os_status = setupAudioSession(RECV_FRAGMENTATION_SAMPLES, sample_rate_table[record_sample_rate_id]);
            if (os_status != noErr) {
                ns_log("%s couldn't setup audio session %i", tag, os_status);
            }
        }
        audio_session = true;
    }
    if(!audio_unit) {
        AudioComponentDescription desc;
        memset(&desc, 0, sizeof(desc));
        desc.componentType = kAudioUnitType_Output;
        desc.componentSubType = componentSubType;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;

#if TARGET_OS_IPHONE

        AudioComponent comp = AudioComponentFindNext(NULL, &desc);
        ns_log("%s update_dsp AudioComponentInstanceNew", tag);
        os_status = AudioComponentInstanceNew(comp, &audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't create audio unit %i", tag, os_status);
        }

#else
        AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyTranslateUIDToDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
        UInt32 ioSize;
        CFStringRef str;
        AudioDeviceID deviceID, inputDeviceID, outputDeviceID, defaultInputDeviceID, defaultOutputDeviceID;
        UInt32 param;
        char *p;

        ns_log("%s update_dsp DefaultDevice", tag);
        param = sizeof(AudioDeviceID);
        os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &param, &defaultInputDeviceID);
        if(os_status != noErr) {
            ns_log("%s couldn't get default input device %i", tag, os_status);
        }
        param = sizeof(AudioDeviceID);
        os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &param, &defaultOutputDeviceID);
        if(os_status != noErr) {
            ns_log("%s couldn't get default output device %i", tag, os_status);
        }

        AudioComponent comp = AudioComponentFindNext(NULL, &desc);

        ioProperties.mSelector = kAudioHardwarePropertyTranslateUIDToDevice;
        ioProperties.mScope = kAudioObjectPropertyScopeGlobal;
        p = strchr(device_id, '\n');
        if (p) {
            *p = '\0';
            p++;
        }
        inputDeviceID = defaultInputDeviceID;
        outputDeviceID = defaultOutputDeviceID;
        if (device_id[0] != '\0') {
            str = CFStringCreateWithCString(kCFAllocatorDefault, device_id, kCFStringEncodingUTF8);
            ioSize = sizeof(AudioDeviceID);
            os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, sizeof(CFStringRef), &str, &ioSize, &deviceID);
            CFRelease(str);
            if ((os_status == noErr) && (deviceID != kAudioObjectUnknown) && (deviceID != defaultInputDeviceID)) {
                os_status = AudioHardwareSetProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(AudioDeviceID), &deviceID);
                if(os_status != noErr) {
                    ns_log("%s couldn't set default input device %i", tag, os_status);
                }
                else {
                    inputDeviceID = deviceID;
                }
            }
        }
        if (p && (*p != '\0')) {
            str = CFStringCreateWithCString(kCFAllocatorDefault, p, kCFStringEncodingUTF8);
            ioSize = sizeof(AudioDeviceID);
            os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, sizeof(CFStringRef), &str, &ioSize, &deviceID);
            CFRelease(str);
            if ((os_status == noErr) && (deviceID != kAudioObjectUnknown) && (deviceID != defaultOutputDeviceID)) {
                os_status = AudioHardwareSetProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(AudioDeviceID), &deviceID);
                if(os_status != noErr) {
                    ns_log("%s couldn't set default output device %i", tag, os_status);
                }
                else {
                    outputDeviceID = deviceID;
                }
            }
        }
        if (p) {
            p--;
            *p = '\n';
        }

        ns_log("%s update_dsp AudioComponentInstanceNew", tag);
        os_status = AudioComponentInstanceNew(comp, &audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't create audio unit %i", tag, os_status);
        }

        if (desc.componentSubType != kAudioUnitSubType_VoiceProcessingIO) {
            os_status = AudioComponentInstanceNew(comp, &record_audio_unit);
            if (os_status != noErr) {
                ns_log("%s couldn't create record audio unit %i", tag, os_status);
            }
        }
        ns_log("%s update_dsp DefaultDevice", tag);
        if (inputDeviceID != defaultInputDeviceID) {
            os_status = AudioHardwareSetProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(AudioDeviceID), &defaultInputDeviceID);
            if(os_status != noErr) {
                ns_log("%s couldn't set default input device %i", tag, os_status);
            }
        }
        if (outputDeviceID != defaultOutputDeviceID) {
            os_status = AudioHardwareSetProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(AudioDeviceID), &defaultOutputDeviceID);
            if(os_status != noErr) {
                ns_log("%s couldn't set default output device %i", tag, os_status);
            }
        }
#endif
    }

    //  Enable input and output on audio unit
    //  Input is enabled on the input scope of the input element
    //  Output is enabled on the output scope of the output element

    ns_log("%s update_dsp EnableIO", tag);
    UInt32 one = 1;
#if TARGET_OS_IPHONE
    os_status = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one));
#else
    os_status = AudioUnitSetProperty(record_audio_unit ? record_audio_unit : audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one));
#endif
    if (os_status != noErr) {
        ns_log("%s couldn't enable audio unit input %i", tag, os_status);
    }
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        UInt32 zero = 0;
        os_status = AudioUnitSetProperty(record_audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &zero, sizeof(zero));
        if (os_status != noErr) {
            ns_log("%s couldn't disable record audio unit output %i", tag, os_status);
        }
    }
#endif
    os_status = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &one, sizeof(one));
    if (os_status != noErr) {
        ns_log("%s couldn't enable audio unit output %i", tag, os_status);
    }

#if TARGET_OS_IPHONE
#else
    AudioDeviceID inputDeviceID, outputDeviceID;
    inputDeviceID = 0;
    outputDeviceID = 0;
    if (record_audio_unit) {
        AudioObjectPropertyAddress ioProperties = { kAudioHardwarePropertyTranslateUIDToDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
        UInt32 ioSize;
        CFStringRef str;
        AudioDeviceID deviceID, defaultInputDeviceID, defaultOutputDeviceID;
        UInt32 param;
        char *p;

        param = sizeof(AudioDeviceID);
        os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, &param, &defaultInputDeviceID);
        if(os_status != noErr) {
            ns_log("%s couldn't get default input device %i", tag, os_status);
        }
        param = sizeof(AudioDeviceID);
        os_status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &param, &defaultOutputDeviceID);
        if(os_status != noErr) {
            ns_log("%s couldn't get default output device %i", tag, os_status);
        }
        p = strchr(device_id, '\n');
        if (p) {
            *p = '\0';
            p++;
        }
        inputDeviceID = defaultInputDeviceID;
        outputDeviceID = defaultOutputDeviceID;
        if (device_id[0] != '\0') {
            str = CFStringCreateWithCString(kCFAllocatorDefault, device_id, kCFStringEncodingUTF8);
            ioSize = sizeof(AudioDeviceID);
            os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, sizeof(CFStringRef), &str, &ioSize, &deviceID);
            CFRelease(str);
        }
        else {
            deviceID = defaultInputDeviceID;
        }
        if ((os_status == noErr) && (deviceID != kAudioObjectUnknown)) {
            os_status = AudioUnitSetProperty(record_audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 1, &deviceID, sizeof(deviceID));
            if(os_status != noErr) {
                ns_log("%s couldn't set input device %i", tag, os_status);
            }
            else {
                inputDeviceID = deviceID;
            }
        }
        if (p && (*p != '\0')) {
            str = CFStringCreateWithCString(kCFAllocatorDefault, p, kCFStringEncodingUTF8);
            ioSize = sizeof(AudioDeviceID);
            os_status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &ioProperties, sizeof(CFStringRef), &str, &ioSize, &deviceID);
            CFRelease(str);
        }
        else {
            deviceID = defaultOutputDeviceID;
        }
        if ((os_status == noErr) && (deviceID != kAudioObjectUnknown)) {
            os_status = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceID, sizeof(deviceID));
            if(os_status != noErr) {
                ns_log("%s couldn't set output device %i", tag, os_status);
            }
            else {
                outputDeviceID = deviceID;
            }
        }
        if (p) {
            p--;
            *p = '\n';
        }
    }
#endif

    // Explicitly set the input and output client formats
    ns_log("%s update_dsp StreamFormat", tag);
    AudioStreamBasicDescription ioFormat;
    memset(&ioFormat, 0, sizeof(ioFormat));
    ioFormat.mFormatID = kAudioFormatLinearPCM;
    ioFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;
    ioFormat.mFramesPerPacket = 1;
    ioFormat.mChannelsPerFrame = 1;
    ioFormat.mBitsPerChannel = sizeof(AudioUnitSampleType) * 8;
    ioFormat.mBytesPerFrame = (ioFormat.mBitsPerChannel / 8) * ioFormat.mChannelsPerFrame;
    ioFormat.mBytesPerPacket = ioFormat.mBytesPerFrame * ioFormat.mFramesPerPacket;
    ioFormat.mReserved = 0;

    input_sample_rate = sample_rate_table[record_sample_rate_id];
    output_sample_rate = sample_rate_table[playback_sample_rate_id];
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        AudioObjectPropertyAddress ioProperties = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
        Float64 inputSampleRate, outputSampleRate;
                                                  
        propSize = sizeof(Float64);
        os_status = AudioObjectGetPropertyData(inputDeviceID, &ioProperties, 0, NULL, &propSize, &inputSampleRate);
        if (os_status != noErr) {
            ns_log("%s couldn't get input sample rate %i", tag, os_status);
        }
        else {
            input_sample_rate = (unsigned int) inputSampleRate;
        }
        propSize = sizeof(Float64);
        os_status = AudioObjectGetPropertyData(outputDeviceID, &ioProperties, 0, NULL, &propSize, &outputSampleRate);
        if (os_status != noErr) {
            ns_log("%s couldn't get output sample rate %i", tag, os_status);
        }
        else {
            output_sample_rate = (unsigned int) outputSampleRate;
        }
        ns_log("%s update_dsp sample rate %u %u %u %u", tag, (unsigned) input_sample_rate, sample_rate_table[record_sample_rate_id], sample_rate_table[playback_sample_rate_id], (unsigned) output_sample_rate);
    }
#endif

    ioFormat.mSampleRate = output_sample_rate;
    os_status = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &ioFormat, sizeof(ioFormat));
    if (os_status != noErr) {
        ns_log("%s couldn't configure audio unit output %i", tag, os_status);
    }
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        os_status = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &ioFormat, sizeof(ioFormat));
        if (os_status != noErr) {
            ns_log("%s couldn't configure audio unit output %i", tag, os_status);
        }
        if (sample_rate_table[playback_sample_rate_id] != output_sample_rate) {
            AudioStreamBasicDescription processFormat;
            memcpy(&processFormat, &ioFormat, sizeof(ioFormat));

            processFormat.mSampleRate = sample_rate_table[playback_sample_rate_id];
            OSStatus os_status = AudioConverterNew(&processFormat, &ioFormat, &output_audio_converter);
            if (os_status != noErr) {
                ns_log("%s couldn't create output audio converter %i", tag, os_status);
            }
        }
    }
#endif

    ioFormat.mSampleRate = input_sample_rate;
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        os_status = AudioUnitSetProperty(record_audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &ioFormat, sizeof(ioFormat));
        if (os_status != noErr) {
            ns_log("%s couldn't configure audio unit input %i", tag, os_status);
        }
        os_status = AudioUnitSetProperty(record_audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &ioFormat, sizeof(ioFormat));
        if (os_status != noErr) {
            ns_log("%s couldn't configure audio unit input %i", tag, os_status);
        }
        if (sample_rate_table[record_sample_rate_id] != input_sample_rate) {
            AudioStreamBasicDescription processFormat;
            memcpy(&processFormat, &ioFormat, sizeof(ioFormat));

            processFormat.mSampleRate = sample_rate_table[record_sample_rate_id];
            OSStatus os_status = AudioConverterNew(&ioFormat, &processFormat, &input_audio_converter);
            if (os_status != noErr) {
                ns_log("%s couldn't create input audio converter %i", tag, os_status);
            }
        }
    }
    else
#endif
    {
        os_status = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &ioFormat, sizeof(ioFormat));
        if (os_status != noErr) {
            ns_log("%s couldn't configure audio unit input %i", tag, os_status);
        }
    }

#if TARGET_OS_IPHONE
#else
    if (!record_audio_unit)
#endif
    {
        // Set the MaximumFramesPerSlice property. This property is used to describe to an audio unit the maximum number
        // of samples it will be asked to produce on any single given call to AudioUnitRender
        UInt32 maxFramesPerSlice = RECV_FRAGMENTATION_SAMPLES * (input_sample_rate / 8000);
        os_status = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(UInt32));
        if (os_status != noErr) {
            ns_log("%s couldn't set slice on audio unit input %i", tag, os_status);
        }

        // Get the property value back from audio unit. We are going to use this value to allocate buffers accordingly
        propSize = sizeof(UInt32);
        os_status = AudioUnitGetProperty(audio_unit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, &propSize);
        if (os_status != noErr) {
            ns_log("%s couldn't get slice on audio unit input %i", tag, os_status);
        }
    }

    memset(audio_play_buffer, 0, AUDIO_PLAY_BUFFER_SAMPLES * sizeof(AudioUnitSampleType));
    recordIoDataList.mBuffers[0].mNumberChannels = 1;
    recordIoDataList.mBuffers[0].mData = recordSampleBuffer;
    recordIoDataList.mBuffers[0].mDataByteSize = 0;
    recordIoDataList.mNumberBuffers = 1;
    // Set the input callback on audio unit
    AURenderCallbackStruct inputCallback;
    memset(&inputCallback, 0, sizeof(inputCallback));
    inputCallback.inputProc = performInput;
    inputCallback.inputProcRefCon = this;
#if TARGET_OS_IPHONE
    os_status = AudioUnitSetProperty(audio_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &inputCallback, sizeof(inputCallback));
#else
    os_status = AudioUnitSetProperty(record_audio_unit ? record_audio_unit : audio_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &inputCallback, sizeof(inputCallback));
#endif
    if (os_status != noErr) {
        ns_log("%s couldn't set input callback on audio unit %i", tag, os_status);
    }
    // Set the render callback on audio unit
    AURenderCallbackStruct renderCallback;
    memset(&renderCallback, 0, sizeof(renderCallback));
    renderCallback.inputProc = performRender;
    renderCallback.inputProcRefCon = this;
    os_status = AudioUnitSetProperty(audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &renderCallback, sizeof(renderCallback));
    if (os_status != noErr) {
        ns_log("%s couldn't set callback on audio unit %i", tag, os_status);
    }

    // Initialize the audio unit instance
    ns_log("%s update_dsp AudioUnitInitialize", tag);
    os_status = AudioUnitInitialize(audio_unit);
    if (os_status != noErr) {
        ns_log("%s couldn't initialize audio unit %i", tag, os_status);
    }
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        os_status = AudioUnitInitialize(record_audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't initialize record audio unit %i", tag, os_status);
        }
    }
#endif

    ns_log("%s update_dsp AudioOutputUnitStart", tag);
    os_status = AudioOutputUnitStart(audio_unit);
    if (os_status != noErr) {
        ns_log("%s couldn't start audio unit %i", tag, os_status);
    }
#if TARGET_OS_IPHONE
#else
    if (record_audio_unit) {
        os_status = AudioOutputUnitStart(record_audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't start record audio unit %i", tag, os_status);
        }
    }

    ns_log("%s update_dsp AudioOutputUnitStop", tag);
    os_status = AudioOutputUnitStop(audio_unit);
    if (os_status != noErr) {
        ns_log("%s couldn't cycle audio unit %i", tag, os_status);
    }
    if (record_audio_unit) {
        os_status = AudioOutputUnitStop(record_audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't cycle record audio unit %i", tag, os_status);
        }
    }
    ns_log("%s update_dsp AudioOutputUnitStart", tag);
    os_status = AudioOutputUnitStart(audio_unit);
    if (os_status != noErr) {
        ns_log("%s couldn't recycle audio unit %i", tag, os_status);
    }
    if (record_audio_unit) {
        os_status = AudioOutputUnitStart(record_audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't recycle record audio unit %i", tag, os_status);
        }
    }
#endif
    ns_log("%s update_dsp done", tag);
    start_watchdog = 0;
}

extern "C" void ios_start_audio_unit(void *context)
{
    class ios_dsp *thiz = (class ios_dsp *)context;

    if (thiz->audio_unit) {
        OSStatus os_status = AudioOutputUnitStart(thiz->audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't restart audio unit %i", thiz->tag, os_status);
        }
#if TARGET_OS_IPHONE
#else
        if (thiz->record_audio_unit) {
            os_status = AudioOutputUnitStart(thiz->record_audio_unit);
            if (os_status != noErr) {
                ns_log("%s couldn't restart record audio unit %i", thiz->tag, os_status);
            }
        }
#endif
        thiz->audio_io_out_doorbell ^= OUT_DOORBELL_RECORD_STOP | OUT_DOORBELL_PLAY_STOP;
    }
}

extern "C" void ios_stop_audio_unit(void *context)
{
    class ios_dsp *thiz = (class ios_dsp *)context;

    if (thiz->audio_unit) {
        OSStatus os_status = AudioOutputUnitStop(thiz->audio_unit);
        if (os_status != noErr) {
            ns_log("%s couldn't pause audio unit %i", thiz->tag, os_status);
        }
#if TARGET_OS_IPHONE
#else
        if (thiz->record_audio_unit) {
            os_status = AudioOutputUnitStop(thiz->record_audio_unit);
            if (os_status != noErr) {
                ns_log("%s couldn't pause record audio unit %i", thiz->tag, os_status);
            }
        }
#endif
        thiz->audio_io_out_doorbell ^= OUT_DOORBELL_RECORD_STOP | OUT_DOORBELL_PLAY_STOP;
    }
}

extern "C" void ios_restart_dsp(void *context)
{
    class ios_dsp *thiz = (class ios_dsp *)context;

    thiz->update_dsp(true);
}

void ios_dsp::init()
{
}

void ios_dsp::finit()
{
}

void ios_dsp::StartDevice(const char *deviceId, enum AudioDeviceMode deviceMode, unsigned micSampleRate, unsigned spkSampleRate)
{
    unsigned id;

    ERR(("%s StartDevice %s %u %u %u", tag, deviceId, deviceMode, micSampleRate, spkSampleRate));
    new_record_sample_rate_id = 0;
    while (sample_rate_table[new_record_sample_rate_id] != micSampleRate) {
        new_record_sample_rate_id++;
    }
    new_playback_sample_rate_id = 0;
    while (sample_rate_table[new_playback_sample_rate_id] != spkSampleRate) {
        new_playback_sample_rate_id++;
    }
    id = 0;
    if (deviceId) {
        while ((id < AudioDeviceCount) && (strcmp(deviceId, AudioDevices[id].id) != 0)) {
            id++;
        }
        if (id == AudioDeviceCount) {
            id = 0;
        }
    }
    free(new_device_id);
    new_device_id = _strdup(deviceId ? deviceId : "");
    if (id == 0) {
        new_headset = (!new_handset && !new_speaker && !new_headset);
        new_handset = true;
        new_speaker = false;
    }
    else {
        new_handset = (AudioDevices[id].type == AudioDeviceType::HandsetDevice);
        new_speaker = (AudioDevices[id].type == AudioDeviceType::HandsfreeDevice);
        new_headset = (AudioDevices[id].type == AudioDeviceType::HeadsetDevice);
    }

    codec_mode_changed(ringing);
    update_dsp(false);
    audio_record_pos_ack = audio_record_pos;
    audio_play_pos_ack = audio_play_pos;
    audio_play_fill = audio_play_pos;
    process_timer.Start(1);
}

void ios_dsp::StopDevice(const char *deviceId)
{
    if (!delayed_off_timer.IsRunning() && (new_handset || new_speaker || new_headset)) {
        ERR(("%s StopDevice delay", tag));
        delayed_off_timer.Start(1000);
    }
}

void ios_dsp::TimerOnTimeout(ios_dsp_delayed_off_timer * timer)
{
    ERR(("%s Codec mode change after delay", tag));
    free(new_device_id);
    new_device_id = _strdup("");
    if (new_handset || new_speaker || new_headset) {
        new_handset = false;
        new_speaker = false;
        new_headset = false;

        codec_mode_changed(ringing);
        update_dsp(true);
        process_timer.Cancel();
    }
}

void ios_dsp::AudioExec(unsigned int mic_samples, unsigned int spk_samples)
{
    short spk_sample_buffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
    unsigned int i, j, n;

/* don't to get 6 dB gain
    for (i = 0; i < mic_samples; i++) {
        mic_sample_ptr[i] >>= 1;
    }
*/
    spk_sample_ptr = spk_sample_buffer;

    user->AudioExec(this);

    for (i = 0; i < spk_samples; i++) {
        spk_sample_ptr[i] = (spk_sample_ptr[i] > 16383) ? 32767 : ((spk_sample_ptr[i] < -16384) ? -32768 : spk_sample_ptr[i] << 1);
    }
    i = 0;
    while (i < spk_samples) {
        n = spk_samples - i;
        if (n > AUDIO_PLAY_BUFFER_SAMPLES - audio_play_fill) {
            n = AUDIO_PLAY_BUFFER_SAMPLES - audio_play_fill;
        }
        for (j = 0; j < n; j++) {
#if TARGET_OS_IPHONE
            audio_play_buffer[audio_play_fill+j] = ((AudioUnitSampleType)(spk_sample_ptr[i+j])) << 9;
#else
            audio_play_buffer[audio_play_fill+j] = ((AudioUnitSampleType)(spk_sample_ptr[i+j])) / 32768.0;
#endif
        }
        audio_play_fill = (n < AUDIO_PLAY_BUFFER_SAMPLES - audio_play_fill) ? audio_play_fill + n : 0;
        i += n;
    }
}

#if TARGET_OS_IPHONE
#else
OSStatus input_audio_converter_proc(AudioConverterRef inAudioConverter,
                                    UInt32* ioNumberDataPackets,
                                    AudioBufferList* ioData,
                                    AudioStreamPacketDescription** ioDataPacketDescription,
                                    void* inUserData)
{
    class ios_dsp *thiz = (class ios_dsp *) inUserData;
    unsigned n;

    n = *ioNumberDataPackets;
    if (n > AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos_ack) {
        n = AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos_ack;
        *ioNumberDataPackets = n;
    }
    ioData->mBuffers[0].mNumberChannels = 1;
    ioData->mBuffers[0].mData = &thiz->audio_record_buffer[thiz->audio_record_pos_ack];
    ioData->mBuffers[0].mDataByteSize = n * sizeof(AudioUnitSampleType);
    ioData->mNumberBuffers = 1;
    thiz->audio_record_pos_ack = ((n < AUDIO_RECORD_BUFFER_SAMPLES - thiz->audio_record_pos_ack) ? thiz->audio_record_pos_ack : thiz->audio_record_pos_ack - AUDIO_RECORD_BUFFER_SAMPLES) + n;
    return noErr;
}
#endif

void ios_dsp::TimerOnTimeout(ios_dsp_process_timer * timer)
{
    short mic_sample_buffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
    unsigned int i, j, n, record_samples, play_samples, mic_samples, spk_samples;
    OSStatus os_status;

    mic_samples = execInterval8khz * (sample_rate_table[record_sample_rate_id] / 8000);
    spk_samples = execInterval8khz * (sample_rate_table[playback_sample_rate_id] / 8000);
    i = audio_record_pos;
    record_samples = ((i >= audio_record_pos_ack) ? i : i + AUDIO_RECORD_BUFFER_SAMPLES) - audio_record_pos_ack;
    record_samples = (record_samples * sample_rate_table[record_sample_rate_id]) / input_sample_rate;
    i = record_samples;
    play_samples = 0;
    while (i >= mic_samples) {
        play_samples += spk_samples;
        i -= mic_samples;
    }
    i = audio_play_pos;
    n = ((audio_play_fill >= i) ? audio_play_fill : audio_play_fill + AUDIO_PLAY_BUFFER_SAMPLES) - i;
    if (n > sample_overrun_threshold * (sample_rate_table[playback_sample_rate_id] / 8000) - play_samples) {
        ERR(("%s adjust %u", tag, n));
        audio_play_fill = i + sample_overrun_threshold * (sample_rate_table[playback_sample_rate_id] / 8000) - play_samples;
        if (audio_play_fill >= AUDIO_PLAY_BUFFER_SAMPLES) {
            audio_play_fill -= AUDIO_PLAY_BUFFER_SAMPLES;
        }
    }
    while (record_samples >= mic_samples) {
        if (i != audio_play_pos_ack) {
            audio_play_pos_ack = i;
            start_watchdog = START_WATCHDOG_SAMPLES;
        }
#if TARGET_OS_IPHONE
#else
        if (input_audio_converter) {
            AudioBufferList ioData;
            AudioUnitSampleType micResampleBuffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
            ioData.mBuffers[0].mNumberChannels = 1;
            ioData.mBuffers[0].mData = micResampleBuffer;
            ioData.mBuffers[0].mDataByteSize = mic_samples * sizeof(AudioUnitSampleType);
            ioData.mNumberBuffers = 1;
            UInt32 converted_samples = mic_samples;
            os_status = AudioConverterFillComplexBuffer(input_audio_converter,
                                                        input_audio_converter_proc,
                                                        this,
                                                        &converted_samples,
                                                        &ioData,
                                                        NULL /* AudioStreamPacketDescription *outPacketDescription */);
            if ((os_status != noErr) || (converted_samples != mic_samples)) {
                ERR(("%s couldn't convert input audio %i %u %u", tag, os_status, converted_samples, mic_samples));
            }
            for (i = 0; i < mic_samples; i++) {
#if TARGET_OS_IPHONE
                AudioUnitSampleType l = micResampleBuffer[i];
                mic_sample_buffer[i] = (l > 16777215) ? 32767 : ((l < -16777216) ? -32768 : (short)(l >> 9));
#else
                mic_sample_buffer[i] = (short)(micResampleBuffer[i] * 32768.0 + 0.5);
#endif
            }
        }
        else
#endif
        {
            i = 0;
            while (i < mic_samples) {
                n = mic_samples - i;
                if (n > AUDIO_RECORD_BUFFER_SAMPLES - audio_record_pos_ack) {
                    n = AUDIO_RECORD_BUFFER_SAMPLES - audio_record_pos_ack;
                }
                for (j = 0; j < n; j++) {
#if TARGET_OS_IPHONE
                    AudioUnitSampleType l = audio_record_buffer[audio_record_pos_ack+j];
                    mic_sample_buffer[i+j] = (l > 16777215) ? 32767 : ((l < -16777216) ? -32768 : (short)(l >> 9));
#else
                    mic_sample_buffer[i+j] = (short)(audio_record_buffer[audio_record_pos_ack+j] * 32768.0 + 0.5);
#endif
                }
                audio_record_pos_ack = (audio_record_pos_ack < AUDIO_RECORD_BUFFER_SAMPLES - n) ? audio_record_pos_ack + n : 0;
                i += n;
            }
        }
        mic_sample_ptr = mic_sample_buffer;

        AudioExec(mic_samples, spk_samples);

        record_samples -= mic_samples;
    }
    if (start_watchdog < START_WATCHDOG_SAMPLES) {
        if (start_watchdog + execInterval8khz < START_WATCHDOG_SAMPLES) {
            start_watchdog += execInterval8khz;
        }
        else {
            ERR(("%s audio start watchdog triggered", tag));
            memset(audio_play_buffer, 0, AUDIO_PLAY_BUFFER_SAMPLES * sizeof(AudioUnitSampleType));
            if (start_thread_running) {
                int err = pthread_join(start_thread_id, NULL);
                if (err != 0) {
                    ERR(("%s couldn't stop audio start thread %s", tag, strerror(err)));
                }
                start_thread_running = false;
            }
            // Uninitialize the audio unit instance
            os_status = AudioUnitUninitialize(audio_unit);
            if (os_status != noErr) {
                ERR(("%s couldn't uninitialize audio unit %i", tag, os_status));
            }
#if TARGET_OS_IPHONE
#else
            if (record_audio_unit) {
                os_status = AudioUnitUninitialize(record_audio_unit);
                if (os_status != noErr) {
                    ERR(("%s couldn't uninitialize record audio unit %i", tag, os_status));
                }
            }
#endif
            os_status = AudioComponentInstanceDispose(audio_unit);
            if (os_status != noErr) {
                ERR(("%s couldn't dispose audio unit %i", tag, os_status));
            }
            audio_unit = 0;
#if TARGET_OS_IPHONE
#else
            if (record_audio_unit) {
                os_status = AudioComponentInstanceDispose(record_audio_unit);
                if (os_status != noErr) {
                    ERR(("%s couldn't dispose record audio unit %i", tag, os_status));
                }
                record_audio_unit = 0;
            }
            if (input_audio_converter) {
                os_status = AudioConverterDispose(input_audio_converter);
                if (os_status != noErr) {
                    ERR(("%s couldn't dispose input audio converter %i", tag, os_status));
                }
                input_audio_converter = 0;
            }
            if (output_audio_converter) {
                os_status = AudioConverterDispose(output_audio_converter);
                if (os_status != noErr) {
                    ERR(("%s couldn't dispose output audio converter %i", tag, os_status));
                }
                output_audio_converter = 0;
            }
#endif
            start_watchdog = START_WATCHDOG_SAMPLES;
            update_dsp(false);
        }
    }
    process_timer.Start(execInterval8khz >> 3);
}

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