Ijkplayer重新编译支持h264裸流
Bilibili ijkplayer 支持 H.264 裸流播放全纪录(从源码编译到代码实现)
在移动端(Android/iOS)音视频开发中,Bilibili 开源的 ijkplayer 凭借其强大的 FFmpeg 底层支持和出色的性能,成为了许多开发者的首选播放器。然而,默认集成的预编译版本往往为了极致缩减包体积,裁剪掉了非标准容器封装的格式支持。
如果你需要通过网络 Socket、本地内存或纯文件播放 H.264 / H.265 裸流(Raw Annex-B Byte Stream) ,你会遇到 Invalid data found when processing input 的错误。本文将手把手带你完成从 环境搭建 -> 源码克隆 -> 修改白名单配置 -> 解决编译报错 -> 最终代码集成 的全流程。
一、 为什么必须重新编译?
官方预编译的 Release 版本默认采用的是 config/module-lite.sh 精简版配置。该配置通过 --disable-demuxers 彻底关闭了所有的解复用器,仅手动开启了 mp4、flv、hls 等几种主流的容器格式白名单。
因为 H.264 裸流没有像 MP4、FLV 那样的文件头/容器封装信息,播放器无法在初始阶段自动探测(Probe)出其确切格式、帧率或时长。要支持裸流解析,必须:
-
底层 FFmpeg 开启
h264的 Demuxer(解复用器) 、Parser(解析器) 和 Decoder(解码器)。 -
上层播放器代码中,强制指定输入格式(
iformat=h264),告知底层直接跳过容器探测,使用起始码(0x00 0x00 0x00 0x01或0x00 0x00 0x01)动态切割 NALU 单元进行解码。
二、 环境准备 (以 Ubuntu 为例)
编译 ijkplayer 的底层 C/C++ 交叉编译脚本极其依赖传统的 GCC 工具链,因此对 Android NDK 的版本要求非常严格。
1. 下载正确的 NDK 与 SDK
-
NDK 版本(至关重要): 绝对不能使用最新版本 (如 r21-r25)。Google 从 NDK r15 开始弃用 GCC,并在 r18 之后彻底删除了 GCC。推荐使用官方验证过的老版本:NDK r10e 或 NDK r14b。
-
SDK 版本: 使用 Android Studio 默认下载的 SDK 即可(通常位于
~/Android/Sdk)。
2. 配置 Ubuntu 永久环境变量
假设你的 android-ndk-r10e 存放在 /home/用户名/Android/Sdk/android-ndk-r10e 目录下。
打开终端,编辑环境变量配置文件:
Bash
nano ~/.bashrc
在文件最末尾添加以下两行:
Bash
# Android SDK 目录
export ANDROID_SDK=/home/用户名/Android/Sdk
# Android NDK 目录 (必须指向兼容的旧版 NDK 根目录)
export ANDROID_NDK=/home/用户名/Android/Sdk/android-ndk-r10e
保存并退出(Ctrl + O 回车,Ctrl + X 退出),然后刷新使其立即生效:
Plain
source ~/.bashrc
验证变量是否生效:
Plain
echo $ANDROID_NDK
三、 源码克隆与初始化
- 克隆 ijkplayer 主仓库:
Plain
git clone [https://github.com/bilibili/ijkplayer.git](https://github.com/bilibili/ijkplayer.git)
cd ijkplayer-android
- 切换到最新稳定分支并拉取底层源码:
Plain
# 检出代码(当前通常是 k0.8.8 分支)
git checkout -B latest k0.8.8
# 初始化并拉取对应版本的 FFmpeg 源码(这一步视网络环境可能需要较长时间)
./init-android.sh
四、 修改 FFmpeg 裁剪配置(核心步骤)
我们要基于精简版进行定制,把 H.264 裸流解复用器加入到"白名单"中。
- 进入配置目录:
Plain
cd config
- 修改
module-lite.sh脚本: 使用文本编辑器打开module-lite.sh,找到# ./configure --list-demuxers下方的白名单开启列表(大概在第 85-105 行附近),在这一组export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=..."的末尾,追加一行:
Plain
# 👇 追加以下行:开启 H.264 裸流解复用器(如果需要H265,同理追加 hevc)
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=h264"
(注:文件中默认已经开启了 --enable-parser=h264 和 --enable-decoder=h264,保险起见可以自行搜索确认下是否有这两行,没有则一同补上)
- 创建软链接,使配置生效:
Plain
rm module.sh
ln -s module-lite.sh module.sh
五、 解决依赖并执行编译
重新退回到编译目录下,开始正式构建。
解决常见编译报错(安装 yasm/nasm)
在编译 x86 / x86_64 模拟器架构时,FFmpeg 的底层汇编代码需要专门的汇编编译器,否则会报 nasm/yasm not found or too old 错误。 在 Ubuntu 下执行安装:
Plain
sudo apt-get update
sudo apt-get install yasm nasm
- 执行编译脚本
Markdown
# 1. 进入 contrib 目录编译底层的 FFmpeg
cd ../android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all # 编译全部 CPU 架构 (armv5, armv7a, arm64, x86, x86_64)
# 2. 返回外层目录,编译 ijkplayer 的外层封装 C 代码
cd ..
./compile-ijk.sh all # 生成最终的 .so 动态链接库
六、 编译产物输出位置
当执行完 ./compile-ijk.sh all 且无报错时,大功告成!你可以在 android/ijkplayer/ 目录下找到生成的三个核心 .so 文件(libijkffmpeg.so、libijkplayer.so、libijksdl.so)。
它们的具体存放路径已按 CPU 架构分类好:
-
ARM 64位 (主流真机):
android/ijkplayer/ijkplayer-arm64/src/main/libs/arm64-v8a/ -
ARM 32位 (老旧真机):
android/ijkplayer/ijkplayer-armv7a/src/main/libs/armeabi-v7a/ -
x86_64 (64位模拟器):
android/ijkplayer/ijkplayer-x86_64/src/main/libs/x86_64/
如何复用: 你可以直接把对应架构的文件夹连同 .so 复制到你已有 Android 项目的 app/src/main/jniLibs/ 下;或者直接在 Android Studio 中打开 android/ijkplayer 工程,将其通过 Gradle 的 assembleRelease 打包成标准的 .aar 组件库。
七、 客户端应用层代码集成(至关重要)
拥有了支持裸流解复用器的 .so 后,在上层必须做以下两项特殊配置,否则依然无法起播。
强制设定配置参数 (setOption)
因为裸流没有容器头,必须人工干预干预参数。
Android (Java / Kotlin):
Java
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
// 【核心配置】强行指定底层解复用格式为 h264,等同于 ffplay -f h264
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "iformat", "h264");
// 【起播优化】裸流由于缺乏时间戳元数据,建议大幅调小探测数据大小和时长,避免起播极慢或卡死
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024 * 16); // 16KB
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzemaxduration", 100); // 100ms
// 【可选丢帧】如果是实时监控等网络低延迟流,允许丢弃过期帧
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
实时网络流/内存流的喂送方式
若你的 H.264 裸流是通过 TCP/UDP 传输的实时字节流(byte\[\]),由于它没有常规的文件 URL,你应该借助 Java 层的 IMediaDataSource 接口来实现类似"管道"的边收边播:
Java
ijkMediaPlayer.setDataSource(new IMediaDataSource() {
@Override
public int readAt(long position, byte[] buffer, int offset, int size) {
// 在这里,从你的网络 Socket 接收队列/缓存池中读取数据
// 拷贝 size 长度的数据到传入的底层 buffer 中。
// 注意:若当前缓存池无数据,此方法应该进行阻塞,直到有新数据到来或流结束
return copiedSize;
}
@Override
public long getSize() {
return -1; // 实时网络裸流,长度返回 -1 即可
}
@Override
public void close() {
// 释放接收网络流的相关资源
}
});
八、 总结
让 ijkplayer 支持 H.264 裸流的底层链条是: iformat=h264 指定格式 -> 绕过 Container Probe -> 触发底层编译进去的 H.264 Demuxer -> 移交给 H.264 Parser 根据起始码切分 NALU 单元 -> 送入软解(FFmpeg)或硬解(MediaCodec/VideoToolbox) -> 完美渲染。
按照此完整指南走下来,即可在你的商业项目中完美解决纯裸流音视频的低延迟、高性能播放需求。