Android音视频录制之MediaRecorder+camera

前言

本篇介绍使用Android 中视频录制,录制工具是:

  • MediaRecorder : 视频编码封装
  • camera : 视频画面原始数据采集
  • TextureView : 提供预览画面

MediaRecorder基本api介绍

MediaRecorder是android中面向应用层的封装,用于提供便捷的音视频编码封装操作,在使用的过程中要严格按照官方指定的生命周期调用顺序,即下图所示的使用步骤

image.png

从图中可以看出,MediaRecorder的生命周期有以下几个阶段,并且必须按顺序执行

  • initial : 在MediaRecorder被创建(刚new 出来)或者滴啊用reset()方法后,会处于该状态
  • initialized : 当调用setAudioSource()或者setVideoSource()后,处于该状态。这两个方法主要用于设置音视频的源配置,通常音频是麦克风,视频是摄像头。该状态可以通过调用reset()方法回到initial状态
  • DataSourceConfigured : 当调用setOutputFormat()方法后,会处于该状态。该方法主要用于设置输出的文件格式,可以是音视频如MP4,也可以是单独的音频如mp3。当处于该状态之后,可以进一步设置音频和视频的配置参数,例如音频封装格式,采样率,视频码率,帧率等等该状态可以通过调用reset()方法回到initial状态
  • Prepared : 在上面几个步骤都配置好之后,可以通过调用prepare()方法进入该状态,只有处于该状态,才能调用start()
  • Recording : 通过调用start()方法进入该状态,该状态就是真正开始进行视频录制编码的阶段,通过调用stop()或者reset()可以回到initial状态
  • error状态 : 当录制过程中发生次错误时,会进入该状态,调用reset()方法回到initial状态。
  • release : 只有在initial状态才可以通过调用release()方法进入该状态,释放所占用的系统资源

编码的步骤

在开始编码之前,应该先规划一下编码的步骤,因为录制视频需要预览画面,所以我们肯定需要构建一个预览的界面,通过camera+TextureView可以实现,音频不需要预览。构建好预览画面之后,就是开始配置MediaRecorder开始录制了。

配置Camera

demo只演示后置摄像头的捕捉,因为使用MediaRecorder进行录制无法处理帧数据,在切换前置摄像头之后,视频会出现左右翻转的问题,无法通过单纯的旋转解决,暂时还没找到方法。

camera的配置重点

  • 预览尺寸,预览尺寸的宽高比应该尽量和TextureView的宽高比一致,这样可以保证画面不变形;
  • 对焦模式,对焦模式一般首选FOCUS_MODE_CONTINUOUS_VIDEO,如果机型不包含,则选择FOCUS_MODE_CONTINUOUS_PICTURE,如果还不包含,则选择FOCUS_MODE_AUTO,然后通过手指点击重新对焦(手指点击TextureView,调用mCamera.autoFocus(null););
  • 预览界面旋转,由于传感器方向和手机自然方向不一致,所以需要调整预览界面进行一定的旋转,旋转的角度大小可以通过google官方提供的方法计算,查看mCamera.setDisplayOrientation(mRotationDegree);方法源码查看注释里有这段代码(shift/command+鼠标左键点击方法)

示例代码

