/* $Id: DrvAudio.cpp $ */
/** @file
 * Intermediate audio driver header.
 *
 * @remarks Intermediate audio driver having audio device as one of the sink and
 *          host backend as other.
 */

/*
 * Copyright (C) 2006-2015 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 * --------------------------------------------------------------------
 *
 * This code is based on: audio.c from QEMU AUDIO subsystem.
 *
 * QEMU Audio subsystem
 *
 * Copyright (c) 2003-2005 Vassili Karpov (malc)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#define LOG_GROUP LOG_GROUP_DRV_AUDIO
#include <VBox/log.h>
#include <VBox/vmm/pdm.h>
#include <VBox/err.h>
#include <VBox/vmm/mm.h>
#include <VBox/vmm/pdmaudioifs.h>

#include <iprt/alloc.h>
#include <iprt/asm-math.h>
#include <iprt/assert.h>
#include <iprt/circbuf.h>
#include <iprt/string.h>
#include <iprt/uuid.h>

#include "VBoxDD.h"

#include <ctype.h>
#include <stdlib.h>

#include "DrvAudio.h"
#include "AudioMixBuffer.h"

static int drvAudioDestroyGstIn(PDRVAUDIO pThis, PPDMAUDIOGSTSTRMIN pGstStrmIn);

static int drvAudioAllocHstIn(PDRVAUDIO pThis, const char *pszName, PPDMAUDIOSTREAMCFG pCfg, PDMAUDIORECSOURCE enmRecSource, PPDMAUDIOHSTSTRMIN *ppHstStrmIn);
static int drvAudioDestroyHstIn(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMIN pHstStrmIn);

int drvAudioAddHstOut(PDRVAUDIO pThis, const char *pszName, PPDMAUDIOSTREAMCFG pCfg, PPDMAUDIOHSTSTRMOUT *ppHstStrmOut)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);

    PPDMAUDIOHSTSTRMOUT pHstStrmOut;

    int rc;
    if (   conf.fixed_out.enabled /** @todo Get rid of these settings! */
        && conf.fixed_out.greedy)
    {
        rc = drvAudioAllocHstOut(pThis, pszName, pCfg, &pHstStrmOut);
    }
    else
        rc = VERR_NOT_FOUND;

    if (RT_FAILURE(rc))
    {
        pHstStrmOut = drvAudioFindSpecificOut(pThis, NULL, pCfg);
        if (!pHstStrmOut)
        {
            rc = drvAudioAllocHstOut(pThis, pszName, pCfg, &pHstStrmOut);
            if (RT_FAILURE(rc))
                pHstStrmOut = drvAudioFindAnyHstOut(pThis, NULL /* pHstStrmOut */);
        }

        rc = pHstStrmOut ? VINF_SUCCESS : rc;
    }

    if (RT_SUCCESS(rc))
        *ppHstStrmOut = pHstStrmOut;

    return rc;
}

static PDMAUDIOFMT drvAudioGetConfFormat(PCFGMNODE pCfgHandle, const char *pszKey,
                                         PDMAUDIOFMT enmDefault, bool *pfDefault)
{
    if (   pCfgHandle == NULL
        || pszKey == NULL)
    {
        *pfDefault = true;
        return enmDefault;
    }

    char *pszValue = NULL;
    int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
    if (RT_FAILURE(rc))
    {
        *pfDefault = true;
        return enmDefault;
    }

    PDMAUDIOFMT fmt = drvAudioHlpStringToFormat(pszValue);
    if (fmt == AUD_FMT_INVALID)
    {
         *pfDefault = true;
        return enmDefault;
    }

    *pfDefault = false;
    return fmt;
}

static int drvAudioGetConfInt(PCFGMNODE pCfgHandle, const char *pszKey,
                              int iDefault, bool *pfDefault)
{

    if (   pCfgHandle == NULL
        || pszKey == NULL)
    {
        *pfDefault = true;
        return iDefault;
    }

    uint64_t u64Data = 0;
    int rc = CFGMR3QueryInteger(pCfgHandle, pszKey, &u64Data);
    if (RT_FAILURE(rc))
    {
        *pfDefault = true;
        return iDefault;

    }

    *pfDefault = false;
    return u64Data;
}

static const char *drvAudioGetConfStr(PCFGMNODE pCfgHandle, const char *pszKey,
                                      const char *pszDefault, bool *pfDefault)
{
    if (   pCfgHandle == NULL
        || pszKey == NULL)
    {
        *pfDefault = true;
        return pszDefault;
    }

    char *pszValue = NULL;
    int rc = CFGMR3QueryStringAlloc(pCfgHandle, pszKey, &pszValue);
    if (RT_FAILURE(rc))
    {
        *pfDefault = true;
        return pszDefault;
    }

    *pfDefault = false;
    return pszValue;
}

static int drvAudioProcessOptions(PCFGMNODE pCfgHandle, const char *pszPrefix, struct audio_option *opt)
{
    AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
    AssertPtrReturn(pszPrefix, VERR_INVALID_POINTER);
    AssertPtrReturn(opt, VERR_INVALID_POINTER);

    PCFGMNODE pCfgChildHandle = NULL;
    PCFGMNODE pCfgChildChildHandle = NULL;

   /* If pCfgHandle is NULL, let NULL be passed to get int and get string functions..
    * The getter function will return default values.
    */
    if (pCfgHandle != NULL)
    {
       /* If its audio general setting, need to traverse to one child node.
        * /Devices/ichac97/0/LUN#0/Config/Audio
        */
       if(!strncmp(pszPrefix, "AUDIO", 5)) /** @todo Use a #define */
       {
            pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
            if(pCfgChildHandle)
                pCfgHandle = pCfgChildHandle;
        }
        else
        {
            /* If its driver specific configuration , then need to traverse two level deep child
             * child nodes. for eg. in case of DirectSoundConfiguration item
             * /Devices/ichac97/0/LUN#0/Config/Audio/DirectSoundConfig
             */
            pCfgChildHandle = CFGMR3GetFirstChild(pCfgHandle);
            if (pCfgChildHandle)
            {
                pCfgChildChildHandle = CFGMR3GetFirstChild(pCfgChildHandle);
                if (pCfgChildChildHandle)
                    pCfgHandle = pCfgChildChildHandle;
            }
        }
    }

    for (; opt->name; opt++)
    {
        LogFlowFunc(("Option value pointer for `%s' is not set\n",
                     opt->name));
        if (!opt->valp) {
            LogFlowFunc(("Option value pointer for `%s' is not set\n",
                   opt->name));
            continue;
        }

        bool fUseDefault;

        switch (opt->tag)
        {
            case AUD_OPT_BOOL:
            case AUD_OPT_INT:
            {
                int *intp = (int *)opt->valp;
                *intp = drvAudioGetConfInt(pCfgHandle, opt->name, *intp, &fUseDefault);

                break;
            }

            case AUD_OPT_FMT:
            {
                PDMAUDIOFMT *fmtp = (PDMAUDIOFMT *)opt->valp;
                *fmtp = drvAudioGetConfFormat(pCfgHandle, opt->name, *fmtp, &fUseDefault);

                break;
            }

            case AUD_OPT_STR:
            {
                const char **strp = (const char **)opt->valp;
                *strp = drvAudioGetConfStr(pCfgHandle, opt->name, *strp, &fUseDefault);

                break;
            }

            default:
                LogFlowFunc(("Bad value tag for option `%s' - %d\n", opt->name, opt->tag));
                fUseDefault = false;
                break;
        }

        if (!opt->overridenp)
            opt->overridenp = &opt->overriden;

        *opt->overridenp = !fUseDefault;
    }

    return VINF_SUCCESS;
}

static bool drvAudioStreamCfgIsValid(PPDMAUDIOSTREAMCFG pCfg)
{
    bool fValid = (   pCfg->cChannels == 1
                   || pCfg->cChannels == 2); /* Either stereo (2) or mono (1), per stream. */

    fValid |= (   pCfg->enmEndianness == PDMAUDIOENDIANNESS_LITTLE
               || pCfg->enmEndianness == PDMAUDIOENDIANNESS_BIG);

    if (fValid)
    {
        switch (pCfg->enmFormat)
        {
            case AUD_FMT_S8:
            case AUD_FMT_U8:
            case AUD_FMT_S16:
            case AUD_FMT_U16:
            case AUD_FMT_S32:
            case AUD_FMT_U32:
                break;
            default:
                fValid = false;
                break;
        }
    }

    /** @todo Check for defined frequencies supported. */
    fValid |= pCfg->uHz > 0;

#ifdef DEBUG
    drvAudioStreamCfgPrint(pCfg);
#endif

    LogFlowFunc(("pCfg=%p, fValid=%RTbool\n", pCfg, fValid));
    return fValid;
}

