* Applied android audio renderer patch (thanks to prich on the forum)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9147 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Sha..rd 13 years ago
parent f357a7e89f
commit 381a3365ef
  1. 6
      engine/src/android/com/jme3/audio/android/AndroidAudioData.java
  2. 340
      engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java
  3. 7
      engine/src/android/com/jme3/audio/plugins/AndroidAudioLoader.java

@ -7,7 +7,7 @@ import com.jme3.util.NativeObject;
public class AndroidAudioData extends AudioData { public class AndroidAudioData extends AudioData {
protected AssetKey assetKey; protected AssetKey<?> assetKey;
protected float currentVolume = 0f; protected float currentVolume = 0f;
public AndroidAudioData(){ public AndroidAudioData(){
@ -18,11 +18,11 @@ public class AndroidAudioData extends AudioData {
super(id); super(id);
} }
public AssetKey getAssetKey() { public AssetKey<?> getAssetKey() {
return assetKey; return assetKey;
} }
public void setAssetKey(AssetKey assetKey) { public void setAssetKey(AssetKey<?> assetKey) {
this.assetKey = assetKey; this.assetKey = assetKey;
} }

@ -38,6 +38,9 @@ import android.content.res.AssetManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.SoundPool; import android.media.SoundPool;
import android.util.Log;
import com.jme3.asset.AssetKey;
import com.jme3.audio.AudioNode.Status; import com.jme3.audio.AudioNode.Status;
import com.jme3.audio.*; import com.jme3.audio.*;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
@ -50,42 +53,48 @@ import java.util.logging.Logger;
/** /**
* This class is the android implementation for {@link AudioRenderer} * This class is the android implementation for {@link AudioRenderer}
* @author larynx
* *
* @author larynx
* @author plan_rich
*/ */
public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { public class AndroidAudioRenderer implements AudioRenderer,
SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
private static final Logger logger = Logger.getLogger(AndroidAudioRenderer.class.getName()); private static final Logger logger = Logger
.getLogger(AndroidAudioRenderer.class.getName());
private final static int MAX_NUM_CHANNELS = 16; private final static int MAX_NUM_CHANNELS = 16;
private final HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
private SoundPool soundPool = null; private SoundPool soundPool = null;
private HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
private final Vector3f listenerPosition = new Vector3f(); private final Vector3f listenerPosition = new Vector3f();
// For temp use // For temp use
private final Vector3f distanceVector = new Vector3f(); private final Vector3f distanceVector = new Vector3f();
private final AudioManager manager;
private final Context context; private final Context context;
private final AssetManager am; private final AssetManager assetManager;
private HashMap<Integer, AudioNode> mapLoadingAudioNodes = new HashMap<Integer, AudioNode>(); private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
private final AtomicBoolean lastLoadCompleted = new AtomicBoolean();
private Listener listener; private Listener listener;
private boolean audioDisabled = false; private boolean audioDisabled = false;
private final AudioManager manager;
public AndroidAudioRenderer(Activity context) { public AndroidAudioRenderer(Activity context) {
this.context = context; this.context = context;
manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); manager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE);
context.setVolumeControlStream(AudioManager.STREAM_MUSIC); context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
am = context.getAssets(); assetManager = context.getAssets();
} }
@Override @Override
public void initialize() { public void initialize() {
soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC, 0); soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
0);
soundPool.setOnLoadCompleteListener(this); soundPool.setOnLoadCompleteListener(this);
} }
@Override @Override
public void updateSourceParam(AudioNode src, AudioParam param) { public void updateSourceParam(AudioNode src, AudioParam param) {
//logger.log(Level.INFO, "updateSourceParam " + param); // logger.log(Level.INFO, "updateSourceParam " + param);
if (audioDisabled) { if (audioDisabled) {
return; return;
@ -95,9 +104,6 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
return; return;
} }
assert src.getChannel() >= 0;
switch (param) { switch (param) {
case Position: case Position:
if (!src.isPositional()) { if (!src.isPositional()) {
@ -168,7 +174,7 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
if (src.getDryFilter() != null) { if (src.getDryFilter() != null) {
Filter f = src.getDryFilter(); Filter f = src.getDryFilter();
if (f.isUpdateNeeded()) { if (f.isUpdateNeeded()) {
//updateFilter(f); // updateFilter(f);
} }
} }
break; break;
@ -178,7 +184,8 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
break; break;
case Volume: case Volume:
soundPool.setVolume(src.getChannel(), src.getVolume(), src.getVolume()); soundPool.setVolume(src.getChannel(), src.getVolume(),
src.getVolume());
break; break;
case Pitch: case Pitch:
@ -190,7 +197,7 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
@Override @Override
public void updateListenerParam(Listener listener, ListenerParam param) { public void updateListenerParam(Listener listener, ListenerParam param) {
//logger.log(Level.INFO, "updateListenerParam " + param); // logger.log(Level.INFO, "updateListenerParam " + param);
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
@ -210,7 +217,7 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
break; break;
case Volume: case Volume:
//alListenerf(AL_GAIN, listener.getVolume()); // alListenerf(AL_GAIN, listener.getVolume());
break; break;
} }
@ -239,16 +246,16 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
} }
volume = src.getRefDistance() / distance; volume = src.getRefDistance() / distance;
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); AndroidAudioData audioData = (AndroidAudioData) src
.getAudioData();
if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) { if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
// Left / Right channel get the same volume by now, only positional // Left / Right channel get the same volume by now, only
// positional
mp.setVolume(volume, volume); mp.setVolume(volume, volume);
audioData.setCurrentVolume(volume); audioData.setCurrentVolume(volume);
} }
} }
} }
} }
@ -269,46 +276,10 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
} }
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
AudioNode src = mapLoadingAudioNodes.get(sampleId);
if (src.getAudioData() instanceof AndroidAudioData) {
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (status == 0) // load was successfull
{
int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, -1, 1f);
src.setChannel(channelIndex);
// Playing started ?
if (src.getChannel() > 0) {
src.setStatus(Status.Playing);
}
} else {
src.setChannel(-1);
}
} else {
throw new IllegalArgumentException("AudioData is not of type AndroidAudioData for AudioNode " + src.toString());
}
}
@Override @Override
public void cleanup() { public void cleanup() {
// Cleanup sound pool // Cleanup sound pool
if (soundPool != null) { if (soundPool != null) {
for (AudioNode src : mapLoadingAudioNodes.values()) {
if ((src.getStatus() == Status.Playing) && (src.getChannel() > 0)) {
soundPool.stop(src.getChannel());
}
if (src.getAudioData() instanceof AndroidAudioData) {
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (audioData.getId() > 0) {
soundPool.unload(audioData.getId());
}
}
}
soundPool.release(); soundPool.release();
soundPool = null; soundPool = null;
} }
@ -327,112 +298,80 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(MediaPlayer mp) {
for (AudioNode src : musicPlaying.keySet()) {
if (musicPlaying.get(src) == mp) {
mp.seekTo(0); mp.seekTo(0);
mp.stop(); mp.stop();
// XXX: This has bad performance -> maybe change overall structure of
// mediaplayer in this audiorenderer?
for (AudioNode src : musicPlaying.keySet()) {
if (musicPlaying.get(src) == mp) {
src.setStatus(Status.Stopped); src.setStatus(Status.Stopped);
break; break;
} }
} }
} }
/**
* Plays using the {@link SoundPool} of Android. Due to hard limitation of
* the SoundPool: After playing more instances of the sound you only have
* the channel of the last played instance.
*
* It is not possible to get information about the state of the soundpool of
* a specific streamid, so removing is not possilbe -> noone knows when
* sound finished.
*/
public void playSourceInstance(AudioNode src) { public void playSourceInstance(AudioNode src) {
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
AndroidAudioData audioData; AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
int soundId = 0;
if (src.getAudioData() instanceof AndroidAudioData) { if (!(audioData.getAssetKey() instanceof AudioKey)) {
audioData = (AndroidAudioData) src.getAudioData(); throw new IllegalArgumentException("Asset is not a AudioKey");
}
if (audioData.getAssetKey() instanceof AudioKey) {
AudioKey assetKey = (AudioKey) audioData.getAssetKey(); AudioKey assetKey = (AudioKey) audioData.getAssetKey();
// streaming audionodes get played using android mediaplayer, non streaming uses SoundPool
if (assetKey.isStream()) {
MediaPlayer mp;
if (musicPlaying.containsKey(src)) {
mp = musicPlaying.get(src);
} else {
mp = new MediaPlayer();
mp.setOnCompletionListener(this);
//mp = MediaPlayer.create(context, new Ur );
musicPlaying.put(src, mp);
}
if (!mp.isPlaying()) {
try { try {
AssetFileDescriptor afd = am.openFd(assetKey.getName()); if (audioData.getId() < 0) { // found something to load
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); int soundId = soundPool.load(
assetManager.openFd(assetKey.getName()), 1);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC); audioData.setId(soundId);
mp.prepare();
mp.setLooping(src.isLooping());
mp.start();
src.setChannel(1);
src.setStatus(Status.Playing);
} catch (IllegalArgumentException e) {
logger.log(Level.SEVERE, "Failed to play " + assetKey.getName(), e);
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
logger.log(Level.SEVERE, "Failed to play " + assetKey.getName(), e);
} catch (IOException e) {
// TODO Auto-generated catch block
logger.log(Level.SEVERE, "Failed to play " + assetKey.getName(), e);
} }
} int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
if (channel == 0) {
soundpoolStillLoading.put(audioData.getId(), src);
} else { } else {
// Low latency Sound effect using SoundPool src.setChannel(channel); // receive a channel at the last
if (audioData.isUpdateNeeded() || (audioData.getId() <= 0)) { // playing at least
if (audioData.getId() > 0) {
if (src.getChannel() > 0) {
soundPool.stop(src.getChannel());
src.setChannel(-1);
} }
soundPool.unload(audioData.getId());
}
try {
soundId = soundPool.load(am.openFd(assetKey.getName()), 1);
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, "Failed to load sound " + assetKey.getName(), e); logger.log(Level.SEVERE,
soundId = -1; "Failed to load sound " + assetKey.getName(), e);
} audioData.setId(-1);
audioData.setId(soundId);
}
// Sound failed to load ?
if (audioData.getId() <= 0) {
throw new IllegalArgumentException("Failed to load: " + assetKey.getName());
} else {
int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, -1, 1f);
if (channelIndex == 0) {
// Loading is not finished
// Store the soundId and the AudioNode for async loading and later play start
mapLoadingAudioNodes.put(audioData.getId(), src);
} }
src.setChannel(channelIndex);
} }
// Playing started ? @Override
if (src.getChannel() > 0) { public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
src.setStatus(Status.Playing); AudioNode src = soundpoolStillLoading.remove(sampleId);
}
}
} if (src == null) {
} else { logger.warning("Something went terribly wrong! onLoadComplete"
throw new IllegalArgumentException("AudioData is not of type AndroidAudioData for AudioNode " + src.toString()); + " had sampleId which was not in the HashMap of loading items");
return;
} }
AudioData audioData = src.getAudioData();
if (status == 0) // load was successfull
{
int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
src.setChannel(channelIndex);
}
} }
public void playSource(AudioNode src) { public void playSource(AudioNode src) {
@ -440,15 +379,33 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
return; return;
} }
//assert src.getStatus() == Status.Stopped || src.getChannel() == -1; AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (src.getStatus() == Status.Playing) { MediaPlayer mp = musicPlaying.get(src);
return; if (mp == null) {
} else if (src.getStatus() == Status.Stopped) { mp = new MediaPlayer();
playSourceInstance(src); mp.setOnCompletionListener(this);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
} }
try {
AssetKey<?> key = audioData.getAssetKey();
AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
afd.getLength());
mp.prepare();
mp.setLooping(src.isLooping());
mp.start();
src.setChannel(0);
src.setStatus(Status.Playing);
musicPlaying.put(src, mp);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} }
public void pauseSource(AudioNode src) { public void pauseSource(AudioNode src) {
@ -456,119 +413,66 @@ public class AndroidAudioRenderer implements AudioRenderer, SoundPool.OnLoadComp
return; return;
} }
if (src.getStatus() == Status.Playing) { MediaPlayer mp = musicPlaying.get(src);
if (src.getAudioData() instanceof AndroidAudioData) { if (mp != null) {
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (audioData.getAssetKey() instanceof AudioKey) {
AudioKey assetKey = (AudioKey) audioData.getAssetKey();
if (assetKey.isStream()) {
MediaPlayer mp;
if (musicPlaying.containsKey(src)) {
mp = musicPlaying.get(src);
mp.pause(); mp.pause();
src.setStatus(Status.Paused); src.setStatus(Status.Paused);
}
} else { } else {
assert src.getChannel() != -1; int channel = src.getChannel();
if (channel != -1)
if (src.getChannel() > 0) { soundPool.pause(channel); // is not very likley to make
soundPool.pause(src.getChannel()); // something useful :)
src.setStatus(Status.Paused);
}
}
} }
} }
}
}
public void stopSource(AudioNode src) { public void stopSource(AudioNode src) {
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
// can be stream or buffer -> so try to get mediaplayer
if (src.getStatus() != Status.Stopped) { // if there is non try to stop soundpool
if (src.getAudioData() instanceof AndroidAudioData) { MediaPlayer mp = musicPlaying.get(src);
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); if (mp != null) {
if (audioData.getAssetKey() instanceof AudioKey) {
AudioKey assetKey = (AudioKey) audioData.getAssetKey();
if (assetKey.isStream()) {
MediaPlayer mp;
if (musicPlaying.containsKey(src)) {
mp = musicPlaying.get(src);
mp.stop(); mp.stop();
src.setStatus(Status.Stopped); src.setStatus(Status.Paused);
src.setChannel(-1);
}
} else { } else {
int chan = src.getChannel(); int channel = src.getChannel();
assert chan != -1; // if it's not stopped, must have id if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
if (src.getChannel() > 0) { // something useful :)
soundPool.stop(src.getChannel());
src.setChannel(-1);
}
src.setStatus(Status.Stopped);
if (audioData.getId() > 0) {
soundPool.unload(audioData.getId());
}
audioData.setId(-1);
}
} }
} }
} }
}
public void updateAudioData(AndroidAudioData data) {
throw new UnsupportedOperationException("updateAudioData");
}
public void deleteFilter(Filter filter) {
}
@Override @Override
public void deleteAudioData(AudioData ad) { public void deleteAudioData(AudioData ad) {
if (ad instanceof AndroidAudioData) {
AndroidAudioData audioData = (AndroidAudioData) ad;
if (audioData.getAssetKey() instanceof AudioKey) {
AudioKey assetKey = (AudioKey) audioData.getAssetKey();
if (assetKey.isStream()) {
for (AudioNode src : musicPlaying.keySet()) { for (AudioNode src : musicPlaying.keySet()) {
if (src.getAudioData() == ad) { if (src.getAudioData() == ad) {
MediaPlayer mp = musicPlaying.get(src); MediaPlayer mp = musicPlaying.remove(src);
mp.stop(); mp.stop();
mp.release(); mp.release();
musicPlaying.remove(src);
src.setStatus(Status.Stopped); src.setStatus(Status.Stopped);
src.setChannel(-1); src.setChannel(-1);
ad.setId(-1);
break; break;
} }
} }
} else {
if (audioData.getId() > 0) {
soundPool.unload(audioData.getId());
}
audioData.setId(0);
}
} if (ad.getId() > 0) {
} else { soundPool.unload(ad.getId());
throw new IllegalArgumentException("AudioData is not of type AndroidAudioData in deleteAudioData"); ad.setId(-1);
} }
} }
@Override @Override
public void setEnvironment(Environment env) { public void setEnvironment(Environment env) {
// TODO Auto-generated method stub //not yet supported
}
@Override
public void deleteFilter(Filter filter) {
} }
} }

@ -4,7 +4,6 @@ import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetLoader;
import com.jme3.audio.android.AndroidAudioData; import com.jme3.audio.android.AndroidAudioData;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
public class AndroidAudioLoader implements AssetLoader public class AndroidAudioLoader implements AssetLoader
{ {
@ -12,12 +11,6 @@ public class AndroidAudioLoader implements AssetLoader
@Override @Override
public Object load(AssetInfo assetInfo) throws IOException public Object load(AssetInfo assetInfo) throws IOException
{ {
InputStream in = assetInfo.openStream();
if (in != null)
{
in.close();
}
AndroidAudioData result = new AndroidAudioData(); AndroidAudioData result = new AndroidAudioData();
result.setAssetKey( assetInfo.getKey() ); result.setAssetKey( assetInfo.getKey() );
return result; return result;

Loading…
Cancel
Save