写在前面的
网上有很多博客都是讲使用opengl+camera2美颜相机,本人技术能力有限,觉得openGL android使用十分复杂,GLES20以后还需要理解顶点着色器片段着色器等晦涩的名词,离开操作手册还是什么也不会写。camera2 api接口的回调太多,导致代码逻辑混乱,难以组织逻辑。
偶然发现opencv处理后拍视频,一点也不用担心处理的耗时导致视频卡顿,MediaCodec自带Buffer缓冲,拍720p的视频也没问题,1080p还没试过。由此做下笔记。
而opencv的滤镜库也很成熟,美颜滤镜磨皮美白也一大把,如果加静态图文水印就更简单,用不到滤镜算法。为windows平台写的opencv滤镜算法都有参考价值。
MediaCodec编码后可以录制视频,也可以编码成h264,h265等推流直播,会议。
首先根据设置支持Profile获取支持录制的视频格式,这里的例子都没添加声道,因为我觉得添加声音一定不难。
public class CameraUtils {
/**
* 根据MediaRecord支持的录像size大小,MediaRecord支持的录像大小,也是MediaCodec支持的
*/
public static Point initSupportProfile() {
int mQuality = 0;
if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_480P)) {//无声音的480p
mQuality = CamcorderProfile.QUALITY_TIME_LAPSE_480P;
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_QVGA)) {
mQuality = CamcorderProfile.QUALITY_TIME_LAPSE_QVGA;
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_CIF)) {
mQuality = CamcorderProfile.QUALITY_TIME_LAPSE_CIF;
} else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_TIME_LAPSE_720P)) {
mQuality = CamcorderProfile.QUALITY_TIME_LAPSE_720P;
}
Log.d("px", "finally mQuality resolution:" + mQuality);
CamcorderProfile profile = CamcorderProfile.get(mQuality);
Log.d("px", "video screen from CamcorderProfile resolution:" + profile.videoFrameWidth + "*" + profile.videoFrameHeight);
return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
}
}
然后根据这个视频尺寸去找预览尺寸,相同尺寸最好,设置帧率,预览颜色格式,帧率。值得一提的是,帧率设置在很多设备报错,可能不能设置,或设置的不对吧。
static Camera.Parameters showSupportPreviewSize(Camera camera, Point size) {
//处理预览尺寸
Camera.Parameters p = camera.getParameters();
List.Size> previewSizes = p.getSupportedPreviewSizes();
if (previewSizes != null) {
StringBuilder sb = new StringBuilder("SupportedPreviewSizes:[");
for (Camera.Size s : previewSizes) {
sb.append(s.width).append("*").append(s.height).append(",");
if (s.width * s.height == size.x * size.y) {
p.setPreviewSize(s.width, s.height);
Log.d("px", "resolution size:" + s.width + "*" + s.height);
}
}
Log.d("px", sb.deleteCharAt(sb.length() - 1).toString());
} else {
Log.d("px", "SupportedVideoSizes:null");
}
//处理预览帧率
int[] range = {0, 0};
List previewFpsRange = p.getSupportedPreviewFpsRange();
StringBuilder sb = new StringBuilder("SupportedPreviewFpsRange:");
for (int[] r : previewFpsRange) {
sb.append("[");
for (int i = 0; i < r.length; i++) {
sb.append(r[i]).append(",");
}
if (r[0] > 20 * 1000 && r[0] > range[0]) {//取一个大于20的帧率
range = r;
}
sb.deleteCharAt(sb.length() - 1);
sb.append("],");
}
sb.deleteCharAt(sb.length() - 1);
Log.d("px", sb.toString() + "->use:[" + range[0] + "," + range[1] + "]");
//帧率
// p.setPreviewFpsRange(24, 24);
List formats = p.getSupportedPreviewFormats();
Log.d("px", "SupportedPreviewFormats:" + formats);
if (formats.contains(ImageFormat.NV21))
p.setPreviewFormat(ImageFormat.NV21);
else if (formats.contains(ImageFormat.YV12))
p.setPreviewFormat(ImageFormat.YV12);
return p;
}
颜色格式默认就是NV21,告诉opencv你使用的颜色格式
public static int preview2deocode(int previewFormat) {
Log.d("px", "previewFormat=" + previewFormat);
int decodeColor = 0;
//获取相机预览的颜色格式,暂时只有这两种
if (previewFormat == ImageFormat.NV21) {
decodeColor = NativeUtils.NV21;
} else if (previewFormat == ImageFormat.YV12) {
decodeColor = NativeUtils.YV12;
}
return decodeColor;
}
然后再设置一些相机其他参数
public static Camera.Parameters setupCameraParams(Camera camera, Point size) {
//处理预览像素,帧率,颜色格式
Camera.Parameters p = showSupportPreviewSize(camera, size);
//设置简单的聚焦,白平衡,闪光灯等等
p.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
p.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
p.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
p.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
camera.setParameters(p);
camera.setDisplayOrientation(90); // Portrait mode
return p;
}
当然最好是,询问设备是否支持这些特性再设置,聚焦模式还可以自建线程聚焦。
怎么启动预览到surfaceview略过,主要是在surfaceChanged或surfaceCreate添加一个预览回调
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
byte[] previewBuf = new byte[mPreviewSize.x * mPreviewSize.y * 3 / 2];
camera.addCallbackBuffer(previewBuf);
camera.setPreviewCallback(this);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (state == State.Recorde) {
recordTask.captureImg(data, mPreviewSize.x, mPreviewSize.y);
}
}
录像时VideoRecordTask中captureImg来持续捕获来自相机的预览数据
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class VideoRecordTask {
public void captureImg(byte[] data, int w, int h) {
Date date = new Date();
NativeUtils.drawText(data, w, h, dateFormat.format(date));
frameInfo.flags = MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
frameInfo.presentationTimeUs = (date.getTime() - videoCreateTime) * 1000;
putIn(data, frameInfo);
}
这个方法再涉及的方法通过名字应该可以看出来含义了,在源码中放出。
NativeUtils这是对接NDK的类了,这些都是根据业务需求来写的,并非通用的方法,根据我的需求,我要想绘制字体,得依次先使用fixFontFile,fixTextArea,fixColorFormat来初始化配置。
public class NativeUtils {
static {
System.loadLibrary("dxtx");//视频水印库
}
public static int NV21 =21,NV12=22,YV12=19,I420=20;
/**
* 测试接口,ndk里面包含各种加水印的方法
* @param imagePath
* @param data
* @param text
* @return
*/
public static native String testImg(String imagePath, int[] data, String text);
/**
* 用一段文字预算文字区域的大小,以方便生成背景
* @param text
*/
public static native void fixTextArea(String text);
/**
* 设置yuv的颜色输入格式和输出,@{link ColorFormat}
* @param decodeColor
* @param encodeColor
*/
public static native void fixColorFormat(int decodeColor, int encodeColor);
/**
* 绘制文字
* @param data yuv数据,来自视频解码.绘制后data数据被修改
* @param w
* @param h
* @param text 需要绘制的文字
*/
public static native void drawText(byte[] data, int w, int h, String text);
/**
* 释放内存
*/
public static native void release();
/**
* 初始化字体文件,ndk内部会初始化字体大小,间距等属性
* @param ttfPath
*/
public static native void fixFontFile(String ttfPath);
}
我集成了typefree库,源码已在jni目录下,我也不关心这个库是什么原理了,只知道他是加载字体的就好了。抄的网上的集成成功的例子,通过编写android.mk,application.mk,命令ndk-build打包成libft2.a,再加入我自己的dxtx.so库。application.mk
APP_ABI := armeabi,arm64-v8a
讲道理应该要提供所有abi的,包括build.gradle里面,也应该生成all abi。
顺便科普一下android 手机架构abi.
armeabi兼容大部分手机
arm64-v8a是64位的,有这个架构的手机使用这里面的so,会加快计算速度。支持arm64-v8a的手机找不到arm64-v8a文件夹最终会兼容使用armeabi。
x86我只知道x86架构的模拟器会用,同理x86_64也是64位的,
mips系列还不知道什么手机在用。
ndk {
abiFilters "armeabi","arm64-v8a"
}
demo地址