/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd and/or its subsidiary(-ies).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qdarwinaudiosource_p.h"
#include "qcoreaudiosessionmanager_p.h"
#include "qdarwinaudiodevice_p.h"
#include "qcoreaudioutils_p.h"
#include "private/qdarwinmediadevices_p.h"
#include <qmediadevices.h>

#if defined(Q_OS_OSX)
# include <AudioUnit/AudioComponent.h>
#endif

#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
# include "qcoreaudiosessionmanager_p.h"
#endif

#include <QtMultimedia/private/qaudiohelpers_p.h>
#include <QtCore/QDataStream>
#include <QtCore/QDebug>

QT_BEGIN_NAMESPACE

static const int DEFAULT_BUFFER_SIZE = 4 * 1024;

QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat)
    : m_owner(false)
    , m_streamDescription(streamFormat)
{
    const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
    const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame;

    m_dataSize = 0;

    m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) +
                                                            (sizeof(AudioBuffer) * numberOfBuffers)));

    m_bufferList->mNumberBuffers = numberOfBuffers;
    for (int i = 0; i < numberOfBuffers; ++i) {
        m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
        m_bufferList->mBuffers[i].mDataByteSize = 0;
        m_bufferList->mBuffers[i].mData = 0;
    }
}

QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, char *buffer, int bufferSize)
    : m_owner(false)
    , m_streamDescription(streamFormat)
    , m_bufferList(0)
{
    m_dataSize = bufferSize;

    m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) + sizeof(AudioBuffer)));

    m_bufferList->mNumberBuffers = 1;
    m_bufferList->mBuffers[0].mNumberChannels = 1;
    m_bufferList->mBuffers[0].mDataByteSize = m_dataSize;
    m_bufferList->mBuffers[0].mData = buffer;
}

QCoreAudioBufferList::QCoreAudioBufferList(const AudioStreamBasicDescription &streamFormat, int framesToBuffer)
    : m_owner(true)
    , m_streamDescription(streamFormat)
    , m_bufferList(0)
{
    const bool isInterleaved = (m_streamDescription.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0;
    const int numberOfBuffers = isInterleaved ? 1 : m_streamDescription.mChannelsPerFrame;

    m_dataSize = framesToBuffer * m_streamDescription.mBytesPerFrame;

    m_bufferList = reinterpret_cast<AudioBufferList*>(malloc(sizeof(AudioBufferList) +
                                                            (sizeof(AudioBuffer) * numberOfBuffers)));
    m_bufferList->mNumberBuffers = numberOfBuffers;
    for (int i = 0; i < numberOfBuffers; ++i) {
        m_bufferList->mBuffers[i].mNumberChannels = isInterleaved ? numberOfBuffers : 1;
        m_bufferList->mBuffers[i].mDataByteSize = m_dataSize;
        m_bufferList->mBuffers[i].mData = malloc(m_dataSize);
    }
}

QCoreAudioBufferList::~QCoreAudioBufferList()
{
    if (m_owner) {
        for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i)
            free(m_bufferList->mBuffers[i].mData);
    }

    free(m_bufferList);
}

char *QCoreAudioBufferList::data(int buffer) const
{
    return static_cast<char*>(m_bufferList->mBuffers[buffer].mData);
}

qint64 QCoreAudioBufferList::bufferSize(int buffer) const
{
    return m_bufferList->mBuffers[buffer].mDataByteSize;
}

int QCoreAudioBufferList::frameCount(int buffer) const
{
    return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerFrame;
}

int QCoreAudioBufferList::packetCount(int buffer) const
{
    return m_bufferList->mBuffers[buffer].mDataByteSize / m_streamDescription.mBytesPerPacket;
}

int QCoreAudioBufferList::packetSize() const
{
    return m_streamDescription.mBytesPerPacket;
}

void QCoreAudioBufferList::reset()
{
    for (UInt32 i = 0; i < m_bufferList->mNumberBuffers; ++i) {
        m_bufferList->mBuffers[i].mDataByteSize = m_dataSize;
        m_bufferList->mBuffers[i].mData = 0;
    }
}

