JNI实现的视频数据转换

  • JNI的相关介绍
  • Android手机摄像头采集的视频数据之间的转换

JNI的相关介绍

JNI (Java Native Interface) Java本地接口,实现了Java与其他语言之间的交互,使的Java跨平台的优势更加的明显。相比较来说,C语言的安全性以及执行效率要比Java这样的半解释语言要高出许多。如果在程序里需要调用C/C++底层的函数库,就必须借助JNI来实现了。在这里JNI百度百科,有着非常详细的介绍以及实例,我这里主要介绍一下 我自己程序相关的操作

Android手机摄像头采集的视频数据之间的转换

首先介绍一下JNI的操作步骤:

    1.编写带有native声明的方法的Java类(仅仅是声明即可);
    2.使用javac命令编译上不步所编写的Java类,生成class文件;
    3.使用Javah命令生成扩展名为h的头文件,javah+包名+类名;
    4.使用C/C++或者其他的编程语言实现本地方法;
    5.编译生成最后的SO库。

Android摄像头采集的数据我设置的是一下的格式:

    params.setPreviewFormat(ImageFormat.NV21);//NV21默认是NV21(YUV420SP)格式,当然你可以自己设置其他的格式。

采集的是是无法直接进行网络传输的,因为太大一帧图片好几M。所以需要经过后的压缩编码处理操作,由于这样的算法一般是比较复杂的,所以我们利用JNI采用C语言实现NV21转换成I420格式(在这里我假设你们已经熟悉了视频格式的一下知识,如果不了解,可以看看的之前的博客,当然我写的知识简单的说明了一下常见的视频格式,如果要深入研究,你可以查看最官方的文档,毕竟YUV家族太过于强大),现在根据JNI的操作步骤一步一步实现:

1.编写带有native声明的方法的Java类(仅仅是声明即可):

    Log.d(TAG,"----------->后置 竖屏 NV21-->I420");
    return NV21ToI420(data, vPrevWidth, vPrevHeight, false, 0);//90 在函数里,我需要这样的一个方法将NV21格式转换成I420格式,因为如果采用H.264标准进行视频数据的压缩编码的时候,必须给H.264S输入标准的I420格式的数据。

    private native byte[] NV21ToI420(byte[] yuvFrame, int width, int height, boolean flip, int rotate);

这里带有native关键字,表示本方法是本地声明的,仅仅是在此函数内部声明,不需要具体的实现。

    static {
    System.loadLibrary("yuv");
    System.loadLibrary("enc");
}

这里也是在刚才的函数里,这里的代码是写在Static类里的,主要是引入动态库(我们可以这样理解:我们的方法没有实现,但是我们在上面的代码中就直接使用了,就是return的那个地方, 所以必须在使用之前对它进行初始化)这里一般是以static块进行加载的。同时需要注意的是 System.loadLibrary();的参数“yuv,enc”都是动态库的名字。

2.使用javac命令编译上不步所编写的Java类,生成class文件:

这步就比较的简单了,直接将上面的Java文件进行编译声音相应的.class文件就可以了。
javac java文件名
java SrsEncoder 生成SrsEncoder.class文件
这些步骤都是一步步顺理成章下来的,如果前面出现错误,后面将一错到底。在编译的生成.class的时候一定要确保第一步的所有操作都是正确的。

3.使用Javah命令生成扩展名为h的头文件,javah+包名+类名;

这步就比较的重要了,将上一步生成的.class文件利用javah进行编译,具体操作如下:

        javah 包名 类名;
        javah com.xiaoai.ecode.SrsEncoder

上步初次编译的时候一般会遇见很多的错误,成功的话会生成一个com_xiaoai_ecode_SrsEncoder.h这里的生成的文件名是很长的,可以在下一步的.c文件中通过函数名映射表来实现简化。

1.找不到相应的方法 可能是文件名有问题,用c++编译c文件或者反过来,编译器当然找不到文件;
2.二次编译出现问题 一般是缓存的问题,以前运行c文件的时候回自动生成.obj文件,找到它删除,然后clean一下工程试试;
3.最后引用so库找不到 一般都是方法名是否是一致以及是不是选对了相应的编程语言(主要是c/c++),仔细检查一下,一般是没有问题的

4.使用C/C++或者其他的编程语言实现本地方法;

