Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media;
     18 
     19 import android.util.AndroidRuntimeException;
     20 import android.util.Log;
     21 import java.io.File;
     22 import java.io.FileDescriptor;
     23 import android.os.ParcelFileDescriptor;
     24 import java.lang.ref.WeakReference;
     25 import android.content.Context;
     26 import android.content.res.AssetFileDescriptor;
     27 import java.io.IOException;
     28 
     29 import android.os.Handler;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 
     33 /**
     34  * The SoundPool class manages and plays audio resources for applications.
     35  *
     36  * <p>A SoundPool is a collection of samples that can be loaded into memory
     37  * from a resource inside the APK or from a file in the file system. The
     38  * SoundPool library uses the MediaPlayer service to decode the audio
     39  * into a raw 16-bit PCM mono or stereo stream. This allows applications
     40  * to ship with compressed streams without having to suffer the CPU load
     41  * and latency of decompressing during playback.</p>
     42  *
     43  * <p>In addition to low-latency playback, SoundPool can also manage the number
     44  * of audio streams being rendered at once. When the SoundPool object is
     45  * constructed, the maxStreams parameter sets the maximum number of streams
     46  * that can be played at a time from this single SoundPool. SoundPool tracks
     47  * the number of active streams. If the maximum number of streams is exceeded,
     48  * SoundPool will automatically stop a previously playing stream based first
     49  * on priority and then by age within that priority. Limiting the maximum
     50  * number of streams helps to cap CPU loading and reducing the likelihood that
     51  * audio mixing will impact visuals or UI performance.</p>
     52  *
     53  * <p>Sounds can be looped by setting a non-zero loop value. A value of -1
     54  * causes the sound to loop forever. In this case, the application must
     55  * explicitly call the stop() function to stop the sound. Any other non-zero
     56  * value will cause the sound to repeat the specified number of times, e.g.
     57  * a value of 3 causes the sound to play a total of 4 times.</p>
     58  *
     59  * <p>The playback rate can also be changed. A playback rate of 1.0 causes
     60  * the sound to play at its original frequency (resampled, if necessary,
     61  * to the hardware output frequency). A playback rate of 2.0 causes the
     62  * sound to play at twice its original frequency, and a playback rate of
     63  * 0.5 causes it to play at half its original frequency. The playback
     64  * rate range is 0.5 to 2.0.</p>
     65  *
     66  * <p>Priority runs low to high, i.e. higher numbers are higher priority.
     67  * Priority is used when a call to play() would cause the number of active
     68  * streams to exceed the value established by the maxStreams parameter when
     69  * the SoundPool was created. In this case, the stream allocator will stop
     70  * the lowest priority stream. If there are multiple streams with the same
     71  * low priority, it will choose the oldest stream to stop. In the case
     72  * where the priority of the new stream is lower than all the active
     73  * streams, the new sound will not play and the play() function will return
     74  * a streamID of zero.</p>
     75  *
     76  * <p>Let's examine a typical use case: A game consists of several levels of
     77  * play. For each level, there is a set of unique sounds that are used only
     78  * by that level. In this case, the game logic should create a new SoundPool
     79  * object when the first level is loaded. The level data itself might contain
     80  * the list of sounds to be used by this level. The loading logic iterates
     81  * through the list of sounds calling the appropriate SoundPool.load()
     82  * function. This should typically be done early in the process to allow time
     83  * for decompressing the audio to raw PCM format before they are needed for
     84  * playback.</p>
     85  *
     86  * <p>Once the sounds are loaded and play has started, the application can
     87  * trigger sounds by calling SoundPool.play(). Playing streams can be
     88  * paused or resumed, and the application can also alter the pitch by
     89  * adjusting the playback rate in real-time for doppler or synthesis
     90  * effects.</p>
     91  *
     92  * <p>Note that since streams can be stopped due to resource constraints, the
     93  * streamID is a reference to a particular instance of a stream. If the stream
     94  * is stopped to allow a higher priority stream to play, the stream is no
     95  * longer be valid. However, the application is allowed to call methods on
     96  * the streamID without error. This may help simplify program logic since
     97  * the application need not concern itself with the stream lifecycle.</p>
     98  *
     99  * <p>In our example, when the player has completed the level, the game
    100  * logic should call SoundPool.release() to release all the native resources
    101  * in use and then set the SoundPool reference to null. If the player starts
    102  * another level, a new SoundPool is created, sounds are loaded, and play
    103  * resumes.</p>
    104  */
    105 public class SoundPool
    106 {
    107     static { System.loadLibrary("soundpool"); }
    108 
    109     private final static String TAG = "SoundPool";
    110     private final static boolean DEBUG = false;
    111 
    112     private int mNativeContext; // accessed by native methods
    113 
    114     private EventHandler mEventHandler;
    115     private OnLoadCompleteListener mOnLoadCompleteListener;
    116 
    117     private final Object mLock;
    118 
    119     // SoundPool messages
    120     //
    121     // must match SoundPool.h
    122     private static final int SAMPLE_LOADED = 1;
    123 
    124     /**
    125      * Constructor. Constructs a SoundPool object with the following
    126      * characteristics:
    127      *
    128      * @param maxStreams the maximum number of simultaneous streams for this
    129      *                   SoundPool object
    130      * @param streamType the audio stream type as described in AudioManager
    131      *                   For example, game applications will normally use
    132      *                   {@link AudioManager#STREAM_MUSIC}.
    133      * @param srcQuality the sample-rate converter quality. Currently has no
    134      *                   effect. Use 0 for the default.
    135      * @return a SoundPool object, or null if creation failed
    136      */
    137     public SoundPool(int maxStreams, int streamType, int srcQuality) {
    138 
    139         // do native setup
    140         if (native_setup(new WeakReference(this), maxStreams, streamType, srcQuality) != 0) {
    141             throw new RuntimeException("Native setup failed");
    142         }
    143         mLock = new Object();
    144     }
    145 
    146     /**
    147      * Load the sound from the specified path.
    148      *
    149      * @param path the path to the audio file
    150      * @param priority the priority of the sound. Currently has no effect. Use
    151      *                 a value of 1 for future compatibility.
    152      * @return a sound ID. This value can be used to play or unload the sound.
    153      */
    154     public int load(String path, int priority)
    155     {
    156         // pass network streams to player
    157         if (path.startsWith("http:"))
    158             return _load(path, priority);
    159 
    160         // try local path
    161         int id = 0;
    162         try {
    163             File f = new File(path);
    164             if (f != null) {
    165                 ParcelFileDescriptor fd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
    166                 if (fd != null) {
    167                     id = _load(fd.getFileDescriptor(), 0, f.length(), priority);
    168                     fd.close();
    169                 }
    170             }
    171         } catch (java.io.IOException e) {
    172             Log.e(TAG, "error loading " + path);
    173         }
    174         return id;
    175     }
    176 
    177     /**
    178      * Load the sound from the specified APK resource.
    179      *
    180      * Note that the extension is dropped. For example, if you want to load
    181      * a sound from the raw resource file "explosion.mp3", you would specify
    182      * "R.raw.explosion" as the resource ID. Note that this means you cannot
    183      * have both an "explosion.wav" and an "explosion.mp3" in the res/raw
    184      * directory.
    185      *
    186      * @param context the application context
    187      * @param resId the resource ID
    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(Context context, int resId, int priority) {
    193         AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
    194         int id = 0;
    195         if (afd != null) {
    196             id = _load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), priority);
    197             try {
    198                 afd.close();
    199             } catch (java.io.IOException ex) {
    200                 //Log.d(TAG, "close failed:", ex);
    201             }
    202         }
    203         return id;
    204     }
    205 
    206     /**
    207      * Load the sound from an asset file descriptor.
    208      *
    209      * @param afd an asset file descriptor
    210      * @param priority the priority of the sound. Currently has no effect. Use
    211      *                 a value of 1 for future compatibility.
    212      * @return a sound ID. This value can be used to play or unload the sound.
    213      */
    214     public int load(AssetFileDescriptor afd, int priority) {
    215         if (afd != null) {
    216             long len = afd.getLength();
    217             if (len < 0) {
    218                 throw new AndroidRuntimeException("no length for fd");
    219             }
    220             return _load(afd.getFileDescriptor(), afd.getStartOffset(), len, priority);
    221         } else {
    222             return 0;
    223         }
    224     }
    225 
    226     /**
    227      * Load the sound from a FileDescriptor.
    228      *
    229      * This version is useful if you store multiple sounds in a single
    230      * binary. The offset specifies the offset from the start of the file
    231      * and the length specifies the length of the sound within the file.
    232      *
    233      * @param fd a FileDescriptor object
    234      * @param offset offset to the start of the sound
    235      * @param length length of the sound
    236      * @param priority the priority of the sound. Currently has no effect. Use
    237      *                 a value of 1 for future compatibility.
    238      * @return a sound ID. This value can be used to play or unload the sound.
    239      */
    240     public int load(FileDescriptor fd, long offset, long length, int priority) {
    241         return _load(fd, offset, length, priority);
    242     }
    243 
    244     private native final int _load(String uri, int priority);
    245 
    246     private native final int _load(FileDescriptor fd, long offset, long length, int priority);
    247 
    248     /**
    249      * Unload a sound from a sound ID.
    250      *
    251      * Unloads the sound specified by the soundID. This is the value
    252      * returned by the load() function. Returns true if the sound is
    253      * successfully unloaded, false if the sound was already unloaded.
    254      *
    255      * @param soundID a soundID returned by the load() function
    256      * @return true if just unloaded, false if previously unloaded
    257      */
    258     public native final boolean unload(int soundID);
    259 
    260     /**
    261      * Play a sound from a sound ID.
    262      *
    263      * Play the sound specified by the soundID. This is the value
    264      * returned by the load() function. Returns a non-zero streamID
    265      * if successful, zero if it fails. The streamID can be used to
    266      * further control playback. Note that calling play() may cause
    267      * another sound to stop playing if the maximum number of active
    268      * streams is exceeded. A loop value of -1 means loop forever,
    269      * a value of 0 means don't loop, other values indicate the
    270      * number of repeats, e.g. a value of 1 plays the audio twice.
    271      * The playback rate allows the application to vary the playback
    272      * rate (pitch) of the sound. A value of 1.0 means play back at
    273      * the original frequency. A value of 2.0 means play back twice
    274      * as fast, and a value of 0.5 means playback at half speed.
    275      *
    276      * @param soundID a soundID returned by the load() function
    277      * @param leftVolume left volume value (range = 0.0 to 1.0)
    278      * @param rightVolume right volume value (range = 0.0 to 1.0)
    279      * @param priority stream priority (0 = lowest priority)
    280      * @param loop loop mode (0 = no loop, -1 = loop forever)
    281      * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
    282      * @return non-zero streamID if successful, zero if failed
    283      */
    284     public native final int play(int soundID, float leftVolume, float rightVolume,
    285             int priority, int loop, float rate);
    286 
    287     /**
    288      * Pause a playback stream.
    289      *
    290      * Pause the stream specified by the streamID. This is the
    291      * value returned by the play() function. If the stream is
    292      * playing, it will be paused. If the stream is not playing
    293      * (e.g. is stopped or was previously paused), calling this
    294      * function will have no effect.
    295      *
    296      * @param streamID a streamID returned by the play() function
    297      */
    298     public native final void pause(int streamID);
    299 
    300     /**
    301      * Resume a playback stream.
    302      *
    303      * Resume the stream specified by the streamID. This
    304      * is the value returned by the play() function. If the stream
    305      * is paused, this will resume playback. If the stream was not
    306      * previously paused, calling this function will have no effect.
    307      *
    308      * @param streamID a streamID returned by the play() function
    309      */
    310     public native final void resume(int streamID);
    311 
    312     /**
    313      * Pause all active streams.
    314      *
    315      * Pause all streams that are currently playing. This function
    316      * iterates through all the active streams and pauses any that
    317      * are playing. It also sets a flag so that any streams that
    318      * are playing can be resumed by calling autoResume().
    319      */
    320     public native final void autoPause();
    321 
    322     /**
    323      * Resume all previously active streams.
    324      *
    325      * Automatically resumes all streams that were paused in previous
    326      * calls to autoPause().
    327      */
    328     public native final void autoResume();
    329 
    330     /**
    331      * Stop a playback stream.
    332      *
    333      * Stop the stream specified by the streamID. This
    334      * is the value returned by the play() function. If the stream
    335      * is playing, it will be stopped. It also releases any native
    336      * resources associated with this stream. If the stream is not
    337      * playing, it will have no effect.
    338      *
    339      * @param streamID a streamID returned by the play() function
    340      */
    341     public native final void stop(int streamID);
    342 
    343     /**
    344      * Set stream volume.
    345      *
    346      * Sets the volume on the stream specified by the streamID.
    347      * This is the value returned by the play() function. The
    348      * value must be in the range of 0.0 to 1.0. If the stream does
    349      * not exist, it will have no effect.
    350      *
    351      * @param streamID a streamID returned by the play() function
    352      * @param leftVolume left volume value (range = 0.0 to 1.0)
    353      * @param rightVolume right volume value (range = 0.0 to 1.0)
    354      */
    355     public native final void setVolume(int streamID,
    356             float leftVolume, float rightVolume);
    357 
    358     /**
    359      * Change stream priority.
    360      *
    361      * Change the priority of the stream specified by the streamID.
    362      * This is the value returned by the play() function. Affects the
    363      * order in which streams are re-used to play new sounds. If the
    364      * stream does not exist, it will have no effect.
    365      *
    366      * @param streamID a streamID returned by the play() function
    367      */
    368     public native final void setPriority(int streamID, int priority);
    369 
    370     /**
    371      * Set loop mode.
    372      *
    373      * Change the loop mode. A loop value of -1 means loop forever,
    374      * a value of 0 means don't loop, other values indicate the
    375      * number of repeats, e.g. a value of 1 plays the audio twice.
    376      * If the stream does not exist, it will have no effect.
    377      *
    378      * @param streamID a streamID returned by the play() function
    379      * @param loop loop mode (0 = no loop, -1 = loop forever)
    380      */
    381     public native final void setLoop(int streamID, int loop);
    382 
    383     /**
    384      * Change playback rate.
    385      *
    386      * The playback rate allows the application to vary the playback
    387      * rate (pitch) of the sound. A value of 1.0 means playback at
    388      * the original frequency. A value of 2.0 means playback twice
    389      * as fast, and a value of 0.5 means playback at half speed.
    390      * If the stream does not exist, it will have no effect.
    391      *
    392      * @param streamID a streamID returned by the play() function
    393      * @param rate playback rate (1.0 = normal playback, range 0.5 to 2.0)
    394      */
    395     public native final void setRate(int streamID, float rate);
    396 
    397     /**
    398      * Interface definition for a callback to be invoked when all the
    399      * sounds are loaded.
    400      */
    401     public interface OnLoadCompleteListener
    402     {
    403         /**
    404          * Called when a sound has completed loading.
    405          *
    406          * @param soundPool SoundPool object from the load() method
    407          * @param soundPool the sample ID of the sound loaded.
    408          * @param status the status of the load operation (0 = success)
    409          */
    410         public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
    411     }
    412 
    413     /**
    414      * Sets the callback hook for the OnLoadCompleteListener.
    415      */
    416     public void setOnLoadCompleteListener(OnLoadCompleteListener listener)
    417     {
    418         synchronized(mLock) {
    419             if (listener != null) {
    420                 // setup message handler
    421                 Looper looper;
    422                 if ((looper = Looper.myLooper()) != null) {
    423                     mEventHandler = new EventHandler(this, looper);
    424                 } else if ((looper = Looper.getMainLooper()) != null) {
    425                     mEventHandler = new EventHandler(this, looper);
    426                 } else {
    427                     mEventHandler = null;
    428                 }
    429             } else {
    430                 mEventHandler = null;
    431             }
    432             mOnLoadCompleteListener = listener;
    433         }
    434     }
    435 
    436     private class EventHandler extends Handler
    437     {
    438         private SoundPool mSoundPool;
    439 
    440         public EventHandler(SoundPool soundPool, Looper looper) {
    441             super(looper);
    442             mSoundPool = soundPool;
    443         }
    444 
    445         @Override
    446         public void handleMessage(Message msg) {
    447             switch(msg.what) {
    448             case SAMPLE_LOADED:
    449                 if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
    450                 synchronized(mLock) {
    451                     if (mOnLoadCompleteListener != null) {
    452                         mOnLoadCompleteListener.onLoadComplete(mSoundPool, msg.arg1, msg.arg2);
    453                     }
    454                 }
    455                 break;
    456             default:
    457                 Log.e(TAG, "Unknown message type " + msg.what);
    458                 return;
    459             }
    460         }
    461     }
    462 
    463     // post event from native code to message handler
    464     private static void postEventFromNative(Object weakRef, int msg, int arg1, int arg2, Object obj)
    465     {
    466         SoundPool soundPool = (SoundPool)((WeakReference)weakRef).get();
    467         if (soundPool == null)
    468             return;
    469 
    470         if (soundPool.mEventHandler != null) {
    471             Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
    472             soundPool.mEventHandler.sendMessage(m);
    473         }
    474     }
    475 
    476     /**
    477      * Release the SoundPool resources.
    478      *
    479      * Release all memory and native resources used by the SoundPool
    480      * object. The SoundPool can no longer be used and the reference
    481      * should be set to null.
    482      */
    483     public native final void release();
    484 
    485     private native final int native_setup(Object weakRef, int maxStreams, int streamType, int srcQuality);
    486 
    487     protected void finalize() { release(); }
    488 }
    489