diff --git a/lib/native/linux-64/libjnpulseaudio.so b/lib/native/linux-64/libjnpulseaudio.so index 3845ff51efd87acfd8f70ba687608ed5acd064c3..80cd02e51cc33d6239178c22e1773d200f4480fe 100755 Binary files a/lib/native/linux-64/libjnpulseaudio.so and b/lib/native/linux-64/libjnpulseaudio.so differ diff --git a/lib/native/linux/libjnpulseaudio.so b/lib/native/linux/libjnpulseaudio.so index e36eff9e09d7f56633b5f7f2491064728b7bc289..ed76a90458f2dad38a58e64ac6eaeb31bccbde25 100755 Binary files a/lib/native/linux/libjnpulseaudio.so and b/lib/native/linux/libjnpulseaudio.so differ diff --git a/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.c b/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.c index 727c4c9fcb6586cb3c3000fa44feaf485e46288d..a83f0e2f48d2d5a04af9d3b938090273c264bec3 100644 --- a/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.c +++ b/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.c @@ -606,6 +606,15 @@ Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1drop return pa_stream_drop((pa_stream *) (intptr_t) s); } +JNIEXPORT jstring JNICALL +Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1get_1device_1name + (JNIEnv *env, jclass clazz, jlong s) +{ + const char *chars = pa_stream_get_device_name((pa_stream *) (intptr_t) s); + + return chars ? (*env)->NewStringUTF(env, chars) : NULL; +} + JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1get_1index (JNIEnv *env, jclass clazz, jlong s) diff --git a/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.h b/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.h index f53ab5e3c3e78a6d5b3f3661a798884fc5075a13..1e1cff96ffcd47fbc9d927083a9d4fd95a0247cc 100644 --- a/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.h +++ b/src/native/linux/pulseaudio/org_jitsi_impl_neomedia_pulseaudio_PA.h @@ -391,6 +391,14 @@ JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1discon JNIEXPORT jint JNICALL Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1drop (JNIEnv *, jclass, jlong); +/* + * Class: org_jitsi_impl_neomedia_pulseaudio_PA + * Method: stream_get_device_name + * Signature: (J)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_jitsi_impl_neomedia_pulseaudio_PA_stream_1get_1device_1name + (JNIEnv *, jclass, jlong); + /* * Class: org_jitsi_impl_neomedia_pulseaudio_PA * Method: stream_get_index diff --git a/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/PulseAudioRenderer.java b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/PulseAudioRenderer.java index 933f09d88018c3f8ec4c3c6b816e44e2de862d7c..1d1419a822f3e46135ca8c60d94d6b40c410743d 100644 --- a/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/PulseAudioRenderer.java +++ b/src/org/jitsi/impl/neomedia/jmfext/media/renderer/audio/PulseAudioRenderer.java @@ -16,6 +16,7 @@ import org.jitsi.impl.neomedia.*; import org.jitsi.impl.neomedia.device.*; import org.jitsi.impl.neomedia.pulseaudio.*; +import org.jitsi.util.*; /** * Implements an audio <tt>Renderer</tt> which uses PulseAudio. @@ -54,6 +55,11 @@ public class PulseAudioRenderer private long cvolume; + /** + * The name of the sink {@link #stream} is connected to. + */ + private String dev; + private final GainControl gainControl; private float gainControlLevel; @@ -142,6 +148,7 @@ public void close() this.stream = 0; corked = true; + dev = null; pulseAudioSystem.signalMainloop(false); @@ -301,9 +308,12 @@ public void run() PA.stream_set_state_callback( stream, stateCallback); + + String dev = getLocatorDev(); + PA.stream_connect_playback( stream, - getLocatorDev(), + dev, attr, PA.STREAM_ADJUST_LATENCY | PA.STREAM_START_CORKED, @@ -355,6 +365,7 @@ public void run() } this.stream = stream; + this.dev = dev; } finally { @@ -390,38 +401,64 @@ protected void playbackDevicePropertyChange(PropertyChangeEvent event) * FIXME Disabled due to freezes reported by Vincent Lucas and Kertesz * Laszlo on the dev mailing list. */ -// pulseAudioSystem.lockMainloop(); -// try -// { -// boolean open = (this.stream != 0); -// -// if (open) -// { -// /* -// * The close method will stop this Renderer if it is currently -// * started. -// */ -// boolean start = !this.corked; -// -// close(); -// -// try -// { -// open(); -// } -// catch (ResourceUnavailableException rue) -// { -// throw new UndeclaredThrowableException(rue); -// } -// -// if (start) -// start(); -// } -// } -// finally -// { -// pulseAudioSystem.unlockMainloop(); -// } + pulseAudioSystem.lockMainloop(); + try + { + /* + * If the stream is not open, changes to the default playback device + * do not really concern this Renderer because it will pick them up + * when it gets open. + */ + boolean open = (stream != 0); + + if (open) + { + /* + * One and the same name of the sink that stream is connected to + * in the server may come from different MediaLocator instances. + */ + String locatorDev = getLocatorDev(); + + if (!StringUtils.isEquals(dev, locatorDev)) + { + /* + * PulseAudio has the capability to move a stream to a + * different device while the stream is connected to a sink. + * In other words, it may turn out that the stream is + * already connected to the sink with the specified name at + * this time of the execution. + */ + String streamDev = PA.stream_get_device_name(stream); + + if (!StringUtils.isEquals(streamDev, locatorDev)) + { + /* + * The close method will stop this Renderer if it is + * currently started. + */ + boolean start = !corked; + + close(); + + try + { + open(); + } + catch (ResourceUnavailableException rue) + { + throw new UndeclaredThrowableException(rue); + } + + if (start) + start(); + } + } + } + } + finally + { + pulseAudioSystem.unlockMainloop(); + } } public int process(Buffer buffer) diff --git a/src/org/jitsi/impl/neomedia/pulseaudio/PA.java b/src/org/jitsi/impl/neomedia/pulseaudio/PA.java index d341f02e275d9fc2fd361a90d6daf2710df98427..bce6e6dc087a547c09aa1442f245769d406148c4 100644 --- a/src/org/jitsi/impl/neomedia/pulseaudio/PA.java +++ b/src/org/jitsi/impl/neomedia/pulseaudio/PA.java @@ -6,6 +6,13 @@ */ package org.jitsi.impl.neomedia.pulseaudio; +/** + * Declares the functions, structures and constants of the native + * <tt>PulseAudio</tt> API for use within Java in general and neomedia in + * particular. + * + * @author Lyubomir Marinov + */ public final class PA { public interface context_success_cb_t @@ -234,6 +241,17 @@ public static native long stream_cork( public static native int stream_drop(long s); + /** + * Gets the name of the sink or source a specified <tt>pa_stream</tt> is + * connected to in the server. + * + * @param s the <tt>pa_stream</tt> of which to get the name of the sink or + * source it is connected to in the server + * @return the name of the sink or source the specified <tt>pa_stream</tt> + * is connected to in the server + */ + public static native String stream_get_device_name(long s); + public static native int stream_get_index(long s); public static native int stream_get_state(long s); diff --git a/src/org/jitsi/util/StringUtils.java b/src/org/jitsi/util/StringUtils.java index 2fa07f66f4633dfb610301679fec00ef749f5e59..5dcf521dbb988060ebbdf15ec5f3d0a5b0aa1623 100644 --- a/src/org/jitsi/util/StringUtils.java +++ b/src/org/jitsi/util/StringUtils.java @@ -92,15 +92,20 @@ public static boolean isNullOrEmpty(String s, boolean trim) } /** - * Indicates whether strings are equal. + * Determines whether a specific <tt>String</tt> value equals another + * <tt>String</tt> value. If the two specified <tt>String</tt> values are + * equal to <tt>null</tt>, they are considered equal. * - * @param s1 the string to analyze. - * @param s2 the string to analyze. - * @return true if string are equal. + * @param s1 the first <tt>String</tt> value to check for value equality + * with the second + * @param s2 the second <tt>String</tt> value to check for value equality + * with the first + * @return <tt>true</tt> if the two specified <tt>Sting</tt> values are + * equal; otherwise, <tt>false</tt> */ public static boolean isEquals(String s1, String s2) { - return (s1 == null && s2 == null) || (s1 != null && s1.equals(s2)); + return (s1 == null) ? (s2 == null) : s1.equals(s2); } /** diff --git a/src/org/jitsi/util/xml/XMLUtils.java b/src/org/jitsi/util/xml/XMLUtils.java index 5c04d3102235f9e0d43e0d654b909331d57255c5..70c02557bbf94bb68bcd51c551dc4e792120441c 100644 --- a/src/org/jitsi/util/xml/XMLUtils.java +++ b/src/org/jitsi/util/xml/XMLUtils.java @@ -556,17 +556,16 @@ public static String getNamespaceUri(Node node) { String prefix = node.getPrefix(); String namespaceUri = node.getNamespaceURI(); + if (!isNullOrEmpty(namespaceUri)) - { return normalizeNamespace(namespaceUri); - } - if (XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName()) || - XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) - { + if (XMLConstants.XMLNS_ATTRIBUTE.equals(node.getNodeName()) + || XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) return normalizeNamespace(XMLNS_ATTRIBUTE_NS_URI); - } + Element rootElement = node.getOwnerDocument().getDocumentElement(); Node parentNode = null; + while (parentNode != rootElement) { if (parentNode == null) @@ -577,23 +576,15 @@ public static String getNamespaceUri(Node node) // If attribute doesn't have prefix - it has its parent // namespace if (isNullOrEmpty(prefix)) - { prefix = parentNode.getPrefix(); - } } else if (node.getNodeType() == Node.ELEMENT_NODE) - { parentNode = node.getParentNode(); - } else - { return null; - } } else - { parentNode = parentNode.getParentNode(); - } String parentPrefix = parentNode.getPrefix(); String parentNamespaceUri = parentNode.getNamespaceURI(); if (isNullOrEmpty(prefix)) @@ -601,22 +592,16 @@ else if (node.getNodeType() == Node.ELEMENT_NODE) Node xmlnsAttribute = parentNode.getAttributes().getNamedItem("xmlns"); if (xmlnsAttribute != null) - { return ((Attr) xmlnsAttribute).getValue(); - } } else if (isEquals(prefix, parentPrefix)) { if (!isNullOrEmpty(parentNamespaceUri)) - { return normalizeNamespace(parentNamespaceUri); - } } } if ("xml".equals(prefix)) - { return normalizeNamespace(XML_NS_URI); - } return null; }