While audible sound effects and music won't necessarily make any video game more fun, they definitely do make it more
interesting and more
immersive. Before I added any sounds to my new Android game, I didn't really notice they were missing because I was so focused on gameplay elements. But after adding them, whenever I muted my device (or disabled the audio in code for performance troubleshooting) it was obvious...
something was missing.
If you want to learn how to code audio for your Android app or game yourself, the Android developer site's
Managing Audio Playback and
Media Playback pages are a good place to start. There are also some good sites/blogs out there that mention how to play a single sound effect or play a single music file, but I had trouble finding a complete solution that handled sound effects AND music, so I created an audio class that allows you to easily play music and sound effects from any activity. I hope this will help others wanting to get started with audio in their Android apps/games.
My GameAudio.java file and its code are licensed under the
Creative Commons Attribution License, meaning you may freely use it and adapt it to your needs as long as you credit me as the original author in a header comment. If you wouldn't mind posting a comment here telling me how you're using it, that would also be really cool and be a good incentive for me to share additional code in the future :)
Download GameAudio.java as a Zip File
View Source of GameAudio.java in a Pop-Up Window
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
/*
* This audio class was originally developed by Jeromie L. Walters
* and distributed for public use on 20 October 2013. This file and all
* contents are licensed under the Creative Commons Attribution "CC BY"
* license described here: http://creativecommons.org/licenses/by/3.0/
*/
public class GameAudio {
private static final int SOUND_STREAM_COUNT = 16; // Max number of streams played at once
private static final int SOUND_QUALITY = 0; // Not used currently but set to 0 (per docs) for future compatibility
private static final int SOUND_PRIORITY = 1; // Not use currently, but set to 1 (per docs) for future compatibility
private static boolean mIsInitialized;
private static Context mContext;
private static AudioManager mAudioManager;
private static MediaPlayer mMediaPlayer;
private static SoundPool mSoundPool;
private static List<Integer> mSoundStreams;
private static HashMap<Integer, Integer> mSoundMap;
// Initialize should be called once in each activity's onCreate method if audio is to be played in the Activity
public static void initialize(Activity activity, Integer soundArrayResourceId) {
mContext = activity;
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
// On API Level 8, it would be proper to request audio focus from mAudioManager,
// as well as handling loss of audio focus and abandoning audio focus when done.
if (soundArrayResourceId != null)
{
mSoundPool = new SoundPool(SOUND_STREAM_COUNT, AudioManager.STREAM_MUSIC, SOUND_QUALITY);
mSoundStreams = new ArrayList<Integer>();
mSoundMap = new HashMap<Integer, Integer>();
TypedArray soundResources = mContext.getResources().obtainTypedArray(soundArrayResourceId);
int soundCount = soundResources.length();
for (int i = 0; i < soundCount; i++)
{
int soundResourceId = soundResources.getResourceId(i, -1);
if (soundResourceId != -1) {
mSoundMap.put(soundResourceId, mSoundPool.load(mContext, soundResourceId, SOUND_PRIORITY));
}
}
soundResources.recycle();
}
mIsInitialized = true;
}
public static void playMusic(int musicResourceId, boolean loop, float volumeMultiplier) {
if (!mIsInitialized) return;
// Only play one music file at a time
stopMusic();
float actualVolume = (float)mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = (float)mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = (actualVolume / maxVolume) * volumeMultiplier;
mMediaPlayer = MediaPlayer.create(mContext, musicResourceId);
mMediaPlayer.setLooping(loop);
mMediaPlayer.setVolume(volume, volume);
mMediaPlayer.start();
}
public static void stopMusic() {
if (mMediaPlayer != null) {
if (mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
}
mMediaPlayer.release();
mMediaPlayer = null;
}
}
public static void playSound(int soundResourceId, boolean loop) {
if (!mIsInitialized) return;
float rate = 1.0f;
int priority = 0;
int loopCount = (loop ? -1 : 0);
float actualVolume = (float)mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
float maxVolume = (float)mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = actualVolume / maxVolume;
if (mSoundPool != null && mSoundMap != null && mSoundMap.containsKey(soundResourceId)) {
int streamId = mSoundPool.play(mSoundMap.get(soundResourceId), volume, volume, priority, loopCount, rate);
mSoundStreams.add(streamId);
}
}
public static void playSound(int soundResourceId) {
playSound(soundResourceId, false);
}
public static void stopSounds() {
if (mSoundStreams != null) {
int streamCount = mSoundStreams.size();
for (int i = 0; i < streamCount; i++) {
int streamId = mSoundStreams.get(i);
mSoundPool.stop(streamId);
}
mSoundStreams.clear();
}
}
public static void stopAll() {
stopMusic();
stopSounds();
}
public static void releaseAll() {
stopMusic();
if (mAudioManager != null) {
mAudioManager = null;
}
if (mSoundPool != null) {
mSoundPool.release();
mSoundPool = null;
}
if (mSoundStreams != null) {
mSoundStreams.clear();
mSoundStreams = null;
}
// CRITICAL to release context to avoid Activity memory leak!
mContext = null;
mSoundMap = null;
mIsInitialized = false;
}
}
Below is an Activity code snippet showing how to use the GameAudio class. Note the placement of the
initialize
and
releaseAll
calls in onResume and onPause, respectively. The
playMusic
and
playSound
methods can be called anytime after
intialize
and before
releaseAll
.
...
@Override
protected void onResume() {
super.onResume();
GameAudio.initialize(this, R.array.MenuSounds);
GameAudio.playMusic(R.raw.music_title_screen, true, 1.0f);
}
@Override
protected void onPause() {
super.onPause();
GameAudio.releaseAll();
}
public void onClick(View v) {
GameAudio.playSound(R.raw.sound_button_press);
...
One final note, you may have noticed the initialize method takes an array resource input parameter --
R.array.MenuSounds
in the snippet above. This is done to allow the GameAudio class to create a mapping between sound effect resource identifiers like
R.raw.sound_button_press
and the handle for the loaded sound created by the SoundPool.load function. If you only want to play music, you can pass null for that array resource identifier. Otherwise, create an entry like the one below in your res/values/arrays.xml file.
- @raw/sound_button_press
...
...
So that's it, the complete audio solution I'm using in my upcoming Android game. I hope this helps someone else just like the community's awesome docs, blogs, and websites have helped me.
Thanks for explaining the functionality clearly.
ReplyDelete