Java调用FFmpeg动态库
作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音
XXX
Java 是不能直接调用 DLL 里面的 C 函数的,它需要通过一个中间层,也就是通过 JNI 来实现调用。

首先,我们创建一个 ffmpegNative.java 文件,如下:
class ffmpegNative {
public static native int avformat_version();
}
这里的 avformat_version() 是一个 Java 的函数,最重要的是 native 关键字,这个关键字代表 avformat_version() 函数会去调原生的 C 函数,那这个原生 C 函数的名称是什么呢?
我们可以用 javac 命令来生成 ffmpegNative.java 对应的 C 语言头文件来查看函数名。如下:
javac -h . ffmpegNative.java
执行完之后,就可以在当前目录看到 ffmpegNative.h,这是 C 语言的头文件,如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ffmpegNative */
#ifndef _Included_ffmpegNative
#define _Included_ffmpegNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ffmpegNative
* Method: avformat_version
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_ffmpegNative_avformat_1version
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
上面这段代码有一些防止重复引入、兼容 C++ 的代码,我们把它们删掉,会看得更清楚,删掉之后如下:
#include <jni.h>
JNIEXPORT jint JNICALL Java_ffmpegNative_avformat_1version
(JNIEnv *, jclass);
可以看到,java 虚拟机给我们定义的 C 语言函数名称是 Java_ffmpegNative_avformat_1version(),现在我们只需要实现这个函数就可以了。
这个 Java_ffmpegNative_avformat_1version 函数命名是有规律的,具体请阅读《Java核心技术 卷二》第12章
现在我们创建一个新的文件 ffmpegNative.c,内容如下:
#include "ffmpegNative.h"
#include "libavformat/avformat.h"
JNIEXPORT jint JNICALL Java_helloNative_avformat_1version(JNIEnv *env, jclass cl)
{
int ver = avformat_version();
return ver;
};
这就是 Java 调用 FFmpeg dll 动态库函数的流程,如下:

完整的项目代码读者可以在 GitHub 下载。
现在让我们来编译这个项目代码。
cl /c ffmpegNative.c ^
/I "C:\Program Files\Java\jdk-22\include" ^
/I "C:\Program Files\Java\jdk-22\include\win32" ^
/I "E:\ffmpeg_principle_volume1\source\code\ffmpeg_java\build64\ffmepg-n4.4.1-msvc\include"
提醒:jni.h 头文件是在 jdk 目录 C:\Program Files\Java\jdk-22\include 里面的。
link.exe /DLL /DEBUG ^
/LIBPATH "E:\ffmpeg_principle_volume1\source\code\ffmpeg_java\build64\ffmepg-n4.4.1-msvc\bin\avformat.lib" ^
/EXPORT:Java_ffmpegNative_avformat_1version ^
/OUT:ffmpegNative.dll ffmpegNative.obj
上面的命令执行完之后,我们就能拿到 ffmpegNative.dll 这个动态库,读者可以使用 dumpbin 来查看依赖,可以看到 ffmpegNative.dll 是依赖 avformat-58.dll
dumpbin /DEPENDENTS ffmpegNative.dll

然后 Main.java 就可以调用 ffmpegNative.dll 动态库了,因为这个动态库完全是按照 JNI 标准编译出来的,里面的函数可以被 Java 虚拟机调用。
Main.java 文件的代码如下:
class Main {
public static void main(String[] args) {
int ver = ffmpegNative.avformat_version();
System.out.println(ver);
}
static
{
System.loadLibrary("ffmpegNative");
}
}
我简单讲一下这段代码的含义,System.loadLibrary("ffmpegNative") 是负责加载 dll 动态库到进程的。
然后 ffmpegNative.avformat_version() 是 Java 类的函数,这个函数会去调 JNI C 函数 Java_helloNative_avformat_1version(),然后 Java_helloNative_avformat_1version() 再去调 ffmpeg 里面的 avformat_version()。
整个过程 JNI 的 C 函数起到了一个桥接的作用。
我们继续编译项目里面的 Main.java,如下:
javac Main.java
# 设置环境变量,让 Java 运行时可以找到动态库。
set path=%path%;E:\ffmpeg_principle_volume1\source\code\ffmpeg_java\build64\ffmepg-n4.4.1-msvc\bin;
java Main
输出的结果如下图:

有另一种更方便的方法,叫 JNA(java-native-access),简单补充一下。
FFmpeg 源码里面也有一个 jni.h,这是干什么的?
参考资料:
1,《Java核心技术 卷二》第12章