blob: 70b32d73942eb170f366ba2187785457efb87ae9 [file] [log] [blame]
/*
* Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
* Not a Contribution.
*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.soundrecorder;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.os.Handler;
import android.os.Message;
import com.android.soundrecorder.util.FileUtils;
import com.android.soundrecorder.util.StorageUtils;
public class Recorder implements MediaRecorder.OnInfoListener {
static final String TAG = "Recorder";
static final String SAMPLE_PREFIX = "recording";
static final String SAMPLE_PATH_KEY = "sample_path";
static final String SAMPLE_LENGTH_KEY = "sample_length";
public static final int IDLE_STATE = 0;
public static final int RECORDING_STATE = 1;
public static final int PAUSE_STATE = 2;
int mState = IDLE_STATE;
public static final int NO_ERROR = 0;
public static final int SDCARD_ACCESS_ERROR = 1;
public static final int INTERNAL_ERROR = 2;
public static final int IN_CALL_RECORD_ERROR = 3;
public static final int UNSUPPORTED_FORMAT = 4;
public static final int RECORD_INTERRUPTED = 5;
public static final int RECORD_LOST_FOCUS = 6;
static final int FOCUSCHANGE = 0;
public int mChannels = 0;
public int mSamplingRate = 0;
private int mBitRate = 0;
public String mStoragePath = null;
private boolean mRequestAudioFocus = true;
private int mMaxDuration;
public interface OnStateChangedListener {
public void onStateChanged(int state);
public void onError(int error);
public void onInfo(int what, int extra);
}
OnStateChangedListener mOnStateChangedListener = null;
MediaRecorder.OnErrorListener mMRErrorListener = new MediaRecorder.OnErrorListener() {
public void onError(MediaRecorder mr, int what, int extra) {
stop();
setError(RECORD_INTERRUPTED);
}
};
long mSampleStart = 0; // time at which latest record or play operation started
long mSampleLength = 0; // length of current sample
File mSampleFile = null;
MediaRecorder mRecorder = null;
private AudioManager mAudioManager;
Context mContext = null;
public Recorder(Context context) {
if (context.getResources().getBoolean(R.bool.config_storage_path)) {
mStoragePath = StorageUtils.applyCustomStoragePath(context);
} else {
mStoragePath = StorageUtils.getPhoneStoragePath();
}
mContext = context;
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
}
public Recorder() {
}
public void saveState(Bundle recorderState) {
recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath());
recorderState.putLong(SAMPLE_LENGTH_KEY, mSampleLength);
}
public int getMaxAmplitude() {
if (mState != RECORDING_STATE)
return 0;
return mRecorder.getMaxAmplitude();
}
public void restoreState(Bundle recorderState) {
String samplePath = recorderState.getString(SAMPLE_PATH_KEY);
if (samplePath == null)
return;
long sampleLength = recorderState.getLong(SAMPLE_LENGTH_KEY, -1);
if (sampleLength == -1)
return;
File file = new File(samplePath);
if (!file.exists())
return;
if (mSampleFile != null
&& mSampleFile.getAbsolutePath().compareTo(file.getAbsolutePath()) == 0)
return;
delete();
mSampleFile = file;
mSampleLength = sampleLength;
signalStateChanged(IDLE_STATE);
}
public void setOnStateChangedListener(OnStateChangedListener listener) {
mOnStateChangedListener = listener;
}
public void setChannels(int nChannelsCount) {
mChannels = nChannelsCount;
}
public void setSamplingRate(int samplingRate) {
mSamplingRate = samplingRate;
}
public void setAudioEncodingBitRate(int bitRate) {
mBitRate = bitRate;
}
public int state() {
return mState;
}
public int progress() {
if (mState == RECORDING_STATE) {
return (int) ((mSampleLength + (System.currentTimeMillis() - mSampleStart)) / 1000);
}
return 0;
}
public int sampleLength() {
return (int) (mSampleLength / 1000);
}
public long sampleLengthMillis() {
return mSampleLength;
}
public File sampleFile() {
return mSampleFile;
}
public void renameSampleFile(String newName) {
mSampleFile = FileUtils.renameFile(mSampleFile, newName);
}
/**
* Resets the recorder state. If a sample was recorded, the file is deleted.
*/
public void delete() {
stop();
if (mSampleFile != null){
mSampleFile.delete();
}
mSampleFile = null;
mSampleLength = 0;
signalStateChanged(IDLE_STATE);
}
/**
* Resets the recorder state. If a sample was recorded, the file is left on disk and will
* be reused for a new recording.
*/
public void clear() {
stop();
mSampleFile = null;
mSampleLength = 0;
signalStateChanged(IDLE_STATE);
}
/**
* Resets the recorder state before prepared. If a sample was recorded, the file is deleted.
*/
public void release(int err) {
releaseRecording(err);
if (mSampleFile != null){
mSampleFile.delete();
}
mSampleFile = null;
mSampleLength = 0;
signalStateChanged(IDLE_STATE);
}
public void startRecording(int outputfileformat, String extension,
Context context, int audiosourcetype, int codectype) {
stop();
if (mSampleFile != null) {
mSampleFile.delete();
mSampleFile = null;
mSampleLength = 0;
}
File sampleDir = new File(mStoragePath);
if (!sampleDir.exists()) {
sampleDir.mkdirs();
}
if (!sampleDir.canWrite()) // Workaround for broken sdcard support on the device.
sampleDir = new File(StorageUtils.getSdStoragePath(context));
try {
String prefix = context.getResources().getString(R.string.def_save_name_prefix);
if (!"".equals(prefix)) {
//long index = FileUtils.getSuitableIndexOfRecording(prefix);
//mSampleFile = createTempFile(prefix, Long.toString(index), extension, sampleDir);
mSampleFile = createTempFile(context, prefix+"-", extension, sampleDir);
} else {
prefix = SAMPLE_PREFIX + '-';
mSampleFile = createTempFile(context, prefix, extension, sampleDir);
}
} catch (IOException e) {
setError(SDCARD_ACCESS_ERROR);
return;
}
mRecorder = new MediaRecorder();
try {
mRecorder.setAudioSource(audiosourcetype);
} catch(RuntimeException exception) {
release(INTERNAL_ERROR);
return;
}
//set channel for surround sound recording.
if (mChannels > 0) {
mRecorder.setAudioChannels(mChannels);
}
if (mSamplingRate > 0) {
mRecorder.setAudioSamplingRate(mSamplingRate);
}
if (mBitRate > 0) {
mRecorder.setAudioEncodingBitRate(mBitRate);
}
try {
mRecorder.setOutputFormat(outputfileformat);
mRecorder.setOnErrorListener(mMRErrorListener);
mRecorder.setMaxDuration(mMaxDuration);
mRecorder.setOnInfoListener(this);
mRecorder.setAudioEncoder(codectype);
} catch(RuntimeException exception) {
release(UNSUPPORTED_FORMAT);
return;
}
// Handle IOException
try {
mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
mRecorder.prepare();
} catch(IOException | IllegalStateException exception) {
release(INTERNAL_ERROR);
return;
}
// Handle RuntimeException if the recording couldn't start
Log.d(TAG,"audiosourcetype " +audiosourcetype);
try {
mRecorder.start();
} catch (RuntimeException exception) {
AudioManager audioMngr = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
boolean isInCall = ((audioMngr.getMode() == AudioManager.MODE_IN_CALL) ||
(audioMngr.getMode() == AudioManager.MODE_IN_COMMUNICATION));
if (isInCall) {
setError(IN_CALL_RECORD_ERROR);
} else {
setError(INTERNAL_ERROR);
}
delete();
return;
}
mSampleStart = System.currentTimeMillis();
setState(RECORDING_STATE);
stopAudioPlayback();
}
public void pauseRecording() {
if (mRecorder == null) {
return;
}
try {
mRecorder.pause();
} catch (RuntimeException exception) {
setError(INTERNAL_ERROR);
Log.e(TAG, "Pause Failed");
}
mSampleLength = mSampleLength + (System.currentTimeMillis() - mSampleStart);
setState(PAUSE_STATE);
}
public void resumeRecording() {
if (mRecorder == null) {
return;
}
stopAudioPlayback();
try {
if(Build.VERSION.SDK_INT >= 23){
mRecorder.resume();
}else{
mRecorder.start();
}
} catch (RuntimeException exception) {
setError(INTERNAL_ERROR);
Log.e(TAG, "Resume Failed");
}
mSampleStart = System.currentTimeMillis();
setState(RECORDING_STATE);
}
public void stopRecording() {
if (mRecorder == null){
return;
}
try {
if ((PAUSE_STATE == mState) && (Build.VERSION.SDK_INT >= 23)){
resumeRecording();
setState(RECORDING_STATE);
}
mRecorder.stop();
}catch (RuntimeException exception){
if(sampleLength() > 1){
setError(INTERNAL_ERROR);
Log.e(TAG, "Stop Failed");
}else{
Log.e(TAG, "Quickly Stop Failed");
}
}
mRecorder.reset();
mRecorder.release();
mRecorder = null;
mChannels = 0;
mSamplingRate = 0;
if (mState == RECORDING_STATE) {
mSampleLength = mSampleLength + (System.currentTimeMillis() - mSampleStart);
}
setState(IDLE_STATE);
}
public void releaseRecording(int err) {
setError(err);
if (mRecorder == null){
return;
}
mRecorder.reset();
mRecorder.release();
mRecorder = null;
}
public void stop() {
stopRecording();
mAudioManager.abandonAudioFocus(mAudioFocusListener);
}
private void setState(int state) {
if (state == mState)
return;
mState = state;
signalStateChanged(mState);
}
private void signalStateChanged(int state) {
if (mOnStateChangedListener != null)
mOnStateChangedListener.onStateChanged(state);
}
private void setError(int error) {
if (mOnStateChangedListener != null)
mOnStateChangedListener.onError(error);
}
public void setStoragePath(String path) {
mStoragePath = path;
}
public File createTempFile(String prefix, String fileName, String suffix, File directory)
throws IOException {
// Force a prefix null check first
if (prefix.length() < 3) {
throw new IllegalArgumentException("prefix must be at least 3 characters");
}
if (suffix == null) {
suffix = ".tmp";
}
File tmpDirFile = directory;
if (tmpDirFile == null) {
String tmpDir = System.getProperty("java.io.tmpdir", ".");
tmpDirFile = new File(tmpDir);
}
File result;
do {
result = new File(tmpDirFile, prefix + fileName + suffix);
} while (!result.createNewFile());
return result;
}
public File createTempFile(Context context, String prefix, String suffix, File directory)
throws IOException {
String nameFormat = context.getResources().getString(R.string.def_save_name_format);
SimpleDateFormat df = new SimpleDateFormat(nameFormat);
String currentTime = df.format(System.currentTimeMillis());
if (!TextUtils.isEmpty(currentTime)) {
currentTime = currentTime.replaceAll("[\\\\*|\":<>/?]", "_").replaceAll(" ",
"\\\\" + " ");
}
return createTempFile(prefix, currentTime, suffix, directory);
}
public void setMaxDuration(int duration) {
mMaxDuration = duration;
}
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
if (mOnStateChangedListener != null) {
mOnStateChangedListener.onInfo(what, extra);
}
}
/*
* Make sure we're not recording music playing in the background, ask
* the MediaPlaybackService to pause playback.
*/
private void stopAudioPlayback() {
Log.i(TAG,"requestAudioFocus " + mRequestAudioFocus);
if (mRequestAudioFocus) {
AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
am.requestAudioFocus(mAudioFocusListener,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
}
public void requestAudioFocus(boolean state ) {
mRequestAudioFocus = state;
}
private OnAudioFocusChangeListener mAudioFocusListener =
new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
mRecorderHandler.obtainMessage(FOCUSCHANGE, focusChange, 0)
.sendToTarget();
}
};
private Handler mRecorderHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case FOCUSCHANGE:
switch (msg.arg1) {
case AudioManager.AUDIOFOCUS_LOSS:
if (state() == Recorder.RECORDING_STATE) {
stop();
setError(RECORD_LOST_FOCUS);
}
break;
default:
break;
}
break;
default:
break;
}
}
};
}