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 SoundState(int name)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 */ MediaActionSound()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 loadSound(SoundState sound)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 */ load(int soundName)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 */ play(int soundName)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 */ release()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