QCoreAudioPacketFeeder::QCoreAudioPacketFeeder(QCoreAudioBufferList *abl)
    : m_audioBufferList(abl)
{
    m_totalPackets = m_audioBufferList->packetCount();
    m_position = 0;
}

bool QCoreAudioPacketFeeder::feed(AudioBufferList &dst, UInt32 &packetCount)
{
    if (m_position == m_totalPackets) {
        dst.mBuffers[0].mDataByteSize = 0;
        packetCount = 0;
        return false;
    }

    if (m_totalPackets - m_position < packetCount)
        packetCount = m_totalPackets - m_position;

    dst.mBuffers[0].mDataByteSize = packetCount * m_audioBufferList->packetSize();
    dst.mBuffers[0].mData = m_audioBufferList->data() + (m_position * m_audioBufferList->packetSize());

    m_position += packetCount;

    return true;
}

bool QCoreAudioPacketFeeder::empty() const
{
    return m_position == m_totalPackets;
}

QDarwinAudioSourceBuffer::QDarwinAudioSourceBuffer(int bufferSize, int maxPeriodSize, const AudioStreamBasicDescription &inputFormat, const AudioStreamBasicDescription &outputFormat, QObject *parent)
    : QObject(parent)
    , m_deviceError(false)
    , m_device(0)
    , m_audioConverter(0)
    , m_inputFormat(inputFormat)
    , m_outputFormat(outputFormat)
    , m_volume(qreal(1.0f))
{
    m_maxPeriodSize = maxPeriodSize;
    m_periodTime = m_maxPeriodSize / m_outputFormat.mBytesPerFrame * 1000 / m_outputFormat.mSampleRate;

    m_buffer = new CoreAudioRingBuffer(bufferSize);

    m_inputBufferList = new QCoreAudioBufferList(m_inputFormat);

    m_flushTimer = new QTimer(this);
    connect(m_flushTimer, SIGNAL(timeout()), SLOT(flushBuffer()));

    if (CoreAudioUtils::toQAudioFormat(inputFormat) != CoreAudioUtils::toQAudioFormat(outputFormat)) {
        if (AudioConverterNew(&m_inputFormat, &m_outputFormat, &m_audioConverter) != noErr) {
            qWarning() << "QAudioSource: Unable to create an Audio Converter";
            m_audioConverter = 0;
        }
    }

    m_qFormat = CoreAudioUtils::toQAudioFormat(inputFormat); // we adjust volume before conversion
}

QDarwinAudioSourceBuffer::~QDarwinAudioSourceBuffer()
{
    delete m_buffer;
}

qreal QDarwinAudioSourceBuffer::volume() const
{
    return m_volume;
}

void QDarwinAudioSourceBuffer::setVolume(qreal v)
{
    m_volume = v;
}

