VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/DrvHostCoreAudio.cpp@ 59174

Last change on this file since 59174 was 59097, checked in by vboxsync, 9 years ago

Audio: Implemented PDMAUDIOSTREAMCMD_PAUSE/PDMAUDIOSTREAMCMD_RESUME.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 81.0 KB
Line 
1/* $Id: DrvHostCoreAudio.cpp 59097 2015-12-11 15:43:12Z vboxsync $ */
2/** @file
3 * VBox audio devices: Mac OS X CoreAudio audio driver.
4 */
5
6/*
7 * Copyright (C) 2010-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
18#include <VBox/log.h>
19
20#include "DrvAudio.h"
21#include "AudioMixBuffer.h"
22
23#include "VBoxDD.h"
24
25#include <iprt/asm.h>
26#include <iprt/cdefs.h>
27#include <iprt/circbuf.h>
28#include <iprt/mem.h>
29
30#include <iprt/uuid.h>
31
32#include <CoreAudio/CoreAudio.h>
33#include <CoreServices/CoreServices.h>
34#include <AudioUnit/AudioUnit.h>
35#include <AudioToolbox/AudioConverter.h>
36
37/* TODO:
38 * - Maybe make sure the threads are immediately stopped if playing/recording stops.
39 */
40
41/*
42 * Most of this is based on:
43 * http://developer.apple.com/mac/library/technotes/tn2004/tn2097.html
44 * http://developer.apple.com/mac/library/technotes/tn2002/tn2091.html
45 * http://developer.apple.com/mac/library/qa/qa2007/qa1533.html
46 * http://developer.apple.com/mac/library/qa/qa2001/qa1317.html
47 * http://developer.apple.com/mac/library/documentation/AudioUnit/Reference/AUComponentServicesReference/Reference/reference.html
48 */
49
50/**
51 * Host Coreaudio driver instance data.
52 * @implements PDMIAUDIOCONNECTOR
53 */
54typedef struct DRVHOSTCOREAUDIO
55{
56 /** Pointer to the driver instance structure. */
57 PPDMDRVINS pDrvIns;
58 /** Pointer to host audio interface. */
59 PDMIHOSTAUDIO IHostAudio;
60} DRVHOSTCOREAUDIO, *PDRVHOSTCOREAUDIO;
61
62/*******************************************************************************
63 *
64 * Helper function section
65 *
66 ******************************************************************************/
67
68#ifdef DEBUG
69static void drvHostCoreAudioPrintASBDesc(const char *pszDesc, const AudioStreamBasicDescription *pStreamDesc)
70{
71 char pszSampleRate[32];
72 Log(("%s AudioStreamBasicDescription:\n", pszDesc));
73 LogFlowFunc(("Format ID: %RU32 (%c%c%c%c)\n", pStreamDesc->mFormatID,
74 RT_BYTE4(pStreamDesc->mFormatID), RT_BYTE3(pStreamDesc->mFormatID),
75 RT_BYTE2(pStreamDesc->mFormatID), RT_BYTE1(pStreamDesc->mFormatID)));
76 LogFlowFunc(("Flags: %RU32", pStreamDesc->mFormatFlags));
77 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsFloat)
78 Log((" Float"));
79 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsBigEndian)
80 Log((" BigEndian"));
81 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsSignedInteger)
82 Log((" SignedInteger"));
83 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsPacked)
84 Log((" Packed"));
85 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsAlignedHigh)
86 Log((" AlignedHigh"));
87 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsNonInterleaved)
88 Log((" NonInterleaved"));
89 if (pStreamDesc->mFormatFlags & kAudioFormatFlagIsNonMixable)
90 Log((" NonMixable"));
91 if (pStreamDesc->mFormatFlags & kAudioFormatFlagsAreAllClear)
92 Log((" AllClear"));
93 Log(("\n"));
94 snprintf(pszSampleRate, 32, "%.2f", (float)pStreamDesc->mSampleRate); /** @todo r=andy Use RTStrPrint*. */
95 LogFlowFunc(("SampleRate : %s\n", pszSampleRate));
96 LogFlowFunc(("ChannelsPerFrame: %RU32\n", pStreamDesc->mChannelsPerFrame));
97 LogFlowFunc(("FramesPerPacket : %RU32\n", pStreamDesc->mFramesPerPacket));
98 LogFlowFunc(("BitsPerChannel : %RU32\n", pStreamDesc->mBitsPerChannel));
99 LogFlowFunc(("BytesPerFrame : %RU32\n", pStreamDesc->mBytesPerFrame));
100 LogFlowFunc(("BytesPerPacket : %RU32\n", pStreamDesc->mBytesPerPacket));
101}
102#endif /* DEBUG */
103
104static void drvHostCoreAudioPCMInfoToASBDesc(PDMPCMPROPS *pPcmProperties, AudioStreamBasicDescription *pStreamDesc)
105{
106 pStreamDesc->mFormatID = kAudioFormatLinearPCM;
107 pStreamDesc->mFormatFlags = kAudioFormatFlagIsPacked;
108 pStreamDesc->mFramesPerPacket = 1;
109 pStreamDesc->mSampleRate = (Float64)pPcmProperties->uHz;
110 pStreamDesc->mChannelsPerFrame = pPcmProperties->cChannels;
111 pStreamDesc->mBitsPerChannel = pPcmProperties->cBits;
112 if (pPcmProperties->fSigned)
113 pStreamDesc->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
114 pStreamDesc->mBytesPerFrame = pStreamDesc->mChannelsPerFrame * (pStreamDesc->mBitsPerChannel / 8);
115 pStreamDesc->mBytesPerPacket = pStreamDesc->mFramesPerPacket * pStreamDesc->mBytesPerFrame;
116}
117
118static OSStatus drvHostCoreAudioSetFrameBufferSize(AudioDeviceID deviceID, bool fInput, UInt32 cReqSize, UInt32 *pcActSize)
119{
120 AudioObjectPropertyScope propScope = fInput
121 ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
122 AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyBufferFrameSize, propScope,
123 kAudioObjectPropertyElementMaster };
124
125 /* First try to set the new frame buffer size. */
126 OSStatus err = AudioObjectSetPropertyData(deviceID, &propAdr, NULL, 0, sizeof(cReqSize), &cReqSize);
127
128 /* Check if it really was set. */
129 UInt32 cSize = sizeof(*pcActSize);
130 err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &cSize, pcActSize);
131 if (RT_UNLIKELY(err != noErr))
132 return err;
133
134 /* If both sizes are the same, we are done. */
135 if (cReqSize == *pcActSize)
136 return noErr;
137
138 /* If not we have to check the limits of the device. First get the size of
139 the buffer size range property. */
140 propAdr.mSelector = kAudioDevicePropertyBufferSizeRange;
141 err = AudioObjectGetPropertyDataSize(deviceID, &propAdr, 0, NULL, &cSize);
142 if (RT_UNLIKELY(err != noErr))
143 return err;
144
145 Assert(cSize);
146 AudioValueRange *pRange = (AudioValueRange *)RTMemAllocZ(cSize);
147 if (pRange)
148 {
149 err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &cSize, pRange);
150 if (err == noErr)
151 {
152 Float64 cMin = -1;
153 Float64 cMax = -1;
154 for (size_t a = 0; a < cSize / sizeof(AudioValueRange); a++)
155 {
156 /* Search for the absolute minimum. */
157 if ( pRange[a].mMinimum < cMin
158 || cMin == -1)
159 cMin = pRange[a].mMinimum;
160
161 /* Search for the best maximum which isn't bigger than cReqSize. */
162 if (pRange[a].mMaximum < cReqSize)
163 {
164 if (pRange[a].mMaximum > cMax)
165 cMax = pRange[a].mMaximum;
166 }
167 }
168 if (cMax == -1)
169 cMax = cMin;
170 cReqSize = cMax;
171
172 /* First try to set the new frame buffer size. */
173 propAdr.mSelector = kAudioDevicePropertyBufferFrameSize;
174 err = AudioObjectSetPropertyData(deviceID, &propAdr, 0, NULL, sizeof(cReqSize), &cReqSize);
175 if (err == noErr)
176 {
177 /* Check if it really was set. */
178 cSize = sizeof(*pcActSize);
179 err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &cSize, pcActSize);
180 }
181 }
182
183 RTMemFree(pRange);
184 }
185 else
186 err = notEnoughMemoryErr;
187
188 return err;
189}
190
191DECL_FORCE_INLINE(bool) drvHostCoreAudioIsRunning(AudioDeviceID deviceID)
192{
193 AudioObjectPropertyAddress propAdr = { kAudioDevicePropertyDeviceIsRunning, kAudioObjectPropertyScopeGlobal,
194 kAudioObjectPropertyElementMaster };
195 UInt32 uFlag = 0;
196 UInt32 uSize = sizeof(uFlag);
197 OSStatus err = AudioObjectGetPropertyData(deviceID, &propAdr, 0, NULL, &uSize, &uFlag);
198 if (err != kAudioHardwareNoError)
199 LogRel(("CoreAudio: Could not determine whether the device is running (%RI32)\n", err));
200
201 return (uFlag >= 1);
202}
203
204static int drvHostCoreAudioCFStringToCString(const CFStringRef pCFString, char **ppszString)
205{
206 CFIndex cLen = CFStringGetLength(pCFString) + 1;
207 char *pszResult = (char *)RTMemAllocZ(cLen * sizeof(char));
208 if (!CFStringGetCString(pCFString, pszResult, cLen, kCFStringEncodingUTF8))
209 {
210 RTMemFree(pszResult);
211 return VERR_NOT_FOUND;
212 }
213
214 *ppszString = pszResult;
215 return VINF_SUCCESS;
216}
217
218static AudioDeviceID drvHostCoreAudioDeviceUIDtoID(const char* pszUID)
219{
220 /* Create a CFString out of our CString. */
221 CFStringRef strUID = CFStringCreateWithCString(NULL, pszUID, kCFStringEncodingMacRoman);
222
223 /* Fill the translation structure. */
224 AudioDeviceID deviceID;
225
226 AudioValueTranslation translation;
227 translation.mInputData = &strUID;
228 translation.mInputDataSize = sizeof(CFStringRef);
229 translation.mOutputData = &deviceID;
230 translation.mOutputDataSize = sizeof(AudioDeviceID);
231
232 /* Fetch the translation from the UID to the device ID. */
233 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDeviceForUID, kAudioObjectPropertyScopeGlobal,
234 kAudioObjectPropertyElementMaster };
235
236 UInt32 uSize = sizeof(AudioValueTranslation);
237 OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &translation);
238
239 /* Release the temporary CFString */
240 CFRelease(strUID);
241
242 if (RT_LIKELY(err == noErr))
243 return deviceID;
244
245 /* Return the unknown device on error. */
246 return kAudioDeviceUnknown;
247}
248
249/*******************************************************************************
250 *
251 * Global structures section
252 *
253 ******************************************************************************/
254
255/* Initialization status indicator used for the recreation of the AudioUnits. */
256#define CA_STATUS_UNINIT UINT32_C(0) /* The device is uninitialized */
257#define CA_STATUS_IN_INIT UINT32_C(1) /* The device is currently initializing */
258#define CA_STATUS_INIT UINT32_C(2) /* The device is initialized */
259#define CA_STATUS_IN_UNINIT UINT32_C(3) /* The device is currently uninitializing */
260#define CA_STATUS_REINIT UINT32_C(4) /* The device has to be reinitialized */
261
262/* Error code which indicates "End of data" */
263static const OSStatus caConverterEOFDErr = 0x656F6664; /* 'eofd' */
264
265typedef struct COREAUDIOSTREAMOUT
266{
267 /** Host stream out. */
268 PDMAUDIOHSTSTRMOUT streamOut;
269 /* Stream description which is default on the device */
270 AudioStreamBasicDescription deviceFormat;
271 /* Stream description which is selected for using by VBox */
272 AudioStreamBasicDescription streamFormat;
273 /* The audio device ID of the currently used device */
274 AudioDeviceID deviceID;
275 /* The AudioUnit used */
276 AudioUnit audioUnit;
277 /* A ring buffer for transferring data to the playback thread. */
278 PRTCIRCBUF pBuf;
279 /* Initialization status tracker. Used when some of the device parameters
280 * or the device itself is changed during the runtime. */
281 volatile uint32_t status;
282 /** Flag whether the "default device changed" listener was registered. */
283 bool fDefDevChgListReg;
284} COREAUDIOSTREAMOUT, *PCOREAUDIOSTREAMOUT;
285
286typedef struct COREAUDIOSTREAMIN
287{
288 /** Host stream in. */
289 PDMAUDIOHSTSTRMIN streamIn;
290 /* Stream description which is default on the device */
291 AudioStreamBasicDescription deviceFormat;
292 /* Stream description which is selected for using by VBox */
293 AudioStreamBasicDescription streamFormat;
294 /* The audio device ID of the currently used device */
295 AudioDeviceID deviceID;
296 /* The AudioUnit used */
297 AudioUnit audioUnit;
298 /* The audio converter if necessary */
299 AudioConverterRef converter;
300 /* A temporary position value used in the caConverterCallback function */
301 uint32_t rpos;
302 /* The ratio between the device & the stream sample rate */
303 Float64 sampleRatio;
304 /* An extra buffer used for render the audio data in the recording thread */
305 AudioBufferList bufferList;
306 /* A ring buffer for transferring data from the recording thread */
307 PRTCIRCBUF pBuf;
308 /* Initialization status tracker. Used when some of the device parameters
309 * or the device itself is changed during the runtime. */
310 volatile uint32_t status;
311 /** Flag whether the "default device changed" listener was registered. */
312 bool fDefDevChgListReg;
313} COREAUDIOSTREAMIN, *PCOREAUDIOSTREAMIN;
314
315static int drvHostCoreAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn, PDMAUDIOSTREAMCMD enmStreamCmd);
316static int drvHostCoreAudioInitInput(PPDMAUDIOHSTSTRMIN pHstStrmIn, uint32_t *pcSamples);
317static int drvHostCoreAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn);
318static int drvHostCoreAudioReinitInput(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn);
319
320static int drvHostCoreAudioControlOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut, PDMAUDIOSTREAMCMD enmStreamCmd);
321static int drvHostCoreAudioInitOutput(PPDMAUDIOHSTSTRMOUT pHstStrmOut, uint32_t *pcSamples);
322static int drvHostCoreAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut);
323static int drvHostCoreAudioReinitOutput(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut);
324static OSStatus drvHostCoreAudioPlaybackAudioDevicePropertyChanged(AudioObjectID propertyID, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void *pvUser);
325static OSStatus drvHostCoreAudioPlaybackCallback(void *pvUser, AudioUnitRenderActionFlags *pActionFlags, const AudioTimeStamp *pAudioTS, UInt32 uBusID, UInt32 cFrames, AudioBufferList* pBufData);
326
327/* Callback for getting notified when the default input/output device has been changed. */
328static DECLCALLBACK(OSStatus) drvHostCoreAudioDefaultDeviceChanged(AudioObjectID propertyID,
329 UInt32 nAddresses,
330 const AudioObjectPropertyAddress properties[],
331 void *pvUser)
332{
333 OSStatus err = noErr;
334
335 LogFlowFunc(("propertyID=%u nAddresses=%u pvUser=%p\n", propertyID, nAddresses, pvUser));
336
337 for (UInt32 idxAddress = 0; idxAddress < nAddresses; idxAddress++)
338 {
339 const AudioObjectPropertyAddress *pProperty = &properties[idxAddress];
340
341 switch (pProperty->mSelector)
342 {
343 case kAudioHardwarePropertyDefaultInputDevice:
344 {
345 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pvUser;
346
347 /* This listener is called on every change of the hardware
348 * device. So check if the default device has really changed. */
349 UInt32 uSize = sizeof(pStreamIn->deviceID);
350 UInt32 uResp;
351 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, pProperty, 0, NULL, &uSize, &uResp);
352
353 if (err == noErr)
354 {
355 if (pStreamIn->deviceID != uResp)
356 {
357 LogRel(("CoreAudio: Default input device has changed\n"));
358
359 /* We move the reinitialization to the next input event.
360 * This make sure this thread isn't blocked and the
361 * reinitialization is done when necessary only. */
362 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_REINIT);
363 }
364 }
365 break;
366 }
367
368 case kAudioHardwarePropertyDefaultOutputDevice:
369 {
370 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pvUser;
371
372 /* This listener is called on every change of the hardware
373 * device. So check if the default device has really changed. */
374 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultOutputDevice,
375 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
376
377 UInt32 uSize = sizeof(pStreamOut->deviceID);
378 UInt32 uResp;
379 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &uResp);
380
381 if (err == noErr)
382 {
383 if (pStreamOut->deviceID != uResp)
384 {
385 LogRel(("CoreAudio: Default output device has changed\n"));
386
387 /* We move the reinitialization to the next input event.
388 * This make sure this thread isn't blocked and the
389 * reinitialization is done when necessary only. */
390 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_REINIT);
391 }
392 }
393 break;
394 }
395
396 default:
397 break;
398 }
399 }
400
401 return noErr;
402}
403
404static int drvHostCoreAudioReinitInput(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
405{
406 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
407 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
408
409 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pHstStrmIn;
410
411 drvHostCoreAudioFiniIn(pInterface, &pStreamIn->streamIn);
412
413 drvHostCoreAudioInitInput(&pStreamIn->streamIn, NULL /* pcSamples */);
414 drvHostCoreAudioControlIn(pInterface, &pStreamIn->streamIn, PDMAUDIOSTREAMCMD_ENABLE);
415
416 return VINF_SUCCESS;
417}
418
419static int drvHostCoreAudioReinitOutput(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
420{
421 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
422 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
423
424 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
425
426 drvHostCoreAudioFiniOut(pInterface, &pStreamOut->streamOut);
427
428 drvHostCoreAudioInitOutput(&pStreamOut->streamOut, NULL /* pcSamples */);
429 drvHostCoreAudioControlOut(pInterface, &pStreamOut->streamOut, PDMAUDIOSTREAMCMD_ENABLE);
430
431 return VINF_SUCCESS;
432}
433
434/* Callback for getting notified when some of the properties of an audio device has changed. */
435static DECLCALLBACK(OSStatus) drvHostCoreAudioRecordingAudioDevicePropertyChanged(AudioObjectID propertyID,
436 UInt32 cAdresses,
437 const AudioObjectPropertyAddress aProperties[],
438 void *pvUser)
439{
440 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pvUser;
441
442 switch (propertyID)
443 {
444#ifdef DEBUG
445 case kAudioDeviceProcessorOverload:
446 {
447 LogFunc(("Processor overload detected!\n"));
448 break;
449 }
450#endif /* DEBUG */
451 case kAudioDevicePropertyNominalSampleRate:
452 {
453 LogRel(("CoreAudio: Recording sample rate changed\n"));
454
455 /* We move the reinitialization to the next input event.
456 * This make sure this thread isn't blocked and the
457 * reinitialization is done when necessary only. */
458 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_REINIT);
459 break;
460 }
461
462 default:
463 break;
464 }
465
466 return noErr;
467}
468
469/* Callback to convert audio input data from one format to another. */
470static DECLCALLBACK(OSStatus) drvHostCoreAudioConverterCallback(AudioConverterRef converterID,
471 UInt32 *pcPackets,
472 AudioBufferList *pBufData,
473 AudioStreamPacketDescription **ppPacketDesc,
474 void *pvUser)
475{
476 /** @todo Check incoming pointers. */
477
478 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pvUser;
479
480 /** @todo Check converter ID? */
481
482 const AudioBufferList *pBufferList = &pStreamIn->bufferList;
483
484 if (ASMAtomicReadU32(&pStreamIn->status) != CA_STATUS_INIT)
485 return noErr;
486
487 /** @todo In principle we had to check here if the source is non interleaved, and if so,
488 * so go through all buffers not only the first one like now. */
489
490 /* Use the lower one of the packets to process & the available packets in the buffer. */
491 Assert(pBufferList->mBuffers[0].mDataByteSize >= pStreamIn->rpos);
492 UInt32 cSize = RT_MIN(*pcPackets * pStreamIn->deviceFormat.mBytesPerPacket,
493 pBufferList->mBuffers[0].mDataByteSize - pStreamIn->rpos);
494
495 /* Set the new size on output, so the caller know what we have processed. */
496 Assert(pStreamIn->deviceFormat.mBytesPerPacket);
497 *pcPackets = cSize / pStreamIn->deviceFormat.mBytesPerPacket;
498
499 OSStatus err;
500
501 /* If no data is available anymore we return with an error code. This error code will be returned
502 * from AudioConverterFillComplexBuffer. */
503 if (*pcPackets == 0)
504 {
505 pBufData->mBuffers[0].mDataByteSize = 0;
506 pBufData->mBuffers[0].mData = NULL;
507
508 err = caConverterEOFDErr;
509 }
510 else
511 {
512 pBufData->mBuffers[0].mNumberChannels = pBufferList->mBuffers[0].mNumberChannels;
513 pBufData->mBuffers[0].mDataByteSize = cSize;
514 pBufData->mBuffers[0].mData = (uint8_t *)pBufferList->mBuffers[0].mData + pStreamIn->rpos;
515
516 pStreamIn->rpos += cSize;
517
518 err = noErr;
519 }
520
521 return err;
522}
523
524/* Callback to feed audio input buffer. */
525static DECLCALLBACK(OSStatus) drvHostCoreAudioRecordingCallback(void *pvUser,
526 AudioUnitRenderActionFlags *pActionFlags,
527 const AudioTimeStamp *pAudioTS,
528 UInt32 uBusID,
529 UInt32 cFrames,
530 AudioBufferList *pBufData)
531{
532 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pvUser;
533 PPDMAUDIOHSTSTRMIN pHstStrmIN = &pStreamIn->streamIn;
534
535 if (ASMAtomicReadU32(&pStreamIn->status) != CA_STATUS_INIT)
536 return noErr;
537
538 /* If nothing is pending return immediately. */
539 if (cFrames == 0)
540 return noErr;
541
542 OSStatus err = noErr;
543 int rc = VINF_SUCCESS;
544
545 do
546 {
547 /* Are we using a converter? */
548 if (pStreamIn->converter)
549 {
550 /* First, render the data as usual. */
551 pStreamIn->bufferList.mBuffers[0].mNumberChannels = pStreamIn->deviceFormat.mChannelsPerFrame;
552 pStreamIn->bufferList.mBuffers[0].mDataByteSize = pStreamIn->deviceFormat.mBytesPerFrame * cFrames;
553 AssertBreakStmt(pStreamIn->bufferList.mBuffers[0].mDataByteSize, rc = VERR_INVALID_PARAMETER);
554 pStreamIn->bufferList.mBuffers[0].mData = RTMemAlloc(pStreamIn->bufferList.mBuffers[0].mDataByteSize);
555 if (!pStreamIn->bufferList.mBuffers[0].mData)
556 {
557 rc = VERR_NO_MEMORY;
558 break;
559 }
560
561 err = AudioUnitRender(pStreamIn->audioUnit, pActionFlags, pAudioTS, uBusID, cFrames, &pStreamIn->bufferList);
562 if (err != noErr)
563 {
564 LogFlowFunc(("Failed rendering audio data (%RI32)\n", err));
565 rc = VERR_IO_GEN_FAILURE; /** @todo Improve this. */
566 break;
567 }
568
569 size_t cbAvail = RT_MIN(RTCircBufFree(pStreamIn->pBuf), pStreamIn->bufferList.mBuffers[0].mDataByteSize);
570
571 /* Initialize the temporary output buffer */
572 AudioBufferList tmpList;
573 tmpList.mNumberBuffers = 1;
574 tmpList.mBuffers[0].mNumberChannels = pStreamIn->streamFormat.mChannelsPerFrame;
575
576 /* Iterate as long as data is available. */
577 uint8_t *puDst = NULL;
578 while (cbAvail)
579 {
580 /* Try to acquire the necessary space from the ring buffer. */
581 size_t cbToWrite = 0;
582 RTCircBufAcquireWriteBlock(pStreamIn->pBuf, cbAvail, (void **)&puDst, &cbToWrite);
583 if (!cbToWrite)
584 break;
585
586 /* Now set how much space is available for output. */
587 Assert(pStreamIn->streamFormat.mBytesPerPacket);
588
589 UInt32 ioOutputDataPacketSize = cbToWrite / pStreamIn->streamFormat.mBytesPerPacket;
590
591 /* Set our ring buffer as target. */
592 tmpList.mBuffers[0].mDataByteSize = cbToWrite;
593 tmpList.mBuffers[0].mData = puDst;
594
595 AudioConverterReset(pStreamIn->converter);
596
597 err = AudioConverterFillComplexBuffer(pStreamIn->converter, drvHostCoreAudioConverterCallback, pStreamIn,
598 &ioOutputDataPacketSize, &tmpList, NULL);
599 if( err != noErr
600 && err != caConverterEOFDErr)
601 {
602 LogFlowFunc(("Failed to convert audio data (%RI32:%c%c%c%c)\n", err,
603 RT_BYTE4(err), RT_BYTE3(err), RT_BYTE2(err), RT_BYTE1(err)));
604 rc = VERR_IO_GEN_FAILURE;
605 break;
606 }
607
608 /* Check in any case what processed size is returned. It could be less than we expected. */
609 cbToWrite = ioOutputDataPacketSize * pStreamIn->streamFormat.mBytesPerPacket;
610
611 /* Release the ring buffer, so the main thread could start reading this data. */
612 RTCircBufReleaseWriteBlock(pStreamIn->pBuf, cbToWrite);
613
614 /* If the error is "End of Data" it means there is no data anymore
615 * which could be converted. So end here now. */
616 if (err == caConverterEOFDErr)
617 break;
618
619 Assert(cbAvail >= cbToWrite);
620 cbAvail -= cbToWrite;
621 }
622 }
623 else /* No converter being used. */
624 {
625 pStreamIn->bufferList.mBuffers[0].mNumberChannels = pStreamIn->streamFormat.mChannelsPerFrame;
626 pStreamIn->bufferList.mBuffers[0].mDataByteSize = pStreamIn->streamFormat.mBytesPerFrame * cFrames;
627 AssertBreakStmt(pStreamIn->bufferList.mBuffers[0].mDataByteSize, rc = VERR_INVALID_PARAMETER);
628 pStreamIn->bufferList.mBuffers[0].mData = RTMemAlloc(pStreamIn->bufferList.mBuffers[0].mDataByteSize);
629 if (!pStreamIn->bufferList.mBuffers[0].mData)
630 {
631 rc = VERR_NO_MEMORY;
632 break;
633 }
634
635 err = AudioUnitRender(pStreamIn->audioUnit, pActionFlags, pAudioTS, uBusID, cFrames, &pStreamIn->bufferList);
636 if (err != noErr)
637 {
638 LogFlowFunc(("Failed rendering audio data (%RI32)\n", err));
639 rc = VERR_IO_GEN_FAILURE; /** @todo Improve this. */
640 break;
641 }
642
643 size_t cbAvail = RT_MIN(RTCircBufFree(pStreamIn->pBuf), pStreamIn->bufferList.mBuffers[0].mDataByteSize);
644
645 /* Iterate as long as data is available. */
646 uint8_t *puDst = NULL;
647 uint32_t cbWrittenTotal = 0;
648 while(cbAvail)
649 {
650 /* Try to acquire the necessary space from the ring buffer. */
651 size_t cbToWrite = 0;
652 RTCircBufAcquireWriteBlock(pStreamIn->pBuf, cbAvail, (void **)&puDst, &cbToWrite);
653 if (!cbToWrite)
654 break;
655
656 /* Copy the data from the core audio buffer to the ring buffer. */
657 memcpy(puDst, (uint8_t *)pStreamIn->bufferList.mBuffers[0].mData + cbWrittenTotal, cbToWrite);
658
659 /* Release the ring buffer, so the main thread could start reading this data. */
660 RTCircBufReleaseWriteBlock(pStreamIn->pBuf, cbToWrite);
661
662 cbWrittenTotal += cbToWrite;
663
664 Assert(cbAvail >= cbToWrite);
665 cbAvail -= cbToWrite;
666 }
667 }
668
669 } while (0);
670
671 if (pStreamIn->bufferList.mBuffers[0].mData)
672 {
673 RTMemFree(pStreamIn->bufferList.mBuffers[0].mData);
674 pStreamIn->bufferList.mBuffers[0].mData = NULL;
675 }
676
677 return err;
678}
679
680/** @todo Eventually split up this function, as this already is huge! */
681static int drvHostCoreAudioInitInput(PPDMAUDIOHSTSTRMIN pHstStrmIn, uint32_t *pcSamples)
682{
683 OSStatus err = noErr;
684
685 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pHstStrmIn;
686
687 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_IN_INIT);
688
689 UInt32 uSize = 0;
690 if (pStreamIn->deviceID == kAudioDeviceUnknown)
691 {
692 /* Fetch the default audio input device currently in use. */
693 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice,
694 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
695 uSize = sizeof(pStreamIn->deviceID);
696 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &pStreamIn->deviceID);
697 if (err != noErr)
698 {
699 LogRel(("CoreAudio: Unable to determine default input device (%RI32)\n", err));
700 return VERR_NOT_FOUND;
701 }
702 }
703
704 /*
705 * Try to get the name of the input device and log it. It's not fatal if it fails.
706 */
707 CFStringRef strTemp;
708
709 AudioObjectPropertyAddress propAdr = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal,
710 kAudioObjectPropertyElementMaster };
711 uSize = sizeof(CFStringRef);
712 err = AudioObjectGetPropertyData(pStreamIn->deviceID, &propAdr, 0, NULL, &uSize, &strTemp);
713 if (err == noErr)
714 {
715 char *pszDevName = NULL;
716 err = drvHostCoreAudioCFStringToCString(strTemp, &pszDevName);
717 if (err == noErr)
718 {
719 CFRelease(strTemp);
720
721 /* Get the device' UUID. */
722 propAdr.mSelector = kAudioDevicePropertyDeviceUID;
723 err = AudioObjectGetPropertyData(pStreamIn->deviceID, &propAdr, 0, NULL, &uSize, &strTemp);
724 if (err == noErr)
725 {
726 char *pszUID = NULL;
727 err = drvHostCoreAudioCFStringToCString(strTemp, &pszUID);
728 if (err == noErr)
729 {
730 CFRelease(strTemp);
731 LogRel(("CoreAudio: Using input device: %s (UID: %s)\n", pszDevName, pszUID));
732
733 RTMemFree(pszUID);
734 }
735 }
736
737 RTMemFree(pszDevName);
738 }
739 }
740 else
741 LogRel(("CoreAudio: Unable to determine input device name (%RI32)\n", err));
742
743 /* Get the default frames buffer size, so that we can setup our internal buffers. */
744 UInt32 cFrames;
745 uSize = sizeof(cFrames);
746 propAdr.mSelector = kAudioDevicePropertyBufferFrameSize;
747 propAdr.mScope = kAudioDevicePropertyScopeInput;
748 err = AudioObjectGetPropertyData(pStreamIn->deviceID, &propAdr, 0, NULL, &uSize, &cFrames);
749 if (err != noErr)
750 {
751 LogRel(("CoreAudio: Failed to determine frame buffer size of the audio input device (%RI32)\n", err));
752 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
753 }
754
755 /* Set the frame buffer size and honor any minimum/maximum restrictions on the device. */
756 err = drvHostCoreAudioSetFrameBufferSize(pStreamIn->deviceID, true /* fInput */, cFrames, &cFrames);
757 if (err != noErr)
758 {
759 LogRel(("CoreAudio: Failed to set frame buffer size for the audio input device (%RI32)\n", err));
760 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
761 }
762
763 ComponentDescription cd;
764 RT_ZERO(cd);
765 cd.componentType = kAudioUnitType_Output;
766 cd.componentSubType = kAudioUnitSubType_HALOutput;
767 cd.componentManufacturer = kAudioUnitManufacturer_Apple;
768
769 /* Try to find the default HAL output component. */
770 Component cp = FindNextComponent(NULL, &cd);
771 if (cp == 0)
772 {
773 LogRel(("CoreAudio: Failed to find HAL output component\n")); /** @todo Return error value? */
774 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
775 }
776
777 /* Open the default HAL output component. */
778 err = OpenAComponent(cp, &pStreamIn->audioUnit);
779 if (err != noErr)
780 {
781 LogRel(("CoreAudio: Failed to open output component (%RI32)\n", err));
782 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
783 }
784
785 /* Switch the I/O mode for input to on. */
786 UInt32 uFlag = 1;
787 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input,
788 1, &uFlag, sizeof(uFlag));
789 if (err != noErr)
790 {
791 LogRel(("CoreAudio: Failed to disable input I/O mode for input stream (%RI32)\n", err));
792 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
793 }
794
795 /* Switch the I/O mode for input to off. This is important, as this is a pure input stream. */
796 uFlag = 0;
797 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output,
798 0, &uFlag, sizeof(uFlag));
799 if (err != noErr)
800 {
801 LogRel(("CoreAudio: Failed to disable output I/O mode for input stream (%RI32)\n", err));
802 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
803 }
804
805 /* Set the default audio input device as the device for the new AudioUnit. */
806 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
807 0, &pStreamIn->deviceID, sizeof(pStreamIn->deviceID));
808 if (err != noErr)
809 {
810 LogRel(("CoreAudio: Failed to set current device (%RI32)\n", err));
811 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
812 }
813
814 /*
815 * CoreAudio will inform us on a second thread for new incoming audio data.
816 * Therefor register a callback function which will process the new data.
817 */
818 AURenderCallbackStruct cb;
819 RT_ZERO(cb);
820 cb.inputProc = drvHostCoreAudioRecordingCallback;
821 cb.inputProcRefCon = pStreamIn;
822
823 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global,
824 0, &cb, sizeof(cb));
825 if (err != noErr)
826 {
827 LogRel(("CoreAudio: Failed to register input callback (%RI32)\n", err));
828 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
829 }
830
831 /* Fetch the current stream format of the device. */
832 uSize = sizeof(pStreamIn->deviceFormat);
833 err = AudioUnitGetProperty(pStreamIn->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
834 1, &pStreamIn->deviceFormat, &uSize);
835 if (err != noErr)
836 {
837 LogRel(("CoreAudio: Failed to get device format (%RI32)\n", err));
838 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
839 }
840
841 /* Create an AudioStreamBasicDescription based on our required audio settings. */
842 drvHostCoreAudioPCMInfoToASBDesc(&pStreamIn->streamIn.Props, &pStreamIn->streamFormat);
843
844#ifdef DEBUG
845 drvHostCoreAudioPrintASBDesc("CoreAudio: Input device", &pStreamIn->deviceFormat);
846 drvHostCoreAudioPrintASBDesc("CoreAudio: Input stream", &pStreamIn->streamFormat);
847#endif /* DEBUG */
848
849 /* If the frequency of the device is different from the requested one we
850 * need a converter. The same count if the number of channels is different. */
851 if ( pStreamIn->deviceFormat.mSampleRate != pStreamIn->streamFormat.mSampleRate
852 || pStreamIn->deviceFormat.mChannelsPerFrame != pStreamIn->streamFormat.mChannelsPerFrame)
853 {
854 err = AudioConverterNew(&pStreamIn->deviceFormat, &pStreamIn->streamFormat, &pStreamIn->converter);
855 if (RT_UNLIKELY(err != noErr))
856 {
857 LogRel(("CoreAudio: Failed to create the audio converte(%RI32). Input Format=%d, Output Foramt=%d\n",
858 err, pStreamIn->deviceFormat, pStreamIn->streamFormat));
859 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
860 }
861
862 if ( pStreamIn->deviceFormat.mChannelsPerFrame == 1 /* Mono */
863 && pStreamIn->streamFormat.mChannelsPerFrame == 2 /* Stereo */)
864 {
865 /*
866 * If the channel count is different we have to tell this the converter
867 * and supply a channel mapping. For now we only support mapping
868 * from mono to stereo. For all other cases the core audio defaults
869 * are used, which means dropping additional channels in most
870 * cases.
871 */
872 const SInt32 channelMap[2] = {0, 0}; /* Channel map for mono -> stereo, */
873
874 err = AudioConverterSetProperty(pStreamIn->converter, kAudioConverterChannelMap, sizeof(channelMap), channelMap);
875 if (err != noErr)
876 {
877 LogRel(("CoreAudio: Failed to set channel mapping for the audio input converter (%RI32)\n", err));
878 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
879 }
880 }
881
882 /* Set the new input format description for the stream. */
883 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
884 1, &pStreamIn->deviceFormat, sizeof(pStreamIn->deviceFormat));
885 if (err != noErr)
886 {
887 LogRel(("CoreAudio: Failed to set input format for input stream (%RI32)\n", err));
888 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
889 }
890#if 0
891 /* Set sample rate converter quality to maximum */
892 uFlag = kAudioConverterQuality_Max;
893 err = AudioConverterSetProperty(pStreamIn->converter, kAudioConverterSampleRateConverterQuality,
894 sizeof(uFlag), &uFlag);
895 if (err != noErr)
896 LogRel(("CoreAudio: Failed to set input audio converter quality to the maximum (%RI32)\n", err));
897#endif
898 LogRel(("CoreAudio: Input converter is active\n"));
899 }
900
901 /* Set the new output format description for the stream. */
902 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
903 1, &pStreamIn->deviceFormat, sizeof(pStreamIn->deviceFormat));
904 if (err != noErr)
905 {
906 LogRel(("CoreAudio: Failed to set output format for input stream (%RI32)\n", err));
907 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
908 }
909
910 /*
911 * Also set the frame buffer size off the device on our AudioUnit. This
912 * should make sure that the frames count which we receive in the render
913 * thread is as we like.
914 */
915 err = AudioUnitSetProperty(pStreamIn->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global,
916 1, &cFrames, sizeof(cFrames));
917 if (err != noErr) {
918 LogRel(("CoreAudio: Failed to set maximum frame buffer size for input stream (%RI32)\n", err));
919 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
920 }
921
922 /* Finally initialize the new AudioUnit. */
923 err = AudioUnitInitialize(pStreamIn->audioUnit);
924 if (err != noErr)
925 {
926 LogRel(("CoreAudio: Failed to initialize audio unit for input stream (%RI32)\n", err));
927 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
928 }
929
930 uSize = sizeof(pStreamIn->deviceFormat);
931 err = AudioUnitGetProperty(pStreamIn->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
932 1, &pStreamIn->deviceFormat, &uSize);
933 if (err != noErr)
934 {
935 LogRel(("CoreAudio: Failed to get input device format (%RI32)\n", err));
936 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
937 }
938
939 /*
940 * There are buggy devices (e.g. my Bluetooth headset) which doesn't honor
941 * the frame buffer size set in the previous calls. So finally get the
942 * frame buffer size after the AudioUnit was initialized.
943 */
944 uSize = sizeof(cFrames);
945 err = AudioUnitGetProperty(pStreamIn->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice,kAudioUnitScope_Global,
946 0, &cFrames, &uSize);
947 if (err != noErr)
948 {
949 LogRel(("CoreAudio: Failed to get maximum frame buffer size from input audio device (%RI32)\n", err));
950 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
951 }
952
953 /* Calculate the ratio between the device and the stream sample rate. */
954 pStreamIn->sampleRatio = pStreamIn->streamFormat.mSampleRate / pStreamIn->deviceFormat.mSampleRate;
955
956 /* Set to zero first */
957 pStreamIn->pBuf = NULL;
958 /* Create the AudioBufferList structure with one buffer. */
959 pStreamIn->bufferList.mNumberBuffers = 1;
960 /* Initialize the buffer to nothing. */
961 pStreamIn->bufferList.mBuffers[0].mNumberChannels = pStreamIn->streamFormat.mChannelsPerFrame;
962 pStreamIn->bufferList.mBuffers[0].mDataByteSize = 0;
963 pStreamIn->bufferList.mBuffers[0].mData = NULL;
964
965 int rc = VINF_SUCCESS;
966
967 /*
968 * Make sure that the ring buffer is big enough to hold the recording
969 * data. Compare the maximum frames per slice value with the frames
970 * necessary when using the converter where the sample rate could differ.
971 * The result is always multiplied by the channels per frame to get the
972 * samples count.
973 */
974 UInt32 cSamples = cFrames * pStreamIn->streamFormat.mChannelsPerFrame;
975 if (!cSamples)
976 {
977 LogRel(("CoreAudio: Failed to determine samples buffer count input stream\n"));
978 rc = VERR_INVALID_PARAMETER;
979 }
980
981 /* Create the internal ring buffer. */
982 if (RT_SUCCESS(rc))
983 rc = RTCircBufCreate(&pStreamIn->pBuf, cSamples << pHstStrmIn->Props.cShift);
984 if (RT_SUCCESS(rc))
985 {
986#ifdef DEBUG
987 propAdr.mSelector = kAudioDeviceProcessorOverload;
988 propAdr.mScope = kAudioUnitScope_Global;
989 err = AudioObjectAddPropertyListener(pStreamIn->deviceID, &propAdr,
990 drvHostCoreAudioRecordingAudioDevicePropertyChanged, (void *)pStreamIn);
991 if (RT_UNLIKELY(err != noErr))
992 LogRel(("CoreAudio: Failed to add the processor overload listener for input stream (%RI32)\n", err));
993#endif /* DEBUG */
994 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
995 propAdr.mScope = kAudioUnitScope_Global;
996 err = AudioObjectAddPropertyListener(pStreamIn->deviceID, &propAdr,
997 drvHostCoreAudioRecordingAudioDevicePropertyChanged, (void *)pStreamIn);
998 /* Not fatal. */
999 if (RT_UNLIKELY(err != noErr))
1000 LogRel(("CoreAudio: Failed to register sample rate changed listener for input stream (%RI32)\n", err));
1001 }
1002
1003 if (RT_SUCCESS(rc))
1004 {
1005 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_INIT);
1006
1007 if (pcSamples)
1008 *pcSamples = cSamples;
1009 }
1010 else
1011 {
1012 AudioUnitUninitialize(pStreamIn->audioUnit);
1013
1014 if (pStreamIn->pBuf)
1015 {
1016 RTCircBufDestroy(pStreamIn->pBuf);
1017 pStreamIn->pBuf = NULL;
1018 }
1019 }
1020
1021 LogFunc(("cSamples=%RU32, rc=%Rrc\n", cSamples, rc));
1022 return rc;
1023}
1024
1025/** @todo Eventually split up this function, as this already is huge! */
1026static int drvHostCoreAudioInitOutput(PPDMAUDIOHSTSTRMOUT pHstStrmOut, uint32_t *pcSamples)
1027{
1028 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
1029
1030 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_IN_INIT);
1031
1032 OSStatus err = noErr;
1033
1034 UInt32 uSize = 0;
1035 if (pStreamOut->deviceID == kAudioDeviceUnknown)
1036 {
1037 /* Fetch the default audio input device currently in use. */
1038 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultOutputDevice,
1039 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
1040 uSize = sizeof(pStreamOut->deviceID);
1041 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAdr, 0, NULL, &uSize, &pStreamOut->deviceID);
1042 if (err != noErr)
1043 {
1044 LogRel(("CoreAudio: Unable to determine default output device (%RI32)\n", err));
1045 return VERR_NOT_FOUND;
1046 }
1047 }
1048
1049 /*
1050 * Try to get the name of the output device and log it. It's not fatal if it fails.
1051 */
1052 CFStringRef strTemp;
1053
1054 AudioObjectPropertyAddress propAdr = { kAudioObjectPropertyName, kAudioObjectPropertyScopeGlobal,
1055 kAudioObjectPropertyElementMaster };
1056 uSize = sizeof(CFStringRef);
1057 err = AudioObjectGetPropertyData(pStreamOut->deviceID, &propAdr, 0, NULL, &uSize, &strTemp);
1058 if (err == noErr)
1059 {
1060 char *pszDevName = NULL;
1061 err = drvHostCoreAudioCFStringToCString(strTemp, &pszDevName);
1062 if (err == noErr)
1063 {
1064 CFRelease(strTemp);
1065
1066 /* Get the device' UUID. */
1067 propAdr.mSelector = kAudioDevicePropertyDeviceUID;
1068 err = AudioObjectGetPropertyData(pStreamOut->deviceID, &propAdr, 0, NULL, &uSize, &strTemp);
1069 if (err == noErr)
1070 {
1071 char *pszUID = NULL;
1072 err = drvHostCoreAudioCFStringToCString(strTemp, &pszUID);
1073 if (err == noErr)
1074 {
1075 CFRelease(strTemp);
1076 LogRel(("CoreAudio: Using output device: %s (UID: %s)\n", pszDevName, pszUID));
1077
1078 RTMemFree(pszUID);
1079 }
1080 }
1081
1082 RTMemFree(pszDevName);
1083 }
1084 }
1085 else
1086 LogRel(("CoreAudio: Unable to determine output device name (%RI32)\n", err));
1087
1088 /* Get the default frames buffer size, so that we can setup our internal buffers. */
1089 UInt32 cFrames;
1090 uSize = sizeof(cFrames);
1091 propAdr.mSelector = kAudioDevicePropertyBufferFrameSize;
1092 propAdr.mScope = kAudioDevicePropertyScopeInput;
1093 err = AudioObjectGetPropertyData(pStreamOut->deviceID, &propAdr, 0, NULL, &uSize, &cFrames);
1094 if (err != noErr)
1095 {
1096 LogRel(("CoreAudio: Failed to determine frame buffer size of the audio output device (%RI32)\n", err));
1097 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1098 }
1099
1100 /* Set the frame buffer size and honor any minimum/maximum restrictions on the device. */
1101 err = drvHostCoreAudioSetFrameBufferSize(pStreamOut->deviceID, false /* fInput */, cFrames, &cFrames);
1102 if (err != noErr)
1103 {
1104 LogRel(("CoreAudio: Failed to set frame buffer size for the audio output device (%RI32)\n", err));
1105 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1106 }
1107
1108 ComponentDescription cd;
1109 RT_ZERO(cd);
1110 cd.componentType = kAudioUnitType_Output;
1111 cd.componentSubType = kAudioUnitSubType_HALOutput;
1112 cd.componentManufacturer = kAudioUnitManufacturer_Apple;
1113
1114 /* Try to find the default HAL output component. */
1115 Component cp = FindNextComponent(NULL, &cd);
1116 if (cp == 0)
1117 {
1118 LogRel(("CoreAudio: Failed to find HAL output component\n")); /** @todo Return error value? */
1119 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1120 }
1121
1122 /* Open the default HAL output component. */
1123 err = OpenAComponent(cp, &pStreamOut->audioUnit);
1124 if (err != noErr)
1125 {
1126 LogRel(("CoreAudio: Failed to open output component (%RI32)\n", err));
1127 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1128 }
1129
1130 /* Switch the I/O mode for output to on. */
1131 UInt32 uFlag = 1;
1132 err = AudioUnitSetProperty(pStreamOut->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output,
1133 0, &uFlag, sizeof(uFlag));
1134 if (err != noErr)
1135 {
1136 LogRel(("CoreAudio: Failed to disable I/O mode for output stream (%RI32)\n", err));
1137 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1138 }
1139
1140 /* Set the default audio output device as the device for the new AudioUnit. */
1141 err = AudioUnitSetProperty(pStreamOut->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
1142 0, &pStreamOut->deviceID, sizeof(pStreamOut->deviceID));
1143 if (err != noErr)
1144 {
1145 LogRel(("CoreAudio: Failed to set current device for output stream (%RI32)\n", err));
1146 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1147 }
1148
1149 /*
1150 * CoreAudio will inform us on a second thread for new incoming audio data.
1151 * Therefor register a callback function which will process the new data.
1152 */
1153 AURenderCallbackStruct cb;
1154 RT_ZERO(cb);
1155 cb.inputProc = drvHostCoreAudioPlaybackCallback; /* pvUser */
1156 cb.inputProcRefCon = pStreamOut;
1157
1158 err = AudioUnitSetProperty(pStreamOut->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
1159 0, &cb, sizeof(cb));
1160 if (err != noErr)
1161 {
1162 LogRel(("CoreAudio: Failed to register output callback (%RI32)\n", err));
1163 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1164 }
1165
1166 /* Fetch the current stream format of the device. */
1167 uSize = sizeof(pStreamOut->deviceFormat);
1168 err = AudioUnitGetProperty(pStreamOut->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1169 0, &pStreamOut->deviceFormat, &uSize);
1170 if (err != noErr)
1171 {
1172 LogRel(("CoreAudio: Failed to get device format (%RI32)\n", err));
1173 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1174 }
1175
1176 /* Create an AudioStreamBasicDescription based on our required audio settings. */
1177 drvHostCoreAudioPCMInfoToASBDesc(&pStreamOut->streamOut.Props, &pStreamOut->streamFormat);
1178
1179#ifdef DEBUG
1180 drvHostCoreAudioPrintASBDesc("CoreAudio: Output device", &pStreamOut->deviceFormat);
1181 drvHostCoreAudioPrintASBDesc("CoreAudio: Output format", &pStreamOut->streamFormat);
1182#endif /* DEBUG */
1183
1184 /* Set the new output format description for the stream. */
1185 err = AudioUnitSetProperty(pStreamOut->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1186 0, &pStreamOut->streamFormat, sizeof(pStreamOut->streamFormat));
1187 if (err != noErr)
1188 {
1189 LogRel(("CoreAudio: Failed to set stream format for output stream (%RI32)\n", err));
1190 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1191 }
1192
1193 uSize = sizeof(pStreamOut->deviceFormat);
1194 err = AudioUnitGetProperty(pStreamOut->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,
1195 0, &pStreamOut->deviceFormat, &uSize);
1196 if (err != noErr)
1197 {
1198 LogRel(("CoreAudio: Failed to retrieve device format for output stream (%RI32)\n", err));
1199 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1200 }
1201
1202 /*
1203 * Also set the frame buffer size off the device on our AudioUnit. This
1204 * should make sure that the frames count which we receive in the render
1205 * thread is as we like.
1206 */
1207 err = AudioUnitSetProperty(pStreamOut->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global,
1208 0, &cFrames, sizeof(cFrames));
1209 if (err != noErr)
1210 {
1211 LogRel(("CoreAudio: Failed to set maximum frame buffer size for output AudioUnit (%RI32)\n", err));
1212 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1213 }
1214
1215 /* Finally initialize the new AudioUnit. */
1216 err = AudioUnitInitialize(pStreamOut->audioUnit);
1217 if (err != noErr)
1218 {
1219 LogRel(("CoreAudio: Failed to initialize the output audio device (%RI32)\n", err));
1220 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1221 }
1222
1223 /*
1224 * There are buggy devices (e.g. my Bluetooth headset) which doesn't honor
1225 * the frame buffer size set in the previous calls. So finally get the
1226 * frame buffer size after the AudioUnit was initialized.
1227 */
1228 uSize = sizeof(cFrames);
1229 err = AudioUnitGetProperty(pStreamOut->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global,
1230 0, &cFrames, &uSize);
1231 if (err != noErr)
1232 {
1233 LogRel(("CoreAudio: Failed to get maximum frame buffer size from output audio device (%RI32)\n", err));
1234
1235 AudioUnitUninitialize(pStreamOut->audioUnit);
1236 return VERR_GENERAL_FAILURE; /** @todo Fudge! */
1237 }
1238
1239 /*
1240 * Make sure that the ring buffer is big enough to hold the recording
1241 * data. Compare the maximum frames per slice value with the frames
1242 * necessary when using the converter where the sample rate could differ.
1243 * The result is always multiplied by the channels per frame to get the
1244 * samples count.
1245 */
1246 int rc = VINF_SUCCESS;
1247
1248 UInt32 cSamples = cFrames * pStreamOut->streamFormat.mChannelsPerFrame;
1249 if (!cSamples)
1250 {
1251 LogRel(("CoreAudio: Failed to determine samples buffer count output stream\n"));
1252 rc = VERR_INVALID_PARAMETER;
1253 }
1254
1255 /* Create the internal ring buffer. */
1256 rc = RTCircBufCreate(&pStreamOut->pBuf, cSamples << pHstStrmOut->Props.cShift);
1257 if (RT_SUCCESS(rc))
1258 {
1259 /*
1260 * Register callbacks.
1261 */
1262#ifdef DEBUG
1263 propAdr.mSelector = kAudioDeviceProcessorOverload;
1264 propAdr.mScope = kAudioUnitScope_Global;
1265 err = AudioObjectAddPropertyListener(pStreamOut->deviceID, &propAdr,
1266 drvHostCoreAudioPlaybackAudioDevicePropertyChanged, (void *)pStreamOut);
1267 if (err != noErr)
1268 LogRel(("CoreAudio: Failed to register processor overload listener for output stream (%RI32)\n", err));
1269#endif /* DEBUG */
1270
1271 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1272 propAdr.mScope = kAudioUnitScope_Global;
1273 err = AudioObjectAddPropertyListener(pStreamOut->deviceID, &propAdr,
1274 drvHostCoreAudioPlaybackAudioDevicePropertyChanged, (void *)pStreamOut);
1275 /* Not fatal. */
1276 if (err != noErr)
1277 LogRel(("CoreAudio: Failed to register sample rate changed listener for output stream (%RI32)\n", err));
1278 }
1279
1280 if (RT_SUCCESS(rc))
1281 {
1282 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_INIT);
1283
1284 if (pcSamples)
1285 *pcSamples = cSamples;
1286 }
1287 else
1288 {
1289 AudioUnitUninitialize(pStreamOut->audioUnit);
1290
1291 if (pStreamOut->pBuf)
1292 {
1293 RTCircBufDestroy(pStreamOut->pBuf);
1294 pStreamOut->pBuf = NULL;
1295 }
1296 }
1297
1298 LogFunc(("cSamples=%RU32, rc=%Rrc\n", cSamples, rc));
1299 return rc;
1300}
1301
1302static DECLCALLBACK(int) drvHostCoreAudioInit(PPDMIHOSTAUDIO pInterface)
1303{
1304 NOREF(pInterface);
1305
1306 LogFlowFuncEnter();
1307
1308 return VINF_SUCCESS;
1309}
1310
1311static DECLCALLBACK(int) drvHostCoreAudioCaptureIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
1312 uint32_t *pcSamplesCaptured)
1313{
1314 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1315 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
1316
1317 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pHstStrmIn;
1318
1319 size_t csReads = 0;
1320 char *pcSrc;
1321 PPDMAUDIOSAMPLE psDst;
1322
1323 /* Check if the audio device should be reinitialized. If so do it. */
1324 if (ASMAtomicReadU32(&pStreamIn->status) == CA_STATUS_REINIT)
1325 drvHostCoreAudioReinitInput(pInterface, &pStreamIn->streamIn);
1326
1327 if (ASMAtomicReadU32(&pStreamIn->status) != CA_STATUS_INIT)
1328 {
1329 if (pcSamplesCaptured)
1330 *pcSamplesCaptured = 0;
1331 return VINF_SUCCESS;
1332 }
1333
1334 int rc = VINF_SUCCESS;
1335 uint32_t cbWrittenTotal = 0;
1336
1337 do
1338 {
1339 size_t cbBuf = AudioMixBufSizeBytes(&pHstStrmIn->MixBuf);
1340 size_t cbToWrite = RT_MIN(cbBuf, RTCircBufFree(pStreamIn->pBuf));
1341 LogFlowFunc(("cbToWrite=%zu\n", cbToWrite));
1342
1343 uint32_t cWritten, cbWritten;
1344 uint8_t *puBuf;
1345 size_t cbToRead;
1346
1347 while (cbToWrite)
1348 {
1349 /* Try to acquire the necessary block from the ring buffer. */
1350 RTCircBufAcquireReadBlock(pStreamIn->pBuf, cbToWrite, (void **)&puBuf, &cbToRead);
1351 if (!cbToRead)
1352 {
1353 RTCircBufReleaseReadBlock(pStreamIn->pBuf, cbToRead);
1354 break;
1355 }
1356
1357 rc = AudioMixBufWriteCirc(&pHstStrmIn->MixBuf, puBuf, cbToRead, &cWritten);
1358 if (RT_FAILURE(rc))
1359 break;
1360
1361 cbWritten = AUDIOMIXBUF_S2B(&pHstStrmIn->MixBuf, cWritten);
1362
1363 /* Release the read buffer, so it could be used for new data. */
1364 RTCircBufReleaseReadBlock(pStreamIn->pBuf, cbWritten);
1365
1366 Assert(cbToWrite >= cbWritten);
1367 cbToWrite -= cbWritten;
1368 cbWrittenTotal += cbWritten;
1369 }
1370 }
1371 while (0);
1372
1373 if (RT_SUCCESS(rc))
1374 {
1375 uint32_t cWrittenTotal = AUDIOMIXBUF_B2S(&pHstStrmIn->MixBuf, cbWrittenTotal);
1376 if (cWrittenTotal)
1377 AudioMixBufFinish(&pHstStrmIn->MixBuf, cWrittenTotal);
1378
1379 LogFlowFunc(("cWrittenTotal=%RU32 (%RU32 bytes)\n", cWrittenTotal, cbWrittenTotal));
1380
1381 if (pcSamplesCaptured)
1382 *pcSamplesCaptured = cWrittenTotal;
1383 }
1384
1385 return rc;
1386}
1387
1388/* Callback for getting notified when some of the properties of an audio device has changed. */
1389static DECLCALLBACK(OSStatus) drvHostCoreAudioPlaybackAudioDevicePropertyChanged(AudioObjectID propertyID,
1390 UInt32 nAddresses,
1391 const AudioObjectPropertyAddress properties[],
1392 void *pvUser)
1393{
1394 switch (propertyID)
1395 {
1396#ifdef DEBUG
1397 case kAudioDeviceProcessorOverload:
1398 {
1399 Log2(("CoreAudio: [Output] Processor overload detected!\n"));
1400 break;
1401 }
1402#endif /* DEBUG */
1403 default:
1404 break;
1405 }
1406
1407 return noErr;
1408}
1409
1410/* Callback to feed audio output buffer. */
1411static DECLCALLBACK(OSStatus) drvHostCoreAudioPlaybackCallback(void *pvUser,
1412 AudioUnitRenderActionFlags *pActionFlags,
1413 const AudioTimeStamp *pAudioTS,
1414 UInt32 uBusID,
1415 UInt32 cFrames,
1416 AudioBufferList *pBufData)
1417{
1418 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pvUser;
1419 PPDMAUDIOHSTSTRMOUT pHstStrmOut = &pStreamOut->streamOut;
1420
1421 if (ASMAtomicReadU32(&pStreamOut->status) != CA_STATUS_INIT)
1422 {
1423 pBufData->mBuffers[0].mDataByteSize = 0;
1424 return noErr;
1425 }
1426
1427 /* How much space is used in the ring buffer? */
1428 size_t cbDataAvail = RT_MIN(RTCircBufUsed(pStreamOut->pBuf), pBufData->mBuffers[0].mDataByteSize);
1429 if (!cbDataAvail)
1430 {
1431 pBufData->mBuffers[0].mDataByteSize = 0;
1432 return noErr;
1433 }
1434
1435 uint8_t *pbSrc = NULL;
1436 size_t cbRead = 0;
1437 size_t cbToRead;
1438 while (cbDataAvail)
1439 {
1440 /* Try to acquire the necessary block from the ring buffer. */
1441 RTCircBufAcquireReadBlock(pStreamOut->pBuf, cbDataAvail, (void **)&pbSrc, &cbToRead);
1442
1443 /* Break if nothing is used anymore. */
1444 if (!cbToRead)
1445 break;
1446
1447 /* Copy the data from our ring buffer to the core audio buffer. */
1448 memcpy((uint8_t *)pBufData->mBuffers[0].mData + cbRead, pbSrc, cbToRead);
1449
1450 /* Release the read buffer, so it could be used for new data. */
1451 RTCircBufReleaseReadBlock(pStreamOut->pBuf, cbToRead);
1452
1453 /* Move offset. */
1454 cbRead += cbToRead;
1455 Assert(pBufData->mBuffers[0].mDataByteSize >= cbRead);
1456
1457 Assert(cbToRead <= cbDataAvail);
1458 cbDataAvail -= cbToRead;
1459 }
1460
1461 /* Write the bytes to the core audio buffer which where really written. */
1462 pBufData->mBuffers[0].mDataByteSize = cbRead;
1463
1464 LogFlowFunc(("CoreAudio: [Output] Read %zu / %zu bytes\n", cbRead, cbDataAvail));
1465
1466 return noErr;
1467}
1468
1469static DECLCALLBACK(int) drvHostCoreAudioPlayOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
1470 uint32_t *pcSamplesPlayed)
1471{
1472 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1473 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
1474
1475 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
1476
1477 /* Check if the audio device should be reinitialized. If so do it. */
1478 if (ASMAtomicReadU32(&pStreamOut->status) == CA_STATUS_REINIT)
1479 drvHostCoreAudioReinitOutput(pInterface, &pStreamOut->streamOut);
1480
1481 /* Not much else to do here. */
1482
1483 uint32_t cLive = AudioMixBufAvail(&pHstStrmOut->MixBuf);;
1484 if (!cLive) /* Not samples to play? Bail out. */
1485 {
1486 if (pcSamplesPlayed)
1487 *pcSamplesPlayed = 0;
1488 return VINF_SUCCESS;
1489 }
1490
1491 int rc = VINF_SUCCESS;
1492 uint32_t cbReadTotal = 0;
1493 uint32_t cAvail = AudioMixBufAvail(&pHstStrmOut->MixBuf);
1494 size_t cbAvail = AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cAvail);
1495 size_t cbToRead = RT_MIN(cbAvail, RTCircBufFree(pStreamOut->pBuf));
1496 LogFlowFunc(("cbToRead=%zu\n", cbToRead));
1497
1498 while (cbToRead)
1499 {
1500 uint32_t cRead, cbRead;
1501 uint8_t *puBuf;
1502 size_t cbCopy;
1503
1504 /* Try to acquire the necessary space from the ring buffer. */
1505 RTCircBufAcquireWriteBlock(pStreamOut->pBuf, cbToRead, (void **)&puBuf, &cbCopy);
1506 if (!cbCopy)
1507 {
1508 RTCircBufReleaseWriteBlock(pStreamOut->pBuf, cbCopy);
1509 break;
1510 }
1511
1512 Assert(cbCopy <= cbToRead);
1513
1514 rc = AudioMixBufReadCirc(&pHstStrmOut->MixBuf,
1515 puBuf, cbCopy, &cRead);
1516
1517 if ( RT_FAILURE(rc)
1518 || !cRead)
1519 {
1520 RTCircBufReleaseWriteBlock(pStreamOut->pBuf, 0);
1521 break;
1522 }
1523
1524 cbRead = AUDIOMIXBUF_S2B(&pHstStrmOut->MixBuf, cRead);
1525
1526 /* Release the ring buffer, so the read thread could start reading this data. */
1527 RTCircBufReleaseWriteBlock(pStreamOut->pBuf, cbRead);
1528
1529 Assert(cbToRead >= cbRead);
1530 cbToRead -= cbRead;
1531 cbReadTotal += cbRead;
1532 }
1533
1534 if (RT_SUCCESS(rc))
1535 {
1536 uint32_t cReadTotal = AUDIOMIXBUF_B2S(&pHstStrmOut->MixBuf, cbReadTotal);
1537 if (cReadTotal)
1538 AudioMixBufFinish(&pHstStrmOut->MixBuf, cReadTotal);
1539
1540 LogFlowFunc(("cReadTotal=%RU32 (%RU32 bytes)\n", cReadTotal, cbReadTotal));
1541
1542 if (pcSamplesPlayed)
1543 *pcSamplesPlayed = cReadTotal;
1544 }
1545
1546 return rc;
1547}
1548
1549static DECLCALLBACK(int) drvHostCoreAudioControlOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut,
1550 PDMAUDIOSTREAMCMD enmStreamCmd)
1551{
1552 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
1553
1554 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1555
1556 uint32_t uStatus = ASMAtomicReadU32(&pStreamOut->status);
1557 if (!( uStatus == CA_STATUS_INIT
1558 || uStatus == CA_STATUS_REINIT))
1559 {
1560 return VINF_SUCCESS;
1561 }
1562
1563 int rc = VINF_SUCCESS;
1564 OSStatus err;
1565
1566 switch (enmStreamCmd)
1567 {
1568 case PDMAUDIOSTREAMCMD_ENABLE:
1569 case PDMAUDIOSTREAMCMD_RESUME:
1570 {
1571 /* Only start the device if it is actually stopped */
1572 if (!drvHostCoreAudioIsRunning(pStreamOut->deviceID))
1573 {
1574 err = AudioUnitReset(pStreamOut->audioUnit, kAudioUnitScope_Input, 0);
1575 if (err != noErr)
1576 {
1577 LogRel(("CoreAudio: Failed to reset AudioUnit (%RI32)\n", err));
1578 /* Keep going. */
1579 }
1580 RTCircBufReset(pStreamOut->pBuf);
1581
1582 err = AudioOutputUnitStart(pStreamOut->audioUnit);
1583 if (RT_UNLIKELY(err != noErr))
1584 {
1585 LogRel(("CoreAudio: Failed to start playback (%RI32)\n", err));
1586 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1587 }
1588 }
1589 break;
1590 }
1591
1592 case PDMAUDIOSTREAMCMD_DISABLE:
1593 case PDMAUDIOSTREAMCMD_PAUSE:
1594 {
1595 /* Only stop the device if it is actually running */
1596 if (drvHostCoreAudioIsRunning(pStreamOut->deviceID))
1597 {
1598 err = AudioOutputUnitStop(pStreamOut->audioUnit);
1599 if (err != noErr)
1600 {
1601 LogRel(("CoreAudio: Failed to stop playback (%RI32)\n", err));
1602 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1603 break;
1604 }
1605
1606 err = AudioUnitReset(pStreamOut->audioUnit, kAudioUnitScope_Input, 0);
1607 if (err != noErr)
1608 {
1609 LogRel(("CoreAudio: Failed to reset AudioUnit (%RI32)\n", err));
1610 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1611 }
1612 }
1613 break;
1614 }
1615
1616 default:
1617 rc = VERR_NOT_SUPPORTED;
1618 break;
1619 }
1620
1621 LogFlowFuncLeaveRC(rc);
1622 return rc;
1623}
1624
1625static DECLCALLBACK(int) drvHostCoreAudioControlIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn,
1626 PDMAUDIOSTREAMCMD enmStreamCmd)
1627{
1628 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pHstStrmIn;
1629
1630 LogFlowFunc(("enmStreamCmd=%ld\n", enmStreamCmd));
1631
1632 uint32_t uStatus = ASMAtomicReadU32(&pStreamIn->status);
1633 if (!( uStatus == CA_STATUS_INIT
1634 || uStatus == CA_STATUS_REINIT))
1635 {
1636 return VINF_SUCCESS;
1637 }
1638
1639 int rc = VINF_SUCCESS;
1640 OSStatus err;
1641
1642 switch (enmStreamCmd)
1643 {
1644 case PDMAUDIOSTREAMCMD_ENABLE:
1645 case PDMAUDIOSTREAMCMD_RESUME:
1646 {
1647 /* Only start the device if it is actually stopped */
1648 if (!drvHostCoreAudioIsRunning(pStreamIn->deviceID))
1649 {
1650 RTCircBufReset(pStreamIn->pBuf);
1651 err = AudioOutputUnitStart(pStreamIn->audioUnit);
1652 if (err != noErr)
1653 {
1654 LogRel(("CoreAudio: Failed to start recording (%RI32)\n", err));
1655 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1656 break;
1657 }
1658 }
1659
1660 if (err != noErr)
1661 {
1662 LogRel(("CoreAudio: Failed to start recording (%RI32)\n", err));
1663 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1664 }
1665 break;
1666 }
1667
1668 case PDMAUDIOSTREAMCMD_DISABLE:
1669 case PDMAUDIOSTREAMCMD_PAUSE:
1670 {
1671 /* Only stop the device if it is actually running */
1672 if (drvHostCoreAudioIsRunning(pStreamIn->deviceID))
1673 {
1674 err = AudioOutputUnitStop(pStreamIn->audioUnit);
1675 if (err != noErr)
1676 {
1677 LogRel(("CoreAudio: Failed to stop recording (%RI32)\n", err));
1678 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1679 break;
1680 }
1681
1682 err = AudioUnitReset(pStreamIn->audioUnit, kAudioUnitScope_Input, 0);
1683 if (err != noErr)
1684 {
1685 LogRel(("CoreAudio: Failed to reset AudioUnit (%RI32)\n", err));
1686 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1687 break;
1688 }
1689 }
1690 break;
1691 }
1692
1693 default:
1694 rc = VERR_NOT_SUPPORTED;
1695 break;
1696 }
1697
1698 LogFlowFuncLeaveRC(rc);
1699 return rc;
1700}
1701
1702static DECLCALLBACK(int) drvHostCoreAudioFiniIn(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMIN pHstStrmIn)
1703{
1704 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN) pHstStrmIn;
1705
1706 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1707 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
1708
1709 LogFlowFuncEnter();
1710
1711 uint32_t status = ASMAtomicReadU32(&pStreamIn->status);
1712 if (!( status == CA_STATUS_INIT
1713 || status == CA_STATUS_REINIT))
1714 {
1715 return VINF_SUCCESS;
1716 }
1717
1718 OSStatus err = noErr;
1719
1720 int rc = drvHostCoreAudioControlIn(pInterface, &pStreamIn->streamIn, PDMAUDIOSTREAMCMD_DISABLE);
1721 if (RT_SUCCESS(rc))
1722 {
1723 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_IN_UNINIT);
1724
1725 /*
1726 * Unregister input device callbacks.
1727 */
1728 AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal,
1729 kAudioObjectPropertyElementMaster };
1730#ifdef DEBUG
1731 err = AudioObjectRemovePropertyListener(pStreamIn->deviceID, &propAdr,
1732 drvHostCoreAudioRecordingAudioDevicePropertyChanged, pStreamIn);
1733 /* Not Fatal */
1734 if (RT_UNLIKELY(err != noErr))
1735 LogRel(("CoreAudio: Failed to remove the processor overload listener (%RI32)\n", err));
1736#endif /* DEBUG */
1737
1738 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1739 err = AudioObjectRemovePropertyListener(pStreamIn->deviceID, &propAdr,
1740 drvHostCoreAudioRecordingAudioDevicePropertyChanged, pStreamIn);
1741 /* Not Fatal */
1742 if (RT_UNLIKELY(err != noErr))
1743 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1744
1745 if (pStreamIn->fDefDevChgListReg)
1746 {
1747 propAdr.mSelector = kAudioHardwarePropertyDefaultInputDevice;
1748 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
1749 drvHostCoreAudioDefaultDeviceChanged, pStreamIn);
1750 if (RT_LIKELY(err == noErr))
1751 {
1752 pStreamIn->fDefDevChgListReg = false;
1753 }
1754 else
1755 LogRel(("CoreAudio: [Output] Failed to remove the default input device changed listener (%RI32)\n", err));
1756 }
1757
1758 if (pStreamIn->converter)
1759 {
1760 AudioConverterDispose(pStreamIn->converter);
1761 pStreamIn->converter = NULL;
1762 }
1763
1764 err = AudioUnitUninitialize(pStreamIn->audioUnit);
1765 if (RT_LIKELY(err == noErr))
1766 {
1767 err = CloseComponent(pStreamIn->audioUnit);
1768 if (RT_LIKELY(err == noErr))
1769 {
1770 RTCircBufDestroy(pStreamIn->pBuf);
1771
1772 pStreamIn->audioUnit = NULL;
1773 pStreamIn->deviceID = kAudioDeviceUnknown;
1774 pStreamIn->pBuf = NULL;
1775 pStreamIn->sampleRatio = 1;
1776 pStreamIn->rpos = 0;
1777
1778 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_UNINIT);
1779 }
1780 else
1781 {
1782 LogRel(("CoreAudio: Failed to close the AudioUnit (%RI32)\n", err));
1783 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1784 }
1785 }
1786 else
1787 {
1788 LogRel(("CoreAudio: Failed to uninitialize the AudioUnit (%RI32)\n", err));
1789 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1790 }
1791 }
1792 else
1793 {
1794 LogRel(("CoreAudio: Failed to stop recording (%RI32)\n", err));
1795 rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */
1796 }
1797
1798 LogFlowFuncLeaveRC(rc);
1799 return rc;
1800}
1801
1802static DECLCALLBACK(int) drvHostCoreAudioFiniOut(PPDMIHOSTAUDIO pInterface, PPDMAUDIOHSTSTRMOUT pHstStrmOut)
1803{
1804 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1805 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
1806
1807 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
1808
1809 LogFlowFuncEnter();
1810
1811 uint32_t status = ASMAtomicReadU32(&pStreamOut->status);
1812 if (!( status == CA_STATUS_INIT
1813 || status == CA_STATUS_REINIT))
1814 {
1815 return VINF_SUCCESS;
1816 }
1817
1818 int rc = drvHostCoreAudioControlOut(pInterface, &pStreamOut->streamOut, PDMAUDIOSTREAMCMD_DISABLE);
1819 if (RT_SUCCESS(rc))
1820 {
1821 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_IN_UNINIT);
1822
1823 OSStatus err;
1824
1825 /*
1826 * Unregister playback device callbacks.
1827 */
1828 AudioObjectPropertyAddress propAdr = { kAudioDeviceProcessorOverload, kAudioObjectPropertyScopeGlobal,
1829 kAudioObjectPropertyElementMaster };
1830#ifdef DEBUG
1831 err = AudioObjectRemovePropertyListener(pStreamOut->deviceID, &propAdr,
1832 drvHostCoreAudioPlaybackAudioDevicePropertyChanged, pStreamOut);
1833 /* Not Fatal */
1834 if (RT_UNLIKELY(err != noErr))
1835 LogRel(("CoreAudio: Failed to remove the processor overload listener (%RI32)\n", err));
1836#endif /* DEBUG */
1837
1838 propAdr.mSelector = kAudioDevicePropertyNominalSampleRate;
1839 err = AudioObjectRemovePropertyListener(pStreamOut->deviceID, &propAdr,
1840 drvHostCoreAudioPlaybackAudioDevicePropertyChanged, pStreamOut);
1841 /* Not Fatal */
1842 if (RT_UNLIKELY(err != noErr))
1843 LogRel(("CoreAudio: Failed to remove the sample rate changed listener (%RI32)\n", err));
1844
1845 if (pStreamOut->fDefDevChgListReg)
1846 {
1847 propAdr.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
1848 propAdr.mScope = kAudioObjectPropertyScopeGlobal;
1849 propAdr.mElement = kAudioObjectPropertyElementMaster;
1850 err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &propAdr,
1851 drvHostCoreAudioDefaultDeviceChanged, pStreamOut);
1852 if (RT_LIKELY(err == noErr))
1853 {
1854 pStreamOut->fDefDevChgListReg = false;
1855 }
1856 else
1857 LogRel(("CoreAudio: [Output] Failed to remove the default playback device changed listener (%RI32)\n", err));
1858 }
1859
1860 err = AudioUnitUninitialize(pStreamOut->audioUnit);
1861 if (err == noErr)
1862 {
1863 err = CloseComponent(pStreamOut->audioUnit);
1864 if (err == noErr)
1865 {
1866 RTCircBufDestroy(pStreamOut->pBuf);
1867 pStreamOut->pBuf = NULL;
1868
1869 pStreamOut->audioUnit = NULL;
1870 pStreamOut->deviceID = kAudioDeviceUnknown;
1871
1872 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_UNINIT);
1873 }
1874 else
1875 LogRel(("CoreAudio: Failed to close the AudioUnit (%RI32)\n", err));
1876 }
1877 else
1878 LogRel(("CoreAudio: Failed to uninitialize the AudioUnit (%RI32)\n", err));
1879 }
1880 else
1881 LogRel(("CoreAudio: Failed to stop playback, rc=%Rrc\n", rc));
1882
1883 LogFlowFuncLeaveRC(rc);
1884 return rc;
1885}
1886
1887static DECLCALLBACK(int) drvHostCoreAudioInitIn(PPDMIHOSTAUDIO pInterface,
1888 PPDMAUDIOHSTSTRMIN pHstStrmIn, PPDMAUDIOSTREAMCFG pCfg,
1889 PDMAUDIORECSOURCE enmRecSource,
1890 uint32_t *pcSamples)
1891{
1892 PCOREAUDIOSTREAMIN pStreamIn = (PCOREAUDIOSTREAMIN)pHstStrmIn;
1893
1894 LogFlowFunc(("enmRecSource=%ld\n"));
1895
1896 ASMAtomicXchgU32(&pStreamIn->status, CA_STATUS_UNINIT);
1897
1898 pStreamIn->audioUnit = NULL;
1899 pStreamIn->deviceID = kAudioDeviceUnknown;
1900 pStreamIn->converter = NULL;
1901 pStreamIn->sampleRatio = 1;
1902 pStreamIn->rpos = 0;
1903
1904 bool fDeviceByUser = false;
1905
1906 /* Initialize the hardware info section with the audio settings */
1907 int rc = DrvAudioStreamCfgToProps(pCfg, &pStreamIn->streamIn.Props);
1908 if (RT_SUCCESS(rc))
1909 {
1910#if 0
1911 /* Try to find the audio device set by the user */
1912 if (DeviceUID.pszInputDeviceUID)
1913 {
1914 pStreamIn->deviceID = drvHostCoreAudioDeviceUIDtoID(DeviceUID.pszInputDeviceUID);
1915 /* Not fatal */
1916 if (pStreamIn->deviceID == kAudioDeviceUnknown)
1917 LogRel(("CoreAudio: Unable to find input device %s. Falling back to the default audio device. \n", DeviceUID.pszInputDeviceUID));
1918 else
1919 fDeviceByUser = true;
1920 }
1921#endif
1922 rc = drvHostCoreAudioInitInput(&pStreamIn->streamIn, pcSamples);
1923 }
1924
1925 if (RT_SUCCESS(rc))
1926 {
1927 /* When the devices isn't forced by the user, we want default device change notifications. */
1928 if (!fDeviceByUser)
1929 {
1930 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,
1931 kAudioObjectPropertyElementMaster };
1932 OSStatus err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
1933 drvHostCoreAudioDefaultDeviceChanged, (void *)pStreamIn);
1934 /* Not fatal. */
1935 if (RT_LIKELY(err == noErr))
1936 {
1937 pStreamIn->fDefDevChgListReg = true;
1938 }
1939 else
1940 LogRel(("CoreAudio: Failed to add the default input device changed listener (%RI32)\n", err));
1941 }
1942 }
1943
1944 LogFlowFuncLeaveRC(rc);
1945 return rc;
1946}
1947
1948static DECLCALLBACK(int) drvHostCoreAudioInitOut(PPDMIHOSTAUDIO pInterface,
1949 PPDMAUDIOHSTSTRMOUT pHstStrmOut, PPDMAUDIOSTREAMCFG pCfg,
1950 uint32_t *pcSamples)
1951{
1952 PCOREAUDIOSTREAMOUT pStreamOut = (PCOREAUDIOSTREAMOUT)pHstStrmOut;
1953
1954 OSStatus err = noErr;
1955 int rc = 0;
1956 bool fDeviceByUser = false; /* use we a device which was set by the user? */
1957
1958 LogFlowFuncEnter();
1959
1960 ASMAtomicXchgU32(&pStreamOut->status, CA_STATUS_UNINIT);
1961
1962 pStreamOut->audioUnit = NULL;
1963 pStreamOut->deviceID = kAudioDeviceUnknown;
1964
1965 /* Initialize the hardware info section with the audio settings */
1966 DrvAudioStreamCfgToProps(pCfg, &pStreamOut->streamOut.Props);
1967
1968#if 0
1969 /* Try to find the audio device set by the user. Use
1970 * export VBOX_COREAUDIO_OUTPUT_DEVICE_UID=AppleHDAEngineOutput:0
1971 * to set it. */
1972 if (DeviceUID.pszOutputDeviceUID)
1973 {
1974 pStreamOut->audioDeviceId = drvHostCoreAudioDeviceUIDtoID(DeviceUID.pszOutputDeviceUID);
1975 /* Not fatal */
1976 if (pStreamOut->audioDeviceId == kAudioDeviceUnknown)
1977 LogRel(("CoreAudio: Unable to find output device %s. Falling back to the default audio device. \n", DeviceUID.pszOutputDeviceUID));
1978 else
1979 fDeviceByUser = true;
1980 }
1981#endif
1982
1983 rc = drvHostCoreAudioInitOutput(pHstStrmOut, pcSamples);
1984 if (RT_FAILURE(rc))
1985 return rc;
1986
1987 /* When the devices isn't forced by the user, we want default device change notifications. */
1988 if (!fDeviceByUser)
1989 {
1990 AudioObjectPropertyAddress propAdr = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,
1991 kAudioObjectPropertyElementMaster };
1992 err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAdr,
1993 drvHostCoreAudioDefaultDeviceChanged, (void *)pStreamOut);
1994 /* Not fatal. */
1995 if (RT_LIKELY(err == noErr))
1996 {
1997 pStreamOut->fDefDevChgListReg = true;
1998 }
1999 else
2000 LogRel(("CoreAudio: Failed to add the default output device changed listener (%RI32)\n", err));
2001 }
2002
2003 return VINF_SUCCESS;
2004}
2005
2006static DECLCALLBACK(bool) drvHostCoreAudioIsEnabled(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
2007{
2008 NOREF(pInterface);
2009 NOREF(enmDir);
2010 return true; /* Always all enabled. */
2011}
2012
2013static DECLCALLBACK(int) drvHostCoreAudioGetConf(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pAudioConf)
2014{
2015 pAudioConf->cbStreamOut = sizeof(COREAUDIOSTREAMOUT);
2016 pAudioConf->cbStreamIn = sizeof(COREAUDIOSTREAMIN);
2017 pAudioConf->cMaxHstStrmsOut = 1;
2018 pAudioConf->cMaxHstStrmsIn = 2;
2019
2020 return VINF_SUCCESS;
2021}
2022
2023static DECLCALLBACK(void) drvHostCoreAudioShutdown(PPDMIHOSTAUDIO pInterface)
2024{
2025 NOREF(pInterface);
2026}
2027
2028static DECLCALLBACK(void *) drvHostCoreAudioQueryInterface(PPDMIBASE pInterface, const char *pszIID)
2029{
2030 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
2031 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2032 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
2033 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
2034
2035 return NULL;
2036}
2037
2038 /* Construct a DirectSound Audio driver instance.
2039 *
2040 * @copydoc FNPDMDRVCONSTRUCT
2041 */
2042static DECLCALLBACK(int) drvHostCoreAudioConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
2043{
2044 PDRVHOSTCOREAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTCOREAUDIO);
2045 LogRel(("Audio: Initializing Core Audio driver\n"));
2046
2047 /*
2048 * Init the static parts.
2049 */
2050 pThis->pDrvIns = pDrvIns;
2051 /* IBase */
2052 pDrvIns->IBase.pfnQueryInterface = drvHostCoreAudioQueryInterface;
2053 /* IHostAudio */
2054 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvHostCoreAudio);
2055
2056 return VINF_SUCCESS;
2057}
2058
2059/**
2060 * Char driver registration record.
2061 */
2062const PDMDRVREG g_DrvHostCoreAudio =
2063{
2064 /* u32Version */
2065 PDM_DRVREG_VERSION,
2066 /* szName */
2067 "CoreAudio",
2068 /* szRCMod */
2069 "",
2070 /* szR0Mod */
2071 "",
2072 /* pszDescription */
2073 "Core Audio host driver",
2074 /* fFlags */
2075 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2076 /* fClass. */
2077 PDM_DRVREG_CLASS_AUDIO,
2078 /* cMaxInstances */
2079 ~0U,
2080 /* cbInstance */
2081 sizeof(DRVHOSTCOREAUDIO),
2082 /* pfnConstruct */
2083 drvHostCoreAudioConstruct,
2084 /* pfnDestruct */
2085 NULL,
2086 /* pfnRelocate */
2087 NULL,
2088 /* pfnIOCtl */
2089 NULL,
2090 /* pfnPowerOn */
2091 NULL,
2092 /* pfnReset */
2093 NULL,
2094 /* pfnSuspend */
2095 NULL,
2096 /* pfnResume */
2097 NULL,
2098 /* pfnAttach */
2099 NULL,
2100 /* pfnDetach */
2101 NULL,
2102 /* pfnPowerOff */
2103 NULL,
2104 /* pfnSoftReset */
2105 NULL,
2106 /* u32EndVersion */
2107 PDM_DRVREG_VERSION
2108};
2109
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