void audio_pcm_info_clear_buf(PPDMPCMPROPS pPCMInfo, void *pvBuf, int len)
{
    if (!len)
        return;

    if (pPCMInfo->fSigned)
    {
        memset (pvBuf, 0, len << pPCMInfo->cShift);
    }
    else
    {
        switch (pPCMInfo->cBits)
        {

        case 8:
            memset (pvBuf, 0x80, len << pPCMInfo->cShift);
            break;

        case 16:
            {
                int i;
                uint16_t *p = (uint16_t *)pvBuf;
                int shift = pPCMInfo->cChannels - 1;
                short s = INT16_MAX;

                if (pPCMInfo->fSwapEndian)
                    s = RT_BSWAP_U16(s);

                for (i = 0; i < len << shift; i++)
                    p[i] = s;
            }
            break;

        case 32:
            {
                int i;
                uint32_t *p = (uint32_t *)pvBuf;
                int shift = pPCMInfo->cChannels - 1;
                int32_t s = INT32_MAX;

                if (pPCMInfo->fSwapEndian)
                    s = RT_BSWAP_U32(s);

                for (i = 0; i < len << shift; i++)
                    p[i] = s;
            }
            break;

        default:
            LogFlowFunc(("audio_pcm_info_clear_buf: invalid bits %d\n", pPCMInfo->cBits));
            break;
        }
    }
}

int drvAudioDestroyHstOut(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);

    LogFlowFunc(("%s\n", pHstStrmOut->MixBuf.pszName));

    int rc;
    if (RTListIsEmpty(&pHstStrmOut->lstGstStrmOut))
    {
        rc = pThis->pHostDrvAudio->pfnFiniOut(pThis->pHostDrvAudio, pHstStrmOut);
        if (RT_SUCCESS(rc))
        {
            drvAudioHstOutFreeRes(pHstStrmOut);

            /* Remove from driver instance list. */
            RTListNodeRemove(&pHstStrmOut->Node);

            RTMemFree(pHstStrmOut);
            pThis->cFreeOutputStreams++;
            return VINF_SUCCESS;
        }
    }
    else
    {
        rc = VERR_ACCESS_DENIED;
        LogFlowFunc(("[%s] Still is being used, rc=%Rrc\n", pHstStrmOut->MixBuf.pszName, rc));
    }

    return rc;
}

int drvAudioDestroyGstOut(PDRVAUDIO pThis, PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    if (pGstStrmOut)
    {
        drvAudioGstOutFreeRes(pGstStrmOut);

        if (pGstStrmOut->pHstStrmOut)
        {
            /* Unregister from parent first. */
            RTListNodeRemove(&pGstStrmOut->Node);

            /* Try destroying the associated host output stream. This could
             * be skipped if there are other guest output streams with this
             * host stream. */
            drvAudioDestroyHstOut(pThis, pGstStrmOut->pHstStrmOut);
        }

        RTMemFree(pGstStrmOut);
    }

    return VINF_SUCCESS;
}

PPDMAUDIOHSTSTRMIN drvAudioFindNextHstIn(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
    if (pHstStrmIn)
    {
        if (RTListNodeIsLast(&pThis->lstHstStrmIn, &pHstStrmIn->Node))
            return NULL;

        return RTListNodeGetNext(&pHstStrmIn->Node, PDMAUDIOHSTSTRMIN, Node);
    }

    return RTListGetFirst(&pThis->lstHstStrmIn, PDMAUDIOHSTSTRMIN, Node);
}

PPDMAUDIOHSTSTRMIN drvAudioFindNextEnabledHstIn(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
    while ((pHstStrmIn = drvAudioFindNextHstIn(pThis, pHstStrmIn)))
        if (pHstStrmIn->fEnabled)
            return pHstStrmIn;

    return NULL;
}

PPDMAUDIOHSTSTRMIN drvAudioFindNextEqHstIn(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMIN pHstStrmIn,
                                           PPDMAUDIOSTREAMCFG pCfg)
{
    while ((pHstStrmIn = drvAudioFindNextHstIn(pThis, pHstStrmIn)))
        if (drvAudioPCMPropsAreEqual(&pHstStrmIn->Props, pCfg))
            return pHstStrmIn;

    return NULL;
}

static int drvAudioHstInAdd(PDRVAUDIO pThis, const char *pszName, PPDMAUDIOSTREAMCFG pCfg, PDMAUDIORECSOURCE enmRecSource,
                            PPDMAUDIOHSTSTRMIN *ppHstStrmIn)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
    AssertPtrReturn(ppHstStrmIn, VERR_INVALID_POINTER);

    PPDMAUDIOHSTSTRMIN pHstStrmIn;
    int rc = drvAudioAllocHstIn(pThis, pszName, pCfg, enmRecSource, &pHstStrmIn);
    if (RT_SUCCESS(rc))
        *ppHstStrmIn = pHstStrmIn;

    LogFlowFuncLeaveRC(rc);
    return rc;
}

int drvAudioGstOutInit(PPDMAUDIOGSTSTRMOUT pGstStrmOut, PPDMAUDIOHSTSTRMOUT pHostStrmOut,
                       const char *pszName, PPDMAUDIOSTREAMCFG pCfg)
{
    AssertPtrReturn(pGstStrmOut,  VERR_INVALID_POINTER);
    AssertPtrReturn(pHostStrmOut, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName,      VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg,         VERR_INVALID_POINTER);

    int rc = drvAudioStreamCfgToProps(pCfg, &pGstStrmOut->Props);
    if (RT_SUCCESS(rc))
    {
        char *pszTemp;
        if (RTStrAPrintf(&pszTemp, "%s (Guest)", pszName) <= 0)
            return VERR_NO_MEMORY;

        rc = AudioMixBufInit(&pGstStrmOut->MixBuf, pszTemp, &pGstStrmOut->Props, AudioMixBufSize(&pHostStrmOut->MixBuf));
        if (RT_SUCCESS(rc))
            rc = AudioMixBufLinkTo(&pGstStrmOut->MixBuf, &pHostStrmOut->MixBuf);

        RTStrFree(pszTemp);

        if (RT_SUCCESS(rc))
        {
            pGstStrmOut->State.fActive = false;
            pGstStrmOut->State.fEmpty  = true;

            pGstStrmOut->State.pszName = RTStrDup(pszName);
            if (!pGstStrmOut->State.pszName)
                return VERR_NO_MEMORY;

            pGstStrmOut->pHstStrmOut = pHostStrmOut;
        }
    }

    LogFlowFunc(("pszName=%s, rc=%Rrc\n", pszName, rc));
    return rc;
}

int drvAudioAllocHstOut(PDRVAUDIO pThis, const char *pszName, PPDMAUDIOSTREAMCFG pCfg, PPDMAUDIOHSTSTRMOUT *ppHstStrmOut)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);

    if (!pThis->cFreeOutputStreams)
    {
        LogFlowFunc(("Maximum number of host output streams reached\n"));
        return VERR_NO_MORE_HANDLES;
    }

    /* Validate backend configuration. */
    if (!pThis->BackendCfg.cbStreamOut)
    {
        LogFlowFunc(("Backend output configuration not valid, bailing out\n"));
        return VERR_INVALID_PARAMETER;
    }

    PPDMAUDIOHSTSTRMOUT pHstStrmOut = (PPDMAUDIOHSTSTRMOUT)RTMemAllocZ(pThis->BackendCfg.cbStreamOut);
    if (!pHstStrmOut)
    {
        LogFlowFunc(("Error allocating host output stream with %zu bytes\n",
                     pThis->BackendCfg.cbStreamOut));
        return VERR_NO_MEMORY;
    }

    int rc;
    bool fInitialized = false;

    do
    {
        RTListInit(&pHstStrmOut->lstGstStrmOut);

        uint32_t cSamples;
        rc = pThis->pHostDrvAudio->pfnInitOut(pThis->pHostDrvAudio, pHstStrmOut, pCfg, &cSamples);
        if (RT_FAILURE(rc))
        {
            LogFlowFunc(("Initializing host backend failed with rc=%Rrc\n", rc));
            break;
        }

        fInitialized = true;

        char *pszTemp;
        if (RTStrAPrintf(&pszTemp, "%s (Host)", pszName) <= 0)
        {
            rc = VERR_NO_MEMORY;
            break;
        }

        rc = AudioMixBufInit(&pHstStrmOut->MixBuf, pszTemp, &pHstStrmOut->Props, cSamples);
        if (RT_SUCCESS(rc))
        {
            RTListPrepend(&pThis->lstHstStrmOut, &pHstStrmOut->Node);
            pThis->cFreeOutputStreams--;
        }

        RTStrFree(pszTemp);

    } while (0);

    if (RT_FAILURE(rc))
    {
        if (fInitialized)
        {
            int rc2 = pThis->pHostDrvAudio->pfnFiniOut(pThis->pHostDrvAudio, pHstStrmOut);
            AssertRC(rc2);
        }

        drvAudioHstOutFreeRes(pHstStrmOut);
        RTMemFree(pHstStrmOut);
    }
    else
        *ppHstStrmOut = pHstStrmOut;

    LogFlowFuncLeaveRC(rc);
    return rc;
}

