Home | History | Annotate | Download | only in android
      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 
     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
     85     public void initialize() {
     86         soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
     87                 0);
     88         soundPool.setOnLoadCompleteListener(this);
     89     }
     90 
     91     @Override
     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
    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
    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 
    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
    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
    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      */
    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
    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 
    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      */
    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      */
    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 
    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 
    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
    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
    491     public void setEnvironment(Environment env) {
    492         // not yet supported
    493     }
    494 
    495     @Override
    496     public void deleteFilter(Filter filter) {
    497     }
    498 }
    499