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

前言

大家好,在前几篇中,我们通过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,重写surfaceCreatedsurfaceChangedsurfaceDestroyed3个方法

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

         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. 开始预览,首先是请求权限,这里使用的是自己封装的 CPermission,也可以使用其他的封装库
        String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
        CPermission.with(this)
                .permiss()
                .permission(perms)
                .listener(new PermissListener<String>() {
                    @Override
                    public void onGranted(List<String> granted) {
                        showtoast("权限申请成功");
                         startPreview();
                    }

                    @Override
                    public void onDenied(List<String> granted) {
                        showtoast("权限被拒绝");
                    }
                }).start();

开始预览

 /**
     * 开始预览
     */
    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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343