低版本的MediaRecorder支持暂停

2019-12-17  本文已影响0人  真胖大海

一.场景

如果android api小于24,MediaRecorder不支持暂停功能,如果想实现暂停功能,要借用ffmpeg的相关功能。

二.目的

实现一个兼容新老版本的MediaRecorder

三.UML图

image.png

3.1 接口说明
抽象出接口MediaRecordInterface
具有start,pause,resume,release等抽象方法

MediaRecordInterface接口

ContactAudioListener接口用于监听临时录音文件的开始拼接和结束拼接


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);

    /**
     * 创建默认的录音对象
     * <p>
     * 文件名的格式为 自己需要的目录/文件名(文件名不要写后缀)
     *
     * @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;
    }

    /**
     * 创建默认的录音对象
     * <p>
     * 文件名的格式为 自己需要的目录/文件名(文件名不要写后缀)
     *
     * @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<String> 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<String> 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<String> 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<Integer>() {
                                @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<Integer>() {
                            @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();
    }


}
上一篇下一篇

猜你喜欢

热点阅读