rlm@11: package com.aurellem.capture.audio; rlm@11: rlm@43: import java.io.IOException; 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@29: import javax.sound.sampled.AudioFormat; rlm@29: 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@43: import com.jme3.system.JmeSystem; rlm@43: import com.jme3.system.Natives; rlm@11: import com.jme3.util.BufferUtils; rlm@11: rlm@11: public class AudioSendRenderer rlm@11: rlm@65: extends LwjglAudioRenderer implements MultiListener { rlm@11: rlm@65: private AudioSend audioSend; rlm@65: private AudioFormat outFormat; rlm@11: rlm@65: /** rlm@65: * Keeps track of all the listeners which have been registered rlm@65: * so far. The first element is null, which rlm@65: * represents the zeroth LWJGL listener which is created rlm@65: * automatically. rlm@65: */ rlm@65: public Vector listeners = new Vector(); rlm@11: rlm@65: public void initialize(){ rlm@65: super.initialize(); rlm@65: listeners.add(null); rlm@65: } rlm@65: rlm@65: /** rlm@65: * This is to call the native methods which require the OpenAL rlm@65: * device ID. Currently it is obtained through reflection. rlm@65: */ rlm@65: private long deviceID; rlm@65: rlm@65: /** rlm@65: * To ensure that deviceID and rlm@65: * listeners are properly initialized before any rlm@65: * additional listeners are added. rlm@65: */ rlm@65: private CountDownLatch latch = new CountDownLatch(1); rlm@65: rlm@65: /** rlm@65: * Each listener (including the main LWJGL listener) can be rlm@65: * registered with a SoundProcessor, which this rlm@65: * Renderer will call whenever there is new audio data to be rlm@65: * processed. rlm@65: */ rlm@65: public HashMap soundProcessorMap = rlm@65: new HashMap(); rlm@65: rlm@65: /** rlm@65: * Create a new slave context on the recorder device which rlm@65: * will render all the sounds in the main LWJGL context with rlm@65: * respect to this listener. rlm@65: */ rlm@65: public void addListener(Listener l) { rlm@65: try {this.latch.await();} rlm@65: catch (InterruptedException e) {e.printStackTrace();} rlm@65: audioSend.addListener(); rlm@65: this.listeners.add(l); rlm@65: l.setRenderer(this); rlm@65: } rlm@65: rlm@65: /** rlm@65: * Whenever new data is rendered in the perspective of this rlm@65: * listener, this Renderer will send that data to the rlm@65: * SoundProcessor of your choosing. rlm@65: */ rlm@65: public void registerSoundProcessor(Listener l, SoundProcessor sp) { rlm@65: this.soundProcessorMap.put(l, sp); rlm@65: } rlm@65: rlm@65: /** rlm@65: * Registers a SoundProcessor for the main LWJGL context. Ig all rlm@65: * you want to do is record the sound you would normally hear in rlm@65: * your application, then this is the only method you have to rlm@65: * worry about. rlm@65: */ rlm@65: public void registerSoundProcessor(SoundProcessor sp){ rlm@65: // register a sound processor for the default listener. rlm@65: this.soundProcessorMap.put(null, sp); rlm@65: } rlm@65: rlm@65: private static final Logger logger = rlm@65: Logger.getLogger(AudioSendRenderer.class.getName()); rlm@65: rlm@65: /** rlm@65: * Instead of taking whatever device is available on the system, rlm@65: * this call creates the "Multiple Audio Send" device, which rlm@65: * supports multiple listeners in a limited capacity. For each rlm@65: * listener, the device renders it not to the sound device, but rlm@65: * instead to buffers which it makes available via JNI. rlm@65: */ rlm@65: public void initInThread(){ rlm@65: try{ rlm@65: switch (JmeSystem.getPlatform()){ rlm@65: case Windows64: rlm@65: Natives.extractNativeLib("windows/audioSend", rlm@65: "OpenAL64", true, true); rlm@65: break; rlm@65: case Windows32: rlm@65: Natives.extractNativeLib("windows/audioSend", rlm@65: "OpenAL32", true, true); rlm@65: break; rlm@65: case Linux64: rlm@65: Natives.extractNativeLib("linux/audioSend", rlm@65: "openal64", true, true); rlm@65: break; rlm@65: case Linux32: rlm@65: Natives.extractNativeLib("linux/audioSend", rlm@65: "openal", true, true); rlm@65: break; rlm@65: } rlm@11: } rlm@65: catch (IOException ex) {ex.printStackTrace();} rlm@11: rlm@65: try{ rlm@11: if (!AL.isCreated()){ rlm@38: AL.create("Multiple Audio Send", 44100, 60, false); rlm@11: } 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@65: super.initInThread(); rlm@11: rlm@65: ALCdevice device = AL.getDevice(); rlm@11: rlm@65: // RLM: use reflection to grab the ID of our device for use rlm@65: // later. rlm@65: try { rlm@65: Field deviceIDField; rlm@65: deviceIDField = ALCdevice.class.getDeclaredField("device"); rlm@65: deviceIDField.setAccessible(true); rlm@65: try {deviceID = (Long)deviceIDField.get(device);} rlm@65: catch (IllegalArgumentException e) {e.printStackTrace();} rlm@65: catch (IllegalAccessException e) {e.printStackTrace();} rlm@65: deviceIDField.setAccessible(false);} rlm@65: catch (SecurityException e) {e.printStackTrace();} rlm@65: catch (NoSuchFieldException e) {e.printStackTrace();} rlm@11: rlm@65: this.audioSend = new AudioSend(this.deviceID); rlm@65: this.outFormat = audioSend.getAudioFormat(); rlm@65: initBuffer(); rlm@39: rlm@65: // The LWJGL context must be established as the master context rlm@65: // before any other listeners can be created on this device. rlm@65: audioSend.initDevice(); rlm@65: // Now, everything is initialized, and it is safe to add more rlm@65: // listeners. rlm@65: latch.countDown(); rlm@65: } rlm@65: rlm@65: public void cleanup(){ rlm@65: for(SoundProcessor sp : this.soundProcessorMap.values()){ rlm@65: sp.cleanup(); rlm@11: } rlm@65: super.cleanup(); rlm@65: } rlm@65: rlm@65: public void updateAllListeners(){ rlm@65: for (int i = 0; i < this.listeners.size(); i++){ rlm@65: Listener lis = this.listeners.get(i); rlm@65: if (null != lis){ rlm@65: Vector3f location = lis.getLocation(); rlm@65: Vector3f velocity = lis.getVelocity(); rlm@65: Vector3f orientation = lis.getUp(); rlm@65: float gain = lis.getVolume(); rlm@65: audioSend.setNthListener3f rlm@65: (AL10.AL_POSITION, rlm@65: location.x, location.y, location.z, i); rlm@65: audioSend.setNthListener3f rlm@65: (AL10.AL_VELOCITY, rlm@65: velocity.x, velocity.y, velocity.z, i); rlm@65: audioSend.setNthListener3f rlm@65: (AL10.AL_ORIENTATION, rlm@65: orientation.x, orientation.y, orientation.z, i); rlm@65: audioSend.setNthListenerf(AL10.AL_GAIN, gain, i); rlm@65: } rlm@65: } rlm@65: } rlm@11: rlm@65: private ByteBuffer buffer;; rlm@65: rlm@65: public static final int MIN_FRAMERATE = 10; rlm@11: rlm@65: private void initBuffer(){ rlm@65: int bufferSize = rlm@65: (int)(this.outFormat.getSampleRate() / rlm@65: ((float)MIN_FRAMERATE)) * rlm@65: this.outFormat.getFrameSize(); rlm@65: rlm@65: this.buffer = BufferUtils.createByteBuffer(bufferSize); rlm@65: } rlm@65: rlm@65: public void dispatchAudio(float tpf){ rlm@65: rlm@65: int samplesToGet = (int) (tpf * outFormat.getSampleRate()); rlm@65: try {latch.await();} rlm@65: catch (InterruptedException e) {e.printStackTrace();} rlm@65: audioSend.step(samplesToGet); rlm@65: updateAllListeners(); rlm@65: rlm@65: for (int i = 0; i < this.listeners.size(); i++){ rlm@65: buffer.clear(); rlm@65: audioSend.getSamples(buffer, samplesToGet, i); rlm@65: SoundProcessor sp = rlm@65: this.soundProcessorMap.get(this.listeners.get(i)); rlm@65: if (null != sp){ rlm@65: sp.process rlm@65: (buffer, rlm@65: samplesToGet*outFormat.getFrameSize(), outFormat);} rlm@11: } rlm@65: } rlm@36: rlm@65: public void update(float tpf){ rlm@65: super.update(tpf); rlm@11: dispatchAudio(tpf); rlm@65: } rlm@11: } rlm@11: