android ndk之opencv+MediaCodec硬编解码来处理视频水印学习笔记
- android视频处理学习笔记。以前android增加时间水印的需求,希望多了解视频编解码,直播,特效这一块,顺便熟悉NDK。
openvc能干什么?为什么要集成openvc?
openvc是一套计算机视觉处理库,直白地讲,就是处理图片和识别图片的。有自己的算法后,可以做一些高级的东西,比如机器视觉,AR,人脸识别等。
android方面自带了bitmap和canvas处理库,底层还可以通过opengl硬件加速,其实渲染处理的速度也很快,甚至很多地方优过opencv。
但是,android针对视频数据,相机采集数据的yuv格式缺少api,或者yuv转rgb转bitmap,这些相互转化的过程也耗时巨大,而我发现opencv是对数据直接进行处理的,一个420sp2bgr的转换耗时极少,图像处理完成以后直达yuv,然后就可以给编码器喂数据了。
MediaCodec和ffmpeg
mediaCodec是系统自带的,原则上有GPU的手机就会有MediaCodec提供出来,使用GPU硬编码,理论上应该比软编码ffmpeg快。
YUV420p,YUV420sp,NV21,YV12
不同的系统的中对应还有别名,简单理解为数据排列的方式不同,详询度娘。
freetype
一款处理字体的C++库,可以加载ttf字体。opencv有默认字体,但是不支持中文,所以需要中文的要集成freetype。用ffmpeg处理中文也需要集成
openvc的集成
opencv支持android版,有java代码,但是集成java代码对我没有意义。首先下载opencv4android,在studio为项目include c++ support,或者在build.gradle添加externalNativeBuild
defaultConfig {
applicationId "com.dxtx.codec"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags ""
}
}
ndk {
abiFilters "armeabi","arm64-v8a"
}
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
版本太旧的android studio并不支持cmakeLists来编译ndk,我使用的是
classpath ‘com.android.tools.build:gradle:3.0.1’,
gradle版本gradle-4.1-all.zip
NDK版本是r15 NDK版本太旧的话,abi编译不全,不能编译64位。我想很多人并不像我这样,都使用了最新的吧
CMakeLists.txt中引入opencv的native/jni代码配置如下
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
dxtx
SHARED
src/main/jni/bs.cpp
)
set(OpenCV_DIR C:/Users/user/Downloads/opencv-3.4.2-android-sdk/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)
target_include_directories(
dxtx
PRIVATE
C:/Users/user/Downloads/opencv-3.4.2-android-sdk/OpenCV-android-sdk/sdk/native/jni/include
)
find_library(
Log-lib
Log )
target_link_libraries(
dxtx
${OpenCV_LIBS}
${Log-lib} )
opencv配置的是绝对路径,放在电脑里,而没有放在项目下,毕竟opencv只是编译一下,而且整个文件夹太大。
配置好以后build一下,就可以在jni里面使用opencv了。这里示范为yuv420sp的数据添加文字的关键代码
先建一个java类
public class Utils {
static {
System.loadLibrary("dxtx");
}
public static native void drawText(byte[] data, int w, int h, String text);
}
drawText方法报红,是C代码没有实现,可以alt+enter快捷键自动补全实现。
bs.cpp代码
#include <jni.h>
#include <android/log.h>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
extern "C"
JNIEXPORT void JNICALL
Java_com_dxtx_test_Utils_drawText(JNIEnv *env, jclass type, jbyteArray data_, jint w,
jint h, jstring text_) {
jbyte *data = env->GetByteArrayElements(data_, NULL);
const char *text = env->GetStringUTFChars(text_,0);
Mat dst;
Mat yuv(h * 3 / 2, w, CV_8UC1, (uchar *) data);
//转换为可以处理的bgr格式
cvtColor(yuv, dst, CV_YUV420sp2BGR);
// printMat(dst);
//旋转图像
rotate(dst, dst, ROTATE_90_CLOCKWISE);
w = dst.cols;
h = dst.rows;
int baseline;
//测量文字的宽高
Size size = getTextSize(text, CV_FONT_HERSHEY_COMPLEX, 0.6, 1, &baseline);
CvRect rect = cvRect(0, 0, size.width + 10, size.height * 1.5);
//灰色背景
rectangle(dst,rect,Scalar(200,200,200),-1,8,0);
//文字
putText(dst, text, Point(0, size.height), FONT_HERSHEY_SIMPLEX,0.6, Scalar(255, 255, 255),1, 8, 0);
//转换为YV12
cvtColor(dst, dst, CV_BGR2YUV_YV12);
// printMat(dst);
//因为YV12 和NV21格式不同,需要重组UV分量
jbyte *out = new jbyte[w * h / 2];
int ulen = w * h / 4;
for (int i = 0; i < ulen; i += 1) {
//从YYYYYYYY UUVV 到YYYYYYYY VUVU
out[i * 2 + 1] = dst.at<uchar>(w * h + ulen + i);
out[i * 2] = dst.at<uchar>(w * h + i);
}
//返回到原来的数据
env->SetByteArrayRegion(data_, 0, w * h, (const jbyte *) dst.data);
env->SetByteArrayRegion(data_, w * h, w * h / 2, out);
}
这段代码的方法从YUV,渲染文字到YUV一气呵成了,节省了很多时间,在arm64-v8a架构手机CPU下,耗时只要30ms左右了
然后是用原来mediaCodec的方案进行编码,这个步骤可以跟之前差别不大。
后来集成了freetype,拥有了加中文水印的能力,工序多了一个,代码耗时也多了一点,但就增加时间水印来说freetype没啥必要,时间里面不含中文。
以后尝试一下直接从相机捕获数据,缓冲处理再直接录制视频的方案可行性。