Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2012 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.media.AudioManager;
     20 import android.media.SoundPool;
     21 import android.util.Log;
     22 
     23 /**
     24  * <p>A class for producing sounds that match those produced by various actions
     25  * taken by the media and camera APIs.  </p>
     26  *
     27  * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the
     28  * camera2 API does not play any sounds on its own for any capture or video recording actions.</p>
     29  *
     30  * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate
     31  * camera operation sound when implementing a custom still or video recording mechanism (through the
     32  * Camera preview callbacks with
     33  * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU
     34  * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for
     35  * example), or when implementing some other camera-like function in your application.</p>
     36  *
     37  * <p>There is no need to play sounds when using
     38  * {@link android.hardware.Camera#takePicture Camera.takePicture} or
     39  * {@link android.media.MediaRecorder} for still images or video, respectively,
     40  * as the Android framework will play the appropriate sounds when needed for
     41  * these calls.</p>
     42  *
     43  */
     44 public class MediaActionSound {
     45     private static final int NUM_MEDIA_SOUND_STREAMS = 1;
     46 
     47     private SoundPool mSoundPool;
     48     private SoundState[] mSounds;
     49 
     50     private static final String[] SOUND_DIRS = {
     51         "/product/media/audio/ui/",
     52         "/system/media/audio/ui/",
     53     };
     54 
     55     private static final String[] SOUND_FILES = {
     56         "camera_click.ogg",
     57         "camera_focus.ogg",
     58         "VideoRecord.ogg",
     59         "VideoStop.ogg"
     60     };
     61 
     62     private static final String TAG = "MediaActionSound";
     63     /**
     64      * The sound used by
     65      * {@link android.hardware.Camera#takePicture Camera.takePicture} to
     66      * indicate still image capture.
     67      * @see #play
     68      */
     69     public static final int SHUTTER_CLICK         = 0;
     70 
     71     /**
     72      * A sound to indicate that focusing has completed. Because deciding
     73      * when this occurs is application-dependent, this sound is not used by
     74      * any methods in the media or camera APIs.
     75      * @see #play
     76      */
     77     public static final int FOCUS_COMPLETE        = 1;
     78 
     79     /**
     80      * The sound used by
     81      * {@link android.media.MediaRecorder#start MediaRecorder.start()} to
     82      * indicate the start of video recording.
     83      * @see #play
     84      */
     85     public static final int START_VIDEO_RECORDING = 2;
     86 
     87     /**
     88      * The sound used by
     89      * {@link android.media.MediaRecorder#stop MediaRecorder.stop()} to
     90      * indicate the end of video recording.
     91      * @see #play
     92      */
     93     public static final int STOP_VIDEO_RECORDING  = 3;
     94 
     95     /**
     96      * States for SoundState.
     97      * STATE_NOT_LOADED             : sample not loaded
     98      * STATE_LOADING                : sample being loaded: waiting for load completion callback
     99      * STATE_LOADING_PLAY_REQUESTED : sample being loaded and playback request received
    100      * STATE_LOADED                 : sample loaded, ready for playback
    101      */
    102     private static final int STATE_NOT_LOADED             = 0;
    103     private static final int STATE_LOADING                = 1;
    104     private static final int STATE_LOADING_PLAY_REQUESTED = 2;
    105     private static final int STATE_LOADED                 = 3;
    106 
    107     private class SoundState {
    108         public final int name;
    109         public int id;
    110         public int state;
    111 
    112         public SoundState(int name) {
    113             this.name = name;
    114             id = 0; // 0 is an invalid sample ID.
    115             state = STATE_NOT_LOADED;
    116         }
    117     }
    118     /**
    119      * Construct a new MediaActionSound instance. Only a single instance is
    120      * needed for playing any platform media action sound; you do not need a
    121      * separate instance for each sound type.
    122      */
    123     public MediaActionSound() {
    124         mSoundPool = new SoundPool.Builder()
    125                 .setMaxStreams(NUM_MEDIA_SOUND_STREAMS)
    126                 .setAudioAttributes(new AudioAttributes.Builder()
    127                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
    128                     .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
    129                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
    130                     .build())
    131                 .build();
    132         mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener);
    133         mSounds = new SoundState[SOUND_FILES.length];
    134         for (int i = 0; i < mSounds.length; i++) {
    135             mSounds[i] = new SoundState(i);
    136         }
    137     }
    138 
    139     private int loadSound(SoundState sound) {
    140         final String soundFileName = SOUND_FILES[sound.name];
    141         for (String soundDir : SOUND_DIRS) {
    142             int id = mSoundPool.load(soundDir + soundFileName, 1);
    143             if (id > 0) {
    144                 sound.state = STATE_LOADING;
    145                 sound.id = id;
    146                 return id;
    147             }
    148         }
    149         return 0;
    150     }
    151 
    152     /**
    153      * Preload a predefined platform sound to minimize latency when the sound is
    154      * played later by {@link #play}.
    155      * @param soundName The type of sound to preload, selected from
    156      *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
    157      *         STOP_VIDEO_RECORDING.
    158      * @see #play
    159      * @see #SHUTTER_CLICK
    160      * @see #FOCUS_COMPLETE
    161      * @see #START_VIDEO_RECORDING
    162      * @see #STOP_VIDEO_RECORDING
    163      */
    164     public void load(int soundName) {
    165         if (soundName < 0 || soundName >= SOUND_FILES.length) {
    166             throw new RuntimeException("Unknown sound requested: " + soundName);
    167         }
    168         SoundState sound = mSounds[soundName];
    169         synchronized (sound) {
    170             switch (sound.state) {
    171             case STATE_NOT_LOADED:
    172                 if (loadSound(sound) <= 0) {
    173                     Log.e(TAG, "load() error loading sound: " + soundName);
    174                 }
    175                 break;
    176             default:
    177                 Log.e(TAG, "load() called in wrong state: " + sound + " for sound: "+ soundName);
    178                 break;
    179             }
    180         }
    181     }
    182 
    183     /**
    184      * <p>Play one of the predefined platform sounds for media actions.</p>
    185      *
    186      * <p>Use this method to play a platform-specific sound for various media
    187      * actions. The sound playback is done asynchronously, with the same
    188      * behavior and content as the sounds played by
    189      * {@link android.hardware.Camera#takePicture Camera.takePicture},
    190      * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
    191      * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
    192      *
    193      * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
    194      * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
    195 
    196      * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
    197      * match the default device sounds when recording or capturing data through the preview
    198      * callbacks, or when implementing custom camera-like features in your application.</p>
    199      *
    200      * <p>If the sound has not been loaded by {@link #load} before calling play,
    201      * play will load the sound at the cost of some additional latency before
    202      * sound playback begins. </p>
    203      *
    204      * @param soundName The type of sound to play, selected from
    205      *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
    206      *         STOP_VIDEO_RECORDING.
    207      * @see android.hardware.Camera#takePicture
    208      * @see android.media.MediaRecorder
    209      * @see #SHUTTER_CLICK
    210      * @see #FOCUS_COMPLETE
    211      * @see #START_VIDEO_RECORDING
    212      * @see #STOP_VIDEO_RECORDING
    213      */
    214     public void play(int soundName) {
    215         if (soundName < 0 || soundName >= SOUND_FILES.length) {
    216             throw new RuntimeException("Unknown sound requested: " + soundName);
    217         }
    218         SoundState sound = mSounds[soundName];
    219         synchronized (sound) {
    220             switch (sound.state) {
    221             case STATE_NOT_LOADED:
    222                 loadSound(sound);
    223                 if (loadSound(sound) <= 0) {
    224                     Log.e(TAG, "play() error loading sound: " + soundName);
    225                     break;
    226                 }
    227                 // FALL THROUGH
    228 
    229             case STATE_LOADING:
    230                 sound.state = STATE_LOADING_PLAY_REQUESTED;
    231                 break;
    232             case STATE_LOADED:
    233                 mSoundPool.play(sound.id, 1.0f, 1.0f, 0, 0, 1.0f);
    234                 break;
    235             default:
    236                 Log.e(TAG, "play() called in wrong state: " + sound.state + " for sound: "+ soundName);
    237                 break;
    238             }
    239         }
    240     }
    241 
    242     private SoundPool.OnLoadCompleteListener mLoadCompleteListener =
    243             new SoundPool.OnLoadCompleteListener() {
    244         public void onLoadComplete(SoundPool soundPool,
    245                 int sampleId, int status) {
    246             for (SoundState sound : mSounds) {
    247                 if (sound.id != sampleId) {
    248                     continue;
    249                 }
    250                 int playSoundId = 0;
    251                 synchronized (sound) {
    252                     if (status != 0) {
    253                         sound.state = STATE_NOT_LOADED;
    254                         sound.id = 0;
    255                         Log.e(TAG, "OnLoadCompleteListener() error: " + status +
    256                                 " loading sound: "+ sound.name);
    257                         return;
    258                     }
    259                     switch (sound.state) {
    260                     case STATE_LOADING:
    261                         sound.state = STATE_LOADED;
    262                         break;
    263                     case STATE_LOADING_PLAY_REQUESTED:
    264                         playSoundId = sound.id;
    265                         sound.state = STATE_LOADED;
    266                         break;
    267                     default:
    268                         Log.e(TAG, "OnLoadCompleteListener() called in wrong state: "
    269                                 + sound.state + " for sound: "+ sound.name);
    270                         break;
    271                     }
    272                 }
    273                 if (playSoundId != 0) {
    274                     soundPool.play(playSoundId, 1.0f, 1.0f, 0, 0, 1.0f);
    275                 }
    276                 break;
    277             }
    278         }
    279     };
    280 
    281     /**
    282      * Free up all audio resources used by this MediaActionSound instance. Do
    283      * not call any other methods on a MediaActionSound instance after calling
    284      * release().
    285      */
    286     public void release() {
    287         if (mSoundPool != null) {
    288             for (SoundState sound : mSounds) {
    289                 synchronized (sound) {
    290                     sound.state = STATE_NOT_LOADED;
    291                     sound.id = 0;
    292                 }
    293             }
    294             mSoundPool.release();
    295             mSoundPool = null;
    296         }
    297     }
    298 }
    299