* Applied audio fix patch (thanks prich)

- Pause audio when jME3 app is paused
   - Add WAV to supported formats 
   - Add Android audio test 
 * Minor javadoc fixes in AndroidHarness
 * Made jme3tools.android.Fixed class deprecated

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9152 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Sha..rd 13 years ago
parent 4721c944a2
commit b89b2428af
  1. 77
      engine/src/android/com/jme3/app/AndroidHarness.java
  2. 230
      engine/src/android/com/jme3/asset/AndroidAssetManager.java
  3. 977
      engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java
  4. 40
      engine/src/android/jme3test/android/SimpleSoundTest.java
  5. 4
      engine/src/android/jme3tools/android/Fixed.java

@ -8,15 +8,13 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable; import android.graphics.drawable.NinePatchDrawable;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
import android.os.Bundle; import android.os.Bundle;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.Window; import android.view.*;
import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.jme3.audio.AudioRenderer;
import com.jme3.audio.android.AndroidAudioRenderer;
import com.jme3.input.android.AndroidInput; import com.jme3.input.android.AndroidInput;
import com.jme3.input.controls.TouchListener; import com.jme3.input.controls.TouchListener;
import com.jme3.input.event.TouchEvent; import com.jme3.input.event.TouchEvent;
@ -26,7 +24,6 @@ import com.jme3.system.android.AndroidConfigChooser.ConfigType;
import com.jme3.system.android.JmeAndroidSystem; import com.jme3.system.android.JmeAndroidSystem;
import com.jme3.system.android.OGLESContext; import com.jme3.system.android.OGLESContext;
import com.jme3.util.JmeFormatter; import com.jme3.util.JmeFormatter;
import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.logging.Handler; import java.util.logging.Handler;
@ -43,76 +40,63 @@ import java.util.logging.Logger;
public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener { public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {
protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
/** /**
* The application class to start * The application class to start
*/ */
protected String appClass = "jme3test.android.Test"; protected String appClass = "jme3test.android.Test";
/** /**
* The jme3 application object * The jme3 application object
*/ */
protected Application app = null; protected Application app = null;
/** /**
* ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is
* RGBA8888 or better if supported by the hardware * RGBA8888 or better if supported by the hardware
*/ */
protected ConfigType eglConfigType = ConfigType.FASTEST; protected ConfigType eglConfigType = ConfigType.FASTEST;
/** /**
* If true all valid and not valid egl configs are logged * If true all valid and not valid egl configs are logged
*/ */
protected boolean eglConfigVerboseLogging = false; protected boolean eglConfigVerboseLogging = false;
/** /**
* If true MouseEvents are generated from TouchEvents * If true MouseEvents are generated from TouchEvents
*/ */
protected boolean mouseEventsEnabled = true; protected boolean mouseEventsEnabled = true;
/** /**
* Flip X axis * Flip X axis
*/ */
protected boolean mouseEventsInvertX = true; protected boolean mouseEventsInvertX = true;
/** /**
* Flip Y axis * Flip Y axis
*/ */
protected boolean mouseEventsInvertY = true; protected boolean mouseEventsInvertY = true;
/** /**
* Title of the exit dialog, default is "Do you want to exit?" * Title of the exit dialog, default is "Do you want to exit?"
*/ */
protected String exitDialogTitle = "Do you want to exit?"; protected String exitDialogTitle = "Do you want to exit?";
/** /**
* Message of the exit dialog, default is "Use your home key to bring this * Message of the exit dialog, default is "Use your home key to bring this
* app into the background or exit to terminate it." * app into the background or exit to terminate it."
*/ */
protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it."; protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
/** /**
* Set the screen window mode. * Set the screen window mode. If screenFullSize is true, then the
* If screenFullSize is true, then the notification bar and title bar are * notification bar and title bar are removed and the screen covers the
* removed and the screen covers the entire display.   * entire display.   If screenFullSize is false, then the notification bar
* If screenFullSize is false, then the notification bar remains visible if * remains visible if screenShowTitle is true while screenFullScreen is
* screenShowTitle is true while screenFullScreen is false, * false, then the title bar is also displayed under the notification bar.
* then the title bar is also displayed under the notification bar.
*/ */
protected boolean screenFullScreen = true; protected boolean screenFullScreen = true;
/** /**
* if screenShowTitle is true while screenFullScreen is false, then the * if screenShowTitle is true while screenFullScreen is false, then the
* title bar is also displayed under the notification bar * title bar is also displayed under the notification bar
*/ */
protected boolean screenShowTitle = true; protected boolean screenShowTitle = true;
/** /**
* Splash Screen picture Resource ID. If a Splash Screen is desired, set * Splash Screen picture Resource ID. If a Splash Screen is desired, set
* splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
* splashPicID = 0, then no splash screen will be displayed. * splashPicID = 0, then no splash screen will be displayed.
*/ */
protected int splashPicID = 0; protected int splashPicID = 0;
/** /**
* Set the screen orientation, default is SENSOR * Set the screen orientation, default is SENSOR
* ActivityInfo.SCREEN_ORIENTATION_* constants package * ActivityInfo.SCREEN_ORIENTATION_* constants package
@ -216,6 +200,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
if (app != null) { if (app != null) {
app.restart(); app.restart();
} }
logger.info("onRestart"); logger.info("onRestart");
} }
@ -231,6 +216,14 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
if (view != null) { if (view != null) {
view.onResume(); view.onResume();
} }
//resume the audio
AudioRenderer result = app.getAudioRenderer();
if (result instanceof AndroidAudioRenderer) {
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.resumeAll();
}
isGLThreadPaused = false; isGLThreadPaused = false;
logger.info("onResume"); logger.info("onResume");
} }
@ -241,6 +234,15 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
if (view != null) { if (view != null) {
view.onPause(); view.onPause();
} }
//pause the audio
AudioRenderer result = app.getAudioRenderer();
logger.info("pause: " + result.getClass().getSimpleName());
if (result instanceof AndroidAudioRenderer) {
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.pauseAll();
}
isGLThreadPaused = true; isGLThreadPaused = true;
logger.info("onPause"); logger.info("onPause");
} }
@ -248,6 +250,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
logger.info("onStop"); logger.info("onStop");
} }
@ -256,6 +259,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
if (app != null) { if (app != null) {
app.stop(!isGLThreadPaused); app.stop(!isGLThreadPaused);
} }
logger.info("onDestroy"); logger.info("onDestroy");
super.onDestroy(); super.onDestroy();
} }
@ -265,35 +269,35 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
} }
/** /**
* Called when an error has occurred. * Called when an error has occurred. By default, will show an error message
* By default, will show an error message to the user * to the user and print the exception/error to the log.
* and print the exception/error to the log.
*/ */
public void handleError(final String errorMsg, final Throwable t) { public void handleError(final String errorMsg, final Throwable t) {
String stackTrace = ""; String stackTrace = "";
String title = "Error"; String title = "Error";
if (t != null){ if (t != null) {
// Convert exception to string // Convert exception to string
StringWriter sw = new StringWriter(100); StringWriter sw = new StringWriter(100);
t.printStackTrace(new PrintWriter(sw)); t.printStackTrace(new PrintWriter(sw));
stackTrace = sw.toString(); stackTrace = sw.toString();
title = t.toString(); title = t.toString();
} }
final String finalTitle = title; final String finalTitle = title;
final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+ "\n" + stackTrace; + "\n" + stackTrace;
logger.log(Level.SEVERE, finalMsg); logger.log(Level.SEVERE, finalMsg);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
.setTitle(finalTitle) .setTitle(finalTitle)
.setPositiveButton("Kill", AndroidHarness.this) .setPositiveButton("Kill", AndroidHarness.this)
.setMessage(finalMsg).create(); .setMessage(finalMsg).create();
dialog.show(); dialog.show();
} }
}); });
@ -324,6 +328,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
switch (evt.getType()) { switch (evt.getType()) {
case KEY_UP: case KEY_UP:
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)

@ -1,115 +1,115 @@
/* /*
* Copyright (c) 2009-2010 jMonkeyEngine * Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
* met: * met:
* *
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* *
* * Redistributions in binary form must reproduce the above copyright * * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the * notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. * documentation and/or other materials provided with the distribution.
* *
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software * may be used to endorse or promote products derived from this software
* without specific prior written permission. * without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.asset; package com.jme3.asset;
import com.jme3.asset.plugins.AndroidLocator; import com.jme3.asset.plugins.AndroidLocator;
import com.jme3.asset.plugins.ClasspathLocator; import com.jme3.asset.plugins.ClasspathLocator;
import com.jme3.audio.plugins.AndroidAudioLoader; import com.jme3.audio.plugins.AndroidAudioLoader;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.plugins.AndroidImageLoader; import com.jme3.texture.plugins.AndroidImageLoader;
import java.net.URL; import java.net.URL;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android * <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android
* *
* @author larynx * @author larynx
*/ */
public class AndroidAssetManager extends DesktopAssetManager { public class AndroidAssetManager extends DesktopAssetManager {
private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName()); private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName());
public AndroidAssetManager() { public AndroidAssetManager() {
this(null); this(null);
} }
@Deprecated @Deprecated
public AndroidAssetManager(boolean loadDefaults) { public AndroidAssetManager(boolean loadDefaults) {
//this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg")); //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));
this(null); this(null);
} }
/** /**
* AndroidAssetManager constructor * AndroidAssetManager constructor
* If URL == null then a default list of locators and loaders for android is set * If URL == null then a default list of locators and loaders for android is set
* @param configFile * @param configFile
*/ */
public AndroidAssetManager(URL configFile) { public AndroidAssetManager(URL configFile) {
System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
// Set Default Android config // Set Default Android config
this.registerLocator("", AndroidLocator.class); this.registerLocator("", AndroidLocator.class);
this.registerLocator("", ClasspathLocator.class); this.registerLocator("", ClasspathLocator.class);
this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg"); this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");
this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3"); this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav");
this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m"); this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");
this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md"); this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");
this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");
this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds"); this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds");
this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm"); this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm");
this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr"); this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr");
this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga"); this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga");
this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");
this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj"); this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj");
this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl"); this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl");
this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml"); this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml");
this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml"); this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml");
this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material"); this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material");
this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene"); this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene");
this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");
logger.info("AndroidAssetManager created."); logger.info("AndroidAssetManager created.");
} }
/** /**
* Loads a texture. * Loads a texture.
* *
* @return * @return
*/ */
@Override @Override
public Texture loadTexture(TextureKey key) { public Texture loadTexture(TextureKey key) {
Texture tex = (Texture) loadAsset(key); Texture tex = (Texture) loadAsset(key);
// XXX: This will improve performance on some really // XXX: This will improve performance on some really
// low end GPUs (e.g. ones with OpenGL ES 1 support only) // low end GPUs (e.g. ones with OpenGL ES 1 support only)
// but otherwise won't help on the higher ones. // but otherwise won't help on the higher ones.
// Strongly consider removing this. // Strongly consider removing this.
tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMagFilter(Texture.MagFilter.Nearest);
tex.setAnisotropicFilter(0); tex.setAnisotropicFilter(0);
if (tex.getMinFilter().usesMipMapLevels()) { if (tex.getMinFilter().usesMipMapLevels()) {
tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap); tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap);
} else { } else {
tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps); tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
} }
return tex; return tex;
} }
} }

