项目背景:
领导某天叫到我,让我做一下“双录”的可行性研究,建议语言使用Java,然后我们就开始进行实验,看看能否利用Java,实现本功能。
要求如下:
1.要求能满足录音录像,并且声画同步
2.要求可以跨平台,代码可以兼容Linux,Windows,以及国产服务器
双录,特指录音录像,比如银行中某种业务,需要将客户与客户经理的一些话语进行录制下来。
一开始我们通过Java实现了录音录像,但是用Java获取设备列表出现了问题.获取到的设备列表不是真实的数量,比如笔记本,获取的麦克风列表居然有7个,但是这是商用项目,不能给用户展示7个设备吧.用户肯定会有意见的.
然后我们就把期望,放到了C语言之上。
但是一些业务模块还是需要 Java 去实现,那怎么办呢,我们就想到了,混合开发或者Socket通信,此处主要讲混合开发。
混合开发我们了解到的能用的技术有两种,分别是JNI与 JNA
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。
JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。
JNI与JNA最大的区别是:
JNI可以支持C与Java相互之间调用
JNA只支持Java调用C
但是相对来说,JNA上手比较快,JNI操作起来比较复杂。
但是我们为了可扩展性,还是选择了JNI。
想实现JNI,由以下几个步骤
定义JNI接口类,方法名由 native 进行修饰 如下
package com.app.modules.xxb.jc;
public class AddJni {
public static native int Add(int X,int Y);
}
然后将代码进行编译,编译成.class文件
命令为:javac AddJni.java文件
然后将代码进行转换成.h文件
命令为:javah -encoding UTF-8 -classpath . -jni com.app.modules.xxb.jc.AddJni
需要注意的是,我这边是SpringBoot项目,执行的目录是在java文件夹下面。
然后跟C同事要C的源码文件
#include "com_app_modules_xxb_jc_AddJni.h"
JNIEXPORT jint JNICALL Java_com_zk_demo_jni_AddJni_Add
(JNIEnv * env, jclass jc, jint x, jint y){
return x+y;
}
这里要注意 include地址
然后将C文件编译成.so文件
Windows环境下使用.dll文件,Linux下面是用.so文件
编译命令如下:gcc -fPic -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libadd.so add.c
然后会获得so文件,这是你Java调用C的库文件
下面上干货代码
控制层代码
@ResponseBody
@ApiOperation(value = "", consumes = "application/json")
@RequestMapping(value = "/videoS5",method = {RequestMethod.POST })
public ServerResponse<Integer> videoS5(@RequestBody AddDto dto){
return ServerResponse.ok(addService.add(dto));
}
业务层代码
public interface AddService {
Integer add(AddDto addDto);
}
@Service
@Slf4j
public class AddServiceImpl implements AddService {
@Override
public Integer add(AddDto addDto) {
//根据操作系统判断,如果是linux系统则加载c++方法库
String systemType = System.getProperty("os.name");
String ext = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";
if(ext.equals(".so")) {
try {
NativeLoader.loader( "native" );
} catch (Exception e) {
System.out.println("加载so库失败");
}
}
log.info("loaded");
return AddJni.Add(addDto.getX(),addDto.getY());
}
}
工具类代码
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class NativeLoader {
/**
* 加载项目下的native文件,DLL或SO
*
* @param dirPath 需要扫描的文件路径,项目下的相对路径
* @throws IOException
* @throws ClassNotFoundException
*/
public synchronized static void loader(String dirPath) throws IOException, ClassNotFoundException {
Enumeration<URL> dir = Thread.currentThread().getContextClassLoader().getResources(dirPath);
// 获取操作系统类型
String systemType = System.getProperty("os.name");
//String systemArch = System.getProperty("os.arch");
// 获取动态链接库后缀名
String ext = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";
while (dir.hasMoreElements()) {
URL url = dir.nextElement();
String protocol = url.getProtocol();
if ("jar".equals(protocol)) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
// 遍历Jar包
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String entityName = jarEntry.getName();
if (jarEntry.isDirectory() || !entityName.startsWith(dirPath)) {
continue;
}
if (entityName.endsWith(ext)) {
loadJarNative(jarEntry);
}
}
} else if ("file".equals(protocol)) {
File file = new File(url.getPath());
loadFileNative(file, ext);
}
}
}
private static void loadFileNative(File file, String ext) {
if (null == file) {
return;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
if (null != files) {
for (File f : files) {
loadFileNative(f, ext);
}
}
}
if (file.canRead() && file.getName().endsWith(ext)) {
try {
System.load(file.getPath());
System.out.println("加载native文件 :" + file + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + file + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
/**
* @throws IOException
* @throws ClassNotFoundException
* @Title: scanJ
* @Description 扫描Jar包下所有class
*/
/**
* 创建动态链接库缓存文件,然后加载资源文件
*
* @param jarEntry
* @throws IOException
* @throws ClassNotFoundException
*/
private static void loadJarNative(JarEntry jarEntry) throws IOException, ClassNotFoundException {
File path = new File(".");
//将所有动态链接库dll/so文件都放在一个临时文件夹下,然后进行加载
//这是应为项目为可执行jar文件的时候不能很方便的扫描里面文件
//此目录放置在与项目同目录下的natives文件夹下
String rootOutputPath = path.getAbsoluteFile().getParent() + File.separator;
String entityName = jarEntry.getName();
String fileName = entityName.substring(entityName.lastIndexOf("/") + 1);
System.out.println(entityName);
System.out.println(fileName);
File tempFile = new File(rootOutputPath + File.separator + entityName);
// 如果缓存文件路径不存在,则创建路径
if (!tempFile.getParentFile().exists()) {
tempFile.getParentFile().mkdirs();
}
// 如果缓存文件存在,则删除
if (tempFile.exists()) {
tempFile.delete();
}
InputStream in = null;
BufferedInputStream reader = null;
FileOutputStream writer = null;
try {
//读取文件形成输入流
in = NativeLoader.class.getResourceAsStream(entityName);
if (in == null) {
in = NativeLoader.class.getResourceAsStream("/" + entityName);
if (null == in) {
return;
}
}
NativeLoader.class.getResource(fileName);
reader = new BufferedInputStream(in);
writer = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
while (reader.read(buffer) > 0) {
writer.write(buffer);
buffer = new byte[1024];
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close();
}
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
System.load(tempFile.getPath());
System.out.println("加载native文件 :" + tempFile + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + tempFile + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
然后去调用接口去尝试就可以了。