From 94bcfef3050bb31ffba834549b23a550557e939b Mon Sep 17 00:00:00 2001 From: Vincent Lucas <chenzo@jitsi.org> Date: Thu, 22 Nov 2012 16:49:58 +0000 Subject: [PATCH] Adds microphone hardware amplification for MacOSX via CoreAudio. --- lib/native/mac/libjncoreaudio.jnilib | Bin 0 -> 35132 bytes src/native/build.xml | 33 ++ ..._impl_neomedia_coreaudio_CoreAudioDevice.c | 111 +++++ ..._impl_neomedia_coreaudio_CoreAudioDevice.h | 60 +++ src/native/macosx/coreaudio/lib/device.c | 459 ++++++++++++++++++ src/native/macosx/coreaudio/lib/device.h | 39 ++ .../impl/neomedia/AbstractVolumeControl.java | 130 +++-- .../jitsi/impl/neomedia/MediaServiceImpl.java | 17 +- .../neomedia/coreaudio/CoreAudioDevice.java | 51 ++ .../coreaudio/CoreAudioVolumeControl.java | 113 +++++ src/org/jitsi/impl/neomedia/portaudio/Pa.java | 38 +- src/org/jitsi/util/StringUtils.java | 31 ++ 12 files changed, 1008 insertions(+), 74 deletions(-) create mode 100755 lib/native/mac/libjncoreaudio.jnilib create mode 100644 src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.c create mode 100644 src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.h create mode 100644 src/native/macosx/coreaudio/lib/device.c create mode 100644 src/native/macosx/coreaudio/lib/device.h create mode 100644 src/org/jitsi/impl/neomedia/coreaudio/CoreAudioDevice.java create mode 100644 src/org/jitsi/impl/neomedia/coreaudio/CoreAudioVolumeControl.java diff --git a/lib/native/mac/libjncoreaudio.jnilib b/lib/native/mac/libjncoreaudio.jnilib new file mode 100755 index 0000000000000000000000000000000000000000..07697e3d57ede90963f041cf48ffebe2fb7e4ff0 GIT binary patch literal 35132 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 literal 0 HcmV?d00001 diff --git a/src/native/build.xml b/src/native/build.xml index 5071e585..b257e950 100644 --- a/src/native/build.xml +++ b/src/native/build.xml @@ -668,6 +668,38 @@ </cc> </target> + <!-- compile jncoreaudio library for Mac OS X (32-bit/64-bit) --> + <target name="coreaudio" description="Build jncoreaudio shared library for Mac OS X" if="is.running.macos" + depends="init-native"> + <cc outtype="shared" name="gcc" outfile="${native_install_dir}/jncoreaudio" objdir="${obj}"> + <compilerarg value="-Wall" /> + <compilerarg value="-O2" /> + <compilerarg value="-arch" /> + <compilerarg value="x86_64" /> + <compilerarg value="-arch" /> + <compilerarg value="i386" /> + <compilerarg value="-I/System/Library/Frameworks/JavaVM.framework/Headers" + /> + + <linkerarg value="-o" location="end" /> + <linkerarg value="libjncoreaudio.jnilib" location="end" /> + <linkerarg value="-arch" /> + <linkerarg value="x86_64" /> + <linkerarg value="-arch" /> + <linkerarg value="i386" /> + <linkerarg value="-framework" /> + <linkerarg value="Foundation" /> + <linkerarg value="-framework" /> + <linkerarg value="Coreaudio" /> + + <fileset dir="${src}/native/macosx/coreaudio/lib" includes="*.c"/> + <fileset dir="${src}/native/macosx/coreaudio/jni" includes="*.c"/> + </cc> + + <delete dir="${obj}" failonerror="false" /> + <delete file="${native_install_dir}/history.xml" failonerror="false" /> + </target> + <!-- compile jnquicktime library for Mac OS X (32-bit/64-bit/ppc) --> <target name="quicktime" description="Build jnquicktime shared library for Mac OS X" if="is.running.macos" depends="init-native"> @@ -767,6 +799,7 @@ <echo message="'ant directshow (Windows only)' to compile jdirectshow shared library" /> <echo message="'ant aegeturleventhandler (Mac OS X only)' to compile AEGetURLEventHandler shared library" /> <echo message="'ant sparkle (Mac OS X only)' to compile sparkle shared library" /> + <echo message="'ant coreaudio (Mac OS X only)' to compile jcoreaudio shared library" /> <echo message="'ant quicktime (Mac OS X only)' to compile jquicktime shared library" /> <echo message="" /> <echo message="Options:" /> diff --git a/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.c b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.c new file mode 100644 index 00000000..d66c9aa4 --- /dev/null +++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.c @@ -0,0 +1,111 @@ +/* + * 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_coreaudio_CoreAudioDevice.h" + +#include "../lib/device.h" + +/** + * JNI code for CoreAudioDevice. + * + * @author Vicnent Lucas + */ + +// Private functions + +static jbyteArray getStrBytes(JNIEnv *env, const char *str); + +// Implementation + +JNIEXPORT jbyteArray JNICALL +Java_org_jitsi_impl_neomedia_coreaudio_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); + // Free + free(deviceName); + (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr); + + return deviceNameBytes; +} + +JNIEXPORT jint JNICALL +Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_setInputDeviceVolume + (JNIEnv *env, jclass clazz, jstring deviceUID, jfloat volume) +{ + const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0); + jint err = setInputDeviceVolume(deviceUIDPtr, volume); + // Free + (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr); + + return err; +} + +JNIEXPORT jint JNICALL +Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_setOutputDeviceVolume + (JNIEnv *env, jclass clazz, jstring deviceUID, jfloat volume) +{ + const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0); + jint err = setOutputDeviceVolume(deviceUIDPtr, volume); + // Free + (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr); + + return err; +} + +JNIEXPORT jfloat JNICALL +Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_getInputDeviceVolume + (JNIEnv *env, jclass clazz, jstring deviceUID) +{ + const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0); + jfloat volume = getInputDeviceVolume(deviceUIDPtr); + // Free + (*env)->ReleaseStringUTFChars(env, deviceUID, deviceUIDPtr); + + return volume; +} + +JNIEXPORT jfloat JNICALL +Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_getOutputDeviceVolume + (JNIEnv *env, jclass clazz, jstring deviceUID) +{ + const char * deviceUIDPtr = (*env)->GetStringUTFChars(env, deviceUID, 0); + jfloat volume = 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_coreaudio_CoreAudioDevice.h b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.h new file mode 100644 index 00000000..5e7e83bb --- /dev/null +++ b/src/native/macosx/coreaudio/jni/org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice.h @@ -0,0 +1,60 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ + +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice */ + +#ifndef _Included_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice +#define _Included_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice + * Method: getDeviceNameBytes + * Signature: (Ljava/lang/String;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_getDeviceNameBytes + (JNIEnv *, jclass, jstring); + +/* + * Class: org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice + * Method: setInputDeviceVolume + * Signature: (Ljava/lang/String;F)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_setInputDeviceVolume + (JNIEnv *, jclass, jstring, jfloat); + +/* + * Class: org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice + * Method: setOutputDeviceVolume + * Signature: (Ljava/lang/String;F)I + */ +JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_setOutputDeviceVolume + (JNIEnv *, jclass, jstring, jfloat); + +/* + * Class: org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice + * Method: getInputDeviceVolume + * Signature: (Ljava/lang/String;)F + */ +JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_getInputDeviceVolume + (JNIEnv *, jclass, jstring); + +/* + * Class: org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice + * Method: getOutputDeviceVolume + * Signature: (Ljava/lang/String;)F + */ +JNIEXPORT jfloat JNICALL Java_org_jitsi_impl_neomedia_coreaudio_CoreAudioDevice_getOutputDeviceVolume + (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 new file mode 100644 index 00000000..a6bfe348 --- /dev/null +++ b/src/native/macosx/coreaudio/lib/device.c @@ -0,0 +1,459 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +#include "device.h" + +#include <CoreAudio/CoreAudio.h> +#include <CoreFoundation/CFString.h> +#include <stdio.h> +/** + * Functions to list, access and modifies audio devices via coreaudio. + * + * @author Vincent Lucas + */ + +/** + * Private definition of functions, + */ +OSStatus setDeviceVolume( + const char * deviceUID, + Float32 volume, + UInt32 inputOutputScope); + +Float32 getDeviceVolume( + const char * deviceUID, + UInt32 inputOutputScope); + +OSStatus getChannelsForStereo( + const char * deviceUID, + UInt32 * channels); + +/** + * 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. + * + * @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( + const char * deviceUID) +{ + OSStatus err = noErr; + AudioObjectPropertyAddress address; + AudioDeviceID device = kAudioObjectUnknown; + UInt32 size; + + // Converts the device UID into a ref. + CFStringRef deviceUIDRef; + if((deviceUIDRef = CFStringCreateWithCString( + kCFAllocatorDefault, + deviceUID, + kCFStringEncodingASCII)) == NULL) + { + fprintf(stderr, + "getDevice (coreaudio/device.c): \ + \n\tCFStringCreateWithCString\n"); + return kAudioObjectUnknown; + } + + // Gets the device corresponding to the given UID. + AudioValueTranslation translation; + translation.mInputData = &deviceUIDRef; + translation.mInputDataSize = sizeof(deviceUIDRef); + translation.mOutputData = &device; + translation.mOutputDataSize = sizeof(device); + size = sizeof(translation); + address.mSelector = kAudioHardwarePropertyDeviceForUID; + address.mScope = kAudioObjectPropertyScopeGlobal; + address.mElement = kAudioObjectPropertyElementMaster; + + if((err = AudioObjectGetPropertyData( + kAudioObjectSystemObject, + &address, + 0, + NULL, + &size, + &translation)) != noErr) + { + fprintf(stderr, + "getDevice (coreaudio/device.c): \ + \n\tAudioObjectGetPropertyData, err: %d\n", + ((int) err)); + return kAudioObjectUnknown; + } + + // Frees the allocated device UID ref. + CFRelease(deviceUIDRef); + + return device; +} + +/** + * Returns the device name 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. + * + * @return The device name for the given device. Or NULL, if not available. The + * returned string must be freed by the caller. + */ +char* getDeviceName( + const char * deviceUID) +{ + AudioDeviceID device; + OSStatus err = noErr; + AudioObjectPropertyAddress address; + UInt32 size; + + // Gets the correspoding device + if((device = getDevice(deviceUID)) == kAudioObjectUnknown) + { + fprintf(stderr, + "getDeviceName (coreaudio/device.c): \ + \n\tgetDevice\n"); + return NULL; + } + + // Gets the device name + CFStringRef deviceName; + size = sizeof(deviceName); + address.mSelector = kAudioObjectPropertyName; + address.mScope = kAudioObjectPropertyScopeGlobal; + address.mElement = kAudioObjectPropertyElementMaster; + + if((err = AudioObjectGetPropertyData( + device, + &address, + 0, + NULL, + &size, + &deviceName)) != noErr) + { + fprintf(stderr, + "getDeviceName (coreaudio/device.c): \ + \n\tAudioObjectGetPropertyData, err: %d\n", + ((int) err)); + return NULL; + } + + // Converts the device name to ASCII. + CFIndex deviceNameLength = CFStringGetLength(deviceName) + 1; + char * deviceASCIIName; + // The caller of this function must free the string. + if((deviceASCIIName = (char *) malloc(deviceNameLength * sizeof(char))) + == NULL) + { + perror("getDeviceName (coreaudio/device.c): \ + \n\tmalloc\n"); + return NULL; + } + if(CFStringGetCString( + deviceName, + deviceASCIIName, + deviceNameLength, + kCFStringEncodingASCII)) + { + return deviceASCIIName; + } + return NULL; +} + +/** + * Sets the input volume for a given device. + * + * @param device The device which volume must be changed. + * @param volume The new volume of the device. This is a scalar value between + * 0.0 and 1.0 + * + * @return noErr if everything works well. Another value if an error has + * occured. + */ +OSStatus setInputDeviceVolume( + const char * deviceUID, + Float32 volume) +{ + return setDeviceVolume( + deviceUID, + volume, + kAudioDevicePropertyScopeInput); +} + +/** + * Sets the output volume for a given device. + * + * @param device The device which volume must be changed. + * @param volume The new volume of the device. This is a scalar value between + * 0.0 and 1.0 + * + * @return noErr if everything works well. Another value if an error has + * occured. + */ +OSStatus setOutputDeviceVolume( + const char * deviceUID, + Float32 volume) +{ + return setDeviceVolume( + deviceUID, + volume, + kAudioDevicePropertyScopeOutput); +} + +/** + * Sets the input or output volume for a given device. This is an internal + * (private) function and must only be called by setInputDeviceVolume or + * setOutputDeviceVolume. + * + * @param device The device which volume must be changed. + * @param volume The new volume of the device. This is a scalar value between + * 0.0 and 1.0 + * @param inputOutputScope The scope to tell if this is an output or an input + * device. + * + * @return noErr if everything works well. Another value if an error has + * occured. + */ +OSStatus setDeviceVolume( + const char * deviceUID, + Float32 volume, + UInt32 inputOutputScope) +{ + AudioDeviceID device; + OSStatus err = noErr; + AudioObjectPropertyAddress address; + UInt32 size; + UInt32 channels[2]; + + // Gets the correspoding device + if((device = getDevice(deviceUID)) == kAudioObjectUnknown) + { + fprintf(stderr, + "setDeviceVolume (coreaudio/device.c): \ + \n\tgetDevice (unknown device for UID: %s)\n", deviceUID); + return -1; + } + + // get the input device stereo channels + if((getChannelsForStereo(deviceUID, channels)) != noErr) + { + fprintf(stderr, + "setDeviceVolume (coreaudio/device.c): \ + \n\tgetChannelsForStereo, err: %d\n", + ((int) err)); + return err; + } + + // Sets the volume + size = sizeof(volume); + address.mSelector = kAudioDevicePropertyVolumeScalar; + address.mScope = inputOutputScope; + int i; + int elementsLength = 3; + UInt32 elements[] = + { + // The global volume. + kAudioObjectPropertyElementMaster, + // The left channel. + channels[0], + // The right channel. + channels[1] + }; + + // Applies the volume to the different elements of the device. + for(i = 0; i < elementsLength; ++i) + { + address.mElement = elements[i]; + // Checks if this device volume can be set. If yes, then do so. + if(AudioObjectHasProperty(device, &address)) + { + if((err = AudioObjectSetPropertyData( + device, + &address, + 0, + NULL, + size, + &volume)) != noErr) + { + fprintf(stderr, + "setDeviceVolume (coreaudio/device.c): \ + \n\tAudioObjectSetPropertyData, err: %d\n", + ((int) err)); + return err; + } + } + } + + return err; +} + +/** + * Gets the input volume for a given device. + * + * @param device The device to get volume from. + * + * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0 + * if an error occurs. + */ +Float32 getInputDeviceVolume( + const char * deviceUID) +{ + return getDeviceVolume( + deviceUID, + kAudioDevicePropertyScopeInput); +} + +/** + * Gets the output volume for a given device. + * + * @param device The device to get volume from. + * + * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0 + * if an error occurs. + */ +Float32 getOutputDeviceVolume( + const char * deviceUID) +{ + return getDeviceVolume( + deviceUID, + kAudioDevicePropertyScopeOutput); +} + +/** + * Gets the input or output volume for a given device. This is an internal + * (private) function and must only be called by getInputDeviceVolume or + * getOutputDeviceVolume. + * + * @param device The device to get volume from. + * @param inputOutputScope The scope to tell if this is an output or an input + * device. + * + * @return The device volume as a scalar value between 0.0 and 1.0. Returns -1.0 + * if an error occurs. + */ +Float32 getDeviceVolume( + const char * deviceUID, + UInt32 inputOutputScope) +{ + AudioDeviceID device; + Float32 volume = -1.0; + OSStatus err = noErr; + AudioObjectPropertyAddress address; + UInt32 size; + UInt32 channels[2]; + + // Gets the correspoding device + if((device = getDevice(deviceUID)) == kAudioObjectUnknown) + { + fprintf(stderr, + "getDeviceVolume (coreaudio/device.c): \ + \n\tgetDevice\n"); + return -1.0; + } + + // get the input device stereo channels + if((getChannelsForStereo(deviceUID, channels)) != noErr) + { + fprintf(stderr, + "getDeviceVolume (coreaudio/device.c): \ + \n\tgetChannelsForStereo, err: %d\n", + ((int) err)); + return -1.0; + } + + // Sets the volume + size = sizeof(volume); + address.mSelector = kAudioDevicePropertyVolumeScalar; + address.mScope = inputOutputScope; + int i; + int elementsLength = 3; + UInt32 elements[] = + { + // The global volume. + kAudioObjectPropertyElementMaster, + // The left channel. + channels[0], + // The right channel. + channels[1] + }; + + // Applies the volume to the different elements of the device. + for(i = 0; i < elementsLength; ++i) + { + address.mElement = elements[i]; + // Checks if this device volume can be set. If yes, then do so. + if(AudioObjectHasProperty(device, &address)) + { + if((err = AudioObjectGetPropertyData( + device, + &address, + 0, + NULL, + &size, + &volume)) != noErr) + { + fprintf(stderr, + "getDeviceVolume (coreaudio/device.c): \ + \n\tAudioObjectSetPropertyData, err: %d\n", + ((int) err)); + return -1.0; + } + } + } + + return volume; +} + +/** + * Sets the channels for stereo of a given device. + * + * @param device The device to get the channels from. + * @param channels The channels to be filled in with the correct values. This + * must be a 2 item length array. + * + * @return An OSStatus set to noErr if everything works well. Any other vlaue + * otherwise. + */ +OSStatus getChannelsForStereo( + const char * deviceUID, + UInt32 * channels) +{ + AudioDeviceID device; + OSStatus err = noErr; + AudioObjectPropertyAddress address; + UInt32 size; + + // Gets the correspoding device + if((device = getDevice(deviceUID)) == kAudioObjectUnknown) + { + fprintf(stderr, + "getChannelsForStereo (coreaudio/device.c): \ + \n\tgetDevice\n"); + return -1; + } + + // get the input device stereo channels + address.mSelector = kAudioDevicePropertyPreferredChannelsForStereo; + address.mScope = kAudioDevicePropertyScopeInput; + address.mElement = kAudioObjectPropertyElementWildcard; + size = sizeof(channels); + if((err = AudioObjectGetPropertyData( + device, + &address, + 0, + NULL, + &size, + channels)) != noErr) + { + fprintf(stderr, + "getChannelsForStereo (coreaudio/device.c): \ + \n\tAudioObjectGetPropertyData, err: %d\n", + ((int) err)); + return err; + } + + return err; +} diff --git a/src/native/macosx/coreaudio/lib/device.h b/src/native/macosx/coreaudio/lib/device.h new file mode 100644 index 00000000..ed5b12cb --- /dev/null +++ b/src/native/macosx/coreaudio/lib/device.h @@ -0,0 +1,39 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +#ifndef device_h +#define device_h + +#include <CoreAudio/CoreAudio.h> +#include <CoreFoundation/CFString.h> +#include <stdio.h> + +/** + * Functions to list, access and modifies audio devices via coreaudio. + * Look at correspondig ".c" file for documentation. + * + * @author Vincent Lucas + */ +AudioDeviceID getDevice( + const char * deviceUID); + +char* getDeviceName( + const char * deviceUID); + +OSStatus setInputDeviceVolume( + const char * deviceUID, + Float32 volume); + +OSStatus setOutputDeviceVolume( + const char * deviceUID, + Float32 volume); + +Float32 getInputDeviceVolume( + const char * deviceUID); + +Float32 getOutputDeviceVolume( + const char * deviceUID); +#endif diff --git a/src/org/jitsi/impl/neomedia/AbstractVolumeControl.java b/src/org/jitsi/impl/neomedia/AbstractVolumeControl.java index 2c8a5234..bc1ff397 100644 --- a/src/org/jitsi/impl/neomedia/AbstractVolumeControl.java +++ b/src/org/jitsi/impl/neomedia/AbstractVolumeControl.java @@ -43,7 +43,7 @@ public class AbstractVolumeControl /** * The minimum volume level accepted by <tt>AbstractVolumeControl</tt>. */ - private static final float MIN_VOLUME_LEVEL = 0.0F; + protected static final float MIN_VOLUME_LEVEL = 0.0F; /** * The minimum volume level expressed in percent accepted by @@ -54,7 +54,7 @@ public class AbstractVolumeControl /** * The maximum volume level accepted by <tt>AbstractVolumeControl</tt>. */ - private static final float MAX_VOLUME_LEVEL = 1.0F; + protected static final float MAX_VOLUME_LEVEL = 1.0F; /** * The maximum volume level expressed in percent accepted by @@ -62,14 +62,6 @@ public class AbstractVolumeControl */ public static final int MAX_VOLUME_PERCENT = 200; - /** - * The default volume level. - */ - private static final float DEFAULT_VOLUME_LEVEL - = MIN_VOLUME_LEVEL - + (MAX_VOLUME_LEVEL - MIN_VOLUME_LEVEL) - / ((MAX_VOLUME_PERCENT - MIN_VOLUME_PERCENT) / 100); - /** * The <tt>VolumeChangeListener</tt>s interested in volume change events * through the <tt>VolumeControl</tt> interface. @@ -92,7 +84,13 @@ public class AbstractVolumeControl /** * The current volume level. */ - private float volumeLevel = DEFAULT_VOLUME_LEVEL; + protected float volumeLevel; + + /** + * The power level reference used to compute equivelents between the volume + * power level and the gain in decibels. + */ + private float gainReferenceLevel; /** * Current mute state, by default we start unmuted. @@ -104,11 +102,6 @@ public class AbstractVolumeControl */ private float db; - /** - * The initial volume level, when this instance was created. - */ - private final float initialVolumeLevel; - /** * The name of the configuration property which specifies the value of the * volume level of this <tt>AbstractVolumeControl</tt>. @@ -126,29 +119,29 @@ public class AbstractVolumeControl public AbstractVolumeControl( String volumeLevelConfigurationPropertyName) { + // Initializes default values. + this.volumeLevel = getDefaultVolumeLevel(); + this.gainReferenceLevel = getGainReferenceLevel(); + this.volumeLevelConfigurationPropertyName = volumeLevelConfigurationPropertyName; // Read the initial volume level from the ConfigurationService. - float initialVolumeLevel = DEFAULT_VOLUME_LEVEL; - try { ConfigurationService cfg = LibJitsi.getConfigurationService(); if (cfg != null) { - String initialVolumeLevelString + String volumeLevelString = cfg.getString(this.volumeLevelConfigurationPropertyName); - if (initialVolumeLevelString != null) + if (volumeLevelString != null) { - initialVolumeLevel - = Float.parseFloat(initialVolumeLevelString); + this.volumeLevel = Float.parseFloat(volumeLevelString); if(logger.isDebugEnabled()) { - logger.debug( - "Restored volume: " + initialVolumeLevelString); + logger.debug("Restored volume: " + volumeLevelString); } } } @@ -157,9 +150,6 @@ public AbstractVolumeControl( { logger.warn("Error restoring volume", t); } - - this.initialVolumeLevel = initialVolumeLevel; - this.volumeLevel = this.initialVolumeLevel; } /** @@ -310,6 +300,7 @@ else if (value > MAX_VOLUME_LEVEL) return value; volumeLevel = value; + updateHardwareVolume(); fireVolumeChange(); /* @@ -325,13 +316,7 @@ else if (value > MAX_VOLUME_LEVEL) String.valueOf(volumeLevel)); } - float f1 = value / initialVolumeLevel; - - db - = (float) - ((Math.log(((double)f1 != 0.0D) ? f1 : 0.0001D) - / Math.log(10D)) - * 20D); + db = getDbFromPowerRatio(value, this.gainReferenceLevel); fireGainEvents(); return volumeLevel; @@ -380,9 +365,7 @@ public float setDB(float gain) if(this.db != gain) { this.db = gain; - - float f1 = (float)Math.pow(10D, (double)this.db / 20D); - float volumeLevel = f1 * this.initialVolumeLevel; + float volumeLevel = getPowerRatioFromDb(gain, gainReferenceLevel); setVolumeLevel(volumeLevel); } @@ -533,4 +516,77 @@ public Component getControlComponent() { return null; } + + /** + * Returns the decibel value for a ratio between a power level required and + * the reference power level. + * + * @param powerLevelRequired The power level wished for the signal + * (corresponds to the mesured power level). + * @param referencePowerLevel The reference power level. + * + * @return The gain in Db. + */ + private static float getDbFromPowerRatio( + float powerLevelRequired, + float referencePowerLevel) + { + float powerRatio = powerLevelRequired / referencePowerLevel; + + // Limits the lowest power ratio to be 0.0001. + float minPowerRatio = 0.0001F; + float flooredPowerRatio = Math.max(powerRatio, minPowerRatio); + + return (float) (20.0 * Math.log10(flooredPowerRatio)); + } + + /** + * Returns the mesured power level corresponding to a gain in decibel and + * compared to the reference power level. + * + * @param gainInDb The gain in Db. + * @param referencePowerLevel The reference power level. + * + * @return The power level the signal, which corresponds to the mesured + * power level. + */ + private static float getPowerRatioFromDb( + float gainInDb, + float referencePowerLevel) + { + return (float) Math.pow(10, (gainInDb / 20)) * referencePowerLevel; + } + + /** + * Returns the default volume level. + * + * @return The default volume level. + */ + protected static float getDefaultVolumeLevel() + { + return MIN_VOLUME_LEVEL + + (MAX_VOLUME_LEVEL - MIN_VOLUME_LEVEL) + / ((MAX_VOLUME_PERCENT - MIN_VOLUME_PERCENT) / 100); + } + + /** + * Returns the reference volume level for computing the gain. + * + * @return The reference volume level for computing the gain. + */ + protected static float getGainReferenceLevel() + { + return getDefaultVolumeLevel(); + } + + /** + * Modifies the hardware microphone sensibility (hardaware amplification). + * This is a void function for AbstractVolumeControl sincei it does not have + * any connection to hardware volume. But, this function must be redefined + * by any extending class. + */ + protected void updateHardwareVolume() + { + // Nothing to do. This AbstractVolumeControl only modifies the gain. + } } diff --git a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java index 0a859220..2055b523 100644 --- a/src/org/jitsi/impl/neomedia/MediaServiceImpl.java +++ b/src/org/jitsi/impl/neomedia/MediaServiceImpl.java @@ -21,6 +21,7 @@ import org.jitsi.impl.neomedia.codec.*; import org.jitsi.impl.neomedia.codec.video.*; +import org.jitsi.impl.neomedia.coreaudio.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.format.*; import org.jitsi.impl.neomedia.transform.sdes.*; @@ -748,9 +749,19 @@ public VolumeControl getInputVolumeControl() { if (inputVolumeControl == null) { - inputVolumeControl - = new AbstractVolumeControl( - VolumeControl.CAPTURE_VOLUME_LEVEL_PROPERTY_NAME); + if(OSUtils.IS_MAC) + { + inputVolumeControl + = new CoreAudioVolumeControl( + this, + VolumeControl.CAPTURE_VOLUME_LEVEL_PROPERTY_NAME); + } + else + { + inputVolumeControl + = new AbstractVolumeControl( + VolumeControl.CAPTURE_VOLUME_LEVEL_PROPERTY_NAME); + } } return inputVolumeControl; } diff --git a/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioDevice.java b/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioDevice.java new file mode 100644 index 00000000..0ed93340 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioDevice.java @@ -0,0 +1,51 @@ +/* + * 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.coreaudio; + +import org.jitsi.util.*; + +/** + * JNI link to the CoreAudio library. + * + * @author Vincent Lucqs + */ +public class CoreAudioDevice +{ + static + { + System.loadLibrary("jncoreaudio"); + } + +// public static native AudioDeviceID getDevice( +// String deviceUID); + + public static String getDeviceName( + String deviceUID) + { + byte[] deviceNameBytes = getDeviceNameBytes(deviceUID); + String deviceName = StringUtils.newString(deviceNameBytes); + + return deviceName; + } + + public static native byte[] getDeviceNameBytes( + String deviceUID); + + public static native int setInputDeviceVolume( + String deviceUID, + float volume); + + public static native int setOutputDeviceVolume( + String deviceUID, + float volume); + + public static native float getInputDeviceVolume( + String deviceUID); + + public static native float getOutputDeviceVolume( + String deviceUID); +} diff --git a/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioVolumeControl.java b/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioVolumeControl.java new file mode 100644 index 00000000..60eb5da6 --- /dev/null +++ b/src/org/jitsi/impl/neomedia/coreaudio/CoreAudioVolumeControl.java @@ -0,0 +1,113 @@ +/* + * 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.coreaudio; + +import org.jitsi.impl.neomedia.*; +import org.jitsi.impl.neomedia.device.*; +import org.jitsi.service.neomedia.*; +import org.jitsi.util.*; + +/** + * Implementation of VolumeControl which uses MacOSX sound architecture + * CoreAudio to change input/output hardware volume. + * + * @author Vincent Lucas + */ +public class CoreAudioVolumeControl + extends AbstractVolumeControl +{ + /** + * The <tt>Logger</tt> used by the <tt>CoreAudioVolumeControl</tt> class and + * its instances for logging output. + */ + private static final Logger logger + = Logger.getLogger(CoreAudioVolumeControl.class); + + /** + * The media service implementation. + */ + MediaServiceImpl mediaServiceImpl = null; + + /** + * The maximal power level used for hardware amplification. Over this value + * software amplification is used. + */ + private static final float MAX_HARDWARE_POWER = 1.0F; + + /** + * Creates volume control instance and initializes initial level value + * if stored in the configuration service. + * + * @param mediaServiceImpl The media service implementation. + * @param volumeLevelConfigurationPropertyName the name of the configuration + * property which specifies the value of the volume level of the new + * instance + */ + public CoreAudioVolumeControl( + MediaServiceImpl mediaServiceImpl, + String volumeLevelConfigurationPropertyName) + { + super(volumeLevelConfigurationPropertyName); + this.mediaServiceImpl = mediaServiceImpl; + updateHardwareVolume(); + } + + /** + * Returns the default volume level. + * + * @return The default volume level. + */ + protected static float getDefaultVolumeLevel() + { + // By default set the microphone at the middle of its hardware + // sensibility range. + return MAX_HARDWARE_POWER / 2; + } + + /** + * Returns the reference volume level for computing the gain. + * + * @return The reference volume level for computing the gain. + */ + protected static float getGainReferenceLevel() + { + // Starts to activate the gain (software amplification), only once the + // microphone sensibility is sets to its maximum (hardware + // amplification). + return MAX_HARDWARE_POWER; + } + + /** + * Modifies the hardware microphone sensibility (hardware amplification). + */ + protected void updateHardwareVolume() + { + // Gets the selected input dvice UID. + AudioSystem audioSystem + = mediaServiceImpl.getDeviceConfiguration().getAudioSystem(); + ExtendedCaptureDeviceInfo captureDevice = (audioSystem == null) + ? null + : audioSystem.getDevice(AudioSystem.CAPTURE_INDEX); + String deviceUID = captureDevice.getUID(); + + // Computes the hardware volume. + float jitsiHarwareVolumeFactor = MAX_VOLUME_LEVEL / MAX_HARDWARE_POWER; + float hardwareVolumeLevel = this.volumeLevel * jitsiHarwareVolumeFactor; + if(hardwareVolumeLevel > 1.0F) + { + hardwareVolumeLevel = 1.0F; + } + + // Changes the input volume of the capture device. + if(CoreAudioDevice.setInputDeviceVolume( + deviceUID, + hardwareVolumeLevel) != 0) + { + logger.debug("Could not change CoreAudio input device level"); + } + } +} diff --git a/src/org/jitsi/impl/neomedia/portaudio/Pa.java b/src/org/jitsi/impl/neomedia/portaudio/Pa.java index 703c89f3..46223bd8 100644 --- a/src/org/jitsi/impl/neomedia/portaudio/Pa.java +++ b/src/org/jitsi/impl/neomedia/portaudio/Pa.java @@ -8,7 +8,6 @@ import java.io.*; import java.lang.reflect.*; -import java.nio.charset.*; import org.jitsi.service.configuration.*; import org.jitsi.service.libjitsi.*; @@ -247,7 +246,7 @@ public static native double DeviceInfo_getDefaultSampleRate( */ public static String DeviceInfo_getDeviceUID(long deviceInfo) { - return newString(DeviceInfo_getDeviceUIDBytes(deviceInfo)); + return StringUtils.newString(DeviceInfo_getDeviceUIDBytes(deviceInfo)); } /** @@ -293,7 +292,7 @@ public static String DeviceInfo_getDeviceUID(long deviceInfo) */ public static String DeviceInfo_getName(long deviceInfo) { - return newString(DeviceInfo_getNameBytes(deviceInfo)); + return StringUtils.newString(DeviceInfo_getNameBytes(deviceInfo)); } /** @@ -316,7 +315,8 @@ public static String DeviceInfo_getName(long deviceInfo) */ public static String DeviceInfo_getTransportType(long deviceInfo) { - return newString(DeviceInfo_getTransportTypeBytes(deviceInfo)); + return StringUtils.newString( + DeviceInfo_getTransportTypeBytes(deviceInfo)); } /** @@ -565,36 +565,6 @@ public static native boolean IsFormatSupported( long outputParameters, double sampleRate); - /** - * Initializes a new <tt>String</tt> instance by decoding a specified array - * of bytes. - * - * @param bytes the bytes to be decoded into characters/a new - * <tt>String</tt> instance - * @return a new <tt>String</tt> instance whose characters were decoded from - * the specified <tt>bytes</tt> - */ - private static String newString(byte[] bytes) - { - if ((bytes == null) || (bytes.length == 0)) - return null; - else - { - Charset defaultCharset = Charset.defaultCharset(); - String charsetName - = (defaultCharset == null) ? "UTF-8" : defaultCharset.name(); - - try - { - return new String(bytes, charsetName); - } - catch (UnsupportedEncodingException ueex) - { - return new String(bytes); - } - } - } - /** * Opens a stream for either input, output or both. * diff --git a/src/org/jitsi/util/StringUtils.java b/src/org/jitsi/util/StringUtils.java index c5dd5a55..e5e35898 100644 --- a/src/org/jitsi/util/StringUtils.java +++ b/src/org/jitsi/util/StringUtils.java @@ -7,6 +7,7 @@ package org.jitsi.util; import java.io.*; +import java.nio.charset.*; /** * Implements utility functions to facilitate work with <tt>String</tt>s. @@ -233,4 +234,34 @@ public static String concatenateWords(String string) } return buff.toString(); } + + /** + * Initializes a new <tt>String</tt> instance by decoding a specified array + * of bytes (mostly used by JNI). + * + * @param bytes the bytes to be decoded into characters/a new + * <tt>String</tt> instance + * @return a new <tt>String</tt> instance whose characters were decoded from + * the specified <tt>bytes</tt> + */ + public static String newString(byte[] bytes) + { + if ((bytes == null) || (bytes.length == 0)) + return null; + else + { + Charset defaultCharset = Charset.defaultCharset(); + String charsetName + = (defaultCharset == null) ? "UTF-8" : defaultCharset.name(); + + try + { + return new String(bytes, charsetName); + } + catch (UnsupportedEncodingException ueex) + { + return new String(bytes); + } + } + } } -- GitLab