• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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