int drvAudioCreateStreamPairOut(PDRVAUDIO pThis, const char *pszName,
                                PPDMAUDIOSTREAMCFG pCfg, PPDMAUDIOGSTSTRMOUT *ppGstStrmOut)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);

    /*
     * Try figuring out which audio stream configuration this backend
     * should use. If fixed output is enabled the backend will be tied
     * to a fixed rate (in Hz, among other parameters), regardless of
     * what the backend could do else.
     */
    PPDMAUDIOSTREAMCFG pBackendCfg;
    if (conf.fixed_out.enabled)
        pBackendCfg = &conf.fixed_out.settings;
    else
        pBackendCfg = pCfg;

    AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);

    LogFlowFunc(("Using fixed audio output settings: %RTbool\n",
                 RT_BOOL(conf.fixed_out.enabled)));

    PPDMAUDIOGSTSTRMOUT pGstStrmOut =
        (PPDMAUDIOGSTSTRMOUT)RTMemAllocZ(sizeof(PDMAUDIOGSTSTRMOUT));
    if (!pGstStrmOut)
    {
        LogFlowFunc(("Failed to allocate memory for guest output stream \"%s\"\n", pszName));
        return VERR_NO_MEMORY;
    }

    /*
     * The host stream always will get the backend audio stream configuration.
     */
    PPDMAUDIOHSTSTRMOUT pHstStrmOut;
    int rc = drvAudioAddHstOut(pThis, pszName, pBackendCfg, &pHstStrmOut);
    if (RT_FAILURE(rc))
    {
        LogFlowFunc(("Error adding host output stream \"%s\", rc=%Rrc\n", pszName, rc));

        RTMemFree(pGstStrmOut);
        return rc;
    }

    /*
     * The guest stream always will get the audio stream configuration told
     * by the device emulation (which in turn was/could be set by the guest OS).
     */
    rc = drvAudioGstOutInit(pGstStrmOut, pHstStrmOut, pszName, pCfg);
    if (RT_SUCCESS(rc))
    {
        RTListPrepend(&pHstStrmOut->lstGstStrmOut, &pGstStrmOut->Node);

        if (ppGstStrmOut)
            *ppGstStrmOut = pGstStrmOut;
    }

    if (RT_FAILURE(rc))
        drvAudioDestroyGstOut(pThis, pGstStrmOut);

    LogFlowFuncLeaveRC(rc);
    return rc;
}

static int drvAudioCreateStreamPairIn(PDRVAUDIO pThis, const char *pszName, PDMAUDIORECSOURCE enmRecSource,
                                      PPDMAUDIOSTREAMCFG pCfg, PPDMAUDIOGSTSTRMIN *ppGstStrmIn)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);

/*
     * Try figuring out which audio stream configuration this backend
     * should use for the audio input data. If fixed input is enabled
     * the backend will be tied to a fixed rate (in Hz, among other parameters),
     * regardless of what the backend initially wanted to use.
     */
    PPDMAUDIOSTREAMCFG pBackendCfg;
    if (conf.fixed_in.enabled)
        pBackendCfg = &conf.fixed_in.settings;
    else
        pBackendCfg = pCfg;

    AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);

    LogFlowFunc(("Using fixed audio input settings: %RTbool\n",
                 RT_BOOL(conf.fixed_in.enabled)));

    PPDMAUDIOGSTSTRMIN pGstStrmIn = (PPDMAUDIOGSTSTRMIN)RTMemAllocZ(sizeof(PDMAUDIOGSTSTRMIN));
    if (!pGstStrmIn)
        return VERR_NO_MEMORY;

    /*
     * The host stream always will get the backend audio stream configuration.
     */
    PPDMAUDIOHSTSTRMIN pHstStrmIn;
    int rc = drvAudioHstInAdd(pThis, pszName, pBackendCfg, enmRecSource, &pHstStrmIn);
    if (RT_FAILURE(rc))
    {
        LogFunc(("Failed to add host audio input stream \"%s\", rc=%Rrc\n", pszName, rc));

        RTMemFree(pGstStrmIn);
        return rc;
    }

    /*
     * The guest stream always will get the audio stream configuration told
     * by the device emulation (which in turn was/could be set by the guest OS).
     */
    rc = drvAudioGstInInit(pGstStrmIn, pHstStrmIn, pszName, pCfg);
    if (RT_SUCCESS(rc))
    {
        pHstStrmIn->pGstStrmIn = pGstStrmIn;

        if (ppGstStrmIn)
            *ppGstStrmIn = pGstStrmIn;
    }
    else
        drvAudioDestroyGstIn(pThis, pGstStrmIn);

    LogFlowFuncLeaveRC(rc);
    return rc;
}

/**
 * Initializes a guest input stream.
 *
 * @return  IPRT status code.
 * @param   pGstStrmIn          Pointer to guest stream to initialize.
 * @param   pHstStrmIn          Pointer to host input stream to associate this guest
 *                              stream with.
 * @param   pszName             Pointer to stream name to use for this stream.
 * @param   pCfg                Pointer to stream configuration to use.
 */
int drvAudioGstInInit(PPDMAUDIOGSTSTRMIN pGstStrmIn, PPDMAUDIOHSTSTRMIN pHstStrmIn,
                      const char *pszName, PPDMAUDIOSTREAMCFG pCfg)
{
    AssertPtrReturn(pGstStrmIn, VERR_INVALID_POINTER);
    AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);

    int rc = drvAudioStreamCfgToProps(pCfg, &pGstStrmIn->Props);
    if (RT_SUCCESS(rc))
    {
        char *pszTemp;
        if (RTStrAPrintf(&pszTemp, "%s (Guest)", pszName) <= 0)
            return VERR_NO_MEMORY;

        rc = AudioMixBufInit(&pGstStrmIn->MixBuf, pszTemp, &pGstStrmIn->Props, AudioMixBufSize(&pHstStrmIn->MixBuf));
        if (RT_SUCCESS(rc))
            rc = AudioMixBufLinkTo(&pHstStrmIn->MixBuf, &pGstStrmIn->MixBuf);

        RTStrFree(pszTemp);

        if (RT_SUCCESS(rc))
        {
#ifdef DEBUG
            drvAudioStreamCfgPrint(pCfg);
#endif
            pGstStrmIn->State.fActive = false;
            pGstStrmIn->State.fEmpty  = true;

            pGstStrmIn->State.pszName = RTStrDup(pszName);
            if (!pGstStrmIn->State.pszName)
                return VERR_NO_MEMORY;

            pGstStrmIn->pHstStrmIn = pHstStrmIn;
        }
    }

    LogFlowFunc(("pszName=%s, rc=%Rrc\n", pszName, rc));
    return rc;
}

