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