From f1f21406540f74a2d002b11ed039eb8dcf4ff64f Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Wed, 17 May 2023 16:48:10 +0200 Subject: ALAudioSink.dequeueBuffer(): wait == true: Fix sleep cycle and use sleep(1) if slept long enough but giving better threading perf for openal-soft This with exclusive context gives us no distortion at 3x 12ms frames, reduced from 3x 16ms. See Synth02AL. --- src/java/jogamp/openal/util/ALAudioSink.java | 70 +++++++++++++++------- .../com/jogamp/openal/test/manual/Synth02AL.java | 26 ++++++-- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/java/jogamp/openal/util/ALAudioSink.java b/src/java/jogamp/openal/util/ALAudioSink.java index bd8c4ae..a801ce9 100644 --- a/src/java/jogamp/openal/util/ALAudioSink.java +++ b/src/java/jogamp/openal/util/ALAudioSink.java @@ -30,11 +30,13 @@ package jogamp.openal.util; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.concurrent.TimeUnit; import jogamp.openal.Debug; import com.jogamp.common.ExceptionUtils; import com.jogamp.common.av.AudioSink; +import com.jogamp.common.os.Clock; import com.jogamp.common.util.LFRingbuffer; import com.jogamp.common.util.PropertyAccess; import com.jogamp.common.util.Ringbuffer; @@ -99,6 +101,7 @@ public class ALAudioSink implements AudioSink { // private ALAudioFrame[] alFrames = null; private int[] alBufferNames = null; + private int avgFrameDuration = 0; // [ms] private int frameGrowAmount = 0; private int frameLimit = 0; @@ -438,6 +441,7 @@ public class ALAudioSink implements AudioSink { destroyBuffers(); { final float useFrameDuration = frameDuration > 1f ? frameDuration : AudioSink.DefaultFrameDuration; + avgFrameDuration = (int) useFrameDuration; final int initialFrameCount = requestedFormat.getFrameCount( initialQueueSize > 0 ? initialQueueSize : AudioSink.DefaultInitialQueueSize, useFrameDuration); // frameDuration, int initialQueueSize, int queueGrowAmount, int queueLimit) { @@ -613,31 +617,54 @@ public class ALAudioSink implements AudioSink { if( alBufferBytesQueued > 0 ) { final int releaseBufferLimes = Math.max(1, alFramesPlaying.size() / 4 ); final int[] val=new int[1]; + final int avgBufferDura = chosenFormat.getBytesDuration( alBufferBytesQueued / alFramesPlaying.size() ); + final int sleepLimes = releaseBufferLimes * avgBufferDura; int i=0; + int slept = 0; + int releasedBuffers = 0; + final long t0 = DEBUG ? Clock.currentNanos() : 0; do { + val[0] = 0; al.alGetSourcei(alSource[0], ALConstants.AL_BUFFERS_PROCESSED, val, 0); alErr = al.alGetError(); if( ALConstants.AL_NO_ERROR != alErr ) { throw new RuntimeException(getThreadName()+": ALError "+toHexString(alErr)+" while quering processed buffers at source. "+this); } - if( wait && val[0] < releaseBufferLimes ) { + releasedBuffers += val[0]; + if( wait && releasedBuffers < releaseBufferLimes ) { i++; - // clip wait at [2 .. 100] ms - final int avgBufferDura = chosenFormat.getBytesDuration( alBufferBytesQueued / alFramesPlaying.size() ); - final int sleep = Math.max(2, Math.min(100, releaseBufferLimes * avgBufferDura)); - if( DEBUG ) { - System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait["+i+"]: avgBufferDura "+avgBufferDura+", releaseBufferLimes "+releaseBufferLimes+", sleep "+sleep+" ms, playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", processed "+val[0]+", "+this); - } - unlockContext(); - try { - Thread.sleep( sleep - 1 ); - } catch (final InterruptedException e) { - } finally { - lockContext(); + // clip wait at [avgFrameDuration .. 100] ms + final int sleep = Math.max(avgFrameDuration, Math.min(100, releaseBufferLimes-releasedBuffers * avgBufferDura)); + if( slept + sleep <= sleepLimes ) { + if( DEBUG ) { + System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait-sleep["+i+"]: avgBufferDura "+avgBufferDura+", releaseBuffers "+releasedBuffers+"/"+releaseBufferLimes+", sleep "+sleep+"/"+slept+"/"+sleepLimes+" ms, playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", processed "+val[0]+", "+shortString()); + } + unlockContext(); + try { + Thread.sleep( sleep ); + slept += sleep; + } catch (final InterruptedException e) { + } finally { + lockContext(); + } + } else { + // Empirical best behavior w/ openal-soft (sort of needs min ~21ms to complete processing a buffer even if period < 20ms?) + unlockContext(); + try { + Thread.sleep( 1 ); + slept += 1; + } catch (final InterruptedException e) { + } finally { + lockContext(); + } } } - } while ( wait && val[0] < releaseBufferLimes && alBufferBytesQueued > 0 ); - releaseBufferCount = val[0]; + } while ( wait && releasedBuffers < releaseBufferLimes && alBufferBytesQueued > 0 ); + releaseBufferCount = releasedBuffers; + if( DEBUG ) { + final long t1 = Clock.currentNanos(); + System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait-done["+i+"]: "+TimeUnit.NANOSECONDS.toMillis(t1-t0)+" ms, avgBufferDura "+avgBufferDura+", releaseBuffers "+releaseBufferCount+"/"+releaseBufferLimes+", slept "+slept+" ms, playImpl "+(ALConstants.AL_PLAYING == getSourceState(false))+", processed "+val[0]+", "+shortString()); + } } else { releaseBufferCount = 0; } @@ -741,20 +768,17 @@ public class ALAudioSink implements AudioSink { lockContext(); try { final int duration = chosenFormat.getBytesDuration(byteCount); - final boolean dequeueDone; if( alFramesAvail.isEmpty() ) { // try to dequeue w/o waiting first - dequeueDone = dequeueBuffer(false, pts, duration) > 0; + dequeueBuffer(false, pts, duration); if( alFramesAvail.isEmpty() ) { // try to grow growBuffers(); } - } else { - dequeueDone = false; - } - if( !dequeueDone && alFramesPlaying.size() > 0 ) { // dequeue only possible if playing .. - final boolean wait = isPlayingImpl0() && alFramesAvail.isEmpty(); // possible if grow failed or already exceeds it's limit! - dequeueBuffer(wait, pts, duration); + if( alFramesAvail.isEmpty() && alFramesPlaying.size() > 0 && isPlayingImpl0() ) { + // possible if grow failed or already exceeds it's limit - only possible if playing .. + dequeueBuffer(true /* wait */, pts, duration); + } } alFrame = alFramesAvail.get(); diff --git a/src/test/com/jogamp/openal/test/manual/Synth02AL.java b/src/test/com/jogamp/openal/test/manual/Synth02AL.java index 068f168..13bec98 100644 --- a/src/test/com/jogamp/openal/test/manual/Synth02AL.java +++ b/src/test/com/jogamp/openal/test/manual/Synth02AL.java @@ -35,6 +35,7 @@ import java.util.concurrent.TimeUnit; import com.jogamp.common.av.AudioSink; import com.jogamp.common.av.AudioSinkFactory; +import com.jogamp.common.nio.Buffers; import com.jogamp.common.os.Clock; import com.jogamp.common.util.InterruptSource; import com.jogamp.common.util.InterruptedRuntimeException; @@ -67,7 +68,7 @@ public final class Synth02AL { private static final float SHORT_MAX = 32767.0f; // == Short.MAX_VALUE - public static final int frameDuration = 16; // AudioSink.DefaultFrameDuration; // [ms] + public static final int frameDuration = 12; // AudioSink.DefaultFrameDuration; // [ms] public static final int audioQueueLimit = 3 * frameDuration; @@ -151,6 +152,11 @@ public final class Synth02AL { } } + private static ByteBuffer allocate(final int size) { + // return ByteBuffer.allocate(size); + return Buffers.newDirectByteBuffer(size); + } + class SynthWorker extends InterruptSource.Thread { private volatile boolean isRunning = false; private volatile boolean isPlaying = false; @@ -161,7 +167,7 @@ public final class Synth02AL { private final AudioSink audioSink; private final AudioSink.AudioFormat audioFormat; - private ByteBuffer sampleBuffer = ByteBuffer.allocate(2*1000); + private ByteBuffer sampleBuffer = allocate(2*1000); private float lastFreq; private float nextSin; private boolean upSin; @@ -261,7 +267,7 @@ public final class Synth02AL { if( DEBUG ) { System.err.printf("SampleBuffer grow: %d -> %d%n", sampleBuffer.capacity(), 2*sample_count); } - sampleBuffer = ByteBuffer.allocate(2*sample_count); + sampleBuffer = allocate(2*sample_count); } { @@ -347,6 +353,8 @@ public final class Synth02AL { setName(getName()+"-SynthWorker_"+SynthWorkerInstanceId); SynthWorkerInstanceId++; + audioSink.lockExclusive(); + synchronized ( this ) { isRunning = true; this.notifyAll(); // wake-up ctor() @@ -378,6 +386,9 @@ public final class Synth02AL { isBlocked = false; } } + + audioSink.unlockExclusive(); + synchronized ( this ) { isRunning = false; isPlaying = false; @@ -420,16 +431,21 @@ public final class Synth02AL { System.err.println("0: "+o); o.play(); System.err.println("1: "+o); + enterValue("Press enter to start"); { + final float min = 100, max = 10000, step = 30; final long t0 = Clock.currentNanos(); - for(float f=100; f<10000; f+=30) { + for(float f=min; f