diff --git a/lib/native/mac/libjncoreaudio.jnilib b/lib/native/mac/libjncoreaudio.jnilib new file mode 100755 index 0000000000000000000000000000000000000000..07697e3d57ede90963f041cf48ffebe2fb7e4ff0 Binary files /dev/null and b/lib/native/mac/libjncoreaudio.jnilib differ diff --git a/src/native/build.xml b/src/native/build.xml index 5071e585a5fcdd516f8b071ffa1cabda813424f0..b257e950c5386fce0496efa18eb719aceced74f8 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 0000000000000000000000000000000000000000..d66c9aa4e2a39571d32c2dfaf358e691cb97c348 --- /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 0000000000000000000000000000000000000000..5e7e83bba31f51f9a941537892dd996df578e701 --- /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 0000000000000000000000000000000000000000..a6bfe3482ca87c58be31ba00a26c95ec555f4cea --- /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 0000000000000000000000000000000000000000..ed5b12cb85c5caad2c6ad0f3128f2a6c47a40510 --- /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 2c8a5234c95ec94705a31c88ab60a93df80ed02e..bc1ff3976d4d156c15cb8f19308d3a77c179e152 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 0a8592201967cb9628664ff7bfd976a088aa8222..2055b523419f75574c5cc70d6191f33f63a1f796 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 0000000000000000000000000000000000000000..0ed93340fe3c6c1d12b6deb6c98d57706683f759 --- /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 0000000000000000000000000000000000000000..60eb5da67f720a258b5b836009d2134c19edfc33 --- /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 703c89f3d1ac78d9d176d57c9fac4e00f6e9c19a..46223bd815e436d707284f85076c33aa7ff239d0 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 c5dd5a55e58f5a2a9479fce0c2a1a81c29463b14..e5e35898498ea40e261f908c777410e8c28122f4 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); + } + } + } }