近期小编正在做类似于朋友圈的功能,调用系统录像,华为机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,但是依旧有变形和镜像问题,还需好好研究。