changeset 4686:ff2d1f46256a

Merge
author michaelm
date Thu, 26 Jan 2012 08:58:24 +0000
parents 988c39c7b9ac fe56f3a406ce
children aa30a9aebd68
files src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Ports.c src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Utils.c
diffstat 3 files changed, 629 insertions(+), 1077 deletions(-) [+]
line wrap: on
line diff
--- a/src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Ports.c	Thu Jan 26 08:53:30 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,550 +0,0 @@
-/*
- * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-//#define USE_ERROR
-//#define USE_TRACE
-
-#include <CoreAudio/CoreAudio.h>
-#include <IOKit/audio/IOAudioTypes.h>
-#include "Ports.h"
-#include "PLATFORM_API_MacOSX_Utils.h"
-
-#if USE_PORTS == TRUE
-
-/*
- TODO
-
- Test devices with >2 channels.
- Compare control names and tree structure to other platforms.
- Implement virtual controls (balance, pan, master volume).
- */
-
-typedef struct {
-    struct PortMixer *mixer;
-
-    AudioObjectID control;
-    AudioClassID class; // kAudioVolumeControlClassID etc.
-    UInt32 scope; // input, output
-
-    void *jcontrol;
-    char *jcontrolType; // CONTROL_TYPE_VOLUME etc.
-
-    int channel; // master = 0, channels = 1 2 ...
-
-    AudioValueRange range;
-} PortControl;
-
-typedef struct {
-    AudioDeviceID deviceID;
-
-    // = # of ports on the mixer
-    // cached here in case the values can change
-    int numInputStreams;
-    int numOutputStreams;
-    // streams[0..numInputStreams-1] contains inputs,
-    // streams[numInputStreams..numInputStreams+numOutputStreams-1] contains outputs
-    AudioStreamID *streams;
-
-    int numDeviceControls;
-    PortControl *deviceControls;
-
-    int *numStreamControls;
-    PortControl **streamControls;
-} PortMixer;
-
-INT32 PORT_GetPortMixerCount() {
-    int count = GetAudioDeviceCount();
-    TRACE1("< PORT_GetPortMixerCount = %d\n", count);
-
-    return count;
-}
-
-INT32 PORT_GetPortMixerDescription(INT32 mixerIndex, PortMixerDescription* mixerDescription) {
-    AudioDeviceDescription description;
-
-    description.strLen = PORT_STRING_LENGTH;
-    description.name   = mixerDescription->name;
-    description.vendor = mixerDescription->vendor;
-    description.description = NULL;
-
-    /*
-     We can't fill out the 'version' and 'description' fields.
-     */
-
-    return GetAudioDeviceDescription(mixerIndex, &description);
-}
-
-void* PORT_Open(INT32 mixerIndex) {
-    AudioDeviceDescription description = {0};
-    PortMixer *mixer = calloc(1, sizeof(PortMixer));
-
-    GetAudioDeviceDescription(mixerIndex, &description);
-    mixer->deviceID = description.deviceID;
-    mixer->numInputStreams  = description.numInputStreams;
-    mixer->numOutputStreams = description.numOutputStreams;
-
-    if (mixer->numInputStreams || mixer->numOutputStreams) {
-        mixer->streams = calloc(mixer->numInputStreams + mixer->numOutputStreams, sizeof(AudioStreamID));
-
-        GetAudioObjectProperty(mixer->deviceID, kAudioDevicePropertyScopeInput, kAudioDevicePropertyStreams,
-                               mixer->numInputStreams * sizeof(AudioStreamID), mixer->streams, 0);
-
-        GetAudioObjectProperty(mixer->deviceID, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreams,
-                               mixer->numOutputStreams * sizeof(AudioStreamID),
-                               mixer->streams + mixer->numInputStreams, 0);
-    }
-
-    TRACE1("< PORT_Open %p\n", mixer);
-    return mixer;
-}
-
-void PORT_Close(void* id) {
-    TRACE1("> PORT_Close %p\n", id);
-
-    if (id) {
-        PortMixer *mixer = id;
-        free(mixer->streams);
-        free(mixer);
-    }
-}
-
-INT32 PORT_GetPortCount(void* id) {
-    PortMixer *mixer = id;
-    int numStreams = mixer->numInputStreams + mixer->numOutputStreams;
-
-    TRACE1("< PORT_GetPortCount = %d\n", numStreams);
-    return numStreams;
-}
-
-INT32 PORT_GetPortType(void* id, INT32 portIndex) {
-    PortMixer *mixer = id;
-
-    AudioStreamID streamID = mixer->streams[portIndex];
-    UInt32 direction;
-    UInt32 terminalType;
-    UInt32 size;
-    int err, ret;
-
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyTerminalType,
-                                 sizeof(terminalType), &terminalType, 1);
-    if (err) goto exit;
-
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyDirection,
-                                 sizeof(direction), &direction, 1);
-    if (err) goto exit;
-
-    /*
-     Note that kAudioStreamPropertyTerminalType actually returns values from
-     IOAudioTypes.h, not the defined kAudioStreamTerminalType*.
-     */
-
-    if (direction) {
-        // input
-        switch (terminalType) {
-            case EXTERNAL_LINE_CONNECTOR:
-                ret = PORT_SRC_LINE_IN;
-                break;
-
-            case INPUT_MICROPHONE:
-                ret = PORT_SRC_MICROPHONE;
-                break;
-
-            case EXTERNAL_SPDIF_INTERFACE:
-                ret = PORT_SRC_UNKNOWN;
-                break;
-
-            default:
-                TRACE1("unknown input terminal type %#x\n", terminalType);
-#ifdef USE_TRACE
-                AudioObjectShow(mixer->deviceID);
-                AudioObjectShow(streamID);
-#endif
-                ret = PORT_SRC_UNKNOWN;
-        }
-    } else {
-        // output
-        switch (terminalType) {
-            case EXTERNAL_LINE_CONNECTOR:
-                ret = PORT_DST_LINE_OUT;
-                break;
-
-            case OUTPUT_SPEAKER:
-                ret = PORT_DST_SPEAKER;
-                break;
-
-            case OUTPUT_HEADPHONES:
-                ret = PORT_DST_HEADPHONE;
-                break;
-
-            case EXTERNAL_SPDIF_INTERFACE:
-                ret = PORT_DST_UNKNOWN;
-                break;
-
-            default:
-                TRACE1("unknown output terminal type %#x\n", terminalType);
-#ifdef USE_TRACE
-                AudioObjectShow(mixer->deviceID);
-                AudioObjectShow(streamID);
-#endif
-                ret = PORT_DST_UNKNOWN;
-        }
-    }
-
-    TRACE1("< PORT_GetPortType = %d\n", ret);
-    return ret;
-exit:
-    ERROR1("< PORT_GetPortType error %d\n", err);
-    return -1;
-}
-
-INT32 PORT_GetPortName(void* id, INT32 portIndex, char* name, INT32 len) {
-    PortMixer *mixer = id;
-    AudioStreamID streamID = mixer->streams[portIndex];
-
-    CFStringRef cfname = NULL;
-    OSStatus err = noErr;
-
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName,
-                                 sizeof(cfname), &cfname, 1);
-    if (err && err != kAudioHardwareUnknownPropertyError) goto exit;
-
-    if (!cfname) {
-        // use the device's name if the stream has no name (usually the case)
-        err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName,
-                                     sizeof(cfname), &cfname, 1);
-    }
-
-    if (cfname) {
-        CFStringGetCString(cfname, name, len, kCFStringEncodingUTF8);
-    }
-
-exit:
-    if (cfname) CFRelease(cfname);
-
-    TRACE1("< PORT_GetPortName %s\n", name);
-
-    return FALSE;
-}
-
-static void CreateVolumeControl(PortControlCreator *creator, PortControl *control)
-{
-    Float32 min = 0, max = 1, precision;
-    AudioValueRange *range = &control->range;
-    UInt32 size;
-
-    control->jcontrolType = CONTROL_TYPE_VOLUME;
-
-    GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyDecibelRange,
-                           sizeof(control->range), &control->range, 1);
-    precision = 1. / (range->mMaximum - range->mMinimum);
-
-    control->jcontrol = creator->newFloatControl(creator, control, CONTROL_TYPE_VOLUME, min, max, precision, "");
-}
-
-static void CreateMuteControl(PortControlCreator *creator, PortControl *control)
-{
-    control->jcontrolType = CONTROL_TYPE_MUTE;
-    control->jcontrol = creator->newBooleanControl(creator, control, CONTROL_TYPE_MUTE);
-}
-
-void PORT_GetControls(void* id, INT32 portIndex, PortControlCreator* creator) {
-    PortMixer *mixer = id;
-    AudioStreamID streamID = mixer->streams[portIndex];
-
-    UInt32 size;
-    OSStatus err;
-    int i;
-
-    int numVolumeControls = 0, numMuteControls = 0; // not counting the master
-    int hasChannelVolume  = 0, hasChannelMute  = 0;
-    PortControl *masterVolume = NULL, *masterMute = NULL;
-
-    UInt32 wantedScope = portIndex < mixer->numInputStreams ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
-
-    // initialize the device controls if this is the first stream
-    if (!mixer->numDeviceControls) {
-        // numDeviceControls / numStreamControls are overestimated
-        // because we don't actually filter by if the owned objects are controls
-        err = GetAudioObjectPropertySize(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects,
-                                         &size);
-        if (err) goto exit;
-
-        mixer->numDeviceControls = size / sizeof(AudioObjectID);
-
-        if (mixer->numDeviceControls) {
-            AudioObjectID *controlIDs = calloc(mixer->numDeviceControls, sizeof(AudioObjectID));
-            mixer->deviceControls     = calloc(mixer->numDeviceControls, sizeof(PortControl));
-
-            err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects,
-                                         mixer->numDeviceControls * sizeof(AudioObjectID), controlIDs, 1);
-            if (err) goto exit;
-
-            for (i = 0; i < mixer->numDeviceControls; i++) {
-                PortControl *control = &mixer->deviceControls[i];
-
-                control->control = controlIDs[i];
-                control->mixer = mixer;
-
-                GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyClass,
-                                       sizeof(control->class), &control->class, 1);
-                err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyElement,
-                                       sizeof(control->channel), &control->channel, 1);
-
-                if (err) { // not a control
-                    control->class = 0;
-                    continue;
-                }
-
-                GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyScope,
-                                       sizeof(control->scope), &control->scope, 1);
-
-                TRACE3("%.4s control, channel %d scope %.4s\n", &control->class, control->channel, &control->scope);
-            }
-        }
-    }
-
-    // count the number of device controls with the appropriate scope
-    if (mixer->numDeviceControls) {
-        for (i = 0; i < mixer->numDeviceControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
-
-            if (control->scope != wantedScope)
-                continue;
-
-            switch (control->class) {
-                case kAudioVolumeControlClassID:
-                    if (control->channel == 0)
-                        masterVolume = control;
-                    else {
-                        numVolumeControls++;
-                        hasChannelVolume = 1;
-                    }
-                    break;
-
-                case kAudioMuteControlClassID:
-                    if (control->channel == 0)
-                        masterMute = control;
-                    else {
-                        numMuteControls++;
-                        hasChannelMute = 1;
-                    }
-                    break;
-            }
-        }
-    }
-
-    TRACE4("volume: channel %d master %d, mute: channel %d master %d\n", numVolumeControls, masterVolume != NULL, numMuteControls, masterMute != NULL);
-
-    if (masterVolume) {
-        if (!masterVolume->jcontrol)
-            CreateVolumeControl(creator, masterVolume);
-        creator->addControl(creator, masterVolume->jcontrol);
-    }
-
-    if (masterMute) {
-        if (!masterMute->jcontrol)
-            CreateMuteControl(creator, masterMute);
-        creator->addControl(creator, masterMute->jcontrol);
-    }
-
-    if (numVolumeControls) {
-        void **jControls = calloc(numVolumeControls, sizeof(void*));
-        int j = 0;
-        for (i = 0; i < mixer->numDeviceControls && j < numVolumeControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
-
-            if (control->class != kAudioVolumeControlClassID || control->channel == 0 || control->scope != wantedScope)
-                continue;
-
-            if (!control->jcontrol)
-                CreateVolumeControl(creator, control);
-            jControls[j++] = control->jcontrol;
-        }
-
-        void *compoundControl = creator->newCompoundControl(creator, "Volume", jControls, numVolumeControls);
-        creator->addControl(creator, compoundControl);
-        free(jControls);
-    }
-
-    if (numMuteControls) {
-        void **jControls = calloc(numMuteControls, sizeof(void*));
-        int j = 0;
-        for (i = 0; i < mixer->numDeviceControls && j < numMuteControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
-
-            if (control->class != kAudioMuteControlClassID || control->channel == 0 || control->scope != wantedScope)
-                continue;
-
-            if (!control->jcontrol)
-                CreateMuteControl(creator, control);
-            jControls[j++] = control->jcontrol;
-        }
-
-        void *compoundControl = creator->newCompoundControl(creator, "Mute", jControls, numMuteControls);
-        creator->addControl(creator, compoundControl);
-        free(jControls);
-    }
-
-    if (!mixer->numStreamControls)
-        mixer->numStreamControls = calloc(mixer->numInputStreams + mixer->numOutputStreams, sizeof(int));
-
-    err = GetAudioObjectPropertySize(streamID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects,
-                                     &size);
-    if (err) goto exit;
-
-    mixer->numStreamControls[portIndex] = size / sizeof(AudioObjectID);
-
-    TRACE2("%d controls on device, %d on stream\n", mixer->numDeviceControls, mixer->numStreamControls[portIndex]);
-
-exit:
-    if (err) {
-        ERROR1("PORT_GetControls err %.4s\n", &err);
-        free(mixer->deviceControls);
-    }
-
-    TRACE0("< PORT_GetControls\n");
-}
-
-INT32 PORT_GetIntValue(void* controlIDV) {
-    PortControl *control = controlIDV;
-    UInt32 value = 0;
-    OSStatus err = 0;
-    UInt32 size;
-
-    switch (control->class) {
-        case kAudioMuteControlClassID:
-        {
-            err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                         kAudioBooleanControlPropertyValue, sizeof(value), &value, 1);
-            if (err) goto exit;
-        }
-            break;
-
-        default:
-            ERROR0("SetIntValue requested for non-Int control\n");
-            goto exit;
-    }
-
-    TRACE1("< PORT_GetIntValue = %d\n", value);
-    return value;
-
-exit:
-    if (err) {
-        ERROR1("PORT_GetIntValue err %d\n", err);
-    }
-    return FALSE;
-}
-
-void PORT_SetIntValue(void* controlIDV, INT32 value) {
-    TRACE1("> PORT_SetIntValue = %d\n", value);
-    PortControl *control = controlIDV;
-    OSStatus err = 0;
-
-    switch (control->class) {
-        case kAudioMuteControlClassID:
-        {
-            const AudioObjectPropertyAddress address =
-            { kAudioBooleanControlPropertyValue, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
-            err = AudioObjectSetPropertyData(control->control, &address, 0, NULL, sizeof(value), &value);
-            if (err) goto exit;
-        }
-            break;
-
-        default:
-            ERROR0("SetIntValue requested for non-Int control\n");
-    }
-
-    return;
-
-exit:
-    if (err) {
-        ERROR1("PORT_SetIntValue err %d\n", err);
-    }
-}
-
-float PORT_GetFloatValue(void* controlIDV) {
-    PortControl *control = controlIDV;
-    Float32 value = 0;
-    OSStatus err = 0;
-    UInt32 size;
-
-    switch (control->class) {
-        case kAudioVolumeControlClassID:
-        {
-            err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                             kAudioLevelControlPropertyDecibelValue, sizeof(value), &value, 1);
-            if (err) goto exit;
-
-            // convert decibel to 0-1 logarithmic
-            value = (value - control->range.mMinimum) / (control->range.mMaximum - control->range.mMinimum);
-        }
-            break;
-
-        default:
-            ERROR0("GetFloatValue requested for non-Float control\n");
-            break;
-    }
-
-    TRACE1("< PORT_GetFloatValue = %f\n", value);
-    return value;
-
-exit:
-    if (err) {
-        ERROR1("PORT_GetFloatValue err %d\n", err);
-    }
-    return 0;
-}
-
-void PORT_SetFloatValue(void* controlIDV, float value) {
-    TRACE1("> PORT_SetFloatValue = %f\n", value);
-    PortControl *control = controlIDV;
-    OSStatus err = 0;
-
-    switch (control->class) {
-        case kAudioVolumeControlClassID:
-        {
-            const AudioObjectPropertyAddress address =
-            { kAudioLevelControlPropertyDecibelValue, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
-
-            value = (value * (control->range.mMaximum - control->range.mMinimum)) + control->range.mMinimum;
-            err = AudioObjectSetPropertyData(control->control, &address, 0, NULL, sizeof(value), &value);
-            if (err) goto exit;
-        }
-            break;
-
-        default:
-            ERROR0("SetFloatValue requested for non-Float control\n");
-            break;
-    }
-
-    return;
-
-exit:
-    if (err) {
-        ERROR1("PORT_GetFloatValue err %d\n", err);
-    }
-}
-
-#endif // USE_PORTS
--- a/src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Ports.cpp	Thu Jan 26 08:53:30 2012 +0000
+++ b/src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Ports.cpp	Thu Jan 26 08:58:24 2012 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,206 +37,351 @@
 
 #if USE_PORTS == TRUE
 
-/*
- TODO
-
- Test devices with >2 channels.
- Compare control names and tree structure to other platforms.
- Implement virtual controls (balance, pan, master volume).
+/* If a device has the only AudioStream in the scope (input or output),
+ * PortMixer provides a single Port, using the stream kAudioStreamPropertyTerminalType
+ * property value to determine Port.Type (PORT_GetPortType function).
+ * If the device has several (more than 1) AudioStreams, there are 2 ways to represent Ports:
+ * 1. (HALLab-style) single Port which represents all device channels with
+ *    "master volume" and (if number of channel is 2) "master balance"; if AudioDevice
+ *    does not provide "master" controls, implement "virtual master" controls.
+ *    Port.Type is PORT_SRC_UNKNOWN or PORT_DST_UNKNOWN.
+ * 2. provide a separate Port for every AudioStream (with appropriate Port.Type);
+ *
+ * AudioHardware.h claims that AudioStream objects share AudioControl objects with their owning AudioDevice.
+ * In practice 10.7 OSX drivers (built-in devices, USB audio) implement AudioControl only for AudioDevice.
+ * For now 1st way is implemented (2nd way can be better if AudioStreams provide AudioControls).
  */
 
 static DeviceList deviceCache;
 
-struct PortMixer;
+#define FourCC2Str(n) ((char[5]){(char)(n >> 24), (char)(n >> 16), (char)(n >> 8), (char)(n), 0})
 
+
+// CoreAudio's AudioControl
+struct AudioControl {
+    AudioObjectID controlID;
+    AudioClassID classID;               // kAudioVolumeControlClassID etc.
+    AudioObjectPropertyScope scope;     // input, output
+    AudioObjectPropertyElement channel; // master = 0, channels = 1 2 ...
+};
+
+// Controls for Java
+// PortMixer do all memory management (alloc/free audioControls)
 struct PortControl {
-    PortMixer *mixer;
+    enum ControlType {
+        Volume,     // manages single or multiple volume AudioControl
+        Mute,       // manages single or multiple mute AudioControls
+        Balance     // "virtual" control, manages 2 volume AudioControls (only for stereo lines)
+    };
+    ControlType type;
 
-    AudioObjectID control;
-    AudioClassID classID; // kAudioVolumeControlClassID etc.
-    UInt32 scope; // input, output
+    int controlCount;
+    AudioControl **audioControls;
 
-    void *jcontrol;
-    char *jcontrolType; // CONTROL_TYPE_VOLUME etc.
+    PortControl *next;  // to organize PortControl list
+};
 
-    int channel; // master = 0, channels = 1 2 ...
-
-    AudioValueRange range;
+// represents line (port) for PortMixer
+// used for PORT_GetPortCount/PORT_GetPortType/PORT_GetPortName functions
+struct PortLine {
+    AudioObjectPropertyScope scope;
+    // if the device has several AudioStreams in the scope, streamID == 0
+    AudioStreamID streamID;
 };
 
 struct PortMixer {
     AudioDeviceID deviceID;
 
-    // = # of ports on the mixer
-    // cached here in case the values can change
-    int numInputStreams;
-    int numOutputStreams;
-    // streams[0..numInputStreams-1] contains inputs,
-    // streams[numInputStreams..numInputStreams+numOutputStreams-1] contains outputs
-    AudioStreamID *streams;
+    int portCount;
+    PortLine ports[2]; // maximum 2 lines - 1 for input & 1 for output
 
-    int numDeviceControls;
-    PortControl *deviceControls;
+    int deviceControlCount; // -1 means "not initialized"
+    AudioControl *deviceControls;
 
-    int *numStreamControls;
-    PortControl **streamControls;
+    PortControl *portControls;  // list of port controls
+
+    bool listenersInstalled;
 };
 
+
+void RemoveChangeListeners(PortMixer *mixer);   // forward declaration
+
+OSStatus ChangeListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses,
+        const AudioObjectPropertyAddress inAddresses[], void *inClientData)
+{
+    PortMixer *mixer = (PortMixer *)inClientData;
+
+    OSStatus err = noErr;
+    UInt32 size;
+
+    bool invalid = false;
+
+    for (UInt32 i = 0; i < inNumberAddresses; i++) {
+        switch (inAddresses[i].mSelector) {
+        case kAudioHardwarePropertyDevices:
+            // check if the device has been removed
+            err = GetAudioObjectPropertySize(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal,
+                kAudioHardwarePropertyDevices, &size);
+            if (err == noErr) {
+                int count = size/sizeof(AudioDeviceID);
+                AudioDeviceID devices[count];
+                err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal,
+                    kAudioHardwarePropertyDevices, count*sizeof(AudioDeviceID), devices, 1);
+                if (err == noErr) {
+                    bool found = false;
+                    for (int j = 0; j < count; j++) {
+                        if (devices[j] == mixer->deviceID) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        invalid = true;
+                    }
+                }
+            }
+            break;
+        case kAudioObjectPropertyOwnedObjects:
+        case kAudioDevicePropertyDeviceHasChanged:
+            // ensure all _used_ AudioControl are valid
+            err = GetAudioObjectPropertySize(mixer->deviceID, kAudioObjectPropertyScopeGlobal,
+                kAudioObjectPropertyOwnedObjects, &size);
+            if (err == noErr) {
+                int count = size / sizeof(AudioObjectID);
+                AudioObjectID controlIDs[count];
+                err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal,
+                    kAudioObjectPropertyOwnedObjects, count * sizeof(AudioObjectID), &controlIDs, 1);
+                if (err == noErr) {
+                    for (PortControl *ctrl = mixer->portControls; ctrl != NULL; ctrl = ctrl->next) {
+                        for (int i = 0; i < ctrl->controlCount; i++) {
+                            bool found = false;
+                            for (int j = 0; j < count; j++) {
+                                if (ctrl->audioControls[i]->controlID == controlIDs[j]) {
+                                    found = true;
+                                    break;
+                                }
+                            }
+                            if (!found) {
+                                invalid = true;
+                                break;  // goto next control
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    if (invalid) {
+        TRACE1("PortMixer (deviceID=0x%x) becomes invalid", (int)mixer->deviceID);
+        // invalidate all controls
+        for (int i=0; i<mixer->deviceControlCount; i++) {
+            mixer->deviceControls[i].controlID = 0;
+        }
+        RemoveChangeListeners(mixer);
+    }
+
+
+    return noErr;
+}
+
+const AudioObjectPropertyAddress changeListenersAddresses[] = {
+    {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster},
+    {kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster},
+    {kAudioDevicePropertyDeviceHasChanged, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}
+};
+
+void AddChangeListeners(PortMixer *mixer) {
+    if (!mixer->listenersInstalled) {
+        for (size_t i=0; i<sizeof(changeListenersAddresses)/sizeof(changeListenersAddresses[0]); i++) {
+            AudioObjectAddPropertyListener(mixer->deviceID, &changeListenersAddresses[i], ChangeListenerProc, mixer);
+        }
+        mixer->listenersInstalled = true;
+    }
+}
+
+void RemoveChangeListeners(PortMixer *mixer) {
+    if (mixer->listenersInstalled) {
+        for (size_t i=0; i<sizeof(changeListenersAddresses)/sizeof(changeListenersAddresses[0]); i++) {
+            AudioObjectRemovePropertyListener(mixer->deviceID, &changeListenersAddresses[i], ChangeListenerProc, mixer);
+        }
+        mixer->listenersInstalled = false;
+    }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// functions from Port.h
+
 INT32 PORT_GetPortMixerCount() {
     deviceCache.Refresh();
     int count = deviceCache.GetCount();
-    TRACE1("< PORT_GetPortMixerCount = %d\n", count);
-
+    TRACE1("<<PORT_GetPortMixerCount = %d\n", count);
     return count;
 }
 
 INT32 PORT_GetPortMixerDescription(INT32 mixerIndex, PortMixerDescription* mixerDescription) {
     bool result = deviceCache.GetDeviceInfo(mixerIndex, NULL, PORT_STRING_LENGTH,
             mixerDescription->name, mixerDescription->vendor, mixerDescription->description, mixerDescription->version);
-
     return result ? TRUE : FALSE;
 }
 
 void* PORT_Open(INT32 mixerIndex) {
-    OSStatus err;
+    TRACE1("\n>>PORT_Open (mixerIndex=%d)\n", (int)mixerIndex);
     PortMixer *mixer = (PortMixer *)calloc(1, sizeof(PortMixer));
-    memset(mixer, 0, sizeof(mixer));
 
     mixer->deviceID = deviceCache.GetDeviceID(mixerIndex);
     if (mixer->deviceID != 0) {
-        UInt32 sizeIn = 0, sizeOut = 0;
-        GetAudioObjectPropertySize(mixer->deviceID, kAudioDevicePropertyScopeInput, kAudioDevicePropertyStreams, &sizeIn);
-        GetAudioObjectPropertySize(mixer->deviceID, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreams, &sizeOut);
+        mixer->deviceControlCount = -1; // not initialized
+        // fill mixer->ports (and mixer->portCount)
+        for (int i=0; i<2; i++) {
+            OSStatus err;
+            UInt32 size = 0;
+            AudioObjectPropertyScope scope =
+                (i == 0) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
 
-        if (sizeIn > 0 || sizeOut > 0) {
-            mixer->numInputStreams  = sizeIn / sizeof(AudioStreamID);
-            mixer->numOutputStreams = sizeOut / sizeof(AudioStreamID);
-
-            mixer->streams = (AudioStreamID *)calloc(mixer->numInputStreams + mixer->numOutputStreams, sizeof(AudioStreamID));
-
-            GetAudioObjectProperty(mixer->deviceID, kAudioDevicePropertyScopeInput, kAudioDevicePropertyStreams,
-                                mixer->numInputStreams * sizeof(AudioStreamID), mixer->streams, 0);
-            GetAudioObjectProperty(mixer->deviceID, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreams,
-                                mixer->numOutputStreams * sizeof(AudioStreamID),
-                                mixer->streams + mixer->numInputStreams, 0);
+            err = GetAudioObjectPropertySize(mixer->deviceID, scope, kAudioDevicePropertyStreams, &size);
+            if (err || size == 0) {
+                continue;
+            }
+            if (size / sizeof(AudioStreamID) == 1) {
+                // the device has the only AudioStream
+                AudioStreamID streamID;
+                err = GetAudioObjectProperty(mixer->deviceID, scope, kAudioDevicePropertyStreams,
+                    sizeof(streamID), &streamID, 1);
+                if (err) {
+                    continue;
+                }
+                mixer->ports[mixer->portCount].streamID = streamID;
+            } else {
+                // the device has several AudioStreams in the scope
+                mixer->ports[mixer->portCount].streamID = 0;
+            }
+            mixer->ports[mixer->portCount].scope = scope;
+            mixer->portCount++;
         }
     }
 
-    TRACE1("< PORT_Open %p\n", mixer);
+    TRACE2("<<PORT_Open (mixerIndex=%d) %p\n", mixerIndex, mixer);
     return mixer;
 }
 
+
 void PORT_Close(void* id) {
+    TRACE1(">>PORT_Close %p\n", id);
     PortMixer *mixer = (PortMixer *)id;
-    TRACE1("> PORT_Close %p\n", id);
 
     if (mixer) {
-        free(mixer->streams);
+        RemoveChangeListeners(mixer);
+        while (mixer->portControls != NULL) {
+            PortControl *control2delete = mixer->portControls;
+            mixer->portControls = control2delete->next;
+
+            if (control2delete->audioControls != NULL) {
+                free(control2delete->audioControls);
+            }
+            free(control2delete);
+        }
+        if (mixer->deviceControls) {
+            free(mixer->deviceControls);
+        }
         free(mixer);
     }
+    TRACE1("<<PORT_Close %p\n", mixer);
 }
 
 INT32 PORT_GetPortCount(void* id) {
     PortMixer *mixer = (PortMixer *)id;
-    int numStreams = mixer->numInputStreams + mixer->numOutputStreams;
 
-    TRACE1("< PORT_GetPortCount = %d\n", numStreams);
-    return numStreams;
+    int result = mixer->portCount;
+
+    TRACE1("<<PORT_GetPortCount = %d\n", result);
+    return result;
 }
 
 INT32 PORT_GetPortType(void* id, INT32 portIndex) {
     PortMixer *mixer = (PortMixer *)id;
+    INT32 ret = 0;
 
-    AudioStreamID streamID = mixer->streams[portIndex];
-    UInt32 direction;
-    UInt32 terminalType;
-    UInt32 size;
-    INT32 ret = 0;
-    OSStatus err;
-
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyTerminalType,
-                                 sizeof(terminalType), &terminalType, 1);
-    if (err) {
-        OS_ERROR1(err, "PORT_GetPortType(kAudioStreamPropertyTerminalType), portIndex=%d", portIndex);
-        return 0;
-    }
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyDirection,
-                                 sizeof(direction), &direction, 1);
-    if (err) {
-        OS_ERROR1(err, "PORT_GetPortType(kAudioStreamPropertyDirection), portIndex=%d", portIndex);
+    if (portIndex < 0 || portIndex >= mixer->portCount) {
+        ERROR1("PORT_GetPortType: line (portIndex = %d) not found\n", portIndex);
         return 0;
     }
 
-    // Note that kAudioStreamPropertyTerminalType actually returns values from
-    // IOAudioTypes.h, not the defined kAudioStreamTerminalType*.
+    AudioObjectPropertyScope scope = mixer->ports[portIndex].scope;
+    AudioStreamID streamID = mixer->ports[portIndex].streamID;
+    if (streamID != 0) {
+        UInt32 terminalType;
 
-    if (direction) {
-        // input
+        OSStatus err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal,
+            kAudioStreamPropertyTerminalType, sizeof(terminalType), &terminalType, 1);
+        if (err) {
+            OS_ERROR1(err, "PORT_GetPortType(kAudioStreamPropertyTerminalType), portIndex=%d", portIndex);
+            return 0;
+        }
+
+        // Note that kAudioStreamPropertyTerminalType actually returns values from
+        // IOAudioTypes.h, not the defined kAudioStreamTerminalType*.
+        TRACE4("PORT_GetPortType (portIndex=%d), scope=%s, termType=0x%04x (%s)\n",
+            (int)portIndex, FourCC2Str(scope), (int)terminalType, FourCC2Str(terminalType));
         switch (terminalType) {
-        case EXTERNAL_LINE_CONNECTOR:
-            ret = PORT_SRC_LINE_IN;
-            break;
         case INPUT_MICROPHONE:
             ret = PORT_SRC_MICROPHONE;
             break;
-        case EXTERNAL_SPDIF_INTERFACE:
-            ret = PORT_SRC_UNKNOWN;
-            break;
-        default:
-            TRACE1("unknown input terminal type %#x\n", terminalType);
-#ifdef USE_TRACE
-            AudioObjectShow(mixer->deviceID);
-            AudioObjectShow(streamID);
-#endif
-            ret = PORT_SRC_UNKNOWN;
-        }
-    } else {
-        // output
-        switch (terminalType) {
-        case EXTERNAL_LINE_CONNECTOR:
-            ret = PORT_DST_LINE_OUT;
-            break;
+
         case OUTPUT_SPEAKER:
             ret = PORT_DST_SPEAKER;
             break;
         case OUTPUT_HEADPHONES:
             ret = PORT_DST_HEADPHONE;
             break;
-        case EXTERNAL_SPDIF_INTERFACE:
-            ret = PORT_DST_UNKNOWN;
+
+        case EXTERNAL_LINE_CONNECTOR:
+            ret = scope == kAudioDevicePropertyScopeInput ? PORT_SRC_LINE_IN : PORT_DST_LINE_OUT;
             break;
+
         default:
-            TRACE1("unknown output terminal type %#x\n", terminalType);
-#ifdef USE_TRACE
-            AudioObjectShow(mixer->deviceID);
-            AudioObjectShow(streamID);
-#endif
-            ret = PORT_DST_UNKNOWN;
+            TRACE1("  unknown output terminal type %#x\n", terminalType);
         }
+    } else {
+        TRACE0("  PORT_GetPortType: multiple streams\n");
     }
 
-    TRACE2("< PORT_GetPortType (portIndex=%d) = %d\n", portIndex, ret);
+    if (ret == 0) {
+        // if the type not detected, return "common type"
+        ret = scope == kAudioDevicePropertyScopeInput ? PORT_SRC_UNKNOWN : PORT_DST_UNKNOWN;
+    }
+
+    TRACE2("<<PORT_GetPortType (portIndex=%d) = %d\n", portIndex, ret);
     return ret;
 }
 
 INT32 PORT_GetPortName(void* id, INT32 portIndex, char* name, INT32 len) {
     PortMixer *mixer = (PortMixer *)id;
-    AudioStreamID streamID = mixer->streams[portIndex];
 
+    name[0] = 0;    // for safety
+
+    if (portIndex < 0 || portIndex >= mixer->portCount) {
+        ERROR1("PORT_GetPortName: line (portIndex = %d) not found\n", portIndex);
+        return FALSE;
+    }
+
+    AudioStreamID streamID = mixer->ports[portIndex].streamID;
     CFStringRef cfname = NULL;
-    OSStatus err = noErr;
-
-    err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName,
-                                 sizeof(cfname), &cfname, 1);
-    if (err && err != kAudioHardwareUnknownPropertyError) {
-        OS_ERROR1(err, "PORT_GetPortName(stream name), portIndex=%d", portIndex);
-        return FALSE;
+    if (streamID != 0) {
+        OSStatus err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal,
+            kAudioObjectPropertyName, sizeof(cfname), &cfname, 1);
+        if (err && err != kAudioHardwareUnknownPropertyError) {
+            OS_ERROR1(err, "PORT_GetPortName(stream name), portIndex=%d", portIndex);
+            return FALSE;
+        }
     }
 
     if (!cfname) {
         // use the device's name if the stream has no name (usually the case)
-        err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName,
-                                     sizeof(cfname), &cfname, 1);
+        // or the device has several AudioStreams
+        OSStatus err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal,
+            kAudioObjectPropertyName, sizeof(cfname), &cfname, 1);
         if (err) {
             OS_ERROR1(err, "PORT_GetPortName(device name), portIndex=%d", portIndex);
             return FALSE;
@@ -248,279 +393,458 @@
         CFRelease(cfname);
     }
 
-    TRACE2("< PORT_GetPortName (portIndex = %d) = %s\n", portIndex, name);
+    TRACE2("<<PORT_GetPortName (portIndex = %d) = %s\n", portIndex, name);
     return TRUE;
 }
 
-static void CreateVolumeControl(PortControlCreator *creator, PortControl *control)
-{
-    Float32 min = 0, max = 1, precision;
-    AudioValueRange *range = &control->range;
-    UInt32 size;
 
-    control->jcontrolType = CONTROL_TYPE_VOLUME;
-
-    GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyDecibelRange,
-                           sizeof(control->range), &control->range, 1);
-    precision = 1. / (range->mMaximum - range->mMinimum);
-
-    control->jcontrol = creator->newFloatControl(creator, control, CONTROL_TYPE_VOLUME, min, max, precision, "");
+// counts number of valid (non-NULL) elements in the array of AudioControls
+static int ValidControlCount(AudioControl **arr, int offset, int len) {
+    int result = 0;
+    int end = offset + len;
+    for (int i=offset; i<end; i++) {
+        if (arr[i] != NULL)
+            result++;
+    }
+    return result;
 }
 
-static void CreateMuteControl(PortControlCreator *creator, PortControl *control)
-{
-    control->jcontrolType = CONTROL_TYPE_MUTE;
-    control->jcontrol = creator->newBooleanControl(creator, control, CONTROL_TYPE_MUTE);
+// returns java control
+static void* CreatePortControl(PortMixer *mixer, PortControlCreator *creator, PortControl::ControlType type,
+                               AudioControl **audioControls, int offset, int len) {
+    void *jControl = NULL;
+    PortControl *control = (PortControl *)calloc(1, sizeof(PortControl));
+    float precision = 0.01;
+
+    control->type = type;
+    control->controlCount = len;
+    control->audioControls = (AudioControl **)malloc(len * sizeof(AudioControl *));
+    memcpy(control->audioControls, audioControls + offset, len * sizeof(AudioControl *));
+
+    switch (control->type) {
+    case PortControl::Volume:
+        jControl = creator->newFloatControl(creator, control, CONTROL_TYPE_VOLUME, 0, 1, precision, "");
+        break;
+    case PortControl::Mute:
+        jControl = creator->newBooleanControl(creator, control, CONTROL_TYPE_MUTE);
+        break;
+    case PortControl::Balance:
+        jControl = creator->newFloatControl(creator, control, CONTROL_TYPE_BALANCE, -1, 1, precision, "");
+        break;
+    };
+
+    if (jControl == NULL) {
+        ERROR0("CreatePortControl: javaControl was not created\n");
+        free(control->audioControls);
+        free(control);
+        return NULL;
+    }
+
+    // add the control to mixer control list;
+    control->next = mixer->portControls;
+    mixer->portControls = control;
+
+    return jControl;
 }
 
 void PORT_GetControls(void* id, INT32 portIndex, PortControlCreator* creator) {
     PortMixer *mixer = (PortMixer *)id;
-    AudioStreamID streamID = mixer->streams[portIndex];
 
-    UInt32 size;
-    OSStatus err;
-    int i;
+    TRACE1(">>PORT_GetControls (portIndex = %d)\n", portIndex);
 
-    int numVolumeControls = 0, numMuteControls = 0; // not counting the master
-    int hasChannelVolume  = 0, hasChannelMute  = 0;
-    PortControl *masterVolume = NULL, *masterMute = NULL;
+    if (portIndex < 0 || portIndex >= mixer->portCount) {
+        ERROR1("<<PORT_GetControls: line (portIndex = %d) not found\n", portIndex);
+        return;
+    }
 
-    UInt32 wantedScope = portIndex < mixer->numInputStreams ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
+    PortLine *port = &(mixer->ports[portIndex]);
 
-    // initialize the device controls if this is the first stream
-    if (!mixer->numDeviceControls) {
-        // numDeviceControls / numStreamControls are overestimated
+    if (mixer->deviceControlCount < 0) {    // not initialized
+        OSStatus err;
+        UInt32 size;
+        // deviceControlCount is overestimated
         // because we don't actually filter by if the owned objects are controls
         err = GetAudioObjectPropertySize(mixer->deviceID, kAudioObjectPropertyScopeGlobal,
-                                         kAudioObjectPropertyOwnedObjects, &size);
-        mixer->numDeviceControls = size / sizeof(AudioObjectID);
+            kAudioObjectPropertyOwnedObjects, &size);
 
-        if (err == noErr && mixer->numDeviceControls) {
-            AudioObjectID controlIDs[mixer->numDeviceControls];
-            mixer->deviceControls = (PortControl *)calloc(mixer->numDeviceControls, sizeof(PortControl));
+        if (err) {
+            OS_ERROR1(err, "PORT_GetControls (portIndex = %d) get OwnedObject size", portIndex);
+        } else {
+            mixer->deviceControlCount = size / sizeof(AudioObjectID);
+            TRACE1("  PORT_GetControls: detected %d owned objects\n", mixer->deviceControlCount);
 
-            err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyOwnedObjects,
-                                         sizeof(controlIDs), &controlIDs, 1);
+            AudioObjectID controlIDs[mixer->deviceControlCount];
 
-            if (err == noErr) {
-                for (i = 0; i < mixer->numDeviceControls; i++) {
-                    PortControl *control = &mixer->deviceControls[i];
+            err = GetAudioObjectProperty(mixer->deviceID, kAudioObjectPropertyScopeGlobal,
+                kAudioObjectPropertyOwnedObjects, sizeof(controlIDs), controlIDs, 1);
 
-                    control->control = controlIDs[i];
-                    control->mixer = mixer;
+            if (err) {
+                OS_ERROR1(err, "PORT_GetControls (portIndex = %d) get OwnedObject values", portIndex);
+            } else {
+                mixer->deviceControls = (AudioControl *)calloc(mixer->deviceControlCount, sizeof(AudioControl));
 
-                    GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyClass,
-                                           sizeof(control->classID), &control->classID, 1);
-                    err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyElement,
-                                           sizeof(control->channel), &control->channel, 1);
+                for (int i = 0; i < mixer->deviceControlCount; i++) {
+                    AudioControl *control = &mixer->deviceControls[i];
 
-                    if (err) { // not a control
+                    control->controlID = controlIDs[i];
+
+                    OSStatus err1 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal,
+                        kAudioObjectPropertyClass, sizeof(control->classID), &control->classID, 1);
+                    OSStatus err2 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal,
+                        kAudioControlPropertyScope, sizeof(control->scope), &control->scope, 1);
+                    OSStatus err3 = GetAudioObjectProperty(control->controlID, kAudioObjectPropertyScopeGlobal,
+                        kAudioControlPropertyElement, sizeof(control->channel), &control->channel, 1);
+                    if (err1 || err2 || err3) { // not a control or other error
                         control->classID = 0;
                         continue;
                     }
 
-                    GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal, kAudioControlPropertyScope,
-                                           sizeof(control->scope), &control->scope, 1);
-
-                    TRACE3("%.4s control, channel %d scope %.4s\n", &control->classID, control->channel, &control->scope);
+                    TRACE4("- control 0x%x, class='%s', scope='%s', channel=%d\n",
+                        control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel);
                 }
             }
         }
-        if (err) {
-            mixer->numDeviceControls = 0;
-            if (mixer->deviceControls) {
-                free(mixer->deviceControls);
-                mixer->deviceControls = NULL;
-            }
-        }
     }
 
-    // count the number of device controls with the appropriate scope
-    if (mixer->numDeviceControls) {
-        for (i = 0; i < mixer->numDeviceControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
+    if (mixer->deviceControlCount <= 0) {
+        TRACE1("<<PORT_GetControls (portIndex = %d): no owned AudioControls\n", portIndex);
+        return;
+    }
 
-            if (control->scope != wantedScope)
-                continue;
+    int totalChannels = GetChannelCount(mixer->deviceID, port->scope == kAudioDevicePropertyScopeOutput ? 1 : 0);
 
-            switch (control->classID) {
-            case kAudioVolumeControlClassID:
-                if (control->channel == 0)
-                    masterVolume = control;
-                else {
-                    numVolumeControls++;
-                    hasChannelVolume = 1;
+    // collect volume and mute controls
+    AudioControl* volumeControls[totalChannels+1];  // 0 - for master channel
+    memset(&volumeControls, 0, sizeof(AudioControl *) * (totalChannels+1));
+    AudioControl* muteControls[totalChannels+1];  // 0 - for master channel
+    memset(&muteControls, 0, sizeof(AudioControl *) * (totalChannels+1));
+
+    for (int i=0; i<mixer->deviceControlCount; i++) {
+        AudioControl *control = &mixer->deviceControls[i];
+        if (control->classID == 0 || control->scope != port->scope || control->channel > (unsigned)totalChannels) {
+            continue;
+        }
+        if (control->classID == kAudioVolumeControlClassID) {
+            if (volumeControls[control->channel] == NULL) {
+                volumeControls[control->channel] = control;
+            } else {
+                ERROR4("WARNING: duplicate VOLUME control 0x%x, class='%s', scope='%s', channel=%d\n",
+                    control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel);
+            }
+        } else if (control->classID == kAudioMuteControlClassID) {
+            if (muteControls[control->channel] == NULL) {
+                muteControls[control->channel] = control;
+            } else {
+                ERROR4("WARNING: duplicate MUTE control 0x%x, class='%s', scope='%s', channel=%d\n",
+                    control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel);
+            }
+        } else {
+#ifdef USE_ERROR
+            if (control->classID != 0) {
+                ERROR4("WARNING: unhandled control 0x%x, class='%s', scope='%s', channel=%d\n",
+                    control->controlID, FourCC2Str(control->classID), FourCC2Str(control->scope), control->channel);
+            }
+#endif
+        }
+    }
+
+    ////////////////////////////////////////////////////////
+    // create java control hierarchy
+
+    void *masterVolume = NULL, *masterMute = NULL, *masterBalance = NULL;
+    // volumeControls[0] and muteControls[0] - master volume/mute
+    // volumeControls[n] and muteControls[n] (n=1..totalChannels) - corresponding channel controls
+    if (volumeControls[0] != NULL) {    // "master volume" AudioControl
+        masterVolume = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, 0, 1);
+    } else {
+        if (ValidControlCount(volumeControls, 1, totalChannels) == totalChannels) {
+            // every channel has volume control => create virtual master volume
+            masterVolume = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, 1, totalChannels);
+        } else {
+            TRACE2("  PORT_GetControls (master volume): totalChannels = %d, valid volume controls = %d\n",
+                totalChannels, ValidControlCount(volumeControls, 1, totalChannels));
+        }
+    }
+
+    if (muteControls[0] != NULL) {      // "master mute"
+        masterMute = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, 0, 1);
+    } else {
+        if (ValidControlCount(muteControls, 1, totalChannels) == totalChannels) {
+            // every channel has mute control => create virtual master mute control
+            masterMute = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, 1, totalChannels);
+        } else {
+            TRACE2("  PORT_GetControls (master mute): totalChannels = %d, valid volume controls = %d\n",
+                totalChannels, ValidControlCount(muteControls, 1, totalChannels));
+        }
+    }
+
+    // virtual balance
+    if (totalChannels == 2) {
+        if (ValidControlCount(volumeControls, 1, totalChannels) == totalChannels) {
+            masterBalance = CreatePortControl(mixer, creator, PortControl::Balance, volumeControls, 1, totalChannels);
+        } else {
+            TRACE2("  PORT_GetControls (naster balance): totalChannels = %d, valid volume controls = %d\n",
+                totalChannels, ValidControlCount(volumeControls, 1, totalChannels));
+        }
+    }
+
+    // add "master" controls
+    if (masterVolume != NULL) {
+        creator->addControl(creator, masterVolume);
+    }
+    if (masterBalance != NULL) {
+        creator->addControl(creator, masterBalance);
+    }
+    if (masterMute != NULL) {
+        creator->addControl(creator, masterMute);
+    }
+
+    // don't add per-channel controls for mono & stereo - they are handled by "master" controls
+    // TODO: this should be reviewed to handle controls other than mute & volume
+    if (totalChannels > 2) {
+        // add separate compound control for each channel (containing volume and mute)
+        // (ensure that we have controls)
+        if (ValidControlCount(volumeControls, 1, totalChannels) > 0 || ValidControlCount(muteControls, 1, totalChannels) > 0) {
+            for (int ch=1; ch<=totalChannels; ch++) {
+                // get the channel name
+                char *channelName;
+                CFStringRef cfname = NULL;
+                const AudioObjectPropertyAddress address = {kAudioObjectPropertyElementName, port->scope, ch};
+                UInt32 size = sizeof(cfname);
+                OSStatus err = AudioObjectGetPropertyData(mixer->deviceID, &address, 0, NULL, &size, &cfname);
+                if (err == noErr) {
+                    CFIndex length = CFStringGetLength(cfname) + 1;
+                    channelName = (char *)malloc(length);
+                    CFStringGetCString(cfname, channelName, length, kCFStringEncodingUTF8);
+                    CFRelease(cfname);
+                } else {
+                    channelName = (char *)malloc(16);
+                    sprintf(channelName, "Ch %d", ch);
                 }
-                break;
-            case kAudioMuteControlClassID:
-                if (control->channel == 0)
-                    masterMute = control;
-                else {
-                    numMuteControls++;
-                    hasChannelMute = 1;
+
+                void* jControls[2];
+                int controlCount = 0;
+                if (volumeControls[ch] != NULL) {
+                    jControls[controlCount++] = CreatePortControl(mixer, creator, PortControl::Volume, volumeControls, ch, 1);
                 }
-                break;
+                if (muteControls[ch] != NULL) {
+                    jControls[controlCount++] = CreatePortControl(mixer, creator, PortControl::Mute, muteControls, ch, 1);
+                }
+                // TODO: add any extra controls for "other" controls for the channel
+
+                void *compoundControl = creator->newCompoundControl(creator, channelName, jControls, controlCount);
+                creator->addControl(creator, compoundControl);
+
+                free(channelName);
             }
         }
     }
 
-    TRACE4("volume: channel %d master %d, mute: channel %d master %d\n", numVolumeControls, masterVolume != NULL, numMuteControls, masterMute != NULL);
+    AddChangeListeners(mixer);
 
-    if (masterVolume) {
-        if (!masterVolume->jcontrol)
-            CreateVolumeControl(creator, masterVolume);
-        creator->addControl(creator, masterVolume->jcontrol);
+    TRACE1("<<PORT_GetControls (portIndex = %d)\n", portIndex);
+}
+
+bool TestPortControlValidity(PortControl *control) {
+    for (int i=0; i<control->controlCount; i++) {
+        if (control->audioControls[i]->controlID == 0)
+            return false;
     }
+    return true;
+}
 
-    if (masterMute) {
-        if (!masterMute->jcontrol)
-            CreateMuteControl(creator, masterMute);
-        creator->addControl(creator, masterMute->jcontrol);
-    }
 
-    if (numVolumeControls) {
-        void **jControls = (void **)calloc(numVolumeControls, sizeof(void*));
-        int j = 0;
-        for (i = 0; i < mixer->numDeviceControls && j < numVolumeControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
-
-            if (control->classID != kAudioVolumeControlClassID || control->channel == 0 || control->scope != wantedScope)
-                continue;
-
-            if (!control->jcontrol)
-                CreateVolumeControl(creator, control);
-            jControls[j++] = control->jcontrol;
-        }
-
-        void *compoundControl = creator->newCompoundControl(creator, "Volume", jControls, numVolumeControls);
-        creator->addControl(creator, compoundControl);
-        free(jControls);
-    }
-
-    if (numMuteControls) {
-        void **jControls = (void **)calloc(numMuteControls, sizeof(void*));
-        int j = 0;
-        for (i = 0; i < mixer->numDeviceControls && j < numMuteControls; i++) {
-            PortControl *control = &mixer->deviceControls[i];
-
-            if (control->classID != kAudioMuteControlClassID || control->channel == 0 || control->scope != wantedScope)
-                continue;
-
-            if (!control->jcontrol)
-                CreateMuteControl(creator, control);
-            jControls[j++] = control->jcontrol;
-        }
-
-        void *compoundControl = creator->newCompoundControl(creator, "Mute", jControls, numMuteControls);
-        creator->addControl(creator, compoundControl);
-        free(jControls);
-    }
-
-    if (!mixer->numStreamControls)
-        mixer->numStreamControls = (int *)calloc(mixer->numInputStreams + mixer->numOutputStreams, sizeof(int));
-
-    err = GetAudioObjectPropertySize(streamID, kAudioObjectPropertyScopeGlobal,
-                                     kAudioObjectPropertyOwnedObjects, &size);
-    if (err != noErr) {
-        mixer->numStreamControls[portIndex] = size / sizeof(AudioObjectID);
-    }
-
-    TRACE2("< PORT_GetControls, %d controls on device, %d on stream\n", mixer->numDeviceControls, mixer->numStreamControls[portIndex]);
-}
+#define DEFAULT_MUTE_VALUE 0
 
 INT32 PORT_GetIntValue(void* controlIDV) {
     PortControl *control = (PortControl *)controlIDV;
-    UInt32 value = 0;
-    OSStatus err = 0;
-    UInt32 size;
+    INT32 result = 0;
 
-    switch (control->classID) {
-    case kAudioMuteControlClassID:
-        err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                     kAudioBooleanControlPropertyValue, sizeof(value), &value, 1);
+    switch (control->type) {
+    case PortControl::Mute:
+        if (!TestPortControlValidity(control)) {
+            return DEFAULT_MUTE_VALUE;
+        }
+        result = 1; // default is "muted", if some channel in unmuted, then "virtual mute" is also unmuted
+        for (int i=0; i<control->controlCount; i++) {
+            UInt32 value;
+            OSStatus err = GetAudioObjectProperty(control->audioControls[i]->controlID,
+                kAudioObjectPropertyScopeGlobal, kAudioBooleanControlPropertyValue, sizeof(value), &value, 1);
+            if (err) {
+                OS_ERROR3(err, "PORT_GetIntValue, control %d of %d (coltrolID = 0x%x)",
+                    i, control->controlCount, control->audioControls[i]->controlID);
+                return DEFAULT_MUTE_VALUE;
+            }
+            if (value == 0) {
+                result = 0;
+            }
+        }
         break;
     default:
-        ERROR0("PORT_GetIntValue requested for non-Int control\n");
+        ERROR1("PORT_GetIntValue requested for non-Int control (control-type == %d)\n", control->type);
         return 0;
     }
 
-    if (err) {
-        OS_ERROR0(err, "PORT_GetIntValue");
-        return 0;
-    }
-
-    TRACE1("< PORT_GetIntValue = %d\n", value);
-    return value;
+    //TRACE1("<<PORT_GetIntValue = %d\n", result);
+    return result;
 }
 
 void PORT_SetIntValue(void* controlIDV, INT32 value) {
-    TRACE1("> PORT_SetIntValue = %d\n", value);
+    //TRACE1("> PORT_SetIntValue = %d\n", value);
     PortControl *control = (PortControl *)controlIDV;
-    OSStatus err = noErr;
 
-    switch (control->classID) {
-    case kAudioMuteControlClassID:
-        err = SetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                     kAudioBooleanControlPropertyValue, sizeof(value), &value);
-        break;
-    default:
-        ERROR0("PORT_SetIntValue requested for non-Int control\n");
+    if (!TestPortControlValidity(control)) {
         return;
     }
 
-    if (err) {
-        OS_ERROR0(err, "PORT_SetIntValue");
+    switch (control->type) {
+    case PortControl::Mute:
+        for (int i=0; i<control->controlCount; i++) {
+            OSStatus err = SetAudioObjectProperty(control->audioControls[i]->controlID,
+                kAudioObjectPropertyScopeGlobal, kAudioBooleanControlPropertyValue, sizeof(value), &value);
+            if (err) {
+                OS_ERROR3(err, "PORT_SetIntValue, control %d of %d (coltrolID = 0x%x)",
+                    i, control->controlCount, control->audioControls[i]->controlID);
+                // don't return - try to set the rest of AudioControls
+            }
+        }
+        break;
+    default:
+        ERROR1("PORT_SetIntValue requested for non-Int control (control-type == %d)\n", control->type);
         return;
     }
 }
 
+
+// gets volume value for all AudioControls of the PortControl
+static bool GetPortControlVolumes(PortControl *control, Float32 *volumes, Float32 *maxVolume) {
+    *maxVolume = 0.0f;
+    for (int i=0; i<control->controlCount; i++) {
+        OSStatus err = GetAudioObjectProperty(control->audioControls[i]->controlID,
+            kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyScalarValue,
+            sizeof(volumes[i]), &volumes[i], 1);
+        if (err) {
+            OS_ERROR3(err, "GetPortControlVolumes, control %d of %d (controlID = 0x%x)",
+                i, control->controlCount, control->audioControls[i]->controlID);
+            return false;
+        }
+        if (volumes[i] > *maxVolume) {
+            *maxVolume = volumes[i];
+        }
+    }
+    return true;
+}
+
+// sets volume value for all AudioControls of the PortControl
+static void SetPortControlVolumes(PortControl *control, Float32 *volumes) {
+    for (int i=0; i<control->controlCount; i++) {
+        OSStatus err = SetAudioObjectProperty(control->audioControls[i]->controlID,
+            kAudioObjectPropertyScopeGlobal, kAudioLevelControlPropertyScalarValue,
+            sizeof(volumes[i]), &volumes[i]);
+        if (err) {
+            OS_ERROR3(err, "SetPortControlVolumes , control %d of %d (coltrolID = 0x%x)",
+                i, control->controlCount, control->audioControls[i]->controlID);
+            // don't return - try to set the rest of AudioControls
+        }
+    }
+}
+
+#define DEFAULT_VOLUME_VALUE    1.0f
+#define DEFAULT_BALANCE_VALUE   0.0f
+
 float PORT_GetFloatValue(void* controlIDV) {
     PortControl *control = (PortControl *)controlIDV;
-    Float32 value = 0;
-    OSStatus err = 0;
-    UInt32 size;
+    Float32 result = 0;
 
-    switch (control->classID) {
-    case kAudioVolumeControlClassID:
-        err = GetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                     kAudioLevelControlPropertyDecibelValue, sizeof(value), &value, 1);
-        if (err == noErr) {
-            // convert decibel to 0-1 logarithmic
-            value = (value - control->range.mMinimum) / (control->range.mMaximum - control->range.mMinimum);
+    Float32 subVolumes[control->controlCount];
+    Float32 maxVolume;
+
+    switch (control->type) {
+    case PortControl::Volume:
+        if (!TestPortControlValidity(control)) {
+            return DEFAULT_VOLUME_VALUE;
+        }
+
+        if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) {
+            return DEFAULT_VOLUME_VALUE;
+        }
+        result = maxVolume;
+        break;
+    case PortControl::Balance:
+        if (!TestPortControlValidity(control)) {
+            return DEFAULT_BALANCE_VALUE;
+        }
+
+        // balance control always has 2 volume controls
+        if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) {
+            return DEFAULT_VOLUME_VALUE;
+        }
+        // calculate balance value
+        if (subVolumes[0] > subVolumes[1]) {
+            result = -1.0f + (subVolumes[1] / subVolumes[0]);
+        } else if (subVolumes[1] > subVolumes[0]) {
+            result = 1.0f - (subVolumes[0] / subVolumes[1]);
+        } else {
+            result = 0.0f;
         }
         break;
     default:
-        ERROR0("GetFloatValue requested for non-Float control\n");
-        break;
-    }
-
-    if (err) {
-        OS_ERROR0(err, "PORT_GetFloatValue");
+        ERROR1("GetFloatValue requested for non-Float control (control-type == %d)\n", control->type);
         return 0;
     }
 
-    TRACE1("< PORT_GetFloatValue = %f\n", value);
-    return value;
+    TRACE1("<<PORT_GetFloatValue = %f\n", result);
+    return result;
 }
 
 void PORT_SetFloatValue(void* controlIDV, float value) {
     TRACE1("> PORT_SetFloatValue = %f\n", value);
     PortControl *control = (PortControl *)controlIDV;
-    OSStatus err = 0;
 
-    switch (control->classID) {
-    case kAudioVolumeControlClassID:
-        value = (value * (control->range.mMaximum - control->range.mMinimum)) + control->range.mMinimum;
-        err = SetAudioObjectProperty(control->control, kAudioObjectPropertyScopeGlobal,
-                                     kAudioLevelControlPropertyDecibelValue, sizeof(value), &value);
+    if (!TestPortControlValidity(control)) {
+        return;
+    }
+
+    Float32 subVolumes[control->controlCount];
+    Float32 maxVolume;
+
+    switch (control->type) {
+    case PortControl::Volume:
+        if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) {
+            return;
+        }
+        // update the values
+        if (maxVolume > 0.001) {
+            float multiplicator = value/maxVolume;
+            for (int i=0; i<control->controlCount; i++)
+                subVolumes[i] *= multiplicator;
+        } else {
+            // volume for all channels == 0, so set all channels to "value"
+            for (int i=0; i<control->controlCount; i++)
+                subVolumes[i] = value;
+        }
+        // set new values
+        SetPortControlVolumes(control, subVolumes);
+        break;
+    case PortControl::Balance:
+        // balance control always has 2 volume controls
+        if (!GetPortControlVolumes(control, subVolumes, &maxVolume)) {
+            return;
+        }
+        // calculate new values
+        if (value < 0.0f) {
+            subVolumes[0] = maxVolume;
+            subVolumes[1] = maxVolume * (value + 1.0f);
+        } else {
+            // this case also handles value == 0
+            subVolumes[0] = maxVolume * (1.0f - value);
+            subVolumes[1] = maxVolume;
+        }
+        // set new values
+        SetPortControlVolumes(control, subVolumes);
         break;
     default:
-        ERROR0("PORT_SetFloatValue requested for non-Float control\n");
-        break;
-    }
-
-    if (err) {
-        OS_ERROR0(err, "PORT_SetFloatValue");
+        ERROR1("PORT_SetFloatValue requested for non-Float control (control-type == %d)\n", control->type);
         return;
     }
 }
--- a/src/macosx/native/com/sun/media/sound/PLATFORM_API_MacOSX_Utils.c	Thu Jan 26 08:53:30 2012 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,222 +0,0 @@
-/*
- * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-//#define USE_TRACE
-//#define USE_ERROR
-
-#include "PLATFORM_API_MacOSX_Utils.h"
-#include "Ports.h"
-
-typedef struct OSXAudioDevice {
-    AudioDeviceID deviceID;
-    int numInputStreams;
-    int numOutputStreams;
-
-    int numInputChannels;
-    int numOutputChannels;
-    Float64 inputSampleRate;
-} OSXAudioDevice;
-
-typedef struct {
-    int numDevices;
-    OSXAudioDevice *devices;
-
-    OSXAudioDevice defaultAudioDevice;
-} AudioDeviceContext;
-
-static AudioDeviceContext deviceCtx;
-
-static int UpdateAudioDeviceInfo(OSXAudioDevice *device)
-{
-    AudioStreamBasicDescription asbd = {0};
-    AudioDeviceID deviceID, inputDeviceID;
-    int streamID, inputStreamID;
-    OSStatus err = noErr;
-    UInt32 size;
-
-    if (device->deviceID == 0) {
-        err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDefaultOutputDevice,
-                               sizeof(deviceID), &deviceID, 1);
-        if (err) goto exit;
-        err = GetAudioObjectProperty(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDefaultInputDevice,
-                               sizeof(inputDeviceID), &inputDeviceID, 1);
-        if (err) goto exit;
-    } else {
-        inputDeviceID = deviceID = device->deviceID;
-    }
-
-    err = GetAudioObjectPropertySize(inputDeviceID, kAudioDevicePropertyScopeInput, kAudioDevicePropertyStreams,
-                               &size);
-    device->numInputStreams  = size / sizeof(AudioStreamID);
-    if (err) goto exit;
-
-    err = GetAudioObjectPropertySize(deviceID, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreams,
-                               &size);
-    device->numOutputStreams = size / sizeof(AudioStreamID);
-    if (err) goto exit;
-
-    if (device->numOutputStreams) {
-        err = GetAudioObjectProperty(deviceID, kAudioDevicePropertyScopeOutput, kAudioDevicePropertyStreams,
-                                     sizeof(streamID), &streamID, 1);
-        if (err) goto exit;
-
-        if (streamID) {
-            err = GetAudioObjectProperty(streamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyVirtualFormat,
-                                         sizeof(asbd), &asbd, 1);
-            if (err) goto exit;
-
-            device->numOutputChannels = asbd.mChannelsPerFrame;
-        }
-    }
-
-    if (device->numInputStreams) {
-        err = GetAudioObjectProperty(inputDeviceID, kAudioDevicePropertyScopeInput, kAudioDevicePropertyStreams,
-                                     sizeof(inputStreamID), &inputStreamID, 1);
-        if (err) goto exit;
-
-        if (streamID) {
-            err = GetAudioObjectProperty(inputStreamID, kAudioObjectPropertyScopeGlobal, kAudioStreamPropertyVirtualFormat,
-                                         sizeof(asbd), &asbd, 1);
-            if (err) goto exit;
-
-            device->numInputChannels = asbd.mChannelsPerFrame;
-            device->inputSampleRate  = asbd.mSampleRate;
-        }
-    }
-
-    return noErr;
-
-exit:
-    ERROR1("UpdateAudioDeviceInfo err %#x\n", err);
-    return err;
-}
-
-int GetAudioDeviceCount()
-{
-    const AudioObjectPropertyAddress devicesAddress = {kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
-    UInt32 size;
-    int i;
-
-    if (!deviceCtx.numDevices) {
-        GetAudioObjectPropertySize(kAudioObjectSystemObject, kAudioObjectPropertyScopeGlobal, kAudioHardwarePropertyDevices,
-                                   &size);
-        deviceCtx.numDevices = size / sizeof(AudioDeviceID);
-
-        if (deviceCtx.numDevices) {
-            AudioDeviceID deviceIDs[deviceCtx.numDevices];
-            deviceCtx.devices = calloc(deviceCtx.numDevices, sizeof(OSXAudioDevice));
-
-            size = deviceCtx.numDevices * sizeof(AudioDeviceID);
-            AudioObjectGetPropertyData(kAudioObjectSystemObject, &devicesAddress, 0, NULL, &size, deviceIDs);
-            deviceCtx.numDevices = size / sizeof(AudioDeviceID); // in case of an unplug
-
-            for (i = 0; i < deviceCtx.numDevices; i++) {
-                OSXAudioDevice *device = &deviceCtx.devices[i];
-
-                device->deviceID = deviceIDs[i];
-                UpdateAudioDeviceInfo(device);
-            }
-        }
-
-        UpdateAudioDeviceInfo(&deviceCtx.defaultAudioDevice);
-    }
-
-    return deviceCtx.numDevices;
-}
-
-int GetAudioDeviceDescription(int index, AudioDeviceDescription *description)
-{
-    OSXAudioDevice *device;
-    CFStringRef name = NULL, vendor = NULL;
-    OSStatus err = noErr;
-    int isDefault = index == -1;
-    UInt32 size;
-
-    device = isDefault ? &deviceCtx.defaultAudioDevice : &deviceCtx.devices[index];
-
-    description->deviceID         = device->deviceID;
-    description->numInputStreams  = device->numInputStreams;
-    description->numOutputStreams = device->numOutputStreams;
-    description->numInputChannels = device->numInputChannels;
-    description->numOutputChannels= device->numOutputChannels;
-    description->inputSampleRate  = device->inputSampleRate;
-
-    if (isDefault) {
-        if (description->name)
-            strncpy(description->name, "Default Audio Device", description->strLen);
-        if (description->description)
-            strncpy(description->description, "Default Audio Device", description->strLen);
-    } else {
-        if (description->name) {
-            err = GetAudioObjectProperty(device->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyName,
-                                         sizeof(name), &name, 1);
-            if (err) goto exit;
-
-            CFStringGetCString(name, description->name, description->strLen, kCFStringEncodingUTF8);
-            if (description->description)
-                CFStringGetCString(name, description->description, description->strLen, kCFStringEncodingUTF8);
-        }
-
-        if (description->vendor) {
-            err = GetAudioObjectProperty(device->deviceID, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyManufacturer,
-                                         sizeof(vendor), &vendor, 1);
-            if (err) goto exit;
-
-            CFStringGetCString(vendor, description->vendor, description->strLen, kCFStringEncodingUTF8);
-        }
-    }
-
-exit:
-    if (err) {
-        ERROR1("GetAudioDeviceDescription error %.4s\n", &err);
-    }
-    if (name)   CFRelease(name);
-    if (vendor) CFRelease(vendor);
-
-    return err ? FALSE : TRUE;
-}
-
-OSStatus GetAudioObjectProperty(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector property, UInt32 size, void *data, int checkSize)
-{
-    const AudioObjectPropertyAddress address = {property, scope, kAudioObjectPropertyElementMaster};
-    UInt32 oldSize = size;
-    OSStatus err;
-
-    err = AudioObjectGetPropertyData(object, &address, 0, NULL, &size, data);
-
-    if (!err && checkSize && size != oldSize)
-        return kAudioHardwareBadPropertySizeError;
-    return err;
-}
-
-OSStatus GetAudioObjectPropertySize(AudioObjectID object, AudioObjectPropertyScope scope, AudioObjectPropertySelector property, UInt32 *size)
-{
-    const AudioObjectPropertyAddress address = {property, scope, kAudioObjectPropertyElementMaster};
-    OSStatus err;
-
-    err = AudioObjectGetPropertyDataSize(object, &address, 0, NULL, size);
-
-    return err;
-}