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 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     */
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     */
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     */
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     */
196    public void release() {
197        if (mSoundPool != null) {
198            mSoundPool.release();
199            mSoundPool = null;
200        }
201    }
202}
203