qint64 QDarwinAudioSourceBuffer::renderFromDevice(AudioUnit audioUnit, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames)
{
    const bool  pullMode = m_device == 0;

    OSStatus    err;
    qint64      framesRendered = 0;

    m_inputBufferList->reset();
    err = AudioUnitRender(audioUnit,
                          ioActionFlags,
                          inTimeStamp,
                          inBusNumber,
                          inNumberFrames,
                          m_inputBufferList->audioBufferList());

    // adjust volume, if necessary
    if (!qFuzzyCompare(m_volume, qreal(1.0f))) {
        QAudioHelperInternal::qMultiplySamples(m_volume,
                                               m_qFormat,
                                               m_inputBufferList->data(), /* input */
                                               m_inputBufferList->data(), /* output */
                                               m_inputBufferList->bufferSize());
    }

    if (m_audioConverter != 0) {
        QCoreAudioPacketFeeder  feeder(m_inputBufferList);

        int     copied = 0;
        const int available = m_buffer->free();

        while (err == noErr && !feeder.empty()) {
            CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied);

            if (region.second == 0)
                break;

            AudioBufferList     output;
            output.mNumberBuffers = 1;
            output.mBuffers[0].mNumberChannels = 1;
            output.mBuffers[0].mDataByteSize = region.second;
            output.mBuffers[0].mData = region.first;

            UInt32  packetSize = region.second / m_outputFormat.mBytesPerPacket;
            err = AudioConverterFillComplexBuffer(m_audioConverter,
                                                  converterCallback,
                                                  &feeder,
                                                  &packetSize,
                                                  &output,
                                                  0);
            region.second = output.mBuffers[0].mDataByteSize;
            copied += region.second;

            m_buffer->releaseWriteRegion(region);
        }

        framesRendered += copied / m_outputFormat.mBytesPerFrame;
    }
    else {
        const int available = m_inputBufferList->bufferSize();
        bool    wecan = true;
        int     copied = 0;

        while (wecan && copied < available) {
            CoreAudioRingBuffer::Region region = m_buffer->acquireWriteRegion(available - copied);

            if (region.second > 0) {
                memcpy(region.first, m_inputBufferList->data() + copied, region.second);
                copied += region.second;
            }
            else
                wecan = false;

            m_buffer->releaseWriteRegion(region);
        }

        framesRendered = copied / m_outputFormat.mBytesPerFrame;
    }

    if (pullMode && framesRendered > 0)
        emit readyRead();

    return framesRendered;
}

qint64 QDarwinAudioSourceBuffer::readBytes(char *data, qint64 len)
{
    bool    wecan = true;
    qint64  bytesCopied = 0;

    len -= len % m_maxPeriodSize;
    while (wecan && bytesCopied < len) {
        CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(len - bytesCopied);

        if (region.second > 0) {
            memcpy(data + bytesCopied, region.first, region.second);
            bytesCopied += region.second;
        }
        else
            wecan = false;

        m_buffer->releaseReadRegion(region);
    }

    return bytesCopied;
}

void QDarwinAudioSourceBuffer::setFlushDevice(QIODevice *device)
{
    if (m_device != device)
        m_device = device;
}

void QDarwinAudioSourceBuffer::startFlushTimer()
{
    if (m_device != 0) {
        // We use the period time for the timer, since that's
        // around the buffer size (pre conversion >.>)
        m_flushTimer->start(qMax(1, m_periodTime));
    }
}

void QDarwinAudioSourceBuffer::stopFlushTimer()
{
    m_flushTimer->stop();
}

void QDarwinAudioSourceBuffer::flush(bool all)
{
    if (m_device == 0)
        return;

    const int used = m_buffer->used();
    const int readSize = all ? used : used - (used % m_maxPeriodSize);

    if (readSize > 0) {
        bool    wecan = true;
        int     flushed = 0;

        while (!m_deviceError && wecan && flushed < readSize) {
            CoreAudioRingBuffer::Region region = m_buffer->acquireReadRegion(readSize - flushed);

            if (region.second > 0) {
                int bytesWritten = m_device->write(region.first, region.second);
                if (bytesWritten < 0) {
                    stopFlushTimer();
                    m_deviceError = true;
                }
                else {
                    region.second = bytesWritten;
                    flushed += bytesWritten;
                    wecan = bytesWritten != 0;
                }
            }
            else
                wecan = false;

            m_buffer->releaseReadRegion(region);
        }
    }
}

void QDarwinAudioSourceBuffer::reset()
{
    m_buffer->reset();
    m_deviceError = false;
}

int QDarwinAudioSourceBuffer::available() const
{
    return m_buffer->free();
}

int QDarwinAudioSourceBuffer::used() const
{
    return m_buffer->used();
}

void QDarwinAudioSourceBuffer::flushBuffer()
{
    flush();
}

OSStatus QDarwinAudioSourceBuffer::converterCallback(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
    Q_UNUSED(inAudioConverter);
    Q_UNUSED(outDataPacketDescription);

    QCoreAudioPacketFeeder* feeder = static_cast<QCoreAudioPacketFeeder*>(inUserData);

    if (!feeder->feed(*ioData, *ioNumberDataPackets))
        return as_empty;

    return noErr;
}

