From f18a94b3defef16e98badd6d99f2422609aa56c5 Mon Sep 17 00:00:00 2001
From: Sven Gothel
Date: Thu, 22 Aug 2013 23:46:35 +0200
Subject: AudioSink: Add END_OF_STREAM_PTS, initSink(..) args: frameGrowAmount
and frameLimit allowing an optional used Ringbuffer to grow in
implementation.
---
.../com/jogamp/opengl/util/av/AudioSink.java | 29 ++--
.../jogamp/opengl/openal/av/ALAudioSink.java | 190 ++++++++++++++-------
.../jogamp/opengl/util/av/JavaSoundAudioSink.java | 2 +-
.../jogamp/opengl/util/av/NullAudioSink.java | 2 +-
4 files changed, 151 insertions(+), 72 deletions(-)
(limited to 'src/jogl')
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/AudioSink.java b/src/jogl/classes/com/jogamp/opengl/util/av/AudioSink.java
index d5db73c6b..ac93068fe 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/av/AudioSink.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/av/AudioSink.java
@@ -71,8 +71,11 @@ public interface AudioSink {
public static final AudioDataFormat DefaultFormat = new AudioDataFormat(AudioDataType.PCM, 44100, 16, 2, true /* signed */, true /* fixed point */, true /* littleEndian */);
public static class AudioFrame {
- /** Constant marking an invalid PTS, i.e. Integer.MIN_VALUE 0x80000000 {@value}. */
- public static final int INVALID_PTS = 0x80000000 ; // == -2147483648 == Integer.MIN_VALUE;
+ /** Constant marking an invalid PTS, i.e. Integer.MIN_VALUE == 0x80000000 == {@value}. Sync w/ native code. */
+ public static final int INVALID_PTS = 0x80000000;
+
+ /** Constant marking the end of the stream PTS, i.e. Integer.MIN_VALUE - 1 == 0x7FFFFFFF == {@value}. Sync w/ native code. */
+ public static final int END_OF_STREAM_PTS = 0x7FFFFFFF;
public final ByteBuffer data;
public final int dataSize;
@@ -136,10 +139,12 @@ public interface AudioSink {
* The {@link #DefaultFormat} should be supported by all implementations.
*
* @param requestedFormat the requested {@link AudioDataFormat}.
- * @param frameCount number of frames to queue in this sink
+ * @param initialFrameCount initial number of frames to queue in this sink
+ * @param frameGrowAmount number of frames to grow queue if full
+ * @param frameLimit maximum number of frames
* @return if successful the chosen AudioDataFormat based on the requestedFormat
and this sinks capabilities, otherwise null
.
*/
- public AudioDataFormat initSink(AudioDataFormat requestedFormat, int frameCount);
+ public AudioDataFormat initSink(AudioDataFormat requestedFormat, int initialFrameCount, int frameGrowAmount, int frameLimit);
/**
* Returns true, if {@link #play()} has been requested and the sink is still playing,
@@ -166,7 +171,7 @@ public interface AudioSink {
/**
* Flush all queued buffers, implies {@link #pause()}.
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
* @see #play()
* @see #pause()
@@ -179,17 +184,17 @@ public interface AudioSink {
/**
* Returns the number of allocated buffers as requested by
- * {@link #initSink(AudioDataFormat, int)}.
+ * {@link #initSink(AudioDataFormat, int, int, int)}.
*/
public int getFrameCount();
- /** @return the current enqueued frames count since {@link #initSink(AudioDataFormat, int)}. */
+ /** @return the current enqueued frames count since {@link #initSink(AudioDataFormat, int, int, int)}. */
public int getEnqueuedFrameCount();
/**
* Returns the current number of frames queued for playing.
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
*/
public int getQueuedFrameCount();
@@ -197,7 +202,7 @@ public interface AudioSink {
/**
* Returns the current number of bytes queued for playing.
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
*/
public int getQueuedByteCount();
@@ -205,7 +210,7 @@ public interface AudioSink {
/**
* Returns the current queued frame time in milliseconds for playing.
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
*/
public int getQueuedTime();
@@ -218,7 +223,7 @@ public interface AudioSink {
/**
* Returns the current number of frames in the sink available for writing.
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
*/
public int getFreeFrameCount();
@@ -229,7 +234,7 @@ public interface AudioSink {
* The data must comply with the chosen {@link AudioDataFormat} as returned by {@link #initSink(AudioDataFormat)}.
*
*
- * {@link #initSink(AudioDataFormat, int)} must be called first.
+ * {@link #initSink(AudioDataFormat, int, int, int)} must be called first.
*
*/
public void enqueueData(AudioFrame audioFrame);
diff --git a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java
index 217ab2954..dd1351756 100644
--- a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java
+++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java
@@ -28,8 +28,8 @@
package jogamp.opengl.openal.av;
-import jogamp.opengl.util.av.SyncedRingbuffer;
-
+import com.jogamp.common.util.LFRingbuffer;
+import com.jogamp.common.util.Ringbuffer;
import com.jogamp.common.util.locks.LockFactory;
import com.jogamp.common.util.locks.RecursiveLock;
import com.jogamp.openal.AL;
@@ -44,10 +44,6 @@ import com.jogamp.opengl.util.av.AudioSink;
*/
public class ALAudioSink implements AudioSink {
- /** Chunk of audio processed at one time. FIXME: Parameterize .. */
- public static final int BUFFER_SIZE = 4096;
- public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2;
-
private static final ALC alc;
private static final AL al;
private static final boolean staticAvailable;
@@ -55,14 +51,10 @@ public class ALAudioSink implements AudioSink {
private String deviceSpecifier;
private ALCdevice device;
private ALCcontext context;
- // private static final ThreadLocal currentContext = new ThreadLocal();
- protected final RecursiveLock lock = LockFactory.createRecursiveLock();
+ private final RecursiveLock lock = LockFactory.createRecursiveLock();
- /** Sample period in seconds */
- public float samplePeriod;
-
/** Playback speed, range [0.5 - 2.0], default 1.0. */
- public float playSpeed;
+ private float playSpeed;
static class ActiveBuffer {
ActiveBuffer(Integer name, int pts, int size) {
@@ -76,9 +68,12 @@ public class ALAudioSink implements AudioSink {
public String toString() { return "ABuffer[name "+name+", pts "+pts+", size "+size+"]"; }
}
- int[] alBuffers = null;
- private SyncedRingbuffer alBufferAvail = null;
- private SyncedRingbuffer alBufferPlaying = null;
+ private int[] alBuffers = null;
+ private int frameGrowAmount = 0;
+ private int frameLimit = 0;
+
+ private Ringbuffer alBufferAvail = null;
+ private Ringbuffer alBufferPlaying = null;
private volatile int alBufferBytesQueued = 0;
private volatile int playingPTS = AudioFrame.INVALID_PTS;
private volatile int enqueuedFrameCount;
@@ -107,6 +102,19 @@ public class ALAudioSink implements AudioSink {
staticAvailable = null != alc && null != al;
}
+ private static Ringbuffer.AllocEmptyArray rbAllocIntArray = new Ringbuffer.AllocEmptyArray() {
+ @Override
+ public Integer[] newArray(int size) {
+ return new Integer[size];
+ }
+ };
+ private static Ringbuffer.AllocEmptyArray rbAllocActiveBufferArray = new Ringbuffer.AllocEmptyArray() {
+ @Override
+ public ActiveBuffer[] newArray(int size) {
+ return new ActiveBuffer[size];
+ }
+ };
+
public ALAudioSink() {
initialized = false;
chosenFormat = null;
@@ -220,7 +228,7 @@ public class ALAudioSink implements AudioSink {
}
@Override
- public final AudioDataFormat initSink(AudioDataFormat requestedFormat, int frameCount) {
+ public final AudioDataFormat initSink(AudioDataFormat requestedFormat, int initialFrameCount, int frameGrowAmount, int frameLimit) {
if( !staticAvailable ) {
return null;
}
@@ -231,7 +239,7 @@ public class ALAudioSink implements AudioSink {
) {
return null; // not supported w/ OpenAL
}
- samplePeriod = 1.0f / requestedFormat.sampleRate;
+ // final float samplePeriod = 1.0f / requestedFormat.sampleRate;
switch( requestedFormat.channelCount ) {
case 1: {
switch ( requestedFormat.sampleSize ) {
@@ -254,19 +262,21 @@ public class ALAudioSink implements AudioSink {
// Allocate buffers
destroyBuffers();
{
- alBuffers = new int[frameCount];
- al.alGenBuffers(frameCount, alBuffers, 0);
+ alBuffers = new int[initialFrameCount];
+ al.alGenBuffers(initialFrameCount, alBuffers, 0);
final int err = al.alGetError();
if( err != AL.AL_NO_ERROR ) {
alBuffers = null;
throw new RuntimeException("ALAudioSink: Error generating Buffers: 0x"+Integer.toHexString(err));
}
- final Integer[] alBufferRingArray = new Integer[frameCount];
- for(int i=0; i(alBufferRingArray, true /* full */);
- alBufferPlaying = new SyncedRingbuffer(new ActiveBuffer[frameCount], false /* full */);
+ alBufferAvail = new LFRingbuffer(alBufferRingArray, rbAllocIntArray);
+ alBufferPlaying = new LFRingbuffer(initialFrameCount, rbAllocActiveBufferArray);
+ this.frameGrowAmount = frameGrowAmount;
+ this.frameLimit = frameLimit;
}
} finally {
unlockContext();
@@ -276,6 +286,49 @@ public class ALAudioSink implements AudioSink {
return chosenFormat;
}
+ private boolean growBuffers() {
+ if( !alBufferAvail.isEmpty() || !alBufferPlaying.isFull() ) {
+ throw new InternalError("Buffers: Avail is !empty "+alBufferAvail+", Playing is !full "+alBufferPlaying);
+ }
+ if( alBufferAvail.capacity() >= frameLimit || alBufferPlaying.capacity() >= frameLimit ) {
+ if( DEBUG ) {
+ System.err.println(getThreadName()+": ALAudioSink.growBuffers: Frame limit "+frameLimit+" reached: Avail "+alBufferAvail+", Playing "+alBufferPlaying);
+ }
+ return false;
+ }
+
+ final int[] newElems = new int[frameGrowAmount];
+ al.alGenBuffers(frameGrowAmount, newElems, 0);
+ final int err = al.alGetError();
+ if( err != AL.AL_NO_ERROR ) {
+ if( DEBUG ) {
+ System.err.println(getThreadName()+": ALAudioSink.growBuffers: Error generating "+frameGrowAmount+" new Buffers: 0x"+Integer.toHexString(err));
+ }
+ return false;
+ }
+ final Integer[] newElemsI = new Integer[frameGrowAmount];
+ for(int i=0; i 0 ) {
+ final int releaseBufferLimes = Math.max(1, alBufferPlaying.size() / 4 );
final int[] val=new int[1];
int i=0;
do {
@@ -361,22 +412,23 @@ public class ALAudioSink implements AudioSink {
if( AL.AL_NO_ERROR != alErr ) {
throw new RuntimeException("ALError "+toHexString(alErr)+" while quering processed buffers at source. "+this);
}
- if( wait && val[0] <= 0 ) {
+ if( wait && val[0] < releaseBufferLimes ) {
i++;
- // clip wait at 60Hz - min 1ms
- final int sleep = Math.max(1, Math.min(15, getQueuedTimeImpl( alBufferBytesQueued / alBufferPlaying.size() ) ));
+ // clip wait at [2 .. 100] ms
+ final int avgBufferDura = getQueuedTimeImpl( alBufferBytesQueued / alBufferPlaying.size() );
+ final int sleep = Math.max(2, Math.min(100, releaseBufferLimes * avgBufferDura));
if( DEBUG ) {
- System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait["+i+"]: sleep "+sleep+" ms, playImpl "+isPlayingImpl1()+", processed "+val[0]+", "+this);
+ System.err.println(getThreadName()+": ALAudioSink: Dequeue.wait["+i+"]: avgBufferDura "+avgBufferDura+", releaseBufferLimes "+releaseBufferLimes+", sleep "+sleep+" ms, playImpl "+isPlayingImpl1()+", processed "+val[0]+", "+this);
}
unlockContext();
try {
- Thread.sleep(sleep);
+ Thread.sleep( sleep - 1 );
} catch (InterruptedException e) {
} finally {
lockContext();
}
}
- } while ( wait && val[0] <= 0 && alBufferBytesQueued > 0 );
+ } while ( wait && val[0] < releaseBufferLimes && alBufferBytesQueued > 0 );
releaseBufferCount = val[0];
} else {
releaseBufferCount = 0;
@@ -390,26 +442,45 @@ public class ALAudioSink implements AudioSink {
throw new RuntimeException("ALError "+toHexString(alErr)+" while dequeueing "+releaseBufferCount+" buffers. "+this);
}
for ( int i=0; i 0 ) {
+ System.err.println(getThreadName()+": ALAudioSink: Write "+inAudioFrame.pts+", "+getQueuedTimeImpl(inAudioFrame.dataSize)+" ms, dequeued "+dequeuedBufferCount+", wait "+wait+", "+getPerfString());
+ }
+ }
+ return dequeuedBufferCount;
+ }
+
@Override
public final void enqueueData(AudioFrame audioFrame) {
if( !initialized || null == chosenFormat ) {
@@ -427,26 +498,26 @@ public class ALAudioSink implements AudioSink {
throw new RuntimeException("ALError "+toHexString(alErr)+" while makeCurrent. "+this);
}
- if( isPlayingImpl0() ) { // dequeue only possible if playing ..
- final boolean wait = alBufferAvail.size() <= 1;
- if( DEBUG ) {
- System.err.println(getThreadName()+": ALAudioSink: Dequeue: playImpl "+isPlayingImpl1()+", wait "+wait+", "+this);
- }
- final int dequeuedBufferCount = dequeueBuffer( false /* all */, wait );
- final ActiveBuffer currentBuffer = alBufferPlaying.peek();
- if( null != currentBuffer ) {
- playingPTS = currentBuffer.pts;
- } else {
- playingPTS = audioFrame.pts;
- }
- if( DEBUG ) {
- System.err.println(getThreadName()+": ALAudioSink: Write "+audioFrame.pts+", "+getQueuedTimeImpl(audioFrame.dataSize)+" ms, dequeued "+dequeuedBufferCount+", wait "+wait+", "+getPerfString());
+ final boolean dequeueDone;
+ if( alBufferAvail.isEmpty() ) {
+ // try to dequeue first
+ dequeueDone = dequeueBuffer(false, audioFrame) > 0;
+ if( alBufferAvail.isEmpty() ) {
+ // try to grow
+ growBuffers();
}
+ } else {
+ dequeueDone = false;
+ }
+ if( !dequeueDone && alBufferPlaying.size() > 0 ) { // dequeue only possible if playing ..
+ final boolean wait = isPlayingImpl0() && alBufferAvail.isEmpty(); // possible if grow failed or already exceeds it's limit!
+ dequeueBuffer(wait, audioFrame);
}
- final Integer alBufferName = alBufferAvail.get(true /* clearRef */);
+ final Integer alBufferName = alBufferAvail.get();
if( null == alBufferName ) {
- throw new InternalError("Internal Error: "+this);
+ alBufferAvail.dump(System.err, "Avail");
+ throw new InternalError("Internal Error: avail.get null "+alBufferAvail+", "+this);
}
if( !alBufferPlaying.put( new ActiveBuffer(alBufferName, audioFrame.pts, audioFrame.dataSize) ) ) {
throw new InternalError("Internal Error: "+this);
@@ -597,7 +668,7 @@ public class ALAudioSink implements AudioSink {
try {
// pauseImpl();
stopImpl();
- dequeueBuffer( true /* all */, false /* wait */ );
+ dequeueBuffer( true /* flush */, false /* wait */ );
if( alBuffers.length != alBufferAvail.size() || alBufferPlaying.size() != 0 ) {
throw new InternalError("XXX: "+this);
}
@@ -657,4 +728,7 @@ public class ALAudioSink implements AudioSink {
@Override
public final int getPTS() { return playingPTS; }
+
+ private static final String toHexString(int v) { return "0x"+Integer.toHexString(v); }
+ private static final String getThreadName() { return Thread.currentThread().getName(); }
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
index e96bb6a50..93be9db8d 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java
@@ -67,7 +67,7 @@ public class JavaSoundAudioSink implements AudioSink {
}
@Override
- public AudioDataFormat initSink(AudioDataFormat requestedFormat, int frameCount) {
+ public AudioDataFormat initSink(AudioDataFormat requestedFormat, int initialFrameCount, int frameGrowAmount, int frameLimit) {
if( !staticAvailable ) {
return null;
}
diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
index c7fecae0b..609973d7a 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java
@@ -31,7 +31,7 @@ public class NullAudioSink implements AudioSink {
}
@Override
- public AudioDataFormat initSink(AudioDataFormat requestedFormat, int frameCount) {
+ public AudioDataFormat initSink(AudioDataFormat requestedFormat, int initialFrameCount, int frameGrowAmount, int frameLimit) {
return requestedFormat;
}
--
cgit v1.2.3