|  | /* | 
|  | * Copyright (C) 2012 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package android.media; | 
|  |  | 
|  | import android.media.AudioManager; | 
|  | import android.media.SoundPool; | 
|  | import android.util.Log; | 
|  |  | 
|  | /** | 
|  | * <p>A class for producing sounds that match those produced by various actions | 
|  | * taken by the media and camera APIs.  </p> | 
|  | * | 
|  | * <p>This class is recommended for use with the {@link android.hardware.camera2} API, since the | 
|  | * camera2 API does not play any sounds on its own for any capture or video recording actions.</p> | 
|  | * | 
|  | * <p>With the older {@link android.hardware.Camera} API, use this class to play an appropriate | 
|  | * camera operation sound when implementing a custom still or video recording mechanism (through the | 
|  | * Camera preview callbacks with | 
|  | * {@link android.hardware.Camera#setPreviewCallback Camera.setPreviewCallback}, or through GPU | 
|  | * processing with {@link android.hardware.Camera#setPreviewTexture Camera.setPreviewTexture}, for | 
|  | * example), or when implementing some other camera-like function in your application.</p> | 
|  | * | 
|  | * <p>There is no need to play sounds when using | 
|  | * {@link android.hardware.Camera#takePicture Camera.takePicture} or | 
|  | * {@link android.media.MediaRecorder} for still images or video, respectively, | 
|  | * as the Android framework will play the appropriate sounds when needed for | 
|  | * these calls.</p> | 
|  | * | 
|  | */ | 
|  | public class MediaActionSound { | 
|  | private static final int NUM_MEDIA_SOUND_STREAMS = 1; | 
|  |  | 
|  | private SoundPool mSoundPool; | 
|  | private SoundState[] mSounds; | 
|  |  | 
|  | private static final String[] SOUND_FILES = { | 
|  | "/system/media/audio/ui/camera_click.ogg", | 
|  | "/system/media/audio/ui/camera_focus.ogg", | 
|  | "/system/media/audio/ui/VideoRecord.ogg", | 
|  | "/system/media/audio/ui/VideoStop.ogg" | 
|  | }; | 
|  |  | 
|  | private static final String TAG = "MediaActionSound"; | 
|  | /** | 
|  | * The sound used by | 
|  | * {@link android.hardware.Camera#takePicture Camera.takePicture} to | 
|  | * indicate still image capture. | 
|  | * @see #play | 
|  | */ | 
|  | public static final int SHUTTER_CLICK         = 0; | 
|  |  | 
|  | /** | 
|  | * A sound to indicate that focusing has completed. Because deciding | 
|  | * when this occurs is application-dependent, this sound is not used by | 
|  | * any methods in the media or camera APIs. | 
|  | * @see #play | 
|  | */ | 
|  | public static final int FOCUS_COMPLETE        = 1; | 
|  |  | 
|  | /** | 
|  | * The sound used by | 
|  | * {@link android.media.MediaRecorder#start MediaRecorder.start()} to | 
|  | * indicate the start of video recording. | 
|  | * @see #play | 
|  | */ | 
|  | public static final int START_VIDEO_RECORDING = 2; | 
|  |  | 
|  | /** | 
|  | * The sound used by | 
|  | * {@link android.media.MediaRecorder#stop MediaRecorder.stop()} to | 
|  | * indicate the end of video recording. | 
|  | * @see #play | 
|  | */ | 
|  | public static final int STOP_VIDEO_RECORDING  = 3; | 
|  |  | 
|  | /** | 
|  | * States for SoundState. | 
|  | * STATE_NOT_LOADED             : sample not loaded | 
|  | * STATE_LOADING                : sample being loaded: waiting for load completion callback | 
|  | * STATE_LOADING_PLAY_REQUESTED : sample being loaded and playback request received | 
|  | * STATE_LOADED                 : sample loaded, ready for playback | 
|  | */ | 
|  | private static final int STATE_NOT_LOADED             = 0; | 
|  | private static final int STATE_LOADING                = 1; | 
|  | private static final int STATE_LOADING_PLAY_REQUESTED = 2; | 
|  | private static final int STATE_LOADED                 = 3; | 
|  |  | 
|  | private class SoundState { | 
|  | public final int name; | 
|  | public int id; | 
|  | public int state; | 
|  |  | 
|  | public SoundState(int name) { | 
|  | this.name = name; | 
|  | id = 0; // 0 is an invalid sample ID. | 
|  | state = STATE_NOT_LOADED; | 
|  | } | 
|  | } | 
|  | /** | 
|  | * Construct a new MediaActionSound instance. Only a single instance is | 
|  | * needed for playing any platform media action sound; you do not need a | 
|  | * separate instance for each sound type. | 
|  | */ | 
|  | public MediaActionSound() { | 
|  | mSoundPool = new SoundPool.Builder() | 
|  | .setMaxStreams(NUM_MEDIA_SOUND_STREAMS) | 
|  | .setAudioAttributes(new AudioAttributes.Builder() | 
|  | .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) | 
|  | .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) | 
|  | .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) | 
|  | .build()) | 
|  | .build(); | 
|  | mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener); | 
|  | mSounds = new SoundState[SOUND_FILES.length]; | 
|  | for (int i = 0; i < mSounds.length; i++) { | 
|  | mSounds[i] = new SoundState(i); | 
|  | } | 
|  | } | 
|  |  | 
|  | private int loadSound(SoundState sound) { | 
|  | int id = mSoundPool.load(SOUND_FILES[sound.name], 1); | 
|  | if (id > 0) { | 
|  | sound.state = STATE_LOADING; | 
|  | sound.id = id; | 
|  | } | 
|  | return id; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Preload a predefined platform sound to minimize latency when the sound is | 
|  | * played later by {@link #play}. | 
|  | * @param soundName The type of sound to preload, selected from | 
|  | *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or | 
|  | *         STOP_VIDEO_RECORDING. | 
|  | * @see #play | 
|  | * @see #SHUTTER_CLICK | 
|  | * @see #FOCUS_COMPLETE | 
|  | * @see #START_VIDEO_RECORDING | 
|  | * @see #STOP_VIDEO_RECORDING | 
|  | */ | 
|  | public void load(int soundName) { | 
|  | if (soundName < 0 || soundName >= SOUND_FILES.length) { | 
|  | throw new RuntimeException("Unknown sound requested: " + soundName); | 
|  | } | 
|  | SoundState sound = mSounds[soundName]; | 
|  | synchronized (sound) { | 
|  | switch (sound.state) { | 
|  | case STATE_NOT_LOADED: | 
|  | if (loadSound(sound) <= 0) { | 
|  | Log.e(TAG, "load() error loading sound: " + soundName); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | Log.e(TAG, "load() called in wrong state: " + sound + " for sound: "+ soundName); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * <p>Play one of the predefined platform sounds for media actions.</p> | 
|  | * | 
|  | * <p>Use this method to play a platform-specific sound for various media | 
|  | * actions. The sound playback is done asynchronously, with the same | 
|  | * behavior and content as the sounds played by | 
|  | * {@link android.hardware.Camera#takePicture Camera.takePicture}, | 
|  | * {@link android.media.MediaRecorder#start MediaRecorder.start}, and | 
|  | * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p> | 
|  | * | 
|  | * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play | 
|  | * standard camera operation sounds with the appropriate system behavior for such sounds.</p> | 
|  |  | 
|  | * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to | 
|  | * match the default device sounds when recording or capturing data through the preview | 
|  | * callbacks, or when implementing custom camera-like features in your application.</p> | 
|  | * | 
|  | * <p>If the sound has not been loaded by {@link #load} before calling play, | 
|  | * play will load the sound at the cost of some additional latency before | 
|  | * sound playback begins. </p> | 
|  | * | 
|  | * @param soundName The type of sound to play, selected from | 
|  | *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or | 
|  | *         STOP_VIDEO_RECORDING. | 
|  | * @see android.hardware.Camera#takePicture | 
|  | * @see android.media.MediaRecorder | 
|  | * @see #SHUTTER_CLICK | 
|  | * @see #FOCUS_COMPLETE | 
|  | * @see #START_VIDEO_RECORDING | 
|  | * @see #STOP_VIDEO_RECORDING | 
|  | */ | 
|  | public void play(int soundName) { | 
|  | if (soundName < 0 || soundName >= SOUND_FILES.length) { | 
|  | throw new RuntimeException("Unknown sound requested: " + soundName); | 
|  | } | 
|  | SoundState sound = mSounds[soundName]; | 
|  | synchronized (sound) { | 
|  | switch (sound.state) { | 
|  | case STATE_NOT_LOADED: | 
|  | loadSound(sound); | 
|  | if (loadSound(sound) <= 0) { | 
|  | Log.e(TAG, "play() error loading sound: " + soundName); | 
|  | break; | 
|  | } | 
|  | // FALL THROUGH | 
|  |  | 
|  | case STATE_LOADING: | 
|  | sound.state = STATE_LOADING_PLAY_REQUESTED; | 
|  | break; | 
|  | case STATE_LOADED: | 
|  | mSoundPool.play(sound.id, 1.0f, 1.0f, 0, 0, 1.0f); | 
|  | break; | 
|  | default: | 
|  | Log.e(TAG, "play() called in wrong state: " + sound.state + " for sound: "+ soundName); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private SoundPool.OnLoadCompleteListener mLoadCompleteListener = | 
|  | new SoundPool.OnLoadCompleteListener() { | 
|  | public void onLoadComplete(SoundPool soundPool, | 
|  | int sampleId, int status) { | 
|  | for (SoundState sound : mSounds) { | 
|  | if (sound.id != sampleId) { | 
|  | continue; | 
|  | } | 
|  | int playSoundId = 0; | 
|  | synchronized (sound) { | 
|  | if (status != 0) { | 
|  | sound.state = STATE_NOT_LOADED; | 
|  | sound.id = 0; | 
|  | Log.e(TAG, "OnLoadCompleteListener() error: " + status + | 
|  | " loading sound: "+ sound.name); | 
|  | return; | 
|  | } | 
|  | switch (sound.state) { | 
|  | case STATE_LOADING: | 
|  | sound.state = STATE_LOADED; | 
|  | break; | 
|  | case STATE_LOADING_PLAY_REQUESTED: | 
|  | playSoundId = sound.id; | 
|  | sound.state = STATE_LOADED; | 
|  | break; | 
|  | default: | 
|  | Log.e(TAG, "OnLoadCompleteListener() called in wrong state: " | 
|  | + sound.state + " for sound: "+ sound.name); | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (playSoundId != 0) { | 
|  | soundPool.play(playSoundId, 1.0f, 1.0f, 0, 0, 1.0f); | 
|  | } | 
|  | break; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * Free up all audio resources used by this MediaActionSound instance. Do | 
|  | * not call any other methods on a MediaActionSound instance after calling | 
|  | * release(). | 
|  | */ | 
|  | public void release() { | 
|  | if (mSoundPool != null) { | 
|  | for (SoundState sound : mSounds) { | 
|  | synchronized (sound) { | 
|  | sound.state = STATE_NOT_LOADED; | 
|  | sound.id = 0; | 
|  | } | 
|  | } | 
|  | mSoundPool.release(); | 
|  | mSoundPool = null; | 
|  | } | 
|  | } | 
|  | } |