1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media; 18 19 import android.util.AndroidRuntimeException; 20 import android.util.Log; 21 import java.io.File; 22 import java.io.FileDescriptor; 23 import android.os.ParcelFileDescriptor; 24 import java.lang.ref.WeakReference; 25 import android.content.Context; 26 import android.content.res.AssetFileDescriptor; 27 import java.io.IOException; 28 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 33 /** 34 * The SoundPool class manages and plays audio resources for applications. 35 * 36 * <p>A SoundPool is a collection of samples that can be loaded into memory 37 * from a resource inside the APK or from a file in the file system. The 38 * SoundPool library uses the MediaPlayer service to decode the audio 39 * into a raw 16-bit PCM mono or stereo stream. This allows applications 40 * to ship with compressed streams without having to suffer the CPU load 41 * and latency of decompressing during playback.</p> 42 * 43 * <p>In addition to low-latency playback, SoundPool can also manage the number 44 * of audio streams being rendered at once. When the SoundPool object is 45 * constructed, the maxStreams parameter sets the maximum number of streams 46 * that can be played at a time from this single SoundPool. SoundPool tracks 47 * the number of active streams. If the maximum number of streams is exceeded, 48 * SoundPool will automatically stop a previously playing stream based first 49 * on priority and then by age within that priority. Limiting the maximum 50 * number of streams helps to cap CPU loading and reducing the likelihood that 51 * audio mixing will impact visuals or UI performance.</p> 52 * 53 * <p>Sounds can be looped by setting a non-zero loop value. A value of -1 54 * causes the sound to loop forever. In this case, the application must 55 * explicitly call the stop() function to stop the sound. Any other non-zero 56 * value will cause the sound to repeat the specified number of times, e.g. 57 * a value of 3 causes the sound to play a total of 4 times.</p> 58 * 59 * <p>The playback rate can also be changed. A playback rate of 1.0 causes 60 * the sound to play at its original frequency (resampled, if necessary, 61 * to the hardware output frequency). A playback rate of 2.0 causes the 62 * sound to play at twice its original frequency, and a playback rate of 63 * 0.5 causes it to play at half its original frequency. The playback 64 * rate range is 0.5 to 2.0.</p> 65 * 66 * <p>Priority runs low to high, i.e. higher numbers are higher priority. 67 * Priority is used when a call to play() would cause the number of active 68 * streams to exceed the value established by the maxStreams parameter when 69 * the SoundPool was created. In this case, the stream allocator will stop 70 * the lowest priority stream. If there are multiple streams with the same 71 * low priority, it will choose the oldest stream to stop. In the case 72 * where the priority of the new stream is lower than all the active 73 * streams, the new sound will not play and the play() function will return 74 * a streamID of zero.</p> 75 * 76 * <p>Let's examine a typical use case: A game consists of several levels of 77 * play. For each level, there is a set of unique sounds that are used only 78 * by that level. In this case, the game logic should create a new SoundPool 79 * object when the first level is loaded. The level data itself might contain 80 * the list of sounds to be used by this level. The loading logic iterates 81 * through the list of sounds calling the appropriate SoundPool.load() 82 * function. This should typically be done early in the process to allow time 83 * for decompressing the audio to raw PCM format before they are needed for 84 * playback.</p> 85 * 86 * <p>Once the sounds are loaded and play has started, the application can 87 * trigger sounds by calling SoundPool.play(). Playing streams can be 88 * paused or resumed, and the application can also alter the pitch by 89 * adjusting the playback rate in real-time for doppler or synthesis 90 * effects.</p> 91 * 92 * <p>Note that since streams can be stopped due to resource constraints, the 93 * streamID is a reference to a particular instance of a stream. If the stream 94 * is stopped to allow a higher priority stream to play, the stream is no 95 * longer be valid. However, the application is allowed to call methods on 96 * the streamID without error. This may help simplify program logic since 97 * the application need not concern itself with the stream lifecycle.</p> 98 * 99 * <p>In our example, when the player has completed the level, the game 100 * logic should call SoundPool.release() to release all the native resources 101 * in use and then set the SoundPool reference to null. If the player starts 102 * another level, a new SoundPool is created, sounds are loaded, and play 103 * resumes.</p> 104 */ 105 public class SoundPool 106 { 107 static { System.loadLibrary("soundpool"); } 108 109 private final static String TAG = "SoundPool"; 110 private final static boolean DEBUG = false; 111 112 private int mNativeContext; // accessed by native methods 113 114 private EventHandler mEventHandler; 115 private OnLoadCompleteListener mOnLoadCompleteListener; 116 117 private final Object mLock; 118 119 // SoundPool messages 120 // 121 // must match SoundPool.h 122 private static final int SAMPLE_LOADED = 1; 123 124 /** 125 * Constructor. Constructs a SoundPool object with the following 126 * characteristics: 127 * 128 * @param maxStreams the maximum number of simultaneous streams for this 129 * SoundPool object 130 * @param streamType the audio stream type as described in AudioManager 131 * For example, game applications will normally use 132 * {@link AudioManager#STREAM_MUSIC}. 133 * @param srcQuality the sample-rate converter quality. Currently has no 134 * effect. Use 0 for the default. 135 * @return a SoundPool object, or null if creation failed 136 */ SoundPool(int maxStreams, int streamType, int srcQuality)137 public SoundPool(int maxStreams, int streamType, int srcQuality) { 138 139 // do native setup 140 if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) { 141 throw new RuntimeException("Native setup failed"); 142 } 143 mLock = new Object(); 144 } 145 146 /** 147 * Load the sound from the specified path. 148 * 149 * @param path the path to the audio file 150 * @param priority the priority of the sound. Currently has no effect. Use 151 * a value of 1 for future compatibility. 152 * @return a sound ID. This value can be used to play or unload the sound. 153 */ load(String path, int priority)154 public int load(String path, int priority) 155 { 156 // pass network streams to player 157 if (path.startsWith("http:")) 158 return _load(path, priority); 159 160 // try local path 161 int id = 0; 162 try { 163 File f = new File(path); 164 ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); 165 if (fd != null) { 166 id = _load(fd.getFileDescriptor(), 0, f.length(), priority); 167 fd.close(); 168 } 169 } catch (java.io.IOException e) { 170 Log.e(TAG, "error loading " + path); 171 } 172 return id; 173 } 174 175 /** 176 * Load the sound from the specified APK resource. 177 * 178 * Note that the extension is dropped. For example, if you want to load 179 * a sound from the raw resource file "explosion.mp3", you would specify 180 * "R.raw.explosion" as the resource ID. Note that this means you cannot 181 * have both an "explosion.wav" and an "explosion.mp3" in the res/raw 182 * directory. 183 * 184 * @param context the application context 185 * @param resId the resource ID 186 * @param priority the priority of the sound. Currently has no effect. Use 187 * a value of 1 for future compatibility. 188 * @return a sound ID. This value can be used to play or unload the sound. 189 */ load(Context context, int resId, int priority)190 public int load(Context context, int resId, int priority) { 191 AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); 192 int id = 0; 193 if (afd != null) { 194 id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority); 195 try { 196 afd.close(); 197 } catch (java.io.IOException ex) { 198 //Log.d(TAG, "close failed:", ex); 199 } 200 } 201 return id; 202 } 203 204 /** 205 * Load the sound from an asset file descriptor. 206 * 207 * @param afd an asset file descriptor 208 * @param priority the priority of the sound. Currently has no effect. Use 209 * a value of 1 for future compatibility. 210 * @return a sound ID. This value can be used to play or unload the sound. 211 */ load(AssetFileDescriptor afd, int priority)212 public int load(AssetFileDescriptor afd, int priority) { 213 if (afd != null) { 214 long len = afd.getLength(); 215 if (len < 0) { 216 throw new AndroidRuntimeException("no length for fd"); 217 } 218 return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority); 219 } else { 220 return 0; 221 } 222 } 223 224 /** 225 * Load the sound from a FileDescriptor. 226 * 227 * This version is useful if you store multiple sounds in a single 228 * binary. The offset specifies the offset from the start of the file 229 * and the length specifies the length of the sound within the file. 230 * 231 * @param fd a FileDescriptor object 232 * @param offset offset to the start of the sound 233 * @param length length of the sound 234 * @param priority the priority of the sound. Currently has no effect. Use 235 * a value of 1 for future compatibility. 236 * @return a sound ID. This value can be used to play or unload the sound. 237 */ load(FileDescriptor fd, long offset, long length, int priority)238 public int load(FileDescriptor fd, long offset, long length, int priority) { 239 return _load(fd, offset, length, priority); 240 } 241 _load(String uri, int priority)242 private native final int _load(String uri, int priority); 243 _load(FileDescriptor fd, long offset, long length, int priority)244 private native final int _load(FileDescriptor fd, long offset, long length, int priority); 245 246 /** 247 * Unload a sound from a sound ID. 248 * 249 * Unloads the sound specified by the soundID. This is the value 250 * returned by the load() function. Returns true if the sound is 251 * successfully unloaded, false if the sound was already unloaded. 252 * 253 * @param soundID a soundID returned by the load() function 254 * @return true if just unloaded, false if previously unloaded 255 */ unload(int soundID)256 public native final boolean unload(int soundID); 257 258 /** 259 * Play a sound from a sound ID. 260 * 261 * Play the sound specified by the soundID. This is the value 262 * returned by the load() function. Returns a non-zero streamID 263 * if successful, zero if it fails. The streamID can be used to 264 * further control playback. Note that calling play() may cause 265 * another sound to stop playing if the maximum number of active 266 * streams is exceeded. A loop value of -1 means loop forever, 267 * a value of 0 means don't loop, other values indicate the 268 * number of repeats, e.g. a value of 1 plays the audio twice. 269 * The playback rate allows the application to vary the playback 270 * rate (pitch) of the sound. A value of 1.0 means play back at 271 * the original frequency. A value of 2.0 means play back twice 272 * as fast, and a value of 0.5 means playback at half speed. 273 * 274 * @param soundID a soundID returned by the load() function 275 * @param leftVolume left volume value (range = 0.0 to 1.0) 276 * @param rightVolume right volume value (range = 0.0 to 1.0) 277 * @param priority stream priority (0 = lowest priority) 278 * @param loop loop mode (0 = no loop, -1 = loop forever) 279 * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) 280 * @return non-zero streamID if successful, zero if failed 281 */ play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)282 public native final int play(int soundID, float leftVolume, float rightVolume, 283 int priority, int loop, float rate); 284 285 /** 286 * Pause a playback stream. 287 * 288 * Pause the stream specified by the streamID. This is the 289 * value returned by the play() function. If the stream is 290 * playing, it will be paused. If the stream is not playing 291 * (e.g. is stopped or was previously paused), calling this 292 * function will have no effect. 293 * 294 * @param streamID a streamID returned by the play() function 295 */ pause(int streamID)296 public native final void pause(int streamID); 297 298 /** 299 * Resume a playback stream. 300 * 301 * Resume the stream specified by the streamID. This 302 * is the value returned by the play() function. If the stream 303 * is paused, this will resume playback. If the stream was not 304 * previously paused, calling this function will have no effect. 305 * 306 * @param streamID a streamID returned by the play() function 307 */ resume(int streamID)308 public native final void resume(int streamID); 309 310 /** 311 * Pause all active streams. 312 * 313 * Pause all streams that are currently playing. This function 314 * iterates through all the active streams and pauses any that 315 * are playing. It also sets a flag so that any streams that 316 * are playing can be resumed by calling autoResume(). 317 */ autoPause()318 public native final void autoPause(); 319 320 /** 321 * Resume all previously active streams. 322 * 323 * Automatically resumes all streams that were paused in previous 324 * calls to autoPause(). 325 */ autoResume()326 public native final void autoResume(); 327 328 /** 329 * Stop a playback stream. 330 * 331 * Stop the stream specified by the streamID. This 332 * is the value returned by the play() function. If the stream 333 * is playing, it will be stopped. It also releases any native 334 * resources associated with this stream. If the stream is not 335 * playing, it will have no effect. 336 * 337 * @param streamID a streamID returned by the play() function 338 */ stop(int streamID)339 public native final void stop(int streamID); 340 341 /** 342 * Set stream volume. 343 * 344 * Sets the volume on the stream specified by the streamID. 345 * This is the value returned by the play() function. The 346 * value must be in the range of 0.0 to 1.0. If the stream does 347 * not exist, it will have no effect. 348 * 349 * @param streamID a streamID returned by the play() function 350 * @param leftVolume left volume value (range = 0.0 to 1.0) 351 * @param rightVolume right volume value (range = 0.0 to 1.0) 352 */ setVolume(int streamID, float leftVolume, float rightVolume)353 public native final void setVolume(int streamID, 354 float leftVolume, float rightVolume); 355 356 /** 357 * Change stream priority. 358 * 359 * Change the priority of the stream specified by the streamID. 360 * This is the value returned by the play() function. Affects the 361 * order in which streams are re-used to play new sounds. If the 362 * stream does not exist, it will have no effect. 363 * 364 * @param streamID a streamID returned by the play() function 365 */ setPriority(int streamID, int priority)366 public native final void setPriority(int streamID, int priority); 367 368 /** 369 * Set loop mode. 370 * 371 * Change the loop mode. A loop value of -1 means loop forever, 372 * a value of 0 means don't loop, other values indicate the 373 * number of repeats, e.g. a value of 1 plays the audio twice. 374 * If the stream does not exist, it will have no effect. 375 * 376 * @param streamID a streamID returned by the play() function 377 * @param loop loop mode (0 = no loop, -1 = loop forever) 378 */ setLoop(int streamID, int loop)379 public native final void setLoop(int streamID, int loop); 380 381 /** 382 * Change playback rate. 383 * 384 * The playback rate allows the application to vary the playback 385 * rate (pitch) of the sound. A value of 1.0 means playback at 386 * the original frequency. A value of 2.0 means playback twice 387 * as fast, and a value of 0.5 means playback at half speed. 388 * If the stream does not exist, it will have no effect. 389 * 390 * @param streamID a streamID returned by the play() function 391 * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0) 392 */ setRate(int streamID, float rate)393 public native final void setRate(int streamID, float rate); 394 395 /** 396 * Interface definition for a callback to be invoked when all the 397 * sounds are loaded. 398 */ 399 public interface OnLoadCompleteListener 400 { 401 /** 402 * Called when a sound has completed loading. 403 * 404 * @param soundPool SoundPool object from the load() method 405 * @param soundPool the sample ID of the sound loaded. 406 * @param status the status of the load operation (0 = success) 407 */ onLoadComplete(SoundPool soundPool, int sampleId, int status)408 public void onLoadComplete(SoundPool soundPool, int sampleId, int status); 409 } 410 411 /** 412 * Sets the callback hook for the OnLoadCompleteListener. 413 */ setOnLoadCompleteListener(OnLoadCompleteListener listener)414 public void setOnLoadCompleteListener(OnLoadCompleteListener listener) 415 { 416 synchronized(mLock) { 417 if (listener != null) { 418 // setup message handler 419 Looper looper; 420 if ((looper = Looper.myLooper()) != null) { 421 mEventHandler = new EventHandler(this, looper); 422 } else if ((looper = Looper.getMainLooper()) != null) { 423 mEventHandler = new EventHandler(this, looper); 424 } else { 425 mEventHandler = null; 426 } 427 } else { 428 mEventHandler = null; 429 } 430 mOnLoadCompleteListener = listener; 431 } 432 } 433 434 private class EventHandler extends Handler 435 { 436 private SoundPool mSoundPool; 437 EventHandler(SoundPool soundPool, Looper looper)438 public EventHandler(SoundPool soundPool, Looper looper) { 439 super(looper); 440 mSoundPool = soundPool; 441 } 442 443 @Override handleMessage(Message msg)444 public void handleMessage(Message msg) { 445 switch(msg.what) { 446 case SAMPLE_LOADED: 447 if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded"); 448 synchronized(mLock) { 449 if (mOnLoadCompleteListener != null) { 450 mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2); 451 } 452 } 453 break; 454 default: 455 Log.e(TAG, "Unknown message type " + msg.what); 456 return; 457 } 458 } 459 } 460 461 // post event from native code to message handler postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)462 private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj) 463 { 464 SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get(); 465 if (soundPool == null) 466 return; 467 468 if (soundPool.mEventHandler != null) { 469 Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj); 470 soundPool.mEventHandler.sendMessage(m); 471 } 472 } 473 474 /** 475 * Release the SoundPool resources. 476 * 477 * Release all memory and native resources used by the SoundPool 478 * object. The SoundPool can no longer be used and the reference 479 * should be set to null. 480 */ release()481 public native final void release(); 482 native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality)483 private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality); 484 finalize()485 protected void finalize() { release(); } 486 } 487