annotate src/com/aurellem/capture/audio/AudioSend.java @ 10:4c5fc53778c1

moved randelshofer stuff to rightfull place, enabled XuggleVideoRecorder
author Robert McIntyre <rlm@mit.edu>
date Wed, 26 Oct 2011 09:38:27 -0700
parents 5dfc9e768816
children
rev   line source
rlm@9 1 package com.aurellem.capture.audio;
rlm@9 2
rlm@9 3 import java.lang.reflect.Field;
rlm@9 4 import java.nio.ByteBuffer;
rlm@9 5 import java.util.HashMap;
rlm@9 6 import java.util.Vector;
rlm@9 7 import java.util.concurrent.CountDownLatch;
rlm@9 8 import java.util.logging.Level;
rlm@9 9 import java.util.logging.Logger;
rlm@9 10
rlm@9 11 import org.lwjgl.LWJGLException;
rlm@9 12 import org.lwjgl.openal.AL;
rlm@9 13 import org.lwjgl.openal.AL10;
rlm@9 14 import org.lwjgl.openal.ALCdevice;
rlm@9 15 import org.lwjgl.openal.OpenALException;
rlm@9 16
rlm@9 17 import com.jme3.audio.Listener;
rlm@9 18 import com.jme3.audio.lwjgl.LwjglAudioRenderer;
rlm@9 19 import com.jme3.math.Vector3f;
rlm@9 20 import com.jme3.util.BufferUtils;
rlm@9 21
rlm@9 22 public class AudioSend
rlm@9 23 extends LwjglAudioRenderer implements MultiListener {
rlm@9 24
rlm@9 25 /**
rlm@9 26 * Keeps track of all the listeners which have been registered so far.
rlm@9 27 * The first element is <code>null</code>, which represents the zeroth
rlm@9 28 * LWJGL listener which is created automatically.
rlm@9 29 */
rlm@9 30 public Vector<Listener> listeners = new Vector<Listener>();
rlm@9 31
rlm@9 32 public void initialize(){
rlm@9 33 super.initialize();
rlm@9 34 listeners.add(null);
rlm@9 35 }
rlm@9 36
rlm@9 37 /**
rlm@9 38 * This is to call the native methods which require the OpenAL device ID.
rlm@9 39 * currently it is obtained through reflection.
rlm@9 40 */
rlm@9 41 private long deviceID;
rlm@9 42
rlm@9 43 /**
rlm@9 44 * To ensure that <code>deviceID<code> and <code>listeners<code> are
rlm@9 45 * properly initialized before any additional listeners are added.
rlm@9 46 */
rlm@9 47 private CountDownLatch latch = new CountDownLatch(1);
rlm@9 48
rlm@9 49 private void waitForInit(){
rlm@9 50 try {latch.await();}
rlm@9 51 catch (InterruptedException e) {e.printStackTrace();}
rlm@9 52 }
rlm@9 53
rlm@9 54 /**
rlm@9 55 * Each listener (including the main LWJGL listener) can be registered
rlm@9 56 * with a <code>SoundProcessor</code>, which this Renderer will call whenever
rlm@9 57 * there is new audio data to be processed.
rlm@9 58 */
rlm@9 59 public HashMap<Listener, SoundProcessor> soundProcessorMap =
rlm@9 60 new HashMap<Listener, SoundProcessor>();
rlm@9 61
rlm@9 62
rlm@9 63 /**
rlm@9 64 * Create a new slave context on the recorder device which will render all the
rlm@9 65 * sounds in the main LWJGL context with respect to this listener.
rlm@9 66 */
rlm@9 67 public void addListener(Listener l) {
rlm@9 68 try {this.latch.await();}
rlm@9 69 catch (InterruptedException e) {e.printStackTrace();}
rlm@9 70 this.addListener();
rlm@9 71 this.listeners.add(l);
rlm@9 72 }
rlm@9 73
rlm@9 74 /**
rlm@9 75 * Whenever new data is rendered in the perspective of this listener,
rlm@9 76 * this Renderer will send that data to the SoundProcessor of your choosing.
rlm@9 77 */
rlm@9 78 public void registerSoundProcessor(Listener l, SoundProcessor sp) {
rlm@9 79 this.soundProcessorMap.put(l, sp);
rlm@9 80 }
rlm@9 81
rlm@9 82 /**
rlm@9 83 * Registers a SoundProcessor for the main LWJGL context. IF all you want to
rlm@9 84 * do is record the sound you would normally hear in your application, then
rlm@9 85 * this is the only method you have to worry about.
rlm@9 86 */
rlm@9 87 public void registerSoundProcessor(SoundProcessor sp){
rlm@9 88 // register a sound processor for the default listener.
rlm@9 89 this.soundProcessorMap.put(null, sp);
rlm@9 90 }
rlm@9 91
rlm@9 92 private static final Logger logger =
rlm@9 93 Logger.getLogger(AudioSend.class.getName());
rlm@9 94
rlm@9 95
rlm@9 96 //////////// Native Methods
rlm@9 97
rlm@9 98 /** This establishes the LWJGL context as the context which will be copies to all
rlm@9 99 * other contexts. It must be called before any calls to <code>addListener();</code>
rlm@9 100 */
rlm@9 101 public void initDevice(){
rlm@9 102 ninitDevice(this.deviceID);}
rlm@9 103 public static native void ninitDevice(long device);
rlm@9 104
rlm@9 105 /**
rlm@9 106 * The send device does not automatically process sound. This step function will cause
rlm@9 107 * the desired number of samples to be processed for each listener. The results will then
rlm@9 108 * be available via calls to <code>getSamples()</code> for each listener.
rlm@9 109 * @param samples
rlm@9 110 */
rlm@9 111 public void step(int samples){
rlm@9 112 nstep(this.deviceID, samples);}
rlm@9 113 public static native void nstep(long device, int samples);
rlm@9 114
rlm@9 115 /**
rlm@9 116 * Retrieve the final rendered sound for a particular listener. <code>contextNum == 0</code>
rlm@9 117 * is the main LWJGL context.
rlm@9 118 * @param buffer
rlm@9 119 * @param samples
rlm@9 120 * @param contextNum
rlm@9 121 */
rlm@9 122 public void getSamples(ByteBuffer buffer, int samples, int contextNum){
rlm@9 123 ngetSamples(this.deviceID, buffer, buffer.position(), samples, contextNum);}
rlm@9 124 public static native void ngetSamples(
rlm@9 125 long device, ByteBuffer buffer, int position, int samples, int contextNum);
rlm@9 126
rlm@9 127 /**
rlm@9 128 * Create an additional listener on the recorder device. The device itself will manage
rlm@9 129 * this listener and synchronize it with the main LWJGL context. Processed sound samples
rlm@9 130 * for this listener will be available via a call to <code>getSamples()</code> with
rlm@9 131 * <code>contextNum</code> equal to the number of times this method has been called.
rlm@9 132 */
rlm@9 133 public void addListener(){naddListener(this.deviceID);}
rlm@9 134 public static native void naddListener(long device);
rlm@9 135
rlm@9 136 /**
rlm@9 137 * This will internally call <code>alListener3f<code> in the appropriate slave context and update
rlm@9 138 * that context's listener's parameters. Calling this for a number greater than the current
rlm@9 139 * number of slave contexts will have no effect.
rlm@9 140 * @param pname
rlm@9 141 * @param v1
rlm@9 142 * @param v2
rlm@9 143 * @param v3
rlm@9 144 * @param contextNum
rlm@9 145 */
rlm@9 146 public void setNthListener3f(int pname, float v1, float v2, float v3, int contextNum){
rlm@9 147 nsetNthListener3f(pname, v1, v2, v3, this.deviceID, contextNum);}
rlm@9 148 public static native void
rlm@9 149 nsetNthListener3f(int pname, float v1, float v2, float v3, long device, int contextNum);
rlm@9 150
rlm@9 151 /**
rlm@9 152 * This will internally call <code>alListenerf<code> in the appropriate slave context and update
rlm@9 153 * that context's listener's parameters. Calling this for a number greater than the current
rlm@9 154 * number of slave contexts will have no effect.
rlm@9 155 * @param pname
rlm@9 156 * @param v1
rlm@9 157 * @param contextNum
rlm@9 158 */
rlm@9 159 public void setNthListenerf(int pname, float v1, int contextNum){
rlm@9 160 nsetNthListenerf(pname, v1, this.deviceID, contextNum);}
rlm@9 161 public static native void nsetNthListenerf(int pname, float v1, long device, int contextNum);
rlm@9 162
rlm@9 163 /**
rlm@9 164 * Instead of taking whatever device is available on the system, this call
rlm@9 165 * creates the "Multiple Audio Send" device, which supports multiple listeners in a limited
rlm@9 166 * capacity. For each listener, the device renders it not to the sound device, but
rlm@9 167 * instead to buffers which it makes available via JNI.
rlm@9 168 */
rlm@9 169 public void initInThread(){
rlm@9 170 try{
rlm@9 171 if (!AL.isCreated()){
rlm@9 172 AL.create("Multiple Audio Send", 44100, 60, false);
rlm@9 173 }
rlm@9 174 }catch (OpenALException ex){
rlm@9 175 logger.log(Level.SEVERE, "Failed to load audio library", ex);
rlm@9 176 System.exit(1);
rlm@9 177 return;
rlm@9 178 }catch (LWJGLException ex){
rlm@9 179 logger.log(Level.SEVERE, "Failed to load audio library", ex);
rlm@9 180 System.exit(1);
rlm@9 181 return;
rlm@9 182 }
rlm@9 183 super.initInThread();
rlm@9 184
rlm@9 185 ALCdevice device = AL.getDevice();
rlm@9 186
rlm@9 187 // RLM: use reflection to grab the ID of our device for use later.
rlm@9 188 try {
rlm@9 189 Field deviceIDField;
rlm@9 190 deviceIDField = ALCdevice.class.getDeclaredField("device");
rlm@9 191 deviceIDField.setAccessible(true);
rlm@9 192 try {deviceID = (Long)deviceIDField.get(device);}
rlm@9 193 catch (IllegalArgumentException e) {e.printStackTrace();}
rlm@9 194 catch (IllegalAccessException e) {e.printStackTrace();}
rlm@9 195 deviceIDField.setAccessible(false);}
rlm@9 196 catch (SecurityException e) {e.printStackTrace();}
rlm@9 197 catch (NoSuchFieldException e) {e.printStackTrace();}
rlm@9 198
rlm@9 199 // the LWJGL context must be established as the master context before
rlm@9 200 // any other listeners can be created on this device.
rlm@9 201 initDevice();
rlm@9 202 // Now, everything is initialized, and it is safe to add more listeners.
rlm@9 203 latch.countDown();
rlm@9 204 }
rlm@9 205
rlm@9 206
rlm@9 207 public void cleanup(){
rlm@9 208 for(SoundProcessor sp : this.soundProcessorMap.values()){
rlm@9 209 sp.cleanup();
rlm@9 210 }
rlm@9 211 super.cleanup();
rlm@9 212 }
rlm@9 213
rlm@9 214 public void updateAllListeners(){
rlm@9 215 for (int i = 0; i < this.listeners.size(); i++){
rlm@9 216 Listener lis = this.listeners.get(i);
rlm@9 217 if (null != lis){
rlm@9 218 Vector3f location = lis.getLocation();
rlm@9 219 Vector3f velocity = lis.getVelocity();
rlm@9 220 Vector3f orientation = lis.getUp();
rlm@9 221 float gain = lis.getVolume();
rlm@9 222 setNthListener3f(AL10.AL_POSITION,
rlm@9 223 location.x, location.y, location.z, i);
rlm@9 224 setNthListener3f(AL10.AL_VELOCITY,
rlm@9 225 velocity.x, velocity.y, velocity.z, i);
rlm@9 226 setNthListener3f(AL10.AL_ORIENTATION,
rlm@9 227 orientation.x, orientation.y, orientation.z, i);
rlm@9 228 setNthListenerf(AL10.AL_GAIN, gain, i);
rlm@9 229 }
rlm@9 230 }
rlm@9 231 }
rlm@9 232
rlm@9 233
rlm@9 234 public final static int BYTES_PER_SAMPLE = 4;
rlm@9 235 private ByteBuffer buffer = BufferUtils.createByteBuffer(4096);
rlm@9 236
rlm@9 237 public void dispatchAudio(float tpf){
rlm@9 238 int samplesToGet = (int) (tpf * 44100);
rlm@9 239 try {latch.await();}
rlm@9 240 catch (InterruptedException e) {e.printStackTrace();}
rlm@9 241 step(samplesToGet);
rlm@9 242 updateAllListeners();
rlm@9 243
rlm@9 244 for (int i = 0; i < this.listeners.size(); i++){
rlm@9 245 buffer.clear();
rlm@9 246 this.getSamples(buffer, samplesToGet, i);
rlm@9 247 SoundProcessor sp =
rlm@9 248 this.soundProcessorMap.get(this.listeners.get(i));
rlm@9 249 if (null != sp){sp.process(buffer, samplesToGet*BYTES_PER_SAMPLE);}
rlm@9 250 }
rlm@9 251
rlm@9 252 }
rlm@9 253
rlm@9 254 public void update(float tpf){
rlm@9 255 super.update(tpf);
rlm@9 256 dispatchAudio(tpf);
rlm@9 257 }
rlm@9 258
rlm@9 259 }
rlm@9 260