rlm@3: package com.aurellem.capture; rlm@3: rlm@3: import java.lang.reflect.Field; rlm@3: import java.nio.ByteBuffer; rlm@3: import java.util.HashMap; rlm@3: import java.util.Vector; rlm@3: import java.util.concurrent.CountDownLatch; rlm@3: import java.util.logging.Level; rlm@3: import java.util.logging.Logger; rlm@3: rlm@3: import org.lwjgl.LWJGLException; rlm@3: import org.lwjgl.openal.AL; rlm@3: import org.lwjgl.openal.AL10; rlm@3: import org.lwjgl.openal.ALCdevice; rlm@3: import org.lwjgl.openal.OpenALException; rlm@3: rlm@3: import com.jme3.audio.Listener; rlm@3: import com.jme3.audio.lwjgl.LwjglAudioRenderer; rlm@3: import com.jme3.math.Vector3f; rlm@3: import com.jme3.util.BufferUtils; rlm@3: rlm@3: public class AudioSend rlm@3: extends LwjglAudioRenderer implements MultiListener { rlm@3: rlm@3: /** rlm@3: * Keeps track of all the listeners which have been registered so far. rlm@3: * The first element is null, which represents the zeroth rlm@3: * LWJGL listener which is created automatically. rlm@3: */ rlm@3: public Vector listeners = new Vector(); rlm@3: rlm@3: public void initialize(){ rlm@3: super.initialize(); rlm@3: listeners.add(null); rlm@3: } rlm@3: rlm@3: /** rlm@3: * This is to call the native methods which require the OpenAL device ID. rlm@3: * currently it is obtained through reflection. rlm@3: */ rlm@3: private long deviceID; rlm@3: rlm@3: /** rlm@3: * To ensure that deviceID and listeners are rlm@3: * properly initialized before any additional listeners are added. rlm@3: */ rlm@3: private CountDownLatch latch = new CountDownLatch(1); rlm@3: rlm@3: private void waitForInit(){ rlm@3: try {latch.await();} rlm@3: catch (InterruptedException e) {e.printStackTrace();} rlm@3: } rlm@3: rlm@3: /** rlm@3: * Each listener (including the main LWJGL listener) can be registered rlm@3: * with a SoundProcessor, which this Renderer will call whenever rlm@3: * there is new audio data to be processed. rlm@3: */ rlm@3: public HashMap soundProcessorMap = rlm@3: new HashMap(); rlm@3: rlm@3: rlm@3: /** rlm@3: * Create a new slave context on the recorder device which will render all the rlm@3: * sounds in the main LWJGL context with respect to this listener. rlm@3: */ rlm@3: public void addListener(Listener l) { rlm@3: try {this.latch.await();} rlm@3: catch (InterruptedException e) {e.printStackTrace();} rlm@3: this.addListener(); rlm@3: this.listeners.add(l); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Whenever new data is rendered in the perspective of this listener, rlm@3: * this Renderer will send that data to the SoundProcessor of your choosing. rlm@3: */ rlm@3: public void registerSoundProcessor(Listener l, SoundProcessor sp) { rlm@3: this.soundProcessorMap.put(l, sp); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Registers a SoundProcessor for the main LWJGL context. IF all you want to rlm@3: * do is record the sound you would normally hear in your application, then rlm@3: * this is the only method you have to worry about. rlm@3: */ rlm@3: public void registerSoundProcessor(SoundProcessor sp){ rlm@3: // register a sound processor for the default listener. rlm@3: this.soundProcessorMap.put(null, sp); rlm@3: } rlm@3: rlm@3: private static final Logger logger = rlm@3: Logger.getLogger(AudioSend.class.getName()); rlm@3: rlm@3: rlm@3: //////////// Native Methods rlm@3: rlm@3: /** This establishes the LWJGL context as the context which will be copies to all rlm@3: * other contexts. It must be called before any calls to addListener(); rlm@3: */ rlm@3: public void initDevice(){ rlm@3: ninitDevice(this.deviceID);} rlm@3: public static native void ninitDevice(long device); rlm@3: rlm@3: /** rlm@3: * The send device does not automatically process sound. This step function will cause rlm@3: * the desired number of samples to be processed for each listener. The results will then rlm@3: * be available via calls to getSamples() for each listener. rlm@3: * @param samples rlm@3: */ rlm@3: public void step(int samples){ rlm@3: nstep(this.deviceID, samples);} rlm@3: public static native void nstep(long device, int samples); rlm@3: rlm@3: /** rlm@3: * Retrieve the final rendered sound for a particular listener. contextNum == 0 rlm@3: * is the main LWJGL context. rlm@3: * @param buffer rlm@3: * @param samples rlm@3: * @param contextNum rlm@3: */ rlm@3: public void getSamples(ByteBuffer buffer, int samples, int contextNum){ rlm@3: ngetSamples(this.deviceID, buffer, buffer.position(), samples, contextNum);} rlm@3: public static native void ngetSamples( rlm@3: long device, ByteBuffer buffer, int position, int samples, int contextNum); rlm@3: rlm@3: /** rlm@3: * Create an additional listener on the recorder device. The device itself will manage rlm@3: * this listener and synchronize it with the main LWJGL context. Processed sound samples rlm@3: * for this listener will be available via a call to getSamples() with rlm@3: * contextNum equal to the number of times this method has been called. rlm@3: */ rlm@3: public void addListener(){naddListener(this.deviceID);} rlm@3: public static native void naddListener(long device); rlm@3: rlm@3: /** rlm@3: * This will internally call alListener3f in the appropriate slave context and update rlm@3: * that context's listener's parameters. Calling this for a number greater than the current rlm@3: * number of slave contexts will have no effect. rlm@3: * @param pname rlm@3: * @param v1 rlm@3: * @param v2 rlm@3: * @param v3 rlm@3: * @param contextNum rlm@3: */ rlm@3: public void setNthListener3f(int pname, float v1, float v2, float v3, int contextNum){ rlm@3: nsetNthListener3f(pname, v1, v2, v3, this.deviceID, contextNum);} rlm@3: public static native void rlm@3: nsetNthListener3f(int pname, float v1, float v2, float v3, long device, int contextNum); rlm@3: rlm@3: /** rlm@3: * This will internally call alListenerf in the appropriate slave context and update rlm@3: * that context's listener's parameters. Calling this for a number greater than the current rlm@3: * number of slave contexts will have no effect. rlm@3: * @param pname rlm@3: * @param v1 rlm@3: * @param contextNum rlm@3: */ rlm@3: public void setNthListenerf(int pname, float v1, int contextNum){ rlm@3: nsetNthListenerf(pname, v1, this.deviceID, contextNum);} rlm@3: public static native void nsetNthListenerf(int pname, float v1, long device, int contextNum); rlm@3: rlm@3: /** rlm@3: * Instead of taking whatever device is available on the system, this call rlm@3: * creates the "Multiple Audio Send" device, which supports multiple listeners in a limited rlm@3: * capacity. For each listener, the device renders it not to the sound device, but rlm@3: * instead to buffers which it makes available via JNI. rlm@3: */ rlm@3: public void initInThread(){ rlm@3: try{ rlm@3: if (!AL.isCreated()){ rlm@3: AL.create("Multiple Audio Send", 44100, 60, false); rlm@3: } rlm@3: }catch (OpenALException ex){ rlm@3: logger.log(Level.SEVERE, "Failed to load audio library", ex); rlm@3: System.exit(1); rlm@3: return; rlm@3: }catch (LWJGLException ex){ rlm@3: logger.log(Level.SEVERE, "Failed to load audio library", ex); rlm@3: System.exit(1); rlm@3: return; rlm@3: } rlm@3: super.initInThread(); rlm@3: rlm@3: ALCdevice device = AL.getDevice(); rlm@3: rlm@3: // RLM: use reflection to grab the ID of our device for use later. rlm@3: try { rlm@3: Field deviceIDField; rlm@3: deviceIDField = ALCdevice.class.getDeclaredField("device"); rlm@3: deviceIDField.setAccessible(true); rlm@3: try {deviceID = (Long)deviceIDField.get(device);} rlm@3: catch (IllegalArgumentException e) {e.printStackTrace();} rlm@3: catch (IllegalAccessException e) {e.printStackTrace();} rlm@3: deviceIDField.setAccessible(false);} rlm@3: catch (SecurityException e) {e.printStackTrace();} rlm@3: catch (NoSuchFieldException e) {e.printStackTrace();} rlm@3: rlm@3: // the LWJGL context must be established as the master context before rlm@3: // any other listeners can be created on this device. rlm@3: initDevice(); rlm@3: // Now, everything is initialized, and it is safe to add more listeners. rlm@3: latch.countDown(); rlm@3: } rlm@3: rlm@3: rlm@3: public void cleanup(){ rlm@3: for(SoundProcessor sp : this.soundProcessorMap.values()){ rlm@3: sp.cleanup(); rlm@3: } rlm@3: super.cleanup(); rlm@3: } rlm@3: rlm@3: public void updateAllListeners(){ rlm@3: for (int i = 0; i < this.listeners.size(); i++){ rlm@3: Listener lis = this.listeners.get(i); rlm@3: if (null != lis){ rlm@3: Vector3f location = lis.getLocation(); rlm@3: Vector3f velocity = lis.getVelocity(); rlm@3: Vector3f orientation = lis.getUp(); rlm@3: float gain = lis.getVolume(); rlm@3: setNthListener3f(AL10.AL_POSITION, rlm@3: location.x, location.y, location.z, i); rlm@3: setNthListener3f(AL10.AL_VELOCITY, rlm@3: velocity.x, velocity.y, velocity.z, i); rlm@3: setNthListener3f(AL10.AL_ORIENTATION, rlm@3: orientation.x, orientation.y, orientation.z, i); rlm@3: setNthListenerf(AL10.AL_GAIN, gain, i); rlm@3: } rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: public final static int BYTES_PER_SAMPLE = 4; rlm@3: private ByteBuffer buffer = BufferUtils.createByteBuffer(4096); rlm@3: rlm@3: public void dispatchAudio(float tpf){ rlm@3: int samplesToGet = (int) (tpf * 44100); rlm@3: try {latch.await();} rlm@3: catch (InterruptedException e) {e.printStackTrace();} rlm@3: step(samplesToGet); rlm@3: updateAllListeners(); rlm@3: rlm@3: for (int i = 0; i < this.listeners.size(); i++){ rlm@3: buffer.clear(); rlm@3: this.getSamples(buffer, samplesToGet, i); rlm@3: SoundProcessor sp = rlm@3: this.soundProcessorMap.get(this.listeners.get(i)); rlm@3: if (null != sp){sp.process(buffer, samplesToGet*BYTES_PER_SAMPLE);} rlm@3: } rlm@3: rlm@3: } rlm@3: rlm@3: public void update(float tpf){ rlm@3: super.update(tpf); rlm@3: dispatchAudio(tpf); rlm@3: } rlm@3: rlm@3: } rlm@3: