annotate src/com/aurellem/capture/audio/AudioSendRenderer.java @ 38:adeb88787645

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