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: