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