# java-native-media
**Repository Path**: ppnt/java-native-mediajava-native-media
## Basic Information
- **Project Name**: java-native-media
- **Description**: java音视频处理优化库
- **Primary Language**: Unknown
- **License**: MIT
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 3
- **Forks**: 0
- **Created**: 2025-03-18
- **Last Updated**: 2026-03-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# java-native-media
音视频处理优化库
[[toc]]
---
# 项目背景
在传统的 Java 音视频处理方案中,开发者通常依赖调用 FFmpeg 命令行工具来实现音视频处理需求。但这种方式存在两个主要痛点:
1. **性能损耗**
调用外部进程会带来额外的进程间通信开销,从而影响整体处理效率。
2. **包体积膨胀**
为了功能完整性,往往需要捆绑 FFmpeg 二进制文件,导致最终应用包体积显著增加。
为了解决以上问题,本项目使用 JNI 技术,使 Java 能直接调用经过深度优化的 C 语言音视频处理库,在特定场景下实现高性能、低体积的音视频处理方案。
---
# 核心功能(已实现)
下面列出的功能都属于“已有功能”,包括最初实现的功能和后续新增/扩展的方法,它们已被整合为统一的能力集合,便于开发者快速查阅与使用:
* **MP3 智能分片**
按指定字节数分割 MP3,保证帧边界完整、播放正确,内存高效分流(适合大语言模型输入限制等场景)。
* **MP4 → MP3 提取**
从 MP4 视频中高效提取音频并转为 MP3,尽量保留音质,适用于语音识别、音频分析。
* **通用格式转换(容器/编码器映射)**
支持传入容器格式(如 `mp3`、`wav`)或编码器名称(如 `libmp3lame`、`aac`),内部自动映射并高效转换。
* **通用格式拆分(流拷贝、实时监控大小、无损拆分)**
对任意 FFmpeg 支持的格式进行按大小拆分,采用流拷贝,避免重编码,动态监控输出文件大小,保证数据无损。
* **支持格式查询**
返回当前 JNI 库支持的所有格式与编码器名称,方便参数选择。
* **HLS 相关功能(持久会话与即时追加)**
* 将 MP4 拆为 HLS 段并追加到指定 playlist(支持单次追加)。
* 持久 HLS 会话:初始化会话、追加 MP4 段、插入静音段、结束会话(写入 `#EXT-X-ENDLIST`)或直接释放会话资源。
* 列出当前活跃 HLS 会话(返回 JSON 信息)。
* **静音处理与拼接支持**
在转码或拼接流程中可插入指定时长的静音段,支持 `toMp3ForSilence` 等场景。
* **合并(stream copy)**
使用流拷贝方式合并多个媒体文件(前提:输入流参数兼容),高效且避免重新编码。
* **信息获取**
获取视频时长等元信息方法(`getVideoLength`)。
* **视频处理工具**
* 给视频添加右下角水印(支持中文,需 UTF-8 文本和字体文件)。
* 保存视频的最后一帧为图片(返回 0 表示成功,其他为错误码或 FFmpeg 错误码)。
* **初始化与库加载**
提供 `init()` 方法用于初始化和加载 native 库(建议应用启动时调用)。
---
# Maven 坐标
```xml
com.litongjava
java-native-media
1.0.0
```
---
# Java API(关键方法说明与示例)
### 初始化
首次使用NativeMedia类时会自动进行初始化,加载本地库与必要资源,无需显示调用
### MP3 智能分片
```java
String[] segments = NativeMedia.splitMp3("/data/input/long_audio.mp3", 25L * 1024 * 1024);
for (String s : segments) {
System.out.println("segment: " + s);
}
```
### MP4 转 MP3
```java
String out = NativeMedia.mp4ToMp3("/data/video/input.mp4");
if (out.startsWith("Error:")) {
System.err.println("转换失败:" + out);
} else {
System.out.println("输出:" + out);
}
```
### 通用转 MP3(含插入静音)
```java
String out1 = NativeMedia.toMp3("/data/audio/input.wav");
String out2 = NativeMedia.toMp3ForSilence("/data/audio/part.wav", 0.8 /* seconds */);
```
### 通用格式转换
```java
String out = NativeMedia.convertTo("/data/video/input.flv", "libmp3lame");
// 或者传入 "mp3" 作为容器名
```
### 通用拆分(任意格式)
```java
String[] parts = NativeMedia.split("/data/video/input.mp4", 10 * 1024 * 1024);
```
### HLS:一次性分片并追加到 playlist
```java
String result = NativeMedia.splitVideoToHLS("./data/hls/playlist.m3u8", "/data/video/scene1.mp4", "./data/hls/segment_%03d.ts", 6);
System.out.println(result); // 成功或错误信息
```
### HLS:持久会话(推荐用于长流程拼接)
```java
long session = NativeMedia.initPersistentHls("./data/hls/playlist.m3u8", "./data/hls/segment_%03d.ts", 1, 6);
// 追加段
NativeMedia.appendVideoSegmentToHls(session, "/data/video/part1.mp4");
NativeMedia.appendVideoSegmentToHls(session, "/data/video/part2.mp4");
// 插入静音段
NativeMedia.insertSilentSegment(session, 1.5);
// 查询会话列表(JSON)
String sessionsJson = NativeMedia.listHlsSession();
// 结束并写入 ENDLIST
NativeMedia.finishPersistentHls(session, "./data/hls/playlist.m3u8");
// 或者直接释放会话(不写 ENDLIST)
NativeMedia.freeHlsSession(session);
```
### 合并(stream copy)
```java
String[] inputs = { "/data/part1.ts", "/data/part2.ts" };
boolean ok = NativeMedia.merge(inputs, "/data/output/merged.mp4");
System.out.println("merge ok = " + ok);
```
### 其它实用方法
```java
double seconds = NativeMedia.getVideoLength("/data/video/input.mp4");
int ret = NativeMedia.saveLastFrame("/data/video/input.mp4", "/data/output/last_frame.jpg");
String res = NativeMedia.addWatermarkToVideo("/data/video/in.mp4", "/data/video/out_watermark.mp4", "版权信息", "/usr/share/fonts/truetype/arial.ttf");
```
---
# 返回值与错误处理
* 多数方法以字符串返回:成功返回输出路径或成功信息;失败通常以 `Error:` 前缀或非零状态码表示,请在业务代码中进行判定。
* `merge` 返回 `boolean`,`true` 表示合并成功(FFmpeg 退出码 0)。
* `saveLastFrame` 返回 `0` 表示成功;其它为 FFmpeg 错误码或自定义错误码。
* HLS 会话相关方法返回状态字符串或 session 指针(`long`)。会话结束后请务必调用 `finishPersistentHls` 或 `freeHlsSession` 做清理,避免资源泄露。
---
# 部署与依赖安装
> 构建/编译 JNI C 代码时需要开发包(headers 与 .so/.a 链接),运行时若使用预编译 native 库通常只需运行时库(非 `-dev` 包)。下面按平台列出常见安装方法与建议。
### Debian / Ubuntu(构建与运行)
开发环境(包含头文件):
```bash
sudo apt-get update
sudo apt-get install -y build-essential cmake pkg-config openjdk-8-jdk \
libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libswresample-dev \
libmp3lame-dev
```
仅运行时(使用已编译 native 二进制):
```bash
sudo apt-get install -y ffmpeg libavcodec57 libavformat57 libavutil55 libswresample2 libmp3lame0
```
示例快捷命令(题中提及):
```bash
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev libswresample-dev libmp3lame-dev -y
```
### Fedora / CentOS / RHEL(通过 RPM Fusion / EPEL 提供 FFmpeg)
* 启用 EPEL 与 RPM Fusion,然后安装 `ffmpeg` 与开发包(`ffmpeg-devel`、`lame-devel` 等)。
* 示例(请根据发行版版本调整):
```bash
sudo yum install -y epel-release
# 添加 RPM Fusion 并安装 ffmpeg/开发包,具体命令参考 rpmfusion.org
sudo yum install -y ffmpeg ffmpeg-devel lame-devel
```
### Alpine Linux
```bash
# 运行时
sudo apk add ffmpeg
# 构建/开发
sudo apk add build-base cmake pkgconfig openjdk11-jdk ffmpeg-dev libmp3lame-dev
```
### macOS(Homebrew)
```bash
xcode-select --install
brew install pkg-config cmake openjdk ffmpeg
```
Homebrew 的 `ffmpeg` 通常已包含主流编码器(如 libmp3lame);如需自定义编译请参阅 Homebrew 公式。
### Windows
* 运行与测试(推荐使用 Chocolatey 或 Scoop):
```powershell
choco install ffmpeg
# 或
scoop install ffmpeg
```
* 构建:推荐使用 MSYS2(mingw-w64)或 Visual Studio + vcpkg,根据选择安装相应的 ffmpeg/lame 开发包。
### Docker(统一环境示例)
以下 Dockerfile 基于 JDK 8,并安装必要依赖(FFmpeg、libmp3lame0):
```dockerfile
FROM litongjava/jdk:8u411-stable-slim
RUN apt-get update && apt-get install -y ffmpeg libmp3lame0
WORKDIR /app
COPY target/java-native-media-test-1.0.0.jar /app/
CMD ["java", "-jar", "java-native-media-test-1.0.0.jar"]
```
构建镜像命令:
```bash
docker build -t litongjava/java-native-media-test:1.0.0 .
```
运行镜像示例:
```bash
docker run --rm --name java-native-media-test -p 80:80 litongjava/java-native-media-test:1.0.0
```
---
## 测试 Controller(基于 tio-boot)
为了便于集成到 HTTP 服务中,本项目在 [java-native-media-test](https://github.com/litongjava/java-native-media-test) 开源项目中提供了一系列基于 tio-boot 的 HTTP 请求处理器。下面是各功能的 Controller 示例,开发者可直接参考并集成到自己的项目中。
### 1. MP3 分片 Controller
```java
package com.litongjava.test.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.hutool.FilenameUtils;
import com.litongjava.tio.utils.http.ContentTypeUtils;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.media.NativeMedia;
import com.litongjava.annotation.RequestPath;
import java.io.File;
@RequestPath("/media/splitMp3")
public class SplitMp3Controller {
public HttpResponse splitMp3(HttpRequest request) {
UploadFile uploadFile = request.getUploadFile("file");
if (uploadFile == null) {
return Resps.error("文件上传失败!");
}
byte[] data = uploadFile.getData();
new File("upload").mkdirs();
File file = new File("upload", uploadFile.getName());
FileUtil.writeBytes(data, file);
// 默认分片大小 25MB
long splitSize = 25 * 1024 * 1024;
String[] segments = NativeMedia.splitMp3(file.getAbsolutePath(), splitSize);
StringBuilder sb = new StringBuilder("生成的分片文件:
");
for (String seg : segments) {
sb.append(seg).append("
");
}
return Resps.html(sb.toString());
}
}
```
### 2. MP4 转 MP3 Controller
```java
package com.litongjava.test.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.media.NativeMedia;
import com.litongjava.annotation.RequestPath;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.tio.utils.hutool.FileUtil;
import java.io.File;
@RequestPath("/media/mp4ToMp3")
public class Mp4ToMp3Controller {
public HttpResponse mp4ToMp3(HttpRequest request) {
UploadFile uploadFile = request.getUploadFile("file");
if (uploadFile == null) {
return Resps.error("文件上传失败!");
}
byte[] data = uploadFile.getData();
new File("upload").mkdirs();
File file = new File("upload", uploadFile.getName());
FileUtil.writeBytes(data, file);
String result = NativeMedia.mp4ToMp3(file.getAbsolutePath());
if (result.startsWith("Error:")) {
return Resps.error("转换失败:" + result);
}
return Resps.text("转换成功!输出文件:" + result);
}
}
```
### 3. 通用格式转换 Controller
```java
package com.litongjava.test.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.media.NativeMedia;
import com.litongjava.annotation.RequestPath;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.tio.utils.hutool.FileUtil;
import java.io.File;
@RequestPath("/media/convertTo")
public class ConvertToController {
public HttpResponse convertTo(HttpRequest request) {
UploadFile uploadFile = request.getUploadFile("file");
if (uploadFile == null) {
return Resps.error("文件上传失败!");
}
byte[] data = uploadFile.getData();
new File("upload").mkdirs();
File file = new File("upload", uploadFile.getName());
FileUtil.writeBytes(data, file);
// 例如转换为 MP3(可以传入 "libmp3lame" 或 "mp3")
String outputPath = NativeMedia.convertTo(file.getAbsolutePath(), "libmp3lame");
if (outputPath.startsWith("Error:")) {
return Resps.error("转换失败:" + outputPath);
}
return Resps.text("转换成功!输出文件:" + outputPath);
}
}
```
### 4. 通用格式拆分 Controller
```java
package com.litongjava.test.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.media.NativeMedia;
import com.litongjava.annotation.RequestPath;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.tio.utils.hutool.FileUtil;
import java.io.File;
@RequestPath("/media/split")
public class SplitController {
public HttpResponse split(HttpRequest request) {
UploadFile uploadFile = request.getUploadFile("file");
if (uploadFile == null) {
return Resps.error("文件上传失败!");
}
byte[] data = uploadFile.getData();
new File("upload").mkdirs();
File file = new File("upload", uploadFile.getName());
FileUtil.writeBytes(data, file);
// 按 10MB 拆分
String[] segments = NativeMedia.split(file.getAbsolutePath(), 10 * 1024 * 1024);
StringBuilder sb = new StringBuilder("生成的拆分文件:
");
for (String seg : segments) {
sb.append(seg).append("
");
}
return Resps.html(sb.toString());
}
}
```
### 5. 支持格式查询 Controller
```java
package com.litongjava.test.controller;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.media.NativeMedia;
import com.litongjava.annotation.RequestPath;
@RequestPath("/media/supportFormats")
public class SupportFormatsController {
public HttpResponse supportFormats(HttpRequest request) {
String[] formats = NativeMedia.supportFormats();
StringBuilder sb = new StringBuilder("支持的格式和编码器列表:
");
for (String format : formats) {
sb.append(format).append("
");
}
return Resps.html(sb.toString());
}
}
```
---
### 测试
- /media/supportFormats
- /media/splitMp3
- /media/split
- /media/convertTo
- /media/mp4ToMp3
- /media/toMp3
---
# 构建 & 本地调试提示
1. JNI 编译时需要 JDK 的头文件(例如 `openjdk-devel` / `default-jdk`)。
2. 确保 FFmpeg 的开发包(`-dev` / `-devel` / MSYS2/vcpkg 对应包)可用,或在 CMake 中提供正确的 `pkg-config` 路径。
3. 运行时需让系统能找到 native 库(`LD_LIBRARY_PATH` / `DYLD_LIBRARY_PATH` / PATH)。
4. `addWatermarkToVideo` 使用中文水印时需传入支持中文的字体文件路径(UTF-8),否则可能乱码或无法渲染。
5. 若为多平台发布,建议为每个平台预构建 native 二进制并在 `NativeMedia.init()` 根据 `os.name`/`os.arch` 动态加载。
---
# 常见问题(FAQ)
* **只运行不编译是否需要 `-dev` 包?**
运行时通常只需运行时库(非 `-dev`)。若 native 库在运行时需要某些开发库的动态链接,请安装对应运行时包。编译 JNI 代码必须安装 `-dev` 包(头文件与静态链接符号)。
* **Windows 下如何获得 libmp3lame 的头文件?**
可通过 MSYS2 的 mingw 包或 vcpkg 获取,或下载 FFmpeg 源码并自行编译。
* **HLS 会话指针(sessionPtr)是否线程安全?**
sessionPtr 由 native 层维护,为安全起见应在 Java 层做并发保护或保证对同一会话的调用序列化;若需跨线程并发操作,建议在 native 层实现线程安全保护。
* **如何诊断转换失败?**
* 检查方法返回的错误字符串(通常包含 FFmpeg 的输出)。
* 确认系统 FFmpeg 库版本与 native 库编译时版本兼容。
* 检查文件读写权限与字体路径(中文水印)。
---
# 许可证与项目定位
本项目聚焦特定场景的垂直优化,不是通用多媒体处理框架。如需更多高级功能(GPU 加速、复杂滤镜、跨平台 GUI 等),建议参考 FFmpeg 原生工具链、gstreamer 或商业 SDK。