一.场景
如果android api小于24,MediaRecorder不支持暂停功能,如果想实现暂停功能,要借用ffmpeg的相关功能。
二.目的
实现一个兼容新老版本的MediaRecorder
三.UML图
3.1 接口说明
抽象出接口MediaRecordInterface
具有start,pause,resume,release等抽象方法
ContactAudioListener接口用于监听临时录音文件的开始拼接和结束拼接
3.2 MediaRecordNew
因为新版api中,MediaRecorder具有暂停功能,所以直接使用系统自带的api即可
3.4 MedailRecordNew
因为旧版api中,MediaRecorder不具有暂停功能,所以要使用ffmpeg
实现概讲
3.4.1 pauseRecord
此方法调用MediaRecorder的stop方法,将录音存储到文件
3.4.2 resumeRecord
此方法调用MediaRecorder的start方法,重新开始录音
3.4.3 stopRecord
此方法将stopRecord产生的临时文件,利用ffmpeg拼接在一起
四.代码实现
4.1 MediaRecordInterface
public interface MediaRecordInterface {
int CONTACT_SUCCESS =0;
int CONTACT_FAIL=-1;
interface ContactAudioListener {
void onBegin();
void onEnd(int contactRes, File recordFile);
}
void setContactAudioListener(ContactAudioListener contactAudioListener);
/**
* 创建默认的录音对象
*
* 文件名的格式为 自己需要的目录/文件名(文件名不要写后缀)
*
* @param fileName 文件名
*/
void createAudio(String parentDir, String fileName);
/**
* 开始录音,与stopRecord相对应
*/
void startRecord();
/**
* 停止录音,输出文件与startRecord相对应
*/
void stopRecord();
/**
* 暂停录音,与resumeRecord配合使用
*/
void pauseRecord();
/**
* 重新开始录音,与pauseRecord配合使用
*/
void resumeRecord();
/**
* 1.释放硬件资源
*/
void releaseResourceAndTemp();
}
4.2 MediaRecordNew
@TargetApi(24)
public class MediaRecordNew implements MediaRecordInterface {
private static final String TAG = "MediaRecordNew";
private File mRecDir;
private MediaRecorder mMediaRecorder;
private String mResFilePath;//最终的录音文件路径 如 /record/temp.m4a
private ContactAudioListener mContactAudioListener;
@Override
public void setContactAudioListener(ContactAudioListener contactAudioListener) {
mContactAudioListener = contactAudioListener;
}
/**
* 创建默认的录音对象
*
* 文件名的格式为 自己需要的目录/文件名(文件名不要写后缀)
*
* @param fileName 文件名
*/
@Override
public void createAudio(String parentDir, String fileName) {
mRecDir = new File(parentDir);
if (!mRecDir.exists()) {
mRecDir.mkdirs();
}
mResFilePath = mRecDir.getAbsolutePath() + File.separator + fileName + ".m4a";
clearTempFile();
}
/**
* 开始录音
*/
@Override
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
}
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);// 设置麦克风
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
/* ③准备 */
mMediaRecorder.setOutputFile(mResFilePath);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
} catch (IllegalStateException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.i(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
/**
* 暂停录音,与resumeRecord配合使用
*/
@Override
public void pauseRecord() {
try {
if (mMediaRecorder != null) {
mMediaRecorder.pause();
}
} catch (IllegalStateException e) {
Log.e(TAG, "pauseRecord: " + ExceptionUtil.collectionExceptionInfo(e));
} catch (RuntimeException e) {
Log.e(TAG, "pauseRecord: " + ExceptionUtil.collectionExceptionInfo(e));
}
}
/**
* 重新开始录音,与pauseRecord配合使用
*/
@Override
public void resumeRecord() {
if (mMediaRecorder != null) {
try {
mMediaRecorder.resume();
}catch (IllegalStateException e){
Log.e(TAG, "resumeRecord" );
}
}
}
/**
* 停止录音,调用后开始合并
*/
@Override
public void stopRecord() {
if (mMediaRecorder != null) {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
} catch (IllegalStateException e) {
Log.e(TAG, "stopRecord: " + ExceptionUtil.collectionExceptionInfo(e));
} catch (RuntimeException e) {
Log.e(TAG, "stopRecord: " + ExceptionUtil.collectionExceptionInfo(e));
}
}
startContact();
}
/**
* sdk>=24因为使用的是MediarPlayer.pause方法,不会生产多个文件,所以不用拼接
*/
private void startContact() {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
File resFile = new File(mResFilePath);
mContactAudioListener.onBegin();
if (mContactAudioListener != null) {
if (MediaMetadataUtil.isDamaged(retriever, mResFilePath)) {
mContactAudioListener.onEnd(CONTACT_FAIL, resFile);
} else {
mContactAudioListener.onEnd(CONTACT_SUCCESS, resFile);
}
}
}
/**
* 1.释放硬件资源
*/
@Override
public void releaseResourceAndTemp() {
if (mMediaRecorder != null) {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
} catch (IllegalStateException illegalStateException) {
LogProxy.e(TAG, "releaseResourceAndTemp: " + illegalStateException.getMessage());
}catch (RuntimeException runtimeException){
LogProxy.e(TAG, "releaseResourceAndTemp: " + ExceptionUtil.collectionExceptionInfo(runtimeException));
}
mMediaRecorder = null;
}
clearTempFile();
}
/**
* 清空录音文件夹
*/
private void clearTempFile() {
if (mRecDir == null) {
return;
}
File[] files = mRecDir.listFiles();
if (files == null) {
return;
}
for (File recordFile : files) {
recordFile.delete();
}
}
}
4.3 MediaRecordOld
public class MediaRecordOld implements MediaRecordInterface {
private static final String TAG = "MediaRecordOld";
private File mRecDir;
private MediaRecorder mMediaRecorder;
private List mTempFileNames = new ArrayList<>();
private String mFileName;//录音文件名 如temp
private String mResFilePath;//最终的录音文件路径 如 /record/temp.m4a
private MediaRecordInterface.ContactAudioListener mContactAudioListener;
@Override
public void setContactAudioListener(MediaRecordInterface.ContactAudioListener contactAudioListener) {
mContactAudioListener = contactAudioListener;
}
@Override
public void createAudio(String parentDir, String fileName) {
mRecDir = new File(parentDir);
if (!mRecDir.exists()) {
mRecDir.mkdirs();
}
mFileName = fileName;
mResFilePath = mRecDir.getAbsolutePath() + File.separator + mFileName + ".m4a";
clearTempFile();
}
@Override
public void startRecord() {
// 开始录音
/* ①Initial:实例化MediaRecorder对象 */
if (mMediaRecorder == null) {
mMediaRecorder = new MediaRecorder();
}
try {
/* ②setAudioSource/setVedioSource */
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);// 设置麦克风
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
/*
* ②设置输出文件的格式:THREE_GPP/MPEG-4/RAW_AMR/Default THREE_GPP(3gp格式
* ,H263视频/ARM音频编码)、MPEG-4、RAW_AMR(只支持音频且音频编码要求为AMR_NB)
*/
/* ②设置音频文件的编码:AAC/AMR_NB/AMR_MB/Default 声音的(波形)的采样 */
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
String tempFilePath = mRecDir + File.separator + mFileName + "" + mTempFileNames.size() + ".m4a";
mTempFileNames.add(tempFilePath);
/* ③准备 */
mMediaRecorder.setOutputFile(tempFilePath);
mMediaRecorder.prepare();
/* ④开始 */
mMediaRecorder.start();
} catch (IllegalStateException e) {
Log.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (IOException e) {
Log.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
} catch (RuntimeException e){
Log.e(TAG, "call startAmr(File mRecAudioFile) failed!" + e.getMessage());
}
}
@Override
public void pauseRecord() {
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
Log.e(TAG, "pauseRecord: " + ExceptionUtil.collectionExceptionInfo(e));
} catch (RuntimeException e) {
Log.e(TAG, "pauseRecord: " + ExceptionUtil.collectionExceptionInfo(e));
}
}
@Override
public void resumeRecord(){
startRecord();//
}
@Override
public void stopRecord() {
if (mMediaRecorder != null) {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
mMediaRecorder = null;
} catch (IllegalStateException e) {
Log.e(TAG, "stopRecord: " + ExceptionUtil.collectionExceptionInfo(e));
} catch (RuntimeException e) {
Log.e(TAG, "stopRecord: " + ExceptionUtil.collectionExceptionInfo(e));
}
}
startContact();
}
/**
* 开始拼接
* ffmpeg的拼接音频命令
* ffmpeg -i file1.mp3 -i file2.mp3 -filter_complex[0:0] [1:0] concat=n=2:v=0:a=1 [a]-map [a] des.file
*/
private void startContact() {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
Iterator iterator = mTempFileNames.iterator();
//检察音频文件是否存在异常
while (iterator.hasNext()) {
String checkedFilePath = iterator.next();
File checkedFile = new File(checkedFilePath);
if (!checkedFile.exists()) {
Log.e(TAG, "startConcat " + checkedFile + " is not exit ");
mContactAudioListener.onEnd(-1, null);
Application instance = ApplicationProxy.getAppInstance();
ToastUtils.show(instance, instance.getResources().getString(R.string.lib_file_lost));
return;
}
if (MediaMetadataUtil.isDamaged(retriever, checkedFilePath)) {
iterator.remove();
checkedFile.delete();
LogProxy.e(TAG, checkedFilePath + " is damaged,discard it");
}
}
if (mTempFileNames.size()==0){
mContactAudioListener.onEnd(-1, null);
Application instance = ApplicationProxy.getAppInstance();
ToastUtils.show(instance, instance.getResources().getString(R.string.lib_file_damaged));
LogProxy.e(TAG, "all file is damaged");
return;
}
String concatCmd1 = "ffmpeg";
for (String concatFile : mTempFileNames) {
concatCmd1 += " -i " + concatFile;
Log.i(TAG, "concat file : " + concatFile);
}
concatCmd1 += " -filter_complex";
String concatCmd2 = "";
for (int i = 0; i < mTempFileNames.size(); i++) {
concatCmd2 += "[" + i + ":0] ";
}
concatCmd2 += "concat=n=" + mTempFileNames.size() + ":v=0:a=1 [a]";
String concatCmd3 = "-map [a] " + mResFilePath;
LogProxy.i(TAG, "cmd is " + concatCmd1 + concatCmd2 + concatCmd3);
ArrayList arrs = new ArrayList<>();
arrs.addAll(Arrays.asList(concatCmd1.split(" ")));
arrs.add(concatCmd2);
arrs.addAll(Arrays.asList(concatCmd3.split(" ")));
String[] cmds = new String[arrs.size()];
int index = 0;
for (String ele : arrs) {
cmds[index++] = ele;
}
FFmpegCmd.execute(cmds, new FFmpegCmd.OnHandleListener() {
@Override
public void onBegin() {
Log.i(TAG, "startConcat");
if (mContactAudioListener != null) {
Observable.just(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(Integer s) {
mContactAudioListener.onBegin();
}
});
}
}
@Override
public void onEnd(final int i) {//i==0表示成功
Log.i(TAG, "endConcat " + i);
Observable.just(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(Integer s) {
mContactAudioListener.onEnd(i, new File(mResFilePath));
}
});
}
});
}
@Override
public void releaseResourceAndTemp() {
if (mMediaRecorder != null) {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
} catch (IllegalStateException illegalStateException) {
LogProxy.e(TAG, "releaseResourceAndTemp: " + illegalStateException.getMessage());
}catch (RuntimeException runtimeException){
LogProxy.e(TAG, "releaseResourceAndTemp: " + ExceptionUtil.collectionExceptionInfo(runtimeException));
}
mMediaRecorder = null;
}
clearTempFile();
}
/**
* 清空录音文件夹
*/
private void clearTempFile() {
if (mRecDir == null) {
return;
}
File[] files = mRecDir.listFiles();
if (files == null) {
return;
}
for (File recordFile : files) {
recordFile.delete();
}
mTempFileNames.clear();
}
}