changeset 2624:b746d080787e

PR734: Fix pulse-java latency problem. 2011-06-20 Denis Lila <dlila@redhat.com> * pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java (bufferSize): Remove. (getBufferSize): Return stream.getBufferSize(). * pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java (connectLine): Improve formatting. * pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java (connectLine): Set up flags to adjust the latency, if needed. * pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java (bufAttr, bufAttrMutex): New members. (setBufAttr, bufferAttrCallback): New methods. They both set bufAttr. (getBufferSize): Return the current buffer size. (connectForRecording): Add a flags argument to allow callers to chose the flags. (stateCallback): When the stream is ready, set the buffer attributes to the actual ones used by the server. * pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c (buf_attr_changed_callback): New function. (Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new): Set the buffer attribute callback.
author Denis Lila <dlila@redhat.com>
date Mon, 20 Jun 2011 11:23:24 -0400
parents 26295314f6d6
children 9b0027357967
files ChangeLog pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
diffstat 6 files changed, 107 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Jun 16 11:11:35 2011 -0400
+++ b/ChangeLog	Mon Jun 20 11:23:24 2011 -0400
@@ -1,3 +1,25 @@
+2011-06-20  Denis Lila  <dlila@redhat.com>
+
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
+	(bufferSize): Remove.
+	(getBufferSize): Return stream.getBufferSize().
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java
+	(connectLine): Improve formatting.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java
+	(connectLine): Set up flags to adjust the latency, if needed.
+	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java
+	(bufAttr, bufAttrMutex): New members.
+	(setBufAttr, bufferAttrCallback): New methods. They both set bufAttr.
+	(getBufferSize): Return the current buffer size.
+	(connectForRecording): Add a flags argument to allow callers to chose the
+	flags.
+	(stateCallback): When the stream is ready, set the buffer attributes to
+	the actual ones used by the server.
+	* pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c
+	(buf_attr_changed_callback): New function. 
+	(Java_org_classpath_icedtea_pulseaudio_Stream_native_1pa_1stream_1new):
+	Set the buffer attribute callback.
+
 2011-06-16  Denis Lila  <dlila@redhat.com>
 
 	* pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java
--- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Thu Jun 16 11:11:35 2011 -0400
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java	Mon Jun 20 11:23:24 2011 -0400
@@ -74,7 +74,6 @@
     protected AudioFormat defaultFormat = null;
     protected boolean sendEvents = true;
 
-    protected int bufferSize = 0;
     // the total number of frames played since this line was opened
     protected long framesSinceOpen = 0;
 
@@ -265,7 +264,6 @@
             throw e;
 
         }
