From 954e2f32b09d4cd821264e496d6e3a1677d20df8 Mon Sep 17 00:00:00 2001
From: Vincent Lucas <chenzo@jitsi.org>
Date: Sat, 28 Sep 2013 03:02:46 +0200
Subject: [PATCH] Adds stream capture and render via CoreAudio for MacOSX. This
 is a first version which needs more testing. The default audio system on
 MacOSX is still PortAudio.

---
 lib/native/mac/libjnmaccoreaudio.jnilib       |  Bin 35600 -> 74352 bytes
 src/native/build.xml                          |    2 +
 .../macosx/coreaudio/jni/maccoreaudio_util.c  |  250 ++++
 .../macosx/coreaudio/jni/maccoreaudio_util.h  |   42 +
 .../org_jitsi_impl_neomedia_CoreAudioDevice.c |   55 +-
 .../org_jitsi_impl_neomedia_CoreAudioDevice.h |   52 +-
 ...g_jitsi_impl_neomedia_MacCoreAudioDevice.c |  277 ++++
 ...g_jitsi_impl_neomedia_MacCoreAudioDevice.h |  125 ++
 src/native/macosx/coreaudio/lib/device.c      | 1314 ++++++++++++++++-
 src/native/macosx/coreaudio/lib/device.h      |   94 +-
 .../jitsi/impl/neomedia/CoreAudioDevice.java  |   24 +
 .../impl/neomedia/MacCoreAudioDevice.java     |   96 ++
 .../impl/neomedia/device/AudioSystem.java     |    6 +
 .../impl/neomedia/device/DeviceSystem.java    |    1 +
 .../neomedia/device/MacCoreaudioSystem.java   |  785 ++++++++++
 .../protocol/maccoreaudio/DataSource.java     |  251 ++++
 .../maccoreaudio/MacCoreaudioStream.java      |  440 ++++++
 .../renderer/audio/MacCoreaudioRenderer.java  |  592 ++++++++
 18 files changed, 4290 insertions(+), 116 deletions(-)
 create mode 100644 src/native/macosx/coreaudio/jni/maccoreaudio_util.c
 create mode 100644 src/native/macosx/coreaudio/jni/maccoreaudio_util.h
 create mode 100644 src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.c
 create mode 100644 src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.h
 create mode 100644 src/org/jitsi/impl/neomedia/MacCoreAudioDevice.java
 create mode 100644 src/org/jitsi/impl/neomedia/device/MacCoreaudioSystem.java
 create mode 100644 src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/DataSource.java
 create mode 100644 src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/MacCoreaudioStream.java
 create mode 100644 src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/MacCoreaudioRenderer.java

