Hi all --

I've been a happy mpd user for many years, and now I finally have an
opportunity to contribute something.  Specifically, I'm setting up a
retired G4 PowerBook as a living-room music server using mpd to feed
an external USB DAC.  And I've found that mpd's OS X output plugin
isn't quite as flexible as I like: there's no way to specify which
audio device to use.  This is no big deal if everything runs the
logged-in desktop user, but I'm running mpd as a system daemon started
at boot, with a dedicated mpd user.  So it only connects to the Mac's
internal audio hardware, not my nice USB DAC.

So I spent a couple of hours learning my way around mpd (quite easy,
thank you!) and several days learning my way around OS X audio
programming (rather more complex), and the resulting patch is below.
This is obviously a work-in-progress, as you can tell from the
hardcoded name of my USB DAC.  (Haven't figured out how to read
configuration yet.)  But I thought I would post it early to get some
feedback.

Before I dump the patch on you, here's what I understand about audio on OS X:

  * you can choose to open the default device, or the system device,
or the "AUHAL" (audio unit hardware abstraction layer) device
  * the default device is where the user has specified that most audio
should go (under System Preferences > Sound)
  * the system device is where system bleeps and pings go (usually
it's the default device, but the user can choose a different device)
  * AUHAL lets you specify a device by device ID
  * device ID is an opaque int, meaningless to end users
  * audio devices also have a UID (readable but quite long and hard to
discover) and a name (which is what you see in the System Preferences
GUI)

Thus, I chose to use the audio device name as the configuration setting in mpd.

The other choice I made was for mpd config to mirror the system API:
either you set

  devicetype "default"        # or "system"

and devicename is ignored, or you set

  devicetype "hal"
  devicename <something you see in the System Preferences GUI>

It would be silly to set devicetype "hal" and not specify devicename.
Conversely, supplying devicename with devicetype "default" or "system"
is pointless.

This is obviously not the only way to do things.  I'm open to feedback.

Anyways, here's my patch to date.


--- mpd-clean/src/output/osx_plugin.c   2010-12-12 18:05:17.000000000 -0500
+++ mpd-hacked/src/output/osx_plugin.c  2010-12-12 19:30:18.000000000 -0500
@@ -28,6 +28,13 @@
 #define G_LOG_DOMAIN "osx"

 struct osx_output {
+       /* configuration settings */
+       const char *devicetype; /* "default", "system", or "hal" */
+       const char *devicename; /* only applicable for devicetype "hal" */
+
+       /* values derived from configuration */
+       OSType component_subtype;
+
        AudioUnit au;
        GMutex *mutex;
        GCond *condition;
@@ -54,6 +61,20 @@
        return true;
 }

+static void
+osx_output_configure(struct osx_output *oo, const struct config_param *param)
+{
+       /* default settings */
+       oo->devicetype = "default";
+       oo->component_subtype = kAudioUnitSubType_DefaultOutput;
+       oo->devicename = NULL;
+
+       /* my preferences */
+       oo->devicetype = "hal";
+       oo->component_subtype = kAudioUnitSubType_HALOutput;
+       oo->devicename = "stereo-link 1200 USB DAC";
+}
+
 static void *
 osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
                G_GNUC_UNUSED const struct config_param *param,
@@ -61,6 +82,7 @@
 {
        struct osx_output *oo = g_new(struct osx_output, 1);

+       osx_output_configure(oo, param);
        oo->mutex = g_mutex_new();
        oo->condition = g_cond_new();

@@ -156,6 +178,86 @@
 }

 static bool
+osx_output_set_device(struct osx_output *oo, GError **error)
+{
+       OSStatus status;
+       UInt32 size, numdevices;
+       AudioDeviceID *deviceids;
+       char name[256];
+       unsigned int i;
+
+       if (oo->component_subtype != kAudioUnitSubType_HALOutput)
+               return true;
+
+       /* how many audio devices are there? */
+       status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+                                             &size,
+                                             NULL);
+       if (status != noErr) {
+               g_set_error(error, osx_output_quark(), 0,
+                           "Unable to determine number of OS X audio devices: 
%s",
+                           GetMacOSStatusCommentString(status));
+               return false;
+       }
+
+       /* what are the available audio device IDs? */
+       numdevices = size / sizeof(AudioDeviceID);
+       deviceids = malloc(size);
+       status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+                                         &size,
+                                         deviceids);
+       if (status != noErr) {
+               g_set_error(error, osx_output_quark(), 0,
+                           "Unable to determine OS X audio device IDs: %s",
+                           GetMacOSStatusCommentString(status));
+               return false;
+       }
+
+       /* which audio device matches oo->devicename? */
+       for (i = 0; i < numdevices; i++) {
+               size = sizeof(name);
+               status = AudioDeviceGetProperty(deviceids[i], 0, false,
+                                               kAudioDevicePropertyDeviceName,
+                                               &size, name);
+               if (status != noErr) {
+                       g_set_error(error, osx_output_quark(), 0,
+                                   "Unable to determine OS X device name "
+                                   "(device %lu): %s",
+                                   deviceids[i],
+                                   GetMacOSStatusCommentString(status));
+                       return false;
+               }
+               if (strcmp(oo->devicename, name) == 0) {
+                       g_debug("found matching device: ID=%lu, name=%s",
+                               deviceids[i], name);
+                       break;
+               }
+       }
+       if (i == numdevices) {
+               g_warning("Found no audio device with name '%s' "
+                         "(will use default audio device)",
+                         oo->devicename);
+               return true;
+       }
+
+       status = AudioUnitSetProperty(oo->au,
+                                     kAudioOutputUnitProperty_CurrentDevice,
+                                     kAudioUnitScope_Global,
+                                     0,
+                                     &(deviceids[i]),
+                                     sizeof(AudioDeviceID));
+       if (status != noErr) {
+               g_set_error(error, osx_output_quark(), 0,
+                           "Unable to set OS X audio output device: %s",
+                           GetMacOSStatusCommentString(status));
+               return false;
+       }
+       g_debug("set OS X audio output device ID=%lu, name=%s",
+               deviceids[i], name);
+       return true;
+}
+
+static bool
 osx_output_open(void *data, struct audio_format *audio_format, GError **error)
 {
        struct osx_output *od = data;
@@ -167,7 +269,7 @@
        ComponentResult result;

        desc.componentType = kAudioUnitType_Output;
-       desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+       desc.componentSubType = od->component_subtype;
        desc.componentManufacturer = kAudioUnitManufacturer_Apple;
        desc.componentFlags = 0;
        desc.componentFlagsMask = 0;
@@ -196,6 +298,10 @@
                return false;
        }

+       /* only applicable if devicetype is "hal" */
+       if (!osx_output_set_device(od, error))
+               return false;
+
        callback.inputProc = osx_render;
        callback.inputProcRefCon = od;


Greg

------------------------------------------------------------------------------
Lotusphere 2011
Register now for Lotusphere 2011 and learn how
to connect the dots, take your collaborative environment
to the next level, and enter the era of Social Business.
http://p.sf.net/sfu/lotusphere-d2d
_______________________________________________
Musicpd-dev-team mailing list
Musicpd-dev-team@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/musicpd-dev-team

Reply via email to