From f18a94b3defef16e98badd6d99f2422609aa56c5 Mon Sep 17 00:00:00 2001
From: Sven Gothel <sgothel@jausoft.com>
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/classes')

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;
     }
     
-- 
cgit v1.2.3