-        this.bufferSize = bufferSize;
         try {
             semaphore.acquire();
             synchronized (eventLoop.threadLock) {
@@ -442,7 +440,7 @@
         if (!isOpen()) {
             return DEFAULT_BUFFER_SIZE;
         }
-        return bufferSize;
+        return stream.getBufferSize();
     }
 
     @Override
--- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Thu Jun 16 11:11:35 2011 -0400
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java	Mon Jun 20 11:23:24 2011 -0400
@@ -116,10 +116,13 @@
     @Override
     protected void connectLine(int bufferSize, Stream masterStream)
             throws LineUnavailableException {
-        StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
-
-        bufferSize, bufferSize / 4, bufferSize / 8,
-                ((bufferSize / 10) > 100 ? bufferSize / 10 : 100), 0);
+        StreamBufferAttributes bufferAttributes =
+            new StreamBufferAttributes(
+                    bufferSize,
+                    bufferSize / 4,
+                    bufferSize / 8,
+                    Math.max(bufferSize / 10, 100),
+                    0);
 
         if (masterStream != null) {
             synchronized (eventLoop.threadLock) {
--- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Thu Jun 16 11:11:35 2011 -0400
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java	Mon Jun 20 11:23:24 2011 -0400
@@ -133,11 +133,42 @@
     @Override
     protected void connectLine(int bufferSize, Stream masterStream)
             throws LineUnavailableException {
-        int fragmentSize = bufferSize / 10 > 500 ? bufferSize / 10 : 500;
-        StreamBufferAttributes bufferAttributes = new StreamBufferAttributes(
-                bufferSize, 0, 0, 0, fragmentSize);
+        int fs = currentFormat.getFrameSize();
+        float fr = currentFormat.getFrameRate();
+        int bps = (int)(fs*fr); // bytes per second.
+
+        // if 2 seconds' worth of data can fit in the buffer of the specified
+        // size, we don't have to adjust the latency. Otherwise we do, so as
+        // to avoid overruns.
+        long flags = Stream.FLAG_START_CORKED;
+        StreamBufferAttributes bufferAttributes;
+        if (bps*2 < bufferSize) {
+            // pulse audio completely ignores our fragmentSize attribute unless
+            // ADJUST_LATENCY is set, so we just leave it at -1.
+            bufferAttributes = new StreamBufferAttributes(bufferSize, -1, -1, -1, -1);
+        } else {
+            flags |= Stream.FLAG_ADJUST_LATENCY;
+            // in this case, the pulse audio docs:
+            // http://www.pulseaudio.org/wiki/LatencyControl
+            // say every field (including bufferSize) must be initialized
+            // to -1 except fragmentSize.
+            // XXX: but in my tests, it just sets it to about 4MB, which
+            // effectively makes it impossible to allocate a small buffer
+            // and nothing bad happens (yet) when you don't set it to -1
+            // so we just leave it at bufferSize.
+            // XXX: the java api has no way to specify latency, which probably
+            // means it should be as low as possible. Right now this method's
+            // primary concern is avoiding dropouts, and if the user-provided
+            // buffer size is large enough, we leave the latency up to pulse
+            // audio (which sets it to something extremely high - about 2
+            // seconds). We might want to always set a low latency.
+            int fragmentSize = bufferSize/2;
+            fragmentSize = Math.max((fragmentSize/fs)*fs, fs);
+            bufferAttributes = new StreamBufferAttributes(bufferSize, -1, -1, -1, fragmentSize);
+        }
+
         synchronized (eventLoop.threadLock) {
-            stream.connectForRecording(Stream.DEFAULT_DEVICE, bufferAttributes);
+            stream.connectForRecording(Stream.DEFAULT_DEVICE, flags, bufferAttributes);
         }
     }
 
--- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Thu Jun 16 11:11:35 2011 -0400
+++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java	Mon Jun 20 11:23:24 2011 -0400
@@ -173,6 +173,9 @@
     private Format format;
     private float cachedVolume;
 
+    private StreamBufferAttributes bufAttr = new StreamBufferAttributes(0,0,0,0,0);
+    private static final Object bufAttrMutex = new Object();
+
     private List<StateListener> stateListeners;
     private List<WriteListener> writeListeners;
     private List<ReadListener> readListeners;
@@ -462,6 +465,23 @@
         return native_pa_stream_get_device_index();
     }
 
+    private void setBufAttr() {
+        synchronized(bufAttrMutex) {
+            bufAttr = native_pa_stream_get_buffer_attr();
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private void bufferAttrCallback() {
+        setBufAttr();
+    }
+
+    int getBufferSize() {
+        synchronized (bufAttrMutex) {
+            return bufAttr.getMaxLength();
+        }
+    }
+
     /**
      * 
      * @return the name of the sink or source this stream is connected to in the
@@ -513,7 +533,7 @@
      * @throws LineUnavailableException
      * 
      */
-    void connectForRecording(String deviceName,
+    void connectForRecording(String deviceName, long flags,
             StreamBufferAttributes bufferAttributes)
             throws LineUnavailableException {
 
@@ -524,7 +544,7 @@
                               bufferAttributes.getPreBuffering(),
                               bufferAttributes.getMinimumRequest(),
                               bufferAttributes.getFragmentSize(),
-                              FLAG_START_CORKED, null, null
+                              flags, null, null
                           );
         if (returnValue < 0) {
             throw new LineUnavailableException(
@@ -608,6 +628,11 @@
      */
     @SuppressWarnings("unused")
     private void stateCallback() {
+        synchronized(EventLoop.getEventLoop().threadLock) {
+            if (getState() == Stream.STATE_READY) {
+                setBufAttr();
+            }
+        }
         synchronized (stateListeners) {
             for (StateListener listener : stateListeners) {
                 listener.update();
--- a/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Thu Jun 16 11:11:35 2011 -0400
+++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c	Mon Jun 20 11:23:24 2011 -0400
@@ -169,7 +169,6 @@
 
 }
 
-
 // requires pulseaudio 0.9.11 :(
 static void stream_started_callback(pa_stream *stream, void *userdata) {
     // printf("stream_started_callback called\n");
@@ -240,6 +239,20 @@
 
 }
 
+static void buf_attr_changed_callback(pa_stream *stream, void *userdata) {
+    java_context* context = userdata;
+    assert(stream);
+    assert(context);
+    assert(context->env);
+    assert(context->obj);
+
+    if (pa_stream_get_state(stream) == PA_STREAM_CREATING) {
+        callJavaVoidMethod(context->env, context->obj, "bufferAttrCallback");
+    } else {
+        callJavaVoidMethod(pulse_thread_env, context->obj, "bufferAttrCallback");
+    }
+}
+
 // used to set stream flags and states.
 #define SET_STREAM_ENUM(env, clz, java_prefix, state_name) \
     SET_JAVA_STATIC_LONG_FIELD_TO_PA_ENUM(env, clz, java_prefix, STREAM, state_name)
@@ -355,7 +368,7 @@
     pa_stream_set_latency_update_callback (stream, stream_latency_update_callback, j_context);
     pa_stream_set_moved_callback (stream, stream_moved_callback, j_context);
     pa_stream_set_suspended_callback (stream, stream_suspended_callback, j_context);
-
+    pa_stream_set_buffer_attr_callback(stream, buf_attr_changed_callback, j_context);
 }
 
 /*