VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostAudioCoreAudio.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 113.5 KB
Line 
1/* $Id: DrvHostAudioCoreAudio.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Host audio driver - Mac OS X CoreAudio.
4 *
5 * For relevant Apple documentation, here are some starters:
6 * - Core Audio Essentials
7 * https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/CoreAudioOverview/CoreAudioEssentials/CoreAudioEssentials.html
8 * - TN2097: Playing a sound file using the Default Output Audio Unit
9 * https://developer.apple.com/library/archive/technotes/tn2097/
10 * - TN2091: Device input using the HAL Output Audio Unit
11 * https://developer.apple.com/library/archive/technotes/tn2091/
12 * - Audio Component Services
13 * https://developer.apple.com/documentation/audiounit/audio_component_services?language=objc
14 * - QA1533: How to handle kAudioUnitProperty_MaximumFramesPerSlice
15 * https://developer.apple.com/library/archive/qa/qa1533/
16 * - QA1317: Signaling the end of data when using AudioConverterFillComplexBuffer
17 * https://developer.apple.com/library/archive/qa/qa1317/
18 */
19
20/*
21 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
22 *
23 * This file is part of VirtualBox base platform packages, as
24 * available from https://www.virtualbox.org.
25 *
26 * This program is free software; you can redistribute it and/or
27 * modify it under the terms of the GNU General Public License
28 * as published by the Free Software Foundation, in version 3 of the
29 * License.
30 *
31 * This program is distributed in the hope that it will be useful, but
32 * WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
34 * General Public License for more details.
35 *
36 * You should have received a copy of the GNU General Public License
37 * along with this program; if not, see <https://www.gnu.org/licenses>.
38 *
39 * SPDX-License-Identifier: GPL-3.0-only
40 */
41
42
43/*********************************************************************************************************************************
44* Header Files *
45*********************************************************************************************************************************/
46#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
47#include <VBox/log.h>
48#include <VBox/vmm/pdmaudioinline.h>
49#include <VBox/vmm/pdmaudiohostenuminline.h>
50
51#include "VBoxDD.h"
52
53#include <iprt/asm.h>
54#include <iprt/cdefs.h>
55#include <iprt/circbuf.h>
56#include <iprt/mem.h>
57#include <iprt/uuid.h>
58#include <iprt/timer.h>
59
60#include <CoreAudio/CoreAudio.h>
61#include <CoreServices/CoreServices.h>
62#include <AudioToolbox/AudioQueue.h>
63#include <AudioUnit/AudioUnit.h>
64
65
66RT_GCC_NO_WARN_DEPRECATED_BEGIN /* Much here is deprecated since 12.0 */
67
68#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 /* possibly 1080 */
69# define kAudioHardwarePropertyTranslateUIDToDevice (AudioObjectPropertySelector)'uidd'
70#endif
71
72
73/*********************************************************************************************************************************
74* Defined Constants And Macros *
75*********************************************************************************************************************************/
76/** The max number of queue buffers we'll use. */
77#define COREAUDIO_MAX_BUFFERS 1024
78/** The minimum number of queue buffers. */
79#define COREAUDIO_MIN_BUFFERS 4
80
81/** Enables the worker thread.
82 * This saves CoreAudio from creating an additional thread upon queue
83 * creation. (It does not help with the slow AudioQueueDispose fun.) */
84#define CORE_AUDIO_WITH_WORKER_THREAD
85#if 0
86/** Enables the AudioQueueDispose breakpoint timer (debugging help). */
87# define CORE_AUDIO_WITH_BREAKPOINT_TIMER
88#endif
89
90
91/*********************************************************************************************************************************
92* Structures and Typedefs *
93*********************************************************************************************************************************/
94/** Pointer to the instance data for a Core Audio driver instance. */
95typedef struct DRVHOSTCOREAUDIO *PDRVHOSTCOREAUDIO;
96/** Pointer to the Core Audio specific backend data for an audio stream. */
97typedef struct COREAUDIOSTREAM *PCOREAUDIOSTREAM;
98
99/**
100 * Core Audio device entry (enumeration).
101 *
102 * @note This is definitely not safe to just copy!
103 */
104typedef struct COREAUDIODEVICEDATA
105{
106 /** The core PDM structure. */
107 PDMAUDIOHOSTDEV Core;
108
109 /** The audio device ID of the currently used device (UInt32 typedef). */
110 AudioDeviceID idDevice;
111} COREAUDIODEVICEDATA;
112/** Pointer to a Core Audio device entry (enumeration). */
113typedef COREAUDIODEVICEDATA *PCOREAUDIODEVICEDATA;
114
115
116/**
117 * Audio device information.
118 *
119 * We do not use COREAUDIODEVICEDATA here as it contains lots more than what we
120 * need and care to query. We also don't want to depend on DrvAudio making
121 * PDMIHOSTAUDIO::pfnGetDevices callbacks to keep this information up to date.
122 */
123typedef struct DRVHSTAUDCADEVICE
124{
125 /** The audio device ID. kAudioDeviceUnknown if not available. */
126 AudioObjectID idDevice;
127 /** Indicates whether we've registered device change listener. */
128 bool fRegisteredListeners;
129 /** The UID string (must release). NULL if not available. */
130 CFStringRef hStrUid;
131 /** The UID string for a specific device, NULL if we're using the default device. */
132 char *pszSpecific;
133} DRVHSTAUDCADEVICE;
134/** Pointer to info about a default device. */
135typedef DRVHSTAUDCADEVICE *PDRVHSTAUDCADEVICE;
136
137
138/**
139 * Core Audio stream state.
140 */
141typedef enum COREAUDIOINITSTATE
142{
143 /** The device is uninitialized. */
144 COREAUDIOINITSTATE_UNINIT = 0,
145 /** The device is currently initializing. */
146 COREAUDIOINITSTATE_IN_INIT,
147 /** The device is initialized. */
148 COREAUDIOINITSTATE_INIT,
149 /** The device is currently uninitializing. */
150 COREAUDIOINITSTATE_IN_UNINIT,
151 /** The usual 32-bit hack. */
152 COREAUDIOINITSTATE_32BIT_HACK = 0x7fffffff
153} COREAUDIOINITSTATE;
154
155
156/**
157 * Core audio buffer tracker.
158 *
159 * For output buffer we'll be using AudioQueueBuffer::mAudioDataByteSize to
160 * track how much we've written. When a buffer is full, or if we run low on
161 * queued bufferes, it will be queued.
162 *
163 * For input buffer we'll be using offRead to track how much we've read.
164 *
165 * The queued/not-queued state is stored in the first bit of
166 * AudioQueueBuffer::mUserData. While bits 8 and up holds the index into
167 * COREAUDIOSTREAM::paBuffers.
168 */
169typedef struct COREAUDIOBUF
170{
171 /** The buffer. */
172 AudioQueueBufferRef pBuf;
173 /** The buffer read offset (input only). */
174 uint32_t offRead;
175} COREAUDIOBUF;
176/** Pointer to a core audio buffer tracker. */
177typedef COREAUDIOBUF *PCOREAUDIOBUF;
178
179
180/**
181 * Core Audio specific data for an audio stream.
182 */
183typedef struct COREAUDIOSTREAM
184{
185 /** Common part. */
186 PDMAUDIOBACKENDSTREAM Core;
187
188 /** The stream's acquired configuration. */
189 PDMAUDIOSTREAMCFG Cfg;
190 /** List node for the device's stream list. */
191 RTLISTNODE Node;
192 /** The acquired (final) audio format for this stream.
193 * @note This what the device requests, we don't alter anything. */
194 AudioStreamBasicDescription BasicStreamDesc;
195 /** The actual audio queue being used. */
196 AudioQueueRef hAudioQueue;
197
198 /** Number of buffers. */
199 uint32_t cBuffers;
200 /** The array of buffer. */
201 PCOREAUDIOBUF paBuffers;
202
203 /** Initialization status tracker, actually COREAUDIOINITSTATE.
204 * Used when some of the device parameters or the device itself is changed
205 * during the runtime. */
206 volatile uint32_t enmInitState;
207 /** The current buffer being written to / read from. */
208 uint32_t idxBuffer;
209 /** Set if the stream is enabled. */
210 bool fEnabled;
211 /** Set if the stream is started (playing/capturing). */
212 bool fStarted;
213 /** Set if the stream is draining (output only). */
214 bool fDraining;
215 /** Set if we should restart the stream on resume (saved pause state). */
216 bool fRestartOnResume;
217// /** Set if we're switching to a new output/input device. */
218// bool fSwitchingDevice;
219 /** Internal stream offset (bytes). */
220 uint64_t offInternal;
221 /** The RTTimeMilliTS() at the end of the last transfer. */
222 uint64_t msLastTransfer;
223
224 /** Critical section for serializing access between thread + callbacks. */
225 RTCRITSECT CritSect;
226 /** Buffer that drvHstAudCaStreamStatusString uses. */
227 char szStatus[64];
228} COREAUDIOSTREAM;
229
230
231/**
232 * Instance data for a Core Audio host audio driver.
233 *
234 * @implements PDMIAUDIOCONNECTOR
235 */
236typedef struct DRVHOSTCOREAUDIO
237{
238 /** Pointer to the driver instance structure. */
239 PPDMDRVINS pDrvIns;
240 /** Pointer to host audio interface. */
241 PDMIHOSTAUDIO IHostAudio;
242 /** The input device. */
243 DRVHSTAUDCADEVICE InputDevice;
244 /** The output device. */
245 DRVHSTAUDCADEVICE OutputDevice;
246 /** Upwards notification interface. */
247 PPDMIHOSTAUDIOPORT pIHostAudioPort;
248 /** Indicates whether we've registered default input device change listener. */
249 bool fRegisteredDefaultInputListener;
250 /** Indicates whether we've registered default output device change listener. */
251 bool fRegisteredDefaultOutputListener;
252
253#ifdef CORE_AUDIO_WITH_WORKER_THREAD
254 /** @name Worker Thread For Queue callbacks and stuff.
255 * @{ */
256 /** The worker thread. */
257 RTTHREAD hThread;
258 /** The runloop of the worker thread. */
259 CFRunLoopRef hThreadRunLoop;
260 /** The message port we use to talk to the thread.
261 * @note While we don't currently use the port, it is necessary to prevent
262 * the thread from spinning or stopping prematurely because of
263 * CFRunLoopRunInMode returning kCFRunLoopRunFinished. */
264 CFMachPortRef hThreadPort;
265 /** Runloop source for hThreadPort. */
266 CFRunLoopSourceRef hThreadPortSrc;
267 /** @} */
268#endif
269
270 /** Critical section to serialize access. */
271 RTCRITSECT CritSect;
272#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
273 /** Timder for debugging AudioQueueDispose slowness. */
274 RTTIMERLR hBreakpointTimer;
275#endif
276} DRVHOSTCOREAUDIO;
277
278
279/*********************************************************************************************************************************
280* Internal Functions *
281*********************************************************************************************************************************/
282static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify);
283
284/* DrvHostAudioCoreAudioAuth.mm: */
285DECLHIDDEN(int) coreAudioInputPermissionCheck(void);
286
287
288#ifdef LOG_ENABLED
289/**
290 * Gets the stream status.
291 *
292 * @returns Pointer to stream status string.
293 * @param pStreamCA The stream to get the status for.
294 */
295static const char *drvHstAudCaStreamStatusString(PCOREAUDIOSTREAM pStreamCA)
296{
297 static RTSTRTUPLE const s_aInitState[5] =
298 {
299 { RT_STR_TUPLE("UNINIT") },
300 { RT_STR_TUPLE("IN_INIT") },
301 { RT_STR_TUPLE("INIT") },
302 { RT_STR_TUPLE("IN_UNINIT") },
303 { RT_STR_TUPLE("BAD") },
304 };
305 uint32_t enmInitState = pStreamCA->enmInitState;
306 PCRTSTRTUPLE pTuple = &s_aInitState[RT_MIN(enmInitState, RT_ELEMENTS(s_aInitState) - 1)];
307 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
308 size_t off = pTuple->cch;
309
310 static RTSTRTUPLE const s_aEnable[2] =
311 {
312 { RT_STR_TUPLE("DISABLED") },
313 { RT_STR_TUPLE("ENABLED ") },
314 };
315 pTuple = &s_aEnable[pStreamCA->fEnabled];
316 memcpy(pStreamCA->szStatus, pTuple->psz, pTuple->cch);
317 off += pTuple->cch;
318
319 static RTSTRTUPLE const s_aStarted[2] =
320 {
321 { RT_STR_TUPLE(" STOPPED") },
322 { RT_STR_TUPLE(" STARTED") },
323 };
324 pTuple = &s_aStarted[pStreamCA->fStarted];
325 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
326 off += pTuple->cch;
327
328 static RTSTRTUPLE const s_aDraining[2] =
329 {
330 { RT_STR_TUPLE("") },
331 { RT_STR_TUPLE(" DRAINING") },
332 };
333 pTuple = &s_aDraining[pStreamCA->fDraining];
334 memcpy(&pStreamCA->szStatus[off], pTuple->psz, pTuple->cch);
335 off += pTuple->cch;
336
337 Assert(off < sizeof(pStreamCA->szStatus));
338 pStreamCA->szStatus[off] = '\0';
339 return pStreamCA->szStatus;
340}
341#endif /*LOG_ENABLED*/
342
343
344
345
346#if 0 /* unused */
347static int drvHstAudCaCFStringToCString(const CFStringRef pCFString, char **ppszString)
348{
349 CFIndex cLen = CFStringGetLength(pCFString) + 1;
350 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
351 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
352 {
353 RTMemFree(pszResult);
354 return VERR_NOT_FOUND;
355 }
356
357 *ppszString = pszResult;
358 return VINF_SUCCESS;
359}
360
361static AudioDeviceID drvHstAudCaDeviceUIDtoID(const char* pszUID)
362{
363 /* Create a CFString out of our CString. */
364 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
365
366 /* Fill the translation structure. */
367 AudioDeviceID deviceID;
368
369 AudioValueTranslation translation;
370 translation.mInputData = &strUID;
371 translation.mInputDataSize = sizeof(CFStringRef);
372 translation.mOutputData = &deviceID;
373 translation.mOutputDataSize = sizeof(AudioDeviceID);
374
375 /* Fetch the translation from the UID to the device ID. */
376 AudioObjectPropertyAddress PropAddr =
377 {
378 kAudioHardwarePropertyDeviceForUID,
379 kAudioObjectPropertyScopeGlobal,
380 kAudioObjectPropertyElementMaster
381 };
382
383 UInt32 uSize = sizeof(AudioValueTranslation);
384 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr, 0, NULL, &uSize, &translation);
385
386 /* Release the temporary CFString */
387 CFRelease(strUID);
388
389 if (RT_LIKELY(err == noErr))
390 return deviceID;
391
392 /* Return the unknown device on error. */
393 return kAudioDeviceUnknown;
394}
395#endif /* unused */
396
397
398/**
399 * Wrapper around AudioObjectGetPropertyData and AudioObjectGetPropertyDataSize.
400 *
401 * @returns Pointer to temp heap allocation with the data on success, free using
402 * RTMemTmpFree. NULL on failure, fully logged.
403 */
404static void *drvHstAudCaGetPropertyDataEx(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
405 AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
406 const char *pszWhat, UInt32 *pcb)
407{
408 AudioObjectPropertyAddress const PropAddr =
409 {
410 /*.mSelector = */ enmSelector,
411 /*.mScope = */ enmScope,
412 /*.mElement = */ enmElement
413 };
414
415 /*
416 * Have to retry here in case the size isn't stable (like if a new device/whatever is added).
417 */
418 for (uint32_t iTry = 0; ; iTry++)
419 {
420 UInt32 cb = 0;
421 OSStatus orc = AudioObjectGetPropertyDataSize(idObject, &PropAddr, 0, NULL, &cb);
422 if (orc == noErr)
423 {
424 cb = RT_MAX(cb, 1); /* we're allergic to zero allocations. */
425 void *pv = RTMemTmpAllocZ(cb);
426 if (pv)
427 {
428 orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
429 if (orc == noErr)
430 {
431 Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n",
432 idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
433 if (pcb)
434 *pcb = cb;
435 return pv;
436 }
437
438 RTMemTmpFree(pv);
439 LogFunc(("AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) -> %#x, iTry=%d\n",
440 idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc, iTry));
441 if (iTry < 3)
442 continue;
443 LogRelMax(32, ("CoreAudio: AudioObjectGetPropertyData(%u/%#x/%#x/%x/%s, cb=%#x) failed: %#x\n",
444 idObject, enmSelector, enmScope, enmElement, pszWhat, cb, orc));
445 }
446 else
447 LogRelMax(32, ("CoreAudio: Failed to allocate %#x bytes (to get %s for %s).\n", cb, pszWhat, idObject));
448 }
449 else
450 LogRelMax(32, ("CoreAudio: Failed to get %s for %u: %#x\n", pszWhat, idObject, orc));
451 if (pcb)
452 *pcb = 0;
453 return NULL;
454 }
455}
456
457
458/**
459 * Wrapper around AudioObjectGetPropertyData.
460 *
461 * @returns Success indicator. Failures (@c false) are fully logged.
462 */
463static bool drvHstAudCaGetPropertyData(AudioObjectID idObject, AudioObjectPropertySelector enmSelector,
464 AudioObjectPropertyScope enmScope, AudioObjectPropertyElement enmElement,
465 const char *pszWhat, void *pv, UInt32 cb)
466{
467 AudioObjectPropertyAddress const PropAddr =
468 {
469 /*.mSelector = */ enmSelector,
470 /*.mScope = */ enmScope,
471 /*.mElement = */ enmElement
472 };
473
474 OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, pv);
475 if (orc == noErr)
476 {
477 Log9Func(("%u/%#x/%#x/%x/%s: returning %p LB %#x\n", idObject, enmSelector, enmScope, enmElement, pszWhat, pv, cb));
478 return true;
479 }
480 LogRelMax(64, ("CoreAudio: Failed to query %s (%u/%#x/%#x/%x, cb=%#x): %#x\n",
481 pszWhat, idObject, enmSelector, enmScope, enmElement, cb, orc));
482 return false;
483}
484
485
486/**
487 * Count the number of channels in one direction.
488 *
489 * @returns Channel count.
490 */
491static uint32_t drvHstAudCaEnumCountChannels(AudioObjectID idObject, AudioObjectPropertyScope enmScope)
492{
493 uint32_t cChannels = 0;
494
495 AudioBufferList *pBufs
496 = (AudioBufferList *)drvHstAudCaGetPropertyDataEx(idObject, kAudioDevicePropertyStreamConfiguration,
497 enmScope, kAudioObjectPropertyElementMaster, "stream config", NULL);
498 if (pBufs)
499 {
500 UInt32 idxBuf = pBufs->mNumberBuffers;
501 while (idxBuf-- > 0)
502 {
503 Log9Func(("%u/%#x[%u]: %u\n", idObject, enmScope, idxBuf, pBufs->mBuffers[idxBuf].mNumberChannels));
504 cChannels += pBufs->mBuffers[idxBuf].mNumberChannels;
505 }
506
507 RTMemTmpFree(pBufs);
508 }
509
510 return cChannels;
511}
512
513
514/**
515 * Translates a UID to an audio device ID.
516 *
517 * @returns Audio device ID on success, kAudioDeviceUnknown on failure.
518 * @param hStrUid The UID string to convert.
519 * @param pszUid The C-string vresion of @a hStrUid.
520 * @param pszWhat What we're converting (for logging).
521 */
522static AudioObjectID drvHstAudCaDeviceUidToId(CFStringRef hStrUid, const char *pszUid, const char *pszWhat)
523{
524 AudioObjectPropertyAddress const PropAddr =
525 {
526 /*.mSelector = */ kAudioHardwarePropertyTranslateUIDToDevice,
527 /*.mScope = */ kAudioObjectPropertyScopeGlobal,
528 /*.mElement = */ kAudioObjectPropertyElementMaster
529 };
530 AudioObjectID idDevice = 0;
531 UInt32 cb = sizeof(idDevice);
532 OSStatus orc = AudioObjectGetPropertyData(kAudioObjectSystemObject, &PropAddr,
533 sizeof(hStrUid), &hStrUid, &cb, &idDevice);
534 if (orc == noErr)
535 {
536 Log9Func(("%s device UID '%s' -> %RU32\n", pszWhat, pszUid, idDevice));
537 return idDevice;
538 }
539 /** @todo test on < 10.9, see which status code and do a fallback using the
540 * enumeration code. */
541 LogRelMax(64, ("CoreAudio: Failed to translate %s device UID '%s' to audio device ID: %#x\n", pszWhat, pszUid, orc));
542 return kAudioDeviceUnknown;
543}
544
545
546/**
547 * Copies a CFString to a buffer (UTF-8).
548 *
549 * @returns VBox status code. In the case of a buffer overflow, the buffer will
550 * contain data and be correctly terminated (provided @a cbDst is not
551 * zero.)
552 */
553static int drvHstAudCaCFStringToBuf(CFStringRef hStr, char *pszDst, size_t cbDst)
554{
555 AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW);
556
557 if (CFStringGetCString(hStr, pszDst, cbDst, kCFStringEncodingUTF8))
558 return VINF_SUCCESS;
559
560 /* First fallback: */
561 const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
562 if (pszSrc)
563 return RTStrCopy(pszDst, cbDst, pszSrc);
564
565 /* Second fallback: */
566 CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
567 AssertReturn(cbMax > 0, VERR_INVALID_UTF8_ENCODING);
568 AssertReturn(cbMax < (CFIndex)_16M, VERR_OUT_OF_RANGE);
569
570 char *pszTmp = (char *)RTMemTmpAlloc(cbMax);
571 AssertReturn(pszTmp, VERR_NO_TMP_MEMORY);
572
573 int rc;
574 if (CFStringGetCString(hStr, pszTmp, cbMax, kCFStringEncodingUTF8))
575 rc = RTStrCopy(pszDst, cbDst, pszTmp);
576 else
577 {
578 *pszDst = '\0';
579 rc = VERR_INVALID_UTF8_ENCODING;
580 }
581
582 RTMemTmpFree(pszTmp);
583 return rc;
584}
585
586
587/**
588 * Copies a CFString to a heap buffer (UTF-8).
589 *
590 * @returns Pointer to the heap buffer on success, NULL if out of heap or some
591 * conversion/extraction problem
592 */
593static char *drvHstAudCaCFStringToHeap(CFStringRef hStr)
594{
595 const char *pszSrc = CFStringGetCStringPtr(hStr, kCFStringEncodingUTF8);
596 if (pszSrc)
597 return RTStrDup(pszSrc);
598
599 /* Fallback: */
600 CFIndex cbMax = CFStringGetMaximumSizeForEncoding(CFStringGetLength(hStr), kCFStringEncodingUTF8) + 1;
601 AssertReturn(cbMax > 0, NULL);
602 AssertReturn(cbMax < (CFIndex)_16M, NULL);
603
604 char *pszDst = RTStrAlloc(cbMax);
605 if (pszDst)
606 {
607 AssertReturnStmt(CFStringGetCString(hStr, pszDst, cbMax, kCFStringEncodingUTF8), RTStrFree(pszDst), NULL);
608 size_t const cchDst = strlen(pszDst);
609 if (cbMax - cchDst > 32)
610 RTStrRealloc(&pszDst, cchDst + 1);
611 }
612 return pszDst;
613}
614
615
616/*********************************************************************************************************************************
617* Device Change Notification Callbacks *
618*********************************************************************************************************************************/
619
620#ifdef LOG_ENABLED
621/**
622 * Called when the kAudioDevicePropertyNominalSampleRate or
623 * kAudioDeviceProcessorOverload properties changes on a default device.
624 *
625 * Registered on default devices after device enumeration.
626 * Not sure on which thread/runloop this runs.
627 *
628 * (See AudioObjectPropertyListenerProc in the SDK headers.)
629 */
630static OSStatus drvHstAudCaDevicePropertyChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
631 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
632{
633 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u pvUser=%p\n", idObject, idObject, cAddresses, pvUser));
634 for (UInt32 idx = 0; idx < cAddresses; idx++)
635 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
636 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
637
638/** @todo r=bird: What's the plan here exactly? I've changed it to
639 * LOG_ENABLED only for now, as this has no other purpose. */
640 switch (idObject)
641 {
642 case kAudioDeviceProcessorOverload:
643 LogFunc(("Processor overload detected!\n"));
644 break;
645 case kAudioDevicePropertyNominalSampleRate:
646 LogFunc(("kAudioDevicePropertyNominalSampleRate!\n"));
647 break;
648 default:
649 /* Just skip. */
650 break;
651 }
652
653 return noErr;
654}
655#endif /* LOG_ENABLED */
656
657
658/**
659 * Called when the kAudioDevicePropertyDeviceIsAlive property changes on a
660 * default device.
661 *
662 * The purpose is mainly to log the event. There isn't much we can do about
663 * active streams or future one, other than waiting for a default device change
664 * notification callback. In the mean time, active streams should start failing
665 * to work and new ones fail on creation. This is the same for when we're
666 * configure to use specific devices, only we don't get any device change
667 * callback like for default ones.
668 *
669 * Not sure on which thread/runloop this runs.
670 *
671 * (See AudioObjectPropertyListenerProc in the SDK headers.)
672 */
673static OSStatus drvHstAudCaDeviceIsAliveChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
674 const AudioObjectPropertyAddress paAddresses[], void *pvUser)
675{
676 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
677 AssertPtr(pThis);
678 RT_NOREF(cAddresses, paAddresses);
679
680 /*
681 * Log everything.
682 */
683 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
684 for (UInt32 idx = 0; idx < cAddresses; idx++)
685 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
686 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
687
688 /*
689 * Check which devices are affected.
690 */
691 int rc = RTCritSectEnter(&pThis->CritSect);
692 AssertRCReturn(rc, noErr); /* could be a destruction race */
693
694 for (unsigned i = 0; i < 2; i++)
695 {
696 if (idObject == (i == 0 ? pThis->InputDevice.idDevice : pThis->OutputDevice.idDevice))
697 {
698 AudioObjectPropertyAddress const PropAddr =
699 {
700 kAudioDevicePropertyDeviceIsAlive,
701 i == 0 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
702 kAudioObjectPropertyElementMaster
703 };
704 UInt32 fAlive = 0;
705 UInt32 cb = sizeof(fAlive);
706 OSStatus orc = AudioObjectGetPropertyData(idObject, &PropAddr, 0, NULL, &cb, &fAlive);
707 if ( orc == kAudioHardwareBadDeviceError
708 || (orc == noErr && !fAlive))
709 {
710 LogRel(("CoreAudio: The default %s device (%u) stopped functioning.\n", idObject, i == 0 ? "input" : "output"));
711#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
712 default device change callback doesn't arrive. So, don't do it! (bird) */
713 PPDMIHOSTAUDIOPORT pIHostAudioPort = pThis->pIHostAudioPort;
714 if (pIHostAudioPort)
715 {
716 RTCritSectLeave(&pThis->CritSect);
717
718 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, i == 0 ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL);
719
720 rc = RTCritSectEnter(&pThis->CritSect);
721 AssertRCReturn(rc, noErr); /* could be a destruction race */
722 }
723#endif
724 }
725 }
726 }
727
728 RTCritSectLeave(&pThis->CritSect);
729 return noErr;
730}
731
732
733/**
734 * Called when the default recording or playback device has changed.
735 *
736 * Registered by the constructor. Not sure on which thread/runloop this runs.
737 *
738 * (See AudioObjectPropertyListenerProc in the SDK headers.)
739 */
740static OSStatus drvHstAudCaDefaultDeviceChangedCallback(AudioObjectID idObject, UInt32 cAddresses,
741 const AudioObjectPropertyAddress *paAddresses, void *pvUser)
742
743{
744 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
745 AssertPtr(pThis);
746 RT_NOREF(idObject, cAddresses, paAddresses);
747
748 /*
749 * Log everything.
750 */
751 LogFlowFunc(("idObject=%#x (%u) cAddresses=%u\n", idObject, idObject, cAddresses));
752 for (UInt32 idx = 0; idx < cAddresses; idx++)
753 LogFlowFunc((" #%u: sel=%#x scope=%#x element=%#x\n",
754 idx, paAddresses[idx].mSelector, paAddresses[idx].mScope, paAddresses[idx].mElement));
755
756 /*
757 * Update the default devices and notify parent driver if anything actually changed.
758 */
759 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/);
760 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/);
761
762 return noErr;
763}
764
765
766/**
767 * Registers callbacks for a specific Core Audio device.
768 *
769 * @returns true if idDevice isn't kAudioDeviceUnknown and callbacks were
770 * registered, otherwise false.
771 * @param pThis The core audio driver instance data.
772 * @param idDevice The device ID to deregister callbacks for.
773 */
774static bool drvHstAudCaDeviceRegisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
775{
776 if (idDevice != kAudioDeviceUnknown)
777 {
778 LogFunc(("idDevice=%RU32\n", idDevice));
779 AudioObjectPropertyAddress PropAddr =
780 {
781 kAudioDevicePropertyDeviceIsAlive,
782 kAudioObjectPropertyScopeGlobal,
783 kAudioObjectPropertyElementMaster
784 };
785 OSStatus orc;
786 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
787 unsigned cRegistrations = orc == noErr;
788 if ( orc != noErr
789 && orc != kAudioHardwareIllegalOperationError)
790 LogRel(("CoreAudio: Failed to add the recording device state changed listener (%#x)\n", orc));
791
792#ifdef LOG_ENABLED
793 PropAddr.mSelector = kAudioDeviceProcessorOverload;
794 PropAddr.mScope = kAudioUnitScope_Global;
795 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
796 cRegistrations += orc == noErr;
797 if (orc != noErr)
798 LogRel(("CoreAudio: Failed to register processor overload listener (%#x)\n", orc));
799
800 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
801 PropAddr.mScope = kAudioUnitScope_Global;
802 orc = AudioObjectAddPropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
803 cRegistrations += orc == noErr;
804 if (orc != noErr)
805 LogRel(("CoreAudio: Failed to register sample rate changed listener (%#x)\n", orc));
806#endif
807 return cRegistrations > 0;
808 }
809 return false;
810}
811
812
813/**
814 * Undoes what drvHstAudCaDeviceRegisterCallbacks() did.
815 *
816 * @param pThis The core audio driver instance data.
817 * @param idDevice The device ID to deregister callbacks for.
818 */
819static void drvHstAudCaDeviceUnregisterCallbacks(PDRVHOSTCOREAUDIO pThis, AudioObjectID idDevice)
820{
821 if (idDevice != kAudioDeviceUnknown)
822 {
823 LogFunc(("idDevice=%RU32\n", idDevice));
824 AudioObjectPropertyAddress PropAddr =
825 {
826 kAudioDevicePropertyDeviceIsAlive,
827 kAudioObjectPropertyScopeGlobal,
828 kAudioObjectPropertyElementMaster
829 };
830 OSStatus orc;
831 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDeviceIsAliveChangedCallback, pThis);
832 if ( orc != noErr
833 && orc != kAudioHardwareBadObjectError)
834 LogRel(("CoreAudio: Failed to remove the device alive listener (%#x)\n", orc));
835
836#ifdef LOG_ENABLED
837 PropAddr.mSelector = kAudioDeviceProcessorOverload;
838 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
839 if ( orc != noErr
840 && orc != kAudioHardwareBadObjectError)
841 LogRel(("CoreAudio: Failed to remove the recording processor overload listener (%#x)\n", orc));
842
843 PropAddr.mSelector = kAudioDevicePropertyNominalSampleRate;
844 orc = AudioObjectRemovePropertyListener(idDevice, &PropAddr, drvHstAudCaDevicePropertyChangedCallback, pThis);
845 if ( orc != noErr
846 && orc != kAudioHardwareBadObjectError)
847 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%#x)\n", orc));
848#endif
849 }
850}
851
852
853/**
854 * Updates the default device for one direction.
855 *
856 * @param pThis The core audio driver instance data.
857 * @param pDevice The device information to update.
858 * @param fInput Set if input device, clear if output.
859 * @param fNotify Whether to notify the parent driver if something
860 * changed.
861 */
862static void drvHstAudCaUpdateOneDefaultDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify)
863{
864 /*
865 * Skip if there is a specific device we should use for this direction.
866 */
867 if (pDevice->pszSpecific)
868 return;
869
870 /*
871 * Get the information before we enter the critical section.
872 *
873 * (Yeah, this may make us get things wrong if the defaults changes really
874 * fast and we get notifications in parallel on multiple threads. However,
875 * the first is a don't-do-that situation and the latter is unlikely.)
876 */
877 AudioDeviceID idDefaultDev = kAudioDeviceUnknown;
878 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject,
879 fInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice,
880 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
881 fInput ? "default input device" : "default output device",
882 &idDefaultDev, sizeof(idDefaultDev)))
883 idDefaultDev = kAudioDeviceUnknown;
884
885 CFStringRef hStrUid = NULL;
886 if (idDefaultDev != kAudioDeviceUnknown)
887 {
888 if (!drvHstAudCaGetPropertyData(idDefaultDev, kAudioDevicePropertyDeviceUID,
889 fInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
890 kAudioObjectPropertyElementMaster,
891 fInput ? "default input device UID" : "default output device UID",
892 &hStrUid, sizeof(hStrUid)))
893 hStrUid = NULL;
894 }
895 char szUid[128];
896 if (hStrUid)
897 drvHstAudCaCFStringToBuf(hStrUid, szUid, sizeof(szUid));
898 else
899 szUid[0] = '\0';
900
901 /*
902 * Grab the lock and do the updating.
903 *
904 * We're a little paranoid wrt the locking in case there turn out to be some kind
905 * of race around destruction (there really can't be, but better play safe).
906 */
907 PPDMIHOSTAUDIOPORT pIHostAudioPort = NULL;
908
909 int rc = RTCritSectEnter(&pThis->CritSect);
910 AssertRC(rc);
911 if (RT_SUCCESS(rc))
912 {
913 if (idDefaultDev != pDevice->idDevice)
914 {
915 if (idDefaultDev != kAudioDeviceUnknown)
916 {
917 LogRel(("CoreAudio: Default %s device: %u (was %u), ID '%s'\n",
918 fInput ? "input" : "output", idDefaultDev, pDevice->idDevice, szUid));
919 pIHostAudioPort = fNotify ? pThis->pIHostAudioPort : NULL; /* (only if there is a new device) */
920 }
921 else
922 LogRel(("CoreAudio: Default %s device is gone (was %u)\n", fInput ? "input" : "output", pDevice->idDevice));
923
924 if (pDevice->hStrUid)
925 CFRelease(pDevice->hStrUid);
926 if (pDevice->fRegisteredListeners)
927 drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
928 pDevice->hStrUid = hStrUid;
929 pDevice->idDevice = idDefaultDev;
930 pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
931 hStrUid = NULL;
932 }
933 RTCritSectLeave(&pThis->CritSect);
934 }
935
936 if (hStrUid != NULL)
937 CFRelease(hStrUid);
938
939 /*
940 * Notify parent driver to trigger a re-init of any associated streams.
941 */
942 if (pIHostAudioPort)
943 {
944 LogFlowFunc(("Notifying parent driver about %s default device change...\n", fInput ? "input" : "output"));
945 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
946 }
947}
948
949
950/**
951 * Sets the device to use in one or the other direction (@a fInput).
952 *
953 * @returns VBox status code.
954 * @param pThis The core audio driver instance data.
955 * @param pDevice The device info structure to update.
956 * @param fInput Set if input, clear if output.
957 * @param fNotify Whether to notify the parent driver if something
958 * changed.
959 * @param pszUid The UID string for the device to use. NULL or empty
960 * string if default should be used.
961 */
962static int drvHstAudCaSetDevice(PDRVHOSTCOREAUDIO pThis, PDRVHSTAUDCADEVICE pDevice, bool fInput, bool fNotify,
963 const char *pszUid)
964{
965 if (!pszUid || !*pszUid)
966 {
967 /*
968 * Use default. Always refresh the given default device.
969 */
970 int rc = RTCritSectEnter(&pThis->CritSect);
971 AssertRCReturn(rc, rc);
972
973 if (pDevice->pszSpecific)
974 {
975 LogRel(("CoreAudio: Changing %s device from '%s' to default.\n", fInput ? "input" : "output", pDevice->pszSpecific));
976 RTStrFree(pDevice->pszSpecific);
977 pDevice->pszSpecific = NULL;
978 }
979
980 RTCritSectLeave(&pThis->CritSect);
981
982 drvHstAudCaUpdateOneDefaultDevice(pThis, pDevice, fInput, fNotify);
983 }
984 else
985 {
986 /*
987 * Use device specified by pszUid. If not change, search for the device
988 * again if idDevice is unknown.
989 */
990 int rc = RTCritSectEnter(&pThis->CritSect);
991 AssertRCReturn(rc, rc);
992
993 bool fSkip = false;
994 bool fSame = false;
995 if (pDevice->pszSpecific)
996 {
997 if (strcmp(pszUid, pDevice->pszSpecific) != 0)
998 {
999 LogRel(("CoreAudio: Changing %s device from '%s' to '%s'.\n",
1000 fInput ? "input" : "output", pDevice->pszSpecific, pszUid));
1001 RTStrFree(pDevice->pszSpecific);
1002 pDevice->pszSpecific = NULL;
1003 }
1004 else
1005 {
1006 fSkip = pDevice->idDevice != kAudioDeviceUnknown;
1007 fSame = true;
1008 }
1009 }
1010 else
1011 LogRel(("CoreAudio: Changing %s device from default to '%s'.\n", fInput ? "input" : "output", pszUid));
1012
1013 /*
1014 * Allocate and swap the strings. This is the bit that might fail.
1015 */
1016 if (!fSame)
1017 {
1018 CFStringRef hStrUid = CFStringCreateWithBytes(NULL /*allocator*/, (UInt8 const *)pszUid, (CFIndex)strlen(pszUid),
1019 kCFStringEncodingUTF8, false /*isExternalRepresentation*/);
1020 char *pszSpecific = RTStrDup(pszUid);
1021 if (hStrUid && pszSpecific)
1022 {
1023 if (pDevice->hStrUid)
1024 CFRelease(pDevice->hStrUid);
1025 pDevice->hStrUid = hStrUid;
1026 RTStrFree(pDevice->pszSpecific);
1027 pDevice->pszSpecific = pszSpecific;
1028 }
1029 else
1030 {
1031 RTCritSectLeave(&pThis->CritSect);
1032
1033 LogFunc(("returns VERR_NO_STR_MEMORY!\n"));
1034 if (hStrUid)
1035 CFRelease(hStrUid);
1036 RTStrFree(pszSpecific);
1037 return VERR_NO_STR_MEMORY;
1038 }
1039
1040 if (pDevice->fRegisteredListeners)
1041 {
1042 drvHstAudCaDeviceUnregisterCallbacks(pThis, pDevice->idDevice);
1043 pDevice->fRegisteredListeners = false;
1044 }
1045 }
1046
1047 /*
1048 * Locate the device ID corresponding to the UID string.
1049 */
1050 if (!fSkip)
1051 {
1052 pDevice->idDevice = drvHstAudCaDeviceUidToId(pDevice->hStrUid, pszUid, fInput ? "input" : "output");
1053 pDevice->fRegisteredListeners = drvHstAudCaDeviceRegisterCallbacks(pThis, pDevice->idDevice);
1054 }
1055
1056 PPDMIHOSTAUDIOPORT pIHostAudioPort = fNotify && !fSame ? pThis->pIHostAudioPort : NULL;
1057 RTCritSectLeave(&pThis->CritSect);
1058
1059 /*
1060 * Notify parent driver to trigger a re-init of any associated streams.
1061 */
1062 if (pIHostAudioPort)
1063 {
1064 LogFlowFunc(("Notifying parent driver about %s device change...\n", fInput ? "input" : "output"));
1065 pIHostAudioPort->pfnNotifyDeviceChanged(pIHostAudioPort, fInput ? PDMAUDIODIR_IN : PDMAUDIODIR_OUT, NULL /*pvUser*/);
1066 }
1067 }
1068 return VINF_SUCCESS;
1069}
1070
1071
1072/*********************************************************************************************************************************
1073* Worker Thread *
1074*********************************************************************************************************************************/
1075#ifdef CORE_AUDIO_WITH_WORKER_THREAD
1076
1077/**
1078 * Message handling callback for CFMachPort.
1079 */
1080static void drvHstAudCaThreadPortCallback(CFMachPortRef hPort, void *pvMsg, CFIndex cbMsg, void *pvUser)
1081{
1082 RT_NOREF(hPort, pvMsg, cbMsg, pvUser);
1083 LogFunc(("hPort=%p pvMsg=%p cbMsg=%#x pvUser=%p\n", hPort, pvMsg, cbMsg, pvUser));
1084}
1085
1086
1087/**
1088 * @callback_method_impl{FNRTTHREAD, Worker thread for buffer callbacks.}
1089 */
1090static DECLCALLBACK(int) drvHstAudCaThread(RTTHREAD hThreadSelf, void *pvUser)
1091{
1092 PDRVHOSTCOREAUDIO pThis = (PDRVHOSTCOREAUDIO)pvUser;
1093
1094 /*
1095 * Get the runloop, add the mach port to it and signal the constructor thread that we're ready.
1096 */
1097 pThis->hThreadRunLoop = CFRunLoopGetCurrent();
1098 CFRetain(pThis->hThreadRunLoop);
1099
1100 CFRunLoopAddSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
1101
1102 int rc = RTThreadUserSignal(hThreadSelf);
1103 AssertRCReturn(rc, rc);
1104
1105 /*
1106 * Do work.
1107 */
1108 for (;;)
1109 {
1110 SInt32 rcRunLoop = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 30.0, TRUE);
1111 Log8Func(("CFRunLoopRunInMode -> %d\n", rcRunLoop));
1112 Assert(rcRunLoop != kCFRunLoopRunFinished);
1113 if (rcRunLoop != kCFRunLoopRunStopped && rcRunLoop != kCFRunLoopRunFinished)
1114 { /* likely */ }
1115 else
1116 break;
1117 }
1118
1119 /*
1120 * Clean up.
1121 */
1122 CFRunLoopRemoveSource(pThis->hThreadRunLoop, pThis->hThreadPortSrc, kCFRunLoopDefaultMode);
1123 LogFunc(("The thread quits!\n"));
1124 return VINF_SUCCESS;
1125}
1126
1127#endif /* CORE_AUDIO_WITH_WORKER_THREAD */
1128
1129
1130
1131/*********************************************************************************************************************************
1132* PDMIHOSTAUDIO *
1133*********************************************************************************************************************************/
1134
1135/**
1136 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
1137 */
1138static DECLCALLBACK(int) drvHstAudCaHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
1139{
1140 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1141 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
1142
1143 /*
1144 * Fill in the config structure.
1145 */
1146 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "Core Audio");
1147 pBackendCfg->cbStream = sizeof(COREAUDIOSTREAM);
1148 pBackendCfg->fFlags = PDMAUDIOBACKEND_F_ASYNC_STREAM_DESTROY;
1149
1150 RTCritSectEnter(&pThis->CritSect);
1151#if 0 /** @todo r=bird: This looks like complete utter non-sense to me. */
1152 /* For Core Audio we provide one stream per device for now. */
1153 pBackendCfg->cMaxStreamsIn = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_IN);
1154 pBackendCfg->cMaxStreamsOut = PDMAudioHostEnumCountMatching(&pThis->Devices, PDMAUDIODIR_OUT);
1155#else
1156 pBackendCfg->cMaxStreamsIn = UINT32_MAX;
1157 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
1158#endif
1159 RTCritSectLeave(&pThis->CritSect);
1160
1161 LogFlowFunc(("Returning %Rrc\n", VINF_SUCCESS));
1162 return VINF_SUCCESS;
1163}
1164
1165
1166/**
1167 * Creates an enumeration of the host's playback and capture devices.
1168 *
1169 * @returns VBox status code.
1170 * @param pDevEnm Where to store the enumerated devices. Caller is
1171 * expected to clean this up on failure, if so desired.
1172 *
1173 * @note Handling of out-of-memory conditions isn't perhaps as good as it
1174 * could be, but it was done so to make the drvHstAudCaGetPropertyData*
1175 * functions as uncomplicated as possible.
1176 */
1177static int drvHstAudCaDevicesEnumerateAll(PPDMAUDIOHOSTENUM pDevEnm)
1178{
1179 AssertPtr(pDevEnm);
1180
1181 /*
1182 * First get the UIDs for the default devices.
1183 */
1184 AudioDeviceID idDefaultDevIn = kAudioDeviceUnknown;
1185 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice,
1186 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1187 "default input device", &idDefaultDevIn, sizeof(idDefaultDevIn)))
1188 idDefaultDevIn = kAudioDeviceUnknown;
1189 if (idDefaultDevIn == kAudioDeviceUnknown)
1190 LogFunc(("No default input device\n"));
1191
1192 AudioDeviceID idDefaultDevOut = kAudioDeviceUnknown;
1193 if (!drvHstAudCaGetPropertyData(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice,
1194 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1195 "default output device", &idDefaultDevOut, sizeof(idDefaultDevOut)))
1196 idDefaultDevOut = kAudioDeviceUnknown;
1197 if (idDefaultDevOut == kAudioDeviceUnknown)
1198 LogFunc(("No default output device\n"));
1199
1200 /*
1201 * Get a list of all audio devices.
1202 * (We have to retry as the we may race new devices being inserted.)
1203 */
1204 UInt32 cDevices = 0;
1205 AudioDeviceID *paidDevices
1206 = (AudioDeviceID *)drvHstAudCaGetPropertyDataEx(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
1207 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster,
1208 "devices", &cDevices);
1209 cDevices /= sizeof(paidDevices[0]);
1210
1211 /*
1212 * Try get details on each device and try add them to the enumeration result.
1213 */
1214 for (uint32_t i = 0; i < cDevices; i++)
1215 {
1216 AudioDeviceID const idDevice = paidDevices[i];
1217
1218 /*
1219 * Allocate a new device entry and populate it.
1220 *
1221 * The only relevant information here is channel counts and the UID(s),
1222 * everything else is just extras we can live without.
1223 */
1224 PCOREAUDIODEVICEDATA pDevEntry = (PCOREAUDIODEVICEDATA)PDMAudioHostDevAlloc(sizeof(*pDevEntry), 0, 0);
1225 AssertReturnStmt(pDevEntry, RTMemTmpFree(paidDevices), VERR_NO_MEMORY);
1226
1227 pDevEntry->idDevice = idDevice;
1228 if (idDevice != kAudioDeviceUnknown)
1229 {
1230 if (idDevice == idDefaultDevIn)
1231 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_IN;
1232 if (idDevice == idDefaultDevOut)
1233 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEFAULT_OUT;
1234 }
1235
1236 /* Count channels and determin the usage. */
1237 pDevEntry->Core.cMaxInputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeInput);
1238 pDevEntry->Core.cMaxOutputChannels = drvHstAudCaEnumCountChannels(idDevice, kAudioDevicePropertyScopeOutput);
1239 if (pDevEntry->Core.cMaxInputChannels > 0 && pDevEntry->Core.cMaxOutputChannels > 0)
1240 pDevEntry->Core.enmUsage = PDMAUDIODIR_DUPLEX;
1241 else if (pDevEntry->Core.cMaxInputChannels > 0)
1242 pDevEntry->Core.enmUsage = PDMAUDIODIR_IN;
1243 else if (pDevEntry->Core.cMaxOutputChannels > 0)
1244 pDevEntry->Core.enmUsage = PDMAUDIODIR_OUT;
1245 else
1246 {
1247 pDevEntry->Core.enmUsage = PDMAUDIODIR_UNKNOWN;
1248 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
1249 /** @todo drop & skip? */
1250 }
1251
1252 /* Get the device UID. (We ASSUME this is the same for both input and
1253 output sides of the device.) */
1254 CFStringRef hStrUid;
1255 if (!drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceUID, kAudioDevicePropertyDeviceUID,
1256 kAudioObjectPropertyElementMaster,
1257 "device UID", &hStrUid, sizeof(hStrUid)))
1258 hStrUid = NULL;
1259
1260 if (hStrUid)
1261 {
1262 pDevEntry->Core.pszId = drvHstAudCaCFStringToHeap(hStrUid);
1263 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_ID_ALLOC;
1264 }
1265 else
1266 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_IGNORE;
1267
1268 /* Get the device name (ignore failures). */
1269 CFStringRef hStrName = NULL;
1270 if (drvHstAudCaGetPropertyData(idDevice, kAudioObjectPropertyName,
1271 pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
1272 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1273 kAudioObjectPropertyElementMaster, "device name", &hStrName, sizeof(hStrName)))
1274 {
1275 pDevEntry->Core.pszName = drvHstAudCaCFStringToHeap(hStrName);
1276 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_NAME_ALLOC;
1277 CFRelease(hStrName);
1278 }
1279
1280 /* Check if the device is alive for the intended usage. For duplex
1281 devices we'll flag it as dead if either of the directions are dead,
1282 as there is no convenient way of saying otherwise. It's acadmic as
1283 nobody currently 2021-05-22) uses the flag for anything. */
1284 UInt32 fAlive = 0;
1285 if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive,
1286 pDevEntry->Core.enmUsage == PDMAUDIODIR_IN
1287 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
1288 kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
1289 if (!fAlive)
1290 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
1291 fAlive = 0;
1292 if ( pDevEntry->Core.enmUsage == PDMAUDIODIR_DUPLEX
1293 && !(pDevEntry->Core.fFlags == PDMAUDIOHOSTDEV_F_DEAD)
1294 && drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyDeviceIsAlive, kAudioDevicePropertyScopeInput,
1295 kAudioObjectPropertyElementMaster, "is-alive", &fAlive, sizeof(fAlive)))
1296 if (!fAlive)
1297 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_DEAD;
1298
1299 /* Check if the device is being hogged by someone else. */
1300 pid_t pidHogger = -2;
1301 if (drvHstAudCaGetPropertyData(idDevice, kAudioDevicePropertyHogMode, kAudioObjectPropertyScopeGlobal,
1302 kAudioObjectPropertyElementMaster, "hog-mode", &pidHogger, sizeof(pidHogger)))
1303 if (pidHogger >= 0)
1304 pDevEntry->Core.fFlags |= PDMAUDIOHOSTDEV_F_LOCKED;
1305
1306 /*
1307 * Try make sure we've got a name... Only add it to the enumeration if we have one.
1308 */
1309 if (!pDevEntry->Core.pszName)
1310 {
1311 pDevEntry->Core.pszName = pDevEntry->Core.pszId;
1312 pDevEntry->Core.fFlags &= ~PDMAUDIOHOSTDEV_F_NAME_ALLOC;
1313 }
1314
1315 if (pDevEntry->Core.pszName)
1316 PDMAudioHostEnumAppend(pDevEnm, &pDevEntry->Core);
1317 else
1318 PDMAudioHostDevFree(&pDevEntry->Core);
1319 }
1320
1321 RTMemTmpFree(paidDevices);
1322
1323 LogFunc(("Returning %u devices\n", pDevEnm->cDevices));
1324 PDMAudioHostEnumLog(pDevEnm, "Core Audio");
1325 return VINF_SUCCESS;
1326}
1327
1328
1329/**
1330 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetDevices}
1331 */
1332static DECLCALLBACK(int) drvHstAudCaHA_GetDevices(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHOSTENUM pDeviceEnum)
1333{
1334 RT_NOREF(pInterface);
1335 AssertPtrReturn(pDeviceEnum, VERR_INVALID_POINTER);
1336
1337 PDMAudioHostEnumInit(pDeviceEnum);
1338 int rc = drvHstAudCaDevicesEnumerateAll(pDeviceEnum);
1339 if (RT_FAILURE(rc))
1340 PDMAudioHostEnumDelete(pDeviceEnum);
1341
1342 LogFlowFunc(("returns %Rrc\n", rc));
1343 return rc;
1344}
1345
1346
1347/**
1348 * @interface_method_impl{PDMIHOSTAUDIO,pfnSetDevice}
1349 */
1350static DECLCALLBACK(int) drvHstAudCaHA_SetDevice(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir, const char *pszId)
1351{
1352 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1353 AssertPtrNullReturn(pszId, VERR_INVALID_POINTER);
1354 if (pszId && !*pszId)
1355 pszId = NULL;
1356 AssertMsgReturn(enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_OUT || enmDir == PDMAUDIODIR_DUPLEX,
1357 ("enmDir=%d\n", enmDir, pszId), VERR_INVALID_PARAMETER);
1358
1359 /*
1360 * Make the change.
1361 */
1362 int rc = VINF_SUCCESS;
1363 if (enmDir == PDMAUDIODIR_IN || enmDir == PDMAUDIODIR_DUPLEX)
1364 rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, true /*fNotify*/, pszId);
1365 if (enmDir == PDMAUDIODIR_OUT || (enmDir == PDMAUDIODIR_DUPLEX && RT_SUCCESS(rc)))
1366 rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, true /*fNotify*/, pszId);
1367
1368 LogFlowFunc(("returns %Rrc\n", rc));
1369 return rc;
1370}
1371
1372
1373/**
1374 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
1375 */
1376static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvHstAudCaHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
1377{
1378 RT_NOREF(pInterface, enmDir);
1379 return PDMAUDIOBACKENDSTS_RUNNING;
1380}
1381
1382
1383/**
1384 * Marks the given buffer as queued or not-queued.
1385 *
1386 * @returns Old queued value.
1387 * @param pAudioBuffer The buffer.
1388 * @param fQueued The new queued state.
1389 */
1390DECLINLINE(bool) drvHstAudCaSetBufferQueued(AudioQueueBufferRef pAudioBuffer, bool fQueued)
1391{
1392 if (fQueued)
1393 return ASMAtomicBitTestAndSet(&pAudioBuffer->mUserData, 0);
1394 return ASMAtomicBitTestAndClear(&pAudioBuffer->mUserData, 0);
1395}
1396
1397
1398/**
1399 * Gets the queued state of the buffer.
1400 * @returns true if queued, false if not.
1401 * @param pAudioBuffer The buffer.
1402 */
1403DECLINLINE(bool) drvHstAudCaIsBufferQueued(AudioQueueBufferRef pAudioBuffer)
1404{
1405 return ((uintptr_t)pAudioBuffer->mUserData & 1) == 1;
1406}
1407
1408
1409/**
1410 * Output audio queue buffer callback.
1411 *
1412 * Called whenever an audio queue is done processing a buffer. This routine
1413 * will set the data fill size to zero and mark it as unqueued so that
1414 * drvHstAudCaHA_StreamPlay knowns it can use it.
1415 *
1416 * @param pvUser User argument.
1417 * @param hAudioQueue Audio queue to process output data for.
1418 * @param pAudioBuffer Audio buffer to store output data in.
1419 *
1420 * @thread queue thread.
1421 */
1422static void drvHstAudCaOutputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue, AudioQueueBufferRef pAudioBuffer)
1423{
1424#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1425 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1426 AssertPtr(pStreamCA);
1427 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1428
1429 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
1430 Log4Func(("Got back buffer #%zu (%p)\n", idxBuf, pAudioBuffer));
1431 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1432 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1433#endif
1434
1435 pAudioBuffer->mAudioDataByteSize = 0;
1436 bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
1437 Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
1438 Assert(fWasQueued); RT_NOREF(fWasQueued);
1439
1440 RT_NOREF(pvUser, hAudioQueue);
1441}
1442
1443
1444/**
1445 * Input audio queue buffer callback.
1446 *
1447 * Called whenever input data from the audio queue becomes available. This
1448 * routine will mark the buffer unqueued so that drvHstAudCaHA_StreamCapture can
1449 * read the data from it.
1450 *
1451 * @param pvUser User argument.
1452 * @param hAudioQueue Audio queue to process input data from.
1453 * @param pAudioBuffer Audio buffer to process input data from.
1454 * @param pAudioTS Audio timestamp.
1455 * @param cPacketDesc Number of packet descriptors.
1456 * @param paPacketDesc Array of packet descriptors.
1457 */
1458static void drvHstAudCaInputQueueBufferCallback(void *pvUser, AudioQueueRef hAudioQueue,
1459 AudioQueueBufferRef pAudioBuffer, const AudioTimeStamp *pAudioTS,
1460 UInt32 cPacketDesc, const AudioStreamPacketDescription *paPacketDesc)
1461{
1462#if defined(VBOX_STRICT) || defined(LOG_ENABLED)
1463 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pvUser;
1464 AssertPtr(pStreamCA);
1465 Assert(pStreamCA->hAudioQueue == hAudioQueue);
1466
1467 uintptr_t idxBuf = (uintptr_t)pAudioBuffer->mUserData >> 8;
1468 Log4Func(("Got back buffer #%zu (%p) with %#x bytes\n", idxBuf, pAudioBuffer, pAudioBuffer->mAudioDataByteSize));
1469 AssertReturnVoid( idxBuf < pStreamCA->cBuffers
1470 && pStreamCA->paBuffers[idxBuf].pBuf == pAudioBuffer);
1471#endif
1472
1473 bool fWasQueued = drvHstAudCaSetBufferQueued(pAudioBuffer, false /*fQueued*/);
1474 Assert(!drvHstAudCaIsBufferQueued(pAudioBuffer));
1475 Assert(fWasQueued); RT_NOREF(fWasQueued);
1476
1477 RT_NOREF(pvUser, hAudioQueue, pAudioTS, cPacketDesc, paPacketDesc);
1478}
1479
1480
1481static void drvHstAudCaLogAsbd(const char *pszDesc, const AudioStreamBasicDescription *pASBD)
1482{
1483 LogRel2(("CoreAudio: %s description:\n", pszDesc));
1484 LogRel2(("CoreAudio: Format ID: %#RX32 (%c%c%c%c)\n", pASBD->mFormatID,
1485 RT_BYTE4(pASBD->mFormatID), RT_BYTE3(pASBD->mFormatID),
1486 RT_BYTE2(pASBD->mFormatID), RT_BYTE1(pASBD->mFormatID)));
1487 LogRel2(("CoreAudio: Flags: %#RX32", pASBD->mFormatFlags));
1488 if (pASBD->mFormatFlags & kAudioFormatFlagIsFloat)
1489 LogRel2((" Float"));
1490 if (pASBD->mFormatFlags & kAudioFormatFlagIsBigEndian)
1491 LogRel2((" BigEndian"));
1492 if (pASBD->mFormatFlags & kAudioFormatFlagIsSignedInteger)
1493 LogRel2((" SignedInteger"));
1494 if (pASBD->mFormatFlags & kAudioFormatFlagIsPacked)
1495 LogRel2((" Packed"));
1496 if (pASBD->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
1497 LogRel2((" AlignedHigh"));
1498 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
1499 LogRel2((" NonInterleaved"));
1500 if (pASBD->mFormatFlags & kAudioFormatFlagIsNonMixable)
1501 LogRel2((" NonMixable"));
1502 if (pASBD->mFormatFlags & kAudioFormatFlagsAreAllClear)
1503 LogRel2((" AllClear"));
1504 LogRel2(("\n"));
1505 LogRel2(("CoreAudio: SampleRate : %RU64.%02u Hz\n",
1506 (uint64_t)pASBD->mSampleRate, (unsigned)(pASBD->mSampleRate * 100) % 100));
1507 LogRel2(("CoreAudio: ChannelsPerFrame: %RU32\n", pASBD->mChannelsPerFrame));
1508 LogRel2(("CoreAudio: FramesPerPacket : %RU32\n", pASBD->mFramesPerPacket));
1509 LogRel2(("CoreAudio: BitsPerChannel : %RU32\n", pASBD->mBitsPerChannel));
1510 LogRel2(("CoreAudio: BytesPerFrame : %RU32\n", pASBD->mBytesPerFrame));
1511 LogRel2(("CoreAudio: BytesPerPacket : %RU32\n", pASBD->mBytesPerPacket));
1512}
1513
1514
1515static void drvHstAudCaPropsToAsbd(PCPDMAUDIOPCMPROPS pProps, AudioStreamBasicDescription *pASBD)
1516{
1517 AssertPtrReturnVoid(pProps);
1518 AssertPtrReturnVoid(pASBD);
1519
1520 RT_BZERO(pASBD, sizeof(AudioStreamBasicDescription));
1521
1522 pASBD->mFormatID = kAudioFormatLinearPCM;
1523 pASBD->mFormatFlags = kAudioFormatFlagIsPacked;
1524 if (pProps->fSigned)
1525 pASBD->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
1526 if (PDMAudioPropsIsBigEndian(pProps))
1527 pASBD->mFormatFlags |= kAudioFormatFlagIsBigEndian;
1528 pASBD->mSampleRate = PDMAudioPropsHz(pProps);
1529 pASBD->mChannelsPerFrame = PDMAudioPropsChannels(pProps);
1530 pASBD->mBitsPerChannel = PDMAudioPropsSampleBits(pProps);
1531 pASBD->mBytesPerFrame = PDMAudioPropsFrameSize(pProps);
1532 pASBD->mFramesPerPacket = 1; /* For uncompressed audio, set this to 1. */
1533 pASBD->mBytesPerPacket = PDMAudioPropsFrameSize(pProps) * pASBD->mFramesPerPacket;
1534}
1535
1536
1537/**
1538 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
1539 */
1540static DECLCALLBACK(int) drvHstAudCaHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
1541 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
1542{
1543 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1544 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1545 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1546 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
1547 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
1548 AssertReturn(pCfgReq->enmDir == PDMAUDIODIR_IN || pCfgReq->enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
1549 int rc;
1550
1551 /** @todo This takes too long. Stats indicates it may take up to 200 ms.
1552 * Knoppix guest resets the stream and we hear nada because the
1553 * draining is aborted when the stream is destroyed. Should try use
1554 * async init for parts (much) of this. */
1555
1556 /*
1557 * Permission check for input devices before we start.
1558 */
1559 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
1560 {
1561 rc = coreAudioInputPermissionCheck();
1562 if (RT_FAILURE(rc))
1563 return rc;
1564 }
1565
1566 /*
1567 * Do we have a device for the requested stream direction?
1568 */
1569 RTCritSectEnter(&pThis->CritSect);
1570 CFStringRef hDevUidStr = pCfgReq->enmDir == PDMAUDIODIR_IN ? pThis->InputDevice.hStrUid : pThis->OutputDevice.hStrUid;
1571 if (hDevUidStr)
1572 CFRetain(hDevUidStr);
1573 RTCritSectLeave(&pThis->CritSect);
1574
1575#ifdef LOG_ENABLED
1576 char szTmp[PDMAUDIOSTRMCFGTOSTRING_MAX];
1577#endif
1578 LogFunc(("hDevUidStr=%p *pCfgReq: %s\n", hDevUidStr, PDMAudioStrmCfgToString(pCfgReq, szTmp, sizeof(szTmp)) ));
1579 if (hDevUidStr)
1580 {
1581 /*
1582 * Basic structure init.
1583 */
1584 pStreamCA->fEnabled = false;
1585 pStreamCA->fStarted = false;
1586 pStreamCA->fDraining = false;
1587 pStreamCA->fRestartOnResume = false;
1588 pStreamCA->offInternal = 0;
1589 pStreamCA->idxBuffer = 0;
1590 pStreamCA->enmInitState = COREAUDIOINITSTATE_IN_INIT;
1591
1592 rc = RTCritSectInit(&pStreamCA->CritSect);
1593 if (RT_SUCCESS(rc))
1594 {
1595 /*
1596 * Do format conversion and create the circular buffer we use to shuffle
1597 * data to/from the queue thread.
1598 */
1599 PDMAudioStrmCfgCopy(&pStreamCA->Cfg, pCfgReq);
1600 drvHstAudCaPropsToAsbd(&pCfgReq->Props, &pStreamCA->BasicStreamDesc);
1601 /** @todo Do some validation? */
1602 drvHstAudCaLogAsbd(pCfgReq->enmDir == PDMAUDIODIR_IN ? "Capturing queue format" : "Playback queue format",
1603 &pStreamCA->BasicStreamDesc);
1604 /*
1605 * Create audio queue.
1606 *
1607 * Documentation says the callbacks will be run on some core audio
1608 * related thread if we don't specify a runloop here. That's simpler.
1609 */
1610#ifdef CORE_AUDIO_WITH_WORKER_THREAD
1611 CFRunLoopRef const hRunLoop = pThis->hThreadRunLoop;
1612 CFStringRef const hRunLoopMode = kCFRunLoopDefaultMode;
1613#else
1614 CFRunLoopRef const hRunLoop = NULL;
1615 CFStringRef const hRunLoopMode = NULL;
1616#endif
1617 OSStatus orc;
1618 if (pCfgReq->enmDir == PDMAUDIODIR_OUT)
1619 orc = AudioQueueNewOutput(&pStreamCA->BasicStreamDesc, drvHstAudCaOutputQueueBufferCallback, pStreamCA,
1620 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1621 else
1622 orc = AudioQueueNewInput(&pStreamCA->BasicStreamDesc, drvHstAudCaInputQueueBufferCallback, pStreamCA,
1623 hRunLoop, hRunLoopMode, 0 /*fFlags - MBZ*/, &pStreamCA->hAudioQueue);
1624 LogFlowFunc(("AudioQueueNew%s -> %#x\n", pCfgReq->enmDir == PDMAUDIODIR_OUT ? "Output" : "Input", orc));
1625 if (orc == noErr)
1626 {
1627 /*
1628 * Assign device to the queue.
1629 */
1630 UInt32 uSize = sizeof(hDevUidStr);
1631 orc = AudioQueueSetProperty(pStreamCA->hAudioQueue, kAudioQueueProperty_CurrentDevice, &hDevUidStr, uSize);
1632 LogFlowFunc(("AudioQueueSetProperty -> %#x\n", orc));
1633 if (orc == noErr)
1634 {
1635 /*
1636 * Sanity-adjust the requested buffer size.
1637 */
1638 uint32_t cFramesBufferSizeMax = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 2 * RT_MS_1SEC);
1639 uint32_t cFramesBufferSize = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props, 32 /*ms*/);
1640 cFramesBufferSize = RT_MAX(cFramesBufferSize, pCfgReq->Backend.cFramesBufferSize);
1641 cFramesBufferSize = RT_MIN(cFramesBufferSize, cFramesBufferSizeMax);
1642
1643 /*
1644 * The queue buffers size is based on cMsSchedulingHint so that we're likely to
1645 * have a new one ready/done after each guest DMA transfer. We must however
1646 * make sure we don't end up with too may or too few.
1647 */
1648 Assert(pCfgReq->Device.cMsSchedulingHint > 0);
1649 uint32_t cFramesQueueBuffer = PDMAudioPropsMilliToFrames(&pStreamCA->Cfg.Props,
1650 pCfgReq->Device.cMsSchedulingHint > 0
1651 ? pCfgReq->Device.cMsSchedulingHint : 10);
1652 uint32_t cQueueBuffers;
1653 if (cFramesQueueBuffer * COREAUDIO_MIN_BUFFERS <= cFramesBufferSize)
1654 {
1655 cQueueBuffers = cFramesBufferSize / cFramesQueueBuffer;
1656 if (cQueueBuffers > COREAUDIO_MAX_BUFFERS)
1657 {
1658 cQueueBuffers = COREAUDIO_MAX_BUFFERS;
1659 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MAX_BUFFERS;
1660 }
1661 }
1662 else
1663 {
1664 cQueueBuffers = COREAUDIO_MIN_BUFFERS;
1665 cFramesQueueBuffer = cFramesBufferSize / COREAUDIO_MIN_BUFFERS;
1666 }
1667
1668 cFramesBufferSize = cQueueBuffers * cFramesBufferSize;
1669
1670 /*
1671 * Allocate the audio queue buffers.
1672 */
1673 pStreamCA->paBuffers = (PCOREAUDIOBUF)RTMemAllocZ(sizeof(pStreamCA->paBuffers[0]) * cQueueBuffers);
1674 if (pStreamCA->paBuffers != NULL)
1675 {
1676 pStreamCA->cBuffers = cQueueBuffers;
1677
1678 const size_t cbQueueBuffer = PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, cFramesQueueBuffer);
1679 LogFlowFunc(("Allocating %u, each %#x bytes / %u frames\n", cQueueBuffers, cbQueueBuffer, cFramesQueueBuffer));
1680 cFramesBufferSize = 0;
1681 for (uint32_t iBuf = 0; iBuf < cQueueBuffers; iBuf++)
1682 {
1683 AudioQueueBufferRef pBuf = NULL;
1684 orc = AudioQueueAllocateBuffer(pStreamCA->hAudioQueue, cbQueueBuffer, &pBuf);
1685 if (RT_LIKELY(orc == noErr))
1686 {
1687 pBuf->mUserData = (void *)(uintptr_t)(iBuf << 8); /* bit zero is the queued-indicator. */
1688 pStreamCA->paBuffers[iBuf].pBuf = pBuf;
1689 cFramesBufferSize += PDMAudioPropsBytesToFrames(&pStreamCA->Cfg.Props,
1690 pBuf->mAudioDataBytesCapacity);
1691 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, pBuf->mAudioDataBytesCapacity));
1692 }
1693 else
1694 {
1695 LogRel(("CoreAudio: Out of memory (buffer %#x out of %#x, %#x bytes)\n",
1696 iBuf, cQueueBuffers, cbQueueBuffer));
1697 while (iBuf-- > 0)
1698 {
1699 AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1700 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1701 }
1702 break;
1703 }
1704 }
1705 if (orc == noErr)
1706 {
1707 /*
1708 * Update the stream config.
1709 */
1710 pStreamCA->Cfg.Backend.cFramesBufferSize = cFramesBufferSize;
1711 pStreamCA->Cfg.Backend.cFramesPeriod = cFramesQueueBuffer; /* whatever */
1712 pStreamCA->Cfg.Backend.cFramesPreBuffering = pStreamCA->Cfg.Backend.cFramesPreBuffering
1713 * pStreamCA->Cfg.Backend.cFramesBufferSize
1714 / RT_MAX(pCfgReq->Backend.cFramesBufferSize, 1);
1715
1716 PDMAudioStrmCfgCopy(pCfgAcq, &pStreamCA->Cfg);
1717
1718 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_INIT);
1719
1720 LogFunc(("returns VINF_SUCCESS\n"));
1721 CFRelease(hDevUidStr);
1722 return VINF_SUCCESS;
1723 }
1724
1725 RTMemFree(pStreamCA->paBuffers);
1726 }
1727 else
1728 rc = VERR_NO_MEMORY;
1729 }
1730 else
1731 LogRelMax(64, ("CoreAudio: Failed to associate device with queue: %#x (%d)\n", orc, orc));
1732 AudioQueueDispose(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1733 }
1734 else
1735 LogRelMax(64, ("CoreAudio: Failed to create audio queue: %#x (%d)\n", orc, orc));
1736 RTCritSectDelete(&pStreamCA->CritSect);
1737 }
1738 else
1739 LogRel(("CoreAudio: Failed to initialize critical section for stream: %Rrc\n", rc));
1740 CFRelease(hDevUidStr);
1741 }
1742 else
1743 {
1744 LogRelMax(64, ("CoreAudio: No device for %s stream.\n", PDMAudioDirGetName(pCfgReq->enmDir)));
1745 rc = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
1746 }
1747
1748 LogFunc(("returns %Rrc\n", rc));
1749 return rc;
1750}
1751
1752
1753/**
1754 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
1755 */
1756static DECLCALLBACK(int) drvHstAudCaHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, bool fImmediate)
1757{
1758 RT_NOREF(pInterface);
1759 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1760 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
1761 LogFunc(("%p: %s fImmediate=%RTbool\n", pStreamCA, pStreamCA->Cfg.szName, fImmediate));
1762#ifdef LOG_ENABLED
1763 uint64_t const nsStart = RTTimeNanoTS();
1764#endif
1765
1766 /*
1767 * Never mind if the status isn't INIT (it should always be, though).
1768 */
1769 COREAUDIOINITSTATE const enmInitState = (COREAUDIOINITSTATE)ASMAtomicReadU32(&pStreamCA->enmInitState);
1770 AssertMsg(enmInitState == COREAUDIOINITSTATE_INIT, ("%d\n", enmInitState));
1771 if (enmInitState == COREAUDIOINITSTATE_INIT)
1772 {
1773 Assert(RTCritSectIsInitialized(&pStreamCA->CritSect));
1774
1775 /*
1776 * Change the stream state and stop the stream (just to be sure).
1777 */
1778 OSStatus orc;
1779 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_IN_UNINIT);
1780 if (pStreamCA->hAudioQueue)
1781 {
1782 orc = AudioQueueStop(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/);
1783 LogFlowFunc(("AudioQueueStop -> %#x\n", orc));
1784 }
1785
1786 /*
1787 * Enter and leave the critsect afterwards for paranoid reasons.
1788 */
1789 RTCritSectEnter(&pStreamCA->CritSect);
1790 RTCritSectLeave(&pStreamCA->CritSect);
1791
1792 /*
1793 * Free the queue buffers and the queue.
1794 *
1795 * This may take a while. The AudioQueueReset call seems to helps
1796 * reducing time stuck in AudioQueueDispose.
1797 */
1798#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1799 LogRel(("Queue-destruction timer starting...\n"));
1800 PDRVHOSTCOREAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTCOREAUDIO, IHostAudio);
1801 RTTimerLRStart(pThis->hBreakpointTimer, RT_NS_100MS);
1802 uint64_t nsStart = RTTimeNanoTS();
1803#endif
1804
1805#if 0 /* This seems to work even when doing a non-immediate stop&dispose. However, it doesn't make sense conceptually. */
1806 if (pStreamCA->hAudioQueue /*&& fImmediate*/)
1807 {
1808 LogFlowFunc(("Calling AudioQueueReset ...\n"));
1809 orc = AudioQueueReset(pStreamCA->hAudioQueue);
1810 LogFlowFunc(("AudioQueueReset -> %#x\n", orc));
1811 }
1812#endif
1813
1814 if (pStreamCA->paBuffers && fImmediate)
1815 {
1816 LogFlowFunc(("Freeing %u buffers ...\n", pStreamCA->cBuffers));
1817 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1818 {
1819 orc = AudioQueueFreeBuffer(pStreamCA->hAudioQueue, pStreamCA->paBuffers[iBuf].pBuf);
1820 AssertMsg(orc == noErr, ("AudioQueueFreeBuffer(#%u) -> orc=%#x\n", iBuf, orc));
1821 pStreamCA->paBuffers[iBuf].pBuf = NULL;
1822 }
1823 }
1824
1825 if (pStreamCA->hAudioQueue)
1826 {
1827 LogFlowFunc(("Disposing of the queue ...\n"));
1828 orc = AudioQueueDispose(pStreamCA->hAudioQueue, fImmediate ? TRUE : FALSE /*inImmediate/synchronously*/); /* may take some time */
1829 LogFlowFunc(("AudioQueueDispose -> %#x (%d)\n", orc, orc));
1830 AssertMsg(orc == noErr, ("AudioQueueDispose -> orc=%#x\n", orc));
1831 pStreamCA->hAudioQueue = NULL;
1832 }
1833
1834 /* We should get no further buffer callbacks at this point according to the docs. */
1835 if (pStreamCA->paBuffers)
1836 {
1837 RTMemFree(pStreamCA->paBuffers);
1838 pStreamCA->paBuffers = NULL;
1839 }
1840 pStreamCA->cBuffers = 0;
1841
1842#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1843 RTTimerLRStop(pThis->hBreakpointTimer);
1844 LogRel(("Queue-destruction: %'RU64\n", RTTimeNanoTS() - nsStart));
1845#endif
1846
1847 /*
1848 * Delete the critsect and we're done.
1849 */
1850 RTCritSectDelete(&pStreamCA->CritSect);
1851
1852 ASMAtomicWriteU32(&pStreamCA->enmInitState, COREAUDIOINITSTATE_UNINIT);
1853 }
1854 else
1855 LogFunc(("Wrong stream init state for %p: %d - leaking it\n", pStream, enmInitState));
1856
1857 LogFunc(("returns (took %'RU64 ns)\n", RTTimeNanoTS() - nsStart));
1858 return VINF_SUCCESS;
1859}
1860
1861
1862#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
1863/** @callback_method_impl{FNRTTIMERLR, For debugging things that takes too long.} */
1864static DECLCALLBACK(void) drvHstAudCaBreakpointTimer(RTTIMERLR hTimer, void *pvUser, uint64_t iTick)
1865{
1866 LogFlowFunc(("Queue-destruction timeout! iTick=%RU64\n", iTick));
1867 RT_NOREF(hTimer, pvUser, iTick);
1868 RTLogFlush(NULL);
1869 RT_BREAKPOINT();
1870}
1871#endif
1872
1873
1874/**
1875 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
1876 */
1877static DECLCALLBACK(int) drvHstAudCaHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1878{
1879 RT_NOREF(pInterface);
1880 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1881 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
1882 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1883 RTCritSectEnter(&pStreamCA->CritSect);
1884
1885 Assert(!pStreamCA->fEnabled);
1886 Assert(!pStreamCA->fStarted);
1887
1888 /*
1889 * We always reset the buffer before enabling the stream (normally never necessary).
1890 */
1891 OSStatus orc = AudioQueueReset(pStreamCA->hAudioQueue);
1892 if (orc != noErr)
1893 LogRelMax(64, ("CoreAudio: Stream reset failed when enabling '%s': %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1894 Assert(orc == noErr);
1895 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1896 Assert(!drvHstAudCaIsBufferQueued(pStreamCA->paBuffers[iBuf].pBuf));
1897
1898 pStreamCA->offInternal = 0;
1899 pStreamCA->fDraining = false;
1900 pStreamCA->fEnabled = true;
1901 pStreamCA->fRestartOnResume = false;
1902 pStreamCA->idxBuffer = 0;
1903
1904 /*
1905 * Input streams will start capturing, while output streams will only start
1906 * playing once we get some audio data to play (see drvHstAudCaHA_StreamPlay).
1907 */
1908 int rc = VINF_SUCCESS;
1909 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
1910 {
1911 /* Zero (probably not needed) and submit all the buffers first. */
1912 for (uint32_t iBuf = 0; iBuf < pStreamCA->cBuffers; iBuf++)
1913 {
1914 AudioQueueBufferRef pBuf = pStreamCA->paBuffers[iBuf].pBuf;
1915
1916 RT_BZERO(pBuf->mAudioData, pBuf->mAudioDataBytesCapacity);
1917 pBuf->mAudioDataByteSize = 0;
1918 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
1919
1920 orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDescs*/, NULL /*inPacketDescs*/);
1921 AssertLogRelMsgBreakStmt(orc == noErr, ("CoreAudio: AudioQueueEnqueueBuffer(#%u) -> %#x (%d) - stream '%s'\n",
1922 iBuf, orc, orc, pStreamCA->Cfg.szName),
1923 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/));
1924 }
1925
1926 /* Start the stream. */
1927 if (orc == noErr)
1928 {
1929 LogFlowFunc(("Start input stream '%s'...\n", pStreamCA->Cfg.szName));
1930 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
1931 AssertLogRelMsgStmt(orc == noErr, ("CoreAudio: AudioQueueStart(%s) -> %#x (%d) \n", pStreamCA->Cfg.szName, orc, orc),
1932 rc = VERR_AUDIO_STREAM_NOT_READY);
1933 pStreamCA->fStarted = orc == noErr;
1934 }
1935 else
1936 rc = VERR_AUDIO_STREAM_NOT_READY;
1937 }
1938 else
1939 Assert(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1940
1941 RTCritSectLeave(&pStreamCA->CritSect);
1942 LogFlowFunc(("returns %Rrc\n", rc));
1943 return rc;
1944}
1945
1946
1947/**
1948 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
1949 */
1950static DECLCALLBACK(int) drvHstAudCaHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1951{
1952 RT_NOREF(pInterface);
1953 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
1954 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
1955 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
1956 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
1957 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
1958 RTCritSectEnter(&pStreamCA->CritSect);
1959
1960 /*
1961 * Always stop it (draining or no).
1962 */
1963 pStreamCA->fEnabled = false;
1964 pStreamCA->fRestartOnResume = false;
1965 Assert(!pStreamCA->fDraining || pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT);
1966
1967 int rc = VINF_SUCCESS;
1968 if (pStreamCA->fStarted)
1969 {
1970#if 0
1971 OSStatus orc2 = AudioQueueReset(pStreamCA->hAudioQueue);
1972 LogFlowFunc(("AudioQueueReset(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1973 orc2 = AudioQueueFlush(pStreamCA->hAudioQueue);
1974 LogFlowFunc(("AudioQueueFlush(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc2, orc2)); RT_NOREF(orc2);
1975#endif
1976
1977 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, TRUE /*inImmediate*/);
1978 LogFlowFunc(("AudioQueueStop(%s,TRUE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1979 if (orc != noErr)
1980 {
1981 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (disable): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
1982 rc = VERR_GENERAL_FAILURE;
1983 }
1984 pStreamCA->fStarted = false;
1985 pStreamCA->fDraining = false;
1986 }
1987
1988 RTCritSectLeave(&pStreamCA->CritSect);
1989 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
1990 return rc;
1991}
1992
1993
1994/**
1995 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
1996 */
1997static DECLCALLBACK(int) drvHstAudCaHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
1998{
1999 RT_NOREF(pInterface);
2000 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2001 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2002 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
2003 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2004 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
2005 RTCritSectEnter(&pStreamCA->CritSect);
2006
2007 /*
2008 * Unless we're draining the stream, pause it if it has started.
2009 */
2010 int rc = VINF_SUCCESS;
2011 if (pStreamCA->fStarted && !pStreamCA->fDraining)
2012 {
2013 pStreamCA->fRestartOnResume = true;
2014
2015 OSStatus orc = AudioQueuePause(pStreamCA->hAudioQueue);
2016 LogFlowFunc(("AudioQueuePause(%s) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2017 if (orc != noErr)
2018 {
2019 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2020 rc = VERR_GENERAL_FAILURE;
2021 }
2022 pStreamCA->fStarted = false;
2023 }
2024 else
2025 {
2026 pStreamCA->fRestartOnResume = false;
2027 if (pStreamCA->fDraining)
2028 {
2029 LogFunc(("Stream '%s' is draining\n", pStreamCA->Cfg.szName));
2030 Assert(pStreamCA->fStarted);
2031 }
2032 }
2033
2034 RTCritSectLeave(&pStreamCA->CritSect);
2035 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2036 return rc;
2037}
2038
2039
2040/**
2041 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
2042 */
2043static DECLCALLBACK(int) drvHstAudCaHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2044{
2045 RT_NOREF(pInterface);
2046 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2047 LogFlowFunc(("Stream '%s' {%s}\n", pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA)));
2048 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
2049 RTCritSectEnter(&pStreamCA->CritSect);
2050
2051 /*
2052 * Resume according to state saved by drvHstAudCaHA_StreamPause.
2053 */
2054 int rc = VINF_SUCCESS;
2055 if (pStreamCA->fRestartOnResume)
2056 {
2057 OSStatus orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2058 LogFlowFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2059 if (orc != noErr)
2060 {
2061 LogRelMax(64, ("CoreAudio: Pausing '%s' failed: %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2062 rc = VERR_AUDIO_STREAM_NOT_READY;
2063 }
2064 }
2065 pStreamCA->fRestartOnResume = false;
2066
2067 RTCritSectLeave(&pStreamCA->CritSect);
2068 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2069 return rc;
2070}
2071
2072
2073/**
2074 * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
2075 */
2076static DECLCALLBACK(int) drvHstAudCaHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2077{
2078 RT_NOREF(pInterface);
2079 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2080 AssertReturn(pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
2081 LogFlowFunc(("cMsLastTransfer=%RI64 ms, stream '%s' {%s} \n",
2082 pStreamCA->msLastTransfer ? RTTimeMilliTS() - pStreamCA->msLastTransfer : -1,
2083 pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2084 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, VERR_AUDIO_STREAM_NOT_READY);
2085 RTCritSectEnter(&pStreamCA->CritSect);
2086
2087 /*
2088 * The AudioQueueStop function has both an immediate and a drain mode,
2089 * so we'll obviously use the latter here. For checking draining progress,
2090 * we will just check if all buffers have been returned or not.
2091 */
2092 int rc = VINF_SUCCESS;
2093 if (pStreamCA->fStarted)
2094 {
2095 if (!pStreamCA->fDraining)
2096 {
2097 OSStatus orc = AudioQueueStop(pStreamCA->hAudioQueue, FALSE /*inImmediate*/);
2098 LogFlowFunc(("AudioQueueStop(%s, FALSE) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2099 if (orc == noErr)
2100 pStreamCA->fDraining = true;
2101 else
2102 {
2103 LogRelMax(64, ("CoreAudio: Stopping '%s' failed (drain): %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2104 rc = VERR_GENERAL_FAILURE;
2105 }
2106 }
2107 else
2108 LogFlowFunc(("Already draining '%s' ...\n", pStreamCA->Cfg.szName));
2109 }
2110 else
2111 {
2112 LogFlowFunc(("Drain requested for '%s', but not started playback...\n", pStreamCA->Cfg.szName));
2113 AssertStmt(!pStreamCA->fDraining, pStreamCA->fDraining = false);
2114 }
2115
2116 RTCritSectLeave(&pStreamCA->CritSect);
2117 LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHstAudCaStreamStatusString(pStreamCA)));
2118 return rc;
2119}
2120
2121
2122/**
2123 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
2124 */
2125static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2126{
2127 RT_NOREF(pInterface);
2128 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2129 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2130 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2131
2132 uint32_t cbReadable = 0;
2133 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_IN)
2134 {
2135 RTCritSectEnter(&pStreamCA->CritSect);
2136 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2137 uint32_t const cBuffers = pStreamCA->cBuffers;
2138 uint32_t const idxStart = pStreamCA->idxBuffer;
2139 uint32_t idxBuffer = idxStart;
2140 AudioQueueBufferRef pBuf;
2141
2142 if ( cBuffers > 0
2143 && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
2144 {
2145 do
2146 {
2147 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2148 uint32_t cbFill = pBuf->mAudioDataByteSize;
2149 AssertStmt(cbFill <= cbTotal, cbFill = cbTotal);
2150 uint32_t off = paBuffers[idxBuffer].offRead;
2151 AssertStmt(off < cbFill, off = cbFill);
2152
2153 cbReadable += cbFill - off;
2154
2155 /* Advance. */
2156 idxBuffer++;
2157 if (idxBuffer < cBuffers)
2158 { /* likely */ }
2159 else
2160 idxBuffer = 0;
2161 } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
2162 }
2163
2164 RTCritSectLeave(&pStreamCA->CritSect);
2165 }
2166 Log2Func(("returns %#x for '%s'\n", cbReadable, pStreamCA->Cfg.szName));
2167 return cbReadable;
2168}
2169
2170
2171/**
2172 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
2173 */
2174static DECLCALLBACK(uint32_t) drvHstAudCaHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
2175{
2176 RT_NOREF(pInterface);
2177 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2178 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2179 AssertReturn(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, 0);
2180
2181 uint32_t cbWritable = 0;
2182 if (pStreamCA->Cfg.enmDir == PDMAUDIODIR_OUT)
2183 {
2184 RTCritSectEnter(&pStreamCA->CritSect);
2185 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2186 uint32_t const cBuffers = pStreamCA->cBuffers;
2187 uint32_t const idxStart = pStreamCA->idxBuffer;
2188 uint32_t idxBuffer = idxStart;
2189 AudioQueueBufferRef pBuf;
2190
2191 if ( cBuffers > 0
2192 && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf))
2193 {
2194 do
2195 {
2196 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2197 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2198 AssertStmt(cbUsed <= cbTotal, paBuffers[idxBuffer].pBuf->mAudioDataByteSize = cbUsed = cbTotal);
2199
2200 cbWritable += cbTotal - cbUsed;
2201
2202 /* Advance. */
2203 idxBuffer++;
2204 if (idxBuffer < cBuffers)
2205 { /* likely */ }
2206 else
2207 idxBuffer = 0;
2208 } while (idxBuffer != idxStart && !drvHstAudCaIsBufferQueued(pBuf = paBuffers[idxBuffer].pBuf));
2209 }
2210
2211 RTCritSectLeave(&pStreamCA->CritSect);
2212 }
2213 Log2Func(("returns %#x for '%s'\n", cbWritable, pStreamCA->Cfg.szName));
2214 return cbWritable;
2215}
2216
2217
2218/**
2219 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
2220 */
2221static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvHstAudCaHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
2222 PPDMAUDIOBACKENDSTREAM pStream)
2223{
2224 RT_NOREF(pInterface);
2225 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2226 AssertPtrReturn(pStreamCA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
2227
2228 if (ASMAtomicReadU32(&pStreamCA->enmInitState) == COREAUDIOINITSTATE_INIT)
2229 {
2230 if (!pStreamCA->fDraining)
2231 { /* likely */ }
2232 else
2233 {
2234 /*
2235 * If we're draining, we're done when we've got all the buffers back.
2236 */
2237 RTCritSectEnter(&pStreamCA->CritSect);
2238 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2239 uintptr_t idxBuffer = pStreamCA->cBuffers;
2240 while (idxBuffer-- > 0)
2241 if (!drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf))
2242 { /* likely */ }
2243 else
2244 {
2245#ifdef LOG_ENABLED
2246 uint32_t cQueued = 1;
2247 while (idxBuffer-- > 0)
2248 cQueued += drvHstAudCaIsBufferQueued(paBuffers[idxBuffer].pBuf);
2249 LogFunc(("Still done draining '%s': %u queued buffers\n", pStreamCA->Cfg.szName, cQueued));
2250#endif
2251 RTCritSectLeave(&pStreamCA->CritSect);
2252 return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
2253 }
2254
2255 LogFunc(("Done draining '%s'\n", pStreamCA->Cfg.szName));
2256 pStreamCA->fDraining = false;
2257 pStreamCA->fEnabled = false;
2258 pStreamCA->fStarted = false;
2259 RTCritSectLeave(&pStreamCA->CritSect);
2260 }
2261
2262 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
2263 }
2264 return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING; /** @todo ?? */
2265}
2266
2267/**
2268 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
2269 */
2270static DECLCALLBACK(int) drvHstAudCaHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2271 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
2272{
2273 RT_NOREF(pInterface);
2274 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2275 AssertPtrReturn(pStreamCA, VERR_INVALID_POINTER);
2276 AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
2277 if (cbBuf)
2278 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2279 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2280 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbWritten = 0, VERR_AUDIO_STREAM_NOT_READY);
2281
2282 RTCritSectEnter(&pStreamCA->CritSect);
2283 if (pStreamCA->fEnabled)
2284 { /* likely */ }
2285 else
2286 {
2287 RTCritSectLeave(&pStreamCA->CritSect);
2288 *pcbWritten = 0;
2289 LogFunc(("Skipping %#x byte write to disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2290 return VINF_SUCCESS;
2291 }
2292 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2293
2294 /*
2295 * Transfer loop.
2296 */
2297 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2298 uint32_t const cBuffers = pStreamCA->cBuffers;
2299 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2300 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2301
2302 uint32_t idxBuffer = pStreamCA->idxBuffer;
2303 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2304
2305 int rc = VINF_SUCCESS;
2306 uint32_t cbWritten = 0;
2307 while (cbBuf > 0)
2308 {
2309 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2310
2311 /*
2312 * Check out how much we can put into the current buffer.
2313 */
2314 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2315 if (!drvHstAudCaIsBufferQueued(pBuf))
2316 { /* likely */ }
2317 else
2318 {
2319 LogFunc(("@%#RX64: Warning! Out of buffer space! (%#x bytes unwritten)\n", pStreamCA->offInternal, cbBuf));
2320 /** @todo stats */
2321 break;
2322 }
2323
2324 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2325 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2326 uint32_t cbUsed = pBuf->mAudioDataByteSize;
2327 AssertStmt(cbUsed < cbTotal, cbUsed = cbTotal);
2328 uint32_t const cbAvail = cbTotal - cbUsed;
2329
2330 /*
2331 * Copy over the data.
2332 */
2333 if (cbBuf < cbAvail)
2334 {
2335 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x only - leaving unqueued {%s}\n",
2336 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2337 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbBuf);
2338 pBuf->mAudioDataByteSize = cbUsed + cbBuf;
2339 cbWritten += cbBuf;
2340 pStreamCA->offInternal += cbBuf;
2341 /** @todo Maybe queue it anyway if it's almost full or we haven't got a lot of
2342 * buffers queued. */
2343 break;
2344 }
2345
2346 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, have %#x - will queue {%s}\n",
2347 pStreamCA->offInternal, idxBuffer, cBuffers, cbAvail, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2348 memcpy((uint8_t *)pBuf->mAudioData + cbUsed, pvBuf, cbAvail);
2349 pBuf->mAudioDataByteSize = cbTotal;
2350 cbWritten += cbAvail;
2351 pStreamCA->offInternal += cbAvail;
2352 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
2353
2354 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2355 if (orc == noErr)
2356 { /* likely */ }
2357 else
2358 {
2359 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2360 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2361 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
2362 pBuf->mAudioDataByteSize -= PDMAudioPropsFramesToBytes(&pStreamCA->Cfg.Props, 1); /* avoid assertions above */
2363 rc = VERR_AUDIO_STREAM_NOT_READY;
2364 break;
2365 }
2366
2367 /*
2368 * Advance.
2369 */
2370 idxBuffer += 1;
2371 if (idxBuffer < cBuffers)
2372 { /* likely */ }
2373 else
2374 idxBuffer = 0;
2375 pStreamCA->idxBuffer = idxBuffer;
2376
2377 pvBuf = (const uint8_t *)pvBuf + cbAvail;
2378 cbBuf -= cbAvail;
2379 }
2380
2381 /*
2382 * Start the stream if we haven't do so yet.
2383 */
2384 if ( pStreamCA->fStarted
2385 || cbWritten == 0
2386 || RT_FAILURE_NP(rc))
2387 { /* likely */ }
2388 else
2389 {
2390 UInt32 cFramesPrepared = 0;
2391#if 0 /* taking too long? */
2392 OSStatus orc = AudioQueuePrime(pStreamCA->hAudioQueue, 0 /*inNumberOfFramesToPrepare*/, &cFramesPrepared);
2393 LogFlowFunc(("AudioQueuePrime(%s, 0,) returns %#x (%d) and cFramesPrepared=%u (offInternal=%#RX64)\n",
2394 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2395 AssertMsg(orc == noErr, ("%#x (%d)\n", orc, orc));
2396#else
2397 OSStatus orc;
2398#endif
2399 orc = AudioQueueStart(pStreamCA->hAudioQueue, NULL /*inStartTime*/);
2400 LogFunc(("AudioQueueStart(%s, NULL) returns %#x (%d)\n", pStreamCA->Cfg.szName, orc, orc));
2401 if (orc == noErr)
2402 pStreamCA->fStarted = true;
2403 else
2404 {
2405 LogRelMax(128, ("CoreAudio: Starting '%s' failed: %#x (%d) - %u frames primed, %#x bytes queued\n",
2406 pStreamCA->Cfg.szName, orc, orc, cFramesPrepared, pStreamCA->offInternal));
2407 rc = VERR_AUDIO_STREAM_NOT_READY;
2408 }
2409 }
2410
2411 /*
2412 * Done.
2413 */
2414#ifdef LOG_ENABLED
2415 uint64_t const msPrev = pStreamCA->msLastTransfer;
2416#endif
2417 uint64_t const msNow = RTTimeMilliTS();
2418 if (cbWritten)
2419 pStreamCA->msLastTransfer = msNow;
2420
2421 RTCritSectLeave(&pStreamCA->CritSect);
2422
2423 *pcbWritten = cbWritten;
2424 if (RT_SUCCESS(rc) || !cbWritten)
2425 { }
2426 else
2427 {
2428 LogFlowFunc(("Suppressing %Rrc to report %#x bytes written\n", rc, cbWritten));
2429 rc = VINF_SUCCESS;
2430 }
2431 LogFlowFunc(("@%#RX64: rc=%Rrc cbWritten=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbWritten,
2432 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
2433 return rc;
2434}
2435
2436
2437/**
2438 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
2439 */
2440static DECLCALLBACK(int) drvHstAudCaHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
2441 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
2442{
2443 RT_NOREF(pInterface);
2444 PCOREAUDIOSTREAM pStreamCA = (PCOREAUDIOSTREAM)pStream;
2445 AssertPtrReturn(pStreamCA, 0);
2446 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
2447 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
2448 AssertPtrReturn(pcbRead, VERR_INVALID_POINTER);
2449 Assert(PDMAudioPropsIsSizeAligned(&pStreamCA->Cfg.Props, cbBuf));
2450 AssertReturnStmt(pStreamCA->enmInitState == COREAUDIOINITSTATE_INIT, *pcbRead = 0, VERR_AUDIO_STREAM_NOT_READY);
2451
2452 RTCritSectEnter(&pStreamCA->CritSect);
2453 if (pStreamCA->fEnabled)
2454 { /* likely */ }
2455 else
2456 {
2457 RTCritSectLeave(&pStreamCA->CritSect);
2458 *pcbRead = 0;
2459 LogFunc(("Skipping %#x byte read from disabled stream {%s}\n", cbBuf, drvHstAudCaStreamStatusString(pStreamCA)));
2460 return VINF_SUCCESS;
2461 }
2462 Log4Func(("cbBuf=%#x stream '%s' {%s}\n", cbBuf, pStreamCA->Cfg.szName, drvHstAudCaStreamStatusString(pStreamCA) ));
2463
2464
2465 /*
2466 * Transfer loop.
2467 */
2468 uint32_t const cbFrame = PDMAudioPropsFrameSize(&pStreamCA->Cfg.Props);
2469 PCOREAUDIOBUF const paBuffers = pStreamCA->paBuffers;
2470 uint32_t const cBuffers = pStreamCA->cBuffers;
2471 AssertMsgReturnStmt(cBuffers >= COREAUDIO_MIN_BUFFERS && cBuffers < COREAUDIO_MAX_BUFFERS, ("%u\n", cBuffers),
2472 RTCritSectLeave(&pStreamCA->CritSect), VERR_AUDIO_STREAM_NOT_READY);
2473
2474 uint32_t idxBuffer = pStreamCA->idxBuffer;
2475 AssertStmt(idxBuffer < cBuffers, idxBuffer %= cBuffers);
2476
2477 int rc = VINF_SUCCESS;
2478 uint32_t cbRead = 0;
2479 while (cbBuf > cbFrame)
2480 {
2481 AssertBreakStmt(pStreamCA->hAudioQueue, rc = VERR_AUDIO_STREAM_NOT_READY);
2482
2483 /*
2484 * Check out how much we can read from the current buffer (if anything at all).
2485 */
2486 AudioQueueBufferRef const pBuf = paBuffers[idxBuffer].pBuf;
2487 if (!drvHstAudCaIsBufferQueued(pBuf))
2488 { /* likely */ }
2489 else
2490 {
2491 LogFunc(("@%#RX64: Warning! Underrun! (%#x bytes unread)\n", pStreamCA->offInternal, cbBuf));
2492 /** @todo stats */
2493 break;
2494 }
2495
2496 AssertPtrBreakStmt(pBuf, rc = VERR_INTERNAL_ERROR_2);
2497 uint32_t const cbTotal = pBuf->mAudioDataBytesCapacity;
2498 uint32_t cbValid = pBuf->mAudioDataByteSize;
2499 AssertStmt(cbValid < cbTotal, cbValid = cbTotal);
2500 uint32_t offRead = paBuffers[idxBuffer].offRead;
2501 uint32_t const cbLeft = cbValid - offRead;
2502
2503 /*
2504 * Copy over the data.
2505 */
2506 if (cbBuf < cbLeft)
2507 {
2508 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want %#x - leaving unqueued {%s}\n",
2509 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2510 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbBuf);
2511 paBuffers[idxBuffer].offRead = offRead + cbBuf;
2512 cbRead += cbBuf;
2513 pStreamCA->offInternal += cbBuf;
2514 break;
2515 }
2516
2517 Log3Func(("@%#RX64: buffer #%u/%u: %#x bytes, want all (%#x) - will queue {%s}\n",
2518 pStreamCA->offInternal, idxBuffer, cBuffers, cbLeft, cbBuf, drvHstAudCaStreamStatusString(pStreamCA) ));
2519 memcpy(pvBuf, (uint8_t const *)pBuf->mAudioData + offRead, cbLeft);
2520 cbRead += cbLeft;
2521 pStreamCA->offInternal += cbLeft;
2522
2523 RT_BZERO(pBuf->mAudioData, cbTotal); /* paranoia */
2524 paBuffers[idxBuffer].offRead = 0;
2525 pBuf->mAudioDataByteSize = 0;
2526 drvHstAudCaSetBufferQueued(pBuf, true /*fQueued*/);
2527
2528 OSStatus orc = AudioQueueEnqueueBuffer(pStreamCA->hAudioQueue, pBuf, 0 /*inNumPacketDesc*/, NULL /*inPacketDescs*/);
2529 if (orc == noErr)
2530 { /* likely */ }
2531 else
2532 {
2533 LogRelMax(256, ("CoreAudio: AudioQueueEnqueueBuffer('%s', #%u) failed: %#x (%d)\n",
2534 pStreamCA->Cfg.szName, idxBuffer, orc, orc));
2535 drvHstAudCaSetBufferQueued(pBuf, false /*fQueued*/);
2536 rc = VERR_AUDIO_STREAM_NOT_READY;
2537 break;
2538 }
2539
2540 /*
2541 * Advance.
2542 */
2543 idxBuffer += 1;
2544 if (idxBuffer < cBuffers)
2545 { /* likely */ }
2546 else
2547 idxBuffer = 0;
2548 pStreamCA->idxBuffer = idxBuffer;
2549
2550 pvBuf = (uint8_t *)pvBuf + cbLeft;
2551 cbBuf -= cbLeft;
2552 }
2553
2554 /*
2555 * Done.
2556 */
2557#ifdef LOG_ENABLED
2558 uint64_t const msPrev = pStreamCA->msLastTransfer;
2559#endif
2560 uint64_t const msNow = RTTimeMilliTS();
2561 if (cbRead)
2562 pStreamCA->msLastTransfer = msNow;
2563
2564 RTCritSectLeave(&pStreamCA->CritSect);
2565
2566 *pcbRead = cbRead;
2567 if (RT_SUCCESS(rc) || !cbRead)
2568 { }
2569 else
2570 {
2571 LogFlowFunc(("Suppressing %Rrc to report %#x bytes read\n", rc, cbRead));
2572 rc = VINF_SUCCESS;
2573 }
2574 LogFlowFunc(("@%#RX64: rc=%Rrc cbRead=%RU32 cMsDelta=%RU64 (%RU64 -> %RU64) {%s}\n", pStreamCA->offInternal, rc, cbRead,
2575 msPrev ? msNow - msPrev : 0, msPrev, pStreamCA->msLastTransfer, drvHstAudCaStreamStatusString(pStreamCA) ));
2576 return rc;
2577}
2578
2579
2580/*********************************************************************************************************************************
2581* PDMIBASE *
2582*********************************************************************************************************************************/
2583
2584/**
2585 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
2586 */
2587static DECLCALLBACK(void *) drvHstAudCaQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2588{
2589 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2590 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2591
2592 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2593 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2594
2595 return NULL;
2596}
2597
2598
2599/*********************************************************************************************************************************
2600* PDMDRVREG *
2601*********************************************************************************************************************************/
2602
2603/**
2604 * Worker for the power off and destructor callbacks.
2605 */
2606static void drvHstAudCaRemoveDefaultDeviceListners(PDRVHOSTCOREAUDIO pThis)
2607{
2608 /*
2609 * Unregister system callbacks.
2610 */
2611 AudioObjectPropertyAddress PropAddr =
2612 {
2613 kAudioHardwarePropertyDefaultInputDevice,
2614 kAudioObjectPropertyScopeGlobal,
2615 kAudioObjectPropertyElementMaster
2616 };
2617
2618 OSStatus orc;
2619 if (pThis->fRegisteredDefaultInputListener)
2620 {
2621 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
2622 drvHstAudCaDefaultDeviceChangedCallback, pThis);
2623 if ( orc != noErr
2624 && orc != kAudioHardwareBadObjectError)
2625 LogRel(("CoreAudio: Failed to remove the default input device changed listener: %d (%#x))\n", orc, orc));
2626 pThis->fRegisteredDefaultInputListener = false;
2627 }
2628
2629 if (pThis->fRegisteredDefaultOutputListener)
2630 {
2631 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2632 orc = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &PropAddr,
2633 drvHstAudCaDefaultDeviceChangedCallback, pThis);
2634 if ( orc != noErr
2635 && orc != kAudioHardwareBadObjectError)
2636 LogRel(("CoreAudio: Failed to remove the default output device changed listener: %d (%#x))\n", orc, orc));
2637 pThis->fRegisteredDefaultOutputListener = false;
2638 }
2639
2640 /*
2641 * Unregister device callbacks.
2642 */
2643 RTCritSectEnter(&pThis->CritSect);
2644
2645 drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->InputDevice.idDevice);
2646 pThis->InputDevice.idDevice = kAudioDeviceUnknown;
2647
2648 drvHstAudCaDeviceUnregisterCallbacks(pThis, pThis->OutputDevice.idDevice);
2649 pThis->OutputDevice.idDevice = kAudioDeviceUnknown;
2650
2651 RTCritSectLeave(&pThis->CritSect);
2652
2653 LogFlowFuncEnter();
2654}
2655
2656
2657/**
2658 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
2659 */
2660static DECLCALLBACK(void) drvHstAudCaPowerOff(PPDMDRVINS pDrvIns)
2661{
2662 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2663 drvHstAudCaRemoveDefaultDeviceListners(pThis);
2664}
2665
2666
2667/**
2668 * @callback_method_impl{FNPDMDRVDESTRUCT}
2669 */
2670static DECLCALLBACK(void) drvHstAudCaDestruct(PPDMDRVINS pDrvIns)
2671{
2672 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
2673 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2674
2675 if (RTCritSectIsInitialized(&pThis->CritSect))
2676 drvHstAudCaRemoveDefaultDeviceListners(pThis);
2677
2678#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2679 if (pThis->hThread != NIL_RTTHREAD)
2680 {
2681 for (unsigned iLoop = 0; iLoop < 60; iLoop++)
2682 {
2683 if (pThis->hThreadRunLoop)
2684 CFRunLoopStop(pThis->hThreadRunLoop);
2685 if (iLoop > 10)
2686 RTThreadPoke(pThis->hThread);
2687 int rc = RTThreadWait(pThis->hThread, 500 /*ms*/, NULL /*prcThread*/);
2688 if (RT_SUCCESS(rc))
2689 break;
2690 AssertMsgBreak(rc == VERR_TIMEOUT, ("RTThreadWait -> %Rrc\n",rc));
2691 }
2692 pThis->hThread = NIL_RTTHREAD;
2693 }
2694 if (pThis->hThreadPortSrc)
2695 {
2696 CFRelease(pThis->hThreadPortSrc);
2697 pThis->hThreadPortSrc = NULL;
2698 }
2699 if (pThis->hThreadPort)
2700 {
2701 CFMachPortInvalidate(pThis->hThreadPort);
2702 CFRelease(pThis->hThreadPort);
2703 pThis->hThreadPort = NULL;
2704 }
2705 if (pThis->hThreadRunLoop)
2706 {
2707 CFRelease(pThis->hThreadRunLoop);
2708 pThis->hThreadRunLoop = NULL;
2709 }
2710#endif
2711
2712#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2713 if (pThis->hBreakpointTimer != NIL_RTTIMERLR)
2714 {
2715 RTTimerLRDestroy(pThis->hBreakpointTimer);
2716 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2717 }
2718#endif
2719
2720 if (RTCritSectIsInitialized(&pThis->CritSect))
2721 {
2722 int rc2 = RTCritSectDelete(&pThis->CritSect);
2723 AssertRC(rc2);
2724 }
2725
2726 LogFlowFuncLeave();
2727}
2728
2729
2730/**
2731 * @callback_method_impl{FNPDMDRVCONSTRUCT,
2732 * Construct a Core Audio driver instance.}
2733 */
2734static DECLCALLBACK(int) drvHstAudCaConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2735{
2736 RT_NOREF(pCfg, fFlags);
2737 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
2738 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2739 PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
2740 LogRel(("Audio: Initializing Core Audio driver\n"));
2741
2742 /*
2743 * Init the static parts.
2744 */
2745 pThis->pDrvIns = pDrvIns;
2746#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2747 pThis->hThread = NIL_RTTHREAD;
2748#endif
2749#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2750 pThis->hBreakpointTimer = NIL_RTTIMERLR;
2751#endif
2752 /* IBase */
2753 pDrvIns->IBase.pfnQueryInterface = drvHstAudCaQueryInterface;
2754 /* IHostAudio */
2755 pThis->IHostAudio.pfnGetConfig = drvHstAudCaHA_GetConfig;
2756 pThis->IHostAudio.pfnGetDevices = drvHstAudCaHA_GetDevices;
2757 pThis->IHostAudio.pfnSetDevice = drvHstAudCaHA_SetDevice;
2758 pThis->IHostAudio.pfnGetStatus = drvHstAudCaHA_GetStatus;
2759 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
2760 pThis->IHostAudio.pfnStreamConfigHint = NULL;
2761 pThis->IHostAudio.pfnStreamCreate = drvHstAudCaHA_StreamCreate;
2762 pThis->IHostAudio.pfnStreamInitAsync = NULL;
2763 pThis->IHostAudio.pfnStreamDestroy = drvHstAudCaHA_StreamDestroy;
2764 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
2765 pThis->IHostAudio.pfnStreamEnable = drvHstAudCaHA_StreamEnable;
2766 pThis->IHostAudio.pfnStreamDisable = drvHstAudCaHA_StreamDisable;
2767 pThis->IHostAudio.pfnStreamPause = drvHstAudCaHA_StreamPause;
2768 pThis->IHostAudio.pfnStreamResume = drvHstAudCaHA_StreamResume;
2769 pThis->IHostAudio.pfnStreamDrain = drvHstAudCaHA_StreamDrain;
2770 pThis->IHostAudio.pfnStreamGetReadable = drvHstAudCaHA_StreamGetReadable;
2771 pThis->IHostAudio.pfnStreamGetWritable = drvHstAudCaHA_StreamGetWritable;
2772 pThis->IHostAudio.pfnStreamGetPending = NULL;
2773 pThis->IHostAudio.pfnStreamGetState = drvHstAudCaHA_StreamGetState;
2774 pThis->IHostAudio.pfnStreamPlay = drvHstAudCaHA_StreamPlay;
2775 pThis->IHostAudio.pfnStreamCapture = drvHstAudCaHA_StreamCapture;
2776
2777 int rc = RTCritSectInit(&pThis->CritSect);
2778 AssertRCReturn(rc, rc);
2779
2780 /*
2781 * Validate and read configuration.
2782 */
2783 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "InputDeviceID|OutputDeviceID", "");
2784
2785 char *pszTmp = NULL;
2786 rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "InputDeviceID", &pszTmp);
2787 if (RT_SUCCESS(rc))
2788 {
2789 rc = drvHstAudCaSetDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotify*/, pszTmp);
2790 PDMDrvHlpMMHeapFree(pDrvIns, pszTmp);
2791 }
2792 else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
2793 return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'InputDeviceID'");
2794
2795 rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "OutputDeviceID", &pszTmp);
2796 if (RT_SUCCESS(rc))
2797 {
2798 rc = drvHstAudCaSetDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotify*/, pszTmp);
2799 PDMDrvHlpMMHeapFree(pDrvIns, pszTmp);
2800 }
2801 else if (rc != VERR_CFGM_VALUE_NOT_FOUND && rc != VERR_CFGM_NO_PARENT)
2802 return PDMDRV_SET_ERROR(pDrvIns, rc, "Failed to query 'OutputDeviceID'");
2803
2804 /*
2805 * Query the notification interface from the driver/device above us.
2806 */
2807 pThis->pIHostAudioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTAUDIOPORT);
2808 AssertReturn(pThis->pIHostAudioPort, VERR_PDM_MISSING_INTERFACE_ABOVE);
2809
2810#ifdef CORE_AUDIO_WITH_WORKER_THREAD
2811 /*
2812 * Create worker thread for running callbacks on.
2813 */
2814 CFMachPortContext PortCtx;
2815 PortCtx.version = 0;
2816 PortCtx.info = pThis;
2817 PortCtx.retain = NULL;
2818 PortCtx.release = NULL;
2819 PortCtx.copyDescription = NULL;
2820 pThis->hThreadPort = CFMachPortCreate(NULL /*allocator*/, drvHstAudCaThreadPortCallback, &PortCtx, NULL);
2821 AssertLogRelReturn(pThis->hThreadPort != NULL, VERR_NO_MEMORY);
2822
2823 pThis->hThreadPortSrc = CFMachPortCreateRunLoopSource(NULL, pThis->hThreadPort, 0 /*order*/);
2824 AssertLogRelReturn(pThis->hThreadPortSrc != NULL, VERR_NO_MEMORY);
2825
2826 rc = RTThreadCreateF(&pThis->hThread, drvHstAudCaThread, pThis, 0, RTTHREADTYPE_IO,
2827 RTTHREADFLAGS_WAITABLE, "CaAud-%u", pDrvIns->iInstance);
2828 AssertLogRelMsgReturn(RT_SUCCESS(rc), ("RTThreadCreateF failed: %Rrc\n", rc), rc);
2829
2830 RTThreadUserWait(pThis->hThread, RT_MS_10SEC);
2831 AssertLogRel(pThis->hThreadRunLoop);
2832#endif
2833
2834#ifdef CORE_AUDIO_WITH_BREAKPOINT_TIMER
2835 /*
2836 * Create a IPRT timer. The TM timers won't necessarily work as EMT is probably busy.
2837 */
2838 rc = RTTimerLRCreateEx(&pThis->hBreakpointTimer, 0 /*no interval*/, 0, drvHstAudCaBreakpointTimer, pThis);
2839 AssertRCReturn(rc, rc);
2840#endif
2841
2842 /*
2843 * Determin the default devices.
2844 */
2845 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->OutputDevice, false /*fInput*/, false /*fNotifty*/);
2846 drvHstAudCaUpdateOneDefaultDevice(pThis, &pThis->InputDevice, true /*fInput*/, false /*fNotifty*/);
2847
2848 /*
2849 * Register callbacks for default device input and output changes.
2850 * (We just ignore failures here as there isn't much we can do about it,
2851 * and it isn't 100% critical.)
2852 */
2853 AudioObjectPropertyAddress PropAddr =
2854 {
2855 /* .mSelector = */ kAudioHardwarePropertyDefaultInputDevice,
2856 /* .mScope = */ kAudioObjectPropertyScopeGlobal,
2857 /* .mElement = */ kAudioObjectPropertyElementMaster
2858 };
2859
2860 OSStatus orc;
2861 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
2862 pThis->fRegisteredDefaultInputListener = orc == noErr;
2863 if ( orc != noErr
2864 && orc != kAudioHardwareIllegalOperationError)
2865 LogRel(("CoreAudio: Failed to add the input default device changed listener: %d (%#x)\n", orc, orc));
2866
2867 PropAddr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
2868 orc = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &PropAddr, drvHstAudCaDefaultDeviceChangedCallback, pThis);
2869 pThis->fRegisteredDefaultOutputListener = orc == noErr;
2870 if ( orc != noErr
2871 && orc != kAudioHardwareIllegalOperationError)
2872 LogRel(("CoreAudio: Failed to add the output default device changed listener: %d (%#x)\n", orc, orc));
2873
2874 LogFlowFuncLeaveRC(rc);
2875 return rc;
2876}
2877
2878
2879/**
2880 * Char driver registration record.
2881 */
2882const PDMDRVREG g_DrvHostCoreAudio =
2883{
2884 /* u32Version */
2885 PDM_DRVREG_VERSION,
2886 /* szName */
2887 "CoreAudio",
2888 /* szRCMod */
2889 "",
2890 /* szR0Mod */
2891 "",
2892 /* pszDescription */
2893 "Core Audio host driver",
2894 /* fFlags */
2895 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2896 /* fClass. */
2897 PDM_DRVREG_CLASS_AUDIO,
2898 /* cMaxInstances */
2899 ~0U,
2900 /* cbInstance */
2901 sizeof(DRVHOSTCOREAUDIO),
2902 /* pfnConstruct */
2903 drvHstAudCaConstruct,
2904 /* pfnDestruct */
2905 drvHstAudCaDestruct,
2906 /* pfnRelocate */
2907 NULL,
2908 /* pfnIOCtl */
2909 NULL,
2910 /* pfnPowerOn */
2911 NULL,
2912 /* pfnReset */
2913 NULL,
2914 /* pfnSuspend */
2915 NULL,
2916 /* pfnResume */
2917 NULL,
2918 /* pfnAttach */
2919 NULL,
2920 /* pfnDetach */
2921 NULL,
2922 /* pfnPowerOff */
2923 drvHstAudCaPowerOff,
2924 /* pfnSoftReset */
2925 NULL,
2926 /* u32EndVersion */
2927 PDM_DRVREG_VERSION
2928};
2929
2930RT_GCC_NO_WARN_DEPRECATED_END
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette