diff options
author | Sven Gothel <[email protected]> | 2013-08-22 23:46:35 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2013-08-22 23:46:35 +0200 |
commit | f18a94b3defef16e98badd6d99f2422609aa56c5 (patch) | |
tree | dbcdc4ac0721ddd595c1a0a1bc38736a95486481 /src/jogl/classes | |
parent | c1b44f3f26a8e44d34dc79850716174a8b36ad91 (diff) |
AudioSink: Add END_OF_STREAM_PTS, initSink(..) args: frameGrowAmount and frameLimit allowing an optional used Ringbuffer to grow in implementation.
Diffstat (limited to 'src/jogl/classes')
4 files changed, 151 insertions, 72 deletions
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} <i>should be</i> supported by all implementations. * </p> * @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 <code>requestedFormat</code> and this sinks capabilities, otherwise <code>null</code>. */ - 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 <i>and</i> the sink is still playing, @@ -166,7 +171,7 @@ public interface AudioSink { /** * Flush all queued buffers, implies {@link #pause()}. * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> * @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. * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> */ public int getQueuedFrameCount(); @@ -197,7 +202,7 @@ public interface AudioSink { /** * Returns the current number of bytes queued for playing. * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> */ public int getQueuedByteCount(); @@ -205,7 +210,7 @@ public interface AudioSink { /** * Returns the current queued frame time in milliseconds for playing. * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> */ public int getQueuedTime(); @@ -218,7 +223,7 @@ public interface AudioSink { /** * Returns the current number of frames in the sink available for writing. * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> */ 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)}. * </p> * <p> - * {@link #initSink(AudioDataFormat, int)} must be called first. + * {@link #initSink(AudioDataFormat, int, int, int)} must be called first. * </p> */ 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<GLContext> currentContext = new ThreadLocal<GLContext>(); - 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<Integer> alBufferAvail = null; - private SyncedRingbuffer<ActiveBuffer> alBufferPlaying = null; + private int[] alBuffers = null; + private int frameGrowAmount = 0; + private int frameLimit = 0; + + private Ringbuffer<Integer> alBufferAvail = null; + private Ringbuffer<ActiveBuffer> 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<Integer> rbAllocIntArray = new Ringbuffer.AllocEmptyArray<Integer>() { + @Override + public Integer[] newArray(int size) { + return new Integer[size]; + } + }; + private static Ringbuffer.AllocEmptyArray<ActiveBuffer> rbAllocActiveBufferArray = new Ringbuffer.AllocEmptyArray<ActiveBuffer>() { + @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<frameCount; i++) { + final Integer[] alBufferRingArray = new Integer[initialFrameCount]; + for(int i=0; i<initialFrameCount; i++) { alBufferRingArray[i] = Integer.valueOf(alBuffers[i]); } - alBufferAvail = new SyncedRingbuffer<Integer>(alBufferRingArray, true /* full */); - alBufferPlaying = new SyncedRingbuffer<ActiveBuffer>(new ActiveBuffer[frameCount], false /* full */); + alBufferAvail = new LFRingbuffer<Integer>(alBufferRingArray, rbAllocIntArray); + alBufferPlaying = new LFRingbuffer<ActiveBuffer>(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<frameGrowAmount; i++) { + newElemsI[i] = Integer.valueOf(newElems[i]); + } + + final int oldSize = alBuffers.length; + final int newSize = oldSize + frameGrowAmount; + final int[] newBuffers = new int[newSize]; + System.arraycopy(alBuffers, 0, newBuffers, 0, oldSize); + System.arraycopy(newElems, 0, newBuffers, oldSize, frameGrowAmount); + alBuffers = newBuffers; + + alBufferAvail.growBuffer(newElemsI, frameGrowAmount, rbAllocIntArray); + alBufferPlaying.growBuffer(null, frameGrowAmount, rbAllocActiveBufferArray); + if( alBufferAvail.isEmpty() || alBufferPlaying.isFull() ) { + throw new InternalError("Buffers: Avail is empty "+alBufferAvail+", Playing is full "+alBufferPlaying); + } + if( DEBUG ) { + System.err.println(getThreadName()+": ALAudioSink: Buffer grown "+frameGrowAmount+": Avail "+alBufferAvail+", playing "+alBufferPlaying); + } + return true; + } + private void destroyBuffers() { if( !staticAvailable ) { return; @@ -344,15 +397,13 @@ public class ALAudioSink implements AudioSink { return initialized; } - private final int dequeueBuffer(boolean all, boolean wait) { - if( !lock.isLocked() ) { - throw new InternalError("XXX"); - } + private final int dequeueBuffer(boolean flush, boolean wait) { int alErr = AL.AL_NO_ERROR; final int releaseBufferCount; - if( all ) { + if( flush ) { releaseBufferCount = alBufferPlaying.size(); } else if( alBufferBytesQueued > 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<releaseBufferCount; i++ ) { - final ActiveBuffer releasedBuffer = alBufferPlaying.get(true /* clearRef */); + final ActiveBuffer releasedBuffer = alBufferPlaying.get(); if( null == releasedBuffer ) { throw new InternalError("Internal Error: "+this); } - if( releasedBuffer.name.intValue() != buffers[i] ) { - throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer); - // System.err.println("XXX ["+i+"]: dequeued: "+buffers[i]+", released "+releasedBuffer); + if( releasedBuffer.name.intValue() != buffers[i] ) { + alBufferAvail.dump(System.err, "Avail-deq02-post"); + alBufferPlaying.dump(System.err, "Playi-deq02-post"); + throw new InternalError("Buffer name mismatch: dequeued: "+buffers[i]+", released "+releasedBuffer+", "+this); } alBufferBytesQueued -= releasedBuffer.size; if( !alBufferAvail.put(releasedBuffer.name) ) { throw new InternalError("Internal Error: "+this); } } + if( flush && ( !alBufferAvail.isFull() || !alBufferPlaying.isEmpty() ) ) { + alBufferAvail.dump(System.err, "Avail-deq03-post"); + alBufferPlaying.dump(System.err, "Playi-deq03-post"); + throw new InternalError("Flush failure: "+this); + } } return releaseBufferCount; } - private static final String toHexString(int v) { return "0x"+Integer.toHexString(v); } - private static final String getThreadName() { return Thread.currentThread().getName(); } - + private final int dequeueBuffer(boolean wait, AudioFrame inAudioFrame) { + final int dequeuedBufferCount = dequeueBuffer( false /* flush */, wait ); + final ActiveBuffer currentBuffer = alBufferPlaying.peek(); + if( null != currentBuffer ) { + playingPTS = currentBuffer.pts; + } else { + playingPTS = inAudioFrame.pts; + } + if( DEBUG ) { + if( dequeuedBufferCount > 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; } |