• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 package com.jme3.audio.android;
33 
34 import android.app.Activity;
35 import android.content.Context;
36 import android.content.res.AssetFileDescriptor;
37 import android.content.res.AssetManager;
38 import android.media.AudioManager;
39 import android.media.MediaPlayer;
40 import android.media.SoundPool;
41 import android.util.Log;
42 
43 import com.jme3.asset.AssetKey;
44 import com.jme3.audio.AudioNode.Status;
45 import com.jme3.audio.*;
46 import com.jme3.math.FastMath;
47 import com.jme3.math.Vector3f;
48 import java.io.IOException;
49 import java.util.HashMap;
50 import java.util.concurrent.atomic.AtomicBoolean;
51 import java.util.logging.Level;
52 import java.util.logging.Logger;
53 
54 /**
55  * This class is the android implementation for {@link AudioRenderer}
56  *
57  * @author larynx
58  * @author plan_rich
59  */
60 public class AndroidAudioRenderer implements AudioRenderer,
61         SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
62 
63     private static final Logger logger = Logger.getLogger(AndroidAudioRenderer.class.getName());
64     private final static int MAX_NUM_CHANNELS = 16;
65     private final HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
66     private SoundPool soundPool = null;
67     private final Vector3f listenerPosition = new Vector3f();
68     // For temp use
69     private final Vector3f distanceVector = new Vector3f();
70     private final Context context;
71     private final AssetManager assetManager;
72     private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
73     private Listener listener;
74     private boolean audioDisabled = false;
75     private final AudioManager manager;
76 
AndroidAudioRenderer(Activity context)77     public AndroidAudioRenderer(Activity context) {
78         this.context = context;
79         manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
80         context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
81         assetManager = context.getAssets();
82     }
83 
84     @Override
initialize()85     public void initialize() {
86         soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
87                 0);
88         soundPool.setOnLoadCompleteListener(this);
89     }
90 
91     @Override
updateSourceParam(AudioNode src, AudioParam param)92     public void updateSourceParam(AudioNode src, AudioParam param) {
93         // logger.log(Level.INFO, "updateSourceParam " + param);
94 
95         if (audioDisabled) {
96             return;
97         }
98 
99         if (src.getChannel() < 0) {
100             return;
101         }
102 
103         switch (param) {
104             case Position:
105                 if (!src.isPositional()) {
106                     return;
107                 }
108 
109                 Vector3f pos = src.getWorldTranslation();
110                 break;
111             case Velocity:
112                 if (!src.isPositional()) {
113                     return;
114                 }
115 
116                 Vector3f vel = src.getVelocity();
117                 break;
118             case MaxDistance:
119                 if (!src.isPositional()) {
120                     return;
121                 }
122                 break;
123             case RefDistance:
124                 if (!src.isPositional()) {
125                     return;
126                 }
127                 break;
128             case ReverbFilter:
129                 if (!src.isPositional() || !src.isReverbEnabled()) {
130                     return;
131                 }
132                 break;
133             case ReverbEnabled:
134                 if (!src.isPositional()) {
135                     return;
136                 }
137 
138                 if (src.isReverbEnabled()) {
139                     updateSourceParam(src, AudioParam.ReverbFilter);
140                 }
141                 break;
142             case IsPositional:
143                 break;
144             case Direction:
145                 if (!src.isDirectional()) {
146                     return;
147                 }
148 
149                 Vector3f dir = src.getDirection();
150                 break;
151             case InnerAngle:
152                 if (!src.isDirectional()) {
153                     return;
154                 }
155                 break;
156             case OuterAngle:
157                 if (!src.isDirectional()) {
158                     return;
159                 }
160                 break;
161             case IsDirectional:
162                 if (src.isDirectional()) {
163                     updateSourceParam(src, AudioParam.Direction);
164                     updateSourceParam(src, AudioParam.InnerAngle);
165                     updateSourceParam(src, AudioParam.OuterAngle);
166                 } else {
167                 }
168                 break;
169             case DryFilter:
170                 if (src.getDryFilter() != null) {
171                     Filter f = src.getDryFilter();
172                     if (f.isUpdateNeeded()) {
173                         // updateFilter(f);
174                     }
175                 }
176                 break;
177             case Looping:
178                 if (src.isLooping()) {
179                 }
180                 break;
181             case Volume:
182 
183                 soundPool.setVolume(src.getChannel(), src.getVolume(),
184                         src.getVolume());
185 
186                 break;
187             case Pitch:
188 
189                 break;
190         }
191 
192     }
193 
194     @Override
updateListenerParam(Listener listener, ListenerParam param)195     public void updateListenerParam(Listener listener, ListenerParam param) {
196         // logger.log(Level.INFO, "updateListenerParam " + param);
197         if (audioDisabled) {
198             return;
199         }
200 
201         switch (param) {
202             case Position:
203                 listenerPosition.set(listener.getLocation());
204 
205                 break;
206             case Rotation:
207                 Vector3f dir = listener.getDirection();
208                 Vector3f up = listener.getUp();
209 
210                 break;
211             case Velocity:
212                 Vector3f vel = listener.getVelocity();
213 
214                 break;
215             case Volume:
216                 // alListenerf(AL_GAIN, listener.getVolume());
217                 break;
218         }
219 
220     }
221 
222     @Override
update(float tpf)223     public void update(float tpf) {
224         float distance;
225         float volume;
226 
227         // Loop over all mediaplayers
228         for (AudioNode src : musicPlaying.keySet()) {
229 
230             MediaPlayer mp = musicPlaying.get(src);
231             {
232                 // Calc the distance to the listener
233                 distanceVector.set(listenerPosition);
234                 distanceVector.subtractLocal(src.getLocalTranslation());
235                 distance = FastMath.abs(distanceVector.length());
236 
237                 if (distance < src.getRefDistance()) {
238                     distance = src.getRefDistance();
239                 }
240                 if (distance > src.getMaxDistance()) {
241                     distance = src.getMaxDistance();
242                 }
243                 volume = src.getRefDistance() / distance;
244 
245                 AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
246 
247                 if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
248                     // Left / Right channel get the same volume by now, only
249                     // positional
250                     mp.setVolume(volume, volume);
251 
252                     audioData.setCurrentVolume(volume);
253                 }
254             }
255         }
256     }
257 
setListener(Listener listener)258     public void setListener(Listener listener) {
259         if (audioDisabled) {
260             return;
261         }
262 
263         if (this.listener != null) {
264             // previous listener no longer associated with current
265             // renderer
266             this.listener.setRenderer(null);
267         }
268 
269         this.listener = listener;
270         this.listener.setRenderer(this);
271 
272     }
273 
274     @Override
cleanup()275     public void cleanup() {
276         // Cleanup sound pool
277         if (soundPool != null) {
278             soundPool.release();
279             soundPool = null;
280         }
281 
282         // Cleanup media player
283         for (AudioNode src : musicPlaying.keySet()) {
284             MediaPlayer mp = musicPlaying.get(src);
285             {
286                 mp.stop();
287                 mp.release();
288                 src.setStatus(Status.Stopped);
289             }
290         }
291         musicPlaying.clear();
292     }
293 
294     @Override
onCompletion(MediaPlayer mp)295     public void onCompletion(MediaPlayer mp) {
296         mp.seekTo(0);
297         mp.stop();
298         // XXX: This has bad performance -> maybe change overall structure of
299         // mediaplayer in this audiorenderer?
300         for (AudioNode src : musicPlaying.keySet()) {
301             if (musicPlaying.get(src) == mp) {
302                 src.setStatus(Status.Stopped);
303                 break;
304             }
305         }
306     }
307 
308     /**
309      * Plays using the {@link SoundPool} of Android. Due to hard limitation of
310      * the SoundPool: After playing more instances of the sound you only have
311      * the channel of the last played instance.
312      *
313      * It is not possible to get information about the state of the soundpool of
314      * a specific streamid, so removing is not possilbe -> noone knows when
315      * sound finished.
316      */
playSourceInstance(AudioNode src)317     public void playSourceInstance(AudioNode src) {
318         if (audioDisabled) {
319             return;
320         }
321 
322         AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
323 
324         if (!(audioData.getAssetKey() instanceof AudioKey)) {
325             throw new IllegalArgumentException("Asset is not a AudioKey");
326         }
327 
328         AudioKey assetKey = (AudioKey) audioData.getAssetKey();
329 
330         try {
331             if (audioData.getId() < 0) { // found something to load
332                 int soundId = soundPool.load(
333                         assetManager.openFd(assetKey.getName()), 1);
334                 audioData.setId(soundId);
335             }
336 
337             int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
338 
339             if (channel == 0) {
340                 soundpoolStillLoading.put(audioData.getId(), src);
341             } else {
342                 src.setChannel(channel); // receive a channel at the last
343                 // playing at least
344             }
345         } catch (IOException e) {
346             logger.log(Level.SEVERE,
347                     "Failed to load sound " + assetKey.getName(), e);
348             audioData.setId(-1);
349         }
350     }
351 
352     @Override
onLoadComplete(SoundPool soundPool, int sampleId, int status)353     public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
354         AudioNode src = soundpoolStillLoading.remove(sampleId);
355 
356         if (src == null) {
357             logger.warning("Something went terribly wrong! onLoadComplete"
358                     + " had sampleId which was not in the HashMap of loading items");
359             return;
360         }
361 
362         AudioData audioData = src.getAudioData();
363 
364         if (status == 0) // load was successfull
365         {
366             int channelIndex;
367             channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
368             src.setChannel(channelIndex);
369         }
370     }
371 
playSource(AudioNode src)372     public void playSource(AudioNode src) {
373         if (audioDisabled) {
374             return;
375         }
376 
377         AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
378 
379         MediaPlayer mp = musicPlaying.get(src);
380         if (mp == null) {
381             mp = new MediaPlayer();
382             mp.setOnCompletionListener(this);
383             mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
384         }
385 
386         try {
387             AssetKey<?> key = audioData.getAssetKey();
388 
389             AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
390             mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
391                     afd.getLength());
392             mp.prepare();
393             mp.setLooping(src.isLooping());
394             mp.start();
395             src.setChannel(0);
396             src.setStatus(Status.Playing);
397             musicPlaying.put(src, mp);
398 
399         } catch (IllegalStateException e) {
400             e.printStackTrace();
401         } catch (Exception e) {
402             e.printStackTrace();
403         }
404     }
405 
406     /**
407      * Pause the current playing sounds. Both from the {@link SoundPool} and the
408      * active {@link MediaPlayer}s
409      */
pauseAll()410     public void pauseAll() {
411         if (soundPool != null) {
412             soundPool.autoPause();
413             for (MediaPlayer mp : musicPlaying.values()) {
414                 mp.pause();
415             }
416         }
417     }
418 
419     /**
420      * Resume all paused sounds.
421      */
resumeAll()422     public void resumeAll() {
423         if (soundPool != null) {
424             soundPool.autoResume();
425             for (MediaPlayer mp : musicPlaying.values()) {
426                 mp.start(); //no resume -> api says call start to resume
427             }
428         }
429     }
430 
pauseSource(AudioNode src)431     public void pauseSource(AudioNode src) {
432         if (audioDisabled) {
433             return;
434         }
435 
436         MediaPlayer mp = musicPlaying.get(src);
437         if (mp != null) {
438             mp.pause();
439             src.setStatus(Status.Paused);
440         } else {
441             int channel = src.getChannel();
442             if (channel != -1) {
443                 soundPool.pause(channel); // is not very likley to make
444             }											// something useful :)
445         }
446     }
447 
stopSource(AudioNode src)448     public void stopSource(AudioNode src) {
449         if (audioDisabled) {
450             return;
451         }
452 
453         // can be stream or buffer -> so try to get mediaplayer
454         // if there is non try to stop soundpool
455         MediaPlayer mp = musicPlaying.get(src);
456         if (mp != null) {
457             mp.stop();
458             src.setStatus(Status.Paused);
459         } else {
460             int channel = src.getChannel();
461             if (channel != -1) {
462                 soundPool.pause(channel); // is not very likley to make
463                 // something useful :)
464             }
465         }
466 
467     }
468 
469     @Override
deleteAudioData(AudioData ad)470     public void deleteAudioData(AudioData ad) {
471 
472         for (AudioNode src : musicPlaying.keySet()) {
473             if (src.getAudioData() == ad) {
474                 MediaPlayer mp = musicPlaying.remove(src);
475                 mp.stop();
476                 mp.release();
477                 src.setStatus(Status.Stopped);
478                 src.setChannel(-1);
479                 ad.setId(-1);
480                 break;
481             }
482         }
483 
484         if (ad.getId() > 0) {
485             soundPool.unload(ad.getId());
486             ad.setId(-1);
487         }
488     }
489 
490     @Override
setEnvironment(Environment env)491     public void setEnvironment(Environment env) {
492         // not yet supported
493     }
494 
495     @Override
deleteFilter(Filter filter)496     public void deleteFilter(Filter filter) {
497     }
498 }
499