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