static int drvAudioAllocHstIn(PDRVAUDIO pThis, const char *pszName, PPDMAUDIOSTREAMCFG pCfg,
                              PDMAUDIORECSOURCE enmRecSource, PPDMAUDIOHSTSTRMIN *ppHstStrmIn)
{
    if (!pThis->cFreeInputStreams)
    {
        LogFlowFunc(("No more input streams free to use, bailing out\n"));
        return VERR_NO_MORE_HANDLES;
    }

    /* Validate backend configuration. */
    if (!pThis->BackendCfg.cbStreamIn)
    {
        LogFlowFunc(("Backend input configuration not valid, bailing out\n"));
        return VERR_INVALID_PARAMETER;
    }

    PPDMAUDIOHSTSTRMIN pHstStrmIn =
        (PPDMAUDIOHSTSTRMIN)RTMemAllocZ(pThis->BackendCfg.cbStreamIn);
    if (!pHstStrmIn)
    {
        LogFlowFunc(("Error allocating host innput stream with %RU32 bytes\n",
                     pThis->BackendCfg.cbStreamOut));
        return VERR_NO_MEMORY;
    }

    int rc;
    bool fInitialized = false;

    do
    {
        uint32_t cSamples;
        rc = pThis->pHostDrvAudio->pfnInitIn(pThis->pHostDrvAudio, pHstStrmIn,
                                             pCfg, enmRecSource, &cSamples);
        if (RT_FAILURE(rc))
        {
            LogFlowFunc(("Initializing host backend failed with rc=%Rrc\n", rc));
            break;
        }

        fInitialized = true;

        char *pszTemp;
        if (RTStrAPrintf(&pszTemp, "%s (Host)", pszName) <= 0)
        {
            rc = VERR_NO_MEMORY;
            break;
        }

        rc = AudioMixBufInit(&pHstStrmIn->MixBuf, pszTemp, &pHstStrmIn->Props, cSamples);
        if (RT_SUCCESS(rc))
        {
            RTListPrepend(&pThis->lstHstStrmIn, &pHstStrmIn->Node);
            pThis->cFreeInputStreams--;
        }

        RTStrFree(pszTemp);

    } while (0);

    if (RT_FAILURE(rc))
    {
        if (fInitialized)
        {
            int rc2 = pThis->pHostDrvAudio->pfnFiniIn(pThis->pHostDrvAudio,
                                                      pHstStrmIn);
            AssertRC(rc2);
        }

        drvAudioHstInFreeRes(pHstStrmIn);
        RTMemFree(pHstStrmIn);
    }
    else
        *ppHstStrmIn = pHstStrmIn;

    LogFlowFuncLeaveRC(rc);
    return rc;
}

/**
 * Writes VM audio output data from the guest stream into the host stream.
 * The attached host driver backend then will play out the audio in a
 * later step then.
 *
 * @return  IPRT status code.
 * @return  int
 * @param   pThis
 * @param   pGstStrmOut
 * @param   pvBuf
 * @param   cbBuf
 * @param   pcbWritten
 */
int drvAudioWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut,
                  const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
{
    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    AssertPtrReturn(pGstStrmOut, VERR_INVALID_POINTER);
    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
    /* pcbWritten is optional. */

    if (!pThis->pHostDrvAudio->pfnIsEnabled(pThis->pHostDrvAudio, PDMAUDIODIR_OUT))
        return VERR_NOT_AVAILABLE;

    PPDMAUDIOHSTSTRMOUT pHstStrmOut = pGstStrmOut->pHstStrmOut;
    AssertPtrReturn(pHstStrmOut, VERR_INVALID_POINTER);

    AssertMsg(pGstStrmOut->pHstStrmOut->fEnabled,
              ("Writing to disabled host output stream \"%s\" not possible\n",
              pHstStrmOut->MixBuf.pszName));

    /*
     * First, write data from the device emulation into our
     * guest mixing buffer.
     */
    uint32_t cWritten;
    int rc = AudioMixBufWriteAt(&pGstStrmOut->MixBuf, 0 /* Offset in samples */, pvBuf, cbBuf, &cWritten);

    /*
     * Second, mix the guest mixing buffer with the host mixing
     * buffer so that the host backend can play the data lateron.
     */
    uint32_t cMixed;
    if (   RT_SUCCESS(rc)
        && cWritten)
    {
        rc = AudioMixBufMixToParent(&pGstStrmOut->MixBuf, cWritten, &cMixed);
    }
    else
        cMixed = 0;

    if (RT_SUCCESS(rc))
    {
        /* Return the number of samples which actually have been mixed
         * down to the parent, regardless how much samples were written
         * into the children buffer. */
        if (pcbWritten)
            *pcbWritten = AUDIOMIXBUF_S2B(&pGstStrmOut->MixBuf, cMixed);
    }

    LogFlowFunc(("%s -> %s: Written pvBuf=%p, cbBuf=%zu, cWritten=%RU32 (%RU32 bytes), cMixed=%RU32, rc=%Rrc\n",
                 pGstStrmOut->MixBuf.pszName, pHstStrmOut->MixBuf.pszName, pvBuf, cbBuf, cWritten,
                 AUDIOMIXBUF_S2B(&pGstStrmOut->MixBuf, cWritten), cMixed, rc));
    return rc;
}

PPDMAUDIOHSTSTRMOUT drvAudioFindAnyHstOut(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
{
    if (pHstStrmOut)
    {
        if (RTListNodeIsLast(&pThis->lstHstStrmOut, &pHstStrmOut->Node))
            return NULL;

        return RTListNodeGetNext(&pHstStrmOut->Node, PDMAUDIOHSTSTRMOUT, Node);
    }

    return RTListGetFirst(&pThis->lstHstStrmOut, PDMAUDIOHSTSTRMOUT, Node);
}

PPDMAUDIOHSTSTRMOUT drvAudioHstFindAnyEnabledOut(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMOUT pHostStrmOut)
{
    while ((pHostStrmOut = drvAudioFindAnyHstOut(pThis, pHostStrmOut)))
    {
        if (pHostStrmOut->fEnabled)
            return pHostStrmOut;
    }

    return NULL;
}

PPDMAUDIOHSTSTRMOUT drvAudioFindSpecificOut(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
                                            PPDMAUDIOSTREAMCFG pCfg)
{
    while ((pHstStrmOut = drvAudioFindAnyHstOut(pThis, pHstStrmOut)))
    {
        if (drvAudioPCMPropsAreEqual(&pHstStrmOut->Props, pCfg))
            return pHstStrmOut;
    }

    return NULL;
}

int drvAudioDestroyHstIn(PDRVAUDIO pThis, PPDMAUDIOHSTSTRMIN pHstStrmIn)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);
    AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);

    LogFlowFunc(("%s\n", pHstStrmIn->MixBuf.pszName));

    int rc;
    if (!pHstStrmIn->pGstStrmIn) /* No parent anymore? */
    {
        rc = pThis->pHostDrvAudio->pfnFiniIn(pThis->pHostDrvAudio, pHstStrmIn);
        if (RT_SUCCESS(rc))
        {
            drvAudioHstInFreeRes(pHstStrmIn);

            /* Remove from driver instance list. */
            RTListNodeRemove(&pHstStrmIn->Node);

            RTMemFree(pHstStrmIn);
            pThis->cFreeInputStreams++;
        }
    }
    else
    {
        rc = VERR_ACCESS_DENIED;
        LogFlowFunc(("[%s] Still is being used, rc=%Rrc\n", pHstStrmIn->MixBuf.pszName, rc));
    }

    return rc;
}

static int drvAudioDestroyGstIn(PDRVAUDIO pThis, PPDMAUDIOGSTSTRMIN pGstStrmIn)
{
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    LogFlowFunc(("%s\n", pGstStrmIn->MixBuf.pszName));

    if (pGstStrmIn)
    {
        drvAudioGstInFreeRes(pGstStrmIn);

        if (pGstStrmIn->pHstStrmIn)
        {
            /* Unlink child. */
            pGstStrmIn->pHstStrmIn->pGstStrmIn = NULL;

            /* Try destroying the associated host input stream. This could
             * be skipped if there are other guest input streams with this
             * host stream. */
            drvAudioDestroyHstIn(pThis, pGstStrmIn->pHstStrmIn);
        }

        RTMemFree(pGstStrmIn);
    }

    return VINF_SUCCESS;
}

