Android中视频文件的录制

之前我们说了音频的录制和播放,那么现在说说视频的录制。其实视频的录制还是用的MediaRecorder,并且配合Camera进行操作。所以这里我们详细说明一下视频录制的做法。至于视频播放,由于涉及到全屏切换,生命周期管理等一系列问题,这里不写博客说了,感兴趣的同学可以在我的资源中找到我的框架,里面有关于视频的播放案例。

好了,我们开始录制视频的学习。

1.媒体播放器MediaRecorder常用方法(录音与录像通用)

  • reset:重置录制资源。
  • prepare:准备录制。
  • start:开始录制。
  • stop:结束录制。
  • release:释放录制资源。
  • setOnErrorListener:设置错误监听器。可监听服务器异常和未知错误的事件。需要实现接口MediaRecorder.OnErrorListener的onError方法。
  • setOnInfoListener:设置信息监听器。可监听录制结束事件,包括达到录制时长或达到录制大小。需要实现接口MediaRecorder.OnInfoListener的onInfo方法。
  • setMaxDuration:设置可录制的最大时长,单位毫秒。
  • setMaxFileSize:设置可录制的最大文件大小,单位字节。
  • setOutputFile:设置输出文件的路径。

2.MeidaRecorder用于录像的常用方法

  • setCamera:设置相机对象。
  • setPreviewDisplay:设置预览界面。预览界面对象可通过SurfaceHolder对象的getSurface方法获得。
  • setOrientationHint:设置预览的角度。跟拍照一样设置为90,表示界面从水平方向到垂直方向旋转90度。
  • setVideoSource:设置视频来源。一般使用VideoSource.CAMERA表示摄像头。
  • setOutputFormat:设置媒体输出格式。媒体输出格式的取值见表1。
表1
OutputFormat类的输出格式 格式分类 扩展名 格式说明
AMR_NB 音频 .amr 窄带格式
AMR_WB 音频 .amr 宽带格式
AAC_ADTS 音频 .aac 高级的音频传输流格式
MPEG_4 视频 .mp4 MPEG4格式
THREE_GPP 视频 .3gp 3GP格式
  • setVideoEncoder:设置视频编码器。一般使用VideoEncoder.MPEG_4_SP表示MPEG4编码。该方法在setOutputFormat方法之后调用,否则会报错java.lang.IllegalStateException。
  • setVideoSize:设置视频的分辨率。
  • setVideoFrameRate:设置视频每秒录制的帧数。越大视频越连贯,当然最终生成的视频文件也越大。
  • setVideoEncodingBitRate:设置视频每秒录制的字节数。越大视频越清晰,setVideoFrameRate与setVideoEncodingBitRate设置一个即可。

3.代码示例

权限申请

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>

布局代码activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/fr_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:id="@+id/bt_record"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="录制视频"/>
</RelativeLayout>

MainActivity.java

public class MainActivity extends WaterPermissionActivity implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {

    private TextView bt_record;
    private FrameLayout fr_root;
    private SurfaceHolder mHolder; // 声明一个表面持有者对象
    private Camera mCamera; // 声明一个相机对象
    private MediaRecorder mMediaRecorder;
    private String moviePath;//视频路径

    @Override
    protected MvcBaseModel getModelImp() {
        return null;
    }

