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@29
|
30 private static final 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@29
|
109 AL.create("Multiple Audio Send", (int) outFormat.getSampleRate(), 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@11
|
137
|
rlm@11
|
138 // The LWJGL context must be established as the master context before
|
rlm@11
|
139 // any other listeners can be created on this device.
|
rlm@11
|
140 audioSend.initDevice();
|
rlm@11
|
141 // Now, everything is initialized, and it is safe to add more listeners.
|
rlm@11
|
142 latch.countDown();
|
rlm@11
|
143 }
|
rlm@11
|
144
|
rlm@11
|
145
|
rlm@11
|
146 public void cleanup(){
|
rlm@11
|
147 for(SoundProcessor sp : this.soundProcessorMap.values()){
|
rlm@11
|
148 sp.cleanup();
|
rlm@11
|
149 }
|
rlm@11
|
150 super.cleanup();
|
rlm@11
|
151 }
|
rlm@11
|
152
|
rlm@11
|
153 public void updateAllListeners(){
|
rlm@11
|
154 for (int i = 0; i < this.listeners.size(); i++){
|
rlm@11
|
155 Listener lis = this.listeners.get(i);
|
rlm@11
|
156 if (null != lis){
|
rlm@11
|
157 Vector3f location = lis.getLocation();
|
rlm@11
|
158 Vector3f velocity = lis.getVelocity();
|
rlm@11
|
159 Vector3f orientation = lis.getUp();
|
rlm@11
|
160 float gain = lis.getVolume();
|
rlm@11
|
161 audioSend.setNthListener3f(AL10.AL_POSITION,
|
rlm@11
|
162 location.x, location.y, location.z, i);
|
rlm@11
|
163 audioSend.setNthListener3f(AL10.AL_VELOCITY,
|
rlm@11
|
164 velocity.x, velocity.y, velocity.z, i);
|
rlm@11
|
165 audioSend.setNthListener3f(AL10.AL_ORIENTATION,
|
rlm@11
|
166 orientation.x, orientation.y, orientation.z, i);
|
rlm@11
|
167 audioSend.setNthListenerf(AL10.AL_GAIN, gain, i);
|
rlm@11
|
168 }
|
rlm@11
|
169 }
|
rlm@11
|
170 }
|
rlm@11
|
171
|
rlm@11
|
172
|
rlm@29
|
173 //public final static int BYTES_PER_SAMPLE = 4;
|
rlm@11
|
174 private ByteBuffer buffer = BufferUtils.createByteBuffer(4096);
|
rlm@11
|
175
|
rlm@36
|
176 private byte[] debug0 = new byte[4096];
|
rlm@36
|
177 private byte[] debug1 = new byte[4096];
|
rlm@36
|
178
|
rlm@36
|
179
|
rlm@11
|
180 public void dispatchAudio(float tpf){
|
rlm@36
|
181
|
rlm@29
|
182 int samplesToGet = (int) (tpf * outFormat.getSampleRate());
|
rlm@11
|
183 try {latch.await();}
|
rlm@11
|
184 catch (InterruptedException e) {e.printStackTrace();}
|
rlm@11
|
185 audioSend.step(samplesToGet);
|
rlm@11
|
186 updateAllListeners();
|
rlm@11
|
187
|
rlm@11
|
188 for (int i = 0; i < this.listeners.size(); i++){
|
rlm@11
|
189 buffer.clear();
|
rlm@11
|
190 audioSend.getSamples(buffer, samplesToGet, i);
|
rlm@36
|
191 if (i == 0 ) buffer.get(debug0);
|
rlm@36
|
192 if (i == 1 ) buffer.get(debug1);
|
rlm@11
|
193 SoundProcessor sp =
|
rlm@11
|
194 this.soundProcessorMap.get(this.listeners.get(i));
|
rlm@30
|
195 if (null != sp){sp.process(buffer, samplesToGet*outFormat.getFrameSize(), outFormat);}
|
rlm@11
|
196 }
|
rlm@11
|
197
|
rlm@36
|
198 for (int i = 0; i < samplesToGet; i++){
|
rlm@36
|
199 if (debug1[i] != debug0[i]){
|
rlm@36
|
200 System.out.println("inconsistency detected @ sample " + i);
|
rlm@36
|
201 System.out.println("main : " + debug0[i]);
|
rlm@36
|
202 System.out.println("aux : " + debug1[i]);
|
rlm@36
|
203
|
rlm@36
|
204 break;
|
rlm@36
|
205 }
|
rlm@36
|
206
|
rlm@36
|
207 }
|
rlm@36
|
208
|
rlm@11
|
209 }
|
rlm@11
|
210
|
rlm@11
|
211 public void update(float tpf){
|
rlm@11
|
212 super.update(tpf);
|
rlm@11
|
213 dispatchAudio(tpf);
|
rlm@11
|
214 }
|
rlm@11
|
215
|
rlm@11
|
216 }
|
rlm@11
|
217
|