/*---------------------------------------------------------------------------*/
/* audio_io.cpp                                                              */
/* copyright (c) innovaphone 2017                                            */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"

#include "common/audio/soft_dsp/sdsputil.h"
#include "common/interface/socket.h"
#include "common/interface/audio.h"
#include "common/interface/media.h"
#include "common/interface/local_storage.h"

#include "common/ilib/json.h"

#include "audio_io.h"

#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,
};

#define AUDIO_IO_EXP_TABLE_SIZE_EXP     4
#define AUDIO_IO_EXP_TABLE_SIZE         (16 + 1)

static const word audio_io_exp_table[AUDIO_IO_EXP_TABLE_SIZE] =
{
    0x8000, 0x85ab, 0x8b96, 0x91c4, 0x9838, 0x9ef5, 0xa5ff, 0xad58,
    0xb505, 0xbd09, 0xc567, 0xce25, 0xd745, 0xe0cd, 0xeac1, 0xf525,
    0x0000
};


static word audio_io_exp (short x, short bias_exp)
/*
  (x == 0x8000) ? 0 : 2 ^ (x / 0x0600 - bias_exp)
*/
{
    int32 l;
    short s;
    word j;

    if (x == -32768)
    return (0);
    l = (((int32)(((int32) x) * ((int32) 0x5556))) >> 15) +
    ((((int32) x) * ((int32) 0x0015)) << 1);
    s = bias_exp - ((short)(l >> 16));
    j = (word)((l & 0xffff) >> (16 - AUDIO_IO_EXP_TABLE_SIZE_EXP));
    j = audio_io_exp_table[j] + ((word)(((dword)(((dword)((l << AUDIO_IO_EXP_TABLE_SIZE_EXP) & 0xffff)) *
        ((dword)((word)(audio_io_exp_table[j+1] - audio_io_exp_table[j]))))) >> 16));
    if (s > 0)
        j = (s > 15) ? 0 : ((word)(((dword)(((dword) j) * ((dword)(0x8000 >> s)) + 0x4000)) >> 15));
    return (j);
}

#define AUDIO_IO_DBOV_CORRECTION  (4 * 256)

static word audio_io_amplitude (short x)
{
    if (x <= -32768 + AUDIO_IO_DBOV_CORRECTION + 0x600)
        return (0);
    if (x >= AUDIO_IO_DBOV_CORRECTION + 0x600)
        return 0x8000;
    return (audio_io_exp(x - (AUDIO_IO_DBOV_CORRECTION + 0x600), 0));
}

static word audio_io_modulation (short x, short y)
{
    if (y - x - 0x600 <= -32768)
        return (0);
    if (y - x - 0x600 >= 0)
        return 0x8000;
    return (audio_io_exp(y - x - 0x600, 0));
}

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

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

/*---------------------------------------------------------------------------*/
/* AudioIo                                                                   */
/*---------------------------------------------------------------------------*/

void AudioIo::IoExec(AudioIoUpdateDsp * context)
{
    update_dsp(NULL);
    execCnt--;
}

void AudioIo::IoExec(AudioIoClose * context)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::IoExec execCnt=%d shutDown=%d remoteUsers=%d", this, execCnt, shutDown, remoteDeviceUsers.empty());

    execCnt--;
    if(shutDown && !execCnt) {
        if (remoteDeviceUsers.empty()) user->CloseAudioIoComplete();
        else {
            execCnt++;
            SetExec(iomux, this, (AudioIoClose*)NULL);
        }
    }
}

AudioIo::AudioIo(class IIoMux * const iomux, class IAudioPhone * const audioPhone, class IInstanceLog * const log, unsigned execInterval8khz, class MediaConfiguration * mediaConfiguration)
{
    this->iomux = iomux;
    this->audioPhone = audioPhone;
    this->log = log;
    this->execInterval8khz = execInterval8khz;
    this->deviceIoUser = NULL;
    this->selectDeviceUser = NULL;
    this->user = NULL;
    this->mediaConfiguration = mediaConfiguration;
    deviceId = 0;
    deviceMode = AudioDeviceMode::AudioModeCommunication;
    deviceStarted = false;
    record_sample_rate_id = SAMPLE_RATE_ID_8000;
    play_sample_rate_id = SAMPLE_RATE_ID_8000;
    channel_attached = false;
    used_channels = 0;
    allocated_channels = 16;
    channels = (struct audioIoCh *)(malloc(allocated_channels * sizeof(struct audioIoCh)));
    microphoneMuted = false;
    speakerMuted = false;
    tone_active = false;
    allocatedTones = 16;
    toneQueue = (struct AudioIoDualTone *)(malloc(allocatedTones * sizeof(struct AudioIoDualTone)));
    toneLastSample = 0;
    toneTime = 0;
    tonePhase0 = 0;
    tonePhase1 = 0;
    loop_test = false;
    loop_test_on = false;
    shutDown = false;
    execCnt = 0;
    softphoneApp = NULL;
    selectedDeviceRemoved = false;

    ReadSelectedDevices();

    if(audioPhone) audioPhone->Initialize(this);
}

AudioIo::~AudioIo()
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::~AudioIo", this);

    if (deviceId) {
        free (deviceId);
    }
    free(channels);
    free(toneQueue);
    if (softphoneApp) free(softphoneApp);
    while(devicesList.empty() == false) delete devicesList.front();
    while(selectedDevices.empty() == false) delete selectedDevices.front();
}

void AudioIo::ReadSelectedDevices()
{
    char * hookDevice = NULL;
    while (selectedDevices.empty() == false) {
        class SelectedDeviceIo * dev = selectedDevices.front();
        if (dev->activeHookDevice && !hookDevice) hookDevice = _strdup(dev->device);
        delete dev;
    }

    char* audioSelectedDevicesStr = ILocalStorage::GetItem("audioSelectedDevices");
    if (audioSelectedDevicesStr) {
        if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AudioIo ReadSelectedDevices (%s)", this, audioSelectedDevicesStr);
        class json_io jsonDevices(audioSelectedDevicesStr);
        if (jsonDevices.decode()) {
            word c_last = 0, t, end = 0xFFFF;
            word base = jsonDevices.get_object(JSON_ID_ROOT, 0);
            word devsArray = jsonDevices.get_array(base, "devices");
            while ((t = jsonDevices.get_object(devsArray, c_last)) != end) {
                if (jsonDevices.get_string(t, "device") && jsonDevices.get_string(t, "app")) {
                    class SelectedDeviceIo* dev = new class SelectedDeviceIo(jsonDevices.get_string(t, "app"), jsonDevices.get_string(t, "device"), jsonDevices.get_int(t, "type"), jsonDevices.get_bool(t, "remote"));
#ifndef IP270
                    selectedDevices.push_back(dev);
#endif
                    if (hookDevice && !strcmp(hookDevice, dev->device)) {
                        dev->activeHookDevice = true;
                    }
                }
            }
        }
        else if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::ReadSelectedDevices failed to decode json", this);
        free(audioSelectedDevicesStr);
    }
    if (hookDevice) free(hookDevice);
}

void AudioIo::WriteSelectedDevices()
{
    int jsonSize = 128;
    int intSize = 0;
    class SelectedDeviceIo* dev = selectedDevices.front();
    while (dev) {
        jsonSize += strlen(dev->app) + strlen(dev->device) + 64;   // {"app":"","device":"","type":0,"remote":false},
        intSize += 32;
        dev = dev->goNext();
    }
    char temp[128];
    char* tmp = temp;
    char* j = (char*)malloc(jsonSize);
    class json_io jsonDevices(j);
    word base = jsonDevices.add_object(0xffff, 0);
    word a = jsonDevices.add_array(base, "devices");
    dev = selectedDevices.front();
    while (dev) {
        word e = jsonDevices.add_object(a, "selectedDevice");
        jsonDevices.add_string(e, "app", dev->app);
        jsonDevices.add_string(e, "device", dev->device);
        jsonDevices.add_int(e, "type", dev->type, tmp);
        jsonDevices.add_bool(e, "remote", dev->isRemote);
        dev = dev->goNext();
    }
    jsonDevices.encode();
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::WriteSelectedDevices Devices json sizes=%d,%d (%s)", this, jsonSize, intSize, j);
    ILocalStorage::SetItem("audioSelectedDevices", j);
    free(j);
}

