树莓派 FFmpeg 实战笔记(二):命令行、源码编译与硬件编码的真相

发布时间:2026/8/22 9:28:22
树莓派 FFmpeg 实战笔记(二):命令行、源码编译与硬件编码的真相 前言第一阶段我手写 V4L2 代码点亮了摄像头第二阶段的目标是把 FFmpeg 从会敲命令啃到看懂源码结构吃透命令行工具、从源码编译一遍、摸清树莓派的硬件编解码路径。这一晚踩了网络的坑、硬件的坑、还有自己给自己挖的坑全部记录在此。实验环境项目配置硬件树莓派 44GBCSI 摄像头 IMX219Camera Module V2无头模式 SSH系统Debian 13 (trixie)arm64FFmpegapt 版 7.1.5deb13u1rpt1树莓派特供版 自编译 n7.1关键设备/dev/video0unicam CSI、/dev/video11bcm2835-codec 硬件编码一、命令行工具吃透1.1 ffprobe容器、编码器、时间基是三件不同的事ffprobe -v error -show_entries formatformat_name,duration,bit_rate \ -show_entries streamcodec_name,time_base in.mp4 ffmpeg -i in.mp4 -c copy in.ts # 无损换壳不重编码 ffprobe -v error -show_entries streamcodec_name,time_base in.ts结果同一个 H.264 流mp4 里time_base1/15360塞进 ts 后变成1/90000。结论format_name是壳容器codec_name是内容编码器time_base是壳自己定义的时间刻度——MPEG-TS 协议硬性规定 90kHz。换壳不换内容-c copy时speed771x瞬间完成但时间基跟着壳走。时间戳是容器层的属性不是编码器的属性这个认知在后面反复救了我。1.2 preset / CRF / GOP编码器的三个旋钮同一段 10 秒 720p 测试视频testsrc2生成三组对照实验实验耗时(real)speed体积日志里的证据-preset ultrafast3.1s3.66x8.4Mcabac0 bframes0 ref1profile 退化为 Constrained Baseline-preset medium9.5s1.08x3.5Mcabac1 bframes3 ref3profile High-crf 18/-crf 30——5.0M / 1.3M平均 QP 17 vs 33-g 15默认 250——3.8Mframe I:20vs 默认的frame I:2结论preset 是拿 CPU 时间换压缩率ultrafast 放弃高级工具算得快但文件大了一倍多CRF 是画质标尺经验法则数值每 6体积约减半GOP 缩小 → I 帧暴增I 帧体积是 P 帧的 2~3 倍→ 文件略变大但播放器能更快开门见山直播延迟更低。1.3-re与推流一次 UDP 丢包实验# 终端1ffplay udp://127.0.0.1:8888 # 终端2不带 -re 全速灌流 ffmpeg -i in.mp4 -c copy -f mpegts udp://127.0.0.1:8888?pkt_size1316播放器端立刻刷出[mpegts ...] Packet corrupt画面花屏卡死。原理不带-re时 FFmpeg 以几百倍速把数据灌进网卡UDP只管发不管收接收缓冲区瞬间溢出大量丢包H.264 前后帧强依赖丢一包倒一片。-re的作用就是给 FFmpeg 踩刹车按原生帧率发流。延伸思考为什么推摄像头不需要-re因为摄像头是活的——传感器物理上就是 30fps 出图自带物理节流本地文件是死的才需要-re模拟实时。1.4 CSI 摄像头为什么我的摄像头没有 H.264ffmpeg -f v4l2 -list_formats all -i /dev/video0输出里全是Raw: yuyv422 / bayer_*没有任何 Compressed 格式直接抓取报错。破案我的摄像头是CSI 接口它只是裸传感器只输出 RAW Bayer 数据USB 摄像头才自带 ISP编码芯片直接吐 H.264/MJPEG。CSI 的正确流水线是传感器 RAW → ISP/dev/video13→ 硬件编码器bcm2835-codec。手动用 FFmpeg 串联这条流水线太折磨工程上的标准做法是让官方工具干苦力rpicam-vid -t 10000 --width 1280 --height 720 -o cam_raw.h264 ffmpeg -framerate 30 -i cam_raw.h264 -c copy cam.mp4日志里能看到 libcamera 自动选择了1920x1080-SBGGR10/RAW传感器格式并输出1280x720-YUV420——ISP 在默默干活。两个无害的假报警Failed to create egl/drm preview无头模式没显示器自动退回后台录制正常Timestamps are unset in a packet基本流ES天生没有时间戳时间戳是容器层赋予的。用-framerate声明帧率、-fflags genpts生成时间戳可缓解就算警告还在文件也完全能播——它只是封装器的牢骚不是错误。这恰好是 1.1 结论的二次验证。二、从源码编译与网络斗智斗勇的一晚2.1 git clone 的两种死法第一种GnuTLS recv error (-110): The TLS connection was non-properly terminated直接失败第二种重试后卡在Receiving objects: 41% ... 19.00 KiB/s假死。解法放弃 git 协议改下源码压缩包走镜像代理十几秒下完wget https://镜像代理/https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.1.tar.gz tar -xf n7.1.tar.gz cd FFmpeg-n7.12.2 configure 是 FFmpeg 的功能开关面板./configure --enable-v4l2-m2m --enable-libx264 --enable-gpl --disable-doc输出里Enabled encoders出现了h264_v4l2m2m、hevc_v4l2m2m——--enable-v4l2-m2m这个开关被实体化了。随后make -j2 make.log 21 扔后台树莓派内存有限-j2防 OOM。2.3 源码漫步三个寻宝grep -A 5 typedef struct AVRational libavutil/rational.h # 时间基的真面目{num, den} 分数 grep -n VIDIOC_REQBUFS libavdevice/v4l2.c # 第一阶段手写的 ioctl被封装在这里 grep v4l2 libavcodec/codec_list.c # 编译生成的编解码器注册表AVRational就是一个分子/分母结构体——FFmpeg 用整数分数彻底避开浮点误差ffprobe里的1/90000就是它。而v4l2.c里躺着我第一阶段写过的同一批VIDIOC_*ioctl。命令行 → 库 → 内核驱动这条链至此在脑子里闭环了。2.4 反转我杀掉了编译一小时的 make做硬件实验前随手一查ffmpeg -hide_banner -encoders | grep v4l2m2m # V..... h264_v4l2m2m V4L2 mem2mem H.264 encoder wrapper ...树莓派官方 apt 源早就默认开启了 v4l2-m2m我对抗龟速网络、配 configure、让 CPU 满载编译一小时……其实根本不需要编译。于是kill %1并用 htop 确认两个 100% 的cc1gcc 编译本体正是-j2的两个工人load≈2.0 与之一一对应。编译是无用功吗不是。configure开关机制、make 后台任务管理、源码与命令行的映射——这些是apt install永远学不到的。但实用层面今晚确实本可以省下一小时去喝茶。三、硬件 vs 软件编码终极对决time ffmpeg -i in.mp4 -c:v h264_v4l2m2m -b:v 4M -y out_hw.mp4 # 硬编 time ffmpeg -i in.mp4 -c:v libx264 -preset medium -y out_sw.mp4 # 软编硬编日志里写着Using device /dev/video11, driver bcm2835-codec——活确实是专用芯片干的。维度h264_v4l2m2mlibx264 medium耗时(real)3.9s18.1s*speed2.82x0.563x*CPU 时间(user)6.0s35.5s体积4.8M3.5M* 带星号是因为这次软编时后台 make 还在偷 CPU空载时是 9.5s / 1.08x——意外收获的一条教训做基准测试必须控制变量后台任务会让数据失真。两个隐藏细节硬件编码器不吃-crf。bcm2835-codec 是死脑筋只接受-b:v码率模式所以硬编文件反而更大4M 固定码率 vs 软编 crf23 跑出的 2.8M。嵌入式硬件加速最容易踩的坑之一。硬编 user 时间只有 6 秒CPU 基本在搬运计算全在芯片里。跑htop对比两次 CPU 占用是理解硬件加速最直观的一课。里程碑达成现在我能对着一份转码日志逐行标注归属——Input #0 ...是 demuxerlibavformat在拆壳Stream mapping是解码→编码管线Output #0是 muxer带[libx264 ]前缀的才是 encoderlibavcodec在说话frame ... speed是运行统计。四、踩坑清单现象原因解法git clone 报 GnuTLS 错误 / 卡 19KB/s网络问题镜像代理 wget 源码包make: no makefile foundconfigure 没成功/没进源码目录先 configure 再 makev4l2 抓 CSI 摄像头只有 raw 格式CSI 传感器只输出 RAW Bayerrpicam-vid走 ISP硬编流水线裸 h264 封装 mp4 报 timestamps 警告基本流无时间戳-framerate/genpts理解警告非错误推流不加-re播放端 Packet corruptUDP 缓冲溢出丢包本地文件加-re摄像头不用rpicam 报 preview 创建失败无头模式正常自动后台录制硬编传-crf无效硬件只支持码率模式用-b:v软编速度莫名减半后台编译偷 CPU基准测试要空载五、第二阶段自检清单说清 format_name / codec_name / time_base 三者关系及 mp4 与 ts 时间基差异一句话说清 preset / crf / g 各自影响说清-re的作用与何时不需要说清 CSI 与 USB 摄像头的架构差异、ES 无时间戳的本质在源码里找到 AVRational、VIDIOC ioctl、codec 注册表对着日志逐行标注 demuxer / muxer / encoder 归属六、写在最后这一晚最大的收获不是某个参数而是三个认知升级时间戳属于容器不属于流、摄像头架构决定数据形态、硬件编码器有自己的脾气。哦对还有第四条动手前先ffmpeg -encoders | grep v4l2看一眼说不定系统早就帮你装好了。