VirtualBox

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

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