From b89b2428af275b398ce47276952615ab20efb040 Mon Sep 17 00:00:00 2001 From: "Sha..rd" Date: Wed, 15 Feb 2012 04:30:35 +0000 Subject: [PATCH] * 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 --- .../android/com/jme3/app/AndroidHarness.java | 77 +- .../com/jme3/asset/AndroidAssetManager.java | 230 ++--- .../audio/android/AndroidAudioRenderer.java | 977 +++++++++--------- .../jme3test/android/SimpleSoundTest.java | 40 + .../src/android/jme3tools/android/Fixed.java | 4 + 5 files changed, 699 insertions(+), 629 deletions(-) create mode 100644 engine/src/android/jme3test/android/SimpleSoundTest.java diff --git a/engine/src/android/com/jme3/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java index de2c45cd3..6d09b8c5d 100644 --- a/engine/src/android/com/jme3/app/AndroidHarness.java +++ b/engine/src/android/com/jme3/app/AndroidHarness.java @@ -8,15 +8,13 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.opengl.GLSurfaceView; import android.os.Bundle; -import android.view.Display; -import android.view.Gravity; -import android.view.View; import android.view.ViewGroup.LayoutParams; -import android.view.Window; -import android.view.WindowManager; +import android.view.*; import android.widget.FrameLayout; import android.widget.ImageView; 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.controls.TouchListener; 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.OGLESContext; import com.jme3.util.JmeFormatter; -import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Handler; @@ -43,76 +40,63 @@ import java.util.logging.Logger; public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener { protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName()); - /** * The application class to start */ protected String appClass = "jme3test.android.Test"; - /** * The jme3 application object */ protected Application app = null; - /** * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is * RGBA8888 or better if supported by the hardware */ protected ConfigType eglConfigType = ConfigType.FASTEST; - /** * If true all valid and not valid egl configs are logged */ protected boolean eglConfigVerboseLogging = false; - /** * If true MouseEvents are generated from TouchEvents */ protected boolean mouseEventsEnabled = true; - /** * Flip X axis */ protected boolean mouseEventsInvertX = true; - /** * Flip Y axis */ protected boolean mouseEventsInvertY = true; - /** * Title of the exit dialog, default is "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 * 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. - * If screenFullSize is true, then the notification bar and title bar are - * removed and the screen covers the entire display.   - * If screenFullSize is false, then the notification bar remains visible if - * screenShowTitle is true while screenFullScreen is false, - * then the title bar is also displayed under the notification bar. + * Set the screen window mode. If screenFullSize is true, then the + * notification bar and title bar are removed and the screen covers the + * entire display.   If screenFullSize is false, then the notification bar + * remains visible if screenShowTitle is true while screenFullScreen is + * false, then the title bar is also displayed under the notification bar. */ protected boolean screenFullScreen = true; - /** * if screenShowTitle is true while screenFullScreen is false, then the * title bar is also displayed under the notification bar */ protected boolean screenShowTitle = true; - /** * 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 = 0, then no splash screen will be displayed. */ protected int splashPicID = 0; - /** * Set the screen orientation, default is SENSOR * ActivityInfo.SCREEN_ORIENTATION_* constants package @@ -216,6 +200,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt if (app != null) { app.restart(); } + logger.info("onRestart"); } @@ -231,6 +216,14 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt if (view != null) { view.onResume(); } + + //resume the audio + AudioRenderer result = app.getAudioRenderer(); + if (result instanceof AndroidAudioRenderer) { + AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; + renderer.resumeAll(); + } + isGLThreadPaused = false; logger.info("onResume"); } @@ -241,6 +234,15 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt if (view != null) { 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; logger.info("onPause"); } @@ -248,6 +250,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt @Override protected void onStop() { super.onStop(); + logger.info("onStop"); } @@ -256,6 +259,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt if (app != null) { app.stop(!isGLThreadPaused); } + logger.info("onDestroy"); super.onDestroy(); } @@ -265,35 +269,35 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt } /** - * Called when an error has occurred. - * By default, will show an error message to the user - * and print the exception/error to the log. + * Called when an error has occurred. By default, will show an error message + * to the user and print the exception/error to the log. */ public void handleError(final String errorMsg, final Throwable t) { String stackTrace = ""; String title = "Error"; - - if (t != null){ + + if (t != null) { // Convert exception to string StringWriter sw = new StringWriter(100); t.printStackTrace(new PrintWriter(sw)); stackTrace = sw.toString(); title = t.toString(); } - - final String finalTitle = title; - final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") - + "\n" + stackTrace; + + final String finalTitle = title; + final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") + + "\n" + stackTrace; logger.log(Level.SEVERE, finalMsg); runOnUiThread(new Runnable() { + @Override public void run() { AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) - .setTitle(finalTitle) - .setPositiveButton("Kill", AndroidHarness.this) - .setMessage(finalMsg).create(); + .setTitle(finalTitle) + .setPositiveButton("Kill", AndroidHarness.this) + .setMessage(finalMsg).create(); dialog.show(); } }); @@ -324,6 +328,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt switch (evt.getType()) { case KEY_UP: runOnUiThread(new Runnable() { + @Override public void run() { AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon) diff --git a/engine/src/android/com/jme3/asset/AndroidAssetManager.java b/engine/src/android/com/jme3/asset/AndroidAssetManager.java index 59e9ca5df..c6f0b1704 100644 --- a/engine/src/android/com/jme3/asset/AndroidAssetManager.java +++ b/engine/src/android/com/jme3/asset/AndroidAssetManager.java @@ -1,115 +1,115 @@ -/* - * Copyright (c) 2009-2010 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.asset; - -import com.jme3.asset.plugins.AndroidLocator; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.audio.plugins.AndroidAudioLoader; -import com.jme3.texture.Texture; -import com.jme3.texture.plugins.AndroidImageLoader; -import java.net.URL; -import java.util.logging.Logger; - -/** - * AndroidAssetManager is an implementation of DesktopAssetManager for Android - * - * @author larynx - */ -public class AndroidAssetManager extends DesktopAssetManager { - - private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName()); - - public AndroidAssetManager() { - this(null); - } - - @Deprecated - public AndroidAssetManager(boolean loadDefaults) { - //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg")); - this(null); - } - - /** - * AndroidAssetManager constructor - * If URL == null then a default list of locators and loaders for android is set - * @param configFile - */ - public AndroidAssetManager(URL configFile) { - System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); - - // Set Default Android config - this.registerLocator("", AndroidLocator.class); - this.registerLocator("", ClasspathLocator.class); - this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg"); - this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3"); - this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m"); - this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md"); - this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); - this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds"); - this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm"); - this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr"); - this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga"); - this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); - this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj"); - 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.SkeletonLoader.class, "skeletonxml", "skeleton.xml"); - this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material"); - this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene"); - this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); - - logger.info("AndroidAssetManager created."); - } - - /** - * Loads a texture. - * - * @return - */ - @Override - public Texture loadTexture(TextureKey key) { - Texture tex = (Texture) loadAsset(key); - - // XXX: This will improve performance on some really - // low end GPUs (e.g. ones with OpenGL ES 1 support only) - // but otherwise won't help on the higher ones. - // Strongly consider removing this. - tex.setMagFilter(Texture.MagFilter.Nearest); - tex.setAnisotropicFilter(0); - if (tex.getMinFilter().usesMipMapLevels()) { - tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap); - } else { - tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps); - } - return tex; - } -} +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.asset; + +import com.jme3.asset.plugins.AndroidLocator; +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.audio.plugins.AndroidAudioLoader; +import com.jme3.texture.Texture; +import com.jme3.texture.plugins.AndroidImageLoader; +import java.net.URL; +import java.util.logging.Logger; + +/** + * AndroidAssetManager is an implementation of DesktopAssetManager for Android + * + * @author larynx + */ +public class AndroidAssetManager extends DesktopAssetManager { + + private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName()); + + public AndroidAssetManager() { + this(null); + } + + @Deprecated + public AndroidAssetManager(boolean loadDefaults) { + //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg")); + this(null); + } + + /** + * AndroidAssetManager constructor + * If URL == null then a default list of locators and loaders for android is set + * @param configFile + */ + public AndroidAssetManager(URL configFile) { + System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); + + // Set Default Android config + this.registerLocator("", AndroidLocator.class); + this.registerLocator("", ClasspathLocator.class); + this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg"); + this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav"); + this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m"); + this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md"); + this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); + this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds"); + this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm"); + this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr"); + this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga"); + this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); + this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj"); + 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.SkeletonLoader.class, "skeletonxml", "skeleton.xml"); + this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material"); + this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene"); + this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); + + logger.info("AndroidAssetManager created."); + } + + /** + * Loads a texture. + * + * @return + */ + @Override + public Texture loadTexture(TextureKey key) { + Texture tex = (Texture) loadAsset(key); + + // XXX: This will improve performance on some really + // low end GPUs (e.g. ones with OpenGL ES 1 support only) + // but otherwise won't help on the higher ones. + // Strongly consider removing this. + tex.setMagFilter(Texture.MagFilter.Nearest); + tex.setAnisotropicFilter(0); + if (tex.getMinFilter().usesMipMapLevels()) { + tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap); + } else { + tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + } + return tex; + } +} diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java index f31b597a6..2b39c30c4 100644 --- a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java +++ b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java @@ -1,478 +1,499 @@ -/* - * Copyright (c) 2009-2010 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.audio.android; - -import android.app.Activity; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.SoundPool; -import android.util.Log; - -import com.jme3.asset.AssetKey; -import com.jme3.audio.AudioNode.Status; -import com.jme3.audio.*; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.HashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * This class is the android implementation for {@link AudioRenderer} - * - * @author larynx - * @author plan_rich - */ -public class AndroidAudioRenderer implements AudioRenderer, - SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { - - private static final Logger logger = Logger - .getLogger(AndroidAudioRenderer.class.getName()); - private final static int MAX_NUM_CHANNELS = 16; - private final HashMap musicPlaying = new HashMap(); - private SoundPool soundPool = null; - - private final Vector3f listenerPosition = new Vector3f(); - // For temp use - private final Vector3f distanceVector = new Vector3f(); - private final Context context; - private final AssetManager assetManager; - private HashMap soundpoolStillLoading = new HashMap(); - private Listener listener; - private boolean audioDisabled = false; - - private final AudioManager manager; - - public AndroidAudioRenderer(Activity context) { - this.context = context; - manager = (AudioManager) context - .getSystemService(Context.AUDIO_SERVICE); - context.setVolumeControlStream(AudioManager.STREAM_MUSIC); - assetManager = context.getAssets(); - } - - @Override - public void initialize() { - soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC, - 0); - soundPool.setOnLoadCompleteListener(this); - } - - @Override - public void updateSourceParam(AudioNode src, AudioParam param) { - // logger.log(Level.INFO, "updateSourceParam " + param); - - if (audioDisabled) { - return; - } - - if (src.getChannel() < 0) { - return; - } - - switch (param) { - case Position: - if (!src.isPositional()) { - return; - } - - Vector3f pos = src.getWorldTranslation(); - break; - case Velocity: - if (!src.isPositional()) { - return; - } - - Vector3f vel = src.getVelocity(); - break; - case MaxDistance: - if (!src.isPositional()) { - return; - } - break; - case RefDistance: - if (!src.isPositional()) { - return; - } - break; - case ReverbFilter: - if (!src.isPositional() || !src.isReverbEnabled()) { - return; - } - break; - case ReverbEnabled: - if (!src.isPositional()) { - return; - } - - if (src.isReverbEnabled()) { - updateSourceParam(src, AudioParam.ReverbFilter); - } - break; - case IsPositional: - break; - case Direction: - if (!src.isDirectional()) { - return; - } - - Vector3f dir = src.getDirection(); - break; - case InnerAngle: - if (!src.isDirectional()) { - return; - } - break; - case OuterAngle: - if (!src.isDirectional()) { - return; - } - break; - case IsDirectional: - if (src.isDirectional()) { - updateSourceParam(src, AudioParam.Direction); - updateSourceParam(src, AudioParam.InnerAngle); - updateSourceParam(src, AudioParam.OuterAngle); - } else { - } - break; - case DryFilter: - if (src.getDryFilter() != null) { - Filter f = src.getDryFilter(); - if (f.isUpdateNeeded()) { - // updateFilter(f); - } - } - break; - case Looping: - if (src.isLooping()) { - } - break; - case Volume: - - soundPool.setVolume(src.getChannel(), src.getVolume(), - src.getVolume()); - - break; - case Pitch: - - break; - } - - } - - @Override - public void updateListenerParam(Listener listener, ListenerParam param) { - // logger.log(Level.INFO, "updateListenerParam " + param); - if (audioDisabled) { - return; - } - - switch (param) { - case Position: - listenerPosition.set(listener.getLocation()); - - break; - case Rotation: - Vector3f dir = listener.getDirection(); - Vector3f up = listener.getUp(); - - break; - case Velocity: - Vector3f vel = listener.getVelocity(); - - break; - case Volume: - // alListenerf(AL_GAIN, listener.getVolume()); - break; - } - - } - - @Override - public void update(float tpf) { - float distance; - float volume; - - // Loop over all mediaplayers - for (AudioNode src : musicPlaying.keySet()) { - - MediaPlayer mp = musicPlaying.get(src); - { - // Calc the distance to the listener - distanceVector.set(listenerPosition); - distanceVector.subtractLocal(src.getLocalTranslation()); - distance = FastMath.abs(distanceVector.length()); - - if (distance < src.getRefDistance()) { - distance = src.getRefDistance(); - } - if (distance > src.getMaxDistance()) { - distance = src.getMaxDistance(); - } - volume = src.getRefDistance() / distance; - - AndroidAudioData audioData = (AndroidAudioData) src - .getAudioData(); - - if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) { - // Left / Right channel get the same volume by now, only - // positional - mp.setVolume(volume, volume); - - audioData.setCurrentVolume(volume); - } - } - } - } - - public void setListener(Listener listener) { - if (audioDisabled) { - return; - } - - if (this.listener != null) { - // previous listener no longer associated with current - // renderer - this.listener.setRenderer(null); - } - - this.listener = listener; - this.listener.setRenderer(this); - - } - - @Override - public void cleanup() { - // Cleanup sound pool - if (soundPool != null) { - soundPool.release(); - soundPool = null; - } - - // Cleanup media player - for (AudioNode src : musicPlaying.keySet()) { - MediaPlayer mp = musicPlaying.get(src); - { - mp.stop(); - mp.release(); - src.setStatus(Status.Stopped); - } - } - musicPlaying.clear(); - } - - @Override - public void onCompletion(MediaPlayer mp) { - mp.seekTo(0); - 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); - 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) { - if (audioDisabled) { - return; - } - - AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); - - if (!(audioData.getAssetKey() instanceof AudioKey)) { - throw new IllegalArgumentException("Asset is not a AudioKey"); - } - - AudioKey assetKey = (AudioKey) audioData.getAssetKey(); - - try { - if (audioData.getId() < 0) { // found something to load - int soundId = soundPool.load( - assetManager.openFd(assetKey.getName()), 1); - audioData.setId(soundId); - } - - int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); - - if (channel == 0) { - soundpoolStillLoading.put(audioData.getId(), src); - } else { - src.setChannel(channel); // receive a channel at the last - // playing at least - } - } catch (IOException e) { - logger.log(Level.SEVERE, - "Failed to load sound " + assetKey.getName(), e); - audioData.setId(-1); - } - } - - @Override - public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { - AudioNode src = soundpoolStillLoading.remove(sampleId); - - if (src == null) { - logger.warning("Something went terribly wrong! onLoadComplete" - + " 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) { - if (audioDisabled) { - return; - } - - AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); - - MediaPlayer mp = musicPlaying.get(src); - if (mp == null) { - mp = new MediaPlayer(); - 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) { - if (audioDisabled) { - return; - } - - MediaPlayer mp = musicPlaying.get(src); - if (mp != null) { - mp.pause(); - src.setStatus(Status.Paused); - } else { - int channel = src.getChannel(); - if (channel != -1) - soundPool.pause(channel); // is not very likley to make - // something useful :) - } - } - - public void stopSource(AudioNode src) { - if (audioDisabled) { - return; - } - - // can be stream or buffer -> so try to get mediaplayer - // if there is non try to stop soundpool - MediaPlayer mp = musicPlaying.get(src); - if (mp != null) { - mp.stop(); - src.setStatus(Status.Paused); - } else { - int channel = src.getChannel(); - if (channel != -1) { - soundPool.pause(channel); // is not very likley to make - // something useful :) - } - } - - } - - @Override - public void deleteAudioData(AudioData ad) { - - for (AudioNode src : musicPlaying.keySet()) { - if (src.getAudioData() == ad) { - MediaPlayer mp = musicPlaying.remove(src); - 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) { - } -} +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.audio.android; + +import android.app.Activity; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.SoundPool; +import android.util.Log; + +import com.jme3.asset.AssetKey; +import com.jme3.audio.AudioNode.Status; +import com.jme3.audio.*; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is the android implementation for {@link AudioRenderer} + * + * @author larynx + * @author plan_rich + */ +public class AndroidAudioRenderer implements AudioRenderer, + SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener { + + private static final Logger logger = Logger + .getLogger(AndroidAudioRenderer.class.getName()); + private final static int MAX_NUM_CHANNELS = 16; + private final HashMap musicPlaying = new HashMap(); + private SoundPool soundPool = null; + + private final Vector3f listenerPosition = new Vector3f(); + // For temp use + private final Vector3f distanceVector = new Vector3f(); + private final Context context; + private final AssetManager assetManager; + private HashMap soundpoolStillLoading = new HashMap(); + private Listener listener; + private boolean audioDisabled = false; + + private final AudioManager manager; + + public AndroidAudioRenderer(Activity context) { + this.context = context; + manager = (AudioManager) context + .getSystemService(Context.AUDIO_SERVICE); + context.setVolumeControlStream(AudioManager.STREAM_MUSIC); + assetManager = context.getAssets(); + } + + @Override + public void initialize() { + soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC, + 0); + soundPool.setOnLoadCompleteListener(this); + } + + @Override + public void updateSourceParam(AudioNode src, AudioParam param) { + // logger.log(Level.INFO, "updateSourceParam " + param); + + if (audioDisabled) { + return; + } + + if (src.getChannel() < 0) { + return; + } + + switch (param) { + case Position: + if (!src.isPositional()) { + return; + } + + Vector3f pos = src.getWorldTranslation(); + break; + case Velocity: + if (!src.isPositional()) { + return; + } + + Vector3f vel = src.getVelocity(); + break; + case MaxDistance: + if (!src.isPositional()) { + return; + } + break; + case RefDistance: + if (!src.isPositional()) { + return; + } + break; + case ReverbFilter: + if (!src.isPositional() || !src.isReverbEnabled()) { + return; + } + break; + case ReverbEnabled: + if (!src.isPositional()) { + return; + } + + if (src.isReverbEnabled()) { + updateSourceParam(src, AudioParam.ReverbFilter); + } + break; + case IsPositional: + break; + case Direction: + if (!src.isDirectional()) { + return; + } + + Vector3f dir = src.getDirection(); + break; + case InnerAngle: + if (!src.isDirectional()) { + return; + } + break; + case OuterAngle: + if (!src.isDirectional()) { + return; + } + break; + case IsDirectional: + if (src.isDirectional()) { + updateSourceParam(src, AudioParam.Direction); + updateSourceParam(src, AudioParam.InnerAngle); + updateSourceParam(src, AudioParam.OuterAngle); + } else { + } + break; + case DryFilter: + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + // updateFilter(f); + } + } + break; + case Looping: + if (src.isLooping()) { + } + break; + case Volume: + + soundPool.setVolume(src.getChannel(), src.getVolume(), + src.getVolume()); + + break; + case Pitch: + + break; + } + + } + + @Override + public void updateListenerParam(Listener listener, ListenerParam param) { + // logger.log(Level.INFO, "updateListenerParam " + param); + if (audioDisabled) { + return; + } + + switch (param) { + case Position: + listenerPosition.set(listener.getLocation()); + + break; + case Rotation: + Vector3f dir = listener.getDirection(); + Vector3f up = listener.getUp(); + + break; + case Velocity: + Vector3f vel = listener.getVelocity(); + + break; + case Volume: + // alListenerf(AL_GAIN, listener.getVolume()); + break; + } + + } + + @Override + public void update(float tpf) { + float distance; + float volume; + + // Loop over all mediaplayers + for (AudioNode src : musicPlaying.keySet()) { + + MediaPlayer mp = musicPlaying.get(src); + { + // Calc the distance to the listener + distanceVector.set(listenerPosition); + distanceVector.subtractLocal(src.getLocalTranslation()); + distance = FastMath.abs(distanceVector.length()); + + if (distance < src.getRefDistance()) { + distance = src.getRefDistance(); + } + if (distance > src.getMaxDistance()) { + distance = src.getMaxDistance(); + } + volume = src.getRefDistance() / distance; + + AndroidAudioData audioData = (AndroidAudioData) src + .getAudioData(); + + if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) { + // Left / Right channel get the same volume by now, only + // positional + mp.setVolume(volume, volume); + + audioData.setCurrentVolume(volume); + } + } + } + } + + public void setListener(Listener listener) { + if (audioDisabled) { + return; + } + + if (this.listener != null) { + // previous listener no longer associated with current + // renderer + this.listener.setRenderer(null); + } + + this.listener = listener; + this.listener.setRenderer(this); + + } + + @Override + public void cleanup() { + // Cleanup sound pool + if (soundPool != null) { + soundPool.release(); + soundPool = null; + } + + // Cleanup media player + for (AudioNode src : musicPlaying.keySet()) { + MediaPlayer mp = musicPlaying.get(src); + { + mp.stop(); + mp.release(); + src.setStatus(Status.Stopped); + } + } + musicPlaying.clear(); + } + + @Override + public void onCompletion(MediaPlayer mp) { + mp.seekTo(0); + 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); + 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) { + if (audioDisabled) { + return; + } + + AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); + + if (!(audioData.getAssetKey() instanceof AudioKey)) { + throw new IllegalArgumentException("Asset is not a AudioKey"); + } + + AudioKey assetKey = (AudioKey) audioData.getAssetKey(); + + try { + if (audioData.getId() < 0) { // found something to load + int soundId = soundPool.load( + assetManager.openFd(assetKey.getName()), 1); + audioData.setId(soundId); + } + + int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f); + + if (channel == 0) { + soundpoolStillLoading.put(audioData.getId(), src); + } else { + src.setChannel(channel); // receive a channel at the last + // playing at least + } + } catch (IOException e) { + logger.log(Level.SEVERE, + "Failed to load sound " + assetKey.getName(), e); + audioData.setId(-1); + } + } + + @Override + public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { + AudioNode src = soundpoolStillLoading.remove(sampleId); + + if (src == null) { + logger.warning("Something went terribly wrong! onLoadComplete" + + " 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) { + if (audioDisabled) { + return; + } + + AndroidAudioData audioData = (AndroidAudioData) src.getAudioData(); + + MediaPlayer mp = musicPlaying.get(src); + if (mp == null) { + mp = new MediaPlayer(); + 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(); + } + } + + /** + * Pause the current playing sounds. Both from the {@link SoundPool} and the + * active {@link MediaPlayer}s + */ + public void pauseAll() { + soundPool.autoPause(); + for (MediaPlayer mp : musicPlaying.values()) { + mp.pause(); + } + } + + /** + * Resume all paused sounds. + */ + public void resumeAll() { + soundPool.autoResume(); + for (MediaPlayer mp : musicPlaying.values()) { + mp.start(); //no resume -> api says call start to resume + } + } + + public void pauseSource(AudioNode src) { + if (audioDisabled) { + return; + } + + MediaPlayer mp = musicPlaying.get(src); + if (mp != null) { + mp.pause(); + src.setStatus(Status.Paused); + } else { + int channel = src.getChannel(); + if (channel != -1) + soundPool.pause(channel); // is not very likley to make + // something useful :) + } + } + + public void stopSource(AudioNode src) { + if (audioDisabled) { + return; + } + + // can be stream or buffer -> so try to get mediaplayer + // if there is non try to stop soundpool + MediaPlayer mp = musicPlaying.get(src); + if (mp != null) { + mp.stop(); + src.setStatus(Status.Paused); + } else { + int channel = src.getChannel(); + if (channel != -1) { + soundPool.pause(channel); // is not very likley to make + // something useful :) + } + } + + } + + @Override + public void deleteAudioData(AudioData ad) { + + for (AudioNode src : musicPlaying.keySet()) { + if (src.getAudioData() == ad) { + MediaPlayer mp = musicPlaying.remove(src); + 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) { + } +} diff --git a/engine/src/android/jme3test/android/SimpleSoundTest.java b/engine/src/android/jme3test/android/SimpleSoundTest.java new file mode 100644 index 000000000..9503bca0e --- /dev/null +++ b/engine/src/android/jme3test/android/SimpleSoundTest.java @@ -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(); + } + } +} diff --git a/engine/src/android/jme3tools/android/Fixed.java b/engine/src/android/jme3tools/android/Fixed.java index c13563a9b..4420999e9 100644 --- a/engine/src/android/jme3tools/android/Fixed.java +++ b/engine/src/android/jme3tools/android/Fixed.java @@ -13,7 +13,11 @@ import java.util.Random; * * @version 1.0 * @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 { /**