QDarwinAudioSourceDevice::QDarwinAudioSourceDevice(QDarwinAudioSourceBuffer *audioBuffer, QObject *parent)
    : QIODevice(parent)
    , m_audioBuffer(audioBuffer)
{
    open(QIODevice::ReadOnly | QIODevice::Unbuffered);
    connect(m_audioBuffer, SIGNAL(readyRead()), SIGNAL(readyRead()));
}

qint64 QDarwinAudioSourceDevice::readData(char *data, qint64 len)
{
    return m_audioBuffer->readBytes(data, len);
}

qint64 QDarwinAudioSourceDevice::writeData(const char *data, qint64 len)
{
    Q_UNUSED(data);
    Q_UNUSED(len);

    return 0;
}

QDarwinAudioSource::QDarwinAudioSource(const QAudioDevice &device)
    : m_audioDeviceInfo(device)
    , m_isOpen(false)
    , m_internalBufferSize(DEFAULT_BUFFER_SIZE)
    , m_totalFrames(0)
    , m_audioUnit(0)
    , m_clockFrequency(CoreAudioUtils::frequency() / 1000)
    , m_errorCode(QAudio::NoError)
    , m_stateCode(QAudio::StoppedState)
    , m_audioBuffer(nullptr)
    , m_volume(1.0)
{
    QAudioDevice di = device;
    if (di.isNull())
        di = QMediaDevices::defaultAudioInput();
#if defined(Q_OS_MACOS)
    const QCoreAudioDeviceInfo *info = static_cast<const QCoreAudioDeviceInfo *>(di.handle());
    Q_ASSERT(info);
    m_audioDeviceId = info->deviceID();
#endif
    m_device = di.id();
}


QDarwinAudioSource::~QDarwinAudioSource()
{
    close();
}

