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 import android.util.Log; 26 27 import com.android.camera2.R; 28 import com.android.camera.util.ApiHelper; 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 String TAG = "MediaActionSoundPlayer"; 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 String 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 }; 118 119 // ID returned by load() should be non-zero. 120 private static final int ID_NOT_LOADED = 0; 121 122 // Maps a sound action to the id; 123 private final int[] mSoundRes = {0, 1, 1, 1}; 124 // Store the context for lazy loading. 125 private Context mContext; 126 // mSoundPool is created every time load() is called and cleared every 127 // time release() is called. 128 private SoundPool mSoundPool; 129 // Sound ID of each sound resources. Given when the sound is loaded. 130 private final int[] mSoundIDs; 131 private final boolean[] mSoundIDReady; 132 private int mSoundIDToPlay; 133 134 public SoundPoolPlayer(Context context) { 135 mContext = context; 136 137 mSoundIDToPlay = ID_NOT_LOADED; 138 139 mSoundPool = new SoundPool(NUM_SOUND_STREAMS, getAudioTypeForSoundPool(), 0); 140 mSoundPool.setOnLoadCompleteListener(this); 141 142 mSoundIDs = new int[SOUND_RES.length]; 143 mSoundIDReady = new boolean[SOUND_RES.length]; 144 for (int i = 0; i < SOUND_RES.length; i++) { 145 mSoundIDs[i] = mSoundPool.load(mContext, SOUND_RES[i], 1); 146 mSoundIDReady[i] = false; 147 } 148 } 149 150 @Override 151 public synchronized void release() { 152 if (mSoundPool != null) { 153 mSoundPool.release(); 154 mSoundPool = null; 155 } 156 } 157 158 @Override 159 public synchronized void play(int action) { 160 if (action < 0 || action >= mSoundRes.length) { 161 Log.e(TAG, "Resource ID not found for action:" + action + " in play()."); 162 return; 163 } 164 165 int index = mSoundRes[action]; 166 if (mSoundIDs[index] == ID_NOT_LOADED) { 167 // Not loaded yet, load first and then play when the loading is complete. 168 mSoundIDs[index] = mSoundPool.load(mContext, SOUND_RES[index], 1); 169 mSoundIDToPlay = mSoundIDs[index]; 170 } else if (!mSoundIDReady[index]) { 171 // Loading and not ready yet. 172 mSoundIDToPlay = mSoundIDs[index]; 173 } else { 174 mSoundPool.play(mSoundIDs[index], 1f, 1f, 0, 0, 1f); 175 } 176 } 177 178 @Override 179 public void onLoadComplete(SoundPool pool, int soundID, int status) { 180 if (status != 0) { 181 Log.e(TAG, "loading sound tracks failed (status=" + status + ")"); 182 for (int i = 0; i < mSoundIDs.length; i++ ) { 183 if (mSoundIDs[i] == soundID) { 184 mSoundIDs[i] = ID_NOT_LOADED; 185 break; 186 } 187 } 188 return; 189 } 190 191 for (int i = 0; i < mSoundIDs.length; i++ ) { 192 if (mSoundIDs[i] == soundID) { 193 mSoundIDReady[i] = true; 194 break; 195 } 196 } 197 198 if (soundID == mSoundIDToPlay) { 199 mSoundIDToPlay = ID_NOT_LOADED; 200 mSoundPool.play(soundID, 1f, 1f, 0, 0, 1f); 201 } 202 } 203 } 204 } 205