static DECLCALLBACK(int) drvAudioQueryStatus(PPDMIAUDIOCONNECTOR pInterface,
                                             uint32_t *pcbAvailIn, uint32_t *pcbFreeOut,
                                             uint32_t *pcSamplesLive)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    /* pcbAvailIn is optional. */
    /* pcbFreeOut is optional. */
    /* pcSamplesLive is optional. */

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    if (!pThis->pHostDrvAudio->pfnIsEnabled(pThis->pHostDrvAudio, PDMAUDIODIR_OUT))
        return VERR_NOT_AVAILABLE;

    int rc = VINF_SUCCESS;
    uint32_t cSamplesLive = 0;

    /*
     * Playback.
     */
    uint32_t cbFreeOut = UINT32_MAX;

    PPDMAUDIOHSTSTRMOUT pHstStrmOut = NULL;
    while ((pHstStrmOut = drvAudioHstFindAnyEnabledOut(pThis, pHstStrmOut)))
    {
        uint32_t cStreamsLive;
        cSamplesLive = drvAudioHstOutSamplesLive(pHstStrmOut, &cStreamsLive);
        if (!cStreamsLive)
            cSamplesLive = 0;

        /* Has this stream marked as disabled but there still were guest streams relying
         * on it? Check if this stream now can be closed and do so, if possible. */
        if (   pHstStrmOut->fPendingDisable
            && !cStreamsLive)
        {
            /* Stop playing the current (pending) stream. */
            int rc2 = pThis->pHostDrvAudio->pfnControlOut(pThis->pHostDrvAudio, pHstStrmOut,
                                                          PDMAUDIOSTREAMCMD_DISABLE);
            if (RT_SUCCESS(rc2))
            {
                pHstStrmOut->fEnabled        = false;
                pHstStrmOut->fPendingDisable = false;

                LogFunc(("[%s] Disabling stream\n", pHstStrmOut->MixBuf.pszName));
            }
            else
                LogFunc(("[%s] Backend vetoed against closing output stream, rc=%Rrc\n", pHstStrmOut->MixBuf.pszName, rc2));

            continue;
        }

        LogFlowFunc(("[%s] cSamplesLive=%RU32\n", pHstStrmOut->MixBuf.pszName, cSamplesLive));

        /*
         * No live samples to play at the moment?
         *
         * Tell the device emulation for each connected guest stream how many
         * bytes are free so that the device emulation can continue writing data to
         * these streams.
         */
        PPDMAUDIOGSTSTRMOUT pGstStrmOut;
        uint32_t cbFree2 = UINT32_MAX;
        RTListForEach(&pHstStrmOut->lstGstStrmOut, pGstStrmOut, PDMAUDIOGSTSTRMOUT, Node)
        {
            if (pGstStrmOut->State.fActive)
            {
                /* Tell the sound device emulation how many samples are free
                 * so that it can start writing PCM data to us. */
                cbFree2 = RT_MIN(cbFree2, AUDIOMIXBUF_S2B_RATIO(&pGstStrmOut->MixBuf,
                                                                AudioMixBufFree(&pGstStrmOut->MixBuf)));

                LogFlowFunc(("\t[%s] cbFree=%RU32\n", pGstStrmOut->MixBuf.pszName, cbFree2));
            }
        }

        cbFreeOut = RT_MIN(cbFreeOut, cbFree2);
    }

    /*
     * Recording.
     */
    uint32_t cbAvailIn = 0;

    PPDMAUDIOHSTSTRMIN pHstStrmIn = NULL;
    while ((pHstStrmIn = drvAudioFindNextEnabledHstIn(pThis, pHstStrmIn)))
    {
        /* Call the host backend to capture the audio input data. */
        uint32_t cSamplesCaptured;
        int rc2 = pThis->pHostDrvAudio->pfnCaptureIn(pThis->pHostDrvAudio, pHstStrmIn,
                                                     &cSamplesCaptured);
        if (RT_FAILURE(rc2))
            continue;

        PPDMAUDIOGSTSTRMIN pGstStrmIn = pHstStrmIn->pGstStrmIn;
        AssertPtrBreak(pGstStrmIn);

        if (pGstStrmIn->State.fActive)
        {
            cbAvailIn = RT_MAX(cbAvailIn, AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf,
                                                          AudioMixBufMixed(&pHstStrmIn->MixBuf)));

            LogFlowFunc(("\t[%s] cbFree=%RU32\n", pHstStrmIn->MixBuf.pszName, cbAvailIn));
        }
    }

    if (RT_SUCCESS(rc))
    {
        if (cbFreeOut == UINT32_MAX)
            cbFreeOut = 0;

        if (pcbAvailIn)
            *pcbAvailIn = cbAvailIn;

        if (pcbFreeOut)
            *pcbFreeOut = cbFreeOut;

        if (pcSamplesLive)
            *pcSamplesLive = cSamplesLive;
    }

    return rc;
}

static DECLCALLBACK(int) drvAudioPlayOut(PPDMIAUDIOCONNECTOR pInterface, uint32_t *pcSamplesPlayed)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    /* pcSamplesPlayed is optional. */

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    int rc = VINF_SUCCESS;
    uint32_t cSamplesPlayedMax = 0;

    /*
     * Process all enabled host output streams.
     */
    PPDMAUDIOHSTSTRMOUT pHstStrmOut = NULL;
    while ((pHstStrmOut = drvAudioHstFindAnyEnabledOut(pThis, pHstStrmOut)))
    {
#if 0
        uint32_t cStreamsLive;
        uint32_t cSamplesLive = drvAudioHstOutSamplesLive(pHstStrmOut, &cStreamsLive);
        if (!cStreamsLive)
            cSamplesLive = 0;

        /* Has this stream marked as disabled but there still were guest streams relying
         * on it? Check if this stream now can be closed and do so, if possible. */
        if (   pHstStrmOut->fPendingDisable
            && !cStreamsLive)
        {
            /* Stop playing the current (pending) stream. */
            int rc2 = pThis->pHostDrvAudio->pfnControlOut(pThis->pHostDrvAudio, pHstStrmOut,
                                                          PDMAUDIOSTREAMCMD_DISABLE);
            if (RT_SUCCESS(rc2))
            {
                pHstStrmOut->fEnabled        = false;
                pHstStrmOut->fPendingDisable = false;

                LogFunc(("\t%p: Disabling stream\n", pHstStrmOut));
            }
            else
                LogFunc(("\t%p: Backend vetoed against closing output stream, rc=%Rrc\n",
                         pHstStrmOut, rc2));

            continue;
        }
#endif

        uint32_t cSamplesPlayed = 0;
        int rc2 = pThis->pHostDrvAudio->pfnPlayOut(pThis->pHostDrvAudio, pHstStrmOut,
                                                   &cSamplesPlayed);
        if (RT_SUCCESS(rc2))
            cSamplesPlayedMax = RT_MAX(cSamplesPlayed, cSamplesPlayedMax);

        LogFlowFunc(("\t[%s] cSamplesPlayed=%RU32, rc=%Rrc\n", pHstStrmOut->MixBuf.pszName, cSamplesPlayed, rc2));

        bool fNeedsCleanup = false;

        PPDMAUDIOGSTSTRMOUT pGstStrmOut;
        RTListForEach(&pHstStrmOut->lstGstStrmOut, pGstStrmOut, PDMAUDIOGSTSTRMOUT, Node)
        {
            if (   !pGstStrmOut->State.fActive
                && pGstStrmOut->State.fEmpty)
                continue;

            if (AudioMixBufIsEmpty(&pGstStrmOut->MixBuf))
            {
                pGstStrmOut->State.fEmpty = true;
                fNeedsCleanup |= !pGstStrmOut->State.fActive;
            }
        }

        if (fNeedsCleanup)
        {
            RTListForEach(&pHstStrmOut->lstGstStrmOut, pGstStrmOut, PDMAUDIOGSTSTRMOUT, Node)
            {
                if (!pGstStrmOut->State.fActive)
                    drvAudioDestroyGstOut(pThis, pGstStrmOut);
            }
        }
    }

    if (RT_SUCCESS(rc))
    {
        if (pcSamplesPlayed)
            *pcSamplesPlayed = cSamplesPlayedMax;
    }

    return rc;
}