bool QDarwinAudioSource::open()
{
#if defined(Q_OS_IOS)
    CoreAudioSessionManager::instance().setCategory(CoreAudioSessionManager::PlayAndRecord, CoreAudioSessionManager::MixWithOthers);
    CoreAudioSessionManager::instance().setActive(true);
#endif

    if (m_isOpen)
        return true;

    AudioComponentDescription componentDescription;
    componentDescription.componentType = kAudioUnitType_Output;
#if defined(Q_OS_OSX)
    componentDescription.componentSubType = kAudioUnitSubType_HALOutput;
#else
    componentDescription.componentSubType = kAudioUnitSubType_RemoteIO;
#endif
    componentDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
    componentDescription.componentFlags = 0;
    componentDescription.componentFlagsMask = 0;

    AudioComponent component = AudioComponentFindNext(0, &componentDescription);
    if (component == 0) {
        qWarning() << "QAudioSource: Failed to find Output component";
        return false;
    }

    if (AudioComponentInstanceNew(component, &m_audioUnit) != noErr) {
        qWarning() << "QAudioSource: Unable to Open Output Component";
        return false;
    }

    // Set mode
    // switch to input mode
    UInt32 enable = 1;
    if (AudioUnitSetProperty(m_audioUnit,
                               kAudioOutputUnitProperty_EnableIO,
                               kAudioUnitScope_Input,
                               1,
                               &enable,
                               sizeof(enable)) != noErr) {
        qWarning() << "QAudioSource: Unable to switch to input mode (Enable Input)";
        return false;
    }

    enable = 0;
    if (AudioUnitSetProperty(m_audioUnit,
                            kAudioOutputUnitProperty_EnableIO,
                            kAudioUnitScope_Output,
                            0,
                            &enable,
                            sizeof(enable)) != noErr) {
        qWarning() << "QAudioSource: Unable to switch to input mode (Disable output)";
        return false;
    }

    // register callback
    AURenderCallbackStruct callback;
    callback.inputProc = inputCallback;
    callback.inputProcRefCon = this;

    if (AudioUnitSetProperty(m_audioUnit,
                               kAudioOutputUnitProperty_SetInputCallback,
                               kAudioUnitScope_Global,
                               0,
                               &callback,
                               sizeof(callback)) != noErr) {
        qWarning() << "QAudioSource: Failed to set AudioUnit callback";
        return false;
    }

#if defined(Q_OS_OSX)
    //Set Audio Device
    if (AudioUnitSetProperty(m_audioUnit,
                             kAudioOutputUnitProperty_CurrentDevice,
                             kAudioUnitScope_Global,
                             0,
                             &m_audioDeviceId,
                             sizeof(m_audioDeviceId)) != noErr) {
        qWarning() << "QAudioSource: Unable to use configured device";
        return false;
    }
#endif

    //set format
    m_streamFormat = CoreAudioUtils::toAudioStreamBasicDescription(m_audioFormat);

#if defined(Q_OS_OSX)
    UInt32  size = 0;
    if (m_audioFormat == m_audioDeviceInfo.preferredFormat()) {
#endif

    m_deviceFormat = m_streamFormat;
    AudioUnitSetProperty(m_audioUnit,
                         kAudioUnitProperty_StreamFormat,
                         kAudioUnitScope_Output,
                         1,
                         &m_deviceFormat,
                         sizeof(m_deviceFormat));
#if defined(Q_OS_OSX)
    } else {
        size = sizeof(m_deviceFormat);
        if (AudioUnitGetProperty(m_audioUnit,
                                 kAudioUnitProperty_StreamFormat,
                                 kAudioUnitScope_Input,
                                 1,
                                 &m_deviceFormat,
                                 &size) != noErr) {
            qWarning() << "QAudioSource: Unable to retrieve device format";
            return false;
        }

        if (AudioUnitSetProperty(m_audioUnit,
                                 kAudioUnitProperty_StreamFormat,
                                 kAudioUnitScope_Output,
                                 1,
                                 &m_deviceFormat,
                                 sizeof(m_deviceFormat)) != noErr) {
            qWarning() << "QAudioSource: Unable to set device format";
            return false;
        }
    }
#endif

    //setup buffers
    UInt32 numberOfFrames;
#if defined(Q_OS_OSX)
    size = sizeof(UInt32);
    if (AudioUnitGetProperty(m_audioUnit,
                             kAudioDevicePropertyBufferFrameSize,
                             kAudioUnitScope_Global,
                             0,
                             &numberOfFrames,
                             &size) != noErr) {
        qWarning() << "QAudioSource: Failed to get audio period size";
        return false;
    }
    //BUG: numberOfFrames gets ignored after this point

    AudioValueRange bufferRange;
    size = sizeof(AudioValueRange);

    if (AudioUnitGetProperty(m_audioUnit,
                             kAudioDevicePropertyBufferFrameSizeRange,
                             kAudioUnitScope_Global,
                             0,
                             &bufferRange,
                             &size) != noErr) {
        qWarning() << "QAudioSource: Failed to get audio period size range";
        return false;
    }

    // See if the requested buffer size is permissible
    numberOfFrames = qBound((UInt32)bufferRange.mMinimum, m_internalBufferSize / m_streamFormat.mBytesPerFrame, (UInt32)bufferRange.mMaximum);

    // Set it back
    if (AudioUnitSetProperty(m_audioUnit,
                             kAudioDevicePropertyBufferFrameSize,
                             kAudioUnitScope_Global,
                             0,
                             &numberOfFrames,
                             sizeof(UInt32)) != noErr) {
        qWarning() << "QAudioSource: Failed to set audio buffer size";
        return false;
    }
#else //iOS
    Float32 bufferSize = CoreAudioSessionManager::instance().currentIOBufferDuration();
    bufferSize *= m_streamFormat.mSampleRate;
    numberOfFrames = bufferSize;
#endif

    // Now allocate a few buffers to be safe.
    m_periodSizeBytes = m_internalBufferSize = numberOfFrames * m_streamFormat.mBytesPerFrame;

    {
        QMutexLocker lock(m_audioBuffer);
        m_audioBuffer = new QDarwinAudioSourceBuffer(m_internalBufferSize * 4,
                                            m_periodSizeBytes,
                                            m_deviceFormat,
                                            m_streamFormat,
                                            this);

        m_audioBuffer->setVolume(m_volume);
    }
    m_audioIO = new QDarwinAudioSourceDevice(m_audioBuffer, this);

    // Init
    if (AudioUnitInitialize(m_audioUnit) != noErr) {
        qWarning() << "QAudioSource: Failed to initialize AudioUnit";
        return false;
    }

    m_isOpen = true;

    return m_isOpen;

}

