* Mac OS X audio device discovery and selection, patch courtesy of
authorChristophe Massiot <massiot@videolan.org>
Wed, 2 Oct 2002 22:56:53 +0000 (22:56 +0000)
committerChristophe Massiot <massiot@videolan.org>
Wed, 2 Oct 2002 22:56:53 +0000 (22:56 +0000)
Heiko Panther <heiko.panther@web.de>.

AUTHORS
extras/MacOSX/vlc.pbproj/project.pbxproj
modules/gui/macosx/Modules.am
modules/gui/macosx/adev_discovery.h [new file with mode: 0755]
modules/gui/macosx/aout.m
modules/gui/macosx/asystm.h [new file with mode: 0755]
modules/gui/macosx/asystm.m [new file with mode: 0755]
modules/gui/macosx/intf.h
modules/gui/macosx/intf.m

diff --git a/AUTHORS b/AUTHORS
index 9e071a4..04b8f6c 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -346,6 +346,11 @@ C: fgp
 D: MacOS X port
 S: Austria
 
+N: Heiko Panther
+E: heiko.panther@web.de
+D: Mac OS X audio device selection framework
+S: Germany
+
 N: Olivier Pomel
 E: pomel@via.ecp.fr
 C: pomel
index bdfab1c..fcf7da2 100644 (file)
                        productName = vlc;
                        productReference = 014CEA410018CDE011CA2923;
                        productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
