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