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