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