@ -1,478 +1,499 @@
/* /*
* Copyright (c) 2009-2010 jMonkeyEngine * Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are * modification, are permitted provided that the following conditions are
* met: * met:
* *
* * Redistributions of source code must retain the above copyright * * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer. * notice, this list of conditions and the following disclaimer.
* *
* * Redistributions in binary form must reproduce the above copyright * * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the * notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. * documentation and/or other materials provided with the distribution.
* *
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software * may be used to endorse or promote products derived from this software
* without specific prior written permission. * without specific prior written permission.
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.audio.android; package com.jme3.audio.android;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor; import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager; 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 android.util.Log;
import com.jme3.asset.AssetKey; 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;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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 * @author plan_rich
*/ */
public class AndroidAudioRenderer implements AudioRenderer, public class AndroidAudioRenderer implements AudioRenderer,
SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
private static final Logger logger = Logger private static final Logger logger = Logger
.getLogger(AndroidAudioRenderer.class.getName()); .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 final HashMap<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
private SoundPool soundPool = null; private SoundPool soundPool = null;
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 Context context; private final Context context;
private final AssetManager assetManager; private final AssetManager assetManager;
private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>(); private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
private Listener listener; private Listener listener;
private boolean audioDisabled = false; private boolean audioDisabled = false;
private final AudioManager manager; private final AudioManager manager;
public AndroidAudioRenderer(Activity context) { public AndroidAudioRenderer(Activity context) {
this.context = context; this.context = context;
manager = (AudioManager) context manager = (AudioManager) context
.getSystemService(Context.AUDIO_SERVICE); .getSystemService(Context.AUDIO_SERVICE);
context.setVolumeControlStream(AudioManager.STREAM_MUSIC); context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
assetManager = context.getAssets(); assetManager = context.getAssets();
} }
@Override @Override
public void initialize() { public void initialize() {
soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC, soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
0); 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;
} }
if (src.getChannel() < 0) { if (src.getChannel() < 0) {
return; return;
} }
switch (param) { switch (param) {
case Position: case Position:
if (!src.isPositional()) { if (!src.isPositional()) {
return; return;
} }
Vector3f pos = src.getWorldTranslation(); Vector3f pos = src.getWorldTranslation();
break; break;
case Velocity: case Velocity:
if (!src.isPositional()) { if (!src.isPositional()) {
return; return;
} }
Vector3f vel = src.getVelocity(); Vector3f vel = src.getVelocity();
break; break;
case MaxDistance: case MaxDistance:
if (!src.isPositional()) { if (!src.isPositional()) {
return; return;
} }
break; break;
case RefDistance: case RefDistance:
if (!src.isPositional()) { if (!src.isPositional()) {
return; return;
} }
break; break;
case ReverbFilter: case ReverbFilter:
if (!src.isPositional() || !src.isReverbEnabled()) { if (!src.isPositional() || !src.isReverbEnabled()) {
return; return;
} }
break; break;
case ReverbEnabled: case ReverbEnabled:
if (!src.isPositional()) { if (!src.isPositional()) {
return; return;
} }
if (src.isReverbEnabled()) { if (src.isReverbEnabled()) {
updateSourceParam(src, AudioParam.ReverbFilter); updateSourceParam(src, AudioParam.ReverbFilter);
} }
break; break;
case IsPositional: case IsPositional:
break; break;
case Direction: case Direction:
if (!src.isDirectional()) { if (!src.isDirectional()) {
return; return;
} }
Vector3f dir = src.getDirection(); Vector3f dir = src.getDirection();
break; break;
case InnerAngle: case InnerAngle:
if (!src.isDirectional()) { if (!src.isDirectional()) {
return; return;
} }
break; break;
case OuterAngle: case OuterAngle:
if (!src.isDirectional()) { if (!src.isDirectional()) {
return; return;
} }
break; break;
case IsDirectional: case IsDirectional:
if (src.isDirectional()) { if (src.isDirectional()) {
updateSourceParam(src, AudioParam.Direction); updateSourceParam(src, AudioParam.Direction);
updateSourceParam(src, AudioParam.InnerAngle); updateSourceParam(src, AudioParam.InnerAngle);
updateSourceParam(src, AudioParam.OuterAngle); updateSourceParam(src, AudioParam.OuterAngle);
} else { } else {
} }
break; break;
case DryFilter: case DryFilter:
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;
case Looping: case Looping:
if (src.isLooping()) { if (src.isLooping()) {
} }
break; break;
case Volume: case Volume:
soundPool.setVolume(src.getChannel(), src.getVolume(), soundPool.setVolume(src.getChannel(), src.getVolume(),
src.getVolume()); src.getVolume());
break; break;
case Pitch: case Pitch:
break; break;
} }
} }
@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;
} }
switch (param) { switch (param) {
case Position: case Position:
listenerPosition.set(listener.getLocation()); listenerPosition.set(listener.getLocation());
break; break;
case Rotation: case Rotation:
Vector3f dir = listener.getDirection(); Vector3f dir = listener.getDirection();
Vector3f up = listener.getUp(); Vector3f up = listener.getUp();
break; break;
case Velocity: case Velocity:
Vector3f vel = listener.getVelocity(); Vector3f vel = listener.getVelocity();
break; break;
case Volume: case Volume:
// alListenerf(AL_GAIN, listener.getVolume()); // alListenerf(AL_GAIN, listener.getVolume());
break; break;
} }
} }
@Override @Override
public void update(float tpf) { public void update(float tpf) {
float distance; float distance;
float volume; float volume;
// Loop over all mediaplayers // Loop over all mediaplayers
for (AudioNode src : musicPlaying.keySet()) { for (AudioNode src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src); MediaPlayer mp = musicPlaying.get(src);
{ {
// Calc the distance to the listener // Calc the distance to the listener
distanceVector.set(listenerPosition); distanceVector.set(listenerPosition);
distanceVector.subtractLocal(src.getLocalTranslation()); distanceVector.subtractLocal(src.getLocalTranslation());
distance = FastMath.abs(distanceVector.length()); distance = FastMath.abs(distanceVector.length());
if (distance < src.getRefDistance()) { if (distance < src.getRefDistance()) {
distance = src.getRefDistance(); distance = src.getRefDistance();
} }
if (distance > src.getMaxDistance()) { if (distance > src.getMaxDistance()) {
distance = src.getMaxDistance(); distance = src.getMaxDistance();
} }
volume = src.getRefDistance() / distance; volume = src.getRefDistance() / distance;
AndroidAudioData audioData = (AndroidAudioData) src AndroidAudioData audioData = (AndroidAudioData) src
.getAudioData(); .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 // Left / Right channel get the same volume by now, only
// positional // positional
mp.setVolume(volume, volume); mp.setVolume(volume, volume);
audioData.setCurrentVolume(volume); audioData.setCurrentVolume(volume);
} }
} }
} }
} }
public void setListener(Listener listener) { public void setListener(Listener listener) {
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
if (this.listener != null) { if (this.listener != null) {
// previous listener no longer associated with current // previous listener no longer associated with current
// renderer // renderer
this.listener.setRenderer(null); this.listener.setRenderer(null);
} }
this.listener = listener; this.listener = listener;
this.listener.setRenderer(this); this.listener.setRenderer(this);
} }
@Override @Override
public void cleanup() { public void cleanup() {
// Cleanup sound pool // Cleanup sound pool
if (soundPool != null) { if (soundPool != null) {
soundPool.release(); soundPool.release();
soundPool = null; soundPool = null;
} }
// Cleanup media player // Cleanup media player
for (AudioNode src : musicPlaying.keySet()) { for (AudioNode src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src); MediaPlayer mp = musicPlaying.get(src);
{ {
mp.stop(); mp.stop();
mp.release(); mp.release();
src.setStatus(Status.Stopped); src.setStatus(Status.Stopped);
} }
} }
musicPlaying.clear(); musicPlaying.clear();
} }
@Override @Override
public void onCompletion(MediaPlayer mp) { public void onCompletion(MediaPlayer mp) {
mp.seekTo(0); mp.seekTo(0);
mp.stop(); mp.stop();
// XXX: This has bad performance -> maybe change overall structure of // XXX: This has bad performance -> maybe change overall structure of
// mediaplayer in this audiorenderer? // mediaplayer in this audiorenderer?
for (AudioNode src : musicPlaying.keySet()) { for (AudioNode src : musicPlaying.keySet()) {
if (musicPlaying.get(src) == mp) { 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 * 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 SoundPool: After playing more instances of the sound you only have
* the channel of the last played instance. * the channel of the last played instance.
* *
* It is not possible to get information about the state of the soundpool of * 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 * a specific streamid, so removing is not possilbe -> noone knows when
* sound finished. * sound finished.
*/ */
public void playSourceInstance(AudioNode src) { public void playSourceInstance(AudioNode src) {
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (!(audioData.getAssetKey() instanceof AudioKey)) { if (!(audioData.getAssetKey() instanceof AudioKey)) {
throw new IllegalArgumentException("Asset is not a AudioKey"); throw new IllegalArgumentException("Asset is not a AudioKey");
} }
AudioKey assetKey = (AudioKey) audioData.getAssetKey(); AudioKey assetKey = (AudioKey) audioData.getAssetKey();
try { try {
if (audioData.getId() < 0) { // found something to load if (audioData.getId() < 0) { // found something to load
int soundId = soundPool.load( int soundId = soundPool.load(
assetManager.openFd(assetKey.getName()), 1); assetManager.openFd(assetKey.getName()), 1);
audioData.setId(soundId); audioData.setId(soundId);
} }
int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
if (channel == 0) { if (channel == 0) {
soundpoolStillLoading.put(audioData.getId(), src); soundpoolStillLoading.put(audioData.getId(), src);
} else { } else {
src.setChannel(channel); // receive a channel at the last src.setChannel(channel); // receive a channel at the last
// playing at least // playing at least
} }
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, logger.log(Level.SEVERE,
"Failed to load sound " + assetKey.getName(), e); "Failed to load sound " + assetKey.getName(), e);
audioData.setId(-1); audioData.setId(-1);
} }
} }
@Override @Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
AudioNode src = soundpoolStillLoading.remove(sampleId); AudioNode src = soundpoolStillLoading.remove(sampleId);
if (src == null) { if (src == null) {
logger.warning("Something went terribly wrong! onLoadComplete" logger.warning("Something went terribly wrong! onLoadComplete"
+ " had sampleId which was not in the HashMap of loading items"); + " had sampleId which was not in the HashMap of loading items");
return; return;
} }
AudioData audioData = src.getAudioData(); AudioData audioData = src.getAudioData();
if (status == 0) // load was successfull if (status == 0) // load was successfull
{ {
int channelIndex; int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
src.setChannel(channelIndex); src.setChannel(channelIndex);
} }
} }
public void playSource(AudioNode src) { public void playSource(AudioNode src) {
if (audioDisabled) { if (audioDisabled) {
return; return;
} }
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
MediaPlayer mp = musicPlaying.get(src); MediaPlayer mp = musicPlaying.get(src);
if (mp == null) { if (mp == null) {
mp = new MediaPlayer(); mp = new MediaPlayer();
mp.setOnCompletionListener(this); mp.setOnCompletionListener(this);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC); mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
} }
try { try {
AssetKey<?> key = audioData.getAssetKey(); AssetKey<?> key = audioData.getAssetKey();
AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName() AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
afd.getLength()); afd.getLength());
mp.prepare(); mp.prepare();
mp.setLooping(src.isLooping()); mp.setLooping(src.isLooping());
mp.start(); mp.start();
src.setChannel(0); src.setChannel(0);
src.setStatus(Status.Playing); src.setStatus(Status.Playing);
musicPlaying.put(src, mp); musicPlaying.put(src, mp);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
e.printStackTrace(); e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public void pauseSource(AudioNode src) { /**
if (audioDisabled) { * Pause the current playing sounds. Both from the {@link SoundPool} and the
return; * active {@link MediaPlayer}s
} */
public void pauseAll() {
MediaPlayer mp = musicPlaying.get(src); soundPool.autoPause();
if (mp != null) { for (MediaPlayer mp : musicPlaying.values()) {
mp.pause(); mp.pause();
src.setStatus(Status.Paused); }
} else { }
int channel = src.getChannel();
if (channel != -1) /**
soundPool.pause(channel); // is not very likley to make * Resume all paused sounds.
// something useful :) */
} public void resumeAll() {
} soundPool.autoResume();
for (MediaPlayer mp : musicPlaying.values()) {
public void stopSource(AudioNode src) { mp.start(); //no resume -> api says call start to resume
if (audioDisabled) { }
return; }
}
public void pauseSource(AudioNode src) {
// can be stream or buffer -> so try to get mediaplayer if (audioDisabled) {
// if there is non try to stop soundpool return;
MediaPlayer mp = musicPlaying.get(src); }
if (mp != null) {
mp.stop(); MediaPlayer mp = musicPlaying.get(src);
src.setStatus(Status.Paused); if (mp != null) {
} else { mp.pause();
int channel = src.getChannel(); src.setStatus(Status.Paused);
if (channel != -1) { } else {
soundPool.pause(channel); // is not very likley to make int channel = src.getChannel();
// something useful :) if (channel != -1)
} soundPool.pause(channel); // is not very likley to make
} // something useful :)
}
} }
@Override public void stopSource(AudioNode src) {
public void deleteAudioData(AudioData ad) { if (audioDisabled) {
return;
for (AudioNode src : musicPlaying.keySet()) { }
if (src.getAudioData() == ad) {
MediaPlayer mp = musicPlaying.remove(src); // can be stream or buffer -> so try to get mediaplayer
mp.stop(); // if there is non try to stop soundpool
mp.release(); MediaPlayer mp = musicPlaying.get(src);
src.setStatus(Status.Stopped); if (mp != null) {
src.setChannel(-1); mp.stop();
ad.setId(-1); src.setStatus(Status.Paused);
break; } else {
} int channel = src.getChannel();
} if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
if (ad.getId() > 0) { // something useful :)
soundPool.unload(ad.getId()); }
ad.setId(-1); }
}
} }
@Override @Override
public void setEnvironment(Environment env) { public void deleteAudioData(AudioData ad) {
//not yet supported
} for (AudioNode src : musicPlaying.keySet()) {
if (src.getAudioData() == ad) {
@Override MediaPlayer mp = musicPlaying.remove(src);
public void deleteFilter(Filter filter) { mp.stop();
} mp.release();
} src.setStatus(Status.Stopped);
src.setChannel(-1);
ad.setId(-1);
break;
}
}
if (ad.getId() > 0) {
soundPool.unload(ad.getId());
ad.setId(-1);
}
}
@Override
public void setEnvironment(Environment env) {
// not yet supported
}
@Override
public void deleteFilter(Filter filter) {
}
}

@ -0,0 +1,40 @@
package jme3test.android;
import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.InputListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Vector3f;
public class SimpleSoundTest extends SimpleApplication implements InputListener {
private AudioNode gun;
private AudioNode nature;
@Override
public void simpleInitApp() {
gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav");
gun.setPositional(true);
gun.setLocalTranslation(new Vector3f(0, 0, 0));
gun.setMaxDistance(100);
gun.setRefDistance(5);
nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);
nature.setVolume(3);
nature.setLooping(true);
nature.play();
inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(this, "click");
rootNode.attachChild(gun);
rootNode.attachChild(nature);
}
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("click") && isPressed) {
gun.playInstance();
}
}
}

@ -13,7 +13,11 @@ import java.util.Random;
* *
* @version 1.0 * @version 1.0
* @author CW * @author CW
*
* @deprecated Most devices with OpenGL ES 2.0 have an FPU. Please use
* floats instead of this class for decimal math.
*/ */
@Deprecated
public final class Fixed { public final class Fixed {
/** /**

Loading…
Cancel
Save