void AudioIo::Close(class UAudioIo * user)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::Close audioPhone=%p used_channels=%d", this, audioPhone, used_channels);

    this->shutDown = true;

    if (!this->user) this->user = user;

    // All channels (calls) must be closed first
    struct audioIoCh* ch = channels;
    while (ch < &channels[used_channels]) {
        if (ch->p) {
            if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::Close channels still active ch=%p ch->p=%p attached=%d initialized=%d opened=%d", this, ch, ch->p, ch->channel_attached, ch->channel_initialized, ch->channel_opened);
            return;
        }
        ch++;
    }

#ifndef IP270
    WriteSelectedDevices();
#endif // !IP270

    if(audioPhone) audioPhone->Close();
    else {
        execCnt++;
        SetExec(iomux, this, (AudioIoClose *)NULL);
    }
}

void AudioIo::AudioDeviceDelayed(const char* deviceIn, const char* deviceOut)
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->AudioDeviceDelayed(this, deviceIn, deviceOut);
            h = h->goNext();
        }
    }
}

void AudioIo::AudioDeviceOpened(const char* deviceIn, const char* deviceOut)
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->AudioDeviceOpened(this, deviceIn, deviceOut);
            h = h->goNext();
        }
    }
}

void AudioIo::CloseAudioPhoneComplete()
{
    audioPhone = NULL; // it will be deleted outside
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::CloseAudioPhoneComplete execCnt=%d", this, execCnt);
    execCnt++;
    SetExec(iomux, this, (AudioIoClose *)NULL);
}

void AudioIo::SelectDevice(const char *app, const char *deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo::SelectDevice app=%s deviceId=%s", app ? app : "", deviceId ? deviceId : "");

    if (!app || !deviceId) return;

    char * hookDevice = NULL;
    class SelectedDeviceIo * dev = NULL;
    if (!selectedDevices.empty()) {
        // Delete device if already exists and allow a maximum number of 10 devices pro SoftphoneApp
        int numSrcDevices = 0;
        dev = selectedDevices.front();
        while (dev) {
            class SelectedDeviceIo* next = dev->goNext();
            if (dev->app && app && !strcmp(dev->app, app)) {
                if ((dev->device && deviceId && !strcmp(dev->device, deviceId)) || (numSrcDevices >= 10)) {
                    if (dev->activeHookDevice && !hookDevice) hookDevice = _strdup(dev->device);
                    dev->remove();
                    delete dev;
                }
                else numSrcDevices++;
            }
            dev = next;
        }
    }
    // Push the new device at the front of the list
    dev = new class SelectedDeviceIo(app, deviceId);
#ifndef IP270
    selectedDevices.push_front(dev);
#endif
    // Check if device is still available
    class AudioDeviceItem * devItem;
    if ((devItem = FindDevice(dev->device)) != NULL) {
        class AudioDeviceItem * temp = NULL;
        for (temp = devicesList.front(); temp; temp = temp->goNext()) {
            temp->isSelected = false;
        }
        dev->available = true;
        dev->type = devItem->type;
        dev->isRemote = devItem->isRemote;
        if (hookDevice && !strcmp(hookDevice, dev->device)) dev->activeHookDevice = true;
        // set it to default deviceId
        if (this->deviceId) free(this->deviceId);
        this->deviceId = _strdup(dev->device);
        if (selectDeviceUser) selectDeviceUser->SelectAudioDevice(this, app, deviceId, devItem->type);
#ifndef IP270
        WriteSelectedDevices();
#endif // !IP270

        devItem->isSelected = true;
    }
    if (hookDevice) free(hookDevice);
}

void AudioIo::ChangeDevice(const char *app, const char *deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::ChangeDevice app=%s deviceId=%s", this, app, deviceId);

    // Check if device is still available
    class AudioDeviceItem* devItem;
    if ((devItem = FindDevice(deviceId)) != NULL) {
        if (selectDeviceUser) selectDeviceUser->ChangeAudioDevice(this, app, deviceId, devItem->type);
    }
}

int AudioIo::GetDeviceIoType(const char* deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::GetDeviceIoType device=%s devices=%d", this, deviceId ? deviceId : "", !selectedDevices.empty());

    class SelectedDeviceIo* dev = NULL;
    if (!selectedDevices.empty()) {
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            if (dev->device && deviceId && !strcmp(dev->device, deviceId)) {
                return dev->type;
            }
        }
    }
    if (!devicesList.empty()) {
        class AudioDeviceItem * dev = devicesList.front();
        while (dev) {
            if (deviceId && dev->deviceId && !strcmp(deviceId, dev->deviceId)) {
                return dev->type;
            }
            dev = dev->goNext();
        }
    }
    return 0;
}

const char * AudioIo::GetDeviceIo(const char * app)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::GetDeviceIo app=%s deviceId=%s selectedDevices=%d", this, app ? app : "", deviceId, !selectedDevices.empty());

    if (softphoneApp) free(softphoneApp);
    softphoneApp = _strdup(app);

    class SelectedDeviceIo * dev = NULL;
    if (!selectedDevices.empty()) {
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            if (dev->available && dev->app && app && !strcmp(dev->app, app)) {
                class AudioDeviceItem* devItem;
                if ((devItem = FindDevice(dev->device)) != NULL) {
                    if (devItem->type == (int)AudioDeviceType::HeadsetDevice && !dev->activeHookDevice) {
                        if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::GetDeviceIo %s still not started as hook device!", this, dev->device);
                        if (selectDeviceUser) selectDeviceUser->StartHookDevice(dev->device, KEY_ONHOOK, 0, 0, 0);
                    }
                    dev->type = devItem->type;
                    return dev->device;
                }
                else if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::GetDeviceIo dev=%s not available any more", this, dev->device);
            }
        }
    }
    
#ifdef IP270
    const char * deviceId = GetDeviceByType((int)AudioDeviceType::HeadsetDevice);
    if (deviceId)
        return deviceId;
    deviceId = GetDeviceByType((int)AudioDeviceType::HandsfreeDevice);
    if (deviceId)
        return deviceId;
#endif

    // Return first audio device
    if (devicesList.empty() == false) {
        class AudioDeviceItem * dev = devicesList.front();
        if (dev->type == (int)AudioDeviceType::HeadsetDevice && !dev->activeHookDevice) {
            if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::GetDeviceIo no selected devices. %s still not started as hook device!", this, dev->deviceId);
            if (selectDeviceUser) selectDeviceUser->StartHookDevice(dev->deviceId, KEY_ONHOOK, 0, 0, 0);
        }
        return dev->deviceId;
    }
    return NULL;
}

const char * AudioIo::GetDeviceId(unsigned index)
{
    class AudioDeviceItem * item = devicesList.front();
    for (unsigned i = 0; true; i++, item = item->goNext()) {
        if (!item) return nullptr;
        if (i == index) return item->deviceId;
    }
    return nullptr;
}

const char * AudioIo::GetDeviceByType(int type)
{
    class AudioDeviceItem * item = devicesList.front();
    for (; item; item = item->goNext()) {
        if (item->type == type) return item->deviceId;
    }
    return nullptr;
}

class AudioDeviceItem * AudioIo::FindDevice(const char * deviceId)
{
    if(devicesList.empty() == false) {
        class AudioDeviceItem * dev = devicesList.front();
        while(dev) {
            if(!strcmp(deviceId, dev->deviceId)) return dev;
            dev = dev->goNext();
        }
    }
    return NULL;
}

