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 com.android.camera; 18 19 import android.annotation.TargetApi; 20 import android.content.Context; 21 import android.media.AudioManager; 22 import android.media.MediaActionSound; 23 import android.media.SoundPool; 24 import android.os.Build; 25 26 import com.android.camera.debug.Log; 27 import com.android.camera.util.ApiHelper; 28 import com.android.camera2.R; 29 30 /* 31 * This class controls the sound playback according to the API level. 32 */ 33 public class SoundClips { 34 // Sound actions. 35 public static final int FOCUS_COMPLETE = 0; 36 public static final int START_VIDEO_RECORDING = 1; 37 public static final int STOP_VIDEO_RECORDING = 2; 38 public static final int SHUTTER_CLICK = 3; 39 40 public interface Player { 41 public void release(); 42 public void play(int action); 43 } 44 45 public static Player getPlayer(Context context) { 46 if (ApiHelper.HAS_MEDIA_ACTION_SOUND) { 47 return new MediaActionSoundPlayer(); 48 } else { 49 return new SoundPoolPlayer(context); 50 } 51 } 52 53 public static int getAudioTypeForSoundPool() { 54 // STREAM_SYSTEM_ENFORCED is hidden API. 55 return ApiHelper.getIntFieldIfExists(AudioManager.class, 56 "STREAM_SYSTEM_ENFORCED", null, AudioManager.STREAM_RING); 57 } 58 59 /** 60 * This class implements SoundClips.Player using MediaActionSound, 61 * which exists since API level 16. 62 */ 63 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 64 private static class MediaActionSoundPlayer implements Player { 65 private static final Log.Tag TAG = new Log.Tag("MediaActSndPlayer"); 66 private MediaActionSound mSound; 67 68 @Override 69 public void release() { 70 if (mSound != null) { 71 mSound.release(); 72 mSound = null; 73 } 74 } 75 76 public MediaActionSoundPlayer() { 77 mSound = new MediaActionSound(); 78 mSound.load(MediaActionSound.START_VIDEO_RECORDING); 79 mSound.load(MediaActionSound.STOP_VIDEO_RECORDING); 80 mSound.load(MediaActionSound.FOCUS_COMPLETE); 81 mSound.load(MediaActionSound.SHUTTER_CLICK); 82 } 83 84 @Override 85 public synchronized void play(int action) { 86 switch(action) { 87 case FOCUS_COMPLETE: 88 mSound.play(MediaActionSound.FOCUS_COMPLETE); 89 break; 90 case START_VIDEO_RECORDING: 91 mSound.play(MediaActionSound.START_VIDEO_RECORDING); 92 break; 93 case STOP_VIDEO_RECORDING: 94 mSound.play(MediaActionSound.STOP_VIDEO_RECORDING); 95 break; 96 case SHUTTER_CLICK: 97 mSound.play(MediaActionSound.SHUTTER_CLICK); 98 break; 99 default: 100 Log.w(TAG, "Unrecognized action:" + action); 101 } 102 } 103 } 104 105 /** 106 * This class implements SoundClips.Player using SoundPool, which 107 * exists since API level 1. 108 */ 109 private static class SoundPoolPlayer implements 110 Player, SoundPool.OnLoadCompleteListener { 111 112 private static final Log.Tag TAG = new Log.Tag("SoundPoolPlayer"); 113 private static final int NUM_SOUND_STREAMS = 1; 114 private static final int[] SOUND_RES = { // Soundtrack res IDs. 115 R.raw.focus_complete, 116 R.raw.video_record, 117 R.raw.video_record, 118 R.raw.shutter 119 }; 120 121 // ID returned by load() should be non-zero. 122 private static final int ID_NOT_LOADED = 0; 123 124 // Maps a sound action to the id; 125 private final int[] mSoundRes = {0, 1, 2, 3}; 126 // Store the context for lazy loading. 127 private Context mContext; 128 // mSoundPool is created every time load() is called and cleared every 129 // time release() is called. 130 private SoundPool mSoundPool; 131 // Sound ID of each sound resources. Given when the sound is loaded. 132 private final int[] mSoundIDs; 133 private final boolean[] mSoundIDReady; 134 private int mSoundIDToPlay; 135 136 public SoundPoolPlayer(Context context) { 137 mContext = context; 138 139 mSoundIDToPlay = ID_NOT_LOADED; 140 141 mSoundPool = new SoundPool(NUM_SOUND_STREAMS, getAudioTypeForSoundPool(), 0); 142 mSoundPool.setOnLoadCompleteListener(this); 143 144 mSoundIDs = new int[SOUND_RES.length]; 145 mSoundIDReady = new boolean[SOUND_RES.length]; 146 for (int i = 0; i < SOUND_RES.length; i++) { 147 mSoundIDs[i] = mSoundPool.load(mContext, SOUND_RES[i], 1); 148 mSoundIDReady[i] = false; 149 } 150 } 151 152 @Override 153 public synchronized void release() { 154 if (mSoundPool != null) { 155 mSoundPool.release(); 156 mSoundPool = null; 157 } 158 } 159 160 @Override 161 public synchronized void play(int action) { 162 if (action < 0 || action >= mSoundRes.length) { 163 Log.e(TAG, "Resource ID not found for action:" + action + " in play()."); 164 return; 165 } 166 167 int index = mSoundRes[action]; 168 if (mSoundIDs[index] == ID_NOT_LOADED) { 169 // Not loaded yet, load first and then play when the loading is complete. 170 mSoundIDs[index] = mSoundPool.load(mContext, SOUND_RES[index], 1); 171 mSoundIDToPlay = mSoundIDs[index]; 172 } else if (!mSoundIDReady[index]) { 173 // Loading and not ready yet. 174 mSoundIDToPlay = mSoundIDs[index]; 175 } else { 176 mSoundPool.play(mSoundIDs[index], 1f, 1f, 0, 0, 1f); 177 } 178 } 179 180 @Override 181 public void onLoadComplete(SoundPool pool, int soundID, int status) { 182 if (status != 0) { 183 Log.e(TAG, "loading sound tracks failed (status=" + status + ")"); 184 for (int i = 0; i < mSoundIDs.length; i++ ) { 185 if (mSoundIDs[i] == soundID) { 186 mSoundIDs[i] = ID_NOT_LOADED; 187 break; 188 } 189 } 190 return; 191 } 192 193 for (int i = 0; i < mSoundIDs.length; i++ ) { 194 if (mSoundIDs[i] == soundID) { 195 mSoundIDReady[i] = true; 196 break; 197 } 198 } 199 200 if (soundID == mSoundIDToPlay) { 201 mSoundIDToPlay = ID_NOT_LOADED; 202 mSoundPool.play(soundID, 1f, 1f, 0, 0, 1f); 203 } 204 } 205 } 206 } 207