diff --git a/lib/native/mac/libjnmaccoreaudio.jnilib b/lib/native/mac/libjnmaccoreaudio.jnilib index 36b14be8817c9e4c79dcf50bdc6e2017a6509850..5be4b22878f8b79b21dab272c8c80b913127a3f2 100755 Binary files a/lib/native/mac/libjnmaccoreaudio.jnilib and b/lib/native/mac/libjnmaccoreaudio.jnilib differ diff --git a/src/native/build.xml b/src/native/build.xml index 503182ebe525ba5c54878364d05ffb899331f63f..6e43b3c9e8d3f49dc3c35d513e7d2e767bb13326 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 0000000000000000000000000000000000000000..2a74413146b2b7b5c39f61a5bb7b4e7afde54f87 --- /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 0000000000000000000000000000000000000000..9f8b5cb96e81dad75d87449f3cebd6e9c13fadb5 --- /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 7ad57402963b4483622548bf350f73a17ff950d2..38022b13b19b467e0247bfaa86c73bfe6a38d972 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 c02caef497f49e3594a415c5df6a0ce6922936de..df7d7a743d2cf4ccb226dffc29c899480698ae83 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 0000000000000000000000000000000000000000..5ec04cd9267143d09efb6d49aed9cedf271626e3 --- /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 0000000000000000000000000000000000000000..729cbe1dc32745fc6b70aa71b32f3de124d98944 --- /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 0894ae2aaacb64cab59e97c4c26721a6413a4fc3..d517c2f3ee54169d6b4f401323debd2f366685fc 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 3a78e8441442973c21c6da3a28c79dd4b98b55ae..b13fc50f477d948ce212b975ba615cf8d801060e 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 07230e01b88b55f8d458c1e68fa202586834fbd1..ae2d56b4739602ff4a38a1dffbaf4d27c7c0863f 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 0000000000000000000000000000000000000000..530ce28fc4b342c0e0a9f9fa49cdb5750d932153 --- /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 bc3a9d784a89ce1f0260a04e45d0c3691c4077ae..962a1cd2a5967077cc55ec376acbe2d83eb9aff8 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 32751a3702c9a0f09c90867739a9e19434b50485..f228d3970c5a38d321da2a1cbc9a7132d884462b 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 0000000000000000000000000000000000000000..484c5ae6f5bf56a2bab51c4f41315c7c491ff91e --- /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 + * "MacCoreaudio". + */ + @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 0000000000000000000000000000000000000000..c149899f84664d4c7519b4374dcdc7efc57e9353 --- /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 0000000000000000000000000000000000000000..40711edb53b065c9d8117e56e8b25cd6633d22ce --- /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 0000000000000000000000000000000000000000..1657c645a6779840ea8ada88908cfebd604cbcce --- /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; + } + } + } +}