void AudioIo::RemoteMediaIoDeviceAdded(const char* deviceId, int deviceType, unsigned deviceCapabilities, const char* deviceName, bool selected)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::RemoteMediaIoDeviceAdded deviceId=%s deviceIoUser=%p deviceCapabilities=%d deviceType=%d", this, deviceId, deviceIoUser, deviceCapabilities, deviceType);

    class AudioDeviceItem* dev = NULL;
    if (devicesList.empty() == false) {
        dev = devicesList.front();
        while (dev) {
            if (deviceId && dev->deviceId && !strcmp(deviceId, dev->deviceId)) break;
            dev = dev->goNext();
        }
    }
    if (!dev) devicesList.push_back(new AudioDeviceItem(deviceId, (AudioDeviceType)deviceType, deviceCapabilities, deviceName, true));
    else {
        if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::RemoteMediaIoDeviceAdded dev (%s) already available capabilities=%d,%d type=%d,%d remote=%d", this, deviceId, deviceCapabilities, dev->capabilities, deviceType, dev->type, dev->isRemote);
        dev->capabilities = deviceCapabilities;
        dev->type = (enum AudioDeviceType)deviceType;
        dev->isRemote = true;
    }
}

void AudioIo::RemoteMediaIoDeviceRemoved(const char* deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::RemoteMediaIoDeviceRemoved deviceId=%s", this, deviceId);

    if (devicesList.empty() == false) {
        class AudioDeviceItem* dev = devicesList.front();
        while (dev) {
            if (deviceId && dev->deviceId && !strcmp(deviceId, dev->deviceId)) {
                if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, NULL, dev->deviceId);
                if (!selectedDevices.empty()) {
                    class SelectedDeviceIo* sel;
                    for (sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                        if (sel->device && !strcmp(sel->device, dev->deviceId)) {
                            sel->available = false;
                            if (dev->isSelected) {
                                if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::RemoteMediaIoDeviceRemoved selected device was removed (%s)", this, dev->deviceId);
                                selectedDeviceRemoved = true;
                                if (selectDeviceUser) selectDeviceUser->AudioDeviceRemoved(deviceId);
                            }
                        }
                    }
                }
                dev->remove();
                delete dev;
                return;
            }
            dev = dev->goNext();
        }
    }
}

void AudioIo::RemoteMediaIoQueryDevicesResult()
{
    AudioDeviceList(NULL);
}

void AudioIo::CleanRemoteDevices()
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::CleanRemoteDevices", this);

    if (devicesList.empty() == false) {
        class AudioDeviceItem* dev = devicesList.front();
        while (dev) {
            class AudioDeviceItem * next = dev->goNext();
            if (dev->isRemote) {
                if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, NULL, dev->deviceId);
                if (!selectedDevices.empty()) {
                    class SelectedDeviceIo* sel;
                    for (sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                        if (sel->device && !strcmp(sel->device, dev->deviceId)) {
                            sel->available = false;
                            if (dev->isSelected) {
                                if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::CleanRemoteDevices selected device was removed (%s)", this, dev->deviceId);
                                selectedDeviceRemoved = true;
                                if (selectDeviceUser) selectDeviceUser->AudioDeviceRemoved(dev->deviceId);
                            }
                        }
                    }
                }
                dev->remove();
                delete dev;
            }
            dev = next;
        }
    }
}

void AudioIo::AudioDeviceList(void * context)
{
    unsigned i;

    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AudioDeviceList context=%p deviceIoUser=%p,%d local devices=%d", this, context, deviceIoUser, !remoteDeviceUsers.empty(), audioPhone ? audioPhone->DeviceCount() : 0);

    int remoteDevices = 0;
    // let's assume all devices were removed 
    if(devicesList.empty() == false) {
        class AudioDeviceItem * dev = devicesList.front();
        while(dev) {
            if (dev->isRemote == false) dev->wasRemoved = true;
            else {
                dev->isSelected = false;
                remoteDevices++;
            }
            dev = dev->goNext();
        }
    }
    // User may have several SoftphoneApps
    class SelectedDeviceIo* selectedDeviceIoFront = NULL;
    if (selectedDevices.empty() == false) {
        MediaProviderContext* ctx = (MediaProviderContext*)context;
        if (ctx || softphoneApp) {
            class SelectedDeviceIo* dev = selectedDevices.front();
            while (dev) {
                if (dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                    selectedDeviceIoFront = dev;
                    break;
                }
                dev = dev->goNext();
            }
        }
    }
    // Check if any available device is present on the list of selected devices for an specific app
    // context contains the app that made the request
    int firstSelected = -1;
    int selected = -1;
    int devLocalSelected = 0;
    if (selectedDevices.empty() == false) {
        MediaProviderContext * ctx = (MediaProviderContext *)context;
        if (audioPhone && (ctx || softphoneApp)) {
            class SelectedDeviceIo* dev = selectedDevices.front();
            while (dev) {
                for (i = 0; i < audioPhone->DeviceCount(); i++) {
                    if (dev->device && audioPhone->DeviceId(i) && !strcmp(dev->device, audioPhone->DeviceId(i)) && dev->app && ((ctx && ctx->app && !strcmp(dev->app, ctx->app)) || (softphoneApp && !strcmp(dev->app, softphoneApp)))) {
                        if (dev == selectedDeviceIoFront) firstSelected = i;
                        else selected = i;
                        if(log) log->Log(LOG_AUDIO, "AudioIo(%p,%d)::AudioDeviceList native selectedDevice (%s) for (%s) selected=%d,%d", this, i, dev->device, dev->app, firstSelected, selected);
                        // Start of the softphone app
                        if (this->deviceId == NULL) this->deviceId = _strdup(dev->device);
                        break;
                    }
                }
                if (selected >= 0 || firstSelected >= 0) break;
                devLocalSelected++;
                dev = dev->goNext();
            }
        }
    }
    if (remoteDevices == 0 && (!audioPhone || audioPhone->DeviceCount() == 0)) {
        if (context && deviceIoUser) deviceIoUser->MediaIoQueryDevicesResult(this, context);
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                h->user->MediaIoQueryDevicesResult(this, context);
                h = h->goNext();
            }
        }
        return;
    }
    // local devices
    for (i = 0; i < (audioPhone ? audioPhone->DeviceCount() : 0); i++) {
        class AudioDeviceItem * dev = FindDevice(audioPhone->DeviceId(i));
        if(dev == NULL) {
            dev = new AudioDeviceItem(audioPhone->DeviceId(i), audioPhone->DeviceType(i), audioPhone->DeviceCapabilities(i), audioPhone->DeviceName(i));
            devicesList.push_back(dev);
        }
        else {
            dev->wasRemoved = false;
            dev->isSelected = false;
        }
        if ((int)i == firstSelected || (int)i == selected) {
            dev->isSelected = true;
            if (dev->type == (int)AudioDeviceType::HeadsetDevice && dev->activeHookDevice == false && selectDeviceUser) selectDeviceUser->StartHookDevice(dev->deviceId, KEY_ONHOOK, 0, 0, 0);
        }
        if (deviceIoUser) deviceIoUser->MediaIoDeviceAdded(this, context, audioPhone->DeviceId(i), audioPhone->DeviceType(i), audioPhone->DeviceCapabilities(i), audioPhone->DeviceName(i), dev->isSelected);
        if (!remoteDeviceUsers.empty()) {
            class UDeviceIoHelper* h = remoteDeviceUsers.front();
            while (h) {
                h->user->MediaIoDeviceAdded(this, context, audioPhone->DeviceId(i), audioPhone->DeviceType(i), audioPhone->DeviceCapabilities(i), audioPhone->DeviceName(i), dev->isSelected);
                h = h->goNext();
            }
        }
    }
    // remote devices
    if (deviceIoUser) {
        if (firstSelected == -1) {
            MediaProviderContext* ctx = (MediaProviderContext*)context;
            class AudioDeviceItem * dev;
            if (ctx || softphoneApp) {
                int devRemoteSelected = 0;
                class SelectedDeviceIo* sel = selectedDevices.front();
                while (sel) {
                    dev = devicesList.front();
                    while (dev) {
                        if (dev->isRemote) {
                            if (dev->deviceId && sel->device && !strcmp(dev->deviceId, sel->device) && sel->app && ((ctx && ctx->app && !strcmp(sel->app, ctx->app)) || (softphoneApp && !strcmp(sel->app, softphoneApp)))) {
                                if (sel == selectedDevices.front() || (devRemoteSelected < devLocalSelected)) {
                                    firstSelected = 1; 
                                    dev->isSelected = true;
                                }
                                if(log) log->Log(LOG_AUDIO, "AudioIo(%p,%d)::AudioDeviceList remote selectedDevice (%s) for (%s) selected=%d %d<%d", this, i, sel->device, sel->app, firstSelected, devRemoteSelected, devLocalSelected);
                                // Start of the softphone app
                                if (this->deviceId == NULL) this->deviceId = _strdup(sel->device);
                                break;
                            }
                        }
                        dev = dev->goNext();
                    }
                    if (firstSelected >= 0) {
                        break;
                    }
                    devRemoteSelected++;
                    sel = sel->goNext();
                }
            }
            dev = devicesList.front();
            while (dev) {
                if (dev->isRemote) deviceIoUser->MediaIoDeviceAdded(this, context, dev->deviceId, dev->type, dev->capabilities, dev->name, dev->isSelected);
                dev = dev->goNext();
            }
        }
        else {
            class AudioDeviceItem* dev = devicesList.front();
            while (dev) {
                if (dev->isRemote) deviceIoUser->MediaIoDeviceAdded(this, context, dev->deviceId, dev->type, dev->capabilities, dev->name, dev->isSelected);
                dev = dev->goNext();
            }
        }
    }
    if (context && deviceIoUser) deviceIoUser->MediaIoQueryDevicesResult(this, context);
    if (!remoteDeviceUsers.empty()) {
        class UDeviceIoHelper* h = remoteDeviceUsers.front();
        while (h) {
            h->user->MediaIoQueryDevicesResult(this, context);
            h = h->goNext();
        }
    }
    // Check which selected devices are still available
    // There could be different softphone in selectedDevices using same device
    if (!selectedDevices.empty()) {
        class SelectedDeviceIo* dev;
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            dev->available = false;
        }
        if (!devicesList.empty()) {
            class AudioDeviceItem* dev = devicesList.front();
            while (dev) {
                for (class SelectedDeviceIo* sel = selectedDevices.front(); sel; sel = sel->goNext()) {
                    if (sel->device && dev->deviceId && !strcmp(sel->device, dev->deviceId)) {
                        if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AudioDeviceList selected (%s) available", this, sel->device);
                        sel->available = true;
                        sel->type = dev->type;
                    }
                }
                dev = dev->goNext();
            }
        }
    }
    // Report removed device to frontend
    if (!devicesList.empty()) {
        class AudioDeviceItem* dev = devicesList.front();
        while (dev) {
            class AudioDeviceItem* nextDevice = dev->goNext();
            if (dev->wasRemoved) {
                // Audio triggers a QueryDevices(NULL) call if a dev is added or removed
                if (!context) {
                    if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, context, dev->deviceId);
                    if (!remoteDeviceUsers.empty()) {
                        class UDeviceIoHelper* h = remoteDeviceUsers.front();
                        while (h) {
                            h->user->MediaIoDeviceRemoved(this, context, dev->deviceId);
                            h = h->goNext();
                        }
                    }
                }
                if (dev->isSelected) {
                    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AudioDeviceList selected native device was removed (%s)", this, dev->deviceId);
                    selectedDeviceRemoved = true;
                    if (dev->activeHookDevice) {
                        dev->activeHookDevice = false;
                        StopHookDevice(dev->deviceId);
                    }
                }
                dev->remove();
                delete dev;
            }
            dev = nextDevice;
        }
    }
    if (selectedDeviceRemoved) {
        if (deviceIoUser && !context) {
            if (!selectedDevices.empty()) {
                class SelectedDeviceIo* dev = NULL;
                for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
                    if (dev->available && dev->app && softphoneApp && !strcmp(dev->app, softphoneApp)) {
                        class AudioDeviceItem* devItem;
                        if ((devItem = FindDevice(dev->device)) != NULL) {
                            if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AudioDeviceList selected new device after removal (%s)", this, devItem->deviceId);
                            if (deviceIoUser) deviceIoUser->MediaIoDeviceRemoved(this, context, devItem->deviceId);
                            if (deviceIoUser) deviceIoUser->MediaIoDeviceAdded(this, context, devItem->deviceId, devItem->type, devItem->capabilities, devItem->name, true);
                            devItem->isSelected = true;
                            break;
                        }
                    }
                }
            }
        }
        selectedDeviceRemoved = false;
    }
    if (selectDeviceUser) selectDeviceUser->AudioDeviceList();
}