static int drvAudioHostInit(PCFGMNODE pCfgHandle, PDRVAUDIO pThis)
{
    /* pCfgHandle is optional. */
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    NOREF(pCfgHandle);

    LogFlowFuncEnter();

    int rc = pThis->pHostDrvAudio->pfnInit(pThis->pHostDrvAudio);
    if (RT_FAILURE(rc))
    {
        LogFlowFunc(("Initialization of lower driver failed with rc=%Rrc\n", rc));
        return rc;
    }

    uint32_t cMaxHstStrmsOut = pThis->BackendCfg.cMaxHstStrmsOut;
    uint32_t cbHstStrmsOut = pThis->BackendCfg.cbStreamOut;

    if (cbHstStrmsOut)
    {
        pThis->cFreeOutputStreams = 1; /** @todo Make this configurable. */
        if (pThis->cFreeOutputStreams > cMaxHstStrmsOut)
        {
            LogRel(("Audio: Warning: %RU32 output streams were requested, host driver only supports %RU32\n",
                    pThis->cFreeOutputStreams, cMaxHstStrmsOut));
            pThis->cFreeOutputStreams = cMaxHstStrmsOut;
        }
    }
    else
        pThis->cFreeOutputStreams = 0;

    uint32_t cMaxHstStrmsIn = pThis->BackendCfg.cMaxHstStrmsIn;
    uint32_t cbHstStrmIn = pThis->BackendCfg.cbStreamIn;

    if (cbHstStrmIn)
    {
        /*
         * Note:
         *  - Our AC'97 emulation has two inputs, line (P.IN) and microphone (P.MIC).
         ** @todo Document HDA.
         */
        pThis->cFreeInputStreams = 2; /** @todo Make this configurable. */
        if (pThis->cFreeInputStreams > cMaxHstStrmsIn)
        {
            LogRel(("Audio: Warning: %RU32 input streams were requested, host driver only supports %RU32\n",
                    pThis->cFreeInputStreams, cMaxHstStrmsIn));
            pThis->cFreeInputStreams = cMaxHstStrmsIn;
        }
    }
    else
        pThis->cFreeInputStreams = 0;

    LogFlowFunc(("cMaxHstStrmsOut=%RU32 (cb=%RU32), cMaxHstStrmsIn=%RU32 (cb=%RU32)\n",
                 cMaxHstStrmsOut, cbHstStrmsOut, cMaxHstStrmsIn, cbHstStrmIn));

    LogFlowFunc(("cFreeInputStreams=%RU8, cFreeOutputStreams=%RU8\n",
                 pThis->cFreeInputStreams, pThis->cFreeOutputStreams));

    LogFlowFuncLeave();
    return VINF_SUCCESS;
}

static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)
{
    PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);

    LogFlowFunc(("enmCmd=%ld\n", enmCmd));

    if (!pThis->pHostDrvAudio)
        return;

    PPDMAUDIOHSTSTRMOUT pHstStrmOut = NULL;
    while ((pHstStrmOut = drvAudioHstFindAnyEnabledOut(pThis, pHstStrmOut)))
        pThis->pHostDrvAudio->pfnControlOut(pThis->pHostDrvAudio, pHstStrmOut, enmCmd);

    PPDMAUDIOHSTSTRMIN pHstStrmIn = NULL;
    while ((pHstStrmIn = drvAudioFindNextEnabledHstIn(pThis, pHstStrmIn)))
        pThis->pHostDrvAudio->pfnControlIn(pThis->pHostDrvAudio, pHstStrmIn, enmCmd);
}

static struct audio_option audio_options[] =
{
    /* DAC */
    {"DACFixedSettings", AUD_OPT_BOOL, &conf.fixed_out.enabled,
     "Use fixed settings for host DAC", NULL, 0},

    {"DACFixedFreq", AUD_OPT_INT, &conf.fixed_out.settings.uHz,
     "Frequency for fixed host DAC", NULL, 0},

    {"DACFixedFmt", AUD_OPT_FMT, &conf.fixed_out.settings.enmFormat,
     "Format for fixed host DAC", NULL, 0},

    {"DACFixedChannels", AUD_OPT_INT, &conf.fixed_out.settings.cChannels,
     "Number of channels for fixed DAC (1 - mono, 2 - stereo)", NULL, 0},

    {"DACVoices", AUD_OPT_INT, &conf.fixed_out.cStreams, /** @todo Rename! */
     "Number of streams for DAC", NULL, 0},

    /* ADC */
    {"ADCFixedSettings", AUD_OPT_BOOL, &conf.fixed_in.enabled,
     "Use fixed settings for host ADC", NULL, 0},

    {"ADCFixedFreq", AUD_OPT_INT, &conf.fixed_in.settings.uHz,
     "Frequency for fixed host ADC", NULL, 0},

    {"ADCFixedFmt", AUD_OPT_FMT, &conf.fixed_in.settings.enmFormat,
     "Format for fixed host ADC", NULL, 0},

    {"ADCFixedChannels", AUD_OPT_INT, &conf.fixed_in.settings.cChannels,
     "Number of channels for fixed ADC (1 - mono, 2 - stereo)", NULL, 0},

    {"ADCVoices", AUD_OPT_INT, &conf.fixed_in.cStreams, /** @todo Rename! */
     "Number of streams for ADC", NULL, 0},

    /* Misc */
    {"TimerFreq", AUD_OPT_INT, &conf.period.hz,
     "Timer frequency in Hz (0 - use lowest possible)", NULL, 0},

    {"PLIVE", AUD_OPT_BOOL, &conf.plive,
     "(undocumented)", NULL, 0}, /** @todo What is this? */

    NULL
};

static DECLCALLBACK(int) drvAudioInit(PCFGMNODE pCfgHandle, PPDMDRVINS pDrvIns)
{
    AssertPtrReturn(pCfgHandle, VERR_INVALID_POINTER);
    AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);

    PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
    LogFlowFunc(("pDrvAudio=%p, pDrvIns=%p\n", pThis, pDrvIns));

    RTListInit(&pThis->lstHstStrmIn);
    RTListInit(&pThis->lstHstStrmOut);

    int rc = VINF_SUCCESS;

    /* Get the configuration data from the selected backend (if available). */
    AssertPtr(pThis->pHostDrvAudio);
    if (RT_LIKELY(pThis->pHostDrvAudio->pfnGetConf))
        rc = pThis->pHostDrvAudio->pfnGetConf(pThis->pHostDrvAudio, &pThis->BackendCfg);

    if (RT_SUCCESS(rc))
    {
        rc = drvAudioProcessOptions(pCfgHandle, "AUDIO", audio_options);
        /** @todo Check for invalid options? */

        pThis->cFreeOutputStreams = conf.fixed_out.cStreams;
        pThis->cFreeInputStreams  = conf.fixed_in.cStreams;

        if (!pThis->cFreeOutputStreams)
            pThis->cFreeOutputStreams = 1;

        if (!pThis->cFreeInputStreams)
            pThis->cFreeInputStreams = 1;
    }

    /*
     * If everything went well, initialize the lower driver.
     */
    if (RT_SUCCESS(rc))
        rc = drvAudioHostInit(pCfgHandle, pThis);

    LogFlowFuncLeaveRC(rc);
    return rc;
}

static DECLCALLBACK(int) drvAudioInitNull(PPDMIAUDIOCONNECTOR pInterface)
{
    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
    NOREF(pThis);

    LogRel(("Audio: Using NULL driver; no sound will be audible\n"));

    /* Nothing to do here yet. */
    return VINF_SUCCESS;
}

static DECLCALLBACK(int) drvAudioRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn,
                                      void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
{
    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
    AssertPtrReturn(pThis, VERR_INVALID_POINTER);

    AssertPtrReturn(pGstStrmIn, VERR_INVALID_POINTER);
    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
    /* pcbWritten is optional. */

    if (!pThis->pHostDrvAudio->pfnIsEnabled(pThis->pHostDrvAudio, PDMAUDIODIR_IN))
    {
        if (pcbRead)
            *pcbRead = 0;
        return VINF_SUCCESS;
    }

    PPDMAUDIOHSTSTRMIN pHstStrmIn = pGstStrmIn->pHstStrmIn;
    AssertPtrReturn(pHstStrmIn, VERR_INVALID_POINTER);

    AssertMsg(pGstStrmIn->pHstStrmIn->fEnabled,
              ("Reading from disabled host input stream \"%s\" not possible\n", pGstStrmIn->MixBuf.pszName));

    /*
     * Read from the parent buffer (that is, the guest buffer) which
     * should have the audio data in the format the guest needs.
     */
    uint32_t cRead;
    int rc = AudioMixBufReadCirc(&pGstStrmIn->MixBuf,
                                 pvBuf, cbBuf, &cRead);
    if (RT_SUCCESS(rc))
    {
        AudioMixBufFinish(&pGstStrmIn->MixBuf, cRead);

        if (pcbRead)
            *pcbRead = AUDIOMIXBUF_S2B(&pGstStrmIn->MixBuf, cRead);
    }

    LogFlowFunc(("cRead=%RU32 (%RU32 bytes), rc=%Rrc\n",
                 cRead, AUDIOMIXBUF_S2B(&pGstStrmIn->MixBuf, cRead), rc));
    return rc;
}

