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