void AudioIo::AudioRouteChanged(const char *deviceId)
{
    if (selectDeviceUser) selectDeviceUser->AudioRouteChanged(deviceId);
}

void AudioIo::RemoteHookDeviceMsg(const char* deviceId, const char* msg)
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->HookDeviceMsg(this, deviceId, msg);
            h = h->goNext();
        }
    }
}

void AudioIo::RemoteHookKeyReceived(const char* deviceId, byte key)
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->HookKeyReceived(this, deviceId, key);
            h = h->goNext();
        }
    }
}

void AudioIo::HookDeviceMsg(const char* deviceId, const char* msg) 
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->HookDeviceMsg(this, deviceId, msg);
            h = h->goNext();
        }
    }
}

void AudioIo::HookKeyReceived(const char *deviceId, byte key)
{
    if (!hookDeviceUsers.empty()) {
        class UHookDeviceHelper* h = hookDeviceUsers.front();
        while (h) {
            h->user->HookKeyReceived(this, deviceId, key);
            h = h->goNext();
        }
    }
}

void AudioIo::QueryDevices(void * context)
{
    ReadSelectedDevices();

    if(audioPhone) audioPhone->QueryDevices(context);
    else if (mediaConfiguration->isRunningTS) AudioDeviceList(context);
}

void AudioIo::Mute(bool microphone, bool speaker)
{
    microphoneMuted = microphone;
    speakerMuted = speaker;
    if(audioPhone) audioPhone->SetMicrophoneMute(microphone);
}

