aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2023-05-17 16:48:10 +0200
committerSven Gothel <[email protected]>2023-05-17 16:48:10 +0200
commitf1f21406540f74a2d002b11ed039eb8dcf4ff64f (patch)
tree166fe0a5bc141296402456e3ba27082abadc14b8
parente7f6807d42d9821e42ee4f8ae3ce9fd8ef2d4e31 (diff)
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.
-rw-r--r--src/java/jogamp/openal/util/ALAudioSink.java70
-rw-r--r--src/test/com/jogamp/openal/test/manual/Synth02AL.java26
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<max; f+=step) {
o.setFreq(f);
try {
Thread.sleep(frameDuration);
} catch (final InterruptedException e) { }
}
final long t1 = Clock.currentNanos();
- System.err.println("Loop "+TimeUnit.NANOSECONDS.toMillis(t1-t0)+" ms");
+ final int exp = (int)( (max - min) / step ) * frameDuration;
+ final int has = (int)TimeUnit.NANOSECONDS.toMillis(t1-t0);
+ final int diff = has - exp;
+ System.err.println("Loop "+has+" / "+exp+" [ms], diff "+diff+" [ms], "+((float)diff/(float)exp) * 100f+"%");
}
o.setFreq( MIDDLE_C );
System.err.println("c: "+o);