Android MediaRecorder录像

近期小编正在做类似于朋友圈的功能,调用系统录像,华为机10s中就录出来41M,上传就要30-40s,测试提出BUG,产品提出需优化,小编在风中凌乱,没做过啊,,,近期终于完成需求,梳理一下,方便以后看。

一. 软编码和硬编码如何区分

软编码:使用CPU进行编码

硬编码:使用非CPU进行编码,如显卡GPU、专用的DSP、FPGA、ASIC芯片等

二. 软编码和硬编码比较

软编码:实现直接、简单,参数调整方便,升级容易,但CPU负载重,性能较硬编码低,低码率下质量通常比硬编码要好一点

硬编码:性能高,低码率下通常质量低于硬编码器,但部分产品在GPU硬件平台移植了优秀的软编码算(如X264)等,质量基于等同于软编码

三. 目前主流GPU加速平台

Intel、AMD、NVIDIA

四. 目前主流GPU平台开发框架

CUDA: NVIDIA的封闭编程框架,通过框架可以调用GPU计算资源

AMD APP: AMD为自己的GPU提出的一套通用并行编程框架,标准放开,通过在CPU、GPU同事支持OpenCL框架,进行计算力融合

OpenCL:开放计算语言,为异构平台编写程序的该框架,异构平台可包含CPU、GPU以及其他计算处理器,木匾是使用相同的计算能支持不同平台硬件加速。

Intel QuickSync: 集成Intel显卡中的专用视频编解码模块。

五. Android实现视频编码——H.264硬编码 硬编码 软编码

硬编码:通过调用Android系统自带的Camera录制视频,实际上是调用了底层的高清编码硬件模块,也即显卡,不适用CPU,速度快

软编码:使用CPU进行编码,如常见C/C++代码,一般编译生成的二进制都是的,速度相对较慢。例如使用AndroidNDK编译H264生成so库,编写jni接口,再使用java调用so库。

软编流程:camera采集YUV数据==>滤镜==>x264编码器机型编码==>MP4打包合成

硬编流程:采用手机提供的硬板接口,利用硬件芯片直接进行编码合成。

优点:速度快、效率高、CPU占用极少,及时长时间高清录制也不会发烫,同事由于使用系统API,库相对较少。

缺点:某些奇葩记性需要处理兼容性问题,同事Android上的硬编跟Surface以及OpenGL关系比较密切,网上相关知识较少,需要自己摸索采坑。

硬编的主要流程如下图所示,可以看到所有的数据,聪采集、编码、显示以及合成都在GPU里面进行流转。

软编:FFmpeg

硬编:MediaCodec、MediaMuxer

六. 录制方案:

1. 使用系统自带的MediaRecorder

坑:

1) Android不同机型,屏幕尺寸不同,支持显示视频的分辨率也不同,在设置MediaRecorder的VideoSize和Profile时,如果手机不支持,就会崩溃,在SurfaceView显示,Camera设置PreviewSize设置不正确时,会出现变形。

2)前置摄像头,录像VideoSize和系统的屏幕比例基本不一致,会导致我们录出来的视频会有变形,另外就是左右镜像问题,其实也能理解,前置摄像头就和后置摄像头一样是对着录制的,而朋友圈录像估计是做了镜像处理,类似直播的API,貌似现在小视频都有做翻转的

2. Google grafika

3. FFmpeg

七. 代码

https://github.com/CarGuo/VideoRecord

录像不太清楚,因为分辨率写死了是640*480的,手机VideoSize都支持,但是由于现在屏幕分辨率较高,导致拍完之后都不太清楚,另外就是一些变形的状况

https://github.com/chenzhihui28/VideoRecorderAndCompressor

1. 切换摄像头的时候,CameraPreview类会执行refreshCamera方法,期间会调用optimizeCameraDimens方法,这个时候获取到的mCamera.getParameters().getSupportedPreviewSizes()明显发生了变化,

