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