diff --git a/lib/native/mac/libjnmaccoreaudio.jnilib b/lib/native/mac/libjnmaccoreaudio.jnilib
index 36b14be8817c9e4c79dcf50bdc6e2017a6509850..5be4b22878f8b79b21dab272c8c80b913127a3f2 100755
GIT binary patch
literal 74352
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
NcImbK`KW^-{|{89J?8)b

literal 35600
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
MK6R%1YL)K)2liV)+yDRo

diff --git a/src/native/build.xml b/src/native/build.xml
index 503182eb..6e43b3c9 100644
--- a/src/native/build.xml
+++ b/src/native/build.xml
@@ -932,6 +932,8 @@
       <linkerarg value="Foundation" />
       <linkerarg value="-framework" />
       <linkerarg value="Coreaudio" />
+      <linkerarg value="-framework" />
+      <linkerarg value="AudioToolbox" />
 
       <fileset dir="${src}/native/macosx/coreaudio/lib" includes="*.c"/>
       <fileset dir="${src}/native/macosx/coreaudio/jni" includes="*.c"/>
diff --git a/src/native/macosx/coreaudio/jni/maccoreaudio_util.c b/src/native/macosx/coreaudio/jni/maccoreaudio_util.c
new file mode 100644
index 00000000..2a744131
--- /dev/null
+++ b/src/native/macosx/coreaudio/jni/maccoreaudio_util.c
@@ -0,0 +1,250 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+#include "maccoreaudio_util.h"
+
+#include "../lib/device.h"
+
+#include <string.h>
+
+/**
+ * JNI utilities.
+ *
+ * @author Vincent Lucas
+ */
+
+// Private static objects.
+
+static JavaVM * maccoreaudio_VM = NULL;
+
+static jclass maccoreaudio_devicesChangedCallbackClass = 0;
+static jmethodID maccoreaudio_devicesChangedCallbackMethodID = 0;
+
+void maccoreaudio_initHotplug(
+        void);
+void maccoreaudio_freeHotplug(
+        void);
+
+
+// Implementation
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM *vm, void *pvt)
+{
+    maccoreaudio_VM = vm;
+    maccoreaudio_initHotplug();
+    return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL
+JNI_OnUnload(JavaVM *vm, void *pvt)
+{
+    maccoreaudio_freeHotplug();
+    maccoreaudio_VM = NULL;
+}
+
+/**
+ * Gets a new <tt>jbyteArray</tt> instance which is initialized with the bytes
+ * of a specific C string i.e. <tt>const char *</tt>.
+ *
+ * @param env
+ * @param str the bytes/C string to initialize the new <tt>jbyteArray</tt>
+ * instance with
+ * @return a new <tt>jbyteArray</tt> instance which is initialized with the
+ * bytes of the specified <tt>str</tt>
+ */
+jbyteArray maccoreaudio_getStrBytes(
+        JNIEnv *env,
+        const char *str)
+{
+    jbyteArray bytes;
+
+    if (str)
+    {
+        size_t length = strlen(str);
+
+        bytes = (*env)->NewByteArray(env, length);
+        if (bytes && length)
+            (*env)->SetByteArrayRegion(env, bytes, 0, length, (jbyte *) str);
+    }
+    else
+        bytes = NULL;
+    return bytes;
+}
+
+/**
+ * Returns a callback method identifier.
+ *
+ * @param env
+ * @param callback The object called back.
+ * @param callbackFunctionName The name of the function used for the callback.
+ *
+ * @return A callback method identifier. 0 if the callback function is not
+ * found.
+ */
+jmethodID maccoreaudio_getCallbackMethodID(
+        JNIEnv *env,
+        jobject callback,
+        char* callbackFunctionName)
+{
+    jclass callbackClass;
+    jmethodID callbackMethodID = NULL;
+
+    if(callback)
+    {
+        if((callbackClass = (*env)->GetObjectClass(env, callback)))
+        {
+            callbackMethodID = (*env)->GetMethodID(
+                    env,
+                    callbackClass,
+                    callbackFunctionName,
+                    "([BI)V");
+        }
+    }
+
+    return callbackMethodID;
+}
+
+/**
+ * Calls back the java side when respectively reading / wrtiting the input
+ * /output stream.
+ */
+void maccoreaudio_callbackMethod(
+        char *buffer,
+        int bufferLength,
+        void* callback,
+        void* callbackMethod)
+{
+    JNIEnv *env = NULL;
+
+    if((*maccoreaudio_VM)->AttachCurrentThreadAsDaemon(
+                maccoreaudio_VM,
+                (void**) &env,
+                NULL)
+            == 0)
+    {
+        jbyteArray bufferBytes = (*env)->NewByteArray(env, bufferLength);
+        (*env)->SetByteArrayRegion(
+                env,
+                bufferBytes,
+                0,
+                bufferLength,
+                (jbyte *) buffer);
+
+        (*env)->CallVoidMethod(
+                env,
+                callback,
+                (jmethodID) callbackMethod,
+                bufferBytes,
+                bufferLength);
+
+        jbyte* bytes = (*env)->GetByteArrayElements(env, bufferBytes, NULL);
+        memcpy(buffer, bytes, bufferLength);
+        (*env)->ReleaseByteArrayElements(env, bufferBytes, bytes, 0);
+
+        (*maccoreaudio_VM)->DetachCurrentThread(maccoreaudio_VM);
+    }
+}
+
+/**
+ * Calls back the java side when the device list has changed.
+ */
+void maccoreaudio_devicesChangedCallbackMethod(void)
+{
+    JNIEnv *env = NULL;
+
+    if((*maccoreaudio_VM)->AttachCurrentThreadAsDaemon(
+                maccoreaudio_VM,
+                (void**) &env,
+                NULL)
+            == 0)
+    {
+        jclass class = maccoreaudio_devicesChangedCallbackClass;
+        jmethodID methodID = maccoreaudio_devicesChangedCallbackMethodID;
+        if(class && methodID)
+        {
+            (*env)->CallStaticVoidMethod(env, class, methodID);
+        }
+
+        (*maccoreaudio_VM)->DetachCurrentThread(maccoreaudio_VM);
+    }
+}
+
+/**
+ * Initializes the hotplug callback process.
+ */
+void maccoreaudio_initHotplug(
+        void)
+{
+    JNIEnv *env = NULL;
+
+    if((*maccoreaudio_VM)->AttachCurrentThreadAsDaemon(
+                maccoreaudio_VM,
+                (void**) &env,
+                NULL)
+            == 0)
+    {
+        if(maccoreaudio_devicesChangedCallbackClass == NULL
+                && maccoreaudio_devicesChangedCallbackMethodID == NULL)
+        {
+            jclass devicesChangedCallbackClass = (*env)->FindClass(
+                    env,
+                    "org/jitsi/impl/neomedia/CoreAudioDevice");
+
+            if (devicesChangedCallbackClass)
+            {
+                devicesChangedCallbackClass
+                    = (*env)->NewGlobalRef(env, devicesChangedCallbackClass);
+                if (devicesChangedCallbackClass)
+                {
+                    jmethodID devicesChangedCallbackMethodID
+                        = (*env)->GetStaticMethodID(
+                                env,
+                                devicesChangedCallbackClass,
+                                "devicesChangedCallback",
+                                "()V");
+
+                    if (devicesChangedCallbackMethodID)
+                    {
+                        maccoreaudio_devicesChangedCallbackClass
+                            = devicesChangedCallbackClass;
+                        maccoreaudio_devicesChangedCallbackMethodID
+                            = devicesChangedCallbackMethodID;
+
+                        maccoreaudio_initializeHotplug(
+                                maccoreaudio_devicesChangedCallbackMethod);
+                    }
+                }
+            }
+        }
+        (*maccoreaudio_VM)->DetachCurrentThread(maccoreaudio_VM);
+    }
+}
+
+/**
+ * Frees the hotplug callback process.
+ */
+void maccoreaudio_freeHotplug(
+        void)
+{
+    maccoreaudio_uninitializeHotplug();
+    JNIEnv *env = NULL;
+
+    if((*maccoreaudio_VM)->AttachCurrentThreadAsDaemon(
+                maccoreaudio_VM,
+                (void**) &env,
+                NULL)
+            == 0)
+    {
+        (*env)->DeleteGlobalRef(
+                env,
+                maccoreaudio_devicesChangedCallbackClass);
+        (*maccoreaudio_VM)->DetachCurrentThread(maccoreaudio_VM);
+    }
+    maccoreaudio_devicesChangedCallbackClass = NULL;
+    maccoreaudio_devicesChangedCallbackMethodID = NULL;
+}
diff --git a/src/native/macosx/coreaudio/jni/maccoreaudio_util.h b/src/native/macosx/coreaudio/jni/maccoreaudio_util.h
new file mode 100644
index 00000000..9f8b5cb9
--- /dev/null
+++ b/src/native/macosx/coreaudio/jni/maccoreaudio_util.h
@@ -0,0 +1,42 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+#ifndef __maccoreaudio_util_h
+#define __maccoreaudio_util_h
+
+#include <jni.h>
+
+/**
+ * JNI utilities.
+ *
+ * @author Vincent Lucas
+ */
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM *vm, void *pvt);
+
+JNIEXPORT void JNICALL
+JNI_OnUnload(JavaVM *vm, void *pvt);
+
+jbyteArray maccoreaudio_getStrBytes(
+        JNIEnv *env,
+        const char *str);
+
+jmethodID maccoreaudio_getCallbackMethodID(
+        JNIEnv *env,
+        jobject callback,
+        char* callbackFunctionName);
+
+void maccoreaudio_callbackMethod(
+        char *buffer,
+        int bufferLength,
+        void* callback,
+        void* callbackMethod);
+
+void maccoreaudio_devicesChangedCallbackMethod(
+        void);
+
+#endif
diff --git a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.c b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.c
index 7ad57402..38022b13 100644
--- a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.c
+++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.c
@@ -8,31 +8,28 @@
 #include "org_jitsi_impl_neomedia_CoreAudioDevice.h"
 
 #include "../lib/device.h"
+#include "maccoreaudio_util.h"
 
 /**
  * JNI code for CoreAudioDevice.
  *
- * @author Vicnent Lucas
+ * @author Vincent Lucas
  */
 
-// Private functions
-
-static jbyteArray getStrBytes(JNIEnv *env, const char *str);
-
 // Implementation
 
 JNIEXPORT jint JNICALL
 Java_org_jitsi_impl_neomedia_CoreAudioDevice_initDevices
   (JNIEnv *env, jclass clazz)
 {
-    return initDevices();
+    return maccoreaudio_initDevices();
 }
 
 JNIEXPORT void JNICALL
 Java_org_jitsi_impl_neomedia_CoreAudioDevice_freeDevices
   (JNIEnv *env, jclass clazz)
 {
-    freeDevices();
+    maccoreaudio_freeDevices();
 }
 
 JNIEXPORT jbyteArray JNICALL
@@ -40,8 +37,8 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_getDeviceNameBytes
   (JNIEnv *env, jclass clazz, jstring deviceUID)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    char * deviceName = getDeviceName(deviceUIDPtr);
-    jbyteArray deviceNameBytes = getStrBytes(env, deviceName);
+    char * deviceName = maccoreaudio_getDeviceName(deviceUIDPtr);
+    jbyteArray deviceNameBytes = maccoreaudio_getStrBytes(env, deviceName);
     // Free
     free(deviceName);
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
@@ -54,9 +51,10 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_getDeviceModelIdentifierBytes
   (JNIEnv *env, jclass clazz, jstring deviceUID)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    char * deviceModelIdentifier = getDeviceModelIdentifier(deviceUIDPtr);
+    char * deviceModelIdentifier
+        = maccoreaudio_getDeviceModelIdentifier(deviceUIDPtr);
     jbyteArray deviceModelIdentifierBytes
-        = getStrBytes(env, deviceModelIdentifier);
+        = maccoreaudio_getStrBytes(env, deviceModelIdentifier);
     // Free
     free(deviceModelIdentifier);
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
@@ -69,7 +67,7 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_setInputDeviceVolume
   (JNIEnv *env, jclass clazz, jstring deviceUID, jfloat volume)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    jint err = setInputDeviceVolume(deviceUIDPtr, volume);
+    jint err = maccoreaudio_setInputDeviceVolume(deviceUIDPtr, volume);
     // Free
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
 
@@ -81,7 +79,7 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_setOutputDeviceVolume
   (JNIEnv *env, jclass clazz, jstring deviceUID, jfloat volume)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    jint err = setOutputDeviceVolume(deviceUIDPtr, volume);
+    jint err = maccoreaudio_setOutputDeviceVolume(deviceUIDPtr, volume);
     // Free
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
 
@@ -93,7 +91,7 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_getInputDeviceVolume
   (JNIEnv *env, jclass clazz, jstring deviceUID)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    jfloat volume = getInputDeviceVolume(deviceUIDPtr);
+    jfloat volume = maccoreaudio_getInputDeviceVolume(deviceUIDPtr);
     // Free
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
 
@@ -105,36 +103,9 @@ Java_org_jitsi_impl_neomedia_CoreAudioDevice_getOutputDeviceVolume
   (JNIEnv *env, jclass clazz, jstring deviceUID)
 {
     const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
-    jfloat volume = getOutputDeviceVolume(deviceUIDPtr);
+    jfloat volume = maccoreaudio_getOutputDeviceVolume(deviceUIDPtr);
     // Free
     (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
 
     return volume;
 }
-
-/**
- * Gets a new <tt>jbyteArray</tt> instance which is initialized with the bytes
- * of a specific C string i.e. <tt>const char *</tt>.
- *
- * @param env
- * @param str the bytes/C string to initialize the new <tt>jbyteArray</tt>
- * instance with
- * @return a new <tt>jbyteArray</tt> instance which is initialized with the
- * bytes of the specified <tt>str</tt>
- */
-static jbyteArray getStrBytes(JNIEnv *env, const char *str)
-{
-    jbyteArray bytes;
-
-    if (str)
-    {
-        size_t length = strlen(str);
-
-        bytes = (*env)->NewByteArray(env, length);
-        if (bytes && length)
-            (*env)->SetByteArrayRegion(env, bytes, 0, length, (jbyte *) str);
-    }
-    else
-        bytes = NULL;
-    return bytes;
-}
diff --git a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.h b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.h
index c02caef4..df7d7a74 100644
--- a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.h
+++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_CoreAudioDevice.h
@@ -9,19 +9,19 @@ extern "C" {
 #endif
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    initDevices
- * Signature: ()I
+ * Method:    freeDevices
+ * Signature: ()V
  */
-JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_initDevices
+JNIEXPORT void JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_freeDevices
   (JNIEnv *, jclass);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    freeDevices
- * Signature: ()V
+ * Method:    getDeviceModelIdentifierBytes
+ * Signature: (Ljava/lang/String;)[B
  */
-JNIEXPORT void JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_freeDevices
-  (JNIEnv *, jclass);
+JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getDeviceModelIdentifierBytes
+  (JNIEnv *, jclass, jstring);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
@@ -33,43 +33,43 @@ JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getDev
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    getDeviceModelIdentifierBytes
- * Signature: (Ljava/lang/String;)[B
+ * Method:    getInputDeviceVolume
+ * Signature: (Ljava/lang/String;)F
  */
-JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getDeviceModelIdentifierBytes
+JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getInputDeviceVolume
   (JNIEnv *, jclass, jstring);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    setInputDeviceVolume
- * Signature: (Ljava/lang/String;F)I
+ * Method:    getOutputDeviceVolume
+ * Signature: (Ljava/lang/String;)F
  */
-JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_setInputDeviceVolume
-  (JNIEnv *, jclass, jstring, jfloat);
+JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getOutputDeviceVolume
+  (JNIEnv *, jclass, jstring);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    setOutputDeviceVolume
- * Signature: (Ljava/lang/String;F)I
+ * Method:    initDevices
+ * Signature: ()I
  */
-JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_setOutputDeviceVolume
-  (JNIEnv *, jclass, jstring, jfloat);
+JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_initDevices
+  (JNIEnv *, jclass);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    getInputDeviceVolume
- * Signature: (Ljava/lang/String;)F
+ * Method:    setInputDeviceVolume
+ * Signature: (Ljava/lang/String;F)I
  */
-JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getInputDeviceVolume
-  (JNIEnv *, jclass, jstring);
+JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_setInputDeviceVolume
+  (JNIEnv *, jclass, jstring, jfloat);
 
 /*
  * Class:     org_jitsi_impl_neomedia_CoreAudioDevice
- * Method:    getOutputDeviceVolume
- * Signature: (Ljava/lang/String;)F
+ * Method:    setOutputDeviceVolume
+ * Signature: (Ljava/lang/String;F)I
  */
-JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_getOutputDeviceVolume
-  (JNIEnv *, jclass, jstring);
+JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_CoreAudioDevice_setOutputDeviceVolume
+  (JNIEnv *, jclass, jstring, jfloat);
 
 #ifdef __cplusplus
 }
diff --git a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.c b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.c
new file mode 100644
index 00000000..5ec04cd9
--- /dev/null
+++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.c
@@ -0,0 +1,277 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+#include "org_jitsi_impl_neomedia_MacCoreAudioDevice.h"
+
+#include "../lib/device.h"
+#include "maccoreaudio_util.h"
+
+/**
+ * JNI code for CoreAudioDevice.
+ *
+ * @author Vicnent Lucas
+ */
+
+// Implementation
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDeviceUIDList
+  (JNIEnv *env, jclass clazz)
+{
+    jobjectArray javaDeviceUIDList = NULL;
+
+    char ** deviceUIDList;
+    int nbDevices = maccoreaudio_getDeviceUIDList(&deviceUIDList);
+    if(nbDevices != -1)
+    {
+        jstring deviceUID;
+        jclass stringClass = (*env)->FindClass(env, "java/lang/String");
+        javaDeviceUIDList
+            = (*env)->NewObjectArray(env, nbDevices, stringClass, NULL);
+        int i;
+        for(i = 0; i < nbDevices; ++i)
+        {
+            deviceUID = (*env)->NewStringUTF(env, deviceUIDList[i]);
+            if(deviceUID != NULL)
+            {
+                (*env)->SetObjectArrayElement(
+                        env,
+                        javaDeviceUIDList,
+                        i,
+                        deviceUID);
+            }
+
+            free(deviceUIDList[i]);
+        }
+
+        free(deviceUIDList);
+    }
+
+    return javaDeviceUIDList;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_isInputDevice
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jint isInputDevice = maccoreaudio_isInputDevice(deviceUIDPtr);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return (isInputDevice != 0);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_isOutputDevice
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jint isOutputDevice = maccoreaudio_isOutputDevice(deviceUIDPtr);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return (isOutputDevice != 0);
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getTransportTypeBytes
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    const char * transportType = maccoreaudio_getTransportType(deviceUIDPtr);
+    jbyteArray transportTypeBytes
+        = maccoreaudio_getStrBytes(env, transportType);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return transportTypeBytes;
+}
+
+JNIEXPORT jfloat JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getNominalSampleRate
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jfloat rate = maccoreaudio_getNominalSampleRate(deviceUIDPtr);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return rate;
+}
+
+JNIEXPORT jfloat JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getMinimalNominalSampleRate
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    Float64 minRate;
+    Float64 maxRate;
+    if(maccoreaudio_getAvailableNominalSampleRates(
+                deviceUIDPtr,
+                &minRate,
+                &maxRate)
+            != noErr)
+    {
+        fprintf(stderr,
+                "MacCoreAudioDevice_getMinimalNominalSampleRate\
+                    \n\tmaccoreaudio_getAvailableNominalSampleRates\n");
+        fflush(stderr);
+        return -1.0;
+    }
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return minRate;
+}
+
+JNIEXPORT jfloat JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getMaximalNominalSampleRate
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    Float64 minRate;
+    Float64 maxRate;
+    if(maccoreaudio_getAvailableNominalSampleRates(
+                deviceUIDPtr,
+                &minRate,
+                &maxRate)
+            != noErr)
+    {
+        fprintf(stderr,
+                "MacCoreAudioDevice_getMaximalNominalSampleRate\
+                    \n\tmaccoreaudio_getAvailableNominalSampleRates\n");
+        fflush(stderr);
+        return -1.0;
+    }
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return maxRate;
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDefaultInputDeviceUIDBytes
+  (JNIEnv *env, jclass clazz)
+{
+    char* defaultInputDeviceUID = maccoreaudio_getDefaultInputDeviceUID();
+    jbyteArray defaultInputDeviceUIDBytes
+        = maccoreaudio_getStrBytes(env, defaultInputDeviceUID);
+    // Free
+    free(defaultInputDeviceUID);
+
+    return defaultInputDeviceUIDBytes;
+}
+
+JNIEXPORT jbyteArray JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDefaultOutputDeviceUIDBytes
+  (JNIEnv *env, jclass clazz)
+{
+    char* defaultOutputDeviceUID
+        = maccoreaudio_getDefaultOutputDeviceUID();
+    jbyteArray defaultOutputDeviceUIDBytes
+        = maccoreaudio_getStrBytes(env, defaultOutputDeviceUID);
+    // Free
+    free(defaultOutputDeviceUID);
+
+    return defaultOutputDeviceUIDBytes;
+}
+
+JNIEXPORT jlong JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_startStream
+  (JNIEnv *env, jclass clazz, jstring deviceUID, jobject callback,
+        jfloat sampleRate,
+        jint nbChannels,
+        jint bitsPerChannel,
+        jboolean isFloat,
+        jboolean isBigEndian,
+        jboolean isNonInterleaved)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jobject callbackObject = (*env)->NewGlobalRef(env, callback);
+    maccoreaudio_stream* stream = NULL;
+
+    if(maccoreaudio_isInputDevice(deviceUIDPtr)) // input
+    {
+        jmethodID callbackMethod = maccoreaudio_getCallbackMethodID(
+                env,
+                callback,
+                "readInput");
+        stream = maccoreaudio_startInputStream(
+                deviceUIDPtr,
+                (void*) maccoreaudio_callbackMethod,
+                callbackObject,
+                callbackMethod,
+                sampleRate,
+                nbChannels,
+                bitsPerChannel,
+                isFloat,
+                isBigEndian,
+                isNonInterleaved);
+    }
+    else if(maccoreaudio_isOutputDevice(deviceUIDPtr)) // output
+    {
+        jmethodID callbackMethod = maccoreaudio_getCallbackMethodID(
+                env,
+                callback,
+                "writeOutput");
+        stream = maccoreaudio_startOutputStream(
+                deviceUIDPtr,
+                (void*) maccoreaudio_callbackMethod,
+                callbackObject,
+                callbackMethod,
+                sampleRate,
+                nbChannels,
+                bitsPerChannel,
+                isFloat,
+                isBigEndian,
+                isNonInterleaved);
+    }
+
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return (long) stream;
+}
+
+JNIEXPORT void JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_stopStream
+  (JNIEnv *env, jclass clazz, jstring deviceUID, jlong streamPtr)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    maccoreaudio_stream * stream = (maccoreaudio_stream*) (long) streamPtr;
+
+    maccoreaudio_stopStream(deviceUIDPtr, stream);
+
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+    (*env)->DeleteGlobalRef(env, stream->callbackObject);
+}
+
+JNIEXPORT jint JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_countInputChannels
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jint nbChannels = maccoreaudio_countInputChannels(deviceUIDPtr);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return nbChannels;
+}
+
+JNIEXPORT jint JNICALL
+Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_countOutputChannels
+  (JNIEnv *env, jclass clazz, jstring deviceUID)
+{
+    const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0);
+    jint nbChannels = maccoreaudio_countOutputChannels(deviceUIDPtr);
+    // Free
+    (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr);
+
+    return nbChannels;
+}
diff --git a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.h b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.h
new file mode 100644
index 00000000..729cbe1d
--- /dev/null
+++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_MacCoreAudioDevice.h
@@ -0,0 +1,125 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class org_jitsi_impl_neomedia_MacCoreAudioDevice */
+
+#ifndef _Included_org_jitsi_impl_neomedia_MacCoreAudioDevice
+#define _Included_org_jitsi_impl_neomedia_MacCoreAudioDevice
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef org_jitsi_impl_neomedia_MacCoreAudioDevice_DEFAULT_SAMPLE_RATE
+#define org_jitsi_impl_neomedia_MacCoreAudioDevice_DEFAULT_SAMPLE_RATE 44100.0
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getDeviceUIDList
+ * Signature: ()[Ljava/lang/String;
+ */
+JNIEXPORT jobjectArray JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDeviceUIDList
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    isInputDevice
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_isInputDevice
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    isOutputDevice
+ * Signature: (Ljava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_isOutputDevice
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getTransportTypeBytes
+ * Signature: (Ljava/lang/String;)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getTransportTypeBytes
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getNominalSampleRate
+ * Signature: (Ljava/lang/String;)F
+ */
+JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getNominalSampleRate
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getMinimalNominalSampleRate
+ * Signature: (Ljava/lang/String;)F
+ */
+JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getMinimalNominalSampleRate
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getMaximalNominalSampleRate
+ * Signature: (Ljava/lang/String;)F
+ */
+JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getMaximalNominalSampleRate
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getDefaultInputDeviceUIDBytes
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDefaultInputDeviceUIDBytes
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    getDefaultOutputDeviceUIDBytes
+ * Signature: ()[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_getDefaultOutputDeviceUIDBytes
+  (JNIEnv *, jclass);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    startStream
+ * Signature: (Ljava/lang/String;Ljava/lang/Object;)J
+ */
+JNIEXPORT jlong JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_startStream
+  (JNIEnv *, jclass, jstring, jobject,
+        jfloat sampleRate,
+        jint nbChannels,
+        jint bitsPerChannel,
+        jboolean isFloat,
+        jboolean isBigEndian,
+        jboolean isNonInterleaved);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    stopStream
+ * Signature: (Ljava/lang/String;J)V
+ */
+JNIEXPORT void JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_stopStream
+  (JNIEnv *, jclass, jstring, jlong);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    countInputChannels
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_countInputChannels
+  (JNIEnv *, jclass, jstring);
+
+/*
+ * Class:     org_jitsi_impl_neomedia_MacCoreAudioDevice
+ * Method:    countOutputChannels
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_MacCoreAudioDevice_countOutputChannels
+  (JNIEnv *, jclass, jstring);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/native/macosx/coreaudio/lib/device.c b/src/native/macosx/coreaudio/lib/device.c
index 0894ae2a..d517c2f3 100644
--- a/src/native/macosx/coreaudio/lib/device.c
+++ b/src/native/macosx/coreaudio/lib/device.c
@@ -9,39 +9,139 @@
 #include <CoreAudio/CoreAudio.h>
 #include <CoreFoundation/CFString.h>
 #include <stdio.h>
+
 /**
  * Functions to list, access and modifies audio devices via coreaudio.
  *
  * @author Vincent Lucas
  */
+const char* transportTypeAggregate = "Aggregate";
+const char* transportTypeAirPlay = "AirPlay";
+const char* transportTypeAutoAggregate = "Auto aggregate";
+const char* transportTypeAVB = "AVB";
+const char* transportTypeBlueTooth = "Bluetooth";
+const char* transportTypeBuiltIn = "Built-in";
+const char* transportTypeDisplayPort = "DisplayPort";
+const char* transportTypeFireWire = "FireWire";
+const char* transportTypeHDMI = "HDMI";
+const char* transportTypePCI = "PCI";
+const char* transportTypeThunderbolt = "Thunderbolt";
+const char* transportTypeUnknown = "Unknown";
+const char* transportTypeUSB = "USB";
+const char* transportTypeVirtual = "Virtual";
 
 /**
  * Private definition of functions,
  */
-char* getDeviceProperty(
+
+AudioDeviceID maccoreaudio_getDevice(
+        const char * deviceUID);
+
+AudioDeviceID maccoreaudio_getDeviceForSpecificScope(
+        const char * deviceUID,
+        UInt32 inputOutputScope);
+
+char* maccoreaudio_getDeviceProperty(
         const char * deviceUID,
         AudioObjectPropertySelector propertySelector);
 
-OSStatus setDeviceVolume(
+char* maccoreaudio_getAudioDeviceProperty(
+        AudioDeviceID device,
+        AudioObjectPropertySelector propertySelector);
+
+OSStatus maccoreaudio_setDeviceVolume(
         const char * deviceUID,
         Float32 volume,
         UInt32 inputOutputScope);
 
-Float32 getDeviceVolume(
+Float32 maccoreaudio_getDeviceVolume(
         const char * deviceUID,
         UInt32 inputOutputScope);
 
-OSStatus getChannelsForStereo(
+OSStatus maccoreaudio_getChannelsForStereo(
         const char * deviceUID,
         UInt32 * channels);
 
+int maccoreaudio_countChannels(
+        const char * deviceUID,
+        AudioObjectPropertyScope inputOutputScope);
+
+static OSStatus maccoreaudio_devicesChangedCallback(
+        AudioObjectID inObjectID,
+        UInt32 inNumberAddresses,
+        const AudioObjectPropertyAddress inAddresses[],
+        void *inClientData);
+
+OSStatus maccoreaudio_initConverter(
+        const char * deviceUID,
+        const AudioStreamBasicDescription * javaFormat,
+        unsigned char isJavaFormatSource,
+        AudioConverterRef * converter,
+        double * conversionRatio);
+
+inline UInt32 CalculateLPCMFlags (
+        UInt32 inValidBitsPerChannel,
+        UInt32 inTotalBitsPerChannel,
+        bool inIsFloat,
+        bool inIsBigEndian,
+        bool inIsNonInterleaved);
+
+inline void FillOutASBDForLPCM(
+        AudioStreamBasicDescription * outASBD,
+        Float64 inSampleRate,
+        UInt32 inChannelsPerFrame,
+        UInt32 inValidBitsPerChannel,
+        UInt32 inTotalBitsPerChannel,
+        bool inIsFloat,
+        bool inIsBigEndian,
+        bool inIsNonInterleaved);
+
+char* maccoreaudio_getDefaultDeviceUID(
+        UInt32 inputOutputScope);
+
+maccoreaudio_stream * maccoreaudio_startStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        void* readWriteFunction,
+        unsigned char isJavaFormatSource,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved);
+
+OSStatus maccoreaudio_readInputStream(
+        AudioDeviceID inDevice,
+        const AudioTimeStamp* inNow,
+        const AudioBufferList* inInputData,
+        const AudioTimeStamp* inInputTime,
+        AudioBufferList* outOutputData,
+        const AudioTimeStamp* inOutputTime,
+        void* inClientData);
+
+OSStatus maccoreaudio_writeOutputStream(
+        AudioDeviceID inDevice,
+        const AudioTimeStamp* inNow,
+        const AudioBufferList* inInputData,
+        const AudioTimeStamp* inInputTime,
+        AudioBufferList* outOutputData,
+        const AudioTimeStamp* inOutputTime,
+        void* inClientData);
+
+OSStatus maccoreaudio_getStreamVirtualFormat(
+        AudioStreamID stream,
+        AudioStreamBasicDescription * format);
+
 /**
  * Do nothing: there is no need to initializes anything to get device
  * information on MacOsX.
  *
  * @return Always returns 0 (always works).
  */
-int initDevices(void)
+int maccoreaudio_initDevices(void)
 {
     return 0;
 }
@@ -50,22 +150,75 @@ int initDevices(void)
  * Do nothing: there is no need to frees anything once getting device
  * information is finished on MacOsX.
  */
-void freeDevices(void)
+void maccoreaudio_freeDevices(void)
 {
     // Nothing to do.
 }
 
+/**
+ * Returns if the audio device is an input device.
+ *
+ * @param deviceUID The device UID.
+ *
+ * @return True if the given device identifier correspond to an input device.
+ * False otherwise.
+ */
+int maccoreaudio_isInputDevice(
+        const char * deviceUID)
+{
+    return (maccoreaudio_countChannels(
+                deviceUID,
+                kAudioDevicePropertyScopeInput) > 0);
+}
+
+/**
+ * Returns if the audio device is an output device.
+ *
+ * @param deviceUID The device UID.
+ *
+ * @return True if the given device identifier correspond to an output device.
+ * False otherwise.
+ */
+int maccoreaudio_isOutputDevice(
+        const char * deviceUID)
+{
+    return (maccoreaudio_countChannels(
+                deviceUID,
+                kAudioDevicePropertyScopeOutput) > 0);
+}
+
 /**
  * Returns the audio device corresponding to the UID given in parameter. Or
  * kAudioObjectUnknown if the device is nonexistant or if anything as failed.
  *
- * @pqrqm deviceUID The device UID.
+ * @param deviceUID The device UID.
  *
  * @return The audio device corresponding to the UID given in parameter. Or
  * kAudioObjectUnknown if the device is nonexistant or if anything as failed.
  */
-AudioDeviceID getDevice(
+AudioDeviceID maccoreaudio_getDevice(
         const char * deviceUID)
+{
+    return maccoreaudio_getDeviceForSpecificScope(
+            deviceUID,
+            kAudioObjectPropertyScopeGlobal);
+}
+
+/**
+ * Returns the audio device corresponding to the UID given in parameter for the
+ * specified scope (global, input or output). Or kAudioObjectUnknown if the
+ * device is nonexistant or if anything as failed.
+ *
+ * @param deviceUID The device UID.
+ * @param inputOutputScope The scope to tell if this is an output or an input
+ * device.
+ *
+ * @return The audio device corresponding to the UID given in parameter. Or
+ * kAudioObjectUnknown if the device is nonexistant or if anything as failed.
+ */
+AudioDeviceID maccoreaudio_getDeviceForSpecificScope(
+        const char * deviceUID,
+        UInt32 inputOutputScope)
 {
     OSStatus err = noErr;
     AudioObjectPropertyAddress address;
@@ -93,7 +246,7 @@ AudioDeviceID getDevice(
     translation.mOutputDataSize = sizeof(device);
     size = sizeof(translation);
     address.mSelector = kAudioHardwarePropertyDeviceForUID;
-    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mScope = inputOutputScope;
     address.mElement = kAudioObjectPropertyElementMaster;
 
     if((err = AudioObjectGetPropertyData(
@@ -117,6 +270,84 @@ AudioDeviceID getDevice(
     return device;
 }
 
+/**
+ * Returns the default input device UID.
+ *
+ * @return The default input device UID. NULL if an error occurs.
+ */
+char* maccoreaudio_getDefaultInputDeviceUID(void)
+{
+    return maccoreaudio_getDefaultDeviceUID(kAudioDevicePropertyScopeInput);
+}
+
+/**
+ * Returns the default output device UID.
+ *
+ * @return The default output device UID. NULL if an error occurs.
+ */
+char* maccoreaudio_getDefaultOutputDeviceUID(void)
+{
+    return maccoreaudio_getDefaultDeviceUID(kAudioDevicePropertyScopeOutput);
+}
+
+/**
+ * Returns the default device UID for input or ouput.
+ *
+ * @param inputOutputScope The scope to tell if this is an output or an input
+ * device.
+ *
+ * @return The default device UID for input or ouput. NULL if an error occurs.
+ */
+char* maccoreaudio_getDefaultDeviceUID(
+        UInt32 inputOutputScope)
+{
+    OSStatus err = noErr;
+    AudioDeviceID device;
+    UInt32 size = sizeof(AudioDeviceID);
+    AudioObjectPropertyAddress address;
+    char * deviceUID = NULL;
+
+    if(inputOutputScope == kAudioDevicePropertyScopeInput)
+    {
+        address.mSelector = kAudioHardwarePropertyDefaultInputDevice;
+    }
+    else
+    {
+        address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
+    }
+    address.mScope = inputOutputScope;
+    address.mElement = kAudioObjectPropertyElementMaster;
+
+    if((err = AudioObjectGetPropertyData(
+                    kAudioObjectSystemObject,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    &device))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_getDefaultDeviceUID (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyData, err: %d\n",
+                    ((int) err));
+        return NULL;
+    }
+
+    if((deviceUID = maccoreaudio_getAudioDeviceProperty(
+                    device,
+                    kAudioDevicePropertyDeviceUID))
+            == NULL)
+    {
+        fprintf(stderr,
+                "maccoreaudio_getDefaultDeviceUID (coreaudio/device.c): \
+                    \n\tgetAudioDeviceProperty\n");
+        return NULL;
+    }
+
+    return deviceUID;
+}
+
 /**
  * Returns the device name for the given device. Or NULL, if not available. The
  * returned string must be freed by the caller.
@@ -126,10 +357,10 @@ AudioDeviceID getDevice(
  * @return The device name for the given device. Or NULL, if not available. The
  * returned string must be freed by the caller.
  */
-char* getDeviceName(
+char* maccoreaudio_getDeviceName(
         const char * deviceUID)
 {
-    return getDeviceProperty(deviceUID, kAudioObjectPropertyName);
+    return maccoreaudio_getDeviceProperty(deviceUID, kAudioObjectPropertyName);
 }
 
 /**
@@ -141,33 +372,31 @@ char* getDeviceName(
  * @return The device model identifier for the given device. Or NULL, if not
  * available. The returned string must be freed by the caller.
  */
-char* getDeviceModelIdentifier(
+char* maccoreaudio_getDeviceModelIdentifier(
         const char * deviceUID)
 {
-    return getDeviceProperty(deviceUID, kAudioDevicePropertyModelUID);
+    return
+        maccoreaudio_getDeviceProperty(deviceUID, kAudioDevicePropertyModelUID);
 }
 
 /**
- * Returns the requested device property for the given device. Or NULL, if not
- * available. The returned string must be freed by the caller.
+ * Returns the requested device property for the given device UID. Or NULL, if
+ * not available. The returned string must be freed by the caller.
  *
- * @param device The device to get the name from.
+ * @param deviceUID The device identifier to get the property from.
  * @param propertySelector The property we want to retrieve.
  *
- * @return The requested device property for the given device. Or NULL, if not
- * available. The returned string must be freed by the caller.
+ * @return The requested device property for the given device UID. Or NULL, if
+ * not available. The returned string must be freed by the caller.
  */
-char* getDeviceProperty(
+char* maccoreaudio_getDeviceProperty(
         const char * deviceUID,
         AudioObjectPropertySelector propertySelector)
 {
     AudioDeviceID device;
-    OSStatus err = noErr;
-    AudioObjectPropertyAddress address;
-    UInt32 size;
 
     // Gets the correspoding device
-    if((device = getDevice(deviceUID)) == kAudioObjectUnknown)
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
     {
         fprintf(stderr,
                 "getDeviceProperty (coreaudio/device.c): \
@@ -175,6 +404,27 @@ char* getDeviceProperty(
         return NULL;
     }
 
+    return maccoreaudio_getAudioDeviceProperty(device, propertySelector);
+}
+
+/**
+ * Returns the requested device property for the given device. Or NULL, if not
+ * available. The returned string must be freed by the caller.
+ *
+ * @param device The device to get the name from.
+ * @param propertySelector The property we want to retrieve.
+ *
+ * @return The requested device property for the given device. Or NULL, if not
+ * available. The returned string must be freed by the caller.
+ */
+char* maccoreaudio_getAudioDeviceProperty(
+        AudioDeviceID device,
+        AudioObjectPropertySelector propertySelector)
+{
+    OSStatus err = noErr;
+    AudioObjectPropertyAddress address;
+    UInt32 size;
+
     // Gets the device property
     CFStringRef deviceProperty;
     size = sizeof(deviceProperty);
@@ -230,11 +480,11 @@ char* getDeviceProperty(
  * @return noErr if everything works well. Another value if an error has
  * occured.  
  */
-OSStatus setInputDeviceVolume(
+OSStatus maccoreaudio_setInputDeviceVolume(
         const char * deviceUID,
         Float32 volume)
 {
-    return setDeviceVolume(
+    return maccoreaudio_setDeviceVolume(
             deviceUID,
             volume,
             kAudioDevicePropertyScopeInput);
@@ -250,11 +500,11 @@ OSStatus setInputDeviceVolume(
  * @return noErr if everything works well. Another value if an error has
  * occured.  
  */
-OSStatus setOutputDeviceVolume(
+OSStatus maccoreaudio_setOutputDeviceVolume(
         const char * deviceUID,
         Float32 volume)
 {
-    return setDeviceVolume(
+    return maccoreaudio_setDeviceVolume(
             deviceUID,
             volume,
             kAudioDevicePropertyScopeOutput);
@@ -274,7 +524,7 @@ OSStatus setOutputDeviceVolume(
  * @return noErr if everything works well. Another value if an error has
  * occured.  
  */
-OSStatus setDeviceVolume(
+OSStatus maccoreaudio_setDeviceVolume(
         const char * deviceUID,
         Float32 volume,
         UInt32 inputOutputScope)
@@ -286,7 +536,7 @@ OSStatus setDeviceVolume(
     UInt32 channels[2];
 
     // Gets the correspoding device
-    if((device = getDevice(deviceUID)) == kAudioObjectUnknown)
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
     {
         fprintf(stderr,
                 "setDeviceVolume (coreaudio/device.c): \
@@ -295,7 +545,7 @@ OSStatus setDeviceVolume(
     }
 
     // get the input device stereo channels
-    if((getChannelsForStereo(deviceUID, channels)) != noErr)
+    if((maccoreaudio_getChannelsForStereo(deviceUID, channels)) != noErr)
     {
         fprintf(stderr,
                 "setDeviceVolume (coreaudio/device.c): \
@@ -355,10 +605,10 @@ OSStatus setDeviceVolume(
  * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0
  * if an error occurs.
  */
-Float32 getInputDeviceVolume(
+Float32 maccoreaudio_getInputDeviceVolume(
         const char * deviceUID)
 {
-    return getDeviceVolume(
+    return maccoreaudio_getDeviceVolume(
             deviceUID,
             kAudioDevicePropertyScopeInput);
 }
@@ -371,10 +621,10 @@ Float32 getInputDeviceVolume(
  * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0
  * if an error occurs.
  */
-Float32 getOutputDeviceVolume(
+Float32 maccoreaudio_getOutputDeviceVolume(
         const char * deviceUID)
 {
-    return getDeviceVolume(
+    return maccoreaudio_getDeviceVolume(
             deviceUID,
             kAudioDevicePropertyScopeOutput);
 }
@@ -391,7 +641,7 @@ Float32 getOutputDeviceVolume(
  * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0
  * if an error occurs.
  */
-Float32 getDeviceVolume(
+Float32 maccoreaudio_getDeviceVolume(
         const char * deviceUID,
         UInt32 inputOutputScope)
 {
@@ -403,7 +653,7 @@ Float32 getDeviceVolume(
     UInt32 channels[2];
 
     // Gets the correspoding device
-    if((device = getDevice(deviceUID)) == kAudioObjectUnknown)
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
     {
         fprintf(stderr,
                 "getDeviceVolume (coreaudio/device.c): \
@@ -412,7 +662,7 @@ Float32 getDeviceVolume(
     }
 
     // get the input device stereo channels
-    if((getChannelsForStereo(deviceUID, channels)) != noErr)
+    if((maccoreaudio_getChannelsForStereo(deviceUID, channels)) != noErr)
     {
         fprintf(stderr,
                 "getDeviceVolume (coreaudio/device.c): \
@@ -474,7 +724,7 @@ Float32 getDeviceVolume(
  * @return An OSStatus set to noErr if everything works well. Any other vlaue
  * otherwise.
  */
-OSStatus getChannelsForStereo(
+OSStatus maccoreaudio_getChannelsForStereo(
         const char * deviceUID,
         UInt32 * channels)
 {
@@ -484,7 +734,7 @@ OSStatus getChannelsForStereo(
     UInt32 size;
 
     // Gets the correspoding device
-    if((device = getDevice(deviceUID)) == kAudioObjectUnknown)
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
     {
         fprintf(stderr,
                 "getChannelsForStereo (coreaudio/device.c): \
@@ -514,3 +764,989 @@ OSStatus getChannelsForStereo(
 
     return err;
 }
+
+/**
+ * Returns the number of channels avaialable for input device.
+ *
+ * @param deviceUID The device UID to get the channels from.
+ *
+ * @return The number of channels avaialable for a given input device.
+ * -1 if an error occurs.
+ */
+int maccoreaudio_countInputChannels(
+        const char * deviceUID)
+{
+    return maccoreaudio_countChannels(
+                deviceUID,
+                kAudioDevicePropertyScopeInput);
+}
+
+/**
+ * Returns the number of channels avaialable for output device.
+ *
+ * @param deviceUID The device UID to get the channels from.
+ *
+ * @return The number of channels avaialable for a given output device.
+ * -1 if an error occurs.
+ */
+int maccoreaudio_countOutputChannels(
+        const char * deviceUID)
+{
+    return maccoreaudio_countChannels(
+                deviceUID,
+                kAudioDevicePropertyScopeOutput);
+}
+
+/**
+ * Returns the number of channels avaialable for a given input / output device.
+ *
+ * @param deviceUID The device UID to get the channels from.
+ * @param inputOutputScope The scope to tell if this is an output or an input
+ * device.
+ *
+ * @return The number of channels avaialable for a given input / output device.
+ * -1 if an error occurs.
+ */
+int maccoreaudio_countChannels(
+        const char * deviceUID,
+        AudioObjectPropertyScope inputOutputScope)
+{
+    AudioDeviceID device;
+    OSStatus err = noErr;
+    AudioObjectPropertyAddress address;
+    UInt32 size;
+    AudioBufferList *audioBufferList = NULL;
+    int nbChannels = 0;
+    int i;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "getChannelsForStereo (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        return -1;
+    }
+
+    // Gets the size of the streams for this device.
+    address.mSelector = kAudioDevicePropertyStreamConfiguration;
+    address.mScope = inputOutputScope;
+    address.mElement = kAudioObjectPropertyElementWildcard; // 0
+    if((err = AudioObjectGetPropertyDataSize(device, &address, 0, NULL, &size))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_countChannels (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyDataSize, err: %d\n",
+                    ((int) err));
+        return -1;
+    }
+
+    // Gets the number of channels ofr each stream.
+    if((audioBufferList = (AudioBufferList *) malloc(size)) == NULL)
+    {
+        perror("maccoreaudio_countChannels (coreaudio/device.c): \
+                \n\tmalloc\n");
+        return -1;
+    }
+    if((err = AudioObjectGetPropertyData(
+                    device,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    audioBufferList))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_countChannels (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyData, err: %d\n",
+                    ((int) err));
+        return -1;
+    }
+    for(i = 0; i < audioBufferList->mNumberBuffers; ++i)
+    {
+        nbChannels += audioBufferList->mBuffers[i].mNumberChannels;
+    }
+    free(audioBufferList);
+
+    return nbChannels;
+}
+
+/**
+ * Returns the nominal sample rate for the given device.
+ *
+ * @param deviceUID The device UID to get the channels from.
+ *
+ * @return The nominal sample rate for the given device. -1.0 if an error
+ * occurs.
+ */
+Float64 maccoreaudio_getNominalSampleRate(
+        const char * deviceUID)
+{
+    AudioDeviceID device;
+    OSStatus err = noErr;
+    AudioObjectPropertyAddress address;
+    UInt32 size;
+    Float64 rate = -1.0;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "getNominalSampleRate (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        return -1.0;
+    }
+
+    // Gets the sample rate.
+    size = sizeof(Float64);
+    address.mSelector = kAudioDevicePropertyNominalSampleRate;
+    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mElement = kAudioObjectPropertyElementMaster;
+
+    if((err = AudioObjectGetPropertyData(
+                    device,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    &rate))
+            != noErr)
+    {
+        fprintf(stderr,
+                "getNominalSampleRate (coreaudio/device.c): \
+                    \n\tAudioObjactGetPropertyData, err: %d\n",
+                    (int) err);
+        return -1.0;
+    }
+
+    return rate;
+}
+
+/**
+ * Gets the minimal and maximal nominal sample rate for the given device.
+ *
+ * @param deviceUID The device UID to get the channels from.
+ * @param minRate The minimal rate available for this device.
+ * @param maxRate The maximal rate available for this device.
+ *
+ * @return noErr if everything is alright. -1.0 if an error occurs.
+ */
+OSStatus maccoreaudio_getAvailableNominalSampleRates(
+        const char * deviceUID,
+        Float64 * minRate,
+        Float64 * maxRate)
+{
+    AudioDeviceID device;
+    OSStatus err = noErr;
+    AudioObjectPropertyAddress address;
+    UInt32 size;
+    AudioValueRange minMaxRate;
+    minMaxRate.mMinimum = -1.0;
+    minMaxRate.mMaximum = -1.0;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "getAvailableNominalSampleRates (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        return -1.0;
+    }
+
+    // Gets the available sample ratea.
+    size = sizeof(AudioValueRange);
+    address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
+    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mElement = kAudioObjectPropertyElementMaster;
+
+    if((err = AudioObjectGetPropertyData(
+                    device,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    &minMaxRate))
+            != noErr)
+    {
+        fprintf(stderr,
+                "getAvailableNominalSampleRates (coreaudio/device.c): \
+                    \n\tAudioObjactGetPropertyData, err: %d\n",
+                    (int) err);
+        return -1.0;
+    }
+
+    (*minRate) = minMaxRate.mMinimum;
+    (*maxRate) = minMaxRate.mMaximum;
+
+    return noErr;
+}
+
+/**
+ * Lists the audio devices available and stores their UIDs in the provided
+ * parameter.
+ *
+ * @param deviceUIDList A pointer which will be filled in with a list of device
+ * UID strings. The caller is responsible to free this list and all the items.
+ *
+ * @return -1 in case of error. Otherwise, returns the number of devices stored
+ * in the deviceUIDList.
+ */
+int maccoreaudio_getDeviceUIDList(
+        char *** deviceUIDList)
+{
+    OSStatus err = noErr;
+    UInt32 propsize;
+    int nbDevices = -1;
+
+    AudioObjectPropertyAddress address =
+    {
+        kAudioHardwarePropertyDevices,
+        kAudioObjectPropertyScopeGlobal,
+        kAudioObjectPropertyElementMaster
+    };
+    if((err = AudioObjectGetPropertyDataSize(
+                    kAudioObjectSystemObject,
+                    &address,
+                    0,
+                    NULL,
+                    &propsize))
+            != noErr)
+    {
+        fprintf(stderr,
+                "getDeviceUIDList (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyDataSize, err: %d\n",
+                    ((int) err));
+        return -1;
+    }
+
+    nbDevices = propsize / sizeof(AudioDeviceID);    
+    AudioDeviceID *devices = NULL;
+    if((devices = (AudioDeviceID*) malloc(nbDevices * sizeof(AudioDeviceID)))
+            == NULL)
+    {
+        perror("getDeviceUIDList (coreaudio/device.c): \
+                    \n\tmalloc\n");
+        return -1;
+    }
+
+    if((err = AudioObjectGetPropertyData(
+                    kAudioObjectSystemObject,
+                    &address,
+                    0,
+                    NULL,
+                    &propsize,
+                    devices))
+            != noErr)
+    {
+        free(devices);
+        fprintf(stderr,
+                "getDeviceUIDList (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyData, err: %d\n",
+                    ((int) err));
+        return -1;
+    }
+
+    if(((*deviceUIDList) = (char**) malloc(nbDevices * sizeof(char*)))
+            == NULL)
+    {
+        free(devices);
+        perror("getDeviceUIDList (coreaudio/device.c): \
+                    \n\tmalloc\n");
+        return -1;
+    }
+
+    int i;
+    for(i = 0; i < nbDevices; ++i)
+    {
+        if(((*deviceUIDList)[i] = maccoreaudio_getAudioDeviceProperty(
+                        devices[i],
+                        kAudioDevicePropertyDeviceUID))
+                == NULL)
+        {
+            int j;
+            for(j = 0; j < i; ++j)
+            {
+                free((*deviceUIDList)[j]);
+            }
+            free(*deviceUIDList);
+            free(devices);
+            fprintf(stderr,
+                    "getDeviceUIDList (coreaudio/device.c): \
+                    \n\tgetAudioDeviceProperty\n");
+            return -1;
+        }
+    }
+
+    free(devices);
+
+    return nbDevices;
+}
+ 
+/**
+ * Registers the listener for new plugged-in/out devices.
+ */
+void maccoreaudio_initializeHotplug(
+        void* callbackFunction)
+{
+    AudioObjectPropertyAddress address =
+    {
+        kAudioHardwarePropertyDevices,
+        kAudioObjectPropertyScopeGlobal,
+        kAudioObjectPropertyElementMaster
+    };
+
+    AudioObjectAddPropertyListener(
+            kAudioObjectSystemObject,
+            &address,
+            maccoreaudio_devicesChangedCallback,
+            callbackFunction);
+}
+
+/**
+ * Unregisters the listener for new plugged-in/out devices.
+ */
+void maccoreaudio_uninitializeHotplug()
+{
+    AudioObjectPropertyAddress address =
+    {
+        kAudioHardwarePropertyDevices,
+        kAudioObjectPropertyScopeGlobal,
+        kAudioObjectPropertyElementMaster
+    };
+
+    AudioObjectRemovePropertyListener(
+            kAudioObjectSystemObject,
+            &address,
+            maccoreaudio_devicesChangedCallback,
+            NULL);
+}
+
+/**
+ * The callback function called when a device is plugged-in/out.
+ *
+ * @param inObjectID The AudioObject whose properties have changed.
+ * @param inNumberAddresses The number of elements in the inAddresses array.
+ * @param inAddresses An array of AudioObjectPropertyAddresses indicating which
+ * properties changed.
+ * @param inClientData A pointer to client data established when the listener
+ * proc was registered with the AudioObject.
+ *
+ * @return The return value is currently unused and should always be 0.
+ */
+static OSStatus maccoreaudio_devicesChangedCallback(
+        AudioObjectID inObjectID,
+        UInt32 inNumberAddresses,
+        const AudioObjectPropertyAddress inAddresses[],
+        void *inClientData)
+{
+    void (*callbackFunction) (void) = inClientData;
+    callbackFunction();
+
+    return noErr;
+}
+
+/**
+ * Returns a string identifier of the device transport type.
+ *
+ * @param deviceUID The device UID to get the transport type from.
+ *
+ * @return The string identifier of the device transport type. Or NULL if
+ * failed.
+ */
+const char* maccoreaudio_getTransportType(
+        const char * deviceUID)
+{
+    AudioDeviceID device;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "maccoreaudio_getTransportType (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        return NULL;
+    }
+    // target device transport type property
+    AudioObjectPropertyAddress address;
+    address.mSelector = kAudioDevicePropertyTransportType;
+    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mElement = kAudioObjectPropertyElementMaster;
+
+    OSStatus err;
+    unsigned int transportType = 0;
+    UInt32 size = sizeof(transportType);
+    if((err = AudioObjectGetPropertyData(
+                    device,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    &transportType))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_getTransportType (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyData: err: %d\n",
+                    (int) err);
+        return NULL;
+    }
+
+    switch(transportType)
+    {
+        case kAudioDeviceTransportTypeAggregate:
+            return transportTypeAggregate;
+            break;
+        case kAudioDeviceTransportTypeAirPlay:
+            return transportTypeAirPlay;
+            break;
+        case kAudioDeviceTransportTypeAutoAggregate:
+            return transportTypeAutoAggregate;
+            break;
+        case kAudioDeviceTransportTypeAVB:
+            return transportTypeAVB;
+            break;
+        case kAudioDeviceTransportTypeBluetooth:
+            return transportTypeBlueTooth;
+            break;
+        case kAudioDeviceTransportTypeBuiltIn:
+            return transportTypeBuiltIn;
+            break;
+        case kAudioDeviceTransportTypeDisplayPort:
+            return transportTypeDisplayPort;
+            break;
+        case kAudioDeviceTransportTypeFireWire:
+            return transportTypeFireWire;
+            break;
+        case kAudioDeviceTransportTypeHDMI:
+            return transportTypeHDMI;
+            break;
+        case kAudioDeviceTransportTypePCI:
+            return transportTypePCI;
+            break;
+        case kAudioDeviceTransportTypeThunderbolt:
+            return transportTypeThunderbolt;
+            break;
+        case kAudioDeviceTransportTypeUnknown:
+            return transportTypeUnknown;
+            break;
+        case kAudioDeviceTransportTypeUSB:
+            return transportTypeUSB;
+            break;
+        case kAudioDeviceTransportTypeVirtual:
+            return transportTypeVirtual;
+            break;
+        default:
+            return NULL;
+            break;
+    }
+}
+
+maccoreaudio_stream * maccoreaudio_startInputStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved)
+{
+    return maccoreaudio_startStream(
+            deviceUID,
+            callbackFunction,
+            callbackObject,
+            callbackMethod,
+            maccoreaudio_readInputStream,
+            false,
+            sampleRate,
+            nbChannels,
+            bitsPerChannel,
+            isFloat,
+            isBigEndian,
+            isNonInterleaved);
+}
+
+maccoreaudio_stream * maccoreaudio_startOutputStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved)
+{
+    return maccoreaudio_startStream(
+            deviceUID,
+            callbackFunction,
+            callbackObject,
+            callbackMethod,
+            maccoreaudio_writeOutputStream,
+            true,
+            sampleRate,
+            nbChannels,
+            bitsPerChannel,
+            isFloat,
+            isBigEndian,
+            isNonInterleaved);
+}
+
+/**
+ * The the IO processing of a device.
+ *
+ * @param deviceUID The device UID to get the data from / to.
+ * @param callbackFunction A function called 
+ * @param readWriteFunction A function pointer called by the IO when data are
+ * available for read / write.
+ */
+maccoreaudio_stream * maccoreaudio_startStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        void* readWriteFunction,
+        unsigned char isJavaFormatSource,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved)
+{
+    AudioDeviceID device;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "maccoreaudio_startStream (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        return NULL;
+    }
+
+    // Init the stream structure.
+    maccoreaudio_stream * stream;
+    if((stream = (maccoreaudio_stream*) malloc(sizeof(maccoreaudio_stream)))
+            == NULL)
+    {
+        perror("maccoreaudio_startStream (coreaudio/device.c): \
+                    \n\tmalloc\n");
+        return NULL;
+    }
+    stream->ioProcId = NULL;
+    stream->callbackFunction = callbackFunction;
+    stream->callbackObject = callbackObject;
+    stream->callbackMethod = callbackMethod;
+
+    AudioStreamBasicDescription javaFormat;
+    FillOutASBDForLPCM(
+            &javaFormat,
+            sampleRate,
+            nbChannels,
+            bitsPerChannel,
+            bitsPerChannel,
+            isFloat,
+            isBigEndian,
+            isNonInterleaved); 
+    if(maccoreaudio_initConverter(
+                deviceUID,
+                &javaFormat,
+                isJavaFormatSource,
+                &stream->converter,
+                &stream->conversionRatio)
+            != noErr)
+    {
+        free(stream);
+        fprintf(stderr,
+                "maccoreaudio_startStream (coreaudio/device.c): \
+                    \n\tmaccoreaudio_initConverter\n");
+        return NULL;
+    }
+
+    //  register the IOProc
+    if(AudioDeviceCreateIOProcID(
+            device,
+            readWriteFunction,
+            stream,
+            &stream->ioProcId) != noErr)
+    {
+        free(stream);
+        fprintf(stderr,
+                "maccoreaudio_startStream (coreaudio/device.c): \
+                    \n\tAudioDeviceIOProcID\n");
+        return NULL;
+    }
+
+    //  start IO
+    AudioDeviceStart(device, stream->ioProcId);
+
+    return stream;
+}
+
+void maccoreaudio_stopStream(
+        const char * deviceUID,
+        maccoreaudio_stream * stream)
+{
+    AudioDeviceID device;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "maccoreaudio_stopStream (coreaudio/device.c): \
+                    \n\tgetDevice: %s\n",
+                    deviceUID);
+        fflush(stderr);
+        return;
+    }
+
+    //  stop IO
+    AudioDeviceStop(device, stream->ioProcId);
+
+    //  unregister the IOProc
+    AudioDeviceDestroyIOProcID(device, stream->ioProcId);
+
+    AudioConverterDispose(stream->converter);
+
+    free(stream);
+}
+
+OSStatus maccoreaudio_readInputStream(
+        AudioDeviceID inDevice,
+        const AudioTimeStamp* inNow,
+        const AudioBufferList* inInputData,
+        const AudioTimeStamp* inInputTime,
+        AudioBufferList* outOutputData,
+        const AudioTimeStamp* inOutputTime,
+        void* inClientData)
+{
+    OSStatus err = noErr;
+    maccoreaudio_stream * stream = (maccoreaudio_stream*) inClientData;
+    void (*callbackFunction) (char*, int, void*, void*)
+        = stream->callbackFunction;
+    UInt32 tmpLength
+        = inInputData->mBuffers[0].mDataByteSize * stream->conversionRatio;
+    char tmpBuffer[tmpLength];
+    int i;
+    for(i = 0; i < inInputData->mNumberBuffers; ++i)
+    {
+        if(inInputData->mBuffers[i].mData != NULL
+                && inInputData->mBuffers[i].mDataByteSize > 0)
+        {
+            if((err = AudioConverterConvertBuffer(
+                            stream->converter,
+                            inInputData->mBuffers[i].mDataByteSize,
+                            inInputData->mBuffers[i].mData,
+                            &tmpLength,
+                            tmpBuffer))
+                    != noErr)
+            {
+                fprintf(stderr,
+                        "maccoreaudio_readInputStream (coreaudio/device.c): \
+                            \n\tAudioConverterConvertBuffer: %x\n",
+                            (int) err);
+                fflush(stderr);
+                return err;
+            }
+
+            callbackFunction(
+                    tmpBuffer,
+                    tmpLength,
+                    stream->callbackObject,
+                    stream->callbackMethod);
+        }
+    }
+
+    return noErr;
+}
+
+OSStatus maccoreaudio_writeOutputStream(
+        AudioDeviceID inDevice,
+        const AudioTimeStamp* inNow,
+        const AudioBufferList* inInputData,
+        const AudioTimeStamp* inInputTime,
+        AudioBufferList* outOutputData,
+        const AudioTimeStamp* inOutputTime,
+        void* inClientData)
+{
+    OSStatus err = noErr;
+
+    maccoreaudio_stream * stream = (maccoreaudio_stream*) inClientData;
+    void (*callbackFunction) (char*, int, void*, void*)
+        = stream->callbackFunction;
+
+    if(outOutputData->mNumberBuffers == 0)
+    {
+        return err;
+    }
+
+    int tmpLength
+        = outOutputData->mBuffers[0].mDataByteSize * stream->conversionRatio;
+    char tmpBuffer[tmpLength];
+
+    callbackFunction(
+            tmpBuffer,
+            tmpLength,
+            stream->callbackObject,
+            stream->callbackMethod);
+
+    if((err = AudioConverterConvertBuffer(
+                    stream->converter,
+                    tmpLength,
+                    tmpBuffer,
+                    &outOutputData->mBuffers[0].mDataByteSize,
+                    outOutputData->mBuffers[0].mData))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_writeOutputStream (coreaudio/device.c): \
+                    \n\tAudioConverterConvertBuffer\n");
+        fflush(stderr);
+        return err;
+    }
+
+    // Copies the same data into the other buffers.
+    int i;
+    UInt32 length;
+    for(i = 1; i < outOutputData->mNumberBuffers; ++i)
+    {
+        // Copies available data.
+        length = outOutputData->mBuffers[i].mDataByteSize;
+        if(length > outOutputData->mBuffers[0].mDataByteSize)
+        {
+            length = outOutputData->mBuffers[0].mDataByteSize;
+        }
+        memcpy(
+                outOutputData->mBuffers[i].mData,
+                outOutputData->mBuffers[0].mData,
+                length);
+
+        // Resets the resting buffer.
+        if(outOutputData->mBuffers[i].mDataByteSize
+                > outOutputData->mBuffers[0].mDataByteSize)
+        {
+            memset(
+                    outOutputData->mBuffers[i].mData
+                        + outOutputData->mBuffers[0].mDataByteSize,
+                    0,
+                    outOutputData->mBuffers[i].mDataByteSize
+                        - outOutputData->mBuffers[0].mDataByteSize);
+        }
+    }
+
+    return noErr;
+}
+
+/**
+ * Returns the stream virtual format for a given stream.
+ *
+ * @param stream The stream to get the format.
+ * @param format The variable to write the forat into.
+ *
+ * @return noErr if everything works fine. Any other value if failed.
+ */
+OSStatus maccoreaudio_getStreamVirtualFormat(
+        AudioStreamID stream,
+        AudioStreamBasicDescription * format)
+{
+    // Gets the audio format of the stream.
+    OSStatus err = noErr;
+    UInt32 size = sizeof(AudioStreamBasicDescription);
+    AudioObjectPropertyAddress address;
+    address.mSelector = kAudioStreamPropertyVirtualFormat;
+    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mElement = kAudioObjectPropertyElementMaster;
+    if((err = AudioObjectGetPropertyData(
+                    stream,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    format))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_getStreamVirtualFormat (coreaudio/device.c): \
+                \n\tAudioObjectGetPropertyData, err: 0x%x\n",
+                ((int) err));
+        fflush(stderr);
+        return err;
+    }
+
+    return err;
+}
+
+/**
+ * Initializes a new audio converter to work between the given device and the
+ * format description.
+ *
+ * @param deviceUID The device identifier.
+ * @param javaFormat The format needed by the upper layer Java aplication.
+ * @param isJavaFormatSource True if the Java format is the source of this
+ * converter and the device the ouput. False otherwise.
+ * @param converter A pointer to the converter used to store the new created
+ * converter.
+ *
+ * @return noErr if everything works correctly. Any other vlue otherwise.
+ */
+OSStatus maccoreaudio_initConverter(
+        const char * deviceUID,
+        const AudioStreamBasicDescription * javaFormat,
+        unsigned char isJavaFormatSource,
+        AudioConverterRef * converter,
+        double * conversionRatio)
+{
+    AudioDeviceID device;
+    OSStatus err = noErr;
+    AudioObjectPropertyAddress address;
+
+    // Gets the correspoding device
+    if((device = maccoreaudio_getDevice(deviceUID)) == kAudioObjectUnknown)
+    {
+        fprintf(stderr,
+                "maccoreaudio_initConverter (coreaudio/device.c): \
+                    \n\tgetDevice\n");
+        fflush(stderr);
+        return kAudioObjectUnknown;
+    }
+
+    AudioStreamBasicDescription deviceFormat;
+    AudioStreamID audioStreamIds[1];
+    UInt32 size = sizeof(AudioStreamID *);
+    address.mSelector = kAudioDevicePropertyStreams;
+    address.mScope = kAudioObjectPropertyScopeGlobal;
+    address.mElement = kAudioObjectPropertyElementMaster;
+    if((err = AudioObjectGetPropertyData(
+                    device,
+                    &address,
+                    0,
+                    NULL,
+                    &size,
+                    &audioStreamIds))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_countChannels (coreaudio/device.c): \
+                    \n\tAudioObjectGetPropertyData, err: 0x%x\n",
+                    ((int) err));
+        fflush(stderr);
+        return err;
+    }
+
+    if((err = maccoreaudio_getStreamVirtualFormat(
+                    audioStreamIds[0],
+                    &deviceFormat))
+                != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_countChannels (coreaudio/device.c): \
+                    \n\tmaccoreaudiogetStreamVirtualFormat, err: 0x%x\n",
+                    ((int) err));
+        fflush(stderr);
+        return err;
+    }
+    
+    const AudioStreamBasicDescription *inFormat = javaFormat;
+    const AudioStreamBasicDescription *outFormat = &deviceFormat;
+    if(!isJavaFormatSource)
+    {
+        inFormat = &deviceFormat;
+        outFormat = javaFormat;
+    }
+
+    if((err = AudioConverterNew(inFormat, outFormat, converter))
+            != noErr)
+    {
+        fprintf(stderr,
+                "maccoreaudio_countChannels (coreaudio/device.c): \
+                    \n\tAudioConverterNew, err: 0x%x\n",
+                    ((int) err));
+        fflush(stderr);
+        return err;
+    }
+
+    *conversionRatio =
+        ((double) javaFormat->mBytesPerFrame)
+            / ((double) deviceFormat.mBytesPerFrame)
+        * javaFormat->mSampleRate / deviceFormat.mSampleRate;
+
+    return err;
+}
+
+/**
+ * Computes the value for the audio stream basic description mFormatFlags
+ * field for linear PCM data. This function does not support specifying sample
+ * formats that are either unsigned integer or low-aligned.
+ *
+ * @param inValidBitsPerChannel The number of valid bits in each sample.
+ * @param inTotalBitsPerChannel The total number of bits in each sample.
+ * @param inIsFloat Use true if the samples are represented with floating point
+             numbers.
+ * @param inIsBigEndian Use true if the samples are big endian.
+ * @param inIsNonInterleaved Use true if the samples are noninterleaved.
+ *
+ * @return A UInt32 value containing the calculated format flags.
+ */
+inline UInt32 CalculateLPCMFlags (
+        UInt32 inValidBitsPerChannel,
+        UInt32 inTotalBitsPerChannel,
+        bool inIsFloat,
+        bool inIsBigEndian,
+        bool inIsNonInterleaved)
+{
+    return
+        (inIsFloat ? kAudioFormatFlagIsFloat : kAudioFormatFlagIsSignedInteger)
+        | (inIsBigEndian ? ((UInt32)kAudioFormatFlagIsBigEndian) : 0)
+        | ((!inIsFloat && (inValidBitsPerChannel == inTotalBitsPerChannel)) ?
+                kAudioFormatFlagIsPacked : kAudioFormatFlagIsAlignedHigh)
+        | (inIsNonInterleaved ? ((UInt32)kAudioFormatFlagIsNonInterleaved) : 0);
+}
+
+/**
+ * Fills AudioStreamBasicDescription information.
+ *
+ * @param outASBD On output, a filled-out AudioStreamBasicDescription structure.
+ * @param inSampleRate The number of sample frames per second of the data in the
+ * stream.
+ * @param inChannelsPerFrame The number of channels in each frame of data.
+ * @param inValidBitsPerChannel The number of valid bits in each sample.
+ * @param inTotalBitsPerChannel The total number of bits in each sample.
+ * @param inIsFloat Use true if the samples are represented as floating-point
+ * numbers.
+ * @param inIsBigEndian Use true if the samples are big endian.
+ * @param inIsNonInterleaved Use true if the samples are noninterleaved.
+ */
+inline void FillOutASBDForLPCM(
+        AudioStreamBasicDescription * outASBD,
+        Float64 inSampleRate,
+        UInt32 inChannelsPerFrame,
+        UInt32 inValidBitsPerChannel,
+        UInt32 inTotalBitsPerChannel,
+        bool inIsFloat,
+        bool inIsBigEndian,
+        bool inIsNonInterleaved)
+{
+    outASBD->mSampleRate = inSampleRate;
+    outASBD->mFormatID = kAudioFormatLinearPCM;
+    outASBD->mFormatFlags = CalculateLPCMFlags(
+            inValidBitsPerChannel,
+            inTotalBitsPerChannel,
+            inIsFloat,
+            inIsBigEndian,
+            inIsNonInterleaved);
+    outASBD->mBytesPerPacket =
+        (inIsNonInterleaved ? 1 : inChannelsPerFrame) *
+        (inTotalBitsPerChannel/8);
+    outASBD->mFramesPerPacket = 1;
+    outASBD->mBytesPerFrame =
+        (inIsNonInterleaved ? 1 : inChannelsPerFrame) *
+        (inTotalBitsPerChannel/8);
+    outASBD->mChannelsPerFrame = inChannelsPerFrame;
+    outASBD->mBitsPerChannel = inValidBitsPerChannel;
+}
diff --git a/src/native/macosx/coreaudio/lib/device.h b/src/native/macosx/coreaudio/lib/device.h
index 3a78e844..b13fc50f 100644
--- a/src/native/macosx/coreaudio/lib/device.h
+++ b/src/native/macosx/coreaudio/lib/device.h
@@ -7,6 +7,7 @@
 #ifndef device_h
 #define device_h
 
+#include <AudioToolbox/AudioConverter.h>
 #include <CoreAudio/CoreAudio.h>
 #include <CoreFoundation/CFString.h>
 #include <stdio.h>
@@ -17,30 +18,105 @@
  *
  * @author Vincent Lucas
  */
-int initDevices(void);
+typedef struct
+{
+    AudioDeviceIOProcID ioProcId;
+    void* callbackFunction;
+    void* callbackObject;
+    void* callbackMethod;
+    AudioConverterRef converter;
+    double conversionRatio;
+} maccoreaudio_stream;
 
-void freeDevices(void);
+int maccoreaudio_initDevices(
+        void);
 
-AudioDeviceID getDevice(
+void maccoreaudio_freeDevices(
+        void);
+
+int maccoreaudio_isInputDevice(
+        const char * deviceUID);
+
+int maccoreaudio_isOutputDevice(
         const char * deviceUID);
 
-char* getDeviceName(
+char* maccoreaudio_getDeviceName(
         const char * deviceUID);
 
-char* getDeviceModelIdentifier(
+char* maccoreaudio_getDeviceModelIdentifier(
         const char * deviceUID);
 
-OSStatus setInputDeviceVolume(
+OSStatus maccoreaudio_setInputDeviceVolume(
         const char * deviceUID,
         Float32 volume);
 
-OSStatus setOutputDeviceVolume(
+OSStatus maccoreaudio_setOutputDeviceVolume(
         const char * deviceUID,
         Float32 volume);
 
-Float32 getInputDeviceVolume(
+Float32 maccoreaudio_getInputDeviceVolume(
         const char * deviceUID);
 
-Float32 getOutputDeviceVolume(
+Float32 maccoreaudio_getOutputDeviceVolume(
         const char * deviceUID);
+
+int maccoreaudio_getDeviceUIDList(
+        char *** deviceUIDList);
+
+const char* maccoreaudio_getTransportType(
+        const char * deviceUID);
+
+Float64 maccoreaudio_getNominalSampleRate(
+        const char * deviceUID);
+
+OSStatus maccoreaudio_getAvailableNominalSampleRates(
+        const char * deviceUID,
+        Float64 * minRate,
+        Float64 * maxRate);
+
+char* maccoreaudio_getDefaultInputDeviceUID(
+        void);
+
+char* maccoreaudio_getDefaultOutputDeviceUID(
+        void);
+
+int maccoreaudio_countInputChannels(
+        const char * deviceUID);
+
+int maccoreaudio_countOutputChannels(
+        const char * deviceUID);
+
+maccoreaudio_stream * maccoreaudio_startInputStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved);
+
+maccoreaudio_stream * maccoreaudio_startOutputStream(
+        const char * deviceUID,
+        void* callbackFunction,
+        void* callbackObject,
+        void* callbackMethod,
+        float sampleRate,
+        UInt32 nbChannels,
+        UInt32 bitsPerChannel,
+        unsigned char isFloat,
+        unsigned char isBigEndian,
+        unsigned char isNonInterleaved);
+
+void maccoreaudio_stopStream(
+        const char * deviceUID,
+        maccoreaudio_stream * stream);
+
+void maccoreaudio_initializeHotplug(
+        void* callbackFunction);
+
+void maccoreaudio_uninitializeHotplug();
+
 #endif
diff --git a/src/org/jitsi/impl/neomedia/CoreAudioDevice.java b/src/org/jitsi/impl/neomedia/CoreAudioDevice.java
index 07230e01..ae2d56b4 100644
--- a/src/org/jitsi/impl/neomedia/CoreAudioDevice.java
+++ b/src/org/jitsi/impl/neomedia/CoreAudioDevice.java
@@ -108,4 +108,28 @@ public static native int setInputDeviceVolume(
     public static native int setOutputDeviceVolume(
             String deviceUID,
             float volume);
+
+    private static Runnable devicesChangedCallback;
+
+    /**
+     * Implements a callback which gets called by the native coreaudio
+     * counterpart to notify the Java counterpart that the list of devices has
+     * changed.
+     */
+    public static void devicesChangedCallback()
+    {
+        Runnable devicesChangedCallback
+            = CoreAudioDevice.devicesChangedCallback;
+
+        if(devicesChangedCallback != null)
+        {
+            devicesChangedCallback.run();
+        }
+    }
+
+    public static void setDevicesChangedCallback(
+            Runnable devicesChangedCallback)
+    {
+        CoreAudioDevice.devicesChangedCallback = devicesChangedCallback;
+    }
 }
diff --git a/src/org/jitsi/impl/neomedia/MacCoreAudioDevice.java b/src/org/jitsi/impl/neomedia/MacCoreAudioDevice.java
new file mode 100644
index 00000000..530ce28f
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/MacCoreAudioDevice.java
@@ -0,0 +1,96 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia;
+
+import org.jitsi.util.*;
+
+/**
+ * Extension for the JNI link to the MacOsX CoreAudio library.
+ *
+ * @author Vincent Lucas
+ */
+public class MacCoreAudioDevice
+    extends CoreAudioDevice
+{
+    /**
+     * The number of milliseconds to be read from or written to a native
+     * CoreAudio stream in a single transfer of data.
+     */
+    public static final int DEFAULT_MILLIS_PER_BUFFER = 20;
+
+    /**
+     * The default value for the sample rate of the input and the output
+     * MacCoreaudio streams with which they are to be opened if no other
+     * specific sample rate is specified to the MacCoreaudio <tt>DataSource</tt>
+     * or <tt>MacCoreaudioRenderer</tt> that they represent.
+     */
+    public static final double DEFAULT_SAMPLE_RATE = 44100.0;
+
+    public static native String[] getDeviceUIDList();
+
+    public static native boolean isInputDevice(String deviceUID);
+
+    public static native boolean isOutputDevice(String deviceUID);
+
+    public static String getTransportType(String deviceUID)
+    {
+        // Prevent an access violation.
+        if (deviceUID == null)
+            throw new NullPointerException("deviceUID");
+
+        byte[] transportTypeBytes = getTransportTypeBytes(deviceUID);
+        String transportType = StringUtils.newString(transportTypeBytes);
+
+        return transportType;
+    }
+
+    public static native byte[] getTransportTypeBytes(String deviceUID);
+
+    public static native float getNominalSampleRate(String deviceUID);
+
+    public static native float getMinimalNominalSampleRate(String deviceUID);
+
+    public static native float getMaximalNominalSampleRate(String deviceUID);
+
+    public static String getDefaultInputDeviceUID()
+    {
+        byte[] defaultInputDeviceUIDBytes = getDefaultInputDeviceUIDBytes();
+        String defaultInputDeviceUID
+            = StringUtils.newString(defaultInputDeviceUIDBytes);
+
+        return defaultInputDeviceUID;
+    }
+
+    public static native byte[] getDefaultInputDeviceUIDBytes();
+
+    public static String getDefaultOutputDeviceUID()
+    {
+        byte[] defaultOutputDeviceUIDBytes = getDefaultOutputDeviceUIDBytes();
+        String defaultOutputDeviceUID
+            = StringUtils.newString(defaultOutputDeviceUIDBytes);
+
+        return defaultOutputDeviceUID;
+    }
+
+    public static native byte[] getDefaultOutputDeviceUIDBytes();
+
+    public static native long startStream(
+            String deviceUID,
+            Object callback,
+            float sampleRate,
+            int nbChannels,
+            int bitsPerChannel,
+            boolean isFloat,
+            boolean isBigEndian,
+            boolean isNonInterleaved);
+
+    public static native void stopStream(String deviceUID, long stream);
+
+    public static native int countInputChannels(String deviceUID);
+
+    public static native int countOutputChannels(String deviceUID);
+}
diff --git a/src/org/jitsi/impl/neomedia/device/AudioSystem.java b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
index bc3a9d78..962a1cd2 100644
--- a/src/org/jitsi/impl/neomedia/device/AudioSystem.java
+++ b/src/org/jitsi/impl/neomedia/device/AudioSystem.java
@@ -78,6 +78,12 @@ public enum DataFlow
 
     public static final String LOCATOR_PROTOCOL_JAVASOUND = "javasound";
 
+    /**
+     * The protocol of the <tt>MediaLocator</tt>s identifying
+     * <tt>CaptureDeviceInfo</tt>s contributed by <tt>MacCoreaudioSystem</tt>.
+     */
+    public static final String LOCATOR_PROTOCOL_MACCOREAUDIO = "maccoreaudio";
+
     public static final String LOCATOR_PROTOCOL_OPENSLES = "opensles";
 
     public static final String LOCATOR_PROTOCOL_PORTAUDIO = "portaudio";
diff --git a/src/org/jitsi/impl/neomedia/device/DeviceSystem.java b/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
index 32751a37..f228d397 100644
--- a/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
+++ b/src/org/jitsi/impl/neomedia/device/DeviceSystem.java
@@ -205,6 +205,7 @@ public static void initializeDeviceSystems(MediaType mediaType)
                         : null,
                     OSUtils.IS_WINDOWS ? ".WASAPISystem" : null,
                     OSUtils.IS_ANDROID ? null : ".PortAudioSystem",
+                    OSUtils.IS_MAC ? ".MacCoreaudioSystem" : null,
                     ".NoneAudioSystem"
                 };
             break;
diff --git a/src/org/jitsi/impl/neomedia/device/MacCoreaudioSystem.java b/src/org/jitsi/impl/neomedia/device/MacCoreaudioSystem.java
new file mode 100644
index 00000000..484c5ae6
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/device/MacCoreaudioSystem.java
@@ -0,0 +1,785 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.device;
+
+import java.lang.ref.*;
+import java.util.*;
+import java.util.regex.*;
+
+import javax.media.*;
+import javax.media.format.*;
+
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.control.*;
+import org.jitsi.impl.neomedia.jmfext.media.renderer.audio.*;
+import org.jitsi.util.*;
+
+/**
+ * Creates MacCoreaudio capture devices by enumerating all host devices that
+ * have input channels.
+ *
+ * @author Vincent Lucas
+ */
+public class MacCoreaudioSystem
+    extends AudioSystem
+{
+    /**
+     * Represents a listener which is to be notified before and after
+     * MacCoreaudio's native function <tt>UpdateAvailableDeviceList()</tt> is
+     * invoked.
+     */
+    public interface UpdateAvailableDeviceListListener
+        extends EventListener
+    {
+        /**
+         * Notifies this listener that MacCoreaudio's native function
+         * <tt>UpdateAvailableDeviceList()</tt> was invoked.
+         *
+         * @throws Exception if this implementation encounters an error. Any
+         * <tt>Throwable</tt> apart from <tt>ThreadDeath</tt> will be ignored
+         * after it is logged for debugging purposes.
+         */
+        void didUpdateAvailableDeviceList()
+            throws Exception;
+
+        /**
+         * Notifies this listener that MacCoreaudio's native function
+         * <tt>UpdateAvailableDeviceList()</tt> will be invoked.
+         *
+         * @throws Exception if this implementation encounters an error. Any
+         * <tt>Throwable</tt> apart from <tt>ThreadDeath</tt> will be ignored
+         * after it is logged for debugging purposes.
+         */
+        void willUpdateAvailableDeviceList()
+            throws Exception;
+    }
+
+    /**
+     * The protocol of the <tt>MediaLocator</tt>s identifying MacCoreaudio
+     * <tt>CaptureDevice</tt>s
+     */
+    private static final String LOCATOR_PROTOCOL
+        = LOCATOR_PROTOCOL_MACCOREAUDIO;
+
+    /**
+     * The <tt>Logger</tt> used by the <tt>MacCoreaudioSystem</tt> class and its
+     * instances for logging output.
+     */
+    private static final Logger logger
+        = Logger.getLogger(MacCoreaudioSystem.class);
+
+    /**
+     * The number of times that {@link #willPaOpenStream()} has been
+     * invoked without an intervening {@link #didPaOpenStream()} i.e. the
+     * number of MacCoreaudio clients which are currently executing
+     * <tt>Pa_OpenStream</tt> and which are thus inhibiting
+     * <tt>Pa_UpdateAvailableDeviceList</tt>.
+     */
+    private static int openStream = 0;
+
+    /**
+     * The <tt>Object</tt> which synchronizes that access to
+     * {@link #paOpenStream} and {@link #updateAvailableDeviceList}.
+     */
+    private static final Object openStreamSyncRoot = new Object();
+
+    /**
+     * The number of times that {@link #willPaUpdateAvailableDeviceList()}
+     * has been invoked without an intervening
+     * {@link #didPaUpdateAvailableDeviceList()} i.e. the number of
+     * MacCoreaudio clients which are currently executing
+     * <tt>Pa_UpdateAvailableDeviceList</tt> and which are thus inhibiting
+     * <tt>Pa_OpenStream</tt>.
+     */
+    private static int updateAvailableDeviceList = 0;
+
+    /**
+     * The list of <tt>PaUpdateAvailableDeviceListListener</tt>s which are to be
+     * notified before and after MacCoreaudio's native function
+     * <tt>Pa_UpdateAvailableDeviceList()</tt> is invoked.
+     */
+    private static final List<WeakReference<UpdateAvailableDeviceListListener>>
+        updateAvailableDeviceListListeners
+        = new LinkedList<WeakReference<UpdateAvailableDeviceListListener>>();
+
+    /**
+     * The <tt>Object</tt> which ensures that MacCoreaudio's native function
+     * <tt>UpdateAvailableDeviceList()</tt> will not be invoked concurrently.
+     * The condition should hold true on the native side but, anyway, it shoul
+     * not hurt (much) to enforce it on the Java side as well.
+     */
+    private static final Object updateAvailableDeviceListSyncRoot
+        = new Object();
+
+    /**
+     * Adds a listener which is to be notified before and after MacCoreaudio's
+     * native function <tt>UpdateAvailableDeviceList()</tt> is invoked.
+     * <p>
+     * <b>Note</b>: The <tt>MacCoreaudioSystem</tt> class keeps a
+     * <tt>WeakReference</tt> to the specified <tt>listener</tt> in order to
+     * avoid memory leaks.
+     * </p>
+     *
+     * @param listener the <tt>UpdateAvailableDeviceListListener</tt> to be
+     * notified before and after MacCoreaudio's native function
+     * <tt>UpdateAvailableDeviceList()</tt> is invoked
+     */
+    public static void addUpdateAvailableDeviceListListener(
+            UpdateAvailableDeviceListListener listener)
+    {
+        if(listener == null)
+            throw new NullPointerException("listener");
+
+        synchronized(updateAvailableDeviceListListeners)
+        {
+            Iterator<WeakReference<UpdateAvailableDeviceListListener>> i
+                = updateAvailableDeviceListListeners.iterator();
+            boolean add = true;
+
+            while(i.hasNext())
+            {
+                UpdateAvailableDeviceListListener l = i.next().get();
+
+                if(l == null)
+                    i.remove();
+                else if(l.equals(listener))
+                    add = false;
+            }
+            if(add)
+            {
+                updateAvailableDeviceListListeners.add(
+                        new WeakReference<UpdateAvailableDeviceListListener>(
+                                listener));
+            }
+        }
+    }
+
+    /**
+     * Notifies <tt>MacCoreaudioSystem</tt> that a MacCoreaudio client finished
+     * executing <tt>OpenStream</tt>.
+     */
+    public static void didOpenStream()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            openStream--;
+            if (openStream < 0)
+                openStream = 0;
+
+            openStreamSyncRoot.notifyAll();
+        }
+    }
+
+    /**
+     * Notifies <tt>MacCoreaudioSystem</tt> that a MacCoreaudio client finished
+     * executing <tt>UpdateAvailableDeviceList</tt>.
+     */
+    private static void didUpdateAvailableDeviceList()
+    {
+        synchronized(openStreamSyncRoot)
+        {
+            updateAvailableDeviceList--;
+            if (updateAvailableDeviceList < 0)
+                updateAvailableDeviceList = 0;
+
+            openStreamSyncRoot.notifyAll();
+        }
+
+        fireUpdateAvailableDeviceListEvent(false);
+    }
+
+    /**
+     * Notifies the registered <tt>UpdateAvailableDeviceListListener</tt>s
+     * that MacCoreaudio's native function
+     * <tt>UpdateAvailableDeviceList()</tt> will be or was invoked.
+     *
+     * @param will <tt>true</tt> if MacCoreaudio's native function
+     * <tt>UpdateAvailableDeviceList()</tt> will be invoked or <tt>false</tt>
+     * if it was invoked
+     */
+    private static void fireUpdateAvailableDeviceListEvent(boolean will)
+    {
+        try
+        {
+            List<WeakReference<UpdateAvailableDeviceListListener>> ls;
+
+            synchronized(updateAvailableDeviceListListeners)
+            {
+                ls = new
+                    ArrayList<WeakReference<UpdateAvailableDeviceListListener>>(
+                            updateAvailableDeviceListListeners);
+            }
+
+            for(WeakReference<UpdateAvailableDeviceListListener> wr : ls)
+            {
+                UpdateAvailableDeviceListListener l = wr.get();
+                if(l != null)
+                {
+                    try
+                    {
+                        if(will)
+                            l.willUpdateAvailableDeviceList();
+                        else
+                            l.didUpdateAvailableDeviceList();
+                    }
+                    catch (Throwable t)
+                    {
+                        if(t instanceof ThreadDeath)
+                            throw(ThreadDeath) t;
+                        else
+                        {
+                            logger.error(
+                                    "UpdateAvailableDeviceListListener."
+                                    + (will ? "will" : "did")
+                                    + "UpdateAvailableDeviceList failed.",
+                                    t);
+                        }
+                    }
+                }
+            }
+        }
+        catch(Throwable t)
+        {
+            if(t instanceof ThreadDeath)
+                throw(ThreadDeath) t;
+        }
+    }
+
+    /**
+     * Gets a sample rate supported by a MacCoreaudio device with a specific
+     * device index with which it is to be registered with JMF.
+     *
+     * @param input <tt>true</tt> if the supported sample rate is to be
+     * retrieved for the MacCoreaudio device with the specified device index as
+     * an input device or <tt>false</tt> for an output device
+     * @param deviceUID The device identifier.
+     * @param channelCount number of channel
+     * @param sampleFormat sample format
+     *
+     * @return a sample rate supported by the MacCoreaudio device with the
+     * specified device index with which it is to be registered with JMF
+     */
+    private static double getSupportedSampleRate(
+            boolean input,
+            String deviceUID)
+    {
+        double supportedSampleRate = MacCoreAudioDevice.DEFAULT_SAMPLE_RATE;
+        double defaultSampleRate
+            = MacCoreAudioDevice.getNominalSampleRate(deviceUID);
+
+        if (defaultSampleRate >= MediaUtils.MAX_AUDIO_SAMPLE_RATE)
+        {
+            supportedSampleRate = defaultSampleRate;
+        }
+
+        return supportedSampleRate;
+    }
+
+    /**
+     * Waits for all MacCoreaudio clients to finish executing
+     * <tt>OpenStream</tt>.
+     */
+    private static void waitForOpenStream()
+    {
+        boolean interrupted = false;
+
+        while(openStream > 0)
+        {
+            try
+            {
+                openStreamSyncRoot.wait();
+            }
+            catch(InterruptedException ie)
+            {
+                interrupted = true;
+            }
+        }
+        if(interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    /**
+     * Waits for all MacCoreaudio clients to finish executing
+     * <tt>UpdateAvailableDeviceList</tt>.
+     */
+    private static void waitForUpdateAvailableDeviceList()
+    {
+        boolean interrupted = false;
+
+        while (updateAvailableDeviceList > 0)
+        {
+            try
+            {
+                openStreamSyncRoot.wait();
+            }
+            catch (InterruptedException ie)
+            {
+                interrupted = true;
+            }
+        }
+        if (interrupted)
+            Thread.currentThread().interrupt();
+    }
+
+    /**
+     * Notifies <tt>MacCoreaudioSystem</tt> that a MacCoreaudio client will
+     * start executing <tt>OpenStream</tt>.
+     */
+    public static void willOpenStream()
+    {
+        synchronized (openStreamSyncRoot)
+        {
+            waitForUpdateAvailableDeviceList();
+
+            openStream++;
+            openStreamSyncRoot.notifyAll();
+        }
+    }
+
+    /**
+     * Notifies <tt>MacCoreaudioSystem</tt> that a MacCoreaudio client will
+     * start executing <tt>UpdateAvailableDeviceList</tt>.
+     */
+    private static void willUpdateAvailableDeviceList()
+    {
+        synchronized(openStreamSyncRoot)
+        {
+            waitForOpenStream();
+
+            updateAvailableDeviceList++;
+            openStreamSyncRoot.notifyAll();
+        }
+
+        fireUpdateAvailableDeviceListEvent(true);
+    }
+
+    private Runnable devicesChangedCallback;
+
+    /**
+     * Initializes a new <tt>MacCoreaudioSystem</tt> instance which creates
+     * MacCoreaudio capture and playback devices by enumerating all host devices
+     * with input channels.
+     *
+     * @throws Exception if anything wrong happens while creating the
+     * MacCoreaudio capture and playback devices
+     */
+    MacCoreaudioSystem()
+        throws Exception
+    {
+        super(
+                LOCATOR_PROTOCOL,
+                0 | FEATURE_NOTIFY_AND_PLAYBACK_DEVICES | FEATURE_REINITIALIZE);
+    }
+
+    /**
+     * Sorts a specific list of <tt>CaptureDeviceInfo2</tt>s so that the
+     * ones representing USB devices appear at the beginning/top of the
+     * specified list.
+     *
+     * @param devices the list of <tt>CaptureDeviceInfo2</tt>s to be
+     * sorted so that the ones representing USB devices appear at the
+     * beginning/top of the list
+     */
+    private void bubbleUpUsbDevices(List<CaptureDeviceInfo2> devices)
+    {
+        if(!devices.isEmpty())
+        {
+            List<CaptureDeviceInfo2> nonUsbDevices
+                = new ArrayList<CaptureDeviceInfo2>(devices.size());
+
+            for(Iterator<CaptureDeviceInfo2> i = devices.iterator();
+                    i.hasNext();)
+            {
+                CaptureDeviceInfo2 d = i.next();
+
+                if(!d.isSameTransportType("USB"))
+                {
+                    nonUsbDevices.add(d);
+                    i.remove();
+                }
+            }
+            if(!nonUsbDevices.isEmpty())
+            {
+                for (CaptureDeviceInfo2 d : nonUsbDevices)
+                    devices.add(d);
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doInitialize()
+        throws Exception
+    {
+        if(!CoreAudioDevice.isLoaded)
+        {
+            String message = "MacOSX CoreAudio library is not loaded";
+            if (logger.isInfoEnabled())
+            {
+                logger.info(message);
+            }
+            throw new Exception(message);
+        }
+
+        // Initializes the library only at the first run.
+        if(devicesChangedCallback == null)
+        {
+            CoreAudioDevice.initDevices();
+        }
+
+        int channels = 1;
+        int sampleSizeInBits = 16;
+        String defaultInputdeviceUID
+            = MacCoreAudioDevice.getDefaultInputDeviceUID();
+        String defaultOutputdeviceUID
+            = MacCoreAudioDevice.getDefaultOutputDeviceUID();
+        List<CaptureDeviceInfo2> captureAndPlaybackDevices
+            = new LinkedList<CaptureDeviceInfo2>();
+        List<CaptureDeviceInfo2> captureDevices
+            = new LinkedList<CaptureDeviceInfo2>();
+        List<CaptureDeviceInfo2> playbackDevices
+            = new LinkedList<CaptureDeviceInfo2>();
+        final boolean loggerIsDebugEnabled = logger.isDebugEnabled();
+
+
+        String[] deviceUIDList = MacCoreAudioDevice.getDeviceUIDList();
+        for(int i = 0; i < deviceUIDList.length; ++i)
+        {
+            String deviceUID = deviceUIDList[i];
+            String name = CoreAudioDevice.getDeviceName(deviceUID);
+            boolean isInputDevice = MacCoreAudioDevice.isInputDevice(deviceUID);
+            boolean isOutputDevice
+                = MacCoreAudioDevice.isOutputDevice(deviceUID);
+            String transportType
+                = MacCoreAudioDevice.getTransportType(deviceUID);
+            String modelIdentifier = null;
+            String locatorRemainder = name;
+
+            if (deviceUID != null)
+            {
+                modelIdentifier
+                    = CoreAudioDevice.getDeviceModelIdentifier(deviceUID);
+                locatorRemainder = deviceUID;
+            }
+
+            /*
+             * TODO The intention of reinitialize() was to perform the
+             * initialization from scratch. However, AudioSystem was later
+             * changed to disobey. But we should at least search through both
+             * CAPTURE_INDEX and PLAYBACK_INDEX.
+             */
+            List<CaptureDeviceInfo2> existingCdis
+                = getDevices(DataFlow.CAPTURE);
+            CaptureDeviceInfo2 cdi = null;
+
+            if (existingCdis != null)
+            {
+                for (CaptureDeviceInfo2 existingCdi : existingCdis)
+                {
+                    /*
+                     * The deviceUID is optional so a device may be identified
+                     * by deviceUID if it is available or by name if the
+                     * deviceUID is not available.
+                     */
+                    String id = existingCdi.getIdentifier();
+
+                    if (id.equals(deviceUID) || id.equals(name))
+                    {
+                        cdi = existingCdi;
+                        break;
+                    }
+                }
+            }
+
+            if (cdi == null)
+            {
+                cdi
+                    = new CaptureDeviceInfo2(
+                            name,
+                            new MediaLocator(
+                                LOCATOR_PROTOCOL + ":#" + locatorRemainder),
+                            new Format[]
+                            {
+                                new AudioFormat(
+                                    AudioFormat.LINEAR,
+                                    isInputDevice
+                                    ? getSupportedSampleRate(
+                                        true,
+                                        deviceUID)
+                                    : MacCoreAudioDevice.DEFAULT_SAMPLE_RATE,
+                                    sampleSizeInBits,
+                                    channels,
+                                    AudioFormat.LITTLE_ENDIAN,
+                                    AudioFormat.SIGNED,
+                                    Format.NOT_SPECIFIED /* frameSizeInBits */,
+                                    Format.NOT_SPECIFIED /* frameRate */,
+                                    Format.byteArray)
+                            },
+                    deviceUID,
+                    transportType,
+                    modelIdentifier);
+            }
+
+            boolean isDefaultInputDevice
+                = deviceUID.equals(defaultInputdeviceUID);
+            boolean isDefaultOutputDevice
+                = deviceUID.equals(defaultOutputdeviceUID);
+
+            /*
+             * When we perform automatic selection of capture and
+             * playback/notify devices, we would like to pick up devices from
+             * one and the same hardware because that sound like a natural
+             * expectation from the point of view of the user. In order to
+             * achieve that, we will bring the devices which support both
+             * capture and playback to the top.
+             */
+            if(isInputDevice)
+            {
+                List<CaptureDeviceInfo2> devices;
+
+                if(isOutputDevice)
+                    devices = captureAndPlaybackDevices;
+                else
+                    devices = captureDevices;
+
+                if(isDefaultInputDevice
+                        || (isOutputDevice && isDefaultOutputDevice))
+                {
+                    devices.add(0, cdi);
+                    if (loggerIsDebugEnabled)
+                        logger.debug("Added default capture device: " + name);
+                }
+                else
+                {
+                    devices.add(cdi);
+                    if (loggerIsDebugEnabled)
+                        logger.debug("Added capture device: " + name);
+                }
+
+                if(loggerIsDebugEnabled && isInputDevice)
+                {
+                    if(isDefaultOutputDevice)
+                        logger.debug("Added default playback device: " + name);
+                    else
+                        logger.debug("Added playback device: " + name);
+                }
+            }
+            else if(isOutputDevice)
+            {
+                if(isDefaultOutputDevice)
+                {
+                    playbackDevices.add(0, cdi);
+                    if (loggerIsDebugEnabled)
+                        logger.debug("Added default playback device: " + name);
+                }
+                else
+                {
+                    playbackDevices.add(cdi);
+                    if (loggerIsDebugEnabled)
+                        logger.debug("Added playback device: " + name);
+                }
+            }
+        }
+
+        /*
+         * Make sure that devices which support both capture and playback are
+         * reported as such and are preferred over devices which support either
+         * capture or playback (in order to achieve our goal to have automatic
+         * selection pick up devices from one and the same hardware).
+         */
+        bubbleUpUsbDevices(captureDevices);
+        bubbleUpUsbDevices(playbackDevices);
+        if(!captureDevices.isEmpty() && !playbackDevices.isEmpty())
+        {
+            /*
+             * Event if we have not been provided with the information regarding
+             * the matching of the capture and playback/notify devices from one
+             * and the same hardware, we may still be able to deduce it by
+             * examining their names.
+             */
+            matchDevicesByName(captureDevices, playbackDevices);
+        }
+        /*
+         * Of course, of highest reliability is the fact that a specific
+         * instance supports both capture and playback.
+         */
+        if(!captureAndPlaybackDevices.isEmpty())
+        {
+            bubbleUpUsbDevices(captureAndPlaybackDevices);
+            for (int i = captureAndPlaybackDevices.size() - 1; i >= 0; i--)
+            {
+                CaptureDeviceInfo2 cdi = captureAndPlaybackDevices.get(i);
+
+                captureDevices.add(0, cdi);
+                playbackDevices.add(0, cdi);
+            }
+        }
+
+        setCaptureDevices(captureDevices);
+        setPlaybackDevices(playbackDevices);
+
+        if(devicesChangedCallback == null)
+        {
+            devicesChangedCallback
+                = new Runnable()
+                {
+                    public void run()
+                    {
+                        try
+                        {
+                            reinitialize();
+                        }
+                        catch (Throwable t)
+                        {
+                            if (t instanceof ThreadDeath)
+                                throw (ThreadDeath) t;
+
+                            logger.warn(
+                                "Failed to reinitialize MacCoreaudio devices",
+                                t);
+                        }
+                    }
+                };
+            CoreAudioDevice.setDevicesChangedCallback(
+                    devicesChangedCallback);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected String getRendererClassName()
+    {
+        return MacCoreaudioRenderer.class.getName();
+    }
+
+    /**
+     * Attempts to reorder specific lists of capture and playback/notify
+     * <tt>CaptureDeviceInfo2</tt>s so that devices from the same
+     * hardware appear at the same indices in the respective lists. The judgment
+     * with respect to the belonging to the same hardware is based on the names
+     * of the specified <tt>CaptureDeviceInfo2</tt>s. The implementation
+     * is provided as a fallback to stand in for scenarios in which more
+     * accurate relevant information is not available.
+     *
+     * @param captureDevices
+     * @param playbackDevices
+     */
+    private void matchDevicesByName(
+            List<CaptureDeviceInfo2> captureDevices,
+            List<CaptureDeviceInfo2> playbackDevices)
+    {
+        Iterator<CaptureDeviceInfo2> captureIter
+            = captureDevices.iterator();
+        Pattern pattern
+            = Pattern.compile(
+                    "array|headphones|microphone|speakers|\\p{Space}|\\(|\\)",
+                    Pattern.CASE_INSENSITIVE);
+        LinkedList<CaptureDeviceInfo2> captureDevicesWithPlayback
+            = new LinkedList<CaptureDeviceInfo2>();
+        LinkedList<CaptureDeviceInfo2> playbackDevicesWithCapture
+            = new LinkedList<CaptureDeviceInfo2>();
+        int count = 0;
+
+        while (captureIter.hasNext())
+        {
+            CaptureDeviceInfo2 captureDevice = captureIter.next();
+            String captureName = captureDevice.getName();
+
+            if (captureName != null)
+            {
+                captureName = pattern.matcher(captureName).replaceAll("");
+                if (captureName.length() != 0)
+                {
+                    Iterator<CaptureDeviceInfo2> playbackIter
+                        = playbackDevices.iterator();
+                    CaptureDeviceInfo2 matchingPlaybackDevice = null;
+
+                    while (playbackIter.hasNext())
+                    {
+                        CaptureDeviceInfo2 playbackDevice
+                            = playbackIter.next();
+                        String playbackName = playbackDevice.getName();
+
+                        if (playbackName != null)
+                        {
+                            playbackName
+                                = pattern
+                                    .matcher(playbackName)
+                                        .replaceAll("");
+                            if (captureName.equals(playbackName))
+                            {
+                                playbackIter.remove();
+                                matchingPlaybackDevice = playbackDevice;
+                                break;
+                            }
+                        }
+                    }
+                    if (matchingPlaybackDevice != null)
+                    {
+                        captureIter.remove();
+                        captureDevicesWithPlayback.add(captureDevice);
+                        playbackDevicesWithCapture.add(
+                                matchingPlaybackDevice);
+                        count++;
+                    }
+                }
+            }
+        }
+
+        for (int i = count - 1; i >= 0; i--)
+        {
+            captureDevices.add(0, captureDevicesWithPlayback.get(i));
+            playbackDevices.add(0, playbackDevicesWithCapture.get(i));
+        }
+    }
+
+    /**
+     * Reinitializes this <tt>MacCoreaudioSystem</tt> in order to bring it up to
+     * date with possible changes in the MacCoreaudio devices. Invokes
+     * <tt>Pa_UpdateAvailableDeviceList()</tt> to update the devices on the
+     * native side and then {@link #initialize()} to reflect any changes on the
+     * Java side. Invoked by MacCoreaudio when it detects that the list of
+     * devices has changed.
+     *
+     * @throws Exception if there was an error during the invocation of
+     * <tt>Pa_UpdateAvailableDeviceList()</tt> and
+     * <tt>DeviceSystem.initialize()</tt>
+     */
+    private void reinitialize()
+        throws Exception
+    {
+        synchronized (updateAvailableDeviceListSyncRoot)
+        {
+            willUpdateAvailableDeviceList();
+            didUpdateAvailableDeviceList();
+        }
+
+        /*
+         * XXX We will likely minimize the risk of crashes on the native side
+         * even further by invoking initialize() with
+         * Pa_UpdateAvailableDeviceList locked. Unfortunately, that will likely
+         * increase the risks of deadlocks on the Java side.
+         */
+        invokeDeviceSystemInitialize(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * The implementation of <tt>MacCoreaudioSystem</tt> always returns
+     * &quot;MacCoreaudio&quot;.
+     */
+    @Override
+    public String toString()
+    {
+        return "MacCoreaudio";
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/DataSource.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/DataSource.java
new file mode 100644
index 00000000..c149899f
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/DataSource.java
@@ -0,0 +1,251 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.jmfext.media.protocol.maccoreaudio;
+
+import java.io.*;
+
+import javax.media.*;
+import javax.media.control.*;
+
+import org.jitsi.impl.neomedia.device.*;
+import org.jitsi.impl.neomedia.jmfext.media.protocol.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements <tt>DataSource</tt> and <tt>CaptureDevice</tt> for MacCoreaudio.
+ *
+ * @author Vincent Lucas
+ */
+public class DataSource
+    extends AbstractPullBufferCaptureDevice
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>DataSource</tt> class and its
+     * instances for logging output.
+     */
+    private static final Logger logger = Logger.getLogger(DataSource.class);
+
+    /**
+     * The indicator which determines whether this <tt>DataSource</tt> will
+     * use audio quality improvement in accord with the preferences of the user.
+     */
+    private final boolean audioQualityImprovement;
+
+    /**
+     * The list of <tt>Format</tt>s in which this <tt>DataSource</tt> is
+     * capable of capturing audio data.
+     */
+    private final Format[] supportedFormats;
+
+    /**
+     * Initializes a new <tt>DataSource</tt> instance.
+     */
+    public DataSource()
+    {
+        this.supportedFormats = null;
+        this.audioQualityImprovement = true;
+    }
+
+    /**
+     * Initializes a new <tt>DataSource</tt> instance from a specific
+     * <tt>MediaLocator</tt>.
+     *
+     * @param locator the <tt>MediaLocator</tt> to create the new instance from
+     */
+    public DataSource(MediaLocator locator)
+    {
+        this(locator, null, true);
+    }
+
+    /**
+     * Initializes a new <tt>DataSource</tt> instance from a specific
+     * <tt>MediaLocator</tt> and which has a specific list of <tt>Format</tt>
+     * in which it is capable of capturing audio data overriding its
+     * registration with JMF and optionally uses audio quality improvement in
+     * accord with the preferences of the user.
+     *
+     * @param locator the <tt>MediaLocator</tt> to create the new instance from
+     * @param supportedFormats the list of <tt>Format</tt>s in which the new
+     * instance is to be capable of capturing audio data
+     * @param audioQualityImprovement <tt>true</tt> if audio quality improvement
+     * is to be enabled in accord with the preferences of the user or
+     * <tt>false</tt> to completely disable audio quality improvement
+     */
+    public DataSource(
+            MediaLocator locator,
+            Format[] supportedFormats,
+            boolean audioQualityImprovement)
+    {
+        super(locator);
+
+        this.supportedFormats
+            = (supportedFormats == null)
+                ? null
+                : supportedFormats.clone();
+        this.audioQualityImprovement = audioQualityImprovement;
+    }
+
+    /**
+     * Creates a new <tt>PullBufferStream</tt> which is to be at a specific
+     * zero-based index in the list of streams of this
+     * <tt>PullBufferDataSource</tt>. The <tt>Format</tt>-related information of
+     * the new instance is to be abstracted by a specific
+     * <tt>FormatControl</tt>.
+     *
+     * @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
+     * in the list of streams of this <tt>PullBufferDataSource</tt>
+     * @param formatControl the <tt>FormatControl</tt> which is to abstract the
+     * <tt>Format</tt>-related information of the new instance
+     * @return a new <tt>PullBufferStream</tt> which is to be at the specified
+     * <tt>streamIndex</tt> in the list of streams of this
+     * <tt>PullBufferDataSource</tt> and which has its <tt>Format</tt>-related
+     * information abstracted by the specified <tt>formatControl</tt>
+     * @see AbstractPullBufferCaptureDevice#createStream(int, FormatControl)
+     */
+    @Override
+    protected MacCoreaudioStream createStream(
+            int streamIndex,
+            FormatControl formatControl)
+    {
+        return new MacCoreaudioStream(
+                this,
+                formatControl,
+                audioQualityImprovement);
+    }
+
+    /**
+     * Opens a connection to the media source specified by the
+     * <tt>MediaLocator</tt> of this <tt>DataSource</tt>.
+     *
+     * @throws IOException if anything goes wrong while opening the connection
+     * to the media source specified by the <tt>MediaLocator</tt> of this
+     * <tt>DataSource</tt>
+     * @see AbstractPullBufferCaptureDevice#doConnect()
+     */
+    @Override
+    protected void doConnect()
+        throws IOException
+    {
+        super.doConnect();
+
+        String deviceID = getDeviceID();
+
+        synchronized (getStreamSyncRoot())
+        {
+            for (Object stream : getStreams())
+                ((MacCoreaudioStream) stream).setDeviceUID(deviceID);
+        }
+    }
+
+    /**
+     * Closes the connection to the media source specified by the
+     * <tt>MediaLocator</tt> of this <tt>DataSource</tt>. Allows extenders to
+     * override and be sure that there will be no request to close a connection
+     * if the connection has not been opened yet.
+     */
+    @Override
+    protected void doDisconnect()
+    {
+        try
+        {
+            synchronized (getStreamSyncRoot())
+            {
+                Object[] streams = streams();
+
+                if (streams != null)
+                {
+                    for (Object stream : streams)
+                    {
+                        ((MacCoreaudioStream) stream).setDeviceUID(null);
+                    }
+                }
+            }
+        }
+        finally
+        {
+            super.doDisconnect();
+        }
+    }
+
+    /**
+     * Gets the device index of the MacCoreaudio device identified by the
+     * <tt>MediaLocator</tt> of this <tt>DataSource</tt>.
+     *
+     * @return the device index of a MacCoreaudio device identified by the
+     * <tt>MediaLocator</tt> of this <tt>DataSource</tt>
+     * @throws IllegalStateException if there is no <tt>MediaLocator</tt>
+     * associated with this <tt>DataSource</tt>
+     */
+    private String getDeviceID()
+    {
+        MediaLocator locator = getLocator();
+
+        if (locator == null)
+            throw new IllegalStateException("locator");
+        else
+            return getDeviceID(locator);
+    }
+
+    /**
+     * Gets the device index of a MacCoreaudio device from a specific
+     * <tt>MediaLocator</tt> identifying it.
+     *
+     * @param locator the <tt>MediaLocator</tt> identifying the device index of
+     * a MacCoreaudio device to get
+     * @return the device index of a MacCoreaudio device identified by
+     * <tt>locator</tt>
+     */
+    public static String getDeviceID(MediaLocator locator)
+    {
+        if (locator == null)
+        {
+            /*
+             * Explicitly throw a NullPointerException because the implicit one
+             * does not have a message and is thus a bit more difficult to
+             * debug.
+             */
+            throw new NullPointerException("locator");
+        }
+        else if (AudioSystem.LOCATOR_PROTOCOL_MACCOREAUDIO.equalsIgnoreCase(
+                locator.getProtocol()))
+        {
+            String remainder = locator.getRemainder();
+
+            if ((remainder != null) && (remainder.charAt(0) == '#'))
+                remainder = remainder.substring(1);
+            return remainder;
+        }
+        else
+        {
+            throw new IllegalArgumentException("locator.protocol");
+        }
+    }
+
+    /**
+     * Gets the <tt>Format</tt>s which are to be reported by a
+     * <tt>FormatControl</tt> as supported formats for a
+     * <tt>PullBufferStream</tt> at a specific zero-based index in the list of
+     * streams of this <tt>PullBufferDataSource</tt>.
+     *
+     * @param streamIndex the zero-based index of the <tt>PullBufferStream</tt>
+     * for which the specified <tt>FormatControl</tt> is to report the list of
+     * supported <tt>Format</tt>s
+     * @return an array of <tt>Format</tt>s to be reported by a
+     * <tt>FormatControl</tt> as the supported formats for the
+     * <tt>PullBufferStream</tt> at the specified <tt>streamIndex</tt> in the
+     * list of streams of this <tt>PullBufferDataSource</tt>
+     * @see AbstractPullBufferCaptureDevice#getSupportedFormats(int)
+     */
+    @Override
+    protected Format[] getSupportedFormats(int streamIndex)
+    {
+        return
+            (supportedFormats == null)
+                ? super.getSupportedFormats(streamIndex)
+                : supportedFormats;
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/MacCoreaudioStream.java b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/MacCoreaudioStream.java
new file mode 100644
index 00000000..40711edb
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/protocol/maccoreaudio/MacCoreaudioStream.java
@@ -0,0 +1,440 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.jmfext.media.protocol.maccoreaudio;
+
+import java.io.*;
+import java.util.*;
+
+import javax.media.*;
+import javax.media.control.*;
+import javax.media.format.*;
+
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.codec.*;
+import org.jitsi.impl.neomedia.control.*;
+import org.jitsi.impl.neomedia.device.*;
+import org.jitsi.impl.neomedia.jmfext.media.protocol.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.util.*;
+
+/**
+ * Implements <tt>PullBufferStream</tt> for MacCoreaudio.
+ *
+ * @author Vincent Lucas
+ */
+public class MacCoreaudioStream
+    extends AbstractPullBufferStream<DataSource>
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>MacCoreaudioStream</tt> class and its
+     * instances for logging output.
+     */
+    private static final Logger logger
+        = Logger.getLogger(MacCoreaudioStream.class);
+
+    /**
+     * The indicator which determines whether audio quality improvement is
+     * enabled for this <tt>MacCoreaudioStream</tt> in accord with the
+     * preferences of the user.
+     */
+    private final boolean audioQualityImprovement;
+
+    /**
+     * The number of bytes to read from a native MacCoreaudio stream in a single
+     * invocation. Based on {@link #framesPerBuffer}.
+     */
+    private int bytesPerBuffer;
+
+    /**
+     * The device identifier (the device UID, or if not available, the device
+     * name) of the MacCoreaudio device read through this
+     * <tt>PullBufferStream</tt>.
+     */
+    private String deviceUID;
+
+    /**
+     * A mutual eclusion used to avoid conflict when starting / stoping the
+     * stream for this stream;
+     */
+    private Object startStopMutex = new Object();
+
+    /**
+     * The buffer which stores the outgoing data before sending them to
+     * the RTP stack.
+     */
+    private byte[] buffer = null;
+
+    /**
+     * A list of already allocated buffers, ready to accept new captured data.
+     */
+    private Vector<byte[]> freeBufferList = new Vector<byte[]>();
+
+    /**
+     * A list of already allocated and filled buffers, ready to be send throw
+     * the network.
+     */
+    private Vector<byte[]> fullBufferList = new Vector<byte[]>();
+
+    /**
+     * The number of data available to feed the RTP stack.
+     */
+    private int nbBufferData = 0;
+
+    /**
+     * The last-known <tt>Format</tt> of the media data made available by this
+     * <tt>PullBufferStream</tt>.
+     */
+    private AudioFormat format = null;
+
+    /**
+     * The <tt>GainControl</tt> through which the volume/gain of captured media
+     * is controlled.
+     */
+    private final GainControl gainControl;
+
+    private final MacCoreaudioSystem.UpdateAvailableDeviceListListener
+        updateAvailableDeviceListListener
+            = new MacCoreaudioSystem.UpdateAvailableDeviceListListener()
+            {
+                /**
+                 * The device ID (could be deviceUID or name but that is not
+                 * really of concern to MacCoreaudioStream) used before and
+                 * after (if still available) the update.
+                 */
+                private String deviceUID = null;
+
+                private boolean start = false;
+
+                public void didUpdateAvailableDeviceList()
+                    throws Exception
+                {
+                    synchronized(startStopMutex)
+                    {
+                        setDeviceUID(deviceUID);
+                        if(start)
+                            start();
+                        deviceUID = null;
+                        start = false;
+                    }
+                }
+
+                public void willUpdateAvailableDeviceList()
+                    throws Exception
+                {
+                    synchronized(startStopMutex)
+                    {
+                        if (stream == 0)
+                        {
+                            deviceUID = null;
+                            start = false;
+                        }
+                        else
+                        {
+                            deviceUID = MacCoreaudioStream.this.deviceUID;
+                            start = true;
+                            stop();
+                            setDeviceUID(null);
+                        }
+                    }
+                }
+            };
+
+    /**
+     * Current sequence number.
+     */
+    private int sequenceNumber = 0;
+
+    /**
+     * The stream structure used by the native maccoreaudio library.
+     */
+    private long stream = 0;
+
+    /**
+     * Initializes a new <tt>MacCoreaudioStream</tt> instance which is to have
+     * its <tt>Format</tt>-related information abstracted by a specific
+     * <tt>FormatControl</tt>.
+     *
+     * @param dataSource the <tt>DataSource</tt> which is creating the new
+     * instance so that it becomes one of its <tt>streams</tt>
+     * @param formatControl the <tt>FormatControl</tt> which is to abstract the
+     * <tt>Format</tt>-related information of the new instance
+     * @param audioQualityImprovement <tt>true</tt> to enable audio quality
+     * improvement for the new instance in accord with the preferences of the
+     * user or <tt>false</tt> to completely disable audio quality improvement
+     */
+    public MacCoreaudioStream(
+            DataSource dataSource,
+            FormatControl formatControl,
+            boolean audioQualityImprovement)
+    {
+        super(dataSource, formatControl);
+
+        this.audioQualityImprovement = audioQualityImprovement;
+
+        MediaServiceImpl mediaServiceImpl
+            = NeomediaServiceUtils.getMediaServiceImpl();
+
+        gainControl = (mediaServiceImpl == null)
+            ? null
+            : (GainControl) mediaServiceImpl.getInputVolumeControl();
+
+        // XXX We will add a UpdateAvailableDeviceListListener and will not
+        // remove it because we will rely on MacCoreaudioSystem's use of
+        // WeakReference.
+        MacCoreaudioSystem.addUpdateAvailableDeviceListListener(
+                updateAvailableDeviceListListener);
+    }
+
+    private void connect()
+    {
+        AudioFormat format = (AudioFormat) getFormat();
+        int channels = format.getChannels();
+        if (channels == Format.NOT_SPECIFIED)
+            channels = 1;
+        int sampleSizeInBits = format.getSampleSizeInBits();
+        double sampleRate = format.getSampleRate();
+        int framesPerBuffer
+            = (int) ((sampleRate * MacCoreAudioDevice.DEFAULT_MILLIS_PER_BUFFER)
+                    / (channels * 1000));
+        bytesPerBuffer = (sampleSizeInBits / 8) * channels * framesPerBuffer;
+
+        // Know the Format in which this MacCoreaudioStream will output audio
+        // data so that it can report it without going through its DataSource.
+        this.format = new AudioFormat(
+                AudioFormat.LINEAR,
+                sampleRate,
+                sampleSizeInBits,
+                channels,
+                AudioFormat.LITTLE_ENDIAN,
+                AudioFormat.SIGNED,
+                Format.NOT_SPECIFIED /* frameSizeInBits */,
+                Format.NOT_SPECIFIED /* frameRate */,
+                Format.byteArray);
+    }
+
+    /**
+     * Gets the <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
+     * known by it.
+     *
+     * @return the <tt>Format</tt> of this <tt>PullBufferStream</tt> as directly
+     * known by it or <tt>null</tt> if this <tt>PullBufferStream</tt> does not
+     * directly know its <tt>Format</tt> and it relies on the
+     * <tt>PullBufferDataSource</tt> which created it to report its
+     * <tt>Format</tt>
+     * @see AbstractPullBufferStream#doGetFormat()
+     */
+    @Override
+    protected Format doGetFormat()
+    {
+        return (format == null) ? super.doGetFormat() : format;
+    }
+
+    /**
+     * Reads media data from this <tt>PullBufferStream</tt> into a specific
+     * <tt>Buffer</tt> with blocking.
+     *
+     * @param buffer the <tt>Buffer</tt> in which media data is to be read from
+     * this <tt>PullBufferStream</tt>
+     * @throws IOException if anything goes wrong while reading media data from
+     * this <tt>PullBufferStream</tt> into the specified <tt>buffer</tt>
+     */
+    public void read(Buffer buffer)
+        throws IOException
+    {
+        int length = 0;
+        byte[] data = AbstractCodec2.validateByteArraySize(
+                        buffer,
+                        bytesPerBuffer,
+                        false);
+
+        synchronized(startStopMutex)
+        {
+            // Waits for the next buffer.
+            while(this.fullBufferList.size() == 0 && stream != 0)
+            {
+                try
+                {
+                    startStopMutex.wait();
+                }
+                catch(InterruptedException ex)
+                {}
+            }
+
+            // If the stream is running.
+            if(stream != 0)
+            {
+                this.freeBufferList.add(data);
+                data = this.fullBufferList.remove(0);
+                length = data.length;
+            }
+        }
+
+        // Take into account the user's preferences with respect to the
+        // input volume.
+        if(length != 0 && gainControl != null)
+        {
+            BasicVolumeControl.applyGain(
+                    gainControl,
+                    data,
+                    0,
+                    length);
+        }
+
+        long bufferTimeStamp = System.nanoTime();
+
+        buffer.setData(data);
+        buffer.setFlags(Buffer.FLAG_SYSTEM_TIME);
+        if (format != null)
+            buffer.setFormat(format);
+        buffer.setHeader(null);
+        buffer.setLength(length);
+        buffer.setOffset(0);
+        buffer.setSequenceNumber(sequenceNumber++);
+        buffer.setTimeStamp(bufferTimeStamp);
+    }
+
+    /**
+     * Sets the device index of the MacCoreaudio device to be read through this
+     * <tt>PullBufferStream</tt>.
+     *
+     * @param deviceID The ID of the device used to be read trough this
+     * MacCoreaudioStream.  This String contains the deviceUID, or if not
+     * available, the device name.  If set to null, then there was no device
+     * used before the update.
+     */
+    void setDeviceUID(String deviceUID)
+    {
+        synchronized(startStopMutex)
+        {
+            if (this.deviceUID != null)
+            {
+                // If there is a running stream, then close it.
+                try
+                {
+                    stop();
+                }
+                catch(IOException ioex)
+                {
+                    logger.info(ioex);
+                }
+
+                // Make sure this AbstractPullBufferStream asks its DataSource
+                // for the Format in which it is supposed to output audio data
+                // the next time it is opened instead of using its Format from a
+                // previous open.
+                this.format = null;
+            }
+            this.deviceUID = deviceUID;
+
+            if (this.deviceUID != null)
+            {
+                connect();
+            }
+        }
+    }
+
+    /**
+     * Starts the transfer of media data from this <tt>PullBufferStream</tt>.
+     */
+    @Override
+    public void start()
+        throws IOException
+    {
+        synchronized(startStopMutex)
+        {
+            if(stream == 0 && deviceUID != null)
+            {
+                buffer = new byte[bytesPerBuffer];
+                nbBufferData = 0;
+                this.fullBufferList.clear();
+                this.freeBufferList.clear();
+
+                MacCoreaudioSystem.willOpenStream();
+                stream = MacCoreAudioDevice.startStream(
+                        deviceUID,
+                        this,
+                        (float) format.getSampleRate(),
+                        format.getChannels(),
+                        format.getSampleSizeInBits(),
+                        false,
+                        format.getEndian() == AudioFormat.BIG_ENDIAN,
+                        false);
+                MacCoreaudioSystem.didOpenStream();
+            }
+        }
+    }
+
+    /**
+     * Stops the transfer of media data from this <tt>PullBufferStream</tt>.
+     */
+    @Override
+    public void stop()
+        throws IOException
+    {
+        synchronized(startStopMutex)
+        {
+            if(stream != 0 && deviceUID != null)
+            {
+                MacCoreAudioDevice.stopStream(deviceUID, stream);
+                stream = 0;
+                this.fullBufferList.clear();
+                this.freeBufferList.clear();
+                startStopMutex.notify();
+            }
+        }
+    }
+
+    /**
+     * Callback which receives the data from the coreaudio library.
+     *
+     * @param buffer The data captured from the input.
+     * @param bufferLength The length of the data captured.
+     */
+    public void readInput(byte[] buffer, int bufferLength)
+    {
+        int nbCopied = 0;
+        while(bufferLength > 0)
+        {
+            int length = this.buffer.length - nbBufferData;
+            if(bufferLength < length)
+            {
+                length = bufferLength;
+            }
+
+            System.arraycopy(
+                    buffer,
+                    nbCopied,
+                    this.buffer,
+                    nbBufferData,
+                    length);
+
+            nbBufferData += length;
+            nbCopied += length;
+            bufferLength -= length;
+
+            if(nbBufferData == this.buffer.length)
+            {
+                this.fullBufferList.add(this.buffer);
+                this.buffer = null;
+                nbBufferData = 0;
+                synchronized(startStopMutex)
+                {
+                    startStopMutex.notify();
+                    if(this.freeBufferList.size() > 0)
+                    {
+                        this.buffer = this.freeBufferList.remove(0);
+                    }
+                }
+
+                if(this.buffer == null)
+                {
+                    this.buffer = new byte[bytesPerBuffer];
+                }
+            }
+        }
+    }
+}
diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/MacCoreaudioRenderer.java b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/MacCoreaudioRenderer.java
new file mode 100644
index 00000000..1657c645
--- /dev/null
+++ b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/MacCoreaudioRenderer.java
@@ -0,0 +1,592 @@
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package org.jitsi.impl.neomedia.jmfext.media.renderer.audio;
+
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.media.*;
+import javax.media.format.*;
+
+import org.jitsi.impl.neomedia.*;
+import org.jitsi.impl.neomedia.control.*;
+import org.jitsi.impl.neomedia.device.*;
+import org.jitsi.service.neomedia.*;
+import org.jitsi.util.*;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Implements an audio <tt>Renderer</tt> which uses MacOSX Coreaudio.
+ *
+ * @author Vincent Lucas
+ */
+public class MacCoreaudioRenderer
+    extends AbstractAudioRenderer<MacCoreaudioSystem>
+{
+    /**
+     * The <tt>Logger</tt> used by the <tt>MacCoreaudioRenderer</tt> class and
+     * its instances for logging output.
+     */
+    private static final Logger logger
+        = Logger.getLogger(MacCoreaudioRenderer.class);
+
+    /**
+     * The device used for this renderer.
+     */
+    private String deviceUID = null;
+
+    /**
+     * The stream structure used by the native maccoreaudio library.
+     */
+    private long stream = 0;
+
+    /**
+     * A mutual eclusion used to avoid conflict when starting / stoping the
+     * stream for this renderer;
+     */
+    private Object startStopMutex = new Object();
+
+    /**
+     * The buffer which stores th incoming data before sending them to
+     * CoreAudio.
+     */
+    private byte[] buffer = null;
+
+    /**
+     * The number of data available to feed CoreAudio output.
+     */
+    private int nbBufferData = 0;
+
+    private boolean isStopping = false;
+
+    /**
+     * The constant which represents an empty array with
+     * <tt>Format</tt> element type. Explicitly defined in order to
+     * reduce unnecessary allocations.
+     */
+    private static final Format[] EMPTY_SUPPORTED_INPUT_FORMATS
+        = new Format[0];
+
+    /**
+     * The human-readable name of the <tt>MacCoreaudioRenderer</tt> JMF plug-in.
+     */
+    private static final String PLUGIN_NAME = "MacCoreaudio Renderer";
+
+    /**
+     * The list of JMF <tt>Format</tt>s of audio data which
+     * <tt>MacCoreaudioRenderer</tt> instances are capable of rendering.
+     */
+    private static final Format[] SUPPORTED_INPUT_FORMATS;
+
+    /**
+     * The list of the sample rates supported by <tt>MacCoreaudioRenderer</tt>
+     * as input.
+     */
+    private static final double[] SUPPORTED_INPUT_SAMPLE_RATES
+        = new double[] { 8000, 11025, 16000, 22050, 32000, 44100, 48000 };
+
+    static
+    {
+        int count = SUPPORTED_INPUT_SAMPLE_RATES.length;
+
+        SUPPORTED_INPUT_FORMATS = new Format[count];
+        for (int i = 0; i < count; i++)
+        {
+            SUPPORTED_INPUT_FORMATS[i]
+                = new AudioFormat(
+                        AudioFormat.LINEAR,
+                        SUPPORTED_INPUT_SAMPLE_RATES[i],
+                        16 /* sampleSizeInBits */,
+                        Format.NOT_SPECIFIED /* channels */,
+                        AudioFormat.LITTLE_ENDIAN,
+                        AudioFormat.SIGNED,
+                        Format.NOT_SPECIFIED /* frameSizeInBits */,
+                        Format.NOT_SPECIFIED /* frameRate */,
+                        Format.byteArray);
+        }
+    }
+
+    /**
+     * The <tt>UpdateAvailableDeviceListListener</tt> which is to be notified
+     * before and after MacCoreaudio's native function
+     * <tt>UpdateAvailableDeviceList()</tt> is invoked. It will close
+     * {@link #stream} before the invocation in order to mitigate memory
+     * corruption afterwards and it will attempt to restore the state of this
+     * <tt>Renderer</tt> after the invocation.
+     */
+    private final MacCoreaudioSystem.UpdateAvailableDeviceListListener
+        updateAvailableDeviceListListener
+            = new MacCoreaudioSystem.UpdateAvailableDeviceListListener()
+    {
+        private boolean start = false;
+
+        public void didUpdateAvailableDeviceList()
+            throws Exception
+        {
+            synchronized(startStopMutex)
+            {
+                updateDeviceUID();
+                if(start)
+                {
+                    open();
+                    start();
+                }
+            }
+        }
+
+        public void willUpdateAvailableDeviceList()
+            throws Exception
+        {
+            synchronized(startStopMutex)
+            {
+                start = false;
+                if(stream != 0)
+                {
+                    start = true;
+                    stop();
+                }
+            }
+        }
+    };
+
+    /**
+     * Array of supported input formats.
+     */
+    private Format[] supportedInputFormats;
+
+    /**
+     * Initializes a new <tt>MacCoreaudioRenderer</tt> instance.
+     */
+    public MacCoreaudioRenderer()
+    {
+        this(true);
+    }
+
+    /**
+     * Initializes a new <tt>MacCoreaudioRenderer</tt> instance which is to
+     * either perform playback or sound a notification.
+     *
+     * @param playback <tt>true</tt> if the new instance is to perform playback
+     * or <tt>false</tt> if the new instance is to sound a notification
+     */
+    public MacCoreaudioRenderer(boolean enableVolumeControl)
+    {
+        super(
+                AudioSystem.LOCATOR_PROTOCOL_MACCOREAUDIO,
+                enableVolumeControl
+                    ? AudioSystem.DataFlow.PLAYBACK
+                    : AudioSystem.DataFlow.NOTIFY);
+
+        // XXX We will add a PaUpdateAvailableDeviceListListener and will not
+        // remove it because we will rely on MacCoreaudioSystem's use of
+        // WeakReference.
+        MacCoreaudioSystem.addUpdateAvailableDeviceListListener(
+                updateAvailableDeviceListListener);
+    }
+
+    /**
+     * Closes this <tt>PlugIn</tt>.
+     */
+    @Override
+    public void close()
+    {
+        stop();
+        super.close();
+    }
+
+    /**
+     * Gets the descriptive/human-readable name of this JMF plug-in.
+     *
+     * @return the descriptive/human-readable name of this JMF plug-in
+     */
+    public String getName()
+    {
+        return PLUGIN_NAME;
+    }
+
+    /**
+     * Gets the list of JMF <tt>Format</tt>s of audio data which this
+     * <tt>Renderer</tt> is capable of rendering.
+     *
+     * @return an array of JMF <tt>Format</tt>s of audio data which this
+     * <tt>Renderer</tt> is capable of rendering
+     */
+    @Override
+    public Format[] getSupportedInputFormats()
+    {
+        if (supportedInputFormats == null)
+        {
+            MediaLocator locator = getLocator();
+            this.updateDeviceUID();
+
+            if(deviceUID == null)
+            {
+                supportedInputFormats = SUPPORTED_INPUT_FORMATS;
+            }
+            else
+            {
+                int minOutputChannels = 1;
+                // The maximum output channels may be a lot and checking all of
+                // them will take a lot of time. Besides, we currently support
+                // at most 2.
+                int maxOutputChannels
+                    = Math.min(
+                            MacCoreAudioDevice.countOutputChannels(deviceUID),
+                            2);
+                List<Format> supportedInputFormats
+                    = new ArrayList<Format>(SUPPORTED_INPUT_FORMATS.length);
+
+                for (Format supportedInputFormat : SUPPORTED_INPUT_FORMATS)
+                {
+                    getSupportedInputFormats(
+                            supportedInputFormat,
+                            minOutputChannels,
+                            maxOutputChannels,
+                            supportedInputFormats);
+                }
+
+                this.supportedInputFormats
+                    = supportedInputFormats.isEmpty()
+                        ? EMPTY_SUPPORTED_INPUT_FORMATS
+                        : supportedInputFormats.toArray(
+                                EMPTY_SUPPORTED_INPUT_FORMATS);
+            }
+        }
+        return
+            (supportedInputFormats.length == 0)
+                ? EMPTY_SUPPORTED_INPUT_FORMATS
+                : supportedInputFormats.clone();
+    }
+
+    private void getSupportedInputFormats(
+            Format format,
+            int minOutputChannels,
+            int maxOutputChannels,
+            List<Format> supportedInputFormats)
+    {
+        AudioFormat audioFormat = (AudioFormat) format;
+        int sampleSizeInBits = audioFormat.getSampleSizeInBits();
+        double sampleRate = audioFormat.getSampleRate();
+        float minRate
+            = MacCoreAudioDevice.getMinimalNominalSampleRate(deviceUID);
+        float maxRate
+            = MacCoreAudioDevice.getMaximalNominalSampleRate(deviceUID);
+
+        for(int channels = minOutputChannels;
+                channels <= maxOutputChannels;
+                channels++)
+        {
+            if(sampleRate >= minRate && sampleRate <= maxRate)
+            {
+                supportedInputFormats.add(
+                        new AudioFormat(
+                            audioFormat.getEncoding(),
+                            sampleRate,
+                            sampleSizeInBits,
+                            channels,
+                            audioFormat.getEndian(),
+                            audioFormat.getSigned(),
+                            Format.NOT_SPECIFIED, // frameSizeInBits
+                            Format.NOT_SPECIFIED, // frameRate
+                            audioFormat.getDataType()));
+            }
+        }
+    }
+
+    /**
+     * Opens the MacCoreaudio device and output stream represented by this
+     * instance which are to be used to render audio.
+     *
+     * @throws ResourceUnavailableException if the MacCoreaudio device or output
+     * stream cannot be created or opened
+     */
+    @Override
+    public void open()
+        throws ResourceUnavailableException
+    {
+        synchronized(startStopMutex)
+        {
+            if(stream == 0)
+            {
+                MacCoreaudioSystem.willOpenStream();
+                try
+                {
+                    if(!this.updateDeviceUID())
+                    {
+                        throw new ResourceUnavailableException(
+                                "No locator/MediaLocator is set.");
+                    }
+                }
+                finally
+                {
+                    MacCoreaudioSystem.didOpenStream();
+                }
+
+            }
+            super.open();
+        }
+    }
+
+    /**
+     * Notifies this instance that the value of the
+     * {@link AudioSystem#PROP_PLAYBACK_DEVICE} property of its associated
+     * <tt>AudioSystem</tt> has changed.
+     *
+     * @param ev a <tt>PropertyChangeEvent</tt> which specifies details about
+     * the change such as the name of the property and its old and new values
+     */
+    @Override
+    protected synchronized void playbackDevicePropertyChange(
+            PropertyChangeEvent ev)
+    {
+        synchronized(startStopMutex)
+        {
+            stop();
+            updateDeviceUID();
+            start();
+        }
+    }
+
+    /**
+     * Renders the audio data contained in a specific <tt>Buffer</tt> onto the
+     * MacCoreaudio device represented by this <tt>Renderer</tt>.
+     *
+     * @param buffer the <tt>Buffer</tt> which contains the audio data to be
+     * rendered
+     * @return <tt>BUFFER_PROCESSED_OK</tt> if the specified <tt>buffer</tt> has
+     * been successfully processed
+     */
+    public int process(Buffer buffer)
+    {
+        synchronized(startStopMutex)
+        {
+            if(stream != 0 && !isStopping)
+            {
+                // Take into account the user's preferences with respect to the
+                // output volume.
+                GainControl gainControl = getGainControl();
+                if (gainControl != null)
+                {
+                    BasicVolumeControl.applyGain(
+                            gainControl,
+                            (byte[]) buffer.getData(),
+                            buffer.getOffset(),
+                            buffer.getLength());
+                }
+
+                int length = buffer.getLength();
+
+                // Update the buffer size if too small.
+                int timeout = 2000;
+                int maxNbBuffers
+                    = timeout / MacCoreAudioDevice.DEFAULT_MILLIS_PER_BUFFER;
+                updateBufferLength(
+                        Math.min(
+                            nbBufferData + length,
+                            length * maxNbBuffers));
+
+                if(nbBufferData + length > this.buffer.length)
+                {
+                    length = this.buffer.length - nbBufferData;
+                }
+
+                // Copy the received data.
+                System.arraycopy(
+                        (byte[]) buffer.getData(),
+                        buffer.getOffset(),
+                        this.buffer,
+                        nbBufferData,
+                        length);
+                nbBufferData += length;
+            }
+        }
+        return BUFFER_PROCESSED_OK;
+    }
+
+    /**
+     * Sets the <tt>MediaLocator</tt> which specifies the device index of the
+     * MacCoreaudio device to be used by this instance for rendering.
+     *
+     * @param locator a <tt>MediaLocator</tt> which specifies the device index
+     * of the MacCoreaudio device to be used by this instance for rendering
+     */
+    @Override
+    public void setLocator(MediaLocator locator)
+    {
+        super.setLocator(locator);
+
+        this.updateDeviceUID();
+
+        supportedInputFormats = null;
+    }
+
+    /**
+     * Starts the rendering process. Any audio data available in the internal
+     * resources associated with this <tt>MacCoreaudioRenderer</tt> will begin
+     * being rendered.
+     */
+    public void start()
+    {
+        // Start the stream
+        synchronized(startStopMutex)
+        {
+            if(stream == 0 && deviceUID != null)
+            {
+                int nbChannels = inputFormat.getChannels();
+                if (nbChannels == Format.NOT_SPECIFIED)
+                    nbChannels = 1;
+
+                MacCoreaudioSystem.willOpenStream();
+                stream = MacCoreAudioDevice.startStream(
+                        deviceUID,
+                        this,
+                        (float) inputFormat.getSampleRate(),
+                        nbChannels,
+                        inputFormat.getSampleSizeInBits(),
+                        false,
+                        inputFormat.getEndian() == AudioFormat.BIG_ENDIAN,
+                        false);
+                MacCoreaudioSystem.didOpenStream();
+            }
+        }
+    }
+
+    /**
+     * Stops the rendering process.
+     */
+    public void stop()
+    {
+        synchronized(startStopMutex)
+        {
+            if(stream != 0 && deviceUID != null && !isStopping)
+            {
+                this.isStopping = true;
+                long timeout = 500;
+                long startTime = System.currentTimeMillis();
+                long currentTime = startTime;
+                long startNbData = nbBufferData;
+                while(nbBufferData > 0
+                        && (currentTime - startTime) < timeout)
+                {
+                    try
+                    {
+                        startStopMutex.wait(timeout);
+                    }
+                    catch(InterruptedException ex)
+                    {
+                    }
+
+                    currentTime = System.currentTimeMillis();
+                    if(startNbData > nbBufferData)
+                    {
+                        startTime = currentTime;
+                        startNbData = nbBufferData;
+                    }
+                }
+
+                MacCoreAudioDevice.stopStream(deviceUID, stream);
+                stream = 0;
+                buffer = null;
+                nbBufferData = 0;
+                this.isStopping = false;
+            }
+        }
+    }
+
+    /**
+     * Writes the data received to the buffer give in arguments, which is
+     * provided by the CoreAudio library.
+     *
+     * @param buffer The buffer to fill in provided by the CoreAudio library.
+     * @param bufferLength The length of the buffer provided.
+     */
+    public void writeOutput(byte[] buffer, int bufferLength)
+    {
+        synchronized(startStopMutex)
+        {
+            updateBufferLength(bufferLength);
+
+            int i = 0;
+            int length = nbBufferData;
+            if(bufferLength < length)
+            {
+                length = bufferLength;
+            }
+
+            System.arraycopy(this.buffer, 0, buffer, 0, length);
+
+            // Fiils the end of the buffer with silence.
+            if(length < bufferLength)
+            {
+                Arrays.fill(buffer, length, bufferLength, (byte) 0);
+            }
+
+
+            nbBufferData -= length;
+            if(nbBufferData > 0)
+            {
+                System.arraycopy(
+                        this.buffer, length, this.buffer, 0, nbBufferData);
+            }
+            // If the stop process is waiting, notifies it  that every sample
+            // has been consummed.
+            // (nbBufferData == 0)
+            else
+            {
+                startStopMutex.notify();
+            }
+        }
+    }
+
+    /**
+     * Updates the deviceUID based on the current locator.
+     *
+     * @return True if the deviceUID has been updated. False otherwise.
+     */
+    private boolean updateDeviceUID()
+    {
+        MediaLocator locator = getLocator();
+        if(locator != null)
+        {
+            String remainder = locator.getRemainder();
+            if(remainder != null && remainder.length() > 1)
+            {
+                synchronized(startStopMutex)
+                {
+                    this.deviceUID = remainder.substring(1);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void updateBufferLength(int newLength)
+    {
+        synchronized(startStopMutex)
+        {
+            if(this.buffer == null)
+            {
+                this.buffer = new byte[newLength];
+                nbBufferData = 0;
+            }
+            else if(newLength > this.buffer.length)
+            {
+                byte[] newBuffer = new byte[newLength];
+                System.arraycopy(
+                        this.buffer,
+                        0,
+                        newBuffer,
+                        0,
+                        nbBufferData);
+                this.buffer = newBuffer;
+            }
+        }
+    }
+}
-- 
GitLab