VirtualBox

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

Last change on this file since 93368 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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