From dce425fdc835ed2289a688bc93cf6037c517d018 Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Sun, 28 Apr 2013 02:19:52 +0200 Subject: FFMPEGMediaPlayer: Lookup decoded audio data_size using av_samples_get_buffer_size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Xerxes Rånby --- .../jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java index 32c863553..35342669f 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java @@ -55,7 +55,7 @@ import com.jogamp.common.util.RunnableExecutor; class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { private static List glueLibNames = new ArrayList(); // none - private static final int symbolCount = 31; + private static final int symbolCount = 32; private static String[] symbolNames = { "avcodec_version", "avformat_version", @@ -78,7 +78,8 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { // libavutil "av_pix_fmt_descriptors", "av_free", -/* 18 */ "av_get_bits_per_pixel", + "av_get_bits_per_pixel", +/* 19 */ "av_samples_get_buffer_size", // libavformat "avformat_alloc_context", @@ -93,7 +94,7 @@ class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo { "avformat_network_init", // 53.13.0 (opt) "avformat_network_deinit", // 53.13.0 (opt) "avformat_find_stream_info", // 53.3.0 (opt) -/* 29 */ "av_find_stream_info", +/* 32 */ "av_find_stream_info", }; // alternate symbol names -- cgit v1.2.3 From 15e8490a6367129ce4cb39f41d40bcb88639b714 Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Sun, 28 Apr 2013 22:01:42 +0200 Subject: FFMPEGMediaPlayer: Workaround dropped video frames while decoding audio. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A temporal solution before we implement video decode and frame cache in a separate thread. Signed-off-by: Xerxes Rånby --- .../classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 4be2bcb58..06fb9fd96 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -382,7 +382,15 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { gl.glActiveTexture(GL.GL_TEXTURE0+getTextureUnit()); tex.enable(gl); tex.bind(gl); - readNextPacket0(moviePtr, procAddrGLTexSubImage2D, textureTarget, textureFormat, textureType); + + /* try decode 10 packets to find one containing video + (res == 2) */ + int res = 0; + int retry = 10; + while(res!=2 && retry >= 0) { + res = readNextPacket0(moviePtr, procAddrGLTexSubImage2D, textureTarget, textureFormat, textureType); + retry--; + } } finally { psm.restore(gl); } -- cgit v1.2.3 From 170826d2e202a9938595017b9621a25a6f36a50b Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Mon, 29 Apr 2013 19:13:53 +0200 Subject: FFMPEGMediaPlayer: Workaround forward seek delay caused by video sync. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent the video sync code to delay a frame more than 1 second. Signed-off-by: Xerxes Rånby --- src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 06fb9fd96..7f17e6234 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -403,7 +403,7 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ; lastVideoTime = now; // System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); - if(dt>dt_d) { + if(dt>dt_d && dt<1000 ) { try { Thread.sleep(dt-dt_d); } catch (InterruptedException e) { } -- cgit v1.2.3 From df6e7dbf623d7559c0b26cbad82d9a8bfda9d9db Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Wed, 8 May 2013 19:05:19 +0200 Subject: FFMPEGMediaPlayer: Add updateSound callback for passing decoded jni audio frames to java. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Xerxes Rånby --- .../jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java | 3 +++ .../libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 7f17e6234..a954c9878 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -213,6 +213,9 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { throw new InternalError("Unknown ProcAddressTable: "+pt.getClass().getName()+" of "+ctx.getClass().getName()); } } + private void updateSound() { + System.out.println("jA"); + } private void updateAttributes2(int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane, int lSz0, int lSz1, int lSz2, int tWd0, int tWd1, int tWd2) { diff --git a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c index a90036fdb..8e5f4124c 100644 --- a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c +++ b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c @@ -38,6 +38,7 @@ typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLi static const char * const ClazzNameFFMPEGMediaPlayer = "jogamp/opengl/util/av/impl/FFMPEGMediaPlayer"; static jclass ffmpegMediaPlayerClazz = NULL; +static jmethodID jni_mid_updateSound = NULL; static jmethodID jni_mid_updateAttributes1 = NULL; static jmethodID jni_mid_updateAttributes2 = NULL; @@ -191,6 +192,13 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGDynamicLibraryB return JNI_TRUE; } +static void _updateSound(JNIEnv *env, jobject instance, char *data, int32_t data_size, int32_t sample_rate) { + if(NULL!=env) { + fprintf(stderr, "nA"); + (*env)->CallVoidMethod(env, instance, jni_mid_updateSound); + } +} + static void _updateJavaAttributes(JNIEnv *env, jobject instance, FFMPEGToolBasicAV_t* pAV) { // int shallBeDetached = 0; @@ -334,10 +342,12 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_ini JoglCommon_FatalError(env, "JOGL FFMPEG: can't use %s", ClazzNameFFMPEGMediaPlayer); } + jni_mid_updateSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateSound", "()V"); jni_mid_updateAttributes1 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes", "(IIIIIFIILjava/lang/String;Ljava/lang/String;)V"); jni_mid_updateAttributes2 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes2", "(IIIIIIIIII)V"); - if(jni_mid_updateAttributes1 == NULL || + if(jni_mid_updateSound == NULL || + jni_mid_updateAttributes1 == NULL || jni_mid_updateAttributes2 == NULL) { return JNI_FALSE; } @@ -644,6 +654,8 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex // TODO: Wrap audio buffer data in a com.jogamp.openal.sound3d.Buffer or similar // and hand it over to the user using a suitable API. // TODO: OR send the audio buffer data down to sound card directly using JOAL. + _updateSound(env, instance, pAV->pAFrame->data[0], data_size, pAV->aSampleRate); + res = 1; } } else if(packet.stream_index==pAV->vid) { -- cgit v1.2.3 From c6081b03d1f47219aa789debf25aee55993e6dcb Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Thu, 9 May 2013 10:38:41 +0200 Subject: partial implementation of Java Sound output --- .../opengl/util/av/impl/FFMPEGMediaPlayer.java | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index a954c9878..55bb4799b 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -36,6 +36,9 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLException; +import java.util.Arrays; +import javax.sound.sampled.*; + import com.jogamp.common.util.VersionNumber; import com.jogamp.gluegen.runtime.ProcAddressTable; import com.jogamp.opengl.util.GLPixelStorageModes; @@ -101,6 +104,32 @@ import jogamp.opengl.util.av.EGLMediaPlayerImpl; * */ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { + + // Count of zeroed buffers to return before switching to real sample provider + private static final int TEMP_BUFFER_COUNT = 20; + + // AudioFormat parameters + public static final int SAMPLE_RATE = 22050; + private static final int SAMPLE_SIZE = 16; + private static final int CHANNELS = 1; + private static final boolean SIGNED = true; + private static final boolean BIG_ENDIAN = true; + + // Chunk of audio processed at one time + public static final int BUFFER_SIZE = 1000; + public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2; + + // Sample time values + public static final double SAMPLE_TIME_IN_SECS = 1.0 / SAMPLE_RATE; + public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; + + // Instance data + private AudioFormat format; + private DataLine.Info info; + private SourceDataLine auline; + private int bufferCount; + private byte [] sampleData = new byte[BUFFER_SIZE]; + public static final VersionNumber avUtilVersion; public static final VersionNumber avFormatVersion; public static final VersionNumber avCodecVersion; @@ -115,6 +144,25 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { System.err.println("LIB_AV Format: "+avFormatVersion); System.err.println("LIB_AV Codec : "+avCodecVersion); available = initIDs0(); + + if(available) { + // init audio + // Create the audio format we wish to use + format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGNED, BIG_ENDIAN); + + // Create dataline info object describing line format + info = new DataLine.Info(SourceDataLine.class, format); + + // Clear buffer initially + Arrays.fill(sampleData, (byte) 0); + + // Get line to write data to + auline = (SourceDataLine) AudioSystem.getLine(info); + auline.open(format); + auline.start(); + + } + } else { avUtilVersion = null; avFormatVersion = null; @@ -215,6 +263,9 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { } private void updateSound() { System.out.println("jA"); + if (data_size > 0) { + auline.write(sampleData, 0, data_size); + } } private void updateAttributes2(int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane, int lSz0, int lSz1, int lSz2, -- cgit v1.2.3 From cc30fa7de95cffa961e9fd3aead2dd8f3bb55aeb Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Fri, 10 May 2013 12:42:09 +0200 Subject: FFMPEGMediaPlayer: blocking Java Sound output --- .../opengl/util/av/impl/FFMPEGMediaPlayer.java | 33 +++++++++++----------- .../jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c | 10 ++++--- 2 files changed, 23 insertions(+), 20 deletions(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 55bb4799b..cf26bbd81 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -124,16 +124,16 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; // Instance data - private AudioFormat format; - private DataLine.Info info; - private SourceDataLine auline; - private int bufferCount; - private byte [] sampleData = new byte[BUFFER_SIZE]; + private static AudioFormat format; + private static DataLine.Info info; + private static SourceDataLine auline; + private static int bufferCount; + private static byte [] sampleData = new byte[BUFFER_SIZE]; public static final VersionNumber avUtilVersion; public static final VersionNumber avFormatVersion; public static final VersionNumber avCodecVersion; - static final boolean available; + static boolean available; static { if(FFMPEGDynamicLibraryBundleInfo.initSingleton()) { @@ -143,9 +143,7 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { System.err.println("LIB_AV Util : "+avUtilVersion); System.err.println("LIB_AV Format: "+avFormatVersion); System.err.println("LIB_AV Codec : "+avCodecVersion); - available = initIDs0(); - - if(available) { + if(initIDs0()) { // init audio // Create the audio format we wish to use format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGNED, BIG_ENDIAN); @@ -155,12 +153,15 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { // Clear buffer initially Arrays.fill(sampleData, (byte) 0); - - // Get line to write data to - auline = (SourceDataLine) AudioSystem.getLine(info); - auline.open(format); - auline.start(); - + try{ + // Get line to write data to + auline = (SourceDataLine) AudioSystem.getLine(info); + auline.open(format); + auline.start(); + available = true; + } catch (LineUnavailableException e){ + available = false; + } } } else { @@ -261,7 +262,7 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { throw new InternalError("Unknown ProcAddressTable: "+pt.getClass().getName()+" of "+ctx.getClass().getName()); } } - private void updateSound() { + private void updateSound(byte[] sampleData, int data_size) { System.out.println("jA"); if (data_size > 0) { auline.write(sampleData, 0, data_size); diff --git a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c index 8e5f4124c..b8ff9a6ca 100644 --- a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c +++ b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c @@ -192,10 +192,12 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGDynamicLibraryB return JNI_TRUE; } -static void _updateSound(JNIEnv *env, jobject instance, char *data, int32_t data_size, int32_t sample_rate) { +static void _updateSound(JNIEnv *env, jobject instance, char *data, int32_t data_size) { if(NULL!=env) { fprintf(stderr, "nA"); - (*env)->CallVoidMethod(env, instance, jni_mid_updateSound); + jbyteArray jbArray = (*env)->NewByteArray(env, data_size); + (*env)->SetByteArrayRegion(env, jbArray, 0, data_size, (jbyte*)data); + (*env)->CallVoidMethod(env, instance, jni_mid_updateSound, jbArray, data_size); } } @@ -342,7 +344,7 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_ini JoglCommon_FatalError(env, "JOGL FFMPEG: can't use %s", ClazzNameFFMPEGMediaPlayer); } - jni_mid_updateSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateSound", "()V"); + jni_mid_updateSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateSound", "([BI)V"); jni_mid_updateAttributes1 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes", "(IIIIIFIILjava/lang/String;Ljava/lang/String;)V"); jni_mid_updateAttributes2 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes2", "(IIIIIIIIII)V"); @@ -654,7 +656,7 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex // TODO: Wrap audio buffer data in a com.jogamp.openal.sound3d.Buffer or similar // and hand it over to the user using a suitable API. // TODO: OR send the audio buffer data down to sound card directly using JOAL. - _updateSound(env, instance, pAV->pAFrame->data[0], data_size, pAV->aSampleRate); + _updateSound(env, instance, pAV->pAFrame->data[0], data_size); res = 1; } -- cgit v1.2.3 From 062f3e9e1ecfeb63ea61eb540d94d17a2de1412a Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Sun, 12 May 2013 07:50:17 +0200 Subject: FFMPEGMediaPlayer: Poor mans audio/video sync. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Xerxes Rånby --- .../opengl/util/av/impl/FFMPEGMediaPlayer.java | 110 ++++++++++++++++++--- .../jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c | 38 +++++-- 2 files changed, 130 insertions(+), 18 deletions(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index cf26bbd81..2c7134a44 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -37,6 +37,7 @@ import javax.media.opengl.GL2ES2; import javax.media.opengl.GLException; import java.util.Arrays; +import java.util.Queue; import javax.sound.sampled.*; import com.jogamp.common.util.VersionNumber; @@ -109,11 +110,11 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { private static final int TEMP_BUFFER_COUNT = 20; // AudioFormat parameters - public static final int SAMPLE_RATE = 22050; + public static final int SAMPLE_RATE = 44100; private static final int SAMPLE_SIZE = 16; - private static final int CHANNELS = 1; + private static final int CHANNELS = 2; private static final boolean SIGNED = true; - private static final boolean BIG_ENDIAN = true; + private static final boolean BIG_ENDIAN = false; // Chunk of audio processed at one time public static final int BUFFER_SIZE = 1000; @@ -130,6 +131,8 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { private static int bufferCount; private static byte [] sampleData = new byte[BUFFER_SIZE]; + private static int maxAvailableAudio; + public static final VersionNumber avUtilVersion; public static final VersionNumber avFormatVersion; public static final VersionNumber avCodecVersion; @@ -158,8 +161,10 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { auline = (SourceDataLine) AudioSystem.getLine(info); auline.open(format); auline.start(); + maxAvailableAudio = auline.available(); available = true; } catch (LineUnavailableException e){ + maxAvailableAudio = 0; available = false; } } @@ -262,12 +267,83 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { throw new InternalError("Unknown ProcAddressTable: "+pt.getClass().getName()+" of "+ctx.getClass().getName()); } } - private void updateSound(byte[] sampleData, int data_size) { + + private class AudioFrame { + final byte[] sampleData; + final int data_size; + final int audio_pts; + AudioFrame(byte[] sampleData, int data_size, int audio_pts) { + this.sampleData=sampleData; + this.data_size=data_size; + this.audio_pts=audio_pts; + } + } + + static final Queue audioFrameBuffer = new java.util.LinkedList(); + + private void updateSound(byte[] sampleData, int data_size, int audio_pts) { +/* + // Visualize incomming data + int c=0; + for(byte b: sampleData){ + if(b<0) { + System.out.print(" "); + } else if(b<64) { + System.out.print("_"); + } else if(b < 128) { + System.out.print("-"); + } else if(b == 128) { + System.out.print("="); + } else if(b < 256-64) { + System.out.print("\""); + } else { + System.out.print("'"); + } + + c++; + if(c>=40) + break; + } System.out.println("jA"); - if (data_size > 0) { - auline.write(sampleData, 0, data_size); +*/ + + //TODO reduce GC + audioFrameBuffer.add(new AudioFrame(sampleData, data_size, audio_pts)); + pumpAudio(); + } + + private void pumpAudio() { + if(auline.available()==maxAvailableAudio){ + System.out.println("warning: audio buffer underrun"); + } + while(audioFrameBuffer.peek()!=null){ + AudioFrame a = audioFrameBuffer.peek(); + + // poor mans audio sync .. TODO: off thread + final long now = System.currentTimeMillis(); + final long now_d = now - lastAudioTime; + final long pts_d = a.audio_pts - lastAudioPTS; + final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ; + + System.err.println("s: pts-a "+a.audio_pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); + lastAudioTime = now; + if( (dta.data_size ) { + audioFrameBuffer.poll(); /* remove first item from the queue */ + int written = 0; + int len; + int data_size = a.data_size; + while (data_size > 0) { + len = auline.write(a.sampleData, written, data_size); + data_size -= len; + written += len; + } + lastAudioPTS=a.audio_pts; + } else { + break; + } } } + private void updateAttributes2(int pixFmt, int planes, int bitsPerPixel, int bytesPerPixelPerPlane, int lSz0, int lSz1, int lSz2, int tWd0, int tWd1, int tWd2) { @@ -413,6 +489,9 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { int pts0 = getVideoPTS0(moviePtr); int pts1 = seek0(moviePtr, msec); System.err.println("Seek: "+pts0+" -> "+msec+" : "+pts1); + audioFrameBuffer.clear(); + lastAudioPTS=pts1; + lastVideoPTS=pts1; return pts1; } @@ -421,9 +500,12 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { return lastTex; } + private long lastAudioTime = 0; + private int lastAudioPTS = 0; + private static final int audio_dt_d = 400; private long lastVideoTime = 0; private int lastVideoPTS = 0; - private static final int dt_d = 9; + private static final int video_dt_d = 9; @Override protected TextureSequence.TextureFrame getNextTextureImpl(GL gl, boolean blocking) { @@ -453,19 +535,23 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { if(blocking) { // poor mans video sync .. TODO: off thread 'readNextPackage0(..)' on shared GLContext and multi textures/unit! final long now = System.currentTimeMillis(); - final long now_d = now - lastVideoTime; - final long pts_d = pts - lastVideoPTS; + // Try sync video to audio + final long now_d = now - lastAudioTime; + final long pts_d = pts - lastAudioPTS - 444; /* hack 444 == play video 444ms ahead of audio */ + //final long dt = Math.min(46, Math.abs( (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ) ) ; final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ; lastVideoTime = now; - // System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); - if(dt>dt_d && dt<1000 ) { + System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); + + if(dt>video_dt_d && dt<1000 && auline.available()pts_d) { System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); } */ } + pumpAudio(); lastVideoPTS = pts; } return lastTex; diff --git a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c index b8ff9a6ca..bac299954 100644 --- a/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c +++ b/src/jogl/native/libav/jogamp_opengl_util_av_impl_FFMPEGMediaPlayer.c @@ -192,12 +192,38 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGDynamicLibraryB return JNI_TRUE; } -static void _updateSound(JNIEnv *env, jobject instance, char *data, int32_t data_size) { +static void _updateSound(JNIEnv *env, jobject instance, int8_t *data, int32_t data_size, int32_t aPTS) { if(NULL!=env) { - fprintf(stderr, "nA"); jbyteArray jbArray = (*env)->NewByteArray(env, data_size); - (*env)->SetByteArrayRegion(env, jbArray, 0, data_size, (jbyte*)data); - (*env)->CallVoidMethod(env, instance, jni_mid_updateSound, jbArray, data_size); + if (jbArray == NULL) { + fprintf(stderr, "FFMPEGMediaPlayer out of memory at native code _updateSound"); + return; /* out of memory error thrown */ + } + +/* + // Visualize sample waveform + int i; + for(i=0;i<40;i++){ + int8_t b = data[i]; + if(b<0) { + printf(" "); + } else if(b<64) { + printf("_"); + } else if(b < 128) { + printf("-"); + } else if(b == 128) { + printf("="); + } else if(b < 256-64) { + printf("\""); + } else { + printf("'"); + } + } + printf("nA\n"); +*/ + + (*env)->SetByteArrayRegion(env, jbArray, 0, data_size, data); + (*env)->CallVoidMethod(env, instance, jni_mid_updateSound, jbArray, data_size, aPTS); } } @@ -344,7 +370,7 @@ JNIEXPORT jboolean JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_ini JoglCommon_FatalError(env, "JOGL FFMPEG: can't use %s", ClazzNameFFMPEGMediaPlayer); } - jni_mid_updateSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateSound", "([BI)V"); + jni_mid_updateSound = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateSound", "([BII)V"); jni_mid_updateAttributes1 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes", "(IIIIIFIILjava/lang/String;Ljava/lang/String;)V"); jni_mid_updateAttributes2 = (*env)->GetMethodID(env, ffmpegMediaPlayerClazz, "updateAttributes2", "(IIIIIIIIII)V"); @@ -656,7 +682,7 @@ JNIEXPORT jint JNICALL Java_jogamp_opengl_util_av_impl_FFMPEGMediaPlayer_readNex // TODO: Wrap audio buffer data in a com.jogamp.openal.sound3d.Buffer or similar // and hand it over to the user using a suitable API. // TODO: OR send the audio buffer data down to sound card directly using JOAL. - _updateSound(env, instance, pAV->pAFrame->data[0], data_size); + _updateSound(env, instance, pAV->pAFrame->data[0], data_size, pAV->aPTS); res = 1; } -- cgit v1.2.3 From f4ab6922a93d349ee5d1a9a90206c6f80ee0c281 Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Sun, 12 May 2013 11:58:50 +0200 Subject: FFMPEGMediaPlayer: Limit video sync delay to 47ms in order to fix audio buffer underrun. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Xerxes Rånby --- src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 2c7134a44..68914639d 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -538,8 +538,8 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { // Try sync video to audio final long now_d = now - lastAudioTime; final long pts_d = pts - lastAudioPTS - 444; /* hack 444 == play video 444ms ahead of audio */ - //final long dt = Math.min(46, Math.abs( (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ) ) ; - final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ; + final long dt = Math.min(47, (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ) ; + //final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ; lastVideoTime = now; System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); -- cgit v1.2.3 From 3bf564210e7dca2f5d6b47898c554f5762ac5282 Mon Sep 17 00:00:00 2001 From: Xerxes Rånby Date: Wed, 19 Jun 2013 18:52:38 +0200 Subject: FFMPEGMediaPlayer: Add AudioSink interface. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use ALAudioSink when available and fallback to JavaSoundAudioSink when JOAL are not found on classpath. Java Sound playback moved from FFMPEGMediaPlayer into JavaSoundAudioSink. Signed-off-by: Xerxes Rånby --- .../jogamp/opengl/openal/av/ALAudioSink.java | 110 +++++++++++++++++++++ .../classes/jogamp/opengl/util/av/AudioSink.java | 11 +++ .../jogamp/opengl/util/av/JavaSoundAudioSink.java | 78 +++++++++++++++ .../jogamp/opengl/util/av/NullAudioSink.java | 18 ++++ .../opengl/util/av/impl/FFMPEGMediaPlayer.java | 73 ++++---------- 5 files changed, 235 insertions(+), 55 deletions(-) create mode 100644 src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java create mode 100644 src/jogl/classes/jogamp/opengl/util/av/AudioSink.java create mode 100644 src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java create mode 100644 src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java new file mode 100644 index 000000000..e7a957156 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java @@ -0,0 +1,110 @@ +package jogamp.opengl.openal.av; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; + +import jogamp.opengl.util.av.AudioSink; + +import com.jogamp.openal.*; +import com.jogamp.openal.util.*; + +public class ALAudioSink implements AudioSink { + + static ALC alc; + static AL al; + static ALCdevice device; + static ALCcontext context; + + // AudioFormat parameters + public static final int SAMPLE_RATE = 44100; + private static final int SAMPLE_SIZE = 16; + private static final int CHANNELS = 2; + private static final boolean SIGNED = true; + private static final boolean BIG_ENDIAN = false; + + // Chunk of audio processed at one time + public static final int BUFFER_SIZE = 1000; + public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2; + + // Sample time values + public static final double SAMPLE_TIME_IN_SECS = 1.0 / SAMPLE_RATE; + public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; + + private static AudioFormat format; + private static DataLine.Info info; + private static SourceDataLine auline; + private static int bufferCount; + private static byte [] sampleData = new byte[BUFFER_SIZE]; + + private static boolean available = false; + + static { + + boolean joalFound = false; + try { + Class.forName("com.jogamp.openal.ALFactory"); + joalFound = true; + } catch(ClassNotFoundException e){ + // Joal not found on classpath + } + + if(joalFound) { + + alc = ALFactory.getALC(); + al = ALFactory.getAL(); + String deviceSpecifier; + + // Get handle to default device. + device = alc.alcOpenDevice(null); + if (device == null) { + throw new ALException("Error opening default OpenAL device"); + } + + // Get the device specifier. + deviceSpecifier = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER); + if (deviceSpecifier == null) { + throw new ALException("Error getting specifier for default OpenAL device"); + } + + // Create audio context. + context = alc.alcCreateContext(device, null); + if (context == null) { + throw new ALException("Error creating OpenAL context"); + } + + // Set active context. + alc.alcMakeContextCurrent(context); + + // Check for an error. + if (alc.alcGetError(device) != ALC.ALC_NO_ERROR) { + throw new ALException("Error making OpenAL context current"); + } + + System.out.println("OpenAL audio sink using device: " + deviceSpecifier); + available = true; + } + } + + @Override + public boolean isDataAvailable(int data_size) { + return false; + } + + @Override + public void writeData(byte[] sampleData, int data_size) { + + } + + @Override + public int getDataAvailable() { + return 0; + } + + public static boolean isAvailable() { + return available; + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/av/AudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/AudioSink.java new file mode 100644 index 000000000..fedead713 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/av/AudioSink.java @@ -0,0 +1,11 @@ +package jogamp.opengl.util.av; + +public interface AudioSink { + + int getDataAvailable(); + + boolean isDataAvailable(int data_size); + + void writeData(byte[] sampleData, int data_size); + +} diff --git a/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java new file mode 100644 index 000000000..0e2806322 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java @@ -0,0 +1,78 @@ +package jogamp.opengl.util.av; + +import java.util.Arrays; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.SourceDataLine; + +public class JavaSoundAudioSink implements AudioSink { + + // AudioFormat parameters + public static final int SAMPLE_RATE = 44100; + private static final int SAMPLE_SIZE = 16; + private static final int CHANNELS = 2; + private static final boolean SIGNED = true; + private static final boolean BIG_ENDIAN = false; + + // Chunk of audio processed at one time + public static final int BUFFER_SIZE = 1000; + public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2; + + // Sample time values + public static final double SAMPLE_TIME_IN_SECS = 1.0 / SAMPLE_RATE; + public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; + + private static AudioFormat format; + private static DataLine.Info info; + private static SourceDataLine auline; + private static int bufferCount; + private static byte [] sampleData = new byte[BUFFER_SIZE]; + + private static boolean available; + + static { + // Create the audio format we wish to use + format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGNED, BIG_ENDIAN); + + // Create dataline info object describing line format + info = new DataLine.Info(SourceDataLine.class, format); + + // Clear buffer initially + Arrays.fill(sampleData, (byte) 0); + try{ + // Get line to write data to + auline = (SourceDataLine) AudioSystem.getLine(info); + auline.open(format); + auline.start(); + System.out.println("JavaSound audio sink"); + available=true; + } catch (Exception e) { + available=false; + } + } + + public void writeData(byte[] sampleData, int data_size) { + int written = 0; + int len; + while (data_size > 0) { + len = auline.write(sampleData, written, data_size); + data_size -= len; + written += len; + } + } + + public int getDataAvailable() { + return auline.available(); + } + + public boolean isDataAvailable(int data_size) { + return auline.available()>=data_size; + } + + public static boolean isAvailable() { + return available; + } + +} diff --git a/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java new file mode 100644 index 000000000..81259f4c5 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java @@ -0,0 +1,18 @@ +package jogamp.opengl.util.av; + +public class NullAudioSink implements AudioSink { + + @Override + public int getDataAvailable() { + return 0; + } + + @Override + public boolean isDataAvailable(int data_size) { + return false; + } + + @Override + public void writeData(byte[] sampleData, int data_size) { + } +} diff --git a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java index 68914639d..972cf0642 100644 --- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java +++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java @@ -38,7 +38,6 @@ import javax.media.opengl.GLException; import java.util.Arrays; import java.util.Queue; -import javax.sound.sampled.*; import com.jogamp.common.util.VersionNumber; import com.jogamp.gluegen.runtime.ProcAddressTable; @@ -50,6 +49,10 @@ import jogamp.opengl.GLContextImpl; import jogamp.opengl.es1.GLES1ProcAddressTable; import jogamp.opengl.es2.GLES2ProcAddressTable; import jogamp.opengl.gl4.GL4bcProcAddressTable; +import jogamp.opengl.util.av.AudioSink; +import jogamp.opengl.util.av.JavaSoundAudioSink; +import jogamp.opengl.util.av.NullAudioSink; +import jogamp.opengl.openal.av.ALAudioSink; import jogamp.opengl.util.av.EGLMediaPlayerImpl; /*** @@ -109,28 +112,8 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { // Count of zeroed buffers to return before switching to real sample provider private static final int TEMP_BUFFER_COUNT = 20; - // AudioFormat parameters - public static final int SAMPLE_RATE = 44100; - private static final int SAMPLE_SIZE = 16; - private static final int CHANNELS = 2; - private static final boolean SIGNED = true; - private static final boolean BIG_ENDIAN = false; - - // Chunk of audio processed at one time - public static final int BUFFER_SIZE = 1000; - public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2; - - // Sample time values - public static final double SAMPLE_TIME_IN_SECS = 1.0 / SAMPLE_RATE; - public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; - // Instance data - private static AudioFormat format; - private static DataLine.Info info; - private static SourceDataLine auline; - private static int bufferCount; - private static byte [] sampleData = new byte[BUFFER_SIZE]; - + private static AudioSink audioSink; private static int maxAvailableAudio; public static final VersionNumber avUtilVersion; @@ -146,28 +129,15 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { System.err.println("LIB_AV Util : "+avUtilVersion); System.err.println("LIB_AV Format: "+avFormatVersion); System.err.println("LIB_AV Codec : "+avCodecVersion); - if(initIDs0()) { - // init audio - // Create the audio format we wish to use - format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGNED, BIG_ENDIAN); - - // Create dataline info object describing line format - info = new DataLine.Info(SourceDataLine.class, format); - - // Clear buffer initially - Arrays.fill(sampleData, (byte) 0); - try{ - // Get line to write data to - auline = (SourceDataLine) AudioSystem.getLine(info); - auline.open(format); - auline.start(); - maxAvailableAudio = auline.available(); - available = true; - } catch (LineUnavailableException e){ - maxAvailableAudio = 0; - available = false; - } + initIDs0(); + available = true; + audioSink = new NullAudioSink(); + if(ALAudioSink.isAvailable()) { + audioSink = new ALAudioSink(); + } else if(JavaSoundAudioSink.isAvailable()) { + audioSink = new JavaSoundAudioSink(); } + maxAvailableAudio = audioSink.getDataAvailable(); } else { avUtilVersion = null; @@ -313,7 +283,7 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { } private void pumpAudio() { - if(auline.available()==maxAvailableAudio){ + if(audioSink.getDataAvailable()==maxAvailableAudio){ System.out.println("warning: audio buffer underrun"); } while(audioFrameBuffer.peek()!=null){ @@ -327,19 +297,12 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { System.err.println("s: pts-a "+a.audio_pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); lastAudioTime = now; - if( (dta.data_size ) { + if( (dt 0) { - len = auline.write(a.sampleData, written, data_size); - data_size -= len; - written += len; - } + audioSink.writeData(a.sampleData, a.data_size); lastAudioPTS=a.audio_pts; } else { - break; + break; } } } @@ -543,7 +506,7 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl { lastVideoTime = now; System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt); - if(dt>video_dt_d && dt<1000 && auline.available()video_dt_d && dt<1000 && audioSink.getDataAvailable() Date: Thu, 20 Jun 2013 19:55:04 +0200 Subject: ALAudioSink: Buffer and playback audio data. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is still something wrong with the buffering part; OpenAL will complain at runtime. Signed-off-by: Xerxes Rånby --- .../jogamp/opengl/openal/av/ALAudioSink.java | 102 ++++++++++++++++----- 1 file changed, 80 insertions(+), 22 deletions(-) (limited to 'src/jogl/classes/jogamp') diff --git a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java index e7a957156..9f0561cb3 100644 --- a/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java +++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java @@ -1,16 +1,12 @@ package jogamp.opengl.openal.av; -import java.util.Arrays; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.DataLine; -import javax.sound.sampled.SourceDataLine; +import java.nio.Buffer; +import java.nio.ByteBuffer; import jogamp.opengl.util.av.AudioSink; +import com.jogamp.common.nio.Buffers; import com.jogamp.openal.*; -import com.jogamp.openal.util.*; public class ALAudioSink implements AudioSink { @@ -21,10 +17,6 @@ public class ALAudioSink implements AudioSink { // AudioFormat parameters public static final int SAMPLE_RATE = 44100; - private static final int SAMPLE_SIZE = 16; - private static final int CHANNELS = 2; - private static final boolean SIGNED = true; - private static final boolean BIG_ENDIAN = false; // Chunk of audio processed at one time public static final int BUFFER_SIZE = 1000; @@ -34,11 +26,13 @@ public class ALAudioSink implements AudioSink { public static final double SAMPLE_TIME_IN_SECS = 1.0 / SAMPLE_RATE; public static final double BUFFER_TIME_IN_SECS = SAMPLE_TIME_IN_SECS * SAMPLES_PER_BUFFER; - private static AudioFormat format; - private static DataLine.Info info; - private static SourceDataLine auline; - private static int bufferCount; - private static byte [] sampleData = new byte[BUFFER_SIZE]; + private static int NUM_BUFFERS = 5; + private static int bufferNumber = 0; + private static int[] buffers = new int[NUM_BUFFERS]; + private static int[] source = new int[1]; + private static boolean initBuffer = true; + private static int frequency = 44100; + private static int format = AL.AL_FORMAT_STEREO16; private static boolean available = false; @@ -54,8 +48,7 @@ public class ALAudioSink implements AudioSink { if(joalFound) { - alc = ALFactory.getALC(); - al = ALFactory.getAL(); + alc = ALFactory.getALC(); String deviceSpecifier; // Get handle to default device. @@ -83,6 +76,17 @@ public class ALAudioSink implements AudioSink { if (alc.alcGetError(device) != ALC.ALC_NO_ERROR) { throw new ALException("Error making OpenAL context current"); } + + al = ALFactory.getAL(); + + // Allocate buffers + al.alGenBuffers(NUM_BUFFERS, buffers, 0); + al.alGenSources(1, source, 0); + al.alSourcei(source[0], AL.AL_BUFFER, buffers[0]); + + if(al.alGetError() != AL.AL_NO_ERROR) { + throw new ALException("Error generating :("); + } System.out.println("OpenAL audio sink using device: " + deviceSpecifier); available = true; @@ -90,18 +94,72 @@ public class ALAudioSink implements AudioSink { } @Override - public boolean isDataAvailable(int data_size) { - return false; + public boolean isDataAvailable(int data_size) { + return true; } @Override public void writeData(byte[] sampleData, int data_size) { - + // OpenAL consumes buffers in the background + // we first need to initialize the OpenAL buffers then + // start continous playback. + alc.alcMakeContextCurrent(context); + if(initBuffer) { + + ByteBuffer data = Buffers.newDirectByteBuffer(sampleData); + al.alBufferData(buffers[bufferNumber], format, data, data_size, frequency); + int error = al.alGetError(); + if(error != AL.AL_NO_ERROR) { + System.out.println("bufferNumber"+bufferNumber+" Data "+sampleData+" size"+data_size); + throw new ALException("Error loading :( error code: " + error); + } + + if(bufferNumber==NUM_BUFFERS-1){ + // all buffers queued + al.alSourceQueueBuffers(source[0], NUM_BUFFERS, buffers, 0); + // start playback + al.alSourcePlay(source[0]); + if(al.alGetError() != AL.AL_NO_ERROR) { + throw new ALException("Error starting :("); + } + initBuffer=false; + } + + // update buffer number to fill + bufferNumber=(bufferNumber+1)%NUM_BUFFERS; + } else { + // OpenAL is playing in the background. + // one new frame with audio data is ready + + // first wait for openal to release one buffer + int[] buffer=new int[1]; + int[] val=new int[1]; + do { + al.alGetSourcei(source[0], AL.AL_BUFFERS_PROCESSED, val, 0); + } while (val[0] <= 0); + + // fill and requeue the empty buffer + al.alSourceUnqueueBuffers(source[0], 1, buffer , 0); + Buffer data = Buffers.newDirectByteBuffer(sampleData); + al.alBufferData(buffer[0], format, data, data_size, frequency); + al.alSourceQueueBuffers(source[0], 1, buffer, 0); + if(al.alGetError() != AL.AL_NO_ERROR) { + throw new ALException("Error buffering :("); + } + + // Restart openal playback if needed + al.alGetSourcei(source[0], AL.AL_SOURCE_STATE, val, 0); + if(val[0] != al.AL_PLAYING) { + al.alSourcePlay(source[0]); + } + } } @Override public int getDataAvailable() { - return 0; + int[] val=new int[1]; + al.alGetSourcei(source[0], AL.AL_BUFFERS_PROCESSED, val, 0); + return (NUM_BUFFERS-val[0])*4096; } public static boolean isAvailable() { -- cgit v1.2.3