-<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">
-<plist version=\"0.9\">
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
 <dict>
        <key>CFBundleDevelopmentRegion</key>
        <string>English</string>
                        files = (
                        );
                        isa = PBXHeadersBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
                };
                089C1675FE841209C02AAC07 = {
                        buildActionMask = 2147483647;
                                F69B0CA802E24F6401A80112,
                        );
                        isa = PBXResourcesBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
                };
                089C1676FE841209C02AAC07 = {
                        buildActionMask = 2147483647;
                        files = (
                        );
                        isa = PBXSourcesBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
                };
                089C1677FE841209C02AAC07 = {
                        buildActionMask = 2147483647;
                                1058C7AFFEA557BF11CA2CBB,
                        );
                        isa = PBXFrameworksBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
                };
                089C1679FE841209C02AAC07 = {
                        buildActionMask = 2147483647;
                        files = (
                        );
                        isa = PBXRezBuildPhase;
+                       runOnlyForDeploymentPostprocessing = 0;
                };
                089C167CFE841241C02AAC07 = {
                        children = (
index 4814365..37e442e 100644 (file)
@@ -5,11 +5,14 @@ SOURCES_macosx = \
        modules/gui/macosx/intf.m \
        modules/gui/macosx/open.m \
        modules/gui/macosx/playlist.m \
-       modules/gui/macosx/controls.m
+       modules/gui/macosx/controls.m \
+        modules/gui/macosx/asystm.m
 
 noinst_HEADERS += \
        modules/gui/macosx/intf.h \
        modules/gui/macosx/open.h \
        modules/gui/macosx/playlist.h \
-       modules/gui/macosx/vout.h
+       modules/gui/macosx/vout.h \
+       modules/gui/macosx/adev_discovery.h \
+       modules/gui/macosx/asystm.h
 
diff --git a/modules/gui/macosx/adev_discovery.h b/modules/gui/macosx/adev_discovery.h
new file mode 100755 (executable)
index 0000000..5b25c3a
--- /dev/null
@@ -0,0 +1,26 @@
+//
+//  main.h
+//  FindHW
+//
+//  Created by Heiko Panther on Sun Sep 08 2002.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreAudio/CoreAudio.h>
+
+enum audiodeviceClasses
+{
+    audiodevice_class_ac3      =1<<0,  // compressed AC3
+    audiodevice_class_pcm2     =1<<1,  // stereo PCM (uncompressed)
+    audiodevice_class_pcm6     =1<<2   // 6-channel PCM (uncompressed)
+};
+
+// specifies a rule for finding if a Device belongs to a class from above.
+// if value==0, the value is ignored when matching. All other values must match.
+struct classificationRule
+{
+    UInt32 mFormatID;
+    UInt32 mChannelsPerFrame;
+    enum audiodeviceClasses characteristic;
+    char qualifierString[16];
+};
index 5c4a8d0..93bd0e5 100644 (file)
@@ -2,7 +2,7 @@
  * aout.m: CoreAudio output plugin
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: aout.m,v 1.11 2002/09/30 21:32:33 massiot Exp $
+ * $Id: aout.m,v 1.12 2002/10/02 22:56:53 massiot Exp $
  *
  * Authors: Colin Delacroix <colin@zoy.org>
  *          Jon Lech Johansen <jon-vl@nanocrew.net>
 #include <vlc/vlc.h>
 #include <vlc/aout.h>
 #include "aout_internal.h"
+#include "asystm.h"
 
 #include <Carbon/Carbon.h>
 #include <CoreAudio/AudioHardware.h>
 #include <CoreAudio/HostTime.h>
 #include <AudioToolbox/AudioConverter.h>
 
+#define A52_FRAME_NB 1536
+
 /*****************************************************************************
  * aout_sys_t: private audio output method descriptor
  *****************************************************************************
@@ -70,13 +73,16 @@ static OSStatus IOCallback      ( AudioDeviceID inDevice,
 /*****************************************************************************
  * Open: open a CoreAudio HAL device
  *****************************************************************************/
+extern MacOSXAudioSystem *gTheMacOSXAudioSystem; // Remove this global, access audio system froma aout some other way
+
 int E_(OpenAudio)( vlc_object_t * p_this )
 {
     OSStatus err;
     UInt32 i_param_size;
     aout_instance_t * p_aout = (aout_instance_t *)p_this;
     struct aout_sys_t * p_sys;
-
+    msg_Dbg(p_aout, "************************* ENTER OpenAudio ****************************");
+    
     /* Allocate instance */
     p_sys = p_aout->output.p_sys = malloc( sizeof( struct aout_sys_t ) );
     memset( p_sys, 0, sizeof( struct aout_sys_t ) );
@@ -87,68 +93,89 @@ int E_(OpenAudio)( vlc_object_t * p_this )
     }
 
     /* Get the default output device */
-    /* FIXME : be more clever in choosing from several devices */
-    i_param_size = sizeof( p_sys->device );
-    err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice,
-                                    &i_param_size, 
-                                    (void *)&p_sys->device );
-    if( err != noErr ) 
+    // We now ask the GUI for the selected device
+    p_sys->device=[gTheMacOSXAudioSystem getSelectedDeviceSetToRate:p_aout->output.output.i_rate];
+    if(p_sys->device==0)
     {
-        msg_Err( p_aout, "failed to get the device: %d", err );
+        msg_Err( p_aout, "couldn't get output device");
         return( -1 );
     }
+    msg_Dbg(p_aout, "device returned: %ld", p_sys->device);
 
     p_aout->output.pf_play = Play;
     aout_VolumeSoftInit( p_aout );
 
     /* Get a description of the data format used by the device */
-    i_param_size = sizeof( p_sys->stream_format ); 
-    err = AudioDeviceGetProperty( p_sys->device, 0, false, 
-                                  kAudioDevicePropertyStreamFormat, 
-                                  &i_param_size,
-                                  &p_sys->stream_format );
+    i_param_size = sizeof(AudioStreamBasicDescription); 
+    err = AudioDeviceGetProperty(p_sys->device, 0, false, kAudioDevicePropertyStreamFormat, &i_param_size, &p_sys->stream_format );
     if( err != noErr )
     {
-        msg_Err( p_aout, "failed to get stream format: %d", err );
+        msg_Err( p_aout, "failed to get stream format: %4.4s", &err );
         return -1 ;
     }
 
-    if( p_sys->stream_format.mFormatID != kAudioFormatLinearPCM )
+    msg_Dbg( p_aout, "mSampleRate %ld, mFormatID %4.4s, mFormatFlags %ld, mBytesPerPacket %ld, mFramesPerPacket %ld, mBytesPerFrame %ld, mChannelsPerFrame %ld, mBitsPerChannel %ld",
+           (UInt32)p_sys->stream_format.mSampleRate, &p_sys->stream_format.mFormatID,
+           p_sys->stream_format.mFormatFlags, p_sys->stream_format.mBytesPerPacket,
+           p_sys->stream_format.mFramesPerPacket, p_sys->stream_format.mBytesPerFrame,
+           p_sys->stream_format.mChannelsPerFrame, p_sys->stream_format.mBitsPerChannel );
+
+    msg_Dbg( p_aout, "vlc format %4.4s, mac output format '%4.4s'",
+             (char *)&p_aout->output.output.i_format, &p_sys->stream_format.mFormatID );
+
+    switch(p_sys->stream_format.mFormatID)
     {
-        msg_Err( p_aout, "kAudioFormatLinearPCM required" );
-        return -1 ;
+    case 0:
+    case kAudioFormatLinearPCM:
+        p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
+        if ( p_sys->stream_format.mChannelsPerFrame < 6 )
+            p_aout->output.output.i_channels = AOUT_CHAN_STEREO;
+        else
+            p_aout->output.output.i_channels = AOUT_CHAN_3F2R | AOUT_CHAN_LFE;
+        break;
+
+    case kAudioFormat60958AC3:
+    case 'IAC3':
+        p_aout->output.output.i_format = VLC_FOURCC('s','p','d','i');
+        //not necessary, use the input's format by default --Meuuh
+        //p_aout->output.output.i_channels = AOUT_CHAN_DOLBY | AOUT_CHAN_LFE;
+        p_aout->output.output.i_bytes_per_frame = AOUT_SPDIF_SIZE; //p_sys->stream_format.mBytesPerFrame;
+        p_aout->output.output.i_frame_length = A52_FRAME_NB; //p_sys->stream_format.mFramesPerPacket;
+        break;
+
+    default:
+        msg_Err( p_aout, "Unknown hardware format '%4.4s'. Go ask Heiko.", &p_sys->stream_format.mFormatID );
+        return -1;
     }
 
-    /* We only deal with floats. FIXME : this is where we should do S/PDIF. */
-    p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
-
     /* Set sample rate and channels per frame */
     p_aout->output.output.i_rate = p_sys->stream_format.mSampleRate;
-    /* FIXME : this is where we should ask for downmixing. */
-    p_aout->output.output.i_channels = 2; //p_sys->stream_format.mChannelsPerFrame;
 
     /* Get the buffer size that the device uses for IO */
     i_param_size = sizeof( p_sys->i_buffer_size );
-#if 0
-    err = AudioDeviceGetProperty( p_sys->device, 0, false, 
+#if 1  // i have a feeling we should use the buffer size imposed by the AC3 device (usually about 6144)
+    err = AudioDeviceGetProperty( p_sys->device, 1, false, 
                                   kAudioDevicePropertyBufferSize, 
                                   &i_param_size, &p_sys->i_buffer_size );
-msg_Dbg( p_aout, "toto : %d", p_sys->i_buffer_size );
+    if(err) {
+       msg_Err(p_aout, "failed to get buffer size - err %4.4s, device %ld", &err, p_sys->device);
+       return -1;
+    }
+    else msg_Dbg( p_aout, "native buffer Size: %d", p_sys->i_buffer_size );
 #else
-    p_sys->i_buffer_size = sizeof(float) * p_aout->output.output.i_channels
-                            * 4096;
-    err = AudioDeviceSetProperty( p_sys->device, 0, 0, false,
+    p_sys->i_buffer_size = p_aout->output.output.i_bytes_per_frame;
+    err = AudioDeviceSetProperty( p_sys->device, 0, 1, false,
                                   kAudioDevicePropertyBufferSize,
                                   i_param_size, &p_sys->i_buffer_size );
-#endif
     if( err != noErr )
     {
-        msg_Err( p_aout, "failed to set device buffer size: %d", err );
+        msg_Err( p_aout, "failed to set device buffer size: %4.4s", err );
         return( -1 );
     }
+    else msg_Dbg(p_aout, "bufferSize set to %d", p_sys->i_buffer_size);
+#endif
 
-    p_aout->output.i_nb_samples = p_sys->i_buffer_size / sizeof(float)
-                                   / p_aout->output.output.i_channels;
+    p_aout->output.i_nb_samples = p_sys->i_buffer_size / p_sys->stream_format.mBytesPerFrame;
 
     /* Add callback */
     err = AudioDeviceAddIOProc( p_sys->device,
@@ -220,15 +247,19 @@ static OSStatus IOCallback( AudioDeviceID inDevice,
     current_date = p_sys->clock_diff
                  + AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000;
 
-    p_buffer = aout_OutputNextBuffer( p_aout, current_date, VLC_FALSE );
+//    msg_Dbg(p_aout, "Now fetching audio data");
+    p_buffer = aout_OutputNextBuffer( p_aout, current_date, (p_aout->output.output.i_format == VLC_FOURCC('s','p','d','i')) );
 
     /* move data into output data buffer */
     if ( p_buffer != NULL )
     {
-        BlockMoveData( p_buffer->p_buffer,
+       BlockMoveData( p_buffer->p_buffer,
                        outOutputData->mBuffers[ 0 ].mData, 
                        p_sys->i_buffer_size );
-        aout_BufferFree( p_buffer );
+
+//     msg_Dbg(p_aout, "This buffer has %d bytes, i take %d", p_buffer->i_nb_bytes, p_sys->i_buffer_size);
+    
+       aout_BufferFree( p_buffer );
     }
     else
     {
diff --git a/modules/gui/macosx/asystm.h b/modules/gui/macosx/asystm.h
new file mode 100755 (executable)
index 0000000..9aec4ff
--- /dev/null
@@ -0,0 +1,58 @@
+//
+//  asystm.h
+//  
+//
+//  Created by Heiko Panther on Tue Sep 10 2002.
+//  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreAudio/CoreAudio.h>
+#import "adev_discovery.h"
+#import "intf.h"
+
+/*****************************************************************************
+* MacOSXSoundOption
+* Each audio device can give several sound options: there might be several
+* streams on one device, each can have different formats which might qualify
+* as an option.
+* We record format and channels, since these attributes are requirements
+* from the user and the aout should deliver what the user wants. This
+* selection is basically done when the user chooses the output option.
+* We do not record sample rate and bit depth, since these attributes are
+* tied to the media source, and the device format that matches these media
+* formats best should be selected. This selection is done when the aout
+* module is created with a certain stream, and asks the asystm for a device.
+*****************************************************************************/
+@interface MacOSXSoundOption:NSObject
+{
+    NSString *name;
+    AudioDeviceID deviceID;
+    UInt32 streamIndex;
+    UInt32 mFormatID;
+    UInt32 mChannels;
+}
+- (id)initWithName:(NSString*)_name deviceID:(AudioDeviceID)devID streamIndex:(UInt32)strInd formatID:(UInt32)formID chans:(UInt32)chans;
+- (AudioDeviceID)deviceID;
+- (UInt32)streamIndex;
+- (UInt32)mFormatID;
+- (UInt32)mChannels;
+- (void)dealloc;
+- (NSString*)name;
+@end
+
+
+@interface MacOSXAudioSystem : NSObject {
+    VLCMain *main;
+    /* selected output device */
+    NSMenuItem *selectedOutput;
+    NSMenu *newMenu;
+}
+- (id)initWithGUI:(VLCMain*)main;
+- (AudioStreamID) getStreamIDForIndex:(UInt32)streamIndex device:(AudioDeviceID)deviceID;
+- (void)CheckDevice:(AudioDeviceID)deviceID isInput:(bool)isInput;
+- (void)registerSoundOption:(MacOSXSoundOption*)option;
+- (void)selectAction:(id)sender;
+- (AudioStreamID)getSelectedDeviceSetToRate:(int)preferredSampleRate;
+- (void)dealloc;
+@end
diff --git a/modules/gui/macosx/asystm.m b/modules/gui/macosx/asystm.m
new file mode 100755 (executable)
index 0000000..a8b7370
--- /dev/null
@@ -0,0 +1,449 @@
+//
+//  asystm.m
+//  
+//
+//  Created by Heiko Panther on Tue Sep 10 2002.
+//  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
+//
+
+#import "asystm.h"
+#define MAXINT 0x7fffffff
+
+#define DEBUG_ASYSTM 1
+
+// this is a basic set of rules
+#define gNumClassificationRules 4
+
+const struct classificationRule gClassificationRules[gNumClassificationRules]=
+{
+{      // The old AC3 format type
+    'IAC3',
+    0,
+    audiodevice_class_ac3,
+    "Digital AC3"
+},
+{      // The new AC3 format type
+    kAudioFormat60958AC3,
+    0,
+    audiodevice_class_ac3,
+    "Digital AC3"
+},
+{
+    kAudioFormatLinearPCM,
+    2,
+    audiodevice_class_pcm2,
+    "Stereo PCM"
+},
+{
+    kAudioFormatLinearPCM,
+    6,
+    audiodevice_class_pcm6,
+    "6 Channel PCM"
+}
+};
+
+MacOSXAudioSystem *gTheMacOSXAudioSystem; // Remove this global, access audio system froma aout some other way
+
+@implementation MacOSXSoundOption
+
+- (id)initWithName:(NSString*)_name deviceID:(AudioDeviceID)devID streamIndex:(UInt32)strInd formatID:(UInt32)formID chans:(UInt32)chans;
+{
+    id me=0;
+    if((me=[super init]))
+    {
+       name = _name;
+       [name retain];
+       deviceID=devID;
+       streamIndex=strInd;
+       mFormatID=formID;
+       mChannels=chans;
+    }
+    return(me);
+}
+
+- (NSString*)name {return name;};
+
+- (AudioDeviceID)deviceID {return deviceID;};
+
+- (UInt32)streamIndex {return streamIndex;};
+
+- (UInt32)mFormatID {return mFormatID;};
+
+- (UInt32)mChannels {return mChannels;};
+
+- (void)dealloc {[name release];};
+
+@end
+
+
+@implementation MacOSXAudioSystem
+
+OSStatus listenerProc (AudioDeviceID           inDevice,
+                      UInt32                   inChannel,
+                      Boolean                  isInput,
+                      AudioDevicePropertyID    inPropertyID,
+                      void*                    inClientData)
+{
+    intf_thread_t * p_intf = [NSApp getIntf];
+    msg_Dbg(p_intf, "**********        Property Listener called! device %d, channel %d, isInput %d, propertyID %4.4s",
+           inDevice, inChannel, isInput, &inPropertyID);
+    return 0;
+};
+
+OSStatus streamListenerProc (AudioStreamID             inStream,
+                      UInt32                   inChannel,
+                      AudioDevicePropertyID    inPropertyID,
+                      void*                    inClientData)
+{
+    intf_thread_t * p_intf = [NSApp getIntf];
+    msg_Dbg(p_intf, "**********        Property Listener called! stream %d, channel %d, propertyID %4.4s",
+           inStream, inChannel, &inPropertyID);
+    return 0;
+};
+
+- (id)initWithGUI:(VLCMain*)_main
+{
+    id me=0;
+    if((me=[super init]))
+    {
+       gTheMacOSXAudioSystem=self;
+       main=_main;
+       [main retain];
+       intf_thread_t * p_intf = [NSApp getIntf];
+       selectedOutput=0;
+
+       // find audio devices
+       // find out how many audio devices there are, if any
+       OSStatus        status = noErr;
+       UInt32          theSize;
+       Boolean         outWritable;
+       AudioDeviceID   *deviceList = NULL;
+       UInt32 i;
+
+       status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &theSize, &outWritable);
+       if(status != noErr)
+       {
+           msg_Err(p_intf, "AudioHardwareGetPropertyInfo failed");
+       };
+
+       // calculate the number of device available
+       UInt32 devicesAvailable = theSize / sizeof(AudioDeviceID);
+       // Bail if there aren't any devices
+       if(devicesAvailable < 1)
+       {
+           msg_Err(p_intf, "no devices found");
+       }
+       if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Have %i devices!", devicesAvailable);
+
+       // make space for the devices we are about to get
+       deviceList = (AudioDeviceID*)malloc(theSize);
+       // get an array of AudioDeviceIDs
+       status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &theSize, (void *) deviceList);
+       if(status != noErr)
+       {
+           msg_Err(p_intf, "could not get Device list");
+       };
+
+       // Build a menu
+       NSMenuItem *newItem;
+       newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output" action:NULL keyEquivalent:@""];
+       newMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output"];
+       [newItem setSubmenu:newMenu];
+       [[NSApp mainMenu] addItem:newItem];
+       [newItem release];
+
+       // check which devices can do what class of audio
+       //    struct mosx_AudioDeviceData deviceData;
+       for(i=0; i<devicesAvailable; i++)
+           [self CheckDevice:deviceList[i] isInput:false];     // only check the output part
+
+       [newMenu release];
+       free(deviceList);
+    };
+    return me;
+};
+
+- (AudioStreamID) getStreamIDForIndex:(UInt32)streamIndex device:(AudioDeviceID)deviceID
+{
+    // Does not currently use the stream index, but just returns the stream ID of the first stream.
+    // Get the stream ID
+    Boolean isInput=false, outWritable;
+    UInt32 theSize;
+    OSStatus err =  AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyStreams,  &theSize, &outWritable);
+    AudioStreamID *streamList = (AudioStreamID*)malloc(theSize);
+    err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreams, &theSize, streamList);
+    AudioStreamID streamID = streamList[streamIndex - 1];
+    free(streamList);
+    return streamID;
+}
+
+- (void)CheckDevice:(AudioDeviceID)deviceID isInput:(bool)isInput
+{
+    OSStatus err;
+    UInt32             theSize;
+    Boolean            outWritable;
+    AudioBufferList    *bufferList = 0;
+    UInt32 i, j;
+    intf_thread_t * p_intf = [NSApp getIntf];
+    char deviceName[32];       // Make this a CFString!
+
+    // Add property listener
+    err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreams, listenerProc, 0);
+    if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+    err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreamConfiguration, listenerProc, 0);
+    if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+    err=AudioDeviceAddPropertyListener(deviceID, 1, isInput, kAudioDevicePropertyStreamFormat, listenerProc, 0);
+    if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+
+    // Get the device name
+    err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyDeviceName,  &theSize, &outWritable);
+    theSize=sizeof(deviceName);
+    err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyDeviceName, &theSize, deviceName);
+
+    // Get the stream configuration
+    err =  AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration,  &theSize, &outWritable);
+    bufferList = (AudioBufferList*)malloc(theSize);
+    err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration, &theSize, bufferList);
+
+    if(DEBUG_ASYSTM) msg_Dbg(p_intf, "\nFound a %s, examing its %s, it has %i streams.", deviceName, (isInput?"Input":"Output"), bufferList->mNumberBuffers);
+
+    // find details of each stream
+    for (i=0; i < bufferList->mNumberBuffers; i++)
+    {
+       short streamIndex=i+1;
+       UInt32 nActFormats;
+       AudioStreamBasicDescription *formatsAvailable;
+
+       AudioStreamID streamID=[self getStreamIDForIndex:streamIndex device:deviceID];
+
+       // Add property listener
+       err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreams, streamListenerProc, 0);
+       if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+       err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreamConfiguration, streamListenerProc, 0);
+       if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+       err=AudioStreamAddPropertyListener(streamID, 0, kAudioDevicePropertyStreamFormat, streamListenerProc, 0);
+       if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+       err=AudioStreamAddPropertyListener(streamID, 0, kAudioStreamPropertyPhysicalFormat, streamListenerProc, 0);
+       if(err) msg_Err(p_intf, "Add Property Listener failed, err=%4.4s", &err);
+
+       // Get the # of actual formats in the current stream
+       err =  AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormats,  &theSize, &outWritable);
+       nActFormats = theSize / sizeof(AudioStreamBasicDescription);
+       if(DEBUG_ASYSTM) msg_Dbg(p_intf, "stream index %i, streamID %i, nActFormats %d", streamIndex, streamID, nActFormats);
+
+       // Get the format specifications
+       formatsAvailable=(AudioStreamBasicDescription*) malloc(theSize);
+       err = AudioStreamGetProperty(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, formatsAvailable);
+       if(err) msg_Err(p_intf, "AudioDeviceGetProperty err %d", err);
+       
+       // now classify the device and add a menu entry for each device class it matches
+       for(j=0; j<gNumClassificationRules; j++)
+       {
+           UInt32 numChans=MAXINT, format=0;
+           for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
+           {
+               if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Finding formats: %4.4s - %d chans, %d Hz, %d bits/sample, %d bytes/frame",
+         &formatsAvailable[i].mFormatID, formatsAvailable[i].mChannelsPerFrame,
+         (UInt32)formatsAvailable[i].mSampleRate,
+         formatsAvailable[i].mBitsPerChannel, formatsAvailable[i].mBytesPerFrame);
+
+               if(formatsAvailable[i].mFormatID != gClassificationRules[j].mFormatID && gClassificationRules[j].mFormatID!=0) continue;
+               if(formatsAvailable[i].mChannelsPerFrame < gClassificationRules[j].mChannelsPerFrame && gClassificationRules[j].mChannelsPerFrame!=0) continue;
+               // we want to choose the format with the smallest allowable channel number for this class
+               if(formatsAvailable[i].mChannelsPerFrame < numChans)
+               {
+                   numChans=formatsAvailable[i].mChannelsPerFrame;
+                   format=i;
+               };
+           };
+           if(numChans!=MAXINT) // we found a good setting
+           {
+               if(DEBUG_ASYSTM) msg_Dbg(p_intf, "classified into %d", gClassificationRules[j].characteristic);
+               // make a sound option object
+               char menuentry[48];
+               snprintf(menuentry, 48, "%.32s: %.16s", deviceName, gClassificationRules[j].qualifierString);
+               MacOSXSoundOption *device=[[MacOSXSoundOption alloc] initWithName:[NSString stringWithCString:menuentry] deviceID:deviceID streamIndex:streamIndex formatID:formatsAvailable[format].mFormatID chans:formatsAvailable[format].mChannelsPerFrame];
+               [self registerSoundOption:device];
+           };
+       };
+       free(formatsAvailable);
+    }
+
+    free(bufferList);
+};
+
+- (void)registerSoundOption:(MacOSXSoundOption*)option {
+    NSMenuItem *newItem;
+    newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:[option name] action:NULL keyEquivalent:@""];
+    [newItem setImage:[NSImage imageNamed:@"eomt_browsedata"]];
+    [newItem setTarget:self];
+    [newItem setAction:@selector(selectAction:)];
+    [newItem setRepresentedObject:option];
+    [newMenu addItem:newItem];
+    if(selectedOutput==0) [self selectAction:newItem];
+    [newItem release];
+};
+    
+- (void)selectAction:(id)sender {
+    [selectedOutput setState:NSOffState];
+    selectedOutput=sender;
+    [sender setState:NSOnState];
+};
+
+static void printStreamDescription(char *description, AudioStreamBasicDescription *format)
+{
+    intf_thread_t * p_intf = [NSApp getIntf];
+    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",
+           description,
+           (UInt32)format->mSampleRate, &format->mFormatID,
+           format->mFormatFlags, format->mBytesPerPacket,
+           format->mFramesPerPacket, format->mBytesPerFrame,
+           format->mChannelsPerFrame, format->mBitsPerChannel);
+};
+
+
+- (AudioDeviceID)getSelectedDeviceSetToRate:(int)preferredSampleRate{
+    // I know the selected device, stream, and the required format ID. Now find a format
+    // that comes closest to the preferred rate
+    // For sample size, it is assumed that 16 bits will always be enough.
+    // Note that the caller is not guranteed to get the rate she preferred.
+    AudioStreamBasicDescription *formatsAvailable;
+    MacOSXSoundOption *selectedOption=[selectedOutput representedObject];
+    bool foundFormat=false;
+    UInt32 theSize;
+    Boolean outWritable;
+    OSStatus err;
+    UInt32 i;
+    intf_thread_t * p_intf = [NSApp getIntf];
+    AudioDeviceID deviceID=[selectedOption deviceID];
+
+    // get the streamID (it might have changed)
+    AudioStreamID streamID=[self getStreamIDForIndex:[selectedOption streamIndex] device:deviceID];
+       
+    // Find the actual formats
+    err =  AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormats,  &theSize, &outWritable);
+    formatsAvailable=(AudioStreamBasicDescription*) malloc(theSize);
+    err = AudioStreamGetProperty(streamID, 0, kAudioStreamPropertyPhysicalFormats, &theSize, formatsAvailable);
+    if(err)
+    {
+       msg_Err(p_intf, "Error %4.4s getting the stream formats", &err);
+       return 0;
+    };
+    
+    UInt32 formtmp=[selectedOption mFormatID];
+    if(DEBUG_ASYSTM) msg_Dbg(p_intf, "looking for:   %4.4s - %d chans, %d Hz", &formtmp,
+           [selectedOption mChannels], preferredSampleRate);
+
+    // Check if there's a "best match" which has our required rate
+    for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
+    {
+       if(DEBUG_ASYSTM) msg_Dbg(p_intf, "actual:   %4.4s - %d chans, %d Hz, %d bits/sample, %d bytes/frame",
+        &formatsAvailable[i].mFormatID, formatsAvailable[i].mChannelsPerFrame,
+        (int)formatsAvailable[i].mSampleRate,
+        formatsAvailable[i].mBitsPerChannel, formatsAvailable[i].mBytesPerFrame);
+
+       if(formatsAvailable[i].mChannelsPerFrame<0 || formatsAvailable[i].mChannelsPerFrame>100) {
+           msg_Err(p_intf, "bogus format! index %i", i);
+           return 0;
+       };
+       
+       if( formatsAvailable[i].mFormatID == [selectedOption mFormatID]
+     && formatsAvailable[i].mChannelsPerFrame == [selectedOption mChannels]
+     && (int)formatsAvailable[i].mSampleRate == preferredSampleRate)
+       {
+           if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Found the perfect format!!");
+           foundFormat=true;
+           break;
+       };
+    };
+
+    int rate=MAXINT, format=0;
+    if(!foundFormat)
+    {
+       for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
+       {
+           // We don't have one... check if there's one with a higher sample rate.
+           // Upsampling should be better than downsampling.
+           // Select the smallest of the higher sample rates, to save resources.
+           int actrate=(int)formatsAvailable[i].mSampleRate;
+           if( formatsAvailable[i].mFormatID == [selectedOption mFormatID]
+        && formatsAvailable[i].mChannelsPerFrame == [selectedOption mChannels]
+        &&  actrate > preferredSampleRate)
+           {
+               if(actrate < rate)
+               {
+                   rate=actrate;
+                   format=i;
+               }
+           };
+       };
+       if(rate!=MAXINT)        // This means we have found a rate!! Yes!
+       {
+           if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Only got a format with higher sample rate");
+           foundFormat=true;
+           i=format;
+       };
+    };
+    
+    if(!foundFormat)
+    {
+       rate=0;
+       for(i=0; i<theSize/sizeof(AudioStreamBasicDescription); i++)
+       {
+           // Our last chance: select the highest lower sample rate.
+           int actrate=(int)formatsAvailable[i].mSampleRate;
+           if( actrate >= preferredSampleRate) // We must have done something wrong
+           {
+               if(DEBUG_ASYSTM) msg_Err(p_intf, "Found a rate that should have been selected previously.");
+               free(formatsAvailable);
+               return 0;
+           };
+
+           if(actrate > rate)
+           {
+               rate=actrate;
+               format=i;
+           }
+       };
+
+       if(rate!=0)     // This means we have found a rate!! Yes!
+       {
+           if(DEBUG_ASYSTM) msg_Dbg(p_intf, "Only got a format with lower sample rate");
+           foundFormat=true;
+           i=format;
+       }
+       else // We must have done something wrong
+       {
+           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.");
+           free(formatsAvailable);
+           return 0;
+       };
+    };
+    AudioStreamBasicDescription desc=formatsAvailable[i];
+    free(formatsAvailable);
+
+    // Set the device stream format
+    Boolean isWriteable;
+
+    err = AudioStreamGetPropertyInfo(streamID, 0, kAudioStreamPropertyPhysicalFormat, &theSize, &isWriteable);
+    if(err) msg_Err(p_intf, "GetPropertyInfo (stream format) error %4.4s - theSize %d", &err, theSize);
+    if(DEBUG_ASYSTM) msg_Dbg(p_intf, "size %d, writable %d", theSize, isWriteable);
+
+    if(DEBUG_ASYSTM) printStreamDescription("want to set", &desc);
+
+    err = AudioStreamSetProperty(streamID, 0, 0, kAudioStreamPropertyPhysicalFormat, theSize, &desc);
+    if(err) msg_Err(p_intf, "SetProperty (stream format) error %4.4s - theSize %d", &err, theSize);
+
+    // Because of the format change, the streamID has changed!
+    // That's why we return the deviceID.
+    return deviceID;
+};
+
+    
+- (void)dealloc
+{
+    [main release];
+};
+
+
+@end
index 276ef92..81226c5 100644 (file)
@@ -2,7 +2,7 @@
  * intf.h: MacOS X interface plugin
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: intf.h,v 1.1 2002/08/04 17:23:43 sam Exp $
+ * $Id: intf.h,v 1.2 2002/10/02 22:56:53 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
  *          Christophe Massiot <massiot@via.ecp.fr>
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
  *****************************************************************************/
 
