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