2. 另外就是横屏的处理,每次拿着手机横屏录像,录像完了之后旋转了90度,需要改动代码:设置屏幕方向为portrait,变换屏幕方向不进行重绘,在CameraActivity中preprareMediaRecorder方法,之前的判断屏幕方向就不需要了,判断rotationRecord,竖着放,mediaRecorder设置OrientationHint,前置摄像头270,后置90,横着放,前后设置为0,横倒着放设置180

if (rotationRecord == 90) {
            mediaRecorder.setOrientationHint(cameraFront ? 270 : 90);
} else if (rotationRecord == 0) {
            mediaRecorder.setOrientationHint(cameraFront ? 0 : 0);
} else if (rotationRecord == 180) {
            mediaRecorder.setOrientationHint(cameraFront ? 180 : 180);
 }

   /**
     * 旋转前置摄像头为正
     */
    private void frontCameraRotate() {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, info);
        int degrees = getDisplayRotation(this);
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        frontOri = info.orientation;
        frontRotate = result;
    }

    /**
     * 获取旋转角度
     *
     * @param activity
     * @return
     */
    private int getDisplayRotation(Activity activity) {
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        switch (rotation) {
            case Surface.ROTATION_0:
                return 0;
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
        }
        return 0;
    }

    /**
     * 旋转界面UI
     */
    private void rotationUIListener() {
        orientationEventListener = new OrientationEventListener(this) {
            @Override
            public void onOrientationChanged(int rotation) {
                if (!recording) {
                    if (((rotation >= 0) && (rotation <= 30)) || (rotation >= 330)) {
                        // 竖屏拍摄
                        if (rotationFlag != 0) {
                            //旋转logo
                            rotationAnimation(rotationFlag, 0);
                            //这是竖屏视频需要的角度
                            rotationRecord = 90;
                            //这是记录当前角度的flag
                            rotationFlag = 0;
                        }
                    } else if (((rotation >= 230) && (rotation <= 310))) {
                        // 横屏拍摄
                        if (rotationFlag != 90) {
                            //旋转logo
                            rotationAnimation(rotationFlag, 90);
                            //这是正横屏视频需要的角度
                            rotationRecord = 0;
                            //这是记录当前角度的flag
                            rotationFlag = 90;
                        }
                    } else if (rotation > 30 && rotation < 95) {
                        // 反横屏拍摄
                        if (rotationFlag != 270) {
                            //旋转logo
                            rotationAnimation(rotationFlag, 270);
                            //这是反横屏视频需要的角度
                            rotationRecord = 180;
                            //这是记录当前角度的flag
                            rotationFlag = 270;
                        }
                    }
                }
            }
        };
        orientationEventListener.enable();
    }


    private void rotationAnimation(int from, int to) {
        ValueAnimator progressAnimator = ValueAnimator.ofInt(from, to);
        progressAnimator.setDuration(300);
        progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentAngle = (int) animation.getAnimatedValue();
                buttonFlash.setRotation(currentAngle);
                textChrono.setRotation(currentAngle);
                buttonChangeCamera.setRotation(currentAngle);
            }
        });
        progressAnimator.start();
    }

3. 后置摄像头,我们可以采用最接近手机屏幕比例的,这样拍出来不会产生变形,CameraPreivew获取到ratio之后,获取摄像头参数的getSupportedVideoSizes(),选择最接近比例的,保存起来,在CameraActivity中获取,MediaRecorder参数设置:

Camera.Size videoSize = mPreview.getVideoSize();

mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

mediaRecorder.setVideoFrameRate(15);

mediaRecorder.setVideoSize(videoSize.width, videoSize.height);

mediaRecorder.setVideoEncodingBitRate(3 * videoSize.width / 2 * videoSize.height / 2);

mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

mediaRecorder.setMaxDuration(10 * 1000);

注意:设置编码setAudioEncorder和setVideoEncoder方法需放置在其他设置之后,比如放在设置帧率之前,会报错

4. 前置摄像头暂时没有找到好的解决方案,因为屏幕比例获取到之后,在获取摄像头getSupportedVideoList()时,能得到的一些尺寸设置会出问题,只能选择demo中720P和480P,但是依旧有变形和镜像问题,还需好好研究。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值