diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp')
-rw-r--r-- | src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp | 2925 |
1 files changed, 2925 insertions, 0 deletions
diff --git a/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp b/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp new file mode 100644 index 00000000..5a366cc1 --- /dev/null +++ b/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp @@ -0,0 +1,2925 @@ +/* $Id: DrvHostAudioCoreAudio.cpp $ */ +/** @file + * Host audio driver - Mac OS X CoreAudio. + * + * For relevant Apple documentation, here are some starters: + * - Core Audio Essentials + * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html + * - TN2097: Playing a sound file using the Default Output Audio Unit + * https://developer.apple.com/library/archive/technotes/tn2097/ + * - TN2091: Device input using the HAL Output Audio Unit + * https://developer.apple.com/library/archive/technotes/tn2091/ + * - Audio Component Services + * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc + * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice + * https://developer.apple.com/library/archive/qa/qa1533/ + * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer + * https://developer.apple.com/library/archive/qa/qa1317/ + */ + +/* + * Copyright (C) 2010-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO +#include <VBox/log.h> +#include <VBox/vmm/pdmaudioinline.h> +#include <VBox/vmm/pdmaudiohostenuminline.h> + +#include "VBoxDD.h" + +#include <iprt/asm.h> +#include <iprt/cdefs.h> +#include <iprt/circbuf.h> +#include <iprt/mem.h> +#include <iprt/uuid.h> +#include <iprt/timer.h> + +#include <CoreAudio/CoreAudio.h> +#include <CoreServices/CoreServices.h> +#include <AudioToolbox/AudioQueue.h> +#include <AudioUnit/AudioUnit.h> + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 /* possibly 1080 */ +# define kAudioHardwarePropertyTranslateUIDToDevice (AudioObjectPropertySelector)'uidd' +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The max number of queue buffers we'll use. */ +#define COREAUDIO_MAX_BUFFERS 1024 +/** The minimum number of queue buffers. */ +#define COREAUDIO_MIN_BUFFERS 4 + +/** Enables the worker thread. + * This saves CoreAudio from creating an additional thread upon queue + * creation. (It does not help with the slow AudioQueueDispose fun.) */ +#define CORE_AUDIO_WITH_WORKER_THREAD +#if 0 +/** Enables the AudioQueueDispose breakpoint timer (debugging help). */ +# define CORE_AUDIO_WITH_BREAKPOINT_TIMER +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to the instance data for a Core Audio driver instance. */ +typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO; +/** Pointer to the Core Audio specific backend data for an audio stream. */ +typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM; + +/** + * Core Audio device entry (enumeration). + * + * @note This is definitely not safe to just copy! + */ +typedef struct COREAUDIODEVICEDATA +{ + /** The core PDM structure. */ + PDMAUDIOHOSTDEV Core; + + /** The audio device ID of the currently used device (UInt32 typedef). */ + AudioDeviceID idDevice; +} COREAUDIODEVICEDATA; +/** Pointer to a Core Audio device entry (enumeration). */ +typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA; + + +/** + * Audio device information. + * + * We do not use COREAUDIODEVICEDATA here as it contains lots more than what we + * need and care to query. We also don't want to depend on DrvAudio making + * PDMIHOSTAUDIO::pfnGetDevices callbacks to keep this information up to date. + */ +typedef struct DRVHSTAUDCADEVICE +{ + /** The audio device ID. kAudioDeviceUnknown if not available. */ + AudioObjectID idDevice; + /** Indicates whether we've registered device change listener. */ + bool fRegisteredListeners; + /** The UID string (must release). NULL if not available. */ + CFStringRef hStrUid; + /** The UID string for a specific device, NULL if we're using the default device. */ + char *pszSpecific; +} DRVHSTAUDCADEVICE; +/** Pointer to info about a default device. */ +typedef DRVHSTAUDCADEVICE *PDRVHSTAUDCADEVICE; + + +/** + * Core Audio stream state. + */ +typedef enum COREAUDIOINITSTATE +{ + /** The device is uninitialized. */ + COREAUDIOINITSTATE_UNINIT = 0, + /** The device is currently initializing. */ + COREAUDIOINITSTATE_IN_INIT, + /** The device is initialized. */ + COREAUDIOINITSTATE_INIT, + /** The device is currently uninitializing. */ + COREAUDIOINITSTATE_IN_UNINIT, + /** The usual 32-bit hack. */ + COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff +} COREAUDIOINITSTATE; + + +/** + * Core audio buffer tracker. + * + * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to + * track how much we've written. When a buffer is full, or if we run low on + * queued bufferes, it will be queued. + * + * For input buffer we'll be using offRead to track how much we've read. + * + * The queued/not-queued state is stored in the first bit of + * AudioQueueBuffer::mUserData. While bits 8 and up holds the index into + * COREAUDIOSTREAM::paBuffers. + */ +typedef struct COREAUDIOBUF +{ + /** The buffer. */ + AudioQueueBufferRef pBuf; + /** The buffer read offset (input only). */ + uint32_t offRead; +} COREAUDIOBUF; +/** Pointer to a core audio buffer tracker. */ +typedef COREAUDIOBUF *PCOREAUDIOBUF; + + +/** + * Core Audio specific data for an audio stream. + */ +typedef struct COREAUDIOSTREAM +{ + /** Common part. */ + PDMAUDIOBACKENDSTREAM Core; + + /** The stream's acquired configuration. */ + PDMAUDIOSTREAMCFG Cfg; + /** List node for the device's stream list. */ + RTLISTNODE Node; + /** The acquired (final) audio format for this stream. + * @note This what the device requests, we don't alter anything. */ + AudioStreamBasicDescription BasicStreamDesc; + /** The actual audio queue being used. */ + AudioQueueRef hAudioQueue; + + /** Number of buffers. */ + uint32_t cBuffers; + /** The array of buffer. */ + PCOREAUDIOBUF paBuffers; + + /** Initialization status tracker, actually COREAUDIOINITSTATE. + * Used when some of the device parameters or the device itself is changed + * during the runtime. */ + volatile uint32_t enmInitState; + /** The current buffer being written to / read from. */ + uint32_t idxBuffer; + /** Set if the stream is enabled. */ + bool fEnabled; + /** Set if the stream is started (playing/capturing). */ + bool fStarted; + /** Set if the stream is draining (output only). */ + bool fDraining; + /** Set if we should restart the stream on resume (saved pause state). */ + bool fRestartOnResume; +// /** Set if we're switching to a new output/input device. */ +// bool fSwitchingDevice; + /** Internal stream offset (bytes). */ + uint64_t offInternal; + /** The RTTimeMilliTS() at the end of the last transfer. */ + uint64_t msLastTransfer; + + /** Critical section for serializing access between thread + callbacks. */ + RTCRITSECT CritSect; + /** Buffer that drvHstAudCaStreamStatusString uses. */ + char szStatus[64]; +} COREAUDIOSTREAM; + + +/** + * Instance data for a Core Audio host audio driver. + * + * @implements PDMIAUDIOCONNECTOR + */ +typedef struct DRVHOSTCOREAUDIO +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to host audio interface. */ + PDMIHOSTAUDIO IHostAudio; + /** The input device. */ + DRVHSTAUDCADEVICE InputDevice; + /** The output device. */ + DRVHSTAUDCADEVICE OutputDevice; + /** Upwards notification interface. */ + PPDMIHOSTAUDIOPORT pIHostAudioPort; + /** Indicates whether we've registered default input device change listener. */ + bool fRegisteredDefaultInputListener; + /** Indicates whether we've registered default output device change listener. */ + bool fRegisteredDefaultOutputListener; + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + /** @name Worker Thread For Queue callbacks and stuff. + * @{ */ + /** The worker thread. */ + RTTHREAD hThread; + /** The runloop of the worker thread. */ + CFRunLoopRef hThreadRunLoop; + /** The message port we use to talk to the thread. + * @note While we don't currently use the port, it is necessary to prevent + * the thread from spinning or stopping prematurely because of + * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */ + CFMachPortRef hThreadPort; + /** Runloop source for hThreadPort. */ + CFRunLoopSourceRef hThreadPortSrc; + /** @} */ +#endif + + /** Critical section to serialize access. */ + RTCRITSECT CritSect; +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + /** Timder for debugging AudioQueueDispose slowness. */ + RTTIMERLR hBreakpointTimer; +#endif +} DRVHOSTCOREAUDIO; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify); + +/* DrvHostAudioCoreAudioAuth.mm: */ +DECLHIDDEN(int) coreAudioInputPermissionCheck(void); + + +#ifdef LOG_ENABLED +/** + * Gets the stream status. + * + * @returns Pointer to stream status string. + * @param pStreamCA The stream to get the status for. + */ +static const char *drvHstAudCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA) +{ + static RTSTRTUPLE const s_aInitState[5] = + { + { RT_STR_TUPLE("UNINIT") }, + { RT_STR_TUPLE("IN_INIT") }, + { RT_STR_TUPLE("INIT") }, + { RT_STR_TUPLE("IN_UNINIT") }, + { RT_STR_TUPLE("BAD") }, + }; + uint32_t enmInitState = pStreamCA->enmInitState; + PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)]; + memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch); + size_t off = pTuple->cch; + + static RTSTRTUPLE const s_aEnable[2] = + { + { RT_STR_TUPLE("DISABLED") }, + { RT_STR_TUPLE("ENABLED ") }, + }; + pTuple = &s_aEnable[pStreamCA->fEnabled]; + memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch); + off += pTuple->cch; + + static RTSTRTUPLE const s_aStarted[2] = + { + { RT_STR_TUPLE(" STOPPED") }, + { RT_STR_TUPLE(" STARTED") }, + }; + pTuple = &s_aStarted[pStreamCA->fStarted]; + memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + static RTSTRTUPLE const s_aDraining[2] = + { + { RT_STR_TUPLE("") }, + { RT_STR_TUPLE(" DRAINING") }, + }; + pTuple = &s_aDraining[pStreamCA->fDraining]; + memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch); + off += pTuple->cch; + + Assert(off < sizeof(pStreamCA->szStatus)); + pStreamCA->szStatus[off] = '\0'; + return pStreamCA->szStatus; +} +#endif /*LOG_ENABLED*/ + + + + +#if 0 /* unused */ +static int drvHstAudCaCFStringToCString(const CFStringRef pCFString, char **ppszString) +{ + CFIndex cLen = CFStringGetLength(pCFString) + 1; + char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char)); + if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8)) + { + RTMemFree(pszResult); + return VERR_NOT_FOUND; + } + + *ppszString = pszResult; + return VINF_SUCCESS; +} + +static AudioDeviceID drvHstAudCaDeviceUIDtoID(const char* pszUID) +{ + /* Create a CFString out of our CString. */ + CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman); + + /* Fill the translation structure. */ + AudioDeviceID deviceID; + + AudioValueTranslation translation; + translation.mInputData = &strUID; + translation.mInputDataSize = sizeof(CFStringRef); + translation.mOutputData = &deviceID; + translation.mOutputDataSize = sizeof(AudioDeviceID); + + /* Fetch the translation from the UID to the device ID. */ + AudioObjectPropertyAddress PropAddr = + { + kAudioHardwarePropertyDeviceForUID, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + UInt32 uSize = sizeof(AudioValueTranslation); + OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation); + + /* Release the temporary CFString */ + CFRelease(strUID); + + if (RT_LIKELY(err == noErr)) + return deviceID; + + /* Return the unknown device on error. */ + return kAudioDeviceUnknown; +} +#endif /* unused */ + + +/** + * Wrapper around AudioObjectGetPropertyData and AudioObjectGetPropertyDataSize. + * + * @returns Pointer to temp heap allocation with the data on success, free using + * RTMemTmpFree. NULL on failure, fully logged. + */ +static void *drvHstAudCaGetPropertyDataEx(AudioObjectID idObject, AudioObjectPropertySelector enmSelector, + AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement, + const char *pszWhat, UInt32 *pcb) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ enmSelector, + /*.mScope = */ enmScope, + /*.mElement = */ enmElement + }; + + /* + * Have to retry here in case the size isn't stable (like if a new device/whatever is added). + */ + for (uint32_t iTry = 0; ; iTry++) + { + UInt32 cb = 0; + OSStatus orc = AudioObjectGetPropertyDataSize(idObject, &PropAddr, 0, NULL, &cb); + if (orc == noErr) + { + cb = RT_MAX(cb, 1); /* we're allergic to zero allocations. */ + void *pv = RTMemTmpAllocZ(cb); + if (pv) + { + orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv); + if (orc == noErr) + { + Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb)); + if (pcb) + *pcb = cb; + return pv; + } + + RTMemTmpFree(pv); + LogFunc(("AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) -> %#x, iTry=%d\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc, iTry)); + if (iTry < 3) + continue; + LogRelMax(32, ("CoreAudio: AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) failed: %#x\n", + idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc)); + } + else + LogRelMax(32, ("CoreAudio: Failed to allocate %#x bytes (to get %s for %s).\n", cb, pszWhat, idObject)); + } + else + LogRelMax(32, ("CoreAudio: Failed to get %s for %u: %#x\n", pszWhat, idObject, orc)); + if (pcb) + *pcb = 0; + return NULL; + } +} + + +/** + * Wrapper around AudioObjectGetPropertyData. + * + * @returns Success indicator. Failures (@c false) are fully logged. + */ +static bool drvHstAudCaGetPropertyData(AudioObjectID idObject, AudioObjectPropertySelector enmSelector, + AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement, + const char *pszWhat, void *pv, UInt32 cb) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ enmSelector, + /*.mScope = */ enmScope, + /*.mElement = */ enmElement + }; + + OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv); + if (orc == noErr) + { + Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb)); + return true; + } + LogRelMax(64, ("CoreAudio: Failed to query %s (%u/%#x/%#x/%x, cb=%#x): %#x\n", + pszWhat, idObject, enmSelector, enmScope, enmElement, cb, orc)); + return false; +} + + +/** + * Count the number of channels in one direction. + * + * @returns Channel count. + */ +static uint32_t drvHstAudCaEnumCountChannels(AudioObjectID idObject, AudioObjectPropertyScope enmScope) +{ + uint32_t cChannels = 0; + + AudioBufferList *pBufs + = (AudioBufferList *)drvHstAudCaGetPropertyDataEx(idObject, kAudioDevicePropertyStreamConfiguration, + enmScope, kAudioObjectPropertyElementMaster, "stream config", NULL); + if (pBufs) + { + UInt32 idxBuf = pBufs->mNumberBuffers; + while (idxBuf-- > 0) + { + Log9Func(("%u/%#x[%u]: %u\n", idObject, enmScope, idxBuf, pBufs->mBuffers[idxBuf].mNumberChannels)); + cChannels += pBufs->mBuffers[idxBuf].mNumberChannels; + } + + RTMemTmpFree(pBufs); + } + + return cChannels; +} + + +/** + * Translates a UID to an audio device ID. + * + * @returns Audio device ID on success, kAudioDeviceUnknown on failure. + * @param hStrUid The UID string to convert. + * @param pszUid The C-string vresion of @a hStrUid. + * @param pszWhat What we're converting (for logging). + */ +static AudioObjectID drvHstAudCaDeviceUidToId(CFStringRef hStrUid, const char *pszUid, const char *pszWhat) +{ + AudioObjectPropertyAddress const PropAddr = + { + /*.mSelector = */ kAudioHardwarePropertyTranslateUIDToDevice, + /*.mScope = */ kAudioObjectPropertyScopeGlobal, + /*.mElement = */ kAudioObjectPropertyElementMaster + }; + AudioObjectID idDevice = 0; + UInt32 cb = sizeof(idDevice); + OSStatus orc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, + sizeof(hStrUid), &hStrUid, &cb, &idDevice); + if (orc == noErr) + { + Log9Func(("%s device UID '%s' -> %RU32\n", pszWhat, pszUid, idDevice)); + return idDevice; + } + /** @todo test on < 10.9, see which status code and do a fallback using the + * enumeration code. */ + LogRelMax(64, ("CoreAudio: Failed to translate %s device UID '%s' to audio device ID: %#x\n", pszWhat, pszUid, orc)); + return kAudioDeviceUnknown; +} + + +/** + * Copies a CFString to a buffer (UTF-8). + * + * @returns VBox status code. In the case of a buffer overflow, the buffer will + * contain data and be correctly terminated (provided @a cbDst is not + * zero.) + */ +static int drvHstAudCaCFStringToBuf(CFStringRef hStr, char *pszDst, size_t cbDst) +{ + AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW); + + if (CFStringGetCString(hStr, pszDst, cbDst, kCFStringEncodingUTF8)) + return VINF_SUCCESS; + + /* First fallback: */ + const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8); + if (pszSrc) + return RTStrCopy(pszDst, cbDst, pszSrc); + + /* Second fallback: */ + CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1; + AssertReturn(cbMax > 0, VERR_INVALID_UTF8_ENCODING); + AssertReturn(cbMax < (CFIndex)_16M, VERR_OUT_OF_RANGE); + + char *pszTmp = (char *)RTMemTmpAlloc(cbMax); + AssertReturn(pszTmp, VERR_NO_TMP_MEMORY); + + int rc; + if (CFStringGetCString(hStr, pszTmp, cbMax, kCFStringEncodingUTF8)) + rc = RTStrCopy(pszDst, cbDst, pszTmp); + else + { + *pszDst = '\0'; + rc = VERR_INVALID_UTF8_ENCODING; + } + + RTMemTmpFree(pszTmp); + return rc; +} + + +/** + * Copies a CFString to a heap buffer (UTF-8). + * + * @returns Pointer to the heap buffer on success, NULL if out of heap or some + * conversion/extraction problem + */ +static char *drvHstAudCaCFStringToHeap(CFStringRef hStr) +{ + const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8); + if (pszSrc) + return RTStrDup(pszSrc); + + /* Fallback: */ + CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1; + AssertReturn(cbMax > 0, NULL); + AssertReturn(cbMax < (CFIndex)_16M, NULL); + + char *pszDst = RTStrAlloc(cbMax); + if (pszDst) + { + AssertReturnStmt(CFStringGetCString(hStr, pszDst, cbMax, kCFStringEncodingUTF8), RTStrFree(pszDst), NULL); + size_t const cchDst = strlen(pszDst); + if (cbMax - cchDst > 32) + RTStrRealloc(&pszDst, cchDst + 1); + } + return pszDst; +} + + +/********************************************************************************************************************************* +* Device Change Notification Callbacks * +*********************************************************************************************************************************/ + +#ifdef LOG_ENABLED +/** + * Called when the kAudioDevicePropertyNominalSampleRate or + * kAudioDeviceProcessorOverload properties changes on a default device. + * + * Registered on default devices after device enumeration. + * Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress paAddresses[], void *pvUser) +{ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pvUser=%p\n", idObject, idObject, cAddresses, pvUser)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + +/** @todo r=bird: What's the plan here exactly? I've changed it to + * LOG_ENABLED only for now, as this has no other purpose. */ + switch (idObject) + { + case kAudioDeviceProcessorOverload: + LogFunc(("Processor overload detected!\n")); + break; + case kAudioDevicePropertyNominalSampleRate: + LogFunc(("kAudioDevicePropertyNominalSampleRate!\n")); + break; + default: + /* Just skip. */ + break; + } + + return noErr; +} +#endif /* LOG_ENABLED */ + + +/** + * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a + * default device. + * + * The purpose is mainly to log the event. There isn't much we can do about + * active streams or future one, other than waiting for a default device change + * notification callback. In the mean time, active streams should start failing + * to work and new ones fail on creation. This is the same for when we're + * configure to use specific devices, only we don't get any device change + * callback like for default ones. + * + * Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress paAddresses[], void *pvUser) +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + AssertPtr(pThis); + RT_NOREF(cAddresses, paAddresses); + + /* + * Log everything. + */ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + + /* + * Check which devices are affected. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, noErr); /* could be a destruction race */ + + for (unsigned i = 0; i < 2; i++) + { + if (idObject == (i == 0 ? pThis->InputDevice.idDevice : pThis->OutputDevice.idDevice)) + { + AudioObjectPropertyAddress const PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + i == 0 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + UInt32 fAlive = 0; + UInt32 cb = sizeof(fAlive); + OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, &fAlive); + if ( orc == kAudioHardwareBadDeviceError + || (orc == noErr && !fAlive)) + { + LogRel(("CoreAudio: The default %s device (%u) stopped functioning.\n", idObject, i == 0 ? "input" : "output")); +#if 0 /* This will only cause an extra re-init (in addition to the default device change) and likely do no good even if that + default device change callback doesn't arrive. So, don't do it! (bird) */ + PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort; + if (pIHostAudioPort) + { + RTCritSectLeave(&pThis->CritSect); + + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, i == 0 ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL); + + rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, noErr); /* could be a destruction race */ + } +#endif + } + } + } + + RTCritSectLeave(&pThis->CritSect); + return noErr; +} + + +/** + * Called when the default recording or playback device has changed. + * + * Registered by the constructor. Not sure on which thread/runloop this runs. + * + * (See AudioObjectPropertyListenerProc in the SDK headers.) + */ +static OSStatus drvHstAudCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses, + const AudioObjectPropertyAddress *paAddresses, void *pvUser) + +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + AssertPtr(pThis); + RT_NOREF(idObject, cAddresses, paAddresses); + + /* + * Log everything. + */ + LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses)); + for (UInt32 idx = 0; idx < cAddresses; idx++) + LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n", + idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement)); + + /* + * Update the default devices and notify parent driver if anything actually changed. + */ + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/); + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/); + + return noErr; +} + + +/** + * Registers callbacks for a specific Core Audio device. + * + * @returns true if idDevice isn't kAudioDeviceUnknown and callbacks were + * registered, otherwise false. + * @param pThis The core audio driver instance data. + * @param idDevice The device ID to deregister callbacks for. + */ +static bool drvHstAudCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice) +{ + if (idDevice != kAudioDeviceUnknown) + { + LogFunc(("idDevice=%RU32\n", idDevice)); + AudioObjectPropertyAddress PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + OSStatus orc; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis); + unsigned cRegistrations = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the recording device state changed listener (%#x)\n", orc)); + +#ifdef LOG_ENABLED + PropAddr.mSelector = kAudioDeviceProcessorOverload; + PropAddr.mScope = kAudioUnitScope_Global; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + cRegistrations += orc == noErr; + if (orc != noErr) + LogRel(("CoreAudio: Failed to register processor overload listener (%#x)\n", orc)); + + PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate; + PropAddr.mScope = kAudioUnitScope_Global; + orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + cRegistrations += orc == noErr; + if (orc != noErr) + LogRel(("CoreAudio: Failed to register sample rate changed listener (%#x)\n", orc)); +#endif + return cRegistrations > 0; + } + return false; +} + + +/** + * Undoes what drvHstAudCaDeviceRegisterCallbacks() did. + * + * @param pThis The core audio driver instance data. + * @param idDevice The device ID to deregister callbacks for. + */ +static void drvHstAudCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice) +{ + if (idDevice != kAudioDeviceUnknown) + { + LogFunc(("idDevice=%RU32\n", idDevice)); + AudioObjectPropertyAddress PropAddr = + { + kAudioDevicePropertyDeviceIsAlive, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + OSStatus orc; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the device alive listener (%#x)\n", orc)); + +#ifdef LOG_ENABLED + PropAddr.mSelector = kAudioDeviceProcessorOverload; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%#x)\n", orc)); + + PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate; + orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%#x)\n", orc)); +#endif + } +} + + +/** + * Updates the default device for one direction. + * + * @param pThis The core audio driver instance data. + * @param pDevice The device information to update. + * @param fInput Set if input device, clear if output. + * @param fNotify Whether to notify the parent driver if something + * changed. + */ +static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify) +{ + /* + * Skip if there is a specific device we should use for this direction. + */ + if (pDevice->pszSpecific) + return; + + /* + * Get the information before we enter the critical section. + * + * (Yeah, this may make us get things wrong if the defaults changes really + * fast and we get notifications in parallel on multiple threads. However, + * the first is a don't-do-that situation and the latter is unlikely.) + */ + AudioDeviceID idDefaultDev = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, + fInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + fInput ? "default input device" : "default output device", + &idDefaultDev, sizeof(idDefaultDev))) + idDefaultDev = kAudioDeviceUnknown; + + CFStringRef hStrUid = NULL; + if (idDefaultDev != kAudioDeviceUnknown) + { + if (!drvHstAudCaGetPropertyData(idDefaultDev, kAudioDevicePropertyDeviceUID, + fInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, + fInput ? "default input device UID" : "default output device UID", + &hStrUid, sizeof(hStrUid))) + hStrUid = NULL; + } + char szUid[128]; + if (hStrUid) + drvHstAudCaCFStringToBuf(hStrUid, szUid, sizeof(szUid)); + else + szUid[0] = '\0'; + + /* + * Grab the lock and do the updating. + * + * We're a little paranoid wrt the locking in case there turn out to be some kind + * of race around destruction (there really can't be, but better play safe). + */ + PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL; + + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (idDefaultDev != pDevice->idDevice) + { + if (idDefaultDev != kAudioDeviceUnknown) + { + LogRel(("CoreAudio: Default %s device: %u (was %u), ID '%s'\n", + fInput ? "input" : "output", idDefaultDev, pDevice->idDevice, szUid)); + pIHostAudioPort = fNotify ? pThis->pIHostAudioPort : NULL; /* (only if there is a new device) */ + } + else + LogRel(("CoreAudio: Default %s device is gone (was %u)\n", fInput ? "input" : "output", pDevice->idDevice)); + + if (pDevice->hStrUid) + CFRelease(pDevice->hStrUid); + if (pDevice->fRegisteredListeners) + drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice); + pDevice->hStrUid = hStrUid; + pDevice->idDevice = idDefaultDev; + pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice); + hStrUid = NULL; + } + RTCritSectLeave(&pThis->CritSect); + } + + if (hStrUid != NULL) + CFRelease(hStrUid); + + /* + * Notify parent driver to trigger a re-init of any associated streams. + */ + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about %s default device change...\n", fInput ? "input" : "output")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/); + } +} + + +/** + * Sets the device to use in one or the other direction (@a fInput). + * + * @returns VBox status code. + * @param pThis The core audio driver instance data. + * @param pDevice The device info structure to update. + * @param fInput Set if input, clear if output. + * @param fNotify Whether to notify the parent driver if something + * changed. + * @param pszUid The UID string for the device to use. NULL or empty + * string if default should be used. + */ +static int drvHstAudCaSetDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify, + const char *pszUid) +{ + if (!pszUid || !*pszUid) + { + /* + * Use default. Always refresh the given default device. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + if (pDevice->pszSpecific) + { + LogRel(("CoreAudio: Changing %s device from '%s' to default.\n", fInput ? "input" : "output", pDevice->pszSpecific)); + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = NULL; + } + + RTCritSectLeave(&pThis->CritSect); + + drvHstAudCaUpdateOneDefaultDevice(pThis, pDevice, fInput, fNotify); + } + else + { + /* + * Use device specified by pszUid. If not change, search for the device + * again if idDevice is unknown. + */ + int rc = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc, rc); + + bool fSkip = false; + bool fSame = false; + if (pDevice->pszSpecific) + { + if (strcmp(pszUid, pDevice->pszSpecific) != 0) + { + LogRel(("CoreAudio: Changing %s device from '%s' to '%s'.\n", + fInput ? "input" : "output", pDevice->pszSpecific, pszUid)); + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = NULL; + } + else + { + fSkip = pDevice->idDevice != kAudioDeviceUnknown; + fSame = true; + } + } + else + LogRel(("CoreAudio: Changing %s device from default to '%s'.\n", fInput ? "input" : "output", pszUid)); + + /* + * Allocate and swap the strings. This is the bit that might fail. + */ + if (!fSame) + { + CFStringRef hStrUid = CFStringCreateWithBytes(NULL /*allocator*/, (UInt8 const *)pszUid, (CFIndex)strlen(pszUid), + kCFStringEncodingUTF8, false /*isExternalRepresentation*/); + char *pszSpecific = RTStrDup(pszUid); + if (hStrUid && pszSpecific) + { + if (pDevice->hStrUid) + CFRelease(pDevice->hStrUid); + pDevice->hStrUid = hStrUid; + RTStrFree(pDevice->pszSpecific); + pDevice->pszSpecific = pszSpecific; + } + else + { + RTCritSectLeave(&pThis->CritSect); + + LogFunc(("returns VERR_NO_STR_MEMORY!\n")); + if (hStrUid) + CFRelease(hStrUid); + RTStrFree(pszSpecific); + return VERR_NO_STR_MEMORY; + } + + if (pDevice->fRegisteredListeners) + { + drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice); + pDevice->fRegisteredListeners = false; + } + } + + /* + * Locate the device ID corresponding to the UID string. + */ + if (!fSkip) + { + pDevice->idDevice = drvHstAudCaDeviceUidToId(pDevice->hStrUid, pszUid, fInput ? "input" : "output"); + pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice); + } + + PPDMIHOSTAUDIOPORT pIHostAudioPort = fNotify && !fSame ? pThis->pIHostAudioPort : NULL; + RTCritSectLeave(&pThis->CritSect); + + /* + * Notify parent driver to trigger a re-init of any associated streams. + */ + if (pIHostAudioPort) + { + LogFlowFunc(("Notifying parent driver about %s device change...\n", fInput ? "input" : "output")); + pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/); + } + } + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Worker Thread * +*********************************************************************************************************************************/ +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + +/** + * Message handling callback for CFMachPort. + */ +static void drvHstAudCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser) +{ + RT_NOREF(hPort, pvMsg, cbMsg, pvUser); + LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser)); +} + + +/** + * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.} + */ +static DECLCALLBACK(int) drvHstAudCaThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser; + + /* + * Get the runloop, add the mach port to it and signal the constructor thread that we're ready. + */ + pThis->hThreadRunLoop = CFRunLoopGetCurrent(); + CFRetain(pThis->hThreadRunLoop); + + CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode); + + int rc = RTThreadUserSignal(hThreadSelf); + AssertRCReturn(rc, rc); + + /* + * Do work. + */ + for (;;) + { + SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE); + Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop)); + Assert(rcRunLoop != kCFRunLoopRunFinished); + if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished) + { /* likely */ } + else + break; + } + + /* + * Clean up. + */ + CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode); + LogFunc(("The thread quits!\n")); + return VINF_SUCCESS; +} + +#endif /* CORE_AUDIO_WITH_WORKER_THREAD */ + + + +/********************************************************************************************************************************* +* PDMIHOSTAUDIO * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} + */ +static DECLCALLBACK(int) drvHstAudCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); + + /* + * Fill in the config structure. + */ + RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio"); + pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM); + pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY; + + RTCritSectEnter(&pThis->CritSect); +#if 0 /** @todo r=bird: This looks like complete utter non-sense to me. */ + /* For Core Audio we provide one stream per device for now. */ + pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN); + pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT); +#else + pBackendCfg->cMaxStreamsIn = UINT32_MAX; + pBackendCfg->cMaxStreamsOut = UINT32_MAX; +#endif + RTCritSectLeave(&pThis->CritSect); + + LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS)); + return VINF_SUCCESS; +} + + +/** + * Creates an enumeration of the host's playback and capture devices. + * + * @returns VBox status code. + * @param pDevEnm Where to store the enumerated devices. Caller is + * expected to clean this up on failure, if so desired. + * + * @note Handling of out-of-memory conditions isn't perhaps as good as it + * could be, but it was done so to make the drvHstAudCaGetPropertyData* + * functions as uncomplicated as possible. + */ +static int drvHstAudCaDevicesEnumerateAll(PPDMAUDIOHOSTENUM pDevEnm) +{ + AssertPtr(pDevEnm); + + /* + * First get the UIDs for the default devices. + */ + AudioDeviceID idDefaultDevIn = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "default input device", &idDefaultDevIn, sizeof(idDefaultDevIn))) + idDefaultDevIn = kAudioDeviceUnknown; + if (idDefaultDevIn == kAudioDeviceUnknown) + LogFunc(("No default input device\n")); + + AudioDeviceID idDefaultDevOut = kAudioDeviceUnknown; + if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "default output device", &idDefaultDevOut, sizeof(idDefaultDevOut))) + idDefaultDevOut = kAudioDeviceUnknown; + if (idDefaultDevOut == kAudioDeviceUnknown) + LogFunc(("No default output device\n")); + + /* + * Get a list of all audio devices. + * (We have to retry as the we may race new devices being inserted.) + */ + UInt32 cDevices = 0; + AudioDeviceID *paidDevices + = (AudioDeviceID *)drvHstAudCaGetPropertyDataEx(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster, + "devices", &cDevices); + cDevices /= sizeof(paidDevices[0]); + + /* + * Try get details on each device and try add them to the enumeration result. + */ + for (uint32_t i = 0; i < cDevices; i++) + { + AudioDeviceID const idDevice = paidDevices[i]; + + /* + * Allocate a new device entry and populate it. + * + * The only relevant information here is channel counts and the UID(s), + * everything else is just extras we can live without. + */ + PCOREAUDIODEVICEDATA pDevEntry = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevEntry), 0, 0); + AssertReturnStmt(pDevEntry, RTMemTmpFree(paidDevices), VERR_NO_MEMORY); + + pDevEntry->idDevice = idDevice; + if (idDevice != kAudioDeviceUnknown) + { + if (idDevice == idDefaultDevIn) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN; + if (idDevice == idDefaultDevOut) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT; + } + + /* Count channels and determin the usage. */ + pDevEntry->Core.cMaxInputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeInput); + pDevEntry->Core.cMaxOutputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeOutput); + if (pDevEntry->Core.cMaxInputChannels > 0 && pDevEntry->Core.cMaxOutputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_DUPLEX; + else if (pDevEntry->Core.cMaxInputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_IN; + else if (pDevEntry->Core.cMaxOutputChannels > 0) + pDevEntry->Core.enmUsage = PDMAUDIODIR_OUT; + else + { + pDevEntry->Core.enmUsage = PDMAUDIODIR_UNKNOWN; + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE; + /** @todo drop & skip? */ + } + + /* Get the device UID. (We ASSUME this is the same for both input and + output sides of the device.) */ + CFStringRef hStrUid; + if (!drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceUID, kAudioDevicePropertyDeviceUID, + kAudioObjectPropertyElementMaster, + "device UID", &hStrUid, sizeof(hStrUid))) + hStrUid = NULL; + + if (hStrUid) + { + pDevEntry->Core.pszId = drvHstAudCaCFStringToHeap(hStrUid); + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC; + } + else + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE; + + /* Get the device name (ignore failures). */ + CFStringRef hStrName = NULL; + if (drvHstAudCaGetPropertyData(idDevice, kAudioObjectPropertyName, + pDevEntry->Core.enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, "device name", &hStrName, sizeof(hStrName))) + { + pDevEntry->Core.pszName = drvHstAudCaCFStringToHeap(hStrName); + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC; + CFRelease(hStrName); + } + + /* Check if the device is alive for the intended usage. For duplex + devices we'll flag it as dead if either of the directions are dead, + as there is no convenient way of saying otherwise. It's acadmic as + nobody currently 2021-05-22) uses the flag for anything. */ + UInt32 fAlive = 0; + if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, + pDevEntry->Core.enmUsage == PDMAUDIODIR_IN + ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive))) + if (!fAlive) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD; + fAlive = 0; + if ( pDevEntry->Core.enmUsage == PDMAUDIODIR_DUPLEX + && !(pDevEntry->Core.fFlags == PDMAUDIOHOSTDEV_F_DEAD) + && drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive))) + if (!fAlive) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD; + + /* Check if the device is being hogged by someone else. */ + pid_t pidHogger = -2; + if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster, "hog-mode", &pidHogger, sizeof(pidHogger))) + if (pidHogger >= 0) + pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED; + + /* + * Try make sure we've got a name... Only add it to the enumeration if we have one. + */ + if (!pDevEntry->Core.pszName) + { + pDevEntry->Core.pszName = pDevEntry->Core.pszId; + pDevEntry->Core.fFlags &= ~PDMAUDIOHOSTDEV_F_NAME_ALLOC; + } + + if (pDevEntry->Core.pszName) + PDMAudioHostEnumAppend(pDevEnm, &pDevEntry->Core); + else + PDMAudioHostDevFree(&pDevEntry->Core); + } + + RTMemTmpFree(paidDevices); + + LogFunc(("Returning %u devices\n", pDevEnm->cDevices)); + PDMAudioHostEnumLog(pDevEnm, "Core Audio"); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices} + */ +static DECLCALLBACK(int) drvHstAudCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum) +{ + RT_NOREF(pInterface); + AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER); + + PDMAudioHostEnumInit(pDeviceEnum); + int rc = drvHstAudCaDevicesEnumerateAll(pDeviceEnum); + if (RT_FAILURE(rc)) + PDMAudioHostEnumDelete(pDeviceEnum); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice} + */ +static DECLCALLBACK(int) drvHstAudCaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + AssertPtrNullReturn(pszId, VERR_INVALID_POINTER); + if (pszId && !*pszId) + pszId = NULL; + AssertMsgReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX, + ("enmDir=%d\n", enmDir, pszId), VERR_INVALID_PARAMETER); + + /* + * Make the change. + */ + int rc = VINF_SUCCESS; + if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX) + rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/, pszId); + if (enmDir == PDMAUDIODIR_OUT || (enmDir == PDMAUDIODIR_DUPLEX && RT_SUCCESS(rc))) + rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/, pszId); + + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} + */ +static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) +{ + RT_NOREF(pInterface, enmDir); + return PDMAUDIOBACKENDSTS_RUNNING; +} + + +/** + * Marks the given buffer as queued or not-queued. + * + * @returns Old queued value. + * @param pAudioBuffer The buffer. + * @param fQueued The new queued state. + */ +DECLINLINE(bool) drvHstAudCaSetBufferQueued(AudioQueueBufferRef pAudioBuffer, bool fQueued) +{ + if (fQueued) + return ASMAtomicBitTestAndSet(&pAudioBuffer->mUserData, 0); + return ASMAtomicBitTestAndClear(&pAudioBuffer->mUserData, 0); +} + + +/** + * Gets the queued state of the buffer. + * @returns true if queued, false if not. + * @param pAudioBuffer The buffer. + */ +DECLINLINE(bool) drvHstAudCaIsBufferQueued(AudioQueueBufferRef pAudioBuffer) +{ + return ((uintptr_t)pAudioBuffer->mUserData & 1) == 1; +} + + +/** + * Output audio queue buffer callback. + * + * Called whenever an audio queue is done processing a buffer. This routine + * will set the data fill size to zero and mark it as unqueued so that + * drvHstAudCaHA_StreamPlay knowns it can use it. + * + * @param pvUser User argument. + * @param hAudioQueue Audio queue to process output data for. + * @param pAudioBuffer Audio buffer to store output data in. + * + * @thread queue thread. + */ +static void drvHstAudCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer) +{ +#if defined(VBOX_STRICT) || defined(LOG_ENABLED) + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pStreamCA); + Assert(pStreamCA->hAudioQueue == hAudioQueue); + + uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8; + Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer)); + AssertReturnVoid( idxBuf < pStreamCA->cBuffers + && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer); +#endif + + pAudioBuffer->mAudioDataByteSize = 0; + bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/); + Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer)); + Assert(fWasQueued); RT_NOREF(fWasQueued); + + RT_NOREF(pvUser, hAudioQueue); +} + + +/** + * Input audio queue buffer callback. + * + * Called whenever input data from the audio queue becomes available. This + * routine will mark the buffer unqueued so that drvHstAudCaHA_StreamCapture can + * read the data from it. + * + * @param pvUser User argument. + * @param hAudioQueue Audio queue to process input data from. + * @param pAudioBuffer Audio buffer to process input data from. + * @param pAudioTS Audio timestamp. + * @param cPacketDesc Number of packet descriptors. + * @param paPacketDesc Array of packet descriptors. + */ +static void drvHstAudCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, + AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS, + UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc) +{ +#if defined(VBOX_STRICT) || defined(LOG_ENABLED) + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser; + AssertPtr(pStreamCA); + Assert(pStreamCA->hAudioQueue == hAudioQueue); + + uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8; + Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize)); + AssertReturnVoid( idxBuf < pStreamCA->cBuffers + && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer); +#endif + + bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/); + Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer)); + Assert(fWasQueued); RT_NOREF(fWasQueued); + + RT_NOREF(pvUser, hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc); +} + + +static void drvHstAudCaLogAsbd(const char *pszDesc, const AudioStreamBasicDescription *pASBD) +{ + LogRel2(("CoreAudio: %s description:\n", pszDesc)); + LogRel2(("CoreAudio: Format ID: %#RX32 (%c%c%c%c)\n", pASBD->mFormatID, + RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID), + RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID))); + LogRel2(("CoreAudio: Flags: %#RX32", pASBD->mFormatFlags)); + if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat) + LogRel2((" Float")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian) + LogRel2((" BigEndian")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger) + LogRel2((" SignedInteger")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked) + LogRel2((" Packed")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh) + LogRel2((" AlignedHigh")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved) + LogRel2((" NonInterleaved")); + if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable) + LogRel2((" NonMixable")); + if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear) + LogRel2((" AllClear")); + LogRel2(("\n")); + LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n", + (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100)); + LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame)); + LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket)); + LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel)); + LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame)); + LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket)); +} + + +static void drvHstAudCaPropsToAsbd(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD) +{ + AssertPtrReturnVoid(pProps); + AssertPtrReturnVoid(pASBD); + + RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription)); + + pASBD->mFormatID = kAudioFormatLinearPCM; + pASBD->mFormatFlags = kAudioFormatFlagIsPacked; + if (pProps->fSigned) + pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger; + if (PDMAudioPropsIsBigEndian(pProps)) + pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian; + pASBD->mSampleRate = PDMAudioPropsHz(pProps); + pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps); + pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps); + pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps); + pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */ + pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) +{ + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); + AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); + AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + int rc; + + /** @todo This takes too long. Stats indicates it may take up to 200 ms. + * Knoppix guest resets the stream and we hear nada because the + * draining is aborted when the stream is destroyed. Should try use + * async init for parts (much) of this. */ + + /* + * Permission check for input devices before we start. + */ + if (pCfgReq->enmDir == PDMAUDIODIR_IN) + { + rc = coreAudioInputPermissionCheck(); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Do we have a device for the requested stream direction? + */ + RTCritSectEnter(&pThis->CritSect); + CFStringRef hDevUidStr = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->InputDevice.hStrUid : pThis->OutputDevice.hStrUid; + if (hDevUidStr) + CFRetain(hDevUidStr); + RTCritSectLeave(&pThis->CritSect); + +#ifdef LOG_ENABLED + char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX]; +#endif + LogFunc(("hDevUidStr=%p *pCfgReq: %s\n", hDevUidStr, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) )); + if (hDevUidStr) + { + /* + * Basic structure init. + */ + pStreamCA->fEnabled = false; + pStreamCA->fStarted = false; + pStreamCA->fDraining = false; + pStreamCA->fRestartOnResume = false; + pStreamCA->offInternal = 0; + pStreamCA->idxBuffer = 0; + pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT; + + rc = RTCritSectInit(&pStreamCA->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Do format conversion and create the circular buffer we use to shuffle + * data to/from the queue thread. + */ + PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq); + drvHstAudCaPropsToAsbd(&pCfgReq->Props, &pStreamCA->BasicStreamDesc); + /** @todo Do some validation? */ + drvHstAudCaLogAsbd(pCfgReq->enmDir == PDMAUDIODIR_IN ? "Capturing queue format" : "Playback queue format", + &pStreamCA->BasicStreamDesc); + /* + * Create audio queue. + * + * Documentation says the callbacks will be run on some core audio + * related thread if we don't specify a runloop here. That's simpler. + */ +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop; + CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode; +#else + CFRunLoopRef const hRunLoop = NULL; + CFStringRef const hRunLoopMode = NULL; +#endif + OSStatus orc; + if (pCfgReq->enmDir == PDMAUDIODIR_OUT) + orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHstAudCaOutputQueueBufferCallback, pStreamCA, + hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue); + else + orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHstAudCaInputQueueBufferCallback, pStreamCA, + hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc)); + if (orc == noErr) + { + /* + * Assign device to the queue. + */ + UInt32 uSize = sizeof(hDevUidStr); + orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &hDevUidStr, uSize); + LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc)); + if (orc == noErr) + { + /* + * Sanity-adjust the requested buffer size. + */ + uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC); + uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/); + cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize); + cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax); + + /* + * The queue buffers size is based on cMsSchedulingHint so that we're likely to + * have a new one ready/done after each guest DMA transfer. We must however + * make sure we don't end up with too may or too few. + */ + Assert(pCfgReq->Device.cMsSchedulingHint > 0); + uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, + pCfgReq->Device.cMsSchedulingHint > 0 + ? pCfgReq->Device.cMsSchedulingHint : 10); + uint32_t cQueueBuffers; + if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize) + { + cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer; + if (cQueueBuffers > COREAUDIO_MAX_BUFFERS) + { + cQueueBuffers = COREAUDIO_MAX_BUFFERS; + cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS; + } + } + else + { + cQueueBuffers = COREAUDIO_MIN_BUFFERS; + cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS; + } + + cFramesBufferSize = cQueueBuffers * cFramesBufferSize; + + /* + * Allocate the audio queue buffers. + */ + pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers); + if (pStreamCA->paBuffers != NULL) + { + pStreamCA->cBuffers = cQueueBuffers; + + const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer); + LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer)); + cFramesBufferSize = 0; + for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++) + { + AudioQueueBufferRef pBuf = NULL; + orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf); + if (RT_LIKELY(orc == noErr)) + { + pBuf->mUserData = (void *)(uintptr_t)(iBuf << 8); /* bit zero is the queued-indicator. */ + pStreamCA->paBuffers[iBuf].pBuf = pBuf; + cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props, + pBuf->mAudioDataBytesCapacity); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity)); + } + else + { + LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n", + iBuf, cQueueBuffers, cbQueueBuffer)); + while (iBuf-- > 0) + { + AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf); + pStreamCA->paBuffers[iBuf].pBuf = NULL; + } + break; + } + } + if (orc == noErr) + { + /* + * Update the stream config. + */ + pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize; + pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */ + pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering + * pStreamCA->Cfg.Backend.cFramesBufferSize + / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1); + + PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg); + + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT); + + LogFunc(("returns VINF_SUCCESS\n")); + CFRelease(hDevUidStr); + return VINF_SUCCESS; + } + + RTMemFree(pStreamCA->paBuffers); + } + else + rc = VERR_NO_MEMORY; + } + else + LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc)); + AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/); + } + else + LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc)); + RTCritSectDelete(&pStreamCA->CritSect); + } + else + LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc)); + CFRelease(hDevUidStr); + } + else + { + LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir))); + rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE; + } + + LogFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + LogFunc(("%p: %s fImmediate=%RTbool\n", pStreamCA, pStreamCA->Cfg.szName, fImmediate)); +#ifdef LOG_ENABLED + uint64_t const nsStart = RTTimeNanoTS(); +#endif + + /* + * Never mind if the status isn't INIT (it should always be, though). + */ + COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState); + AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState)); + if (enmInitState == COREAUDIOINITSTATE_INIT) + { + Assert(RTCritSectIsInitialized(&pStreamCA->CritSect)); + + /* + * Change the stream state and stop the stream (just to be sure). + */ + OSStatus orc; + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT); + if (pStreamCA->hAudioQueue) + { + orc = AudioQueueStop(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); + LogFlowFunc(("AudioQueueStop -> %#x\n", orc)); + } + + /* + * Enter and leave the critsect afterwards for paranoid reasons. + */ + RTCritSectEnter(&pStreamCA->CritSect); + RTCritSectLeave(&pStreamCA->CritSect); + + /* + * Free the queue buffers and the queue. + * + * This may take a while. The AudioQueueReset call seems to helps + * reducing time stuck in AudioQueueDispose. + */ +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + LogRel(("Queue-destruction timer starting...\n")); + PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio); + RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS); + uint64_t nsStart = RTTimeNanoTS(); +#endif + +#if 0 /* This seems to work even when doing a non-immediate stop&dispose. However, it doesn't make sense conceptually. */ + if (pStreamCA->hAudioQueue /*&& fImmediate*/) + { + LogFlowFunc(("Calling AudioQueueReset ...\n")); + orc = AudioQueueReset(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueReset -> %#x\n", orc)); + } +#endif + + if (pStreamCA->paBuffers && fImmediate) + { + LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers)); + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + { + orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf); + AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc)); + pStreamCA->paBuffers[iBuf].pBuf = NULL; + } + } + + if (pStreamCA->hAudioQueue) + { + LogFlowFunc(("Disposing of the queue ...\n")); + orc = AudioQueueDispose(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); /* may take some time */ + LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc)); + AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc)); + pStreamCA->hAudioQueue = NULL; + } + + /* We should get no further buffer callbacks at this point according to the docs. */ + if (pStreamCA->paBuffers) + { + RTMemFree(pStreamCA->paBuffers); + pStreamCA->paBuffers = NULL; + } + pStreamCA->cBuffers = 0; + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + RTTimerLRStop(pThis->hBreakpointTimer); + LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart)); +#endif + + /* + * Delete the critsect and we're done. + */ + RTCritSectDelete(&pStreamCA->CritSect); + + ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT); + } + else + LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState)); + + LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart)); + return VINF_SUCCESS; +} + + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER +/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */ +static DECLCALLBACK(void) drvHstAudCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick) +{ + LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick)); + RT_NOREF(hTimer, pvUser, iTick); + RTLogFlush(NULL); + RT_BREAKPOINT(); +} +#endif + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA))); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + Assert(!pStreamCA->fEnabled); + Assert(!pStreamCA->fStarted); + + /* + * We always reset the buffer before enabling the stream (normally never necessary). + */ + OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue); + if (orc != noErr) + LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + Assert(orc == noErr); + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + Assert(!drvHstAudCaIsBufferQueued(pStreamCA->paBuffers[iBuf].pBuf)); + + pStreamCA->offInternal = 0; + pStreamCA->fDraining = false; + pStreamCA->fEnabled = true; + pStreamCA->fRestartOnResume = false; + pStreamCA->idxBuffer = 0; + + /* + * Input streams will start capturing, while output streams will only start + * playing once we get some audio data to play (see drvHstAudCaHA_StreamPlay). + */ + int rc = VINF_SUCCESS; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN) + { + /* Zero (probably not needed) and submit all the buffers first. */ + for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++) + { + AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf; + + RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity); + pBuf->mAudioDataByteSize = 0; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/); + AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n", + iBuf, orc, orc, pStreamCA->Cfg.szName), + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/)); + } + + /* Start the stream. */ + if (orc == noErr) + { + LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName)); + orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc), + rc = VERR_AUDIO_STREAM_NOT_READY); + pStreamCA->fStarted = orc == noErr; + } + else + rc = VERR_AUDIO_STREAM_NOT_READY; + } + else + Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT); + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc\n", rc)); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Always stop it (draining or no). + */ + pStreamCA->fEnabled = false; + pStreamCA->fRestartOnResume = false; + Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT); + + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted) + { +#if 0 + OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2); + orc2 = AudioQueueFlush(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2); +#endif + + OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/); + LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamCA->fStarted = false; + pStreamCA->fDraining = false; + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Unless we're draining the stream, pause it if it has started. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted && !pStreamCA->fDraining) + { + pStreamCA->fRestartOnResume = true; + + OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue); + LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + pStreamCA->fStarted = false; + } + else + { + pStreamCA->fRestartOnResume = false; + if (pStreamCA->fDraining) + { + LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName)); + Assert(pStreamCA->fStarted); + } + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA))); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * Resume according to state saved by drvHstAudCaHA_StreamPause. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fRestartOnResume) + { + OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc != noErr) + { + LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + pStreamCA->fRestartOnResume = false; + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER); + LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n", + pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1, + pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY); + RTCritSectEnter(&pStreamCA->CritSect); + + /* + * The AudioQueueStop function has both an immediate and a drain mode, + * so we'll obviously use the latter here. For checking draining progress, + * we will just check if all buffers have been returned or not. + */ + int rc = VINF_SUCCESS; + if (pStreamCA->fStarted) + { + if (!pStreamCA->fDraining) + { + OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/); + LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc == noErr) + pStreamCA->fDraining = true; + else + { + LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + rc = VERR_GENERAL_FAILURE; + } + } + else + LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName)); + } + else + { + LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName)); + AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false); + } + + RTCritSectLeave(&pStreamCA->CritSect); + LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA))); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} + */ +static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0); + + uint32_t cbReadable = 0; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN) + { + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + uint32_t const idxStart = pStreamCA->idxBuffer; + uint32_t idxBuffer = idxStart; + AudioQueueBufferRef pBuf; + + if ( cBuffers > 0 + && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)) + { + do + { + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbFill = pBuf->mAudioDataByteSize; + AssertStmt(cbFill <= cbTotal, cbFill = cbTotal); + uint32_t off = paBuffers[idxBuffer].offRead; + AssertStmt(off < cbFill, off = cbFill); + + cbReadable += cbFill - off; + + /* Advance. */ + idxBuffer++; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)); + } + + RTCritSectLeave(&pStreamCA->CritSect); + } + Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName)); + return cbReadable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} + */ +static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0); + + uint32_t cbWritable = 0; + if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT) + { + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + uint32_t const idxStart = pStreamCA->idxBuffer; + uint32_t idxBuffer = idxStart; + AudioQueueBufferRef pBuf; + + if ( cBuffers > 0 + && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)) + { + do + { + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbUsed = pBuf->mAudioDataByteSize; + AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal); + + cbWritable += cbTotal - cbUsed; + + /* Advance. */ + idxBuffer++; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf)); + } + + RTCritSectLeave(&pStreamCA->CritSect); + } + Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName)); + return cbWritable; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState} + */ +static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface, + PPDMAUDIOBACKENDSTREAM pStream) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID); + + if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT) + { + if (!pStreamCA->fDraining) + { /* likely */ } + else + { + /* + * If we're draining, we're done when we've got all the buffers back. + */ + RTCritSectEnter(&pStreamCA->CritSect); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uintptr_t idxBuffer = pStreamCA->cBuffers; + while (idxBuffer-- > 0) + if (!drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf)) + { /* likely */ } + else + { +#ifdef LOG_ENABLED + uint32_t cQueued = 1; + while (idxBuffer-- > 0) + cQueued += drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf); + LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued)); +#endif + RTCritSectLeave(&pStreamCA->CritSect); + return PDMHOSTAUDIOSTREAMSTATE_DRAINING; + } + + LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName)); + pStreamCA->fDraining = false; + pStreamCA->fEnabled = false; + pStreamCA->fStarted = false; + RTCritSectLeave(&pStreamCA->CritSect); + } + + return PDMHOSTAUDIOSTREAMSTATE_OKAY; + } + return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */ +} + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER); + AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER); + if (cbBuf) + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf)); + AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY); + + RTCritSectEnter(&pStreamCA->CritSect); + if (pStreamCA->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamCA->CritSect); + *pcbWritten = 0; + LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + + /* + * Transfer loop. + */ + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers), + RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY); + + uint32_t idxBuffer = pStreamCA->idxBuffer; + AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers); + + int rc = VINF_SUCCESS; + uint32_t cbWritten = 0; + while (cbBuf > 0) + { + AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Check out how much we can put into the current buffer. + */ + AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf; + if (!drvHstAudCaIsBufferQueued(pBuf)) + { /* likely */ } + else + { + LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf)); + /** @todo stats */ + break; + } + + AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2); + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbUsed = pBuf->mAudioDataByteSize; + AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal); + uint32_t const cbAvail = cbTotal - cbUsed; + + /* + * Copy over the data. + */ + if (cbBuf < cbAvail) + { + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf); + pBuf->mAudioDataByteSize = cbUsed + cbBuf; + cbWritten += cbBuf; + pStreamCA->offInternal += cbBuf; + /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of + * buffers queued. */ + break; + } + + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail); + pBuf->mAudioDataByteSize = cbTotal; + cbWritten += cbAvail; + pStreamCA->offInternal += cbAvail; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/); + if (orc == noErr) + { /* likely */ } + else + { + LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n", + pStreamCA->Cfg.szName, idxBuffer, orc, orc)); + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/); + pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */ + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Advance. + */ + idxBuffer += 1; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + pStreamCA->idxBuffer = idxBuffer; + + pvBuf = (const uint8_t *)pvBuf + cbAvail; + cbBuf -= cbAvail; + } + + /* + * Start the stream if we haven't do so yet. + */ + if ( pStreamCA->fStarted + || cbWritten == 0 + || RT_FAILURE_NP(rc)) + { /* likely */ } + else + { + UInt32 cFramesPrepared = 0; +#if 0 /* taking too long? */ + OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared); + LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n", + pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal)); + AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc)); +#else + OSStatus orc; +#endif + orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/); + LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc)); + if (orc == noErr) + pStreamCA->fStarted = true; + else + { + LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n", + pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal)); + rc = VERR_AUDIO_STREAM_NOT_READY; + } + } + + /* + * Done. + */ +#ifdef LOG_ENABLED + uint64_t const msPrev = pStreamCA->msLastTransfer; +#endif + uint64_t const msNow = RTTimeMilliTS(); + if (cbWritten) + pStreamCA->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamCA->CritSect); + + *pcbWritten = cbWritten; + if (RT_SUCCESS(rc) || !cbWritten) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten, + msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) )); + return rc; +} + + +/** + * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} + */ +static DECLCALLBACK(int) drvHstAudCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, + void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) +{ + RT_NOREF(pInterface); + PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream; + AssertPtrReturn(pStreamCA, 0); + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf, VERR_INVALID_PARAMETER); + AssertPtrReturn(pcbRead, VERR_INVALID_POINTER); + Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf)); + AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY); + + RTCritSectEnter(&pStreamCA->CritSect); + if (pStreamCA->fEnabled) + { /* likely */ } + else + { + RTCritSectLeave(&pStreamCA->CritSect); + *pcbRead = 0; + LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA))); + return VINF_SUCCESS; + } + Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) )); + + + /* + * Transfer loop. + */ + uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props); + PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers; + uint32_t const cBuffers = pStreamCA->cBuffers; + AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers), + RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY); + + uint32_t idxBuffer = pStreamCA->idxBuffer; + AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers); + + int rc = VINF_SUCCESS; + uint32_t cbRead = 0; + while (cbBuf > cbFrame) + { + AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY); + + /* + * Check out how much we can read from the current buffer (if anything at all). + */ + AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf; + if (!drvHstAudCaIsBufferQueued(pBuf)) + { /* likely */ } + else + { + LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf)); + /** @todo stats */ + break; + } + + AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2); + uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity; + uint32_t cbValid = pBuf->mAudioDataByteSize; + AssertStmt(cbValid < cbTotal, cbValid = cbTotal); + uint32_t offRead = paBuffers[idxBuffer].offRead; + uint32_t const cbLeft = cbValid - offRead; + + /* + * Copy over the data. + */ + if (cbBuf < cbLeft) + { + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf); + paBuffers[idxBuffer].offRead = offRead + cbBuf; + cbRead += cbBuf; + pStreamCA->offInternal += cbBuf; + break; + } + + Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n", + pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) )); + memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft); + cbRead += cbLeft; + pStreamCA->offInternal += cbLeft; + + RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */ + paBuffers[idxBuffer].offRead = 0; + pBuf->mAudioDataByteSize = 0; + drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/); + + OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/); + if (orc == noErr) + { /* likely */ } + else + { + LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n", + pStreamCA->Cfg.szName, idxBuffer, orc, orc)); + drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/); + rc = VERR_AUDIO_STREAM_NOT_READY; + break; + } + + /* + * Advance. + */ + idxBuffer += 1; + if (idxBuffer < cBuffers) + { /* likely */ } + else + idxBuffer = 0; + pStreamCA->idxBuffer = idxBuffer; + + pvBuf = (uint8_t *)pvBuf + cbLeft; + cbBuf -= cbLeft; + } + + /* + * Done. + */ +#ifdef LOG_ENABLED + uint64_t const msPrev = pStreamCA->msLastTransfer; +#endif + uint64_t const msNow = RTTimeMilliTS(); + if (cbRead) + pStreamCA->msLastTransfer = msNow; + + RTCritSectLeave(&pStreamCA->CritSect); + + *pcbRead = cbRead; + if (RT_SUCCESS(rc) || !cbRead) + { } + else + { + LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead)); + rc = VINF_SUCCESS; + } + LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead, + msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) )); + return rc; +} + + +/********************************************************************************************************************************* +* PDMIBASE * +*********************************************************************************************************************************/ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvHstAudCaQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); + + return NULL; +} + + +/********************************************************************************************************************************* +* PDMDRVREG * +*********************************************************************************************************************************/ + +/** + * Worker for the power off and destructor callbacks. + */ +static void drvHstAudCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis) +{ + /* + * Unregister system callbacks. + */ + AudioObjectPropertyAddress PropAddr = + { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + OSStatus orc; + if (pThis->fRegisteredDefaultInputListener) + { + orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, + drvHstAudCaDefaultDeviceChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc)); + pThis->fRegisteredDefaultInputListener = false; + } + + if (pThis->fRegisteredDefaultOutputListener) + { + PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr, + drvHstAudCaDefaultDeviceChangedCallback, pThis); + if ( orc != noErr + && orc != kAudioHardwareBadObjectError) + LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc)); + pThis->fRegisteredDefaultOutputListener = false; + } + + /* + * Unregister device callbacks. + */ + RTCritSectEnter(&pThis->CritSect); + + drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->InputDevice.idDevice); + pThis->InputDevice.idDevice = kAudioDeviceUnknown; + + drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->OutputDevice.idDevice); + pThis->OutputDevice.idDevice = kAudioDeviceUnknown; + + RTCritSectLeave(&pThis->CritSect); + + LogFlowFuncEnter(); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) drvHstAudCaPowerOff(PPDMDRVINS pDrvIns) +{ + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + drvHstAudCaRemoveDefaultDeviceListners(pThis); +} + + +/** + * @callback_method_impl{FNPDMDRVDESTRUCT} + */ +static DECLCALLBACK(void) drvHstAudCaDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + + if (RTCritSectIsInitialized(&pThis->CritSect)) + drvHstAudCaRemoveDefaultDeviceListners(pThis); + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + if (pThis->hThread != NIL_RTTHREAD) + { + for (unsigned iLoop = 0; iLoop < 60; iLoop++) + { + if (pThis->hThreadRunLoop) + CFRunLoopStop(pThis->hThreadRunLoop); + if (iLoop > 10) + RTThreadPoke(pThis->hThread); + int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/); + if (RT_SUCCESS(rc)) + break; + AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc)); + } + pThis->hThread = NIL_RTTHREAD; + } + if (pThis->hThreadPortSrc) + { + CFRelease(pThis->hThreadPortSrc); + pThis->hThreadPortSrc = NULL; + } + if (pThis->hThreadPort) + { + CFMachPortInvalidate(pThis->hThreadPort); + CFRelease(pThis->hThreadPort); + pThis->hThreadPort = NULL; + } + if (pThis->hThreadRunLoop) + { + CFRelease(pThis->hThreadRunLoop); + pThis->hThreadRunLoop = NULL; + } +#endif + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + if (pThis->hBreakpointTimer != NIL_RTTIMERLR) + { + RTTimerLRDestroy(pThis->hBreakpointTimer); + pThis->hBreakpointTimer = NIL_RTTIMERLR; + } +#endif + + if (RTCritSectIsInitialized(&pThis->CritSect)) + { + int rc2 = RTCritSectDelete(&pThis->CritSect); + AssertRC(rc2); + } + + LogFlowFuncLeave(); +} + + +/** + * @callback_method_impl{FNPDMDRVCONSTRUCT, + * Construct a Core Audio driver instance.} + */ +static DECLCALLBACK(int) drvHstAudCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg, fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO); + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + LogRel(("Audio: Initializing Core Audio driver\n")); + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + pThis->hThread = NIL_RTTHREAD; +#endif +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + pThis->hBreakpointTimer = NIL_RTTIMERLR; +#endif + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvHstAudCaQueryInterface; + /* IHostAudio */ + pThis->IHostAudio.pfnGetConfig = drvHstAudCaHA_GetConfig; + pThis->IHostAudio.pfnGetDevices = drvHstAudCaHA_GetDevices; + pThis->IHostAudio.pfnSetDevice = drvHstAudCaHA_SetDevice; + pThis->IHostAudio.pfnGetStatus = drvHstAudCaHA_GetStatus; + pThis->IHostAudio.pfnDoOnWorkerThread = NULL; + pThis->IHostAudio.pfnStreamConfigHint = NULL; + pThis->IHostAudio.pfnStreamCreate = drvHstAudCaHA_StreamCreate; + pThis->IHostAudio.pfnStreamInitAsync = NULL; + pThis->IHostAudio.pfnStreamDestroy = drvHstAudCaHA_StreamDestroy; + pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL; + pThis->IHostAudio.pfnStreamEnable = drvHstAudCaHA_StreamEnable; + pThis->IHostAudio.pfnStreamDisable = drvHstAudCaHA_StreamDisable; + pThis->IHostAudio.pfnStreamPause = drvHstAudCaHA_StreamPause; + pThis->IHostAudio.pfnStreamResume = drvHstAudCaHA_StreamResume; + pThis->IHostAudio.pfnStreamDrain = drvHstAudCaHA_StreamDrain; + pThis->IHostAudio.pfnStreamGetReadable = drvHstAudCaHA_StreamGetReadable; + pThis->IHostAudio.pfnStreamGetWritable = drvHstAudCaHA_StreamGetWritable; + pThis->IHostAudio.pfnStreamGetPending = NULL; + pThis->IHostAudio.pfnStreamGetState = drvHstAudCaHA_StreamGetState; + pThis->IHostAudio.pfnStreamPlay = drvHstAudCaHA_StreamPlay; + pThis->IHostAudio.pfnStreamCapture = drvHstAudCaHA_StreamCapture; + + int rc = RTCritSectInit(&pThis->CritSect); + AssertRCReturn(rc, rc); + + /* + * Validate and read configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "InputDeviceID|OutputDeviceID", ""); + + char *pszTmp = NULL; + rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "InputDeviceID", &pszTmp); + if (RT_SUCCESS(rc)) + { + rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotify*/, pszTmp); + PDMDrvHlpMMHeapFree(pDrvIns, pszTmp); + } + else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'InputDeviceID'"); + + rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "OutputDeviceID", &pszTmp); + if (RT_SUCCESS(rc)) + { + rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotify*/, pszTmp); + PDMDrvHlpMMHeapFree(pDrvIns, pszTmp); + } + else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT) + return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'OutputDeviceID'"); + + /* + * Query the notification interface from the driver/device above us. + */ + pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT); + AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE); + +#ifdef CORE_AUDIO_WITH_WORKER_THREAD + /* + * Create worker thread for running callbacks on. + */ + CFMachPortContext PortCtx; + PortCtx.version = 0; + PortCtx.info = pThis; + PortCtx.retain = NULL; + PortCtx.release = NULL; + PortCtx.copyDescription = NULL; + pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHstAudCaThreadPortCallback, &PortCtx, NULL); + AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY); + + pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/); + AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY); + + rc = RTThreadCreateF(&pThis->hThread, drvHstAudCaThread, pThis, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance); + AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc); + + RTThreadUserWait(pThis->hThread, RT_MS_10SEC); + AssertLogRel(pThis->hThreadRunLoop); +#endif + +#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER + /* + * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy. + */ + rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHstAudCaBreakpointTimer, pThis); + AssertRCReturn(rc, rc); +#endif + + /* + * Determin the default devices. + */ + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotifty*/); + drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotifty*/); + + /* + * Register callbacks for default device input and output changes. + * (We just ignore failures here as there isn't much we can do about it, + * and it isn't 100% critical.) + */ + AudioObjectPropertyAddress PropAddr = + { + /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice, + /* .mScope = */ kAudioObjectPropertyScopeGlobal, + /* .mElement = */ kAudioObjectPropertyElementMaster + }; + + OSStatus orc; + orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis); + pThis->fRegisteredDefaultInputListener = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc)); + + PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis); + pThis->fRegisteredDefaultOutputListener = orc == noErr; + if ( orc != noErr + && orc != kAudioHardwareIllegalOperationError) + LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvHostCoreAudio = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "CoreAudio", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Core Audio host driver", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_AUDIO, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVHOSTCOREAUDIO), + /* pfnConstruct */ + drvHstAudCaConstruct, + /* pfnDestruct */ + drvHstAudCaDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + drvHstAudCaPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; |