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