这就是写c/c++实际的程序,需要实现的就是上面为实现的方法,在本地仅建一个.c文件,然后进行编写,需要注意的是里面的方法名必须与实际调用的方法名称一致。
我上面需要实现的是将NV21数据转换成I420数据,方法具体如下:

    case FOURCC_NV21:
        src = sample + (src_width * crop_y + crop_x);
        src_uv = sample + (src_width * src_height)
        +((crop_y / 2) * aligned_src_width) + ((crop_x / 2) * 2);
        //Call NV12 but with u and v parameters swapped.
        r = NV12ToI420Rotate(src, src_width,
            src_uv, aligned_src_width,
            y, y_stride,
            v, v_stride,//交换
            u, u_stride,//交换
            crop_width, inv_crop_height, rotation);
        break;

如果细心地你可以发现,在我的程序里我将U和V进行了位置调换,正常使用的话应该YUV的顺序,但无奈我们的项目是运行在一个定制的Android设备上,正常顺序的情况下,红色变成蓝色,蓝色变成红色。亮度也可以正常的显示出来,仅仅是颜色发生了改变。曾经这个问题困扰了我很久,应该在手机上运行是没有问题的,可以以放到定制的这个设备就不行,无奈之下,我们决定修改底层的c函数,编译重新生成so库。首先是开始着手研究YUV的相关知识点。这不就将UV调换了位置,然后重新编译。解决了问题。

5.编译生成最后的SO库。

如果上面的所有步骤都没有问题的话,证明你就成功一半了。首先编写编译生成so库的Android.mk文件:

    LOCAL_PATH := $(call my-dir)
    ############# prebuilt ###############
    include $(CLEAR_VARS)
    LOCAL_MODULE := libyuv
    LOCAL_SRC_FILES := lib/libyuv.so
    include $(PREBUILT_SHARED_LIBRARY)

    include $(CLEAR_VARS)
    LOCAL_MODULE := libx264
    LOCAL_SRC_FILES := lib/libx264.a
    include $(PREBUILT_STATIC_LIBRARY)

    ############# build libenc ###########
    include $(CLEAR_VARS)

    LOCAL_MODULE := libenc
    LOCAL_SRC_FILES := libenc.cc
    LOCAL_CFLAGS    :=
    LOCAL_LDLIBS    := -llog
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/libyuv/jni/include $(LOCAL_PATH)/libx264
    LOCAL_STATIC_LIBRARIES := libx264
    LOCAL_SHARED_LIBRARIES := libyuv
    LOCAL_DISABLE_FORMAT_STRING_CHECKS := true
    include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH - 编译时的目录
(call.)src (call src),那么就会得到 src 目录的完整路径
include (CLEAR_VARS) -清除之前的一些系统变量  
LOCAL_MODULE - 编译生成的目标对象  
LOCAL_SRC_FILES - 编译的源文件  
LOCAL_C_INCLUDES - 需要包含的头文件目录  
LOCAL_SHARED_LIBRARIES - 链接时需要的外部库  
LOCAL_PRELINK_MODULE - 是否需要prelink处理  
include
(BUILD_SHARED_LIBRARY) - 指明要编译成动态库

在Android Studio编译环境下,安装NDK,方式很简单,你可以去官网上面下载后解压就可以直接使用;当然也可以在编译环境里直接下载安装file–>settings–>Appearance & Behavior–>System Settings–>Android SDK–>SDK tools 下面找到ndk安装就可以了,当然你需要记住上面的安装地址,记不住,使用的时候按照上面进入方法找到NDK点击就可以看见相应的地址了。
具体编译在Android Studio下进入命令行进入到ndk的安装目录下执行ndk-build.cmd,如果没有错误就可以在\libs\armeabi目录下生成相应的.so文件。如果这里没有,你可以在自己的项目里面好好找一找,找到后复制一份到\libs\armeabi下就可以了,最后运行程序如果可以了就说明成功了!

总结

刚接触这个JNI不建议直接用我的这个,建议你去官网上去看看那个最简单的HelloWorld的例子。这样可以提高你的学习的积极性。在这里我是希望帮助那些正在解决视频编解码的程序员。当然啦,有不足之处还望大家指出批评,我定当采纳更改,希望可以一起学习!明天就是中秋节了,这里提前祝福大家中秋节快乐,身体健康,万事如意!我们公司发月饼了,嘻嘻,回家吃月饼喽!我是Mr.小艾

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值