# zlm4j_FFI
**Repository Path**: IFinal/zlm4j_ffi
## Basic Information
- **Project Name**: zlm4j_FFI
- **Description**: 用Java FFM API(Project Panama),完成对ZLMediaKit 库调用,让java对视频开发更简单
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2026-06-06
- **Last Updated**: 2026-06-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# zlm4j_FFI
[](LICENSE)
## 介绍
zlm4j_FFI 是一个基于 Java FFM API(Project Panama)实现的 ZLMediaKit 库调用方案。通过 Java 22+ 提供的 FFM 接口,可以实现对 ZLMediaKit 原生库的高效调用,让 Java 开发视频流媒体应用更加简单便捷。
相比传统的 JNA 调用方式,FFM API 提供更接近原生的性能表现,能够更直接地操作内存和调用 native 函数。
可以用来编写JT1078流媒体服务器和GB28181视频服务器
## 软件架构
| 组件 | 说明 |
|------|------|
| **JDK 版本** | JDK 22+(推荐 JDK 25) |
| **依赖库** | ZLMediaKit(原生动静态库) |
| **构建工具** | Maven |
## 功能特性
- ✅ HTTP/RTSP/RTMP/RTC 服务器管理
- ✅ 媒体播放器(MK_PLAYER)
- ✅ 代理播放器(MK_PROXY_PLAYER)
- ✅ 媒体录制(MP4/TS)
- ✅ 流媒体推流(Pusher)
- ✅ RTP 服务器
- ✅ WebRTC 支持
- ✅ 完整的回调事件机制(60+ 回调接口)
- ✅ 跨平台支持(Windows/Linux/macOS)
## 项目结构
```
src/main/java/com/sky/zlm4j/
├── core/ # 核心 FFM 绑定
│ ├── InitEnv.java # 库加载与环境初始化
│ ├── ZLMApi.java # ZLMediaKit API 封装
│ └── ZLMediaKit.java # ZLMediaKit C库函数绑定
├── structure/ # 数据结构封装
│ ├── MK_CONFIG.java # 配置结构
│ ├── MK_EVENTS.java # 事件回调结构
│ ├── MK_FRAME.java # 帧数据结构
│ ├── MK_INI.java # 配置文件操作
│ ├── MK_MEDIA.java # 媒体源管理
│ ├── MK_PLAYER.java # 播放器
│ ├── MK_PROXY_PLAYER.java # 代理播放器
│ ├── MK_TRACK.java # 音视频轨道
│ └── CodecArgs.java # 编解码参数
├── callback/ # 回调接口定义(60+)
│ ├── IMKPlayCallBack.java
│ ├── IMKPublishCallBack.java
│ └── ...
├── config/
│ └── MK_EVENTS.java # 全局事件配置
├── Util/
│ └── Util.java # 工具类
└── WebRTCController.java # WebRTC 控制器
```
## 快速开始
### 环境要求
- JDK 22 或更高版本
- ZLMediaKit 动态库(项目已内置各平台预编译库)
### 代理流案例
```
package com.sky.zlm4j;
import com.sky.zlm4j.callback.IMKNoReaderCallBack;
import com.sky.zlm4j.callback.IMKProxyPlayerCallBack;
import com.sky.zlm4j.callback.IMKStreamChangeCallBack;
import com.sky.zlm4j.config.MK_CONFIG;
import com.sky.zlm4j.config.MK_EVENTS;
import com.sky.zlm4j.core.ZLMApi;
import com.sky.zlm4j.structure.MK_INI;
import com.sky.zlm4j.structure.*;
import java.lang.foreign.MemorySegment;
import static com.sky.zlm4j.core.ZLMApi.*;
/**
* MK_PROXY_PLAYER 独立测试类
*/
public class MK_PROXY_PLAYER_Test {
public static void main(String[] args) {
// 使用一个全局 Arena,整个程序生命周期
// 1. 初始化环境
MK_CONFIG mk_config = new MK_CONFIG(8, 1, 1, null, 0, 0, null, 0, null, null);
mk_config.mk_env_init(mk_config);
// 2. 配置服务器
MK_INI mkIni = ZLMApi.mk_ini_default();
mkIni.mk_ini_set_option("general.mediaServerId", "JMediaServer");
mkIni.mk_ini_set_option("http.notFound", "
Media Server V1.0 By LiDaoFu
");
mkIni.mk_ini_set_option_int("protocol.auto_close", 0);
mkIni.mk_ini_set_option_int("general.streamNoneReaderDelayMS", 10000);
mkIni.mk_ini_set_option_int("general.maxStreamWaitMS", 30000);
mkIni.mk_ini_set_option_int("protocol.enable_ts", 1);
mkIni.mk_ini_set_option_int("protocol.enable_hls", 1);
mkIni.mk_ini_set_option_int("protocol.enable_fmp4", 1);
mkIni.mk_ini_set_option_int("protocol.enable_rtsp", 1);
mkIni.mk_ini_set_option_int("protocol.enable_rtmp", 1);
mkIni.mk_ini_set_option_int("protocol.enable_mp4", 1);
mkIni.mk_ini_set_option_int("protocol.enable_hls_fmp4", 1);
mkIni.mk_ini_set_option_int("protocol.enable_audio", 1);
mkIni.mk_ini_set_option_int("protocol.mp4_as_player", 1);
mkIni.mk_ini_set_option_int("protocol.mp4_max_second", 3600);
mkIni.mk_ini_set_option("http.rootPath", "D:/www");
mkIni.mk_ini_set_option("protocol.mp4_save_path", "D:/www");
mkIni.mk_ini_set_option("protocol.hls_save_path", "D:/www");
mkIni.mk_ini_set_option_int("protocol.hls_demand", 0);
mkIni.mk_ini_set_option_int("protocol.rtsp_demand", 0);
mkIni.mk_ini_set_option_int("protocol.rtmp_demand", 0);
mkIni.mk_ini_set_option_int("protocol.ts_demand", 0);
mkIni.mk_ini_set_option_int("protocol.fmp4_demand", 0);
// 3. 设置事件回调
MK_EVENTS mkEvents = new MK_EVENTS();
mkEvents.on_mk_media_changed = IMKStreamChangeCallBack.of((regist, mkMediaSource) -> {
System.out.println("状态: " + (regist == 1 ? "注册" : "注销"));
System.out.println("app: " + mkMediaSource.mk_media_source_get_app());
System.out.println("stream: " + mkMediaSource.mk_media_source_get_stream());
});
mkEvents.on_mk_media_no_reader = IMKNoReaderCallBack.of((sender) -> {
System.out.println("无人观看,关闭流");
try {
sender.mk_media_source_close(true);
} catch (Throwable e) {
e.printStackTrace();
}
});
mkEvents.mk_events_listen();
// 4. 启动服务器
mk_http_server_start(7788, false);
mk_rtsp_server_start(554, false);
mk_rtmp_server_start(1935, false);
mk_rtc_server_start(8000);
// 5. 创建配置
MK_INI option = ZLMApi.mk_ini_create();
option.mk_ini_set_option_int("enable_mp4", 0);
option.mk_ini_set_option_int("enable_audio", 0);
option.mk_ini_set_option_int("enable_fmp4", 1);
option.mk_ini_set_option_int("enable_ts", 1);
option.mk_ini_set_option_int("enable_hls", 1);
option.mk_ini_set_option_int("enable_rtsp", 1);
option.mk_ini_set_option_int("enable_rtmp", 1);
option.mk_ini_set_option_int("mp4_max_second", 3600);
option.mk_ini_set_option_int("add_mute_audio", 0);
option.mk_ini_set_option_int("auto_close", 0);
// 6. 创建代理播放器 - 使用全局 Arena
System.out.println("创建代理播放器...");
MK_PROXY_PLAYER proxy = ZLMApi.mk_proxy_player_create2("__defaultVhost__", "live", "test_stream", option);
option.mk_ini_release();
if (proxy == null) {
System.err.println("代理播放器创建失败!");
return;
}
// 7. 设置回调 - 使用全局 Arena
IMKProxyPlayerCallBack onPlayResult = (pUser, err, what, sys_err) -> {
if (err == 0) {
System.out.println("✅ 播放成功");
} else {
System.out.println("❌ 播放失败: " + what);
}
};
proxy.mk_proxy_player_set_on_play_result(onPlayResult, MemorySegment.NULL, null);
// 8. 设置关闭回调 - 注意不要立即释放
IMKProxyPlayerCallBack onClose = (pUser, err, what, sys_err) -> {
System.out.println("设置关闭回调");
// 13. 再次查询
int readerCount = proxy.mk_proxy_player_total_reader_count();
System.out.println("最终读者数量: " + readerCount);
};
proxy.mk_proxy_player_set_on_close(onClose, MemorySegment.NULL);
// 9. 开始播放 - 使用全局 Arena
String sourceUrl = "rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid";
System.out.println("开始拉流: " + sourceUrl);
proxy.mk_proxy_player_play(sourceUrl);
// 11. 查询读者数量(这个调用是安全的)
int readerCount = proxy.mk_proxy_player_total_reader_count();
System.out.println("当前读者数量: " + readerCount);
try {
Thread.sleep(60000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
// 14.停止
mk_stop_all_server();
}
}
```