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
17package android.media;
18
19import android.media.AudioManager;
20import android.media.SoundPool;
21import 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 */
44public 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
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     */
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
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     */
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     */
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     */
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