static DECLCALLBACK(int) drvAudioEnableOut(PPDMIAUDIOCONNECTOR pInterface,
                                           PPDMAUDIOGSTSTRMOUT pGstStrmOut, bool fEnable)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    /* pGstStrmOut is optional. */

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    if (pGstStrmOut)
    {
        PPDMAUDIOHSTSTRMOUT pHstStrmOut = pGstStrmOut->pHstStrmOut;
        AssertPtr(pHstStrmOut);

        LogFlowFunc(("%s: fEnable=%RTbool\n", pGstStrmOut->MixBuf.pszName, fEnable));

        if (pGstStrmOut->State.fActive != fEnable)
        {
            if (fEnable)
            {
                pHstStrmOut->fPendingDisable = false;
                if (!pHstStrmOut->fEnabled)
                {
                    pHstStrmOut->fEnabled = true;
                    pThis->pHostDrvAudio->pfnControlOut(pThis->pHostDrvAudio, pHstStrmOut,
                                                        PDMAUDIOSTREAMCMD_ENABLE);
                    /** @todo Check rc. */
                }
            }
            else
            {
                if (pHstStrmOut->fEnabled)
                {
                    uint32_t cGstStrmsActive = 0;

                    PPDMAUDIOGSTSTRMOUT pIter;
                    RTListForEach(&pHstStrmOut->lstGstStrmOut, pIter, PDMAUDIOGSTSTRMOUT, Node)
                    {
                        if (pIter->State.fActive)
                            cGstStrmsActive++;
                    }

                    pHstStrmOut->fPendingDisable = cGstStrmsActive == 1;
                }
            }

            pGstStrmOut->State.fActive = fEnable;
        }
    }

    return VINF_SUCCESS;
}

static DECLCALLBACK(int) drvAudioEnableIn(PPDMIAUDIOCONNECTOR pInterface,
                                          PPDMAUDIOGSTSTRMIN pGstStrmIn, bool fEnable)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    /* pGstStrmIn is optional. */

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    if (pGstStrmIn)
    {
        PPDMAUDIOHSTSTRMIN pHstStrmIn = pGstStrmIn->pHstStrmIn;
        AssertPtr(pHstStrmIn);

        LogFlowFunc(("%s: fEnable=%RTbool\n", pGstStrmIn->MixBuf.pszName, fEnable));

        if (pGstStrmIn->State.fActive != fEnable)
        {
            if (fEnable)
            {
                if (!pHstStrmIn->fEnabled)
                {
                    int rc2 = pThis->pHostDrvAudio->pfnControlIn(pThis->pHostDrvAudio, pHstStrmIn,
                                                                 PDMAUDIOSTREAMCMD_ENABLE);
                    if (RT_LIKELY(RT_SUCCESS(rc2)))
                        pHstStrmIn->fEnabled = true;
                    else
                        LogFlowFunc(("Error opening host input stream in backend, rc=%Rrc\n", rc2));
                }
            }
            else
            {
                if (pHstStrmIn->fEnabled)
                {
                    int rc2 = pThis->pHostDrvAudio->pfnControlIn(pThis->pHostDrvAudio, pHstStrmIn,
                                                                 PDMAUDIOSTREAMCMD_DISABLE);
                    if (RT_LIKELY(RT_SUCCESS(rc2))) /* Did the backend veto? */
                        pHstStrmIn->fEnabled = false;
                    else
                        LogFlowFunc(("Backend vetoed closing input stream, rc=%Rrc\n", rc2));
                }
            }

            pGstStrmIn->State.fActive = fEnable;
        }
    }

    return VINF_SUCCESS;
}

static DECLCALLBACK(bool) drvAudioIsInputOK(PPDMIAUDIOCONNECTOR pInterface,
                                            PPDMAUDIOGSTSTRMIN  pGstStrmIn)
{
    return (pGstStrmIn != NULL);
}

static DECLCALLBACK(bool) drvAudioIsOutputOK(PPDMIAUDIOCONNECTOR pInterface,
                                             PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
    return (pGstStrmOut != NULL);
}

static DECLCALLBACK(int) drvAudioOpenIn(PPDMIAUDIOCONNECTOR pInterface, const char *pszName,
                                        PDMAUDIORECSOURCE enmRecSource, PPDMAUDIOSTREAMCFG pCfg,
                                        PPDMAUDIOGSTSTRMIN *ppGstStrmIn)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    AssertPtrReturn(ppGstStrmIn, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
    AssertPtrReturn(ppGstStrmIn, VERR_INVALID_POINTER);

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    LogFlowFunc(("pszName=%s, pCfg=%p\n", pszName, pCfg));

    if (!drvAudioStreamCfgIsValid(pCfg))
    {
        LogFunc(("Input stream configuration is not valid, bailing out\n"));
        return VERR_INVALID_PARAMETER;
    }

    PPDMAUDIOGSTSTRMIN pGstStrmIn = *ppGstStrmIn;
    if (   pGstStrmIn
        && drvAudioPCMPropsAreEqual(&pGstStrmIn->Props, pCfg))
    {
        LogFunc(("[%s] Exists and matches required configuration, skipping creation\n",
                 pGstStrmIn->MixBuf.pszName));
        return VWRN_ALREADY_EXISTS;
    }

    if (   !conf.fixed_in.enabled
        && pGstStrmIn)
    {
        drvAudioDestroyGstIn(pThis, pGstStrmIn);
        pGstStrmIn = NULL;
    }

    int rc;

    if (pGstStrmIn)
    {
        PPDMAUDIOHSTSTRMIN pHstStrmIn = pGstStrmIn->pHstStrmIn;
        AssertPtr(pHstStrmIn);

        drvAudioGstInFreeRes(pGstStrmIn);

        char *pszTemp;
        if (RTStrAPrintf(&pszTemp, "%s (Guest)", pszName) <= 0)
        {
            RTMemFree(pGstStrmIn);
            return VERR_NO_MEMORY;
        }

        rc = drvAudioGstInInit(pGstStrmIn, pHstStrmIn, pszName, pCfg);

        RTStrFree(pszTemp);
    }
    else
        rc = drvAudioCreateStreamPairIn(pThis, pszName, enmRecSource, pCfg, &pGstStrmIn);

    if (pGstStrmIn)
        *ppGstStrmIn = pGstStrmIn;

    LogFlowFuncLeaveRC(rc);
    return rc;
}

DECLCALLBACK(int) drvAudioOpenOut(PPDMIAUDIOCONNECTOR pInterface, const char *pszName,
                                  PPDMAUDIOSTREAMCFG pCfg, PPDMAUDIOGSTSTRMOUT *ppGstStrmOut)
{
    AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
    AssertPtrReturn(ppGstStrmOut, VERR_INVALID_POINTER);

    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);

    LogFlowFunc(("pszName=%s, pCfg=%p\n", pszName, pCfg));

    if (!drvAudioStreamCfgIsValid(pCfg))
    {
        LogFunc(("Output stream configuration is not valid, bailing out\n"));
        return VERR_INVALID_PARAMETER;
    }

    PPDMAUDIOGSTSTRMOUT pGstStrmOut = *ppGstStrmOut;
    if (   pGstStrmOut
        && drvAudioPCMPropsAreEqual(&pGstStrmOut->Props, pCfg))
    {
        LogFunc(("[%s] Exists and matches required configuration, skipping creation\n",
                 pGstStrmOut->MixBuf.pszName));
        return VWRN_ALREADY_EXISTS;
    }

#if 0
    /* Any live samples that need to be updated after
     * we set the new parameters? */
    PPDMAUDIOGSTSTRMOUT pOldGstStrmOut = NULL;
    uint32_t cLiveSamples = 0;

    if (   conf.plive
        && pGstStrmOut
        && (   !pGstStrmOut->State.fActive
            && !pGstStrmOut->State.fEmpty))
    {
        cLiveSamples = pGstStrmOut->cTotalSamplesWritten;
        if (cLiveSamples)
        {
            pOldGstStrmOut = pGstStrmOut;
            pGstStrmOut = NULL;
        }
    }