void QDarwinAudioSource::close()
{
    stop();
    if (m_audioUnit != 0) {
        AudioOutputUnitStop(m_audioUnit);
        AudioUnitUninitialize(m_audioUnit);
        AudioComponentInstanceDispose(m_audioUnit);
    }

    delete m_audioBuffer;
    m_audioBuffer = nullptr;
    m_isOpen = false;
}

void QDarwinAudioSource::start(QIODevice *device)
{
    QIODevice* op = device;

    if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
        m_stateCode = QAudio::StoppedState;
        m_errorCode = QAudio::OpenError;
        return;
    }

    reset();
    {
        QMutexLocker lock(m_audioBuffer);
        m_audioBuffer->reset();
        m_audioBuffer->setFlushDevice(op);
    }

    if (op == 0)
        op = m_audioIO;

    // Start
    m_totalFrames = 0;

    m_stateCode = QAudio::IdleState;
    m_errorCode = QAudio::NoError;
    emit stateChanged(m_stateCode);

    audioThreadStart();
}


QIODevice *QDarwinAudioSource::start()
{
    QIODevice* op = 0;

    if (!m_audioDeviceInfo.isFormatSupported(m_audioFormat) || !open()) {
        m_stateCode = QAudio::StoppedState;
        m_errorCode = QAudio::OpenError;
        return m_audioIO;
    }

    reset();
    {
        QMutexLocker lock(m_audioBuffer);
        m_audioBuffer->reset();
        m_audioBuffer->setFlushDevice(op);
    }

    if (op == 0)
        op = m_audioIO;

    // Start
    m_totalFrames = 0;

    m_stateCode = QAudio::IdleState;
    m_errorCode = QAudio::NoError;
    emit stateChanged(m_stateCode);

    audioThreadStart();

    return op;
}


void QDarwinAudioSource::stop()
{
    QMutexLocker lock(m_audioBuffer);
    if (m_stateCode != QAudio::StoppedState) {
        audioThreadStop();
        m_audioBuffer->flush(true);

        m_errorCode = QAudio::NoError;
        m_stateCode = QAudio::StoppedState;
        QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode));
    }
}


void QDarwinAudioSource::reset()
{
    QMutexLocker lock(m_audioBuffer);
    if (m_stateCode != QAudio::StoppedState) {
        audioThreadStop();

        m_errorCode = QAudio::NoError;
        m_stateCode = QAudio::StoppedState;
        m_audioBuffer->reset();
        QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode));
    }
}


void QDarwinAudioSource::suspend()
{
    QMutexLocker lock(m_audioBuffer);
    if (m_stateCode == QAudio::ActiveState || m_stateCode == QAudio::IdleState) {
        audioThreadStop();

        m_errorCode = QAudio::NoError;
        m_stateCode = QAudio::SuspendedState;
        QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode));
    }
}


void QDarwinAudioSource::resume()
{
    QMutexLocker lock(m_audioBuffer);
    if (m_stateCode == QAudio::SuspendedState) {
        audioThreadStart();

        m_errorCode = QAudio::NoError;
        m_stateCode = QAudio::ActiveState;
        QMetaObject::invokeMethod(this, "stateChanged", Qt::QueuedConnection, Q_ARG(QAudio::State, m_stateCode));
    }
}


qsizetype QDarwinAudioSource::bytesReady() const
{
    QMutexLocker lock(m_audioBuffer);
    if (!m_audioBuffer)
        return 0;
    return m_audioBuffer->used();
}

