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 int[]     mSoundIds;
49     private int       mSoundIdToPlay;
50 
51     private static final String[] SOUND_FILES = {
52         "/system/media/audio/ui/camera_click.ogg",
53         "/system/media/audio/ui/camera_focus.ogg",
54         "/system/media/audio/ui/VideoRecord.ogg",
55         "/system/media/audio/ui/VideoRecord.ogg"
56     };
57 
58     private static final String TAG = "MediaActionSound";
59     /**
60      * The sound used by
61      * {@link android.hardware.Camera#takePicture Camera.takePicture} to
62      * indicate still image capture.
63      * @see #play
64      */
65     public static final int SHUTTER_CLICK         = 0;
66 
67     /**
68      * A sound to indicate that focusing has completed. Because deciding
69      * when this occurs is application-dependent, this sound is not used by
70      * any methods in the media or camera APIs.
71      * @see #play
72      */
73     public static final int FOCUS_COMPLETE        = 1;
74 
75     /**
76      * The sound used by
77      * {@link android.media.MediaRecorder#start MediaRecorder.start()} to
78      * indicate the start of video recording.
79      * @see #play
80      */
81     public static final int START_VIDEO_RECORDING = 2;
82 
83     /**
84      * The sound used by
85      * {@link android.media.MediaRecorder#stop MediaRecorder.stop()} to
86      * indicate the end of video recording.
87      * @see #play
88      */
89     public static final int STOP_VIDEO_RECORDING  = 3;
90 
91     private static final int SOUND_NOT_LOADED = -1;
92 
93     /**
94      * Construct a new MediaActionSound instance. Only a single instance is
95      * needed for playing any platform media action sound; you do not need a
96      * separate instance for each sound type.
97      */
MediaActionSound()98     public MediaActionSound() {
99         mSoundPool = new SoundPool(NUM_MEDIA_SOUND_STREAMS,
100                 AudioManager.STREAM_SYSTEM_ENFORCED, 0);
101         mSoundPool.setOnLoadCompleteListener(mLoadCompleteListener);
102         mSoundIds = new int[SOUND_FILES.length];
103         for (int i = 0; i < mSoundIds.length; i++) {
104             mSoundIds[i] = SOUND_NOT_LOADED;
105         }
106         mSoundIdToPlay = SOUND_NOT_LOADED;
107     }
108 
109     /**
110      * Preload a predefined platform sound to minimize latency when the sound is
111      * played later by {@link #play}.
112      * @param soundName The type of sound to preload, selected from
113      *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
114      *         STOP_VIDEO_RECORDING.
115      * @see #play
116      * @see #SHUTTER_CLICK
117      * @see #FOCUS_COMPLETE
118      * @see #START_VIDEO_RECORDING
119      * @see #STOP_VIDEO_RECORDING
120      */
load(int soundName)121     public synchronized void load(int soundName) {
122         if (soundName < 0 || soundName >= SOUND_FILES.length) {
123             throw new RuntimeException("Unknown sound requested: " + soundName);
124         }
125         if (mSoundIds[soundName] == SOUND_NOT_LOADED) {
126             mSoundIds[soundName] =
127                     mSoundPool.load(SOUND_FILES[soundName], 1);
128         }
129     }
130 
131     /**
132      * <p>Play one of the predefined platform sounds for media actions.</p>
133      *
134      * <p>Use this method to play a platform-specific sound for various media
135      * actions. The sound playback is done asynchronously, with the same
136      * behavior and content as the sounds played by
137      * {@link android.hardware.Camera#takePicture Camera.takePicture},
138      * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
139      * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
140      *
141      * <p>With the {@link android.hardware.camera2 camera2} API, this method can be used to play
142      * standard camera operation sounds with the appropriate system behavior for such sounds.</p>
143 
144      * <p>With the older {@link android.hardware.Camera} API, using this method makes it easy to
145      * match the default device sounds when recording or capturing data through the preview
146      * callbacks, or when implementing custom camera-like features in your application.</p>
147      *
148      * <p>If the sound has not been loaded by {@link #load} before calling play,
149      * play will load the sound at the cost of some additional latency before
150      * sound playback begins. </p>
151      *
152      * @param soundName The type of sound to play, selected from
153      *         SHUTTER_CLICK, FOCUS_COMPLETE, START_VIDEO_RECORDING, or
154      *         STOP_VIDEO_RECORDING.
155      * @see android.hardware.Camera#takePicture
156      * @see android.media.MediaRecorder
157      * @see #SHUTTER_CLICK
158      * @see #FOCUS_COMPLETE
159      * @see #START_VIDEO_RECORDING
160      * @see #STOP_VIDEO_RECORDING
161      */
play(int soundName)162     public synchronized void play(int soundName) {
163         if (soundName < 0 || soundName >= SOUND_FILES.length) {
164             throw new RuntimeException("Unknown sound requested: " + soundName);
165         }
166         if (mSoundIds[soundName] == SOUND_NOT_LOADED) {
167             mSoundIdToPlay =
168                     mSoundPool.load(SOUND_FILES[soundName], 1);
169             mSoundIds[soundName] = mSoundIdToPlay;
170         } else {
171             mSoundPool.play(mSoundIds[soundName], 1.0f, 1.0f, 0, 0, 1.0f);
172         }
173     }
174 
175     private SoundPool.OnLoadCompleteListener mLoadCompleteListener =
176             new SoundPool.OnLoadCompleteListener() {
177         public void onLoadComplete(SoundPool soundPool,
178                 int sampleId, int status) {
179             if (status == 0) {
180                 if (mSoundIdToPlay == sampleId) {
181                     soundPool.play(sampleId, 1.0f, 1.0f, 0, 0, 1.0f);
182                     mSoundIdToPlay = SOUND_NOT_LOADED;
183                 }
184             } else {
185                 Log.e(TAG, "Unable to load sound for playback (status: " +
186                         status + ")");
187             }
188         }
189     };
190 
191     /**
192      * Free up all audio resources used by this MediaActionSound instance. Do
193      * not call any other methods on a MediaActionSound instance after calling
194      * release().
195      */
release()196     public void release() {
197         if (mSoundPool != null) {
198             mSoundPool.release();
199             mSoundPool = null;
200         }
201     }
202 }
203