+#include <vlc/vlc.h>
+#include <vlc/intf.h>
+#include <vlc/vout.h>
+
+#include <Cocoa/Cocoa.h>
+
 /*****************************************************************************
  * VLCApplication interface 
  *****************************************************************************/
@@ -140,6 +146,8 @@ struct intf_sys_t
     IBOutlet id o_dmi_play;
     IBOutlet id o_dmi_pause;
     IBOutlet id o_dmi_stop;
+
+    id asystm;                 // MacOSXAudioSystem
 }
 
 - (void)terminate;
@@ -156,6 +164,8 @@ struct intf_sys_t
 - (IBAction)clearRecentItems:(id)sender;
 - (void)openRecentItem:(id)sender;
 
+//- (void)selectAction:(id)sender;
+
 @end
 
 @interface VLCMain (Internal)
index e5f08ed..a97d22d 100644 (file)
@@ -2,7 +2,7 @@
  * intf.m: MacOS X interface plugin
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: intf.m,v 1.2 2002/08/12 09:34:15 sam Exp $
+ * $Id: intf.m,v 1.3 2002/10/02 22:56:53 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
  *          Christophe Massiot <massiot@via.ecp.fr>
 #include <sys/param.h>                                    /* for MAXPATHLEN */
 #include <string.h>
 
