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