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