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 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()