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
targetSdkVersion22 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( dxtxPRIVATE 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
nativevoid 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没啥必要,时间里面不含中文。

以后尝试一下直接从相机捕获数据,缓冲处理再直接录制视频的方案可行性。

demo地址 <https://gitee.com/dxtx100/mediaCodecDemo>

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信