hydrogen 1.2.3
CoreAudioDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://hydrogen.sourceforge.net
7 *
8 * CoreAudio Driver for Hydrogen
9 * Copyright(c) 2005 by Jonathan Dempsey [jonathandempsey@fastmail.fm]
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY, without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see https://www.gnu.org/licenses
23 *
24 * Rewrote CoreAudio driver, now using AUHAL (2005/03/02 Jonathan Dempsey)
25 * Set Hydrogen to detect hardware device buffer size (2005/03/06 Jonathan Dempsey)
26 * Cleaned up the code a bit (2005/11/29 Jonathan Dempsey)
27 * More cleaning . . . (2005/12/28 Jonathan Dempsey)
28 */
29
31
32#if defined(H2CORE_HAVE_COREAUDIO) || _DOXYGEN_
33
34#include "CoreServices/CoreServices.h"
38static OSStatus renderProc(
39 void *inRefCon,
40 AudioUnitRenderActionFlags *ioActionFlags,
41 const AudioTimeStamp *inTimeStamp,
42 UInt32 inBusNumber,
43 UInt32 inNumberFrames,
44 AudioBufferList *ioData
45)
46{
47 H2Core::CoreAudioDriver* pDriver = ( H2Core::CoreAudioDriver * )inRefCon;
48 assert( ioData->mNumberBuffers > 0 && ioData->mNumberBuffers <= 2 );
49 pDriver->m_pOut_L = static_cast< float *>( ioData->mBuffers[ 0 ].mData );
50 pDriver->m_pOut_R = static_cast< float *>( ioData->mBuffers[ 1 ].mData );
51 pDriver->mProcessCallback( inNumberFrames, NULL );
52 pDriver->m_pOut_L = nullptr;
53 pDriver->m_pOut_R = nullptr;
54 return noErr;
55}
56
57
58
59namespace H2Core
60{
61
63 // Calculate the overall latency as the device latency + the stream latency.
64 OSStatus err;
65 UInt32 nSize;
66
67 AudioObjectPropertyAddress propertyAddress = {
68 kAudioDevicePropertyLatency,
69 kAudioDevicePropertyScopeInput,
70 0
71 };
72 UInt32 nDeviceLatency;
73 nSize = sizeof( nDeviceLatency );
74 err = AudioObjectGetPropertyData( m_outputDevice, &propertyAddress, 0, NULL, &nSize, &nDeviceLatency );
75 if ( err != noErr ) {
76 ERRORLOG( "Couldn't get device latency" );
77 return -1;
78 }
79
80 // Find the stream ID for the output stream, then find the latency
81 AudioStreamID streamID;
82 nSize = sizeof( streamID );
83 propertyAddress = {
84 kAudioDevicePropertyStreams,
85 kAudioDevicePropertyScopeOutput,
86 0
87 };
88 err = AudioObjectGetPropertyData( m_outputDevice, &propertyAddress, 0, NULL, &nSize, &streamID );
89 if ( err != noErr ) {
90 ERRORLOG( "Couldn't get stream for output device" );
91 }
92
93 UInt32 nStreamLatency;
94 nSize = sizeof(nStreamLatency);
95 propertyAddress = {
96 kAudioStreamPropertyLatency,
97 kAudioObjectPropertyScopeOutput,
98 0
99 };
100 err = AudioObjectGetPropertyData( streamID, &propertyAddress, 0, NULL, &nSize, &nStreamLatency );
101 if ( err != noErr ) {
102 ERRORLOG( QString("Couldn't get stream latency") );
103 }
104
105 return nDeviceLatency + nStreamLatency + m_nBufferSize;
106}
107
108QString CoreAudioDriver::deviceName( AudioDeviceID deviceID )
109{
110 OSStatus err;
111 CFStringRef deviceNameRef;
112 UInt32 size = sizeof( deviceNameRef );
113 AudioObjectPropertyAddress propertyAddress = {
114 kAudioDevicePropertyDeviceNameCFString,
115 kAudioDevicePropertyScopeOutput,
116 0
117 };
118 err = AudioObjectGetPropertyData( deviceID, &propertyAddress, 0, NULL, &size, &deviceNameRef );
119 if ( err != noErr ) {
120 ERRORLOG( QString( "Couldn't get name for device %1" ).arg( deviceID ) );
121 return QString();
122 }
123 UInt32 nBufferSize = CFStringGetMaximumSizeForEncoding( CFStringGetLength( deviceNameRef ), kCFStringEncodingUTF8 );
124 char buffer[ nBufferSize + 1 ];
125 CFStringGetCString( deviceNameRef, buffer, nBufferSize + 1, kCFStringEncodingUTF8 );
126 CFRelease( deviceNameRef );
127
128 return QString( buffer );
129
130}
131
132std::vector< AudioDeviceID > CoreAudioDriver::outputDeviceIDs()
133{
134 std::vector< AudioDeviceID > outputDeviceIDs;
135 QStringList res;
136 UInt32 dataSize;
137 OSStatus err;
138
139 // Read the 'Devices' system property
140
141 AudioObjectPropertyAddress propertyAddress = {
142 kAudioHardwarePropertyDevices,
143 kAudioObjectPropertyScopeGlobal,
144 kAudioObjectPropertyElementMaster
145 };
146
147 err = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject,
148 &propertyAddress, 0, NULL, &dataSize );
149 if ( err != noErr ) {
150 ERRORLOG( "Couldn't get size for devices list" );
151 return outputDeviceIDs;
152 }
153
154 int nDevices = dataSize / sizeof( AudioDeviceID );
155 AudioDeviceID deviceIDs[ nDevices ];
156
157 err = AudioObjectGetPropertyData( kAudioObjectSystemObject,
158 &propertyAddress, 0, NULL, &dataSize, deviceIDs );
159 if ( err != noErr ) {
160 ERRORLOG( "Couldn't read device IDs" );
161 return outputDeviceIDs;
162 }
163
164 // Find suitable output devices
165
166 for ( int i = 0; i < nDevices; i++ ) {
167 UInt32 nBufferListSize = 0;
168 AudioObjectPropertyAddress propertyAddress = {
169 kAudioDevicePropertyStreamConfiguration,
170 kAudioDevicePropertyScopeOutput,
171 0
172 };
173 err = AudioObjectGetPropertyDataSize( deviceIDs[ i ], &propertyAddress, 0, NULL, &nBufferListSize );
174 if ( err != noErr ) {
175 ERRORLOG( "Couldn't get device config size" );
176 continue;
177 }
178 AudioBufferList *pBufferList = (AudioBufferList *) alloca( nBufferListSize );
179 err = AudioObjectGetPropertyData( deviceIDs[ i ], &propertyAddress, 0, NULL, &nBufferListSize, pBufferList );
180
181 int nChannels = 0;
182 for ( int nBuffer = 0; nBuffer < pBufferList->mNumberBuffers; nBuffer++ ) {
183 nChannels += pBufferList->mBuffers[ nBuffer ].mNumberChannels;
184 }
185 if ( nChannels < 2 ) {
186 // Skip input devices and any mono outputs
187 if ( nChannels == 1 ) {
188 INFOLOG( QString( "Skipping mono output device %1" ).arg( deviceIDs[ i ] ) );
189 }
190 continue;
191 }
192
193 outputDeviceIDs.push_back( deviceIDs[ i ] );
194 }
195
196 return outputDeviceIDs;
197}
198
199
201{
202 QStringList res;
203 res.push_back( "default" );
204 for ( AudioDeviceID device : outputDeviceIDs() ) {
205 res.push_back( deviceName( device ) );
206 }
207 return res;
208}
209
211{
212 QString sPreferredDeviceName = Preferences::get_instance()->m_sCoreAudioDevice;
213
214 if ( sPreferredDeviceName.isNull()
215 || QString::compare( sPreferredDeviceName, "default", Qt::CaseInsensitive ) == 0 ) {
216 INFOLOG( "Using default device" );
217 return defaultOutputDevice();
218 }
219 for ( AudioDeviceID device : outputDeviceIDs() ) {
220 QString sDeviceName = deviceName( device );
221 if ( QString::compare( sDeviceName, sPreferredDeviceName, Qt::CaseInsensitive ) == 0 ) {
222 // Found it.
223 INFOLOG( QString( "Found device '%1' (%2) for preference '%3'" )
224 .arg( sDeviceName ).arg( (int)device ).arg( sPreferredDeviceName ) );
225 return device;
226 }
227 }
228 ERRORLOG( QString( "Couldn't find device '%1', falling back to default" ).arg( sPreferredDeviceName ) );
229 return defaultOutputDevice();
230}
231
233{
234 getDevices();
235 UInt32 dataSize = 0;
236 OSStatus err = 0;
237 AudioDeviceID device;
238
239 AudioObjectPropertyAddress propertyAddress = {
240 kAudioHardwarePropertyDefaultOutputDevice,
241 kAudioObjectPropertyScopeGlobal,
242 kAudioObjectPropertyElementMaster
243 };
244
245 dataSize = sizeof(AudioDeviceID);
246 err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
247 &propertyAddress,
248 0,
249 NULL,
250 &dataSize,
251 &device);
252
253 if ( err != noErr ) {
254 ERRORLOG( "Could not get Default Output Device" );
255 }
256 return device;
257}
258
260{
261 UInt32 dataSize = 0;
262 OSStatus err = 0;
263
264 AudioObjectPropertyAddress propertyAddress = {
265 kAudioDevicePropertyBufferFrameSize,
266 kAudioObjectPropertyScopeGlobal,
267 kAudioObjectPropertyElementMaster
268 };
269
270 dataSize = sizeof( m_nBufferSize );
271
272 err = AudioObjectGetPropertyData(m_outputDevice,
273 &propertyAddress,
274 0,
275 NULL,
276 &dataSize,
277 ( void * )&m_nBufferSize
278 );
279
280 if ( err != noErr ) {
281 ERRORLOG( "get BufferSize error" );
282 }
283 INFOLOG( QString( "Buffersize: %1" ).arg( m_nBufferSize ) );
284}
285
287{
288 AudioStreamBasicDescription outputStreamBasicDescription;
289 UInt32 propertySize = sizeof( outputStreamBasicDescription );
290 OSStatus err = 0;
291
292 AudioObjectPropertyAddress propertyAddress = {
293 kAudioDevicePropertyStreamFormat,
294 kAudioObjectPropertyScopeGlobal,
295 kAudioObjectPropertyElementMaster
296 };
297
298 err = AudioObjectGetPropertyData(m_outputDevice,
299 &propertyAddress,
300 0,
301 NULL,
302 &propertySize,
303 &outputStreamBasicDescription
304 );
305
306 if ( err ) {
307 ERRORLOG( QString("AudioDeviceGetProperty: returned %1 when getting kAudioDevicePropertyStreamFormat").arg(err) );
308 }
309
310 INFOLOG( QString("SampleRate: %1").arg( outputStreamBasicDescription.mSampleRate ) );
311 INFOLOG( QString("BytesPerPacket: %1").arg( outputStreamBasicDescription.mBytesPerPacket ) );
312 INFOLOG( QString("FramesPerPacket: %1").arg( outputStreamBasicDescription.mFramesPerPacket ) );
313 INFOLOG( QString("BytesPerFrame: %1").arg( outputStreamBasicDescription.mBytesPerFrame ) );
314 INFOLOG( QString("ChannelsPerFrame: %1").arg( outputStreamBasicDescription.mChannelsPerFrame ) );
315 INFOLOG( QString("BitsPerChannel: %1").arg( outputStreamBasicDescription.mBitsPerChannel ) );
316}
317
318
322 , m_bIsRunning( false )
323 , mProcessCallback( processCallback )
324 , m_pOut_L( NULL )
325 , m_pOut_R( NULL )
326{
328
329 //Get the default playback device and store it in m_outputDevice
331
332 //Get the buffer size of the previously detected device and store it in m_nBufferSize
334
335 // print some info
337}
338
339
340
345
346
347
348int CoreAudioDriver::init( unsigned nBufferSize )
349{
350 OSStatus err = noErr;
351
352 // Get Component
353 AudioComponent compOutput;
354 AudioComponentDescription descAUHAL;
355
356 descAUHAL.componentType = kAudioUnitType_Output;
357 descAUHAL.componentSubType = kAudioUnitSubType_HALOutput;
358 descAUHAL.componentManufacturer = kAudioUnitManufacturer_Apple;
359 descAUHAL.componentFlags = 0;
360 descAUHAL.componentFlagsMask = 0;
361
362 compOutput = AudioComponentFindNext( NULL, &descAUHAL );
363 if ( compOutput == NULL ) {
364 ERRORLOG( "Error in FindNextComponent" );
365 //exit (-1);
366 }
367
368 err = AudioComponentInstanceNew( compOutput, &m_outputUnit );
369 if ( err != noErr ) {
370 ERRORLOG( "Error Opening Component" );
371 }
372
373 // Get Current Output Device
375
376 // Set AUHAL to Current Device
377 err = AudioUnitSetProperty(
379 kAudioOutputUnitProperty_CurrentDevice,
380 kAudioUnitScope_Global,
381 0,
383 sizeof( m_outputDevice )
384 );
385 if ( err != noErr ) {
386 ERRORLOG( "Could not set Current Device" );
387 }
388
389 AudioStreamBasicDescription asbdesc;
390 asbdesc.mSampleRate = ( Float64 )m_nSampleRate;
391 asbdesc.mFormatID = kAudioFormatLinearPCM;
392 asbdesc.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
393 asbdesc.mBytesPerPacket = sizeof( Float32 );
394 asbdesc.mFramesPerPacket = 1;
395 asbdesc.mBytesPerFrame = sizeof( Float32 );
396 asbdesc.mChannelsPerFrame = 2; // comix: was set to 1
397 asbdesc.mBitsPerChannel = 32;
398
399 err = AudioUnitSetProperty(
401 kAudioUnitProperty_StreamFormat,
402 kAudioUnitScope_Input,
403 0,
404 &asbdesc,
405 sizeof( AudioStreamBasicDescription )
406 );
407
408 // Set Render Callback
409 AURenderCallbackStruct out;
410 out.inputProc = renderProc;
411 out.inputProcRefCon = ( void * )this;
412
413 err = AudioUnitSetProperty(
415 kAudioUnitProperty_SetRenderCallback,
416 kAudioUnitScope_Global,
417 0,
418 &out,
419 sizeof( out )
420 );
421 if ( err != noErr ) {
422 ERRORLOG( "Could not Set Render Callback" );
423 }
424
425 //Initialize AUHAL
426 err = AudioUnitInitialize( m_outputUnit );
427 if ( err != noErr ) {
428 ERRORLOG( "Could not Initialize AudioUnit" );
429 }
430
431 // Set buffer size
432 INFOLOG( QString( "Setting buffer size to %1" ).arg( nBufferSize ) );
433 AudioObjectPropertyAddress propertyAddress = {
434 kAudioDevicePropertyBufferFrameSize,
435 kAudioObjectPropertyScopeGlobal,
436 kAudioObjectPropertyElementMaster
437 };
438
439 err = AudioObjectSetPropertyData( m_outputDevice,
440 &propertyAddress,
441 0,
442 NULL,
443 sizeof(UInt32), &nBufferSize);
444
445 if ( err != noErr ) {
446 ERRORLOG( QString( "Could not set buffer size to %1" ).arg( nBufferSize ) );
447 }
448
450
451 return 0;
452}
453
454
455
457{
458 OSStatus err;
459 err = AudioOutputUnitStart( m_outputUnit );
460 if ( err != noErr ) {
461 ERRORLOG( "Could not start AudioUnit" );
462 }
463
464 m_bIsRunning = true;
465 return 0;
466}
467
468
469
471{
472 OSStatus err = noErr;
473 err = AudioOutputUnitStop( m_outputUnit );
474 err = AudioUnitUninitialize( m_outputUnit );
475 err = AudioComponentInstanceDispose( m_outputUnit );
476}
477
479{
480 return m_pOut_L;
481}
482
483
484
486{
487 return m_pOut_R;
488}
489
490
491
493{
494 return m_nBufferSize;
495}
496
497
498
500{
501 return m_nSampleRate;
502}
503
504}
505
506#endif // H2CORE_HAVE_COREAUDIO
static OSStatus renderProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
The Render Callback.
#define INFOLOG(x)
Definition Object.h:237
#define ERRORLOG(x)
Definition Object.h:239
Base abstract class for audio output classes.
Definition AudioOutput.h:39
virtual void disconnect() override
virtual float * getOut_L() override
virtual int init(unsigned nBufferSize) override
virtual int getLatency() override
Approximate audio latency (in frames) A reasonable approximation is the buffer time on most audio sys...
virtual float * getOut_R() override
virtual unsigned getBufferSize() override
audioProcessCallback mProcessCallback
virtual int connect() override
static QStringList getDevices()
AudioDeviceID preferredOutputDevice()
static QString deviceName(AudioDeviceID deviceID)
AudioDeviceID defaultOutputDevice(void)
static std::vector< AudioDeviceID > outputDeviceIDs()
virtual unsigned getSampleRate() override
CoreAudioDriver(audioProcessCallback processCallback)
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
unsigned m_nSampleRate
Sample rate of the audio.
int(* audioProcessCallback)(uint32_t, void *)
Definition AudioOutput.h:32