JNI实现的视频数据转换

    xiaoxiao2022-06-30  55

    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.小艾

    转载请注明原文地址: https://ju.6miu.com/read-1125751.html

    最新回复(0)