void QDarwinAudioSource::setBufferSize(qsizetype value)
{
    m_internalBufferSize = value;
}


qsizetype QDarwinAudioSource::bufferSize() const
{
    return m_internalBufferSize;
}

qint64 QDarwinAudioSource::processedUSecs() const
{
    return m_totalFrames * 1000000 / m_audioFormat.sampleRate();
}

QAudio::Error QDarwinAudioSource::error() const
{
    return m_errorCode;
}


QAudio::State QDarwinAudioSource::state() const
{
    return m_stateCode;
}


void QDarwinAudioSource::setFormat(const QAudioFormat &format)
{
    if (m_stateCode == QAudio::StoppedState)
        m_audioFormat = format;
}


QAudioFormat QDarwinAudioSource::format() const
{
    return m_audioFormat;
}


void QDarwinAudioSource::setVolume(qreal volume)
{
    QMutexLocker lock(m_audioBuffer);
    m_volume = volume;
    if (m_audioBuffer)
        m_audioBuffer->setVolume(m_volume);
}


qreal QDarwinAudioSource::volume() const
{
    return m_volume;
}

void QDarwinAudioSource::deviceStoppped()
{
    stopTimers();
    emit stateChanged(m_stateCode);
}

void QDarwinAudioSource::audioThreadStart()
{
    startTimers();
    m_audioThreadState.storeRelaxed(Running);
    AudioOutputUnitStart(m_audioUnit);
}

void QDarwinAudioSource::audioThreadStop()
{
    stopTimers();
    if (m_audioThreadState.testAndSetAcquire(Running, Stopped))
        m_audioBuffer->wait();
}

void QDarwinAudioSource::audioDeviceStop()
{
    AudioOutputUnitStop(m_audioUnit);
    m_audioThreadState.storeRelaxed(Stopped);
    m_audioBuffer->wake();
}

void QDarwinAudioSource::audioDeviceActive()
{
    if (m_stateCode == QAudio::IdleState) {
        QMutexLocker lock(m_audioBuffer);
        m_stateCode = QAudio::ActiveState;
        emit stateChanged(m_stateCode);
    }
}

void QDarwinAudioSource::audioDeviceFull()
{
    if (m_stateCode == QAudio::ActiveState) {
        QMutexLocker lock(m_audioBuffer);
        m_errorCode = QAudio::UnderrunError;
        m_stateCode = QAudio::IdleState;
        emit stateChanged(m_stateCode);
    }
}

void QDarwinAudioSource::audioDeviceError()
{
    if (m_stateCode == QAudio::ActiveState) {
        QMutexLocker lock(m_audioBuffer);
        audioDeviceStop();

        m_errorCode = QAudio::IOError;
        m_stateCode = QAudio::StoppedState;
        QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection);
    }
}

void QDarwinAudioSource::startTimers()
{
    m_audioBuffer->startFlushTimer();
}

void QDarwinAudioSource::stopTimers()
{
    m_audioBuffer->stopFlushTimer();
}

OSStatus QDarwinAudioSource::inputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{
    Q_UNUSED(ioData);

    QDarwinAudioSource* d = static_cast<QDarwinAudioSource*>(inRefCon);

    const int threadState = d->m_audioThreadState.loadAcquire();
    if (threadState == Stopped)
        d->audioDeviceStop();
    else {
        qint64 framesWritten;

        {
            QMutexLocker locker(d->m_audioBuffer);
            framesWritten = d->m_audioBuffer->renderFromDevice(d->m_audioUnit,
                                                             ioActionFlags,
                                                             inTimeStamp,
                                                             inBusNumber,
                                                             inNumberFrames);
        }

        if (framesWritten > 0) {
            d->m_totalFrames += framesWritten;
            d->audioDeviceActive();
        } else if (framesWritten == 0)
            d->audioDeviceFull();
        else if (framesWritten < 0)
            d->audioDeviceError();
    }

    return noErr;
}

QT_END_NAMESPACE

#include "moc_qdarwinaudiosource_p.cpp"