/**
     * 初始化相机
     */
    private void initCamera() {
        if (mSurfaceTexture == null) return;
        if (mCamera != null) {
            releaseCamera();
        }

        mCamera = Camera.open(mCameraId);
        if (mCamera == null) {
            Toast.makeText(this, "没有可用相机", Toast.LENGTH_SHORT).show();
            return;
        }

        try {
            mCamera.setPreviewTexture(mSurfaceTexture);
            mRotationDegree = CameraUtil.getCameraDisplayOrientation(this, mCameraId);
            mCamera.setDisplayOrientation(mRotationDegree);
            setCameraParameter(mCamera);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 设置相机的参数
     *
     * @param camera
     */
    private void setCameraParameter(Camera camera) {
        if (camera == null) return;
        Camera.Parameters parameters = camera.getParameters();
        //获取相机支持的>=20fps的帧率,用于设置给MediaRecorder
        //因为获取的数值是*1000的,所以要除以1000
        List<int[]> previewFpsRange = parameters.getSupportedPreviewFpsRange();
        for (int[] ints : previewFpsRange) {
            if (ints[0] >= 20000) {
                mFps = ints[0] / 1000;
                break;
            }
        }
        //设置聚焦模式
        List<String> focusModes = parameters.getSupportedFocusModes();
        if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        } else {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }


        //设置预览尺寸,因为预览的尺寸和最终是录制视频的尺寸无关,所以我们选取最大的数值
        //通常最大的是手机的分辨率,这样可以让预览画面尽可能清晰并且尺寸不变形,前提是TextureView的尺寸是全屏或者接近全屏
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        parameters.setPreviewSize(supportedPreviewSizes.get(0).width, supportedPreviewSizes.get(0).height);
        //缩短Recording启动时间
        parameters.setRecordingHint(true);
        //是否支持影像稳定能力,支持则开启
        if (parameters.isVideoStabilizationSupported())
            parameters.setVideoStabilization(true);
        camera.setParameters(parameters);
    }

配置MediaRecorder

按照生命周期进行每一步的配置,重点关注

  • 编码参数,配置MediaRecorder的编码参数有两种方式;
  1. 通过系统提供的CamcorderProfile类,搭配mMediaRecorder.setProfile(profile);方法进行设置,CamcorderProfile对象包含了输出封装格式,视频编码格式,帧率,码率,分辨率,音频采样率,声道数,码率等参数。

示例代码

 /**
     * 通过系统的CamcorderProfile设置MediaRecorder的录制参数
     * 首先查看系统是否包含对应质量的封装参数,然后再设置,根据具体需要的视频质量进行判断和设置
     */
    private void setProfile() {
        CamcorderProfile profile = null;
        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_1080P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
            profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        }
        if (profile != null) {
            mMediaRecorder.setProfile(profile);
        }
    }
  1. 自定义参数,通过profile可以一步到位配置各个参数,但是缺点是没办法设置自己想要的视频清晰度,因为视频清晰度是根据码率和分辨率决定的,而每个profile已经固定了码率和分辨率,所以无法进行调整。这种情况我们就可以自己配置参数。

需要注意

  • 帧率不可以随便定义,如果系统不支持就会报错。应该先通过camera获取支持的帧率,然后再设置。
  • 视频尺寸的大小,可以根据需要的质量,比如需要高清720的尺寸,那么先获取系统720p的profile,然后取profile.videoFrameWidth; profile.videoFrameHeight作为输出宽高。我这里为了方便直接写了1280:720,大部分手机都尺寸这个参数。

示例代码

/**
     * 自定义MediaRecorder的录制参数
     */
    private void setConfig() {
        //设置封装格式 默认是MP4
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
        //音频编码
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
        //图像编码
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        //声道
        mMediaRecorder.setAudioChannels(1);
        //设置最大录像时间 单位:毫秒
        mMediaRecorder.setMaxDuration(60 * 1000);
        //设置最大录制的大小60M 单位,字节
        mMediaRecorder.setMaxFileSize(60 * 1024 * 1024);
        //再用44.1Hz采样率
        mMediaRecorder.setAudioEncodingBitRate(22050);
        //设置帧率,该帧率必须是硬件支持的,可以通过Camera.CameraParameter.getSupportedPreviewFpsRange()方法获取相机支持的帧率
        mMediaRecorder.setVideoFrameRate(mFps);
        //设置码率
        mMediaRecorder.setVideoEncodingBitRate(500 * 1024 * 8);
        //设置视频尺寸,通常搭配码率一起使用,可调整视频清晰度
        mMediaRecorder.setVideoSize(1280, 720);
    }
录制的控制

控制流代码如下 chronometer是用于计时的

/**
     * 开始录制和停止录制
     *
     * @param v
     */
    public void control(View v) {
        if (mStatus == RecorderStatus.RECORDING) {
            stopRecord();
        } else {
            startRecord();
        }
    }

    /**
     * 开始录制
     */
    private void startRecord() {
        initCamera();
        mCamera.unlock();
        initMediaRecorder();
        try {
            mMediaRecorder.prepare();
            mMediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

        chronometer.setBase(SystemClock.elapsedRealtime());
        chronometer.start();
        mStatus = RecorderStatus.RECORDING;
    }

    /**
     * 停止录制
     */
    private void stopRecord() {
        releaseMediaRecorder();
        releaseCamera();
    }

视频录制好之后可以在手机目录aaamedia文件夹下找到,以aaa开头方便查找!

完整代码git地址

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