#endif

    if (   pGstStrmOut
        && !conf.fixed_out.enabled)
    {
        drvAudioDestroyGstOut(pThis, pGstStrmOut);
        pGstStrmOut = NULL;
    }

    int rc;
    if (pGstStrmOut)
    {
        PPDMAUDIOHSTSTRMOUT pHstStrmOut = pGstStrmOut->pHstStrmOut;
        AssertPtr(pHstStrmOut);

        drvAudioGstOutFreeRes(pGstStrmOut);

        rc = drvAudioGstOutInit(pGstStrmOut, pHstStrmOut, pszName, pCfg);
    }
    else
    {
        rc = drvAudioCreateStreamPairOut(pThis, pszName, pCfg, &pGstStrmOut);
        if (RT_FAILURE(rc))
            LogFunc(("Failed to create output stream \"%s\", rc=%Rrc\n", pszName, rc));
    }

    if (RT_SUCCESS(rc))
    {
        AssertPtr(pGstStrmOut);
        *ppGstStrmOut = pGstStrmOut;
#if 0
        /* Update remaining live samples with new rate. */
        if (cLiveSamples)
        {
            AssertPtr(pOldGstStrmOut);

            uint32_t cSamplesMixed =
                (cLiveSamples << pOldGstStrmOut->Props.cShift)
                * pOldGstStrmOut->Props.cbPerSec
                / (*ppGstStrmOut)->Props.cbPerSec;

            pGstStrmOut->cTotalSamplesWritten += cSamplesMixed;
        }
#endif
    }

    LogFlowFuncLeaveRC(rc);
    return rc;
}

static DECLCALLBACK(bool) drvAudioIsActiveIn(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn)
{
    return pGstStrmIn ? pGstStrmIn->State.fActive : false;
}

static DECLCALLBACK(bool) drvAudioIsActiveOut(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
    return pGstStrmOut ? pGstStrmOut->State.fActive : false;
}

static DECLCALLBACK(void) drvAudioCloseIn(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMIN pGstStrmIn)
{
    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
    if (pGstStrmIn)
        drvAudioDestroyGstIn(pThis, pGstStrmIn);
}

DECLCALLBACK(void) drvAudioCloseOut(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOGSTSTRMOUT pGstStrmOut)
{
    PDRVAUDIO pThis = PDMIAUDIOCONNECTOR_2_DRVAUDIO(pInterface);
    if (pGstStrmOut)
        drvAudioDestroyGstOut(pThis, pGstStrmOut);
}

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

/**
 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
 */
static DECLCALLBACK(void *) drvAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
{
    LogFlowFunc(("pInterface=%p, pszIID=%s\n", pInterface, pszIID));

    PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
    PDRVAUDIO  pThis   = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);

    PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
    PDMIBASE_RETURN_INTERFACE(pszIID, PDMIAUDIOCONNECTOR, &pThis->IAudioConnector);

    return NULL;
}

/**
 * Power Off notification.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvAudioPowerOff(PPDMDRVINS pDrvIns)
{
    drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_DISABLE);
}

/**
 * Destructs an audio driver instance.
 *
 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
 * resources can be freed correctly.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvAudioDestruct(PPDMDRVINS pDrvIns)
{
    LogFlowFuncEnter();
    PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);

    PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);

    /* Tear down all host output streams. */
    PPDMAUDIOHSTSTRMOUT pHstStrmOut = NULL;
    while ((pHstStrmOut = drvAudioFindAnyHstOut(pThis, pHstStrmOut)))
    {
        pThis->pHostDrvAudio->pfnControlOut(pThis->pHostDrvAudio, pHstStrmOut, PDMAUDIOSTREAMCMD_DISABLE);
        pThis->pHostDrvAudio->pfnFiniOut(pThis->pHostDrvAudio, pHstStrmOut);
    }

    /* Tear down all host input streams. */
    PPDMAUDIOHSTSTRMIN pHstStrmIn = NULL;
    while ((pHstStrmIn = drvAudioFindNextHstIn(pThis, pHstStrmIn)))
    {
        pThis->pHostDrvAudio->pfnControlIn(pThis->pHostDrvAudio, pHstStrmIn, PDMAUDIOSTREAMCMD_DISABLE);
        pThis->pHostDrvAudio->pfnFiniIn(pThis->pHostDrvAudio, pHstStrmIn);
    }

    if (pThis->pHostDrvAudio->pfnShutdown)
        pThis->pHostDrvAudio->pfnShutdown(pThis->pHostDrvAudio);

    LogFlowFuncLeave();
}

/**
 * Constructs an audio driver instance.
 *
 * @copydoc FNPDMDRVCONSTRUCT
 */
static DECLCALLBACK(int) drvAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle, uint32_t fFlags)
{
    LogFlowFunc(("pDrvIns=%#p, pCfgHandle=%#p, fFlags=%x\n", pDrvIns, pCfgHandle, fFlags));

    PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
    PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);

    /*
     * Init the static parts.
     */
    pThis->pDrvIns                                   = pDrvIns;
    /* IBase. */
    pDrvIns->IBase.pfnQueryInterface                 = drvAudioQueryInterface;
    /* IAudio. */
    pThis->IAudioConnector.pfnQueryStatus            = drvAudioQueryStatus;
    pThis->IAudioConnector.pfnRead                   = drvAudioRead;
    pThis->IAudioConnector.pfnWrite                  = drvAudioWrite;
    pThis->IAudioConnector.pfnIsInputOK              = drvAudioIsInputOK;
    pThis->IAudioConnector.pfnIsOutputOK             = drvAudioIsOutputOK;
    pThis->IAudioConnector.pfnInitNull               = drvAudioInitNull;
    pThis->IAudioConnector.pfnEnableOut              = drvAudioEnableOut;
    pThis->IAudioConnector.pfnEnableIn               = drvAudioEnableIn;
    pThis->IAudioConnector.pfnCloseIn                = drvAudioCloseIn;
    pThis->IAudioConnector.pfnCloseOut               = drvAudioCloseOut;
    pThis->IAudioConnector.pfnOpenIn                 = drvAudioOpenIn;
    pThis->IAudioConnector.pfnOpenOut                = drvAudioOpenOut;
    pThis->IAudioConnector.pfnPlayOut                = drvAudioPlayOut;
    pThis->IAudioConnector.pfnIsActiveIn             = drvAudioIsActiveIn;
    pThis->IAudioConnector.pfnIsActiveOut            = drvAudioIsActiveOut;

    /*
     * Attach driver below and query its connector interface.
     */
    PPDMIBASE pDownBase;
    int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pDownBase);
    if (RT_FAILURE(rc))
    {
        LogRel(("Audio: Failed to attach to driver %p below (flags=0x%x), rc=%Rrc\n",
                pDrvIns, fFlags, rc));
        return rc;
    }

    pThis->pHostDrvAudio = PDMIBASE_QUERY_INTERFACE(pDownBase, PDMIHOSTAUDIO);
    if (!pThis->pHostDrvAudio)
    {
        LogRel(("Audio: Failed to query interface for underlying host driver\n"));
        return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
                                N_("Host audio backend missing or invalid"));
    }

#ifdef DEBUG_andy
    CFGMR3Dump(pCfgHandle);
#endif

    rc = drvAudioInit(pCfgHandle, pDrvIns);
    if (RT_SUCCESS(rc))
    {
        pThis->fTerminate = false;
        pThis->pDrvIns    = pDrvIns;
    }

    LogFlowFuncLeaveRC(rc);
    return rc;
}

/**
 * Suspend notification.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvAudioSuspend(PPDMDRVINS pDrvIns)
{
    drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_DISABLE);
}

/**
 * Resume notification.
 *
 * @param   pDrvIns     The driver instance data.
 */
static DECLCALLBACK(void) drvAudioResume(PPDMDRVINS pDrvIns)
{
    drvAudioStateHandler(pDrvIns, PDMAUDIOSTREAMCMD_ENABLE);
}

/**
 * Audio driver registration record.
 */
const PDMDRVREG g_DrvAUDIO =
{
    /* u32Version */
    PDM_DRVREG_VERSION,
    /* szName */
    "AUDIO",
    /* szRCMod */
    "",
    /* szR0Mod */
    "",
    /* pszDescription */
    "Audio connector driver",
    /* fFlags */
    PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
    /* fClass. */
    PDM_DRVREG_CLASS_AUDIO,
    /* cMaxInstances */
    2,
    /* cbInstance */
    sizeof(DRVAUDIO),
    /* pfnConstruct */
    drvAudioConstruct,
    /* pfnDestruct */
    drvAudioDestruct,
    /* pfnRelocate */
    NULL,
    /* pfnIOCtl */
    NULL,
    /* pfnPowerOn */
    NULL,
    /* pfnReset */
    NULL,
    /* pfnSuspend */
    drvAudioSuspend,
    /* pfnResume */
    drvAudioResume,
    /* pfnAttach */
    NULL,
    /* pfnDetach */
    NULL,
    /* pfnPowerOff */
    drvAudioPowerOff,
    /* pfnSoftReset */
    NULL,
    /* u32EndVersion */
    PDM_DRVREG_VERSION
};
