diff -r 2962ab27cd01 pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java --- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java Fri Jun 17 16:16:47 2011 -0400 +++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioDataLine.java Fri Jun 17 16:46:58 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 diff -r 2962ab27cd01 pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java --- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java Fri Jun 17 16:16:47 2011 -0400 +++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioSourceDataLine.java Fri Jun 17 16:46:58 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) { diff -r 2962ab27cd01 pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java --- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java Fri Jun 17 16:16:47 2011 -0400 +++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/PulseAudioTargetDataLine.java Fri Jun 17 16:46:58 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); } } diff -r 2962ab27cd01 pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java --- a/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java Fri Jun 17 16:16:47 2011 -0400 +++ b/pulseaudio/src/java/org/classpath/icedtea/pulseaudio/Stream.java Fri Jun 17 16:46:58 2011 -0400 @@ -170,6 +170,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; @@ -459,6 +462,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 @@ -510,7 +530,7 @@ * @throws LineUnavailableException * */ - void connectForRecording(String deviceName, + void connectForRecording(String deviceName, long flags, StreamBufferAttributes bufferAttributes) throws LineUnavailableException { @@ -521,7 +541,7 @@ bufferAttributes.getPreBuffering(), bufferAttributes.getMinimumRequest(), bufferAttributes.getFragmentSize(), - FLAG_START_CORKED, null, null + flags, null, null ); if (returnValue < 0) { throw new LineUnavailableException( @@ -605,6 +625,11 @@ */ @SuppressWarnings("unused") private void stateCallback() { + synchronized(EventLoop.getEventLoop().threadLock) { + if (getState() == Stream.STATE_READY) { + setBufAttr(); + } + } synchronized (stateListeners) { for (StateListener listener : stateListeners) { listener.update(); diff -r 2962ab27cd01 pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c --- a/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c Fri Jun 17 16:16:47 2011 -0400 +++ b/pulseaudio/src/native/org_classpath_icedtea_pulseaudio_Stream.c Fri Jun 17 16:46:58 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) @@ -353,7 +366,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); } /*