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.IAppOpsCallback;
39import com.android.internal.app.IAppOpsService;
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 be 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 {
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    private final IAppOpsService mAppOps;
134    private final IAppOpsCallback mAppOpsCallback;
135
136    /**
137     * Constructor. Constructs a SoundPool object with the following
138     * characteristics:
139     *
140     * @param maxStreams the maximum number of simultaneous streams for this
141     *                   SoundPool object
142     * @param streamType the audio stream type as described in AudioManager
143     *                   For example, game applications will normally use
144     *                   {@link AudioManager#STREAM_MUSIC}.
145     * @param srcQuality the sample-rate converter quality. Currently has no
146     *                   effect. Use 0 for the default.
147     * @return a SoundPool object, or null if creation failed
148     * @deprecated use {@link SoundPool.Builder} instead to create and configure a
149     *     SoundPool instance
150     */
151    public SoundPool(int maxStreams, int streamType, int srcQuality) {
152        this(maxStreams,
153                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
154    }
155
156    private SoundPool(int maxStreams, AudioAttributes attributes) {
157        // do native setup
158        if (native_setup(new WeakReference<SoundPool>(this), maxStreams, attributes) != 0) {
159            throw new RuntimeException("Native setup failed");
160        }
161        mLock = new Object();
162        mAttributes = attributes;
163        IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
164        mAppOps = IAppOpsService.Stub.asInterface(b);
165        // initialize mHasAppOpsPlayAudio
166        updateAppOpsPlayAudio();
167        // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
168        mAppOpsCallback = new IAppOpsCallback.Stub() {
169            public void opChanged(int op, String packageName) {
170                synchronized (mLock) {
171                    if (op == AppOpsManager.OP_PLAY_AUDIO) {
172                        updateAppOpsPlayAudio();
173                    }
174                }
175            }
176        };
177        try {
178            mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
179                    ActivityThread.currentPackageName(), mAppOpsCallback);
180        } catch (RemoteException e) {
181            mHasAppOpsPlayAudio = false;
182        }
183    }
184
185    /**
186     * Release the SoundPool resources.
187     *
188     * Release all memory and native resources used by the SoundPool
189     * object. The SoundPool can no longer be used and the reference
190     * should be set to null.
191     */
192    public final void release() {
193        try {
194            mAppOps.stopWatchingMode(mAppOpsCallback);
195        } catch (RemoteException e) {
196            // nothing to do here, the SoundPool is being released anyway
197        }
198        native_release();
199    }
200
201    private native final void native_release();
202
203    protected void finalize() { release(); }
204
205    /**
206     * Load the sound from the specified path.
207     *
208     * @param path the path to the audio file
209     * @param priority the priority of the sound. Currently has no effect. Use
210     *                 a value of 1 for future compatibility.
211     * @return a sound ID. This value can be used to play or unload the sound.
212     */
213    public int load(String path, int priority) {
214        int id = 0;
215        try {
216            File f = new File(path);
217            ParcelFileDescriptor fd = ParcelFileDescriptor.open(f,
218                    ParcelFileDescriptor.MODE_READ_ONLY);
219            if (fd != null) {
220                id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
221                fd.close();
222            }
223        } catch (java.io.IOException e) {
224            Log.e(TAG, "error loading " + path);
225        }
226        return id;
227    }
228
229    /**
230     * Load the sound from the specified APK resource.
231     *
232     * Note that the extension is dropped. For example, if you want to load
233     * a sound from the raw resource file "explosion.mp3", you would specify
234     * "R.raw.explosion" as the resource ID. Note that this means you cannot
235     * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
236     * directory.
237     *
238     * @param context the application context
239     * @param resId the resource ID
240     * @param priority the priority of the sound. Currently has no effect. Use
241     *                 a value of 1 for future compatibility.
242     * @return a sound ID. This value can be used to play or unload the sound.
243     */
244    public int load(Context context, int resId, int priority) {
245        AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
246        int id = 0;
247        if (afd != null) {
248            id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
249            try {
250                afd.close();
251            } catch (java.io.IOException ex) {
252                //Log.d(TAG, "close failed:", ex);
253            }
254        }
255        return id;
256    }
257
258    /**
259     * Load the sound from an asset file descriptor.
260     *
261     * @param afd an asset file descriptor
262     * @param priority the priority of the sound. Currently has no effect. Use
263     *                 a value of 1 for future compatibility.
264     * @return a sound ID. This value can be used to play or unload the sound.
265     */
266    public int load(AssetFileDescriptor afd, int priority) {
267        if (afd != null) {
268            long len = afd.getLength();
269            if (len < 0) {
270                throw new AndroidRuntimeException("no length for fd");
271            }
272            return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
273        } else {
274            return 0;
275        }
276    }
277
278    /**
279     * Load the sound from a FileDescriptor.
280     *
281     * This version is useful if you store multiple sounds in a single
282     * binary. The offset specifies the offset from the start of the file
283     * and the length specifies the length of the sound within the file.
284     *
285     * @param fd a FileDescriptor object
286     * @param offset offset to the start of the sound
287     * @param length length of the sound
288     * @param priority the priority of the sound. Currently has no effect. Use
289     *                 a value of 1 for future compatibility.
290     * @return a sound ID. This value can be used to play or unload the sound.
291     */
292    public int load(FileDescriptor fd, long offset, long length, int priority) {
293        return _load(fd, offset, length, priority);
294    }
295
296    /**
297     * Unload a sound from a sound ID.
298     *
299     * Unloads the sound specified by the soundID. This is the value
300     * returned by the load() function. Returns true if the sound is
301     * successfully unloaded, false if the sound was already unloaded.
302     *
303     * @param soundID a soundID returned by the load() function
304     * @return true if just unloaded, false if previously unloaded
305     */
306    public native final boolean unload(int soundID);
307
308    /**
309     * Play a sound from a sound ID.
310     *
311     * Play the sound specified by the soundID. This is the value
312     * returned by the load() function. Returns a non-zero streamID
313     * if successful, zero if it fails. The streamID can be used to
314     * further control playback. Note that calling play() may cause
315     * another sound to stop playing if the maximum number of active
316     * streams is exceeded. A loop value of -1 means loop forever,
317     * a value of 0 means don't loop, other values indicate the
318     * number of repeats, e.g. a value of 1 plays the audio twice.
319     * The playback rate allows the application to vary the playback
320     * rate (pitch) of the sound. A value of 1.0 means play back at
321     * the original frequency. A value of 2.0 means play back twice
322     * as fast, and a value of 0.5 means playback at half speed.
323     *
324     * @param soundID a soundID returned by the load() function
325     * @param leftVolume left volume value (range = 0.0 to 1.0)
326     * @param rightVolume right volume value (range = 0.0 to 1.0)
327     * @param priority stream priority (0 = lowest priority)
328     * @param loop loop mode (0 = no loop, -1 = loop forever)
329     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
330     * @return non-zero streamID if successful, zero if failed
331     */
332    public final int play(int soundID, float leftVolume, float rightVolume,
333            int priority, int loop, float rate) {
334        if (isRestricted()) {
335            leftVolume = rightVolume = 0;
336        }
337        return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
338    }
339
340    /**
341     * Pause a playback stream.
342     *
343     * Pause the stream specified by the streamID. This is the
344     * value returned by the play() function. If the stream is
345     * playing, it will be paused. If the stream is not playing
346     * (e.g. is stopped or was previously paused), calling this
347     * function will have no effect.
348     *
349     * @param streamID a streamID returned by the play() function
350     */
351    public native final void pause(int streamID);
352
353    /**
354     * Resume a playback stream.
355     *
356     * Resume the stream specified by the streamID. This
357     * is the value returned by the play() function. If the stream
358     * is paused, this will resume playback. If the stream was not
359     * previously paused, calling this function will have no effect.
360     *
361     * @param streamID a streamID returned by the play() function
362     */
363    public native final void resume(int streamID);
364
365    /**
366     * Pause all active streams.
367     *
368     * Pause all streams that are currently playing. This function
369     * iterates through all the active streams and pauses any that
370     * are playing. It also sets a flag so that any streams that
371     * are playing can be resumed by calling autoResume().
372     */
373    public native final void autoPause();
374
375    /**
376     * Resume all previously active streams.
377     *
378     * Automatically resumes all streams that were paused in previous
379     * calls to autoPause().
380     */
381    public native final void autoResume();
382
383    /**
384     * Stop a playback stream.
385     *
386     * Stop the stream specified by the streamID. This
387     * is the value returned by the play() function. If the stream
388     * is playing, it will be stopped. It also releases any native
389     * resources associated with this stream. If the stream is not
390     * playing, it will have no effect.
391     *
392     * @param streamID a streamID returned by the play() function
393     */
394    public native final void stop(int streamID);
395
396    /**
397     * Set stream volume.
398     *
399     * Sets the volume on the stream specified by the streamID.
400     * This is the value returned by the play() function. The
401     * value must be in the range of 0.0 to 1.0. If the stream does
402     * not exist, it will have no effect.
403     *
404     * @param streamID a streamID returned by the play() function
405     * @param leftVolume left volume value (range = 0.0 to 1.0)
406     * @param rightVolume right volume value (range = 0.0 to 1.0)
407     */
408    public final void setVolume(int streamID, float leftVolume, float rightVolume) {
409        if (isRestricted()) {
410            return;
411        }
412        _setVolume(streamID, leftVolume, rightVolume);
413    }
414
415    /**
416     * Similar, except set volume of all channels to same value.
417     * @hide
418     */
419    public void setVolume(int streamID, float volume) {
420        setVolume(streamID, volume, volume);
421    }
422
423    /**
424     * Change stream priority.
425     *
426     * Change the priority of the stream specified by the streamID.
427     * This is the value returned by the play() function. Affects the
428     * order in which streams are re-used to play new sounds. If the
429     * stream does not exist, it will have no effect.
430     *
431     * @param streamID a streamID returned by the play() function
432     */
433    public native final void setPriority(int streamID, int priority);
434
435    /**
436     * Set loop mode.
437     *
438     * Change the loop mode. A loop value of -1 means loop forever,
439     * a value of 0 means don't loop, other values indicate the
440     * number of repeats, e.g. a value of 1 plays the audio twice.
441     * If the stream does not exist, it will have no effect.
442     *
443     * @param streamID a streamID returned by the play() function
444     * @param loop loop mode (0 = no loop, -1 = loop forever)
445     */
446    public native final void setLoop(int streamID, int loop);
447
448    /**
449     * Change playback rate.
450     *
451     * The playback rate allows the application to vary the playback
452     * rate (pitch) of the sound. A value of 1.0 means playback at
453     * the original frequency. A value of 2.0 means playback twice
454     * as fast, and a value of 0.5 means playback at half speed.
455     * If the stream does not exist, it will have no effect.
456     *
457     * @param streamID a streamID returned by the play() function
458     * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
459     */
460    public native final void setRate(int streamID, float rate);
461
462    public interface OnLoadCompleteListener {
463        /**
464         * Called when a sound has completed loading.
465         *
466         * @param soundPool SoundPool object from the load() method
467         * @param sampleId the sample ID of the sound loaded.
468         * @param status the status of the load operation (0 = success)
469         */
470        public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
471    }
472
473    /**
474     * Sets the callback hook for the OnLoadCompleteListener.
475     */
476    public void setOnLoadCompleteListener(OnLoadCompleteListener listener) {
477        synchronized(mLock) {
478            if (listener != null) {
479                // setup message handler
480                Looper looper;
481                if ((looper = Looper.myLooper()) != null) {
482                    mEventHandler = new EventHandler(looper);
483                } else if ((looper = Looper.getMainLooper()) != null) {
484                    mEventHandler = new EventHandler(looper);
485                } else {
486                    mEventHandler = null;
487                }
488            } else {
489                mEventHandler = null;
490            }
491            mOnLoadCompleteListener = listener;
492        }
493    }
494
495    private boolean isRestricted() {
496        if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
497            return false;
498        }
499        return !mHasAppOpsPlayAudio;
500    }
501
502    private void updateAppOpsPlayAudio() {
503        try {
504            final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
505                    mAttributes.getUsage(),
506                    Process.myUid(), ActivityThread.currentPackageName());
507            mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
508        } catch (RemoteException e) {
509            mHasAppOpsPlayAudio = false;
510        }
511    }
512
513    private native final int _load(FileDescriptor fd, long offset, long length, int priority);
514
515    private native final int native_setup(Object weakRef, int maxStreams,
516            Object/*AudioAttributes*/ attributes);
517
518    private native final int _play(int soundID, float leftVolume, float rightVolume,
519            int priority, int loop, float rate);
520
521    private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
522
523    // post event from native code to message handler
524    @SuppressWarnings("unchecked")
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        if (soundPool.mEventHandler != null) {
531            Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
532            soundPool.mEventHandler.sendMessage(m);
533        }
534    }
535
536    private final class EventHandler extends Handler {
537        public EventHandler(Looper looper) {
538            super(looper);
539        }
540
541        @Override
542        public void handleMessage(Message msg) {
543            switch(msg.what) {
544            case SAMPLE_LOADED:
545                if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
546                synchronized(mLock) {
547                    if (mOnLoadCompleteListener != null) {
548                        mOnLoadCompleteListener.onLoadComplete(SoundPool.this, msg.arg1, msg.arg2);
549                    }
550                }
551                break;
552            default:
553                Log.e(TAG, "Unknown message type " + msg.what);
554                return;
555            }
556        }
557    }
558
559    /**
560     * Builder class for {@link SoundPool} objects.
561     */
562    public static class Builder {
563        private int mMaxStreams = 1;
564        private AudioAttributes mAudioAttributes;
565
566        /**
567         * Constructs a new Builder with the defaults format values.
568         * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to
569         * change it), and the audio attributes have a usage value of
570         * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to
571         * change them).
572         */
573        public Builder() {
574        }
575
576        /**
577         * Sets the maximum of number of simultaneous streams that can be played simultaneously.
578         * @param maxStreams a value equal to 1 or greater.
579         * @return the same Builder instance
580         * @throws IllegalArgumentException
581         */
582        public Builder setMaxStreams(int maxStreams) throws IllegalArgumentException {
583            if (maxStreams <= 0) {
584                throw new IllegalArgumentException(
585                        "Strictly positive value required for the maximum number of streams");
586            }
587            mMaxStreams = maxStreams;
588            return this;
589        }
590
591        /**
592         * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
593         * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
594         * @param attributes a non-null
595         * @return
596         */
597        public Builder setAudioAttributes(AudioAttributes attributes)
598                throws IllegalArgumentException {
599            if (attributes == null) {
600                throw new IllegalArgumentException("Invalid null AudioAttributes");
601            }
602            mAudioAttributes = attributes;
603            return this;
604        }
605
606        public SoundPool build() {
607            if (mAudioAttributes == null) {
608                mAudioAttributes = new AudioAttributes.Builder()
609                        .setUsage(AudioAttributes.USAGE_MEDIA).build();
610            }
611            return new SoundPool(mMaxStreams, mAudioAttributes);
612        }
613    }
614}
615