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

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

demo地址