summaryrefslogtreecommitdiffstats
path: root/src/jogl/classes/jogamp
diff options
context:
space:
mode:
authorXerxes Rånby <[email protected]>2013-06-24 01:04:09 +0200
committerXerxes Rånby <[email protected]>2013-06-24 01:04:09 +0200
commit8dc44b6426ef8915a335e9d5e4a23c17292ecbf8 (patch)
treede4fb5e649ef95da5c86e9337a7117425fac43e9 /src/jogl/classes/jogamp
parentcb7118fc875b6722803e4b11d5681671962a8d3a (diff)
parent16d446b7ac91dbddc0d848a137ac1e5a0c800870 (diff)
Merge remote-tracking branch 'xranby-github/FFMPEGMediaPlayer'
Conflicts: src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java Signed-off-by: Xerxes Rånby <[email protected]>
Diffstat (limited to 'src/jogl/classes/jogamp')
-rw-r--r--src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java168
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/AudioSink.java11
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/JavaSoundAudioSink.java78
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/NullAudioSink.java18
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java7
-rw-r--r--src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java134
6 files changed, 402 insertions, 14 deletions
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..9f0561cb3
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/openal/av/ALAudioSink.java
@@ -0,0 +1,168 @@
+package jogamp.opengl.openal.av;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+
+import jogamp.opengl.util.av.AudioSink;
+
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.openal.*;
+
+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;
+
+ // 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 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;
+
+ 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();
+ 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");
+ }
+
+ 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;
+ }
+ }
+
+ @Override
+ 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() {
+ 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() {
+ 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/FFMPEGDynamicLibraryBundleInfo.java b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
index 2d40fe4ec..852e5149c 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGDynamicLibraryBundleInfo.java
@@ -57,7 +57,7 @@ import com.jogamp.common.util.RunnableExecutor;
class FFMPEGDynamicLibraryBundleInfo implements DynamicLibraryBundleInfo {
private static final List<String> glueLibNames = new ArrayList<String>(); // none
- private static final int symbolCount = 31;
+ private static final int symbolCount = 32;
private static final String[] symbolNames = {
"avcodec_version",
"avformat_version",
@@ -80,7 +80,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",
@@ -95,7 +96,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
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 a4178967c..9ae1541f9 100644
--- a/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
+++ b/src/jogl/classes/jogamp/opengl/util/av/impl/FFMPEGMediaPlayer.java
@@ -38,6 +38,9 @@ import javax.media.opengl.GL;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLException;
+import java.util.Arrays;
+import java.util.Queue;
+
import com.jogamp.common.util.VersionNumber;
import com.jogamp.gluegen.runtime.ProcAddressTable;
import com.jogamp.opengl.util.GLPixelStorageModes;
@@ -45,6 +48,10 @@ import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureSequence;
import jogamp.opengl.GLContextImpl;
+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;
/***
@@ -100,10 +107,18 @@ import jogamp.opengl.util.av.EGLMediaPlayerImpl;
* </ul>
*/
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;
+
+ // Instance data
+ private static AudioSink audioSink;
+ private static int maxAvailableAudio;
+
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()) {
@@ -113,7 +128,16 @@ 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();
+ 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;
avFormatVersion = null;
@@ -223,7 +247,77 @@ public class FFMPEGMediaPlayer extends EGLMediaPlayerImpl {
}
} ).longValue();
}
-
+
+
+ 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<AudioFrame> audioFrameBuffer = new java.util.LinkedList<AudioFrame>();
+
+ 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");
+*/
+
+ //TODO reduce GC
+ audioFrameBuffer.add(new AudioFrame(sampleData, data_size, audio_pts));
+ pumpAudio();
+ }
+
+ private void pumpAudio() {
+ if(audioSink.getDataAvailable()==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( (dt<audio_dt_d ) && audioSink.isDataAvailable(a.data_size)) {
+ audioFrameBuffer.poll(); /* remove first item from the queue */
+ audioSink.writeData(a.sampleData, a.data_size);
+ 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) {
@@ -369,6 +463,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;
}
@@ -377,9 +474,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) {
@@ -393,7 +493,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);
}
@@ -401,19 +509,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;
- final long dt = (long) ( (float) ( pts_d - now_d ) / getPlaySpeed() ) ;
+ // 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(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);
- if(dt>dt_d) {
+ System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt);
+
+ if(dt>video_dt_d && dt<1000 && audioSink.getDataAvailable()<maxAvailableAudio-10000) {
try {
- Thread.sleep(dt-dt_d);
+ Thread.sleep(dt-video_dt_d);
} catch (InterruptedException e) { }
} /* else if(0>pts_d) {
System.err.println("s: pts-v "+pts+", pts-d "+pts_d+", now_d "+now_d+", dt "+dt);
} */
}
+ pumpAudio();
lastVideoPTS = pts;
}
return lastTex;