美文网首页免费Android项目安卓开发
【Android 进阶】仿抖音系列之视频预览和录制(五)

【Android 进阶】仿抖音系列之视频预览和录制(五)

作者: 欢子3824 | 来源:发表于2018-11-05 17:43 被阅读481次

    大家好,又见面了。在前几篇中,我们通过2种方式实现了仿抖音的翻页切换视频,仿抖音列表播放视频功能;这一篇,我们来说说视频的录制。

    主流的视频录制,一般都采用的是FFmpeg 例如 腾讯短视频,由于FFmpeg的学习成本较大,这里我们就说说系统自带的MediaRecorder。

    首先,需要实现摄像头的预览,这里我们就用SurfaceView 。

    • 1.在布局中引入
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activity.RecordActivity">
    
        <SurfaceView
            android:id="@+id/sv_record"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    
        <Button
            android:id="@+id/btn_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="start"
            app:layout_constraintBottom_toBottomOf="parent" />
    
        <Button
            android:id="@+id/btn_switch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="switch"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <Button
            android:id="@+id/btn_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="end"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintRight_toRightOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    
      1. 实现SurfaceHolder.Callback,重写surfaceCreated、surfaceChanged、surfaceDestroyed 3个方法

    其中surfaceCreated是SurfaceView创建成功时回调,可以在这里开始预览;surfaceChanged是SurfaceView变化时回调,这里不做处理;surfaceDestroyed 是SurfaceView销毁时回调,可以在这里释放资源

             surfaceHolder = svRecord.getHolder();
            surfaceHolder.addCallback(this);
            //设置一些参数方便后面绘图
            svRecord.setFocusable(true);
            svRecord.setKeepScreenOn(true);
            svRecord.setFocusableInTouchMode(true);
    
    
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            surfaceHolder = holder;
            requestPermision();
        }
    
    
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            surfaceHolder = holder;
        }
    
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            //停止预览并释放摄像头资源
            stopPreview();
            //停止录制
            startRecord();
        }
    
      1. 开始预览,首先是请求权限,这里使用的是easypermissions,也可以使用其他的封装库
      private void requestPermision() {
            String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
            if (EasyPermissions.hasPermissions(this, perms)) {
                // Already have permission, do the thing
                startPreview();
                // ...
            } else {
                // Do not have permissions, request them now
                EasyPermissions.requestPermissions(this, "我们的app需要以下权限",
                        RC_STORAGE, perms);
            }
        }
    

    需要实现EasyPermissions.PermissionCallbacks,以及处理授权回调

    
        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            // Forward results to EasyPermissions
            EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
        }
    
        @Override
        public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
            // Some permissions have been granted
            startPreview();
        }
    
        @Override
        public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
            // Some permissions have been denied
            finish();
        }
    
     /**
         * 开始预览
         */
        private void startPreview() {
            if (svRecord == null || surfaceHolder == null) {
                return;
            }
    
    
            if (camera == null) {
                camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
                currentCameraType = 1;
                btnSwitch.setText("后");
            }
    
    
            try {
                camera.setPreviewDisplay(surfaceHolder);
                Camera.Parameters parameters = camera.getParameters();
    
                camera.setDisplayOrientation(90);
    
                //实现Camera自动对焦
                List<String> focusModes = parameters.getSupportedFocusModes();
                if (focusModes != null) {
                    for (String mode : focusModes) {
                        mode.contains("continuous-video");
                        parameters.setFocusMode("continuous-video");
                    }
                }
    
                List<Camera.Size> sizes = parameters.getSupportedVideoSizes();
                if (sizes.size() > 0) {
                    size = sizes.get(sizes.size() - 1);
                }
    
                camera.setParameters(parameters);
                camera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    

    其中,Camera.CameraInfo.CAMERA_FACING_BACK 代表后置摄像头,保险起见,应该检查设备是否有后置摄像头,这里就不检查了;还需要注意,当时后置时,应该旋转摄像头90度,否则预览是斜的

      1. 切换摄像头,这里如上代码所见,使用了一个int 型变量currentCameraType来记录前后摄像头;
                    stopPreview();
                    if (currentCameraType == 1) {
                        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
                        currentCameraType = 2;
                        btnSwitch.setText("前");
                    } else {
                        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
                        currentCameraType = 1;
                        btnSwitch.setText("后");
                    }
    
                    startPreview();
    
     /**
         * 停止预览
         */
        private void stopPreview() {
            //停止预览并释放摄像头资源
            if (camera == null) {
                return;
            }
    
            camera.setPreviewCallback(null);
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    

    需要注意,需要先停止预览,切换摄像头之后,再开始预览;

    到这里摄像头已经实现了摄像头预览。

    开始录制视频

    
        /**
         * 开始录制
         */
        private void startRecord() {
            if (mediaRecorder == null) {
                mediaRecorder = new MediaRecorder();
            }
            temFile = getTemFile();
    
    
            try {
                camera.unlock();
                mediaRecorder.setCamera(camera);
                //从相机采集视频
                mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
                // 从麦克采集音频信息
                mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
                //编码格式
                mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
                mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    
                mediaRecorder.setVideoSize(size.width, size.height);
    
                //每秒的帧数
                mediaRecorder.setVideoFrameRate(24);
                // 设置帧频率,然后就清晰了
                mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100);
    
    
                mediaRecorder.setOutputFile(temFile.getAbsolutePath());
                mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
                //解决录制视频, 播放器横向问题
                if (currentCameraType == 1) {
                    //后置
                    mediaRecorder.setOrientationHint(90);
                } else {
                    //前置
                    mediaRecorder.setOrientationHint(270);
                }
                mediaRecorder.prepare();
                //正式录制
                mediaRecorder.start();
    
           
                isRecording = true;
                showtoast("开始录制");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 获取临时文件目录
         *
         * @return
         */
        private File getTemFile() {
            String basePath = Environment.getExternalStorageDirectory().getPath() + "/doudemo/";
    
            File baseFile = new File(basePath);
            if (!baseFile.exists()) {
                baseFile.mkdirs();
            }
    
            File temp = new File(basePath + System.currentTimeMillis() + ".mp4");
    
            return temp;
        }
    

    这里的坑还是比较多的

    • 1.首先需要解锁相机,调用camera.unlock();
    • 2.关于视频的size ,应该通过parameters.getSupportedVideoSizes(); 获取该手机支持的宽高,如果设置手机不支持,会报错;
    • 3.注意各个方法调用顺序,否则会报一些奇怪的错,无奈..................
    • 4.摄像机角度问题,后置时,旋转90度,前置时,旋转270度

    停止录制,需要锁定相机,需要预览时,跳到预览(播放)界面

      /**
         * 停止录制
         */
        private void stopRecord(boolean delete) {
            if (mediaRecorder == null) {
                return;
            }
            if (myTimer != null) {
                myTimer.cancel();
            }
    
            try {
                mediaRecorder.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
            if (camera != null) {
                camera.lock();
            }
            isRecording = false;
    
            if (delete) {
                if (temFile != null && temFile.exists()) {
                    temFile.delete();
                }
            } else {
                //停止预览
                stopPreview();
    
                Intent intent = new Intent(RecordActivity.this, PrepareActivity.class);
                intent.putExtra(PrepareActivity.VIDEO_PATH, temFile.getPath());
                startActivity(intent);
    
            }
            showtoast("停止录制");
        }
    

    最后,献上完整代码。Github

    相关文章

      网友评论

        本文标题:【Android 进阶】仿抖音系列之视频预览和录制(五)

        本文链接:https://www.haomeiwen.com/subject/revsxqtx.html