5 // Created by Heiko Panther on Tue Sep 10 2002.
6 // Copyright (c) 2002 __MyCompanyName__. All rights reserved.
10 #define MAXINT 0x7fffffff
12 #define DEBUG_ASYSTM 1
14 // this is a basic set of rules
15 #define gNumClassificationRules 4
17 const struct classificationRule gClassificationRules[gNumClassificationRules]=
19 { // The old AC3 format type
22 audiodevice_class_ac3,
25 { // The new AC3 format type
28 audiodevice_class_ac3,
32 kAudioFormatLinearPCM,
34 audiodevice_class_pcm2,
38 kAudioFormatLinearPCM,
40 audiodevice_class_pcm6,
45 MacOSXAudioSystem *gTheMacOSXAudioSystem; // Remove this global, access audio system froma aout some other way
47 @implementation MacOSXSoundOption
49 - (id)initWithName:(NSString*)_name deviceID:(AudioDeviceID)devID streamIndex:(UInt32)strInd formatID:(UInt32)formID chans:(UInt32)chans;
64 - (NSString*)name {return name;};
66 - (AudioDeviceID)deviceID {return deviceID;};
68 - (UInt32)streamIndex {return streamIndex;};
70 - (UInt32)mFormatID {return mFormatID;};
72 - (UInt32)mChannels {return mChannels;};
74 - (void)dealloc {[name release];};
79 @implementation MacOSXAudioSystem
81 OSStatus listenerProc (AudioDeviceID inDevice,
84 AudioDevicePropertyID inPropertyID,
87 intf_thread_t * p_intf = [NSApp getIntf];
88 msg_Dbg(p_intf, "********** Property Listener called! device %d, channel %d, isInput %d, propertyID %4.4s",
89 inDevice, inChannel, isInput, &inPropertyID);
93 OSStatus streamListenerProc (AudioStreamID inStream,
95 AudioDevicePropertyID inPropertyID,
98 intf_thread_t * p_intf = [NSApp getIntf];
99 msg_Dbg(p_intf, "********** Property Listener called! stream %d, channel %d, propertyID %4.4s",
100 inStream, inChannel, &inPropertyID);
104 - (id)initWithGUI:(VLCMain*)_main
107 if((me=[super init]))
109 gTheMacOSXAudioSystem=self;
112 intf_thread_t * p_intf = [NSApp getIntf];
115 // find audio devices
116 // find out how many audio devices there are, if any
117 OSStatus status = noErr;
120 AudioDeviceID *deviceList = NULL;
123 status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &theSize, &outWritable);
126 msg_Err(p_intf, "AudioHardwareGetPropertyInfo failed");
129 // calculate the number of device available
130 UInt32 devicesAvailable = theSize / sizeof(AudioDeviceID);
131 // Bail if there aren't any devices
132 if(devicesAvailable < 1)
134 msg_Err(p_intf, "no devices found");
136 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Have %i devices!", devicesAvailable);
138 // make space for the devices we are about to get
139 deviceList = (AudioDeviceID*)malloc(theSize);
140 // get an array of AudioDeviceIDs
141 status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &theSize, (void *) deviceList);
144 msg_Err(p_intf, "could not get Device list");
149 newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output" action:NULL keyEquivalent:@""];
150 newMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output"];
151 [newItem setSubmenu:newMenu];
152 [[NSApp mainMenu] addItem:newItem];
155 // check which devices can do what class of audio
156 // struct mosx_AudioDeviceData deviceData;
157 for(i=0; i<devicesAvailable; i++)
158 [self CheckDevice:deviceList[i] isInput:false]; // only check the output part
166 - (AudioStreamID) getStreamIDForIndex:(UInt32)streamIndex device:(AudioDeviceID)deviceID
168 // Does not currently use the stream index, but just returns the stream ID of the first stream.
170 Boolean isInput=false, outWritable;
172 OSStatus err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyStreams, &theSize, &outWritable);
173 AudioStreamID *streamList = (AudioStreamID*)malloc(theSize);
174 err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreams, &theSize, streamList);
175 AudioStreamID streamID = streamList[streamIndex - 1];
180 - (void)CheckDevice:(AudioDeviceID)deviceID isInput:(bool)isInput
185 AudioBufferList *bufferList = 0;
187 intf_thread_t * p_intf = [NSApp getIntf];
188 char deviceName[32]; // Make this a CFString!
190 // Add property listener
191 err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreams, listenerProc, 0);
192 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
193 err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreamConfiguration, listenerProc, 0);
194 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
195 err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreamFormat, listenerProc, 0);
196 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
198 // Get the device name
199 err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &theSize, &outWritable);
200 theSize=sizeof(deviceName);
201 err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &theSize, deviceName);
203 // Get the stream configuration
204 err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration, &theSize, &outWritable);
205 bufferList = (AudioBufferList*)malloc(theSize);
206 err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration, &theSize, bufferList);
208 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "\nFound a %s, examing its %s, it has %i streams.", deviceName, (isInput?"Input":"Output"), bufferList->mNumberBuffers);
210 // find details of each stream
211 for (i=0; i < bufferList->mNumberBuffers; i++)
213 short streamIndex=i+1;
215 AudioStreamBasicDescription *formatsAvailable;
217 AudioStreamID streamID=[self getStreamIDForIndex:streamIndex device:deviceID];
219 // Add property listener
220 err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreams, streamListenerProc, 0);
221 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
222 err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreamConfiguration, streamListenerProc, 0);
223 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
224 err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreamFormat, streamListenerProc, 0);
225 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
226 err=AudioStreamAddPropertyListener(streamID, 0, kAudioStreamPropertyPhysicalFormat, streamListenerProc, 0);
227 if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
229 // Get the # of actual formats in the current stream
230 err = AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, &outWritable);
231 nActFormats = theSize / sizeof(AudioStreamBasicDescription);
232 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "stream index %i, streamID %i, nActFormats %d", streamIndex, streamID, nActFormats);
234 // Get the format specifications
235 formatsAvailable=(AudioStreamBasicDescription*) malloc(theSize);
236 err = AudioStreamGetProperty(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, formatsAvailable);
237 if(err) msg_Err(p_intf, "AudioDeviceGetProperty err %d", err);
239 // now classify the device and add a menu entry for each device class it matches
240 for(j=0; j<gNumClassificationRules; j++)
242 UInt32 numChans=MAXINT, format=0;
243 for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
245 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Finding formats: %4.4s - %d chans, %d Hz, %d bits/sample, %d bytes/frame",
246 &formatsAvailable[i].mFormatID, formatsAvailable[i].mChannelsPerFrame,
247 (UInt32)formatsAvailable[i].mSampleRate,
248 formatsAvailable[i].mBitsPerChannel, formatsAvailable[i].mBytesPerFrame);
250 if(formatsAvailable[i].mFormatID != gClassificationRules[j].mFormatID && gClassificationRules[j].mFormatID!=0) continue;
251 if(formatsAvailable[i].mChannelsPerFrame < gClassificationRules[j].mChannelsPerFrame && gClassificationRules[j].mChannelsPerFrame!=0) continue;
252 // we want to choose the format with the smallest allowable channel number for this class
253 if(formatsAvailable[i].mChannelsPerFrame < numChans)
255 numChans=formatsAvailable[i].mChannelsPerFrame;
259 if(numChans!=MAXINT) // we found a good setting
261 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "classified into %d", gClassificationRules[j].characteristic);
262 // make a sound option object
264 snprintf(menuentry, 48, "%.32s: %.16s", deviceName, gClassificationRules[j].qualifierString);
265 MacOSXSoundOption *device=[[MacOSXSoundOption alloc] initWithName:[NSString stringWithCString:menuentry] deviceID:deviceID streamIndex:streamIndex formatID:formatsAvailable[format].mFormatID chans:formatsAvailable[format].mChannelsPerFrame];
266 [self registerSoundOption:device];
269 free(formatsAvailable);
275 - (void)registerSoundOption:(MacOSXSoundOption*)option {
277 newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[option name] action:NULL keyEquivalent:@""];
278 [newItem setImage:[NSImage imageNamed:@"eomt_browsedata"]];
279 [newItem setTarget:self];
280 [newItem setAction:@selector(selectAction:)];
281 [newItem setRepresentedObject:option];
282 [newMenu addItem:newItem];
283 if(selectedOutput==0) [self selectAction:newItem];
287 - (void)selectAction:(id)sender {
288 [selectedOutput setState:NSOffState];
289 selectedOutput=sender;
290 [sender setState:NSOnState];
293 static void printStreamDescription(char *description, AudioStreamBasicDescription *format)
295 intf_thread_t * p_intf = [NSApp getIntf];
296 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "%s: mSampleRate %ld, mFormatID %4.4s, mFormatFlags %ld, mBytesPerPacket %ld, mFramesPerPacket %ld, mBytesPerFrame %ld, mChannelsPerFrame %ld, mBitsPerChannel %ld",
298 (UInt32)format->mSampleRate, &format->mFormatID,
299 format->mFormatFlags, format->mBytesPerPacket,
300 format->mFramesPerPacket, format->mBytesPerFrame,
301 format->mChannelsPerFrame, format->mBitsPerChannel);
305 - (AudioDeviceID)getSelectedDeviceSetToRate:(int)preferredSampleRate{
306 // I know the selected device, stream, and the required format ID. Now find a format
307 // that comes closest to the preferred rate
308 // For sample size, it is assumed that 16 bits will always be enough.
309 // Note that the caller is not guranteed to get the rate she preferred.
310 AudioStreamBasicDescription *formatsAvailable;
311 MacOSXSoundOption *selectedOption=[selectedOutput representedObject];
312 bool foundFormat=false;
317 intf_thread_t * p_intf = [NSApp getIntf];
318 AudioDeviceID deviceID=[selectedOption deviceID];
320 // get the streamID (it might have changed)
321 AudioStreamID streamID=[self getStreamIDForIndex:[selectedOption streamIndex] device:deviceID];
323 // Find the actual formats
324 err = AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, &outWritable);
325 formatsAvailable=(AudioStreamBasicDescription*) malloc(theSize);
326 err = AudioStreamGetProperty(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, formatsAvailable);
329 msg_Err(p_intf, "Error %4.4s getting the stream formats", &err);
333 UInt32 formtmp=[selectedOption mFormatID];
334 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "looking for: %4.4s - %d chans, %d Hz", &formtmp,
335 [selectedOption mChannels], preferredSampleRate);
337 // Check if there's a "best match" which has our required rate
338 for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
340 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "actual: %4.4s - %d chans, %d Hz, %d bits/sample, %d bytes/frame",
341 &formatsAvailable[i].mFormatID, formatsAvailable[i].mChannelsPerFrame,
342 (int)formatsAvailable[i].mSampleRate,
343 formatsAvailable[i].mBitsPerChannel, formatsAvailable[i].mBytesPerFrame);
345 if(formatsAvailable[i].mChannelsPerFrame<0 || formatsAvailable[i].mChannelsPerFrame>100) {
346 msg_Err(p_intf, "bogus format! index %i", i);
350 if( formatsAvailable[i].mFormatID == [selectedOption mFormatID]
351 && formatsAvailable[i].mChannelsPerFrame == [selectedOption mChannels]
352 && (int)formatsAvailable[i].mSampleRate == preferredSampleRate)
354 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Found the perfect format!!");
360 int rate=MAXINT, format=0;
363 for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
365 // We don't have one... check if there's one with a higher sample rate.
366 // Upsampling should be better than downsampling.
367 // Select the smallest of the higher sample rates, to save resources.
368 int actrate=(int)formatsAvailable[i].mSampleRate;
369 if( formatsAvailable[i].mFormatID == [selectedOption mFormatID]
370 && formatsAvailable[i].mChannelsPerFrame == [selectedOption mChannels]
371 && actrate > preferredSampleRate)
380 if(rate!=MAXINT) // This means we have found a rate!! Yes!
382 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Only got a format with higher sample rate");
391 for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
393 // Our last chance: select the highest lower sample rate.
394 int actrate=(int)formatsAvailable[i].mSampleRate;
395 if( actrate >= preferredSampleRate) // We must have done something wrong
397 if(DEBUG_ASYSTM) msg_Err(p_intf, "Found a rate that should have been selected previously.");
398 free(formatsAvailable);
409 if(rate!=0) // This means we have found a rate!! Yes!
411 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Only got a format with lower sample rate");
415 else // We must have done something wrong
417 msg_Err(p_intf, "Found no rate which is equal, higher or lower to requested rate. That means our device either: a) didn't exist in the first place or b) has been removed since.");
418 free(formatsAvailable);
422 AudioStreamBasicDescription desc=formatsAvailable[i];
423 free(formatsAvailable);
425 // Set the device stream format
428 err = AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormat, &theSize, &isWriteable);
429 if(err) msg_Err(p_intf, "GetPropertyInfo (stream format) error %4.4s - theSize %d", &err, theSize);
430 if(DEBUG_ASYSTM) msg_Dbg(p_intf, "size %d, writable %d", theSize, isWriteable);
432 if(DEBUG_ASYSTM) printStreamDescription("want to set", &desc);
434 err = AudioStreamSetProperty(streamID, 0, 0, kAudioStreamPropertyPhysicalFormat, theSize, &desc);
435 if(err) msg_Err(p_intf, "SetProperty (stream format) error %4.4s - theSize %d", &err, theSize);
437 // Because of the format change, the streamID has changed!
438 // That's why we return the deviceID.