void AudioIo::StartDualTones(dword flags, unsigned count, const struct AudioIoDualTone *tones)
{
    struct AudioIoDualTone *t;
    dword time;

    time = toneTime;
    if (toneTime != 0) {
        if ((flags != toneFlags) || (flags & AUDIO_IO_DUAL_TONE_LOOP) || (toneIndex == toneCount)) {
            toneLastSample = 0;
            toneIndex = toneCount;
            toneOn = false;
            toneTime = 0;
            tonePhase0 = 0;
            tonePhase1 = 0;
        }
        else {
            memmove(toneQueue, &toneQueue[toneIndex], (toneCount - toneIndex) * sizeof(struct AudioIoDualTone));
            toneCount -= toneIndex;
            toneIndex = 0;
        }
    }
    if (toneTime == 0) {
        toneIndex = 0;
        toneCount = 0;
    }
    if (allocatedTones < toneCount + count) {
        while (allocatedTones < toneCount + count) {
            allocatedTones <<= 1;
        }
        t = (struct AudioIoDualTone *)(malloc(allocatedTones * sizeof(struct AudioIoDualTone)));
        if (toneCount != 0) {
            memcpy(t, toneQueue, toneCount * sizeof(struct AudioIoDualTone));
        }
        free (toneQueue);
        toneQueue = t;
    }
    toneFlags = flags;
    memcpy(&toneQueue[toneCount], tones, count * sizeof(struct AudioIoDualTone));
    toneCount += count;
    if (toneTime == 0) {
        toneOn = (toneQueue[toneIndex].onTimeMs != 0);
        toneTime = ((toneQueue[toneIndex].onTimeMs != 0) ? toneQueue[toneIndex].onTimeMs : toneQueue[toneIndex].offTimeMs) * MAX_SAMPLE_RATE / 1000;
        toneIncrement0 = ((dword)(toneQueue[toneIndex].frequency0)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
        toneAmplitude0 = audio_io_amplitude(toneQueue[toneIndex].level0);
        toneIncrement1 = ((dword)(toneQueue[toneIndex].frequency1)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
        toneAmplitude1 = (toneFlags & AUDIO_IO_DUAL_TONE_MODULATE) ?
            audio_io_modulation(toneQueue[toneIndex].level0, toneQueue[toneIndex].level1) : audio_io_amplitude(toneQueue[toneIndex].level1);
    }
    if (((toneTime != 0) ^ (time != 0)) && !shutDown) {
        execCnt++;
        SetExec(iomux, this, (AudioIoUpdateDsp *)NULL);
    }
}

void AudioIo::StopDualTones()
{
    if (toneTime != 0) {
        toneIndex = toneCount;
        toneOn = false;
        if ((toneLastSample == 0) || !deviceStarted) {
            toneLastSample = 0;
            toneTime = 0;
            tonePhase0 = 0;
            tonePhase1 = 0;
            if(!shutDown) {
                execCnt++;
                SetExec(iomux, this, (AudioIoUpdateDsp *)NULL);
            }
        }
        else {
            toneTime = 1;
        }
    }
}

void AudioIo::LoopTest(bool on)
{
    loopTestDelayPos = 0;
    memset(loopTestDelayLine, 0, sizeof(loopTestDelayLine));
    if ((on ^ loop_test) && !shutDown) {
        loop_test = on;
        execCnt++;
        SetExec(iomux, this, (AudioIoUpdateDsp *)NULL);
    }
}

void AudioIo::RecordWaves()
{
    if (audioPhone) audioPhone->RecordWaves();
}

const char * AudioIo::StartDevice(void * context, void * src, const char *deviceId, int deviceMode)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::StartDevice %s -> %s (%d) audioPhone=%p", this, this->deviceId ? this->deviceId : "", deviceId ? deviceId : "", channel_attached || tone_active || loop_test_on, audioPhone);

    if (!audioPhone) return NULL;

    if (this->deviceId) {
        free (this->deviceId);
    }
    this->deviceId = _strdup(deviceId);
    this->deviceMode = (enum AudioDeviceMode) deviceMode;
    this->deviceStarted = true;
    if (channel_attached || tone_active || loop_test_on) {
        audioPhone->StartDevice(this->deviceId, this->deviceMode, sample_rate_table[record_sample_rate_id], sample_rate_table[play_sample_rate_id]);
        if (deviceIoUser) deviceIoUser->MediaIoDeviceStarted(this, this->deviceId);
    }
    return (this->deviceId);
}

void AudioIo::StopDevice(void * context, const char *deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::StopDevice %s (%d) audioPhone=%p", this, deviceId ? deviceId : "", channel_attached || tone_active || loop_test_on, audioPhone);

    if (!audioPhone) return;

    deviceStarted = false;
    if (channel_attached || tone_active || loop_test_on) {
        if (!toneOn && (toneIndex == toneCount) && (toneTime == 1)) {
            toneLastSample = 0;
            toneTime = 0;
            tonePhase0 = 0;
            tonePhase1 = 0;
            tone_active = false;
        }
        audioPhone->StopDevice(deviceId);
        if (deviceIoUser) deviceIoUser->MediaIoDeviceStopped(this, deviceId);
#ifdef IP270
        free(this->deviceId);
        this->deviceId = NULL;
#endif
    }
}

void AudioIo::StartMonitoring(const char *deviceId, int deviceMode)
{
    if(audioPhone) audioPhone->StartMonitoring(deviceId, (enum AudioDeviceMode) deviceMode);
}

void AudioIo::StopMonitoring(const char *deviceId)
{
    if (audioPhone) audioPhone->StopMonitoring(deviceId);
}

void AudioIo::AcquireHookDevice(const char* deviceId)
{
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::AcquireHookDevice %s audioPhone %p", this, deviceId ? deviceId : "", audioPhone);

    if (audioPhone) audioPhone->AcquireHookDevice(deviceId);
}

unsigned AudioIo::DeviceCapabilities(const char* deviceId)
{
    class AudioDeviceItem* devItem = NULL;
    if (devicesList.empty() == false) {
        for (devItem = devicesList.front(); devItem; devItem = devItem->goNext()) {
            if (devItem->isRemote && deviceId && devItem->deviceId && !strcmp(devItem->deviceId, deviceId)) {
                if(log) log->Log(LOG_AUDIO, "AudioIo::DeviceCapabilities remote deviceId=%s capabilities=%d", deviceId ? deviceId : "", devItem->capabilities);
                return devItem->capabilities;
            }
        }
    }

    for (unsigned int i = 0; i < (audioPhone ? audioPhone->DeviceCount() : 0); i++) {
        if (deviceId && audioPhone->DeviceId(i) && !strcmp(deviceId, audioPhone->DeviceId(i))) {
            return audioPhone->DeviceCapabilities(i);
        }
    }
    return 0;
}

void AudioIo::StartHookDevice(const char *deviceId)
{
    class SelectedDeviceIo* dev = NULL;
    if (!selectedDevices.empty()) {
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            if (!strcmp(dev->device, deviceId)) {
                dev->activeHookDevice = true;
                break;
            }
            else dev->activeHookDevice = false;
        }
    }
    class AudioDeviceItem* devItem = NULL;
    if (devicesList.empty() == false) {
        for (devItem = devicesList.front(); devItem; devItem = devItem->goNext()) {
            if (!strcmp(devItem->deviceId, deviceId)) {
                devItem->activeHookDevice = true;
                break;
            }
            else devItem->activeHookDevice = false;
        }
    }
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::StartHookDevice deviceId=%s dev=%p,%p remote=%d,%d hook=%d", this, deviceId, dev, devItem, dev ? dev->isRemote : 0, devItem ? devItem->isRemote : 0, dev ? dev->activeHookDevice : 0);

    if ((dev && dev->isRemote) || (devItem && devItem->isRemote)) return;

    if(audioPhone) audioPhone->StartHookDevice(deviceId);
}

void AudioIo::StopHookDevice(const char *deviceId)
{
    class SelectedDeviceIo* dev = NULL;
    if (!selectedDevices.empty()) {
        for (dev = selectedDevices.front(); dev; dev = dev->goNext()) {
            if (!strcmp(dev->device, deviceId)) {
                dev->activeHookDevice = false;
                break;
            }
        }
    }
    class AudioDeviceItem* devItem = NULL;
    if (devicesList.empty() == false) {
        for (devItem = devicesList.front(); devItem; devItem = devItem->goNext()) {
            if (!strcmp(devItem->deviceId, deviceId)) {
                devItem->activeHookDevice = false;
                break;
            }
        }
    }
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::StopHookDevice deviceId=%s dev=%p,%p remote=%d,%d", this, deviceId, dev, devItem, dev ? dev->isRemote : 0, devItem ? devItem->isRemote : 0);

    if ((dev && dev->isRemote) || (devItem && devItem->isRemote)) return;

    if(audioPhone) audioPhone->StopHookDevice(deviceId);
}

void AudioIo::SendHookKey(const char *deviceId, byte key, byte callId, const char * number, const char * name)
{
    if(deviceId == NULL) return;
    if (audioPhone) audioPhone->SendHookKey(deviceId, key, callId, number, name);
}

class IAudioPhone *AudioIo::Initialize(class UAudioIo * const user, class UDeviceIo * const deviceIoUser)
{
    this->user = user;
    this->deviceIoUser = deviceIoUser;
    return audioPhone;
}

#define AUDIO_IO_FEEDBACK_ATTENUATION  0x2000

