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