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