1/**********************************************************************************************************************************
2*
3*   OpenAL cross platform audio library
4*	Copyright (c) 2004, Apple Computer, Inc., Copyright (c) 2012, Apple Inc. All rights reserved.
5*
6*	Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
7*	conditions are met:
8*
9*	1.  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
10*	2.  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
11*		disclaimer in the documentation and/or other materials provided with the distribution.
12*	3.  Neither the name of Apple Inc. ("Apple") nor the names of its contributors may be used to endorse or promote products derived
13*		from this software without specific prior written permission.
14*
15*	THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16*	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS
17*	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
18*	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
19*	AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
20*	ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
21*
22**********************************************************************************************************************************/
23
24#include "oalDevice.h"
25#include "oalContext.h"
26
27// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
28
29#define LOG_DEVICE_CHANGES  	0
30#define PROFILE_IO_USAGE		0
31#define LOG_VERBOSE             0
32
33#if PROFILE_IO_USAGE
34static int debugCounter         = -1;
35static int numCyclesToPrint     = 1000;
36
37static UInt64 lastHostTime;
38static UInt64 totalHostTime;
39static UInt64 minUsage;
40static UInt64 maxUsage;
41static UInt64 totalUsage;
42
43#define PROFILE_IO_CYCLE 0
44#if PROFILE_IO_CYCLE
45static UInt64 maxHT;
46static UInt64 minHT;
47#endif
48
49#include <CoreAudio/HostTime.h>
50#endif
51
52// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53#if GET_OVERLOAD_NOTIFICATIONS
54OSStatus	PrintTheOverloadMessage(	AudioDeviceID			inDevice,
55										UInt32					inChannel,
56										Boolean					isInput,
57										AudioDevicePropertyID	inPropertyID,
58										void*					inClientData)
59{
60	DebugMessage("OVERLOAD OCCURRED");
61}
62#endif
63
64// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66// OALDevices
67// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69#pragma mark ***** OALDevices *****
70
71/*
72	If caller wants a specific HAL device (instead of the default output device), a NULL terminated
73	C-String representation of the CFStringRef returned from the HAL APIs for the
74	kAudioDevicePropertyDeviceUID property
75*/
76OALDevice::OALDevice (const char* 	 inDeviceName, uintptr_t   inSelfToken, UInt32	inRenderChannelSetting)
77	: 	mSelfToken (inSelfToken),
78		mCurrentError(ALC_NO_ERROR),
79        mHALDevice (0),
80        mDistanceScalingRequired(false),
81		mGraphInitialized(false),
82        mAUGraph(0),
83		mOutputNode(0),
84		mOutputUnit(0),
85		mMixerNode(0),
86		mChannelLayoutTag(0),
87		mConnectedContext(NULL),
88		mDeviceSampleRate(kDefaultMixerRate),
89		mRenderChannelCount(0),
90        mRenderChannelSetting(inRenderChannelSetting),
91		mFramesPerSlice(512),
92		mInUseFlag(0)
93{
94#if LOG_VERBOSE
95	DebugMessageN1("OALDevice::OALDevice() - OALDevice = %ld", (long int) mSelfToken);
96#endif
97	OSStatus	result = noErr;
98	UInt32		size = 0;
99	CFStringRef	cfString = NULL;
100    char        *useThisDevice = (char *) inDeviceName;
101
102	try {
103		// make sure a proper render channel setting was passed to teh constructor
104		if ((inRenderChannelSetting != ALC_MAC_OSX_RENDER_CHANNEL_COUNT_MULTICHANNEL) && (inRenderChannelSetting != ALC_MAC_OSX_RENDER_CHANNEL_COUNT_STEREO))
105			throw (OSStatus) AL_INVALID_VALUE;
106
107        // until the ALC_ENUMERATION_EXT extension is supported only use the default output device
108        useThisDevice = NULL;
109
110		// first, get the requested HAL device's ID
111		if (useThisDevice)
112		{
113			// turn the inDeviceName into a CFString
114			cfString = CFStringCreateWithCString(NULL, useThisDevice, kCFStringEncodingUTF8);
115			if (cfString)
116			{
117				AudioValueTranslation	translation;
118
119				translation.mInputData = &cfString;
120				translation.mInputDataSize = sizeof(cfString);
121				translation.mOutputData = &mHALDevice;
122				translation.mOutputDataSize = sizeof(mHALDevice);
123
124				size = sizeof(AudioValueTranslation);
125				result = AudioHardwareGetProperty(kAudioHardwarePropertyDeviceForUID, &size, &translation);
126                CFRelease (cfString);
127			}
128			else
129				result = -1; // couldn't get string ref
130
131			THROW_RESULT
132		}
133		else
134		{
135			size = sizeof(AudioDeviceID);
136			result = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &mHALDevice);
137				THROW_RESULT
138		}
139
140		InitializeGraph(useThisDevice);
141
142		mRenderChannelCount = GetDesiredRenderChannelCount();
143
144#if PROFILE_IO_USAGE
145		debugCounter = -1;
146#endif
147
148	}
149	catch (...) {
150        throw;
151	}
152}
153
154// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
155OALDevice::~OALDevice()
156{
157#if LOG_VERBOSE
158	DebugMessageN1("OALDevice::~OALDevice() - OALDevice = %ld", (long int) mSelfToken);
159#endif
160	TeardownGraph();
161}
162
163// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
164void	OALDevice::SetError(ALenum errorCode)
165{
166#if LOG_VERBOSE
167	DebugMessageN2("OALDevice::SetError() - OALDevice:errorCode = %ld:%d", (long int) mSelfToken, errorCode);
168#endif
169	if (mCurrentError == ALC_NO_ERROR)
170		return;
171
172	mCurrentError = errorCode;
173}
174
175// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
176ALenum	OALDevice::GetError()
177{
178#if LOG_VERBOSE
179	DebugMessageN1("OALDevice::GetError() - OALDevice = %ld", (long int) mSelfToken);
180#endif
181	ALenum	latestError = mCurrentError;
182	mCurrentError = ALC_NO_ERROR;
183
184	return latestError;
185}
186
187// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
188void OALDevice::TeardownGraph()
189{
190#if LOG_VERBOSE || LOG_DEVICE_CHANGES
191	DebugMessageN1("OALDevice::TeardownGraph() - OALDevice = %ld", (long int) mSelfToken);
192#endif
193
194#if GET_OVERLOAD_NOTIFICATIONS
195	AudioDeviceID	device = 0;
196	UInt32			size = sizeof(device);
197
198	AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &size, &device);
199	if (device != 0)
200	{
201		DebugMessage("********** Removing Overload Notification ***********");
202		AudioDeviceRemovePropertyListener(	device, 0, false, kAudioDeviceProcessorOverload, PrintTheOverloadMessage);
203	}
204#endif
205
206	if (mAUGraph)
207	{
208		StopGraph();
209		DisposeAUGraph (mAUGraph);
210		mAUGraph = 0;
211	}
212}
213
214// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215// also resets the audio channel layout if necessary
216void	OALDevice::ResetRenderChannelSettings()
217{
218#if LOG_VERBOSE || LOG_DEVICE_CHANGES
219	DebugMessageN1("OALDevice::ResetRenderChannelSettings() - OALDevice = %ld", (long int) mSelfToken);
220#endif
221
222	// verify that the channel count has actually changed before doing all this work...
223	UInt32	channelCount = GetDesiredRenderChannelCount();
224
225	if (mRenderChannelCount == channelCount)
226		return; // only reset the graph if the channel count has changed
227
228	mRenderChannelCount = channelCount;
229
230	Boolean		wasRunning = false;
231	AUGraphIsRunning (mAUGraph, &wasRunning);
232	if (wasRunning)
233		StopGraph();
234
235	// disconnect the mixer (mMixerNode) from the  output au if necessary
236	OSStatus	result = noErr;
237	if (mMixerNode)
238	{
239		// mixer is currently connected
240		result = AUGraphDisconnectNodeInput (mAUGraph, mOutputNode, 0);
241			THROW_RESULT
242		// update the graph
243		result = AUGraphUpdate (mAUGraph, NULL);
244			THROW_RESULT
245	}
246
247	// set AU properties
248	{
249		CAStreamBasicDescription	format;
250		UInt32                      propSize = sizeof(format);
251		// get the current input scope format  of the output device, so we can just change the channel count
252		result = AudioUnitGetProperty(mOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, &propSize);
253			THROW_RESULT
254
255		format.SetCanonical (mRenderChannelCount, false);     // not interleaved
256		result = AudioUnitSetProperty (mOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(format));
257			THROW_RESULT
258
259		// The Output Format of the Contexts that use this device will be set later by ReconfigureContextsOfThisDevice()
260
261		// channel layout may have changed do to a different render channel count
262		AudioChannelLayout		layout;
263		layout.mChannelLayoutTag = GetChannelLayoutTag();
264		layout.mChannelBitmap = 0;
265		layout.mNumberChannelDescriptions = 0;
266		result = AudioUnitSetProperty (mOutputUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof(layout));
267			THROW_RESULT
268	}
269
270	// tell the contexts using this device to change their mixer output format - so when they are reconnected to the output au - formats will be reset
271	ReconfigureContextsOfThisDevice(mSelfToken);
272
273	// reconnect mixer to output unit if it was previously connected
274	if (mMixerNode)
275	{
276		result = AUGraphConnectNodeInput (mAUGraph, mMixerNode, 0, mOutputNode, 0);
277			THROW_RESULT
278
279		// update the graph
280		result = AUGraphUpdate (mAUGraph, NULL);
281			THROW_RESULT
282	}
283
284	if (wasRunning)
285		AUGraphStart(mAUGraph); // restart the graph if it was already running
286
287	return;
288}
289
290// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
291void OALDevice::GraphFormatPropertyListener (	void				*inRefCon,
292												AudioUnit			ci,
293												AudioUnitPropertyID inID,
294												AudioUnitScope		inScope,
295												AudioUnitElement	inElement)
296{
297#if LOG_VERBOSE || LOG_DEVICE_CHANGES
298	DebugMessageN1("OALDevice::GraphFormatPropertyListener - OALDevice: %ld", ((OALDevice*)inRefCon)->mSelfToken);
299#endif
300
301	try {
302		if (inScope == kAudioUnitScope_Output)
303		{
304			((OALDevice*)inRefCon)->ResetRenderChannelSettings ();
305		}
306	}
307	catch (...) {
308	}
309}
310
311// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
312void OALDevice::SetRenderChannelSetting (UInt32 inRenderChannelSetting)
313{
314#if LOG_VERBOSE || LOG_DEVICE_CHANGES
315	DebugMessageN1("OALDevice::SetRenderChannelSetting() - OALDevice = %ld", (long int) mSelfToken);
316#endif
317
318    try {
319		if ((inRenderChannelSetting != ALC_MAC_OSX_RENDER_CHANNEL_COUNT_MULTICHANNEL) && (inRenderChannelSetting != ALC_MAC_OSX_RENDER_CHANNEL_COUNT_STEREO))
320			throw (OSStatus) AL_INVALID_VALUE;
321
322		if (inRenderChannelSetting == mRenderChannelSetting)
323			return; //nothing to do
324
325		mRenderChannelSetting = inRenderChannelSetting;
326
327		if (inRenderChannelSetting == ALC_MAC_OSX_RENDER_CHANNEL_COUNT_STEREO)
328		{
329			// clamping to stereo
330			if (mRenderChannelCount == 2)
331				return; // already rendering to stereo, so there's nothing to do
332		}
333		else
334		{
335			// allowing multi channel now
336			if (mRenderChannelCount > 2)
337				return; // already rendering to mc, so there's nothing to do
338		}
339
340		// work to be done now, it is necessary to change the channel layout and stream format from multi channel to stereo
341		// this requires the graph to be stopped and reconfigured
342		ResetRenderChannelSettings ();
343	}
344	catch (OSStatus		result) {
345		DebugMessageN2("OALDevice::SetRenderChannelSetting - OALDevice: %ld:%ld", mSelfToken, (long int) result);
346	}
347	catch (...) {
348	}
349}
350
351// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
352void OALDevice::InitializeGraph (const char* 		inDeviceName)
353{
354#if LOG_VERBOSE || LOG_DEVICE_CHANGES
355	DebugMessageN1("OALDevice::InitializeGraph() - OALDevice = %ld", (long int) mSelfToken);
356#endif
357
358	if (mAUGraph)
359		throw static_cast<OSStatus>('init');
360
361    OSStatus result = noErr;
362
363    // ~~~~~~~~~~~~~~~~~~~~ CREATE GRAPH
364
365	result = NewAUGraph(&mAUGraph);
366		THROW_RESULT
367
368    // ~~~~~~~~~~~~~~~~~~~~ SET UP OUTPUT NODE
369
370	ComponentDescription	cd;
371	cd.componentFlags = 0;
372	cd.componentFlagsMask = 0;
373
374	// At this time, only allow the default output device to be used and ignore the inDeviceName parameter
375	cd.componentType = kAudioUnitType_Output;
376	cd.componentSubType = kAudioUnitSubType_DefaultOutput;
377	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
378	result = AUGraphNewNode (mAUGraph, &cd, 0, NULL, &mOutputNode);
379		THROW_RESULT
380
381	// ~~~~~~~~~~~~~~~~~~~~ OPEN GRAPH
382
383	result = AUGraphOpen (mAUGraph);
384		THROW_RESULT
385
386	result = AUGraphGetNodeInfo (mAUGraph, mOutputNode, 0, 0, 0, &mOutputUnit);
387		THROW_RESULT
388
389 	result = AudioUnitInitialize (mOutputUnit);
390		THROW_RESULT
391
392	result = AudioUnitAddPropertyListener (mOutputUnit, kAudioUnitProperty_StreamFormat, GraphFormatPropertyListener, this);
393		THROW_RESULT
394
395	// Frame Per Slice
396	// get the device's frame count and set the AUs to match, will be set to 512 if this fails
397	AudioDeviceID	device = 0;
398	UInt32	dataSize = sizeof(device);
399	result = AudioUnitGetProperty(mOutputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &device, &dataSize);
400	if (result == noErr)
401	{
402		dataSize = sizeof(mFramesPerSlice);
403		result = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyBufferFrameSize, &dataSize, &mFramesPerSlice);
404		if (result == noErr)
405		{
406			result = AudioUnitSetProperty(  mOutputUnit, kAudioUnitProperty_MaximumFramesPerSlice,
407											kAudioUnitScope_Global, 0, &mFramesPerSlice, sizeof(mFramesPerSlice));
408		}
409	}
410
411	// get the device's outputRate
412	CAStreamBasicDescription	format;
413	UInt32                      propSize = sizeof(format);
414	result = AudioUnitGetProperty(mOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &format, &propSize);
415	if (result == noErr)
416		mDeviceSampleRate = format.mSampleRate;
417
418}
419
420// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
421UInt32 OALDevice::GetDesiredRenderChannelCount ()
422{
423#if LOG_VERBOSE
424	DebugMessageN1("OALDevice::GetDesiredRenderChannelCount - OALDevice: %ld", mSelfToken);
425#endif
426
427	UInt32			returnValue = 2;	// return stereo by default
428
429    // observe the mRenderChannelSetting flag and clamp to stereo if necessary
430    // This allows the user to request the libary to render to stereo in the case where only 2 speakers
431    // are connected to multichannel hardware
432    if (mRenderChannelSetting == ALC_MAC_OSX_RENDER_CHANNEL_COUNT_STEREO)
433        return (returnValue);
434
435	// get the HAL device id form the output AU
436	AudioDeviceID	deviceID;
437	UInt32			propSize =  sizeof(deviceID);
438	OSStatus	result = AudioUnitGetProperty(mOutputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Output, 1, &deviceID, &propSize);
439		THROW_RESULT
440
441	// get the channel layout set by the user in AMS
442	result = AudioDeviceGetPropertyInfo(deviceID, 0, false, kAudioDevicePropertyPreferredChannelLayout, &propSize, NULL);
443
444	if (result == noErr)
445	{
446		AudioChannelLayout* layout = (AudioChannelLayout *) calloc(1, propSize);
447		if (layout != NULL)
448		{
449			result = AudioDeviceGetProperty(deviceID, 0, false, kAudioDevicePropertyPreferredChannelLayout, &propSize, layout);
450
451			if (layout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions)
452			{
453				// no channel layout tag is returned, so walk through the channel descriptions and count
454				// the channels that are associated with a speaker
455				if (layout->mNumberChannelDescriptions == 2)
456				{
457					returnValue = 2;        // there is no channel info for stereo
458				}
459				else
460				{
461					returnValue = 0;
462					for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++)
463					{
464						if ((layout->mChannelDescriptions[i].mChannelLabel != kAudioChannelLabel_Unknown) && (layout->mChannelDescriptions[i].mChannelLabel != kAudioChannelLabel_LFEScreen))
465							returnValue++;
466					}
467				}
468				mChannelLayoutTag = GetLayoutTagForLayout(layout, returnValue);
469			}
470			else
471			{
472				mChannelLayoutTag = layout->mChannelLayoutTag;
473				switch (layout->mChannelLayoutTag)
474				{
475					case kAudioChannelLayoutTag_AudioUnit_5_0:
476					case kAudioChannelLayoutTag_AudioUnit_5_1:
477						returnValue = 5;
478						break;
479					case kAudioChannelLayoutTag_AudioUnit_6_0:
480					case kAudioChannelLayoutTag_AudioUnit_6_1:
481						returnValue = 6;
482						break;
483					case kAudioChannelLayoutTag_AudioUnit_7_0:
484					case kAudioChannelLayoutTag_AudioUnit_7_1:
485					case kAudioChannelLayoutTag_AudioUnit_7_0_Front:
486						returnValue = 7;
487						break;
488					case kAudioChannelLayoutTag_AudioUnit_8:
489						returnValue = 8;
490						break;
491					case kAudioChannelLayoutTag_AudioUnit_4:
492						returnValue = 4;
493						break;
494					default:
495						returnValue = 2;
496						break;
497				}
498			}
499
500			free(layout);
501		}
502    }
503	// pass in num channels on the hw,
504	// how many channels the user has requested, and which 3DMixer is present
505	returnValue	= GetDesiredRenderChannelsFor3DMixer(returnValue);
506
507	return (returnValue);
508}
509
510// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
511// called from alcDestroyContext(), when all context's that use this device are gone
512void OALDevice::StopGraph()
513{
514#if LOG_VERBOSE
515	DebugMessageN1("OALDevice::StopGraph() - OALDevice = %ld", (long int) mSelfToken);
516#endif
517	AUGraphStop (mAUGraph);
518	Boolean flag;
519	do {
520		AUGraphIsRunning (mAUGraph, &flag);
521	} while (flag);
522}
523
524// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
525/*
526	When a Context is connected for the first time, the graph is initialized & started.
527	It will not be explicitly stopped until all the contexts that use this device have been destroyed.
528*/
529void	OALDevice::ConnectContext (OALContext*	inContext)
530{
531#if LOG_VERBOSE || LOG_DEVICE_CHANGES
532	DebugMessageN1("OALDevice::ConnectContext() - OALDevice = %ld", (long int) mSelfToken);
533#endif
534
535	OSStatus	result = noErr;
536	OALContext*	oldContext = mConnectedContext; // save in case it needs to be restored
537
538	if (inContext == mConnectedContext)
539		return;	// already connected
540
541	try {
542		// we only have to disconnect when the 3DMixer node has changed
543		if (mConnectedContext && (inContext->GetMixerNode() != mMixerNode)){
544			result = AUGraphDisconnectNodeInput(mAUGraph, mOutputNode, 0);
545				THROW_RESULT
546
547			mMixerNode = 0;
548			mConnectedContext = 0;
549		}
550
551		// set AU properties
552		{
553			CAStreamBasicDescription	format;
554			UInt32                      propSize = sizeof(format);
555			result = AudioUnitGetProperty(mOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &format, &propSize);
556				THROW_RESULT
557
558			format.SetCanonical (mRenderChannelCount, false);     // not interleaved
559			format.mSampleRate = inContext->GetMixerRate();
560
561			result = AudioUnitSetProperty (mOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(format));
562				THROW_RESULT
563
564			result = AudioUnitSetProperty (inContext->GetMixerUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &format, sizeof(format));
565				THROW_RESULT
566
567			// used to be a configure graph AUs (outout AU and mixerAU to be connected)
568			AudioChannelLayout		layout;
569			layout.mChannelLayoutTag = GetChannelLayoutTag();
570			layout.mChannelBitmap = 0;
571			layout.mNumberChannelDescriptions = 0;
572			result = AudioUnitSetProperty (mOutputUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof(layout));
573				THROW_RESULT
574		}
575
576		// connect new mixer to output unit
577		result = AUGraphConnectNodeInput (mAUGraph, inContext->GetMixerNode(), 0, mOutputNode, 0);
578			THROW_RESULT
579
580		mMixerNode = inContext->GetMixerNode();
581		mConnectedContext = inContext;
582
583		// initialize the graph on the 1st connection of a mixer to the graph
584		if (!mGraphInitialized) {
585			AUGraphInitialize(mAUGraph);
586			mGraphInitialized = true;
587		}
588		else
589		{
590			// if graph in uninitialized or not running , where should this go
591			result = AUGraphUpdate (mAUGraph, NULL);
592				THROW_RESULT
593		}
594
595		// the graph may not be running yet
596		if (!IsGraphStillRunning ())
597			AUGraphStart (mAUGraph);
598	}
599	catch (OSStatus		result) {
600		mConnectedContext = oldContext;
601		throw result;
602	}
603	catch (...) {
604		mConnectedContext = oldContext;
605		throw -1;
606	}
607	return;
608}
609
610// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
611void		OALDevice::DisconnectContext(OALContext* inContext)
612{
613#if LOG_VERBOSE || LOG_DEVICE_CHANGES
614	DebugMessageN1("OALDevice::DisconnectContext() - OALDevice = %ld", (long int) mSelfToken);
615#endif
616	if (inContext == mConnectedContext)
617		mConnectedContext = NULL;
618
619	AUGraphDisconnectNodeInput(mAUGraph, mOutputNode, 0);
620
621	// AUGraphUpdate will block here, until the node is removed
622	AUGraphUpdate(mAUGraph, NULL);
623}
624
625// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
626void		OALDevice::RemoveContext(OALContext* inContext)
627{
628#if LOG_VERBOSE || LOG_DEVICE_CHANGES
629	DebugMessageN1("OALDevice::RemoveContext() - OALDevice = %ld", (long int) mSelfToken);
630#endif
631	if(inContext == mConnectedContext)
632		mConnectedContext = NULL;
633
634	// now remove the remove the mixer node for the context
635	AUGraphRemoveNode(mAUGraph, inContext->GetMixerNode());
636	// AUGraphUpdate will block here, until the node is removed
637	AUGraphUpdate(mAUGraph, NULL);
638}
639
640// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
641AudioChannelLayoutTag OALDevice::GetLayoutTagForLayout(AudioChannelLayout *inLayout, UInt32 inNumChannels)
642{
643#if LOG_VERBOSE
644	DebugMessageN1("OALDevice::GetLayoutTagForLayout() - OALDevice = %ld", (long int) mSelfToken);
645#endif
646	if (inNumChannels == 5)
647		return kAudioChannelLayoutTag_AudioUnit_5_0;
648
649	// Quad not supported prior to 3d mixer ver. 2.0
650	else if ((inNumChannels == 4) && (Get3DMixerVersion() >= k3DMixerVersion_2_0))
651		return kAudioChannelLayoutTag_AudioUnit_4;
652
653	// now check for new multichannel formats in the 3d mixer v. 2.3 and higher
654	else if (inNumChannels == 6 && (Get3DMixerVersion() >= k3DMixerVersion_2_3))
655		return kAudioChannelLayoutTag_AudioUnit_6_0;
656
657	else if (inNumChannels == 7 && (Get3DMixerVersion() >= k3DMixerVersion_2_3))
658	{
659		//return kAudioChannelLayoutTag_AudioUnit_7_0;
660		for (UInt32 i=0; i< inLayout->mNumberChannelDescriptions; i++)
661		{
662			if((inLayout->mChannelDescriptions[i].mChannelLabel == kAudioChannelLabel_RearSurroundLeft) ||
663				(inLayout->mChannelDescriptions[i].mChannelLabel == kAudioChannelLabel_RearSurroundRight))
664			{
665				//if we have rear channels, we need kAudioChannelLayoutTag_AudioUnit_7_0
666				return kAudioChannelLayoutTag_AudioUnit_7_0;
667			}
668		}
669		//if we didn't find rear channels, default 7.0 front
670		return kAudioChannelLayoutTag_AudioUnit_7_0_Front;
671	}
672
673	else if (inNumChannels == 8 && (Get3DMixerVersion() >= k3DMixerVersion_2_3))
674		return kAudioChannelLayoutTag_AudioUnit_8;
675
676	return  kAudioChannelLayoutTag_Stereo; // default case
677}
678
679// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
680UInt32		OALDevice::GetChannelLayoutTag()
681{
682#if LOG_VERBOSE
683	DebugMessageN1("OALDevice::GetChannelLayoutTag() - OALDevice = %ld", (long int) mSelfToken);
684#endif
685	return mChannelLayoutTag;
686}
687