1 /* 2 * Copyright (C) 2011 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.soundrecorder; 18 19 import java.io.File; 20 import java.io.IOException; 21 22 import android.content.Context; 23 import android.media.AudioManager; 24 import android.media.MediaPlayer; 25 import android.media.MediaRecorder; 26 import android.media.MediaPlayer.OnCompletionListener; 27 import android.media.MediaPlayer.OnErrorListener; 28 import android.os.Bundle; 29 import android.os.Environment; 30 import android.util.Log; 31 32 public class Recorder implements OnCompletionListener, OnErrorListener { 33 static final String SAMPLE_PREFIX = "recording"; 34 static final String SAMPLE_PATH_KEY = "sample_path"; 35 static final String SAMPLE_LENGTH_KEY = "sample_length"; 36 37 public static final int IDLE_STATE = 0; 38 public static final int RECORDING_STATE = 1; 39 public static final int PLAYING_STATE = 2; 40 41 int mState = IDLE_STATE; 42 43 public static final int NO_ERROR = 0; 44 public static final int SDCARD_ACCESS_ERROR = 1; 45 public static final int INTERNAL_ERROR = 2; 46 public static final int IN_CALL_RECORD_ERROR = 3; 47 48 public interface OnStateChangedListener { onStateChanged(int state)49 public void onStateChanged(int state); onError(int error)50 public void onError(int error); 51 } 52 OnStateChangedListener mOnStateChangedListener = null; 53 54 long mSampleStart = 0; // time at which latest record or play operation started 55 int mSampleLength = 0; // length of current sample 56 File mSampleFile = null; 57 58 MediaRecorder mRecorder = null; 59 MediaPlayer mPlayer = null; 60 Recorder()61 public Recorder() { 62 } 63 saveState(Bundle recorderState)64 public void saveState(Bundle recorderState) { 65 recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath()); 66 recorderState.putInt(SAMPLE_LENGTH_KEY, mSampleLength); 67 } 68 getMaxAmplitude()69 public int getMaxAmplitude() { 70 if (mState != RECORDING_STATE) 71 return 0; 72 return mRecorder.getMaxAmplitude(); 73 } 74 restoreState(Bundle recorderState)75 public void restoreState(Bundle recorderState) { 76 String samplePath = recorderState.getString(SAMPLE_PATH_KEY); 77 if (samplePath == null) 78 return; 79 int sampleLength = recorderState.getInt(SAMPLE_LENGTH_KEY, -1); 80 if (sampleLength == -1) 81 return; 82 83 File file = new File(samplePath); 84 if (!file.exists()) 85 return; 86 if (mSampleFile != null 87 && mSampleFile.getAbsolutePath().compareTo(file.getAbsolutePath()) == 0) 88 return; 89 90 delete(); 91 mSampleFile = file; 92 mSampleLength = sampleLength; 93 94 signalStateChanged(IDLE_STATE); 95 } 96 setOnStateChangedListener(OnStateChangedListener listener)97 public void setOnStateChangedListener(OnStateChangedListener listener) { 98 mOnStateChangedListener = listener; 99 } 100 state()101 public int state() { 102 return mState; 103 } 104 progress()105 public int progress() { 106 if (mState == RECORDING_STATE || mState == PLAYING_STATE) 107 return (int) ((System.currentTimeMillis() - mSampleStart)/1000); 108 return 0; 109 } 110 sampleLength()111 public int sampleLength() { 112 return mSampleLength; 113 } 114 sampleFile()115 public File sampleFile() { 116 return mSampleFile; 117 } 118 119 /** 120 * Resets the recorder state. If a sample was recorded, the file is deleted. 121 */ delete()122 public void delete() { 123 stop(); 124 125 if (mSampleFile != null) 126 mSampleFile.delete(); 127 128 mSampleFile = null; 129 mSampleLength = 0; 130 131 signalStateChanged(IDLE_STATE); 132 } 133 134 /** 135 * Resets the recorder state. If a sample was recorded, the file is left on disk and will 136 * be reused for a new recording. 137 */ clear()138 public void clear() { 139 stop(); 140 141 mSampleLength = 0; 142 143 signalStateChanged(IDLE_STATE); 144 } 145 startRecording(int outputfileformat, String extension, Context context)146 public void startRecording(int outputfileformat, String extension, Context context) { 147 stop(); 148 149 if (mSampleFile == null) { 150 File sampleDir = Environment.getExternalStorageDirectory(); 151 if (!sampleDir.canWrite()) // Workaround for broken sdcard support on the device. 152 sampleDir = new File("/sdcard/sdcard"); 153 154 try { 155 mSampleFile = File.createTempFile(SAMPLE_PREFIX, extension, sampleDir); 156 } catch (IOException e) { 157 setError(SDCARD_ACCESS_ERROR); 158 return; 159 } 160 } 161 162 mRecorder = new MediaRecorder(); 163 mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 164 mRecorder.setOutputFormat(outputfileformat); 165 mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 166 mRecorder.setOutputFile(mSampleFile.getAbsolutePath()); 167 168 // Handle IOException 169 try { 170 mRecorder.prepare(); 171 } catch(IOException exception) { 172 setError(INTERNAL_ERROR); 173 mRecorder.reset(); 174 mRecorder.release(); 175 mRecorder = null; 176 return; 177 } 178 // Handle RuntimeException if the recording couldn't start 179 try { 180 mRecorder.start(); 181 } catch (RuntimeException exception) { 182 AudioManager audioMngr = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 183 boolean isInCall = ((audioMngr.getMode() == AudioManager.MODE_IN_CALL) || 184 (audioMngr.getMode() == AudioManager.MODE_IN_COMMUNICATION)); 185 if (isInCall) { 186 setError(IN_CALL_RECORD_ERROR); 187 } else { 188 setError(INTERNAL_ERROR); 189 } 190 mRecorder.reset(); 191 mRecorder.release(); 192 mRecorder = null; 193 return; 194 } 195 mSampleStart = System.currentTimeMillis(); 196 setState(RECORDING_STATE); 197 } 198 stopRecording()199 public void stopRecording() { 200 if (mRecorder == null) 201 return; 202 203 try { 204 mRecorder.stop(); 205 } catch (RuntimeException e) { 206 Log.w("Recorder", "Catch RuntimeException on MediaRecorder.stop() due to a call " + 207 "immediately after MediaRecorder.start()."); 208 } 209 mRecorder.release(); 210 mRecorder = null; 211 212 mSampleLength = (int)( (System.currentTimeMillis() - mSampleStart)/1000 ); 213 setState(IDLE_STATE); 214 } 215 startPlayback()216 public void startPlayback() { 217 stop(); 218 219 mPlayer = new MediaPlayer(); 220 try { 221 mPlayer.setDataSource(mSampleFile.getAbsolutePath()); 222 mPlayer.setOnCompletionListener(this); 223 mPlayer.setOnErrorListener(this); 224 mPlayer.prepare(); 225 mPlayer.start(); 226 } catch (IllegalArgumentException e) { 227 setError(INTERNAL_ERROR); 228 mPlayer = null; 229 return; 230 } catch (IOException e) { 231 setError(SDCARD_ACCESS_ERROR); 232 mPlayer = null; 233 return; 234 } 235 236 mSampleStart = System.currentTimeMillis(); 237 setState(PLAYING_STATE); 238 } 239 stopPlayback()240 public void stopPlayback() { 241 if (mPlayer == null) // we were not in playback 242 return; 243 244 mPlayer.stop(); 245 mPlayer.release(); 246 mPlayer = null; 247 setState(IDLE_STATE); 248 } 249 stop()250 public void stop() { 251 stopRecording(); 252 stopPlayback(); 253 } 254 onError(MediaPlayer mp, int what, int extra)255 public boolean onError(MediaPlayer mp, int what, int extra) { 256 stop(); 257 setError(SDCARD_ACCESS_ERROR); 258 return true; 259 } 260 onCompletion(MediaPlayer mp)261 public void onCompletion(MediaPlayer mp) { 262 stop(); 263 } 264 setState(int state)265 private void setState(int state) { 266 if (state == mState) 267 return; 268 269 mState = state; 270 signalStateChanged(mState); 271 } 272 signalStateChanged(int state)273 private void signalStateChanged(int state) { 274 if (mOnStateChangedListener != null) 275 mOnStateChangedListener.onStateChanged(state); 276 } 277 setError(int error)278 private void setError(int error) { 279 if (mOnStateChangedListener != null) 280 mOnStateChangedListener.onError(error); 281 } 282 } 283