-#include <vlc/vlc.h>
-#include <vlc/intf.h>
-#include <vlc/vout.h>
-
-#include <Cocoa/Cocoa.h>
 #include <QuickTime/QuickTime.h>
 
 #include "intf.h"
 #include "vout.h"
 #include "playlist.h"
+#include "asystm.h"
 
 /*****************************************************************************
  * Local prototypes.
@@ -246,6 +242,15 @@ static void Run( intf_thread_t *p_intf )
     [[NSRunLoop currentRunLoop] 
         addPort: p_intf->p_sys->o_sendport
         forMode: NSDefaultRunLoopMode];
+
+    // Since we need the sound menu now, it's a good time to create the sound system class
+    asystm=[[MacOSXAudioSystem alloc] initWithGUI:self];
+    [asystm retain];
+    
+}
+
+- (void)noopAction:(id)sender {
+    // nothing
 }
 
 - (BOOL)application:(NSApplication *)o_app openFile:(NSString *)o_filename
@@ -431,6 +436,8 @@ static void Run( intf_thread_t *p_intf )
                 context: [NSGraphicsContext currentContext] eventNumber: 1
                 clickCount: 1 pressure: 0.0];
     [NSApp postEvent: pEvent atStart: YES];
+
+    [asystm release];
 }
 
 - (void)manageMode