    @Override
    protected int getContentLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    protected void initWidget() {
        fr_root = findViewById(R.id.fr_root);
        bt_record = findViewById(R.id.bt_record);
        bt_record.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    //按下,开始录制
                    getPath();//初始化视频路径
                    initRecord();//开始录制
                } else if (event.getAction() == MotionEvent.ACTION_UP) {
                    //松开,停止录制
                    cancelRecord(); // 取消录制操作
                    freeCamera(); // 释放相机资源
                }
                return true;
            }
        });
        requestPermission(WRITE_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDWrite() {
        requestPermission(READ_EXTERNAL_STORAGE);
    }

    @Override
    protected void doSDRead() {
        requestPermission(CAMERA);
    }

    @Override
    protected void doCamera() {
        requestPermission(RECORD);
    }

    @Override
    protected void doRecord() {
        //这里为了请求好动态权限再让Surface初始化才这么弄得,可以在进入录制页面就请求好动态权限,就不用这么写了。
        SurfaceView surfaceView = new SurfaceView(this);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
        surfaceView.setLayoutParams(params);
        fr_root.addView(surfaceView);
        // 获取表面视图的表面持有者
        mHolder = surfaceView.getHolder();
        // 给表面持有者添加表面变更监听器
        mHolder.addCallback(mSurfaceCallback);
    }

    /**
     * 初始化录制操作,开始录制调用这个方法
     */
    private void initRecord() {
        mMediaRecorder = new MediaRecorder(); // 创建一个媒体录制器
        mMediaRecorder.setCamera(mCamera); // 设置媒体录制器的摄像头
        mMediaRecorder.setOnErrorListener(this); // 设置媒体录制器的错误监听器
        mMediaRecorder.setOnInfoListener(this); // 设置媒体录制器的信息监听器
        mMediaRecorder.setPreviewDisplay(mHolder.getSurface()); // 设置媒体录制器的预览界面
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); // 设置视频源为摄像头
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); // 设置音频源为麦克风
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); // 设置媒体的输出格式
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); // 设置媒体的音频编码器
        // 如果录像报错:MediaRecorder start failed: -19
        // 试试把setVideoSize和setVideoFrameRate注释掉,因为尺寸设置必须为摄像头所支持,否则报错
         mMediaRecorder.setVideoSize(2280, 1080); // 设置视频的分辨率
        // mMediaRecorder.setVideoFrameRate(16); // 设置视频每秒录制的帧数
        // setVideoFrameRate与setVideoEncodingBitRate设置其一即可
        mMediaRecorder.setVideoEncodingBitRate(1 * 1024 * 512); // 设置视频每秒录制的字节数
        mMediaRecorder.setOrientationHint(90); // 输出旋转90度,也就是保持竖屏录制
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); // 设置媒体的视频编码器
        mMediaRecorder.setMaxDuration(10 * 1000); // 设置媒体的最大录制时长
        // mMediaRecorder.setMaxFileSize(1024*1024*10); // 设置媒体的最大文件大小
        // setMaxFileSize与setMaxDuration设置其一即可
        mMediaRecorder.setOutputFile(moviePath); // 设置媒体文件的保存路径
        try {
            mMediaRecorder.prepare(); // 媒体录制器准备就绪
            mMediaRecorder.start(); // 媒体录制器开始录制
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 定义一个表面持有者的变更监听器
     */
    private SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() {
        // 在表面视图创建时触发
        public void surfaceCreated(SurfaceHolder holder) {
            initCamera(); // 初始化相机
        }

        // 在表面视图变更时触发
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        }

        // 在表面视图销毁时触发
        public void surfaceDestroyed(SurfaceHolder holder) {
            freeCamera(); // 释放相机资源
        }
    };

    /**
     * 释放相机资源
     */
    private void freeCamera() {
        if (mCamera != null) {
            mCamera.stopPreview(); // 停止预览
            mCamera.lock(); // 锁定相机,即关闭相机
            mCamera.release(); // 释放相机资源
            mCamera = null;
        }
    }

    /**
     * 初始化相机操作
     */
    private void initCamera() {
        if (mCamera != null) {
            freeCamera();
        }
        try {
            // 打开摄像头,默认后置摄像头
            mCamera = Camera.open();
            // 设置相机的展示角度
            mCamera.setDisplayOrientation(90);
            // 设置相机的预览界面
            mCamera.setPreviewDisplay(mHolder);
            // 开始预览画面
            mCamera.startPreview();
            // 解锁相机,即打开相机
            mCamera.unlock();
        } catch (Exception e) {
            e.printStackTrace();
            freeCamera();
        }
    }

    /**
     * 录制前创建一个空文件并获取路径
     */
    private void getPath() {
        List<String> list = new ArrayList<>();
        list.add("record");
        String dirPath = PathGetUtil.getLongwayPath(this, list);
        File fileDir = new File(dirPath);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        File fileVoice = new File(dirPath, "movie" + System.currentTimeMillis() + ".mp4");
        if (!fileVoice.exists()) {
            try {
                fileVoice.createNewFile();
                moviePath = fileVoice.getPath();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        if (mr != null) {
            mr.reset();  // 重置媒体录制器
        }
    }

    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        // 录制达到最大时长,或者达到文件大小限制,都停止录制
        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
                || what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
            cancelRecord();
        }
    }

    /**
     * 取消录制操作,停止录制的时候调用该方法
     */
    private void cancelRecord() {
        if (mMediaRecorder != null) {
            mMediaRecorder.setOnErrorListener(null); // 错误监听器置空
            mMediaRecorder.setPreviewDisplay(null); // 预览界面置空
            try {
                mMediaRecorder.stop(); // 媒体录制器停止录制
            } catch (Exception e) {
                e.printStackTrace();
            }
            mMediaRecorder.release(); // 媒体录制器释放资源
            mMediaRecorder = null;
        }
    }
}

同样的,我们录制出来的视频由于没有进行处理,视频质量不是很高,很不清晰。所以如果大家想更加清晰的话,可以考虑把设置的Camera部分换成Camera2或者更好用的CameraX。我这里就不进行具体操作了,原理都是一样的,大家感兴趣可以进行尝试。

猜你喜欢

转载自blog.csdn.net/weixin_38322371/article/details/115249593