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