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.app.ActivityThread;
     24 import android.app.AppOpsManager;
     25 import android.content.Context;
     26 import android.content.res.AssetFileDescriptor;
     27 import android.os.Handler;
     28 import android.os.IBinder;
     29 import android.os.Looper;
     30 import android.os.Message;
     31 import android.os.ParcelFileDescriptor;
     32 import android.os.Process;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.util.AndroidRuntimeException;
     36 import android.util.Log;
     37 
     38 import com.android.internal.app.IAppOpsCallback;
     39 import 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  */
    114 public 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         // check app ops
    509         if (mHasAppOpsPlayAudio) {
    510             return false;
    511         }
    512         // check bypass flag
    513         if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
    514             return false;
    515         }
    516         // check force audibility flag and camera restriction
    517         if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0) {
    518 // FIXME: should also check usage when set properly by camera app
    519 //          && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
    520             boolean cameraSoundForced = false;
    521             try {
    522                 cameraSoundForced = getService().isCameraSoundForced();
    523             } catch (RemoteException e) {
    524                 Log.e(TAG, "Cannot access AudioService in isRestricted()");
    525             } catch (NullPointerException e) {
    526                 Log.e(TAG, "Null AudioService in isRestricted()");
    527             }
    528             if (cameraSoundForced) {
    529                 return false;
    530             }
    531         }
    532         return true;
    533     }
    534 
    535     private void updateAppOpsPlayAudio() {
    536         try {
    537             final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
    538                     mAttributes.getUsage(),
    539                     Process.myUid(), ActivityThread.currentPackageName());
    540             mHasAppOpsPlayAudio = (mode == AppOpsManager.MODE_ALLOWED);
    541         } catch (RemoteException e) {
    542             mHasAppOpsPlayAudio = false;
    543         }
    544     }
    545 
    546     private native final int _load(FileDescriptor fd, long offset, long length, int priority);
    547 
    548     private native final int native_setup(Object weakRef, int maxStreams,
    549             Object/*AudioAttributes*/ attributes);
    550 
    551     private native final int _play(int soundID, float leftVolume, float rightVolume,
    552             int priority, int loop, float rate);
    553 
    554     private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
    555 
    556     // post event from native code to message handler
    557     @SuppressWarnings("unchecked")
    558     private static void postEventFromNative(Object ref, int msg, int arg1, int arg2, Object obj) {
    559         SoundPool soundPool = ((WeakReference<SoundPool>) ref).get();
    560         if (soundPool == null)
    561             return;
    562 
    563         if (soundPool.mEventHandler != null) {
    564             Message m = soundPool.mEventHandler.obtainMessage(msg, arg1, arg2, obj);
    565             soundPool.mEventHandler.sendMessage(m);
    566         }
    567     }
    568 
    569     private final class EventHandler extends Handler {
    570         public EventHandler(Looper looper) {
    571             super(looper);
    572         }
    573 
    574         @Override
    575         public void handleMessage(Message msg) {
    576             switch(msg.what) {
    577             case SAMPLE_LOADED:
    578                 if (DEBUG) Log.d(TAG, "Sample " + msg.arg1 + " loaded");
    579                 synchronized(mLock) {
    580                     if (mOnLoadCompleteListener != null) {
    581                         mOnLoadCompleteListener.onLoadComplete(SoundPool.this, msg.arg1, msg.arg2);
    582                     }
    583                 }
    584                 break;
    585             default:
    586                 Log.e(TAG, "Unknown message type " + msg.what);
    587                 return;
    588             }
    589         }
    590     }
    591 
    592     /**
    593      * Builder class for {@link SoundPool} objects.
    594      */
    595     public static class Builder {
    596         private int mMaxStreams = 1;
    597         private AudioAttributes mAudioAttributes;
    598 
    599         /**
    600          * Constructs a new Builder with the defaults format values.
    601          * If not provided, the maximum number of streams is 1 (see {@link #setMaxStreams(int)} to
    602          * change it), and the audio attributes have a usage value of
    603          * {@link AudioAttributes#USAGE_MEDIA} (see {@link #setAudioAttributes(AudioAttributes)} to
    604          * change them).
    605          */
    606         public Builder() {
    607         }
    608 
    609         /**
    610          * Sets the maximum of number of simultaneous streams that can be played simultaneously.
    611          * @param maxStreams a value equal to 1 or greater.
    612          * @return the same Builder instance
    613          * @throws IllegalArgumentException
    614          */
    615         public Builder setMaxStreams(int maxStreams) throws IllegalArgumentException {
    616             if (maxStreams <= 0) {
    617                 throw new IllegalArgumentException(
    618                         "Strictly positive value required for the maximum number of streams");
    619             }
    620             mMaxStreams = maxStreams;
    621             return this;
    622         }
    623 
    624         /**
    625          * Sets the {@link AudioAttributes}. For examples, game applications will use attributes
    626          * built with usage information set to {@link AudioAttributes#USAGE_GAME}.
    627          * @param attributes a non-null
    628          * @return
    629          */
    630         public Builder setAudioAttributes(AudioAttributes attributes)
    631                 throws IllegalArgumentException {
    632             if (attributes == null) {
    633                 throw new IllegalArgumentException("Invalid null AudioAttributes");
    634             }
    635             mAudioAttributes = attributes;
    636             return this;
    637         }
    638 
    639         public SoundPool build() {
    640             if (mAudioAttributes == null) {
    641                 mAudioAttributes = new AudioAttributes.Builder()
    642                         .setUsage(AudioAttributes.USAGE_MEDIA).build();
    643             }
    644             return new SoundPool(mMaxStreams, mAudioAttributes);
    645         }
    646     }
    647 }
    648