void AudioIo::AudioExec(class IAudioExec *audioExec)
{
    int32 mix_buffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
    short tmp_buffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
    short tone_buffer[MAX_EXEC_INTERVAL_8KHZ * (MAX_SAMPLE_RATE / 8000)];
    struct audioIoCh *ch, *ch_first, *mix_ch;
    const short *mic_sample_buffer;
    short *spk_sample_buffer;
    const short *buf;
    int32 l;
    unsigned i, mic_sample_count, spk_sample_count, count, index, time, step;
    dword phase0, increment0, step0, phase1, increment1, step1;
    word amplitude0, amplitude1, feedback0, feedback1;
    short sample;
    bool on, rtp_packets;

    if (!audioPhone) return;

    mic_sample_buffer = audioPhone->MicrophoneSamplePtr();
    spk_sample_buffer = audioPhone->SpeakerSamplePtr();
    mic_sample_count = execInterval8khz * (sample_rate_table[record_sample_rate_id] / 8000);
    spk_sample_count = execInterval8khz * (sample_rate_table[play_sample_rate_id] / 8000);

    rtp_packets = false;
    for(ch = channels; ch < &channels[used_channels]; ch++) {
        if(ch->p && ch->channel_attached) {
            rtp_packets |= ch->p->Pull();
        }
    }

    count = toneCount;
    index = toneIndex;
    on = toneOn;
    sample = toneLastSample;
    time = toneTime;
    phase0 = tonePhase0;
    increment0 = toneIncrement0;
    amplitude0 = toneAmplitude0;
    phase1 = tonePhase1;
    increment1 = toneIncrement1;
    amplitude1 = toneAmplitude1;
    if ((toneTime != 0) && ((toneFlags & AUDIO_IO_DUAL_TONE_PEER) || (toneFlags & AUDIO_IO_DUAL_TONE_BOTH))) {
        step = MAX_SAMPLE_RATE / sample_rate_table[record_sample_rate_id];
        step0 = increment0 * step;
        step1 = increment1 * step;
        if (rtp_packets && (toneFlags & AUDIO_IO_DUAL_TONE_AUTOOFF)) {
            index = count;
            on = false;
            if (sample == 0) {
                time = 0;
                phase0 = 0;
                phase1 = 0;
            }
            else {
                time = 1;
            }
        }
        for (i = 0; i < mic_sample_count; i++) {
            if (time != 0) {
                if (on || (sample != 0)) {
                    phase0 += step0;
                    phase1 += step1;
                    if (toneFlags & AUDIO_IO_DUAL_TONE_MODULATE) {
                        l = ((int32)((((int32)(((int32)(sdsp_sin((word)(phase0 >> 16)))) *
                            ((int32)(0x8000 - amplitude1 + (((int32)(((int32)(sdsp_sin((word)(phase1 >> 16)))) * ((int32) amplitude1))) >> 15))))) >> 15) *
                            ((int32) amplitude0))) >> 15;
                    }
                    else {
                        l = ((int32)(((int32)(sdsp_sin((word)(phase0 >> 16)))) * ((int32) amplitude0) +
                            ((int32)(sdsp_sin((word)(phase1 >> 16)))) * ((int32) amplitude1))) >> 15;
                    }
                    if (l > 32767) {
                        tone_buffer[i] = 32767;
                    }
                    else if (l < -32768) {
                        tone_buffer[i] = -32768;
                    }
                    else {
                        tone_buffer[i] = (short) l;
                    }
                    if (on) {
                        sample = tone_buffer[i];
                    }
                    else {
                        if (((sample >= 0) && (tone_buffer[i] <= 0)) || ((sample < 0) && (tone_buffer[i] >= 0))) {
                            sample = 0;
                            phase0 = 0;
                            phase1 = 0;
                        }
                    }
                }
                else {
                    tone_buffer[i] = (toneFlags & AUDIO_IO_DUAL_TONE_PASSTHROUGH) ? mic_sample_buffer[i] : 0;
                }
                time = (time > step) ? time - step : 0;
                if (time == 0) {
                    if (on) {
                        on = false;
                        time = toneQueue[index].offTimeMs * MAX_SAMPLE_RATE / 1000;
                        if ((time != 0) && (sample == 0)) {
                            phase0 = 0;
                            phase1 = 0;
                        }
                    }
                    if (time == 0) {
                        if (index < count) {
                            index++;
                            if ((index == count) && (toneFlags & AUDIO_IO_DUAL_TONE_LOOP)) {
                                index = (toneQueue[0].onTimeMs != 0) ? 0 : 1;
                            }
                        }
                        if (index < count) {
                            on = (toneQueue[index].onTimeMs != 0);
                            time = ((toneQueue[index].onTimeMs != 0) ? toneQueue[index].onTimeMs : toneQueue[index].offTimeMs) * MAX_SAMPLE_RATE / 1000;
                            increment0 = ((dword)(toneQueue[index].frequency0)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
                            step0 = increment0 * step;
                            amplitude0 = audio_io_amplitude(toneQueue[index].level0);
                            increment1 = ((dword)(toneQueue[index].frequency1)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
                            step1 = increment1 * step;
                            amplitude1 = (toneFlags & AUDIO_IO_DUAL_TONE_MODULATE) ?
                                audio_io_modulation(toneQueue[index].level0, toneQueue[index].level1) : audio_io_amplitude(toneQueue[index].level1);
                        }
                        else {
                            if (sample != 0) {
                                time = 1;
                            }
                        }
                    }
                }
            }
            else {
                tone_buffer[i] = mic_sample_buffer[i];
            }
        }
    }
    else {
        memcpy(tone_buffer, mic_sample_buffer, mic_sample_count * sizeof(short));
    }
    if (microphoneMuted) {
        memset(tone_buffer, 0x0, mic_sample_count * sizeof(short));
    }

    for(mix_ch = channels; mix_ch < &channels[used_channels]; mix_ch++) {
        if(mix_ch->p && mix_ch->channel_attached) {
            ch = channels;
            while((ch < &channels[used_channels]) && (!ch->p || !ch->channel_attached || (ch == mix_ch))) {
                ch++;
            }
            if(ch < &channels[used_channels]) {
                for(i = 0; i < mic_sample_count; i++) {
                    mix_buffer[i] = tone_buffer[i];
                }
                do {
                    buf = ch->p->FeedbackSamples();
                    for(i = 0; i < mic_sample_count; i++) {
                        mix_buffer[i] += buf[i];
                    }
                    do {
                        ch++;
                    }
                    while((ch < &channels[used_channels]) && (!ch->p || !ch->channel_attached || (ch == mix_ch)));
                }
                while(ch < &channels[used_channels]);
                for(i = 0; i < mic_sample_count; i++) {
                    l = mix_buffer[i];
                    if(l > 32767) {
                        tmp_buffer[i] = 32767;
                    }
                    else if(l < -32768) {
                        tmp_buffer[i] = -32768;
                    }
                    else {
                        tmp_buffer[i] = (short) l;
                    }
                }
			    //	pass samples to encoder
			    mix_ch->p->PutSamples(tmp_buffer);
            }
            else {
			    //	pass samples to encoder
			    mix_ch->p->PutSamples(tone_buffer);
            }
		}
	}
    ch_first = channels;
    while((ch_first < &channels[used_channels]) && (!ch_first->p || !ch_first->channel_attached)) {
        ch_first++;
    }
    if (ch_first < &channels[used_channels]) {
        ch = ch_first;
        do {
            ch++;
        }
        while((ch < &channels[used_channels]) && (!ch->p || !ch->channel_attached));
        if(ch < &channels[used_channels]) {
            buf = ch_first->p->PulledSamples();
            for(i = 0; i < spk_sample_count; i++) {
                mix_buffer[i] = buf[i];
            }
            do {
                buf = ch->p->PulledSamples();
                for(i = 0; i < spk_sample_count; i++) {
                    mix_buffer[i] += buf[i];
                }
                do {
                    ch++;
                }
                while((ch < &channels[used_channels]) && (!ch->p || !ch->channel_attached));
            }
            while(ch < &channels[used_channels]);
            for(i = 0; i < spk_sample_count; i++) {
                l = mix_buffer[i];
                if(l > 32767) {
                    spk_sample_buffer[i] = 32767;
                }
                else if(l < -32768) {
                    spk_sample_buffer[i] = -32768;
                }
                else {
                    spk_sample_buffer[i] = (short) l;
                }
            }
        }
        else {
            memcpy(spk_sample_buffer, ch_first->p->PulledSamples(), spk_sample_count * sizeof(short));
        }
    }
    else {
        //	feed silence if samples are missing, otherwise noise may be heard
        memset(spk_sample_buffer, 0, spk_sample_count * sizeof(short));
    }

    if (loop_test_on) {
        if ((toneTime != 0) && (!(toneFlags & AUDIO_IO_DUAL_TONE_PEER) || (toneFlags & AUDIO_IO_DUAL_TONE_BOTH))) {
            memset(loopTestDelayLine, 0, (((dword)(sizeof(loopTestDelayLine))) * ((dword)(sample_rate_table[record_sample_rate_id]))) / MAX_SAMPLE_RATE);
            memset(spk_sample_buffer, 0, spk_sample_count * sizeof(short));
        }
        else {
            memcpy(tmp_buffer, mic_sample_buffer, mic_sample_count * sizeof(short));
            i = (((dword)(sizeof(loopTestDelayLine) / sizeof(loopTestDelayLine[0]))) * ((dword)(sample_rate_table[play_sample_rate_id]))) / MAX_SAMPLE_RATE;
            if (loopTestDelayPos + spk_sample_count < i) {
                memcpy(spk_sample_buffer, &loopTestDelayLine[loopTestDelayPos], spk_sample_count * sizeof(short));
            }
            else {
                memcpy(spk_sample_buffer, &loopTestDelayLine[loopTestDelayPos], (i - loopTestDelayPos) * sizeof(short));
                if (loopTestDelayPos + spk_sample_count != i) {
                    memcpy(&spk_sample_buffer[i - loopTestDelayPos], loopTestDelayLine, (spk_sample_count - (i - loopTestDelayPos)) * sizeof(short));
                }
            }
            i = (((dword)(sizeof(loopTestDelayLine) / sizeof(loopTestDelayLine[0]))) * ((dword)(sample_rate_table[record_sample_rate_id]))) / MAX_SAMPLE_RATE;
            if (loopTestDelayPos + mic_sample_count < i) {
                memcpy(&loopTestDelayLine[loopTestDelayPos], tmp_buffer, mic_sample_count * sizeof(short));
                loopTestDelayPos += mic_sample_count;
            }
            else {
                memcpy(&loopTestDelayLine[loopTestDelayPos], tmp_buffer, (i - loopTestDelayPos) * sizeof(short));
                loopTestDelayPos = mic_sample_count - (i - loopTestDelayPos);
                if (loopTestDelayPos != 0) {
                    memcpy(loopTestDelayLine, &tmp_buffer[mic_sample_count - loopTestDelayPos], loopTestDelayPos * sizeof(short));
                }
            }
        }
    }

    if ((toneTime != 0) && (!(toneFlags & AUDIO_IO_DUAL_TONE_PEER) || (toneFlags & AUDIO_IO_DUAL_TONE_BOTH))) {
        count = toneCount;
        index = toneIndex;
        on = toneOn;
        sample = toneLastSample;
        time = toneTime;
        phase0 = tonePhase0;
        increment0 = toneIncrement0;
        amplitude0 = toneAmplitude0;
        phase1 = tonePhase1;
        increment1 = toneIncrement1;
        amplitude1 = toneAmplitude1;
        if (toneFlags & AUDIO_IO_DUAL_TONE_PEER) {
            feedback0 = (word)(((dword)(((dword) amplitude0) * ((dword) AUDIO_IO_FEEDBACK_ATTENUATION))) >> 15);
            feedback1 = (word)(((dword)(((dword) amplitude1) * ((dword) AUDIO_IO_FEEDBACK_ATTENUATION))) >> 15);
        }
        else
        {
            feedback0 = amplitude0;
            feedback1 = amplitude1;
        }
        step = MAX_SAMPLE_RATE / sample_rate_table[play_sample_rate_id];
        step0 = increment0 * step;
        step1 = increment1 * step;
        if (rtp_packets && (toneFlags & AUDIO_IO_DUAL_TONE_AUTOOFF)) {
            index = count;
            on = false;
            if (sample == 0) {
                time = 0;
                phase0 = 0;
                phase1 = 0;
            }
            else {
                time = 1;
            }
        }
        for (i = 0; i < spk_sample_count; i++) {
            if (time != 0) {
                if (on || (sample != 0)) {
                    phase0 += step0;
                    phase1 += step1;
                    if (toneFlags & AUDIO_IO_DUAL_TONE_MODULATE) {
                        l = ((int32)((((int32)(((int32)(sdsp_sin((word)(phase0 >> 16)))) *
                            ((int32)(0x8000 - amplitude1 + (((int32)(((int32)(sdsp_sin((word)(phase1 >> 16)))) * ((int32) amplitude1))) >> 15))))) >> 15) *
                            ((int32) feedback0))) >> 15;
                    }
                    else {
                        l = ((int32)(((int32)(sdsp_sin((word)(phase0 >> 16)))) * ((int32) feedback0) +
                            ((int32)(sdsp_sin((word)(phase1 >> 16)))) * ((int32) feedback1))) >> 15;
                    }
                    if (l > 32767) {
                        spk_sample_buffer[i] = 32767;
                    }
                    else if (l < -32768) {
                        spk_sample_buffer[i] = -32768;
                    }
                    else {
                        spk_sample_buffer[i] = (short) l;
                    }
                    if (on) {
                        sample = spk_sample_buffer[i];
                    }
                    else {
                        if (((sample >= 0) && (spk_sample_buffer[i] <= 0)) || ((sample < 0) && (spk_sample_buffer[i] >= 0))) {
                            sample = 0;
                            phase0 = 0;
                            phase1 = 0;
                        }
                    }
                }
                else {
                    if (!(toneFlags & AUDIO_IO_DUAL_TONE_PASSTHROUGH)) {
                        spk_sample_buffer[i] = 0;
                    }
                }
                time = (time > step) ? time - step : 0;
                if (time == 0) {
                    if (on) {
                        on = false;
                        time = toneQueue[index].offTimeMs * MAX_SAMPLE_RATE / 1000;
                        if ((time != 0) && (sample == 0)) {
                            phase0 = 0;
                            phase1 = 0;
                        }
                    }
                    if (time == 0) {
                        if (index < count) {
                            index++;
                            if ((index == count) && (toneFlags & AUDIO_IO_DUAL_TONE_LOOP)) {
                                index = (toneQueue[0].onTimeMs != 0) ? 0 : 1;
                            }
                        }
                        if (index < count) {
                            on = (toneQueue[index].onTimeMs != 0);
                            time = ((toneQueue[index].onTimeMs != 0) ? toneQueue[index].onTimeMs : toneQueue[index].offTimeMs) * MAX_SAMPLE_RATE / 1000;
                            increment0 = ((dword)(toneQueue[index].frequency0)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
                            step0 = increment0 * step;
                            amplitude0 = audio_io_amplitude(toneQueue[index].level0);
                            increment1 = ((dword)(toneQueue[index].frequency1)) * (((dword) 0x80000000) / (MAX_SAMPLE_RATE / 2));
                            step1 = increment1 * step;
                            amplitude1 = (toneFlags & AUDIO_IO_DUAL_TONE_MODULATE) ?
                                audio_io_modulation(toneQueue[index].level0, toneQueue[index].level1) : audio_io_amplitude(toneQueue[index].level1);
                            if (toneFlags & AUDIO_IO_DUAL_TONE_PEER) {
                                feedback0 = (word)(((dword)(((dword) amplitude0) * ((dword) AUDIO_IO_FEEDBACK_ATTENUATION))) >> 15);
                                feedback1 = (word)(((dword)(((dword) amplitude1) * ((dword) AUDIO_IO_FEEDBACK_ATTENUATION))) >> 15);
                            }
                            else
                            {
                                feedback0 = amplitude0;
                                feedback1 = amplitude1;
                            }
                        }
                        else {
                            if (sample != 0) {
                                time = 1;
                            }
                        }
                    }
                }
            }
        }
    }
    if (((toneTime != 0) && (time == 0)) && !shutDown) {
        execCnt++;
        SetExec(iomux, this, (AudioIoUpdateDsp *)NULL);
    }
    toneCount = count;
    toneIndex = index;
    toneOn = on;
    toneLastSample = sample;
    toneTime = time;
    tonePhase0 = phase0;
    toneIncrement0 = increment0;
    toneAmplitude0 = amplitude0;
    tonePhase1 = phase1;
    toneIncrement1 = increment1;
    toneAmplitude1 = amplitude1;
    if (speakerMuted) {
        memset(spk_sample_buffer, 0, spk_sample_count * sizeof(short));
    }
}

class IAudioIoChannel *AudioIo::CreateChannel(class UMediaIoChannel * const user)
{
    struct audioIoCh *ch;

    if (!audioPhone) return NULL;

    ch = channels;
    while ((ch < &channels[used_channels]) && ch->p) {
        ch++;
    }
    if (ch == &channels[used_channels]) {
        if (ch == &channels[allocated_channels]) {
            ch = (struct audioIoCh *)(malloc(2*allocated_channels * sizeof(struct audioIoCh)));
            memcpy(ch, channels, allocated_channels * sizeof(struct audioIoCh));
            free(channels);
            channels = ch;
            ch = &channels[allocated_channels];
            allocated_channels *= 2;
        }
        used_channels++;
    }
    ch->p = IAudioIoChannel::Create(this, user);
    ch->channel_initialized = false;
    ch->channel_opened = false;
    ch->channel_attached = false;

    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::CreateChannel ch=%p audioIoChannel=%p user=%p", this, ch, ch->p, user);

    return ch->p;
}

void AudioIo::update_dsp(struct audioIoCh *calling_channel)
{
    if (log && calling_channel)
        log->Log(LOG_AUDIO, "AudioIo(%p)::update_dsp deviceId: %s, AudioIoChannel: 0x%p, channel_initialized: %i, channel_opened: %i, channel_attached: %i", this, deviceId, calling_channel->p, calling_channel->channel_initialized, calling_channel->channel_opened, calling_channel->channel_attached);
    else if (log)
        log->Log(LOG_AUDIO, "AudioIo(%p)::update_dsp deviceId: %s", this, deviceId);
    
    struct audioIoCh *ch;
    unsigned int new_record_sample_rate_id;
    unsigned int new_play_sample_rate_id;
    bool sample_rate_changed;
    bool new_channel_attached;
    bool new_tone_active;
    bool new_loop_test_on;

    if (!audioPhone) return;

    new_channel_attached = false;
#if (defined(__ANDROID__) || defined(_IOS_))
    new_record_sample_rate_id = SAMPLE_RATE_ID_16000;
    new_play_sample_rate_id = SAMPLE_RATE_ID_16000;
#else
    new_record_sample_rate_id = SAMPLE_RATE_ID_8000;
    new_play_sample_rate_id = SAMPLE_RATE_ID_8000;
#endif
    for(ch = channels; ch < &channels[used_channels]; ch++) {
        if(ch->p && ch->channel_attached) {
            new_channel_attached = true;
            if(sample_rate_table[ch->payload_sample_rate_id] > sample_rate_table[new_record_sample_rate_id]) {
                new_record_sample_rate_id = ch->payload_sample_rate_id;
            }
            if(sample_rate_table[ch->payload_sample_rate_id] > sample_rate_table[new_play_sample_rate_id]) {
                new_play_sample_rate_id = ch->payload_sample_rate_id;
            }
        }
    }
    new_loop_test_on = loop_test;
    new_tone_active = (toneTime != 0);
    sample_rate_changed = ((new_record_sample_rate_id != record_sample_rate_id) || (new_play_sample_rate_id != play_sample_rate_id));
    record_sample_rate_id = new_record_sample_rate_id;
    play_sample_rate_id = new_play_sample_rate_id;
    if (sample_rate_changed) {
        for(ch = channels; ch < &channels[used_channels]; ch++) {
            if(ch->p && ch->channel_attached && (ch != calling_channel)) {
                ch->p->Initialize(ch->coder, ch->sc, ch->mediaPacketizationMs, execInterval8khz,
                    audioPhone->ExecJitter8khz(), sample_rate_table[play_sample_rate_id], sample_rate_table[record_sample_rate_id]);
            }
        }
    }
    if (sample_rate_changed || ((new_channel_attached || new_tone_active || new_loop_test_on) ^ (channel_attached || tone_active || loop_test_on))) {
        channel_attached = new_channel_attached;
        tone_active = new_tone_active;
        loop_test_on = new_loop_test_on;
        if (channel_attached || tone_active || loop_test_on) {
            audioPhone->StartDevice(deviceId, deviceMode, sample_rate_table[record_sample_rate_id], sample_rate_table[play_sample_rate_id]);
            if (deviceIoUser) deviceIoUser->MediaIoDeviceStarted(this, this->deviceId);
        }
        else {
            audioPhone->StopDevice(deviceId);
            if (deviceIoUser) deviceIoUser->MediaIoDeviceStopped(this, deviceId);
        }
    }
}

void AudioIo::InitializeChannel(class IAudioIoChannel *audioIoChannel, enum AudioCoder coder, bool sc, unsigned mediaPacketizationMs)
{
    struct audioIoCh *ch;

    if (!audioPhone) return;

    ch = channels;
    while (ch->p != audioIoChannel) {
        ch++;
    }
    ch->coder = coder;
    ch->sc = sc;
    ch->mediaPacketizationMs = mediaPacketizationMs;
    switch (coder) {
    case AudioCoder::G722:
    case AudioCoder::OPUS_WB:
        ch->payload_sample_rate_id = SAMPLE_RATE_ID_16000;
        break;
    default:
        ch->payload_sample_rate_id = SAMPLE_RATE_ID_8000;
        break;
    }
    ch->channel_initialized = true;
    if (ch->channel_opened) {
        ch->channel_attached = true;
    }
    update_dsp(ch);
    ch->p->Initialize(ch->coder, ch->sc, ch->mediaPacketizationMs, execInterval8khz,
        audioPhone->ExecJitter8khz(), sample_rate_table[play_sample_rate_id], sample_rate_table[record_sample_rate_id], mediaConfiguration->recordRtpStream);
}

void AudioIo::ChannelOpened(class IAudioIoChannel *audioIoChannel)
{
    struct audioIoCh *ch;

    if (!audioPhone) return;

    ch = channels;
    while (ch->p != audioIoChannel) {
        ch++;
    }
    ch->channel_opened = true;
    if (ch->channel_initialized && !ch->channel_attached) {
        ch->channel_attached = true;
        update_dsp(ch);
        ch->p->Initialize(ch->coder, ch->sc, ch->mediaPacketizationMs, execInterval8khz,
            audioPhone->ExecJitter8khz(), sample_rate_table[play_sample_rate_id], sample_rate_table[record_sample_rate_id]);
    }
}

void AudioIo::ChannelClosed(class IAudioIoChannel *audioIoChannel)
{
    struct audioIoCh *ch;

    ch = channels;
    while (ch->p != audioIoChannel) {
        ch++;
    }
    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::ChannelClosed audioIoChannel=%p ch(%p)=%p attached=%d opened=%d", this, audioIoChannel, ch, ch ? ch->p : NULL, ch ? ch->channel_attached : 0, ch ? ch->channel_opened : 0);
    ch->channel_opened = false;
    if (ch->channel_attached) {
        ch->channel_attached = false;
        update_dsp(ch);
    }
}

void AudioIo::ChannelDestroyed(class IAudioIoChannel *audioIoChannel)
{
    struct audioIoCh *ch;

    if(log) log->Log(LOG_AUDIO, "AudioIo(%p)::ChannelDestroyed audioIoChannel=%p", this, audioIoChannel);

    ch = channels;
    while (ch->p != audioIoChannel) {
        ch++;
    }
    ch->p = 0;
    ch->channel_initialized = false;
    ch->channel_opened = false;
    ch->channel_attached = false;
    if (this->shutDown) Close(this->user);
}

