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