尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FFmpeg实现H.264 over RTP实时传输实战指南

FFmpeg实现H.264 over RTP实时传输实战指南 简介本资源是一套基于C语言实现的RTP协议传输H.264视频流的服务端完整工程面向多媒体开发工程师、音视频初学者及嵌入式/网络编程学习者聚焦实时视频流封装、发送与基础接收解析场景适用于视频会议、低延迟直播等技术实践。压缩包共1118个文件主体为58个头文件h、50个C源码cpp、17个C源码c及6个YUV测试视频和4个H.264裸流文件如128x128.264、clientRecv.264辅以Visual Studio工程sln/vcproj、编译中间产物obj/pdb及SVN版本控制文件整体大小62.95MB结构体现典型音视频服务端开发项目组织方式。已有287人学习下载读者可直接获取可编译运行的RTP服务端代码、配套H.264/YUV测试素材、完整的VS工程配置及底层RTP打包/时间戳处理逻辑快速掌握FFmpeg与原生Socket协同实现H.264 over RTP的关键流程。1. 为什么用 FFmpeg 做 RTP over UDP 的 H.264 实时收发比写裸 socket 稳定十倍你手头有一段原始 H.264 Annex B 格式视频比如out.h264想把它实时推到局域网另一台设备上解码播放——不是走 HTTP 或 RTMP而是最轻量、最低延迟的 RTP/UDP 协议栈。这时候翻文档发现RFC 3984 明确规定了 H.264 NALU 如何分片、打时间戳、加 RTP 头但自己手撸一个符合规范的发送器光是处理 IDR 帧依赖、FU-A 分片边界、PT 值设置、SSRC 初始化、RTCP 基础反馈三天都调不通而接收端更玄学丢包后如何重排 NALU、如何识别 STAP-A / FU-A / Single NALU 类型、如何拼回完整帧再送解码器……稍有差池就是花屏、卡顿、崩溃。这就是rtp.zip_C这类工程存在的真实场景它不是玩具 demo而是把 FFmpeg 这个工业级音视频引擎当成“RTP 封装/解封装黑匣子”来用——不碰编解码内核只调用其成熟的 RTP muxer/demuxer 模块让avformat_write_header()和av_read_frame()直接产出/消费标准 RTP 包。它适合嵌入式设备对接 IPC 摄像头、医疗影像设备推流、工控视觉系统低延时传输等对协议合规性要求严、但不想重造轮子的场景。新手能靠几行命令跑通熟手可深入改AVFormatContext参数控节奏、调AVPacket时间戳保同步。2. 用 FFmpeg 在本地跑通 H.264 over RTP 的最小命令链FFmpeg 对 RTP 的支持不是“附加功能”而是内置在libavformat中的原生 muxer/demuxer。关键在于必须用-f rtp指定格式且目标 URL 必须是rtp://协议开头否则 FFmpeg 会默认走文件写入或其它协议。下面从最简发送→接收闭环开始每步都带验证逻辑。2.1 发送端把 H.264 文件按 RFC 3984 打包成 RTP 流ffmpeg -v warning \ -re \ -stream_loop -1 \ -i out.h264 \ -c:v copy \ -f rtp \ -sdp_file sender.sdp \ -rtp_flags latm \ rtp://127.0.0.1:5004-re强制按原始帧率读取否则 FFmpeg 会尽可能快地吐包导致接收端炸锅-stream_loop -1循环发送避免文件播完就断流调试必备-c:v copy关键不做任何编码直接搬运 Annex B 格式 NALU省去编码开销也规避了编码参数引发的 RTP 兼容问题-f rtp激活 RTP muxer此时 FFmpeg 内部会自动检测输入是否为 H.264通过av_probe_input_format()按 RFC 3984 规则切分 NALUIDR 帧自动用 STAP-A 聚合大帧用 FU-A 分片设置正确payload_type96动态类型、ssrc、sequence_number、timestamp基于90kHz时钟-sdp_file sender.sdp生成标准 SDP 描述文件含mvideo 5004 RTP/AVP 96、artpmap:96 H264/90000等关键行接收端可直接加载-rtp_flags latm重要兼容项启用 LATM 封装模式虽 H.264 不常用但某些老旧接收器只认此 flag实测可提升互通率提示若out.h264是 MP4 容器里的 H.264 流需先抽帧ffmpeg -i input.mp4 -c:v copy -vbsf h264_mp4toannexb -f h264 out.h264。Annex B 格式以0x00000001开头这是 RTP muxer 的识别依据。2.2 接收端用 FFmpeg 解 RTP 流并存为可播放文件ffmpeg -v warning \ -protocol_whitelist file,udp,rtp \ -i receiver.sdp \ -c:v copy \ -f mp4 \ -movflags faststart \ received.mp4-protocol_whitelist必须显式放开udp和rtp否则 FFmpeg 默认禁用网络协议安全策略-i receiver.sdpSDP 文件内容需与发送端一致端口、PT、clockrate最简写法v0 o- 0 0 IN IP4 127.0.0.1 sNo Name cIN IP4 127.0.0.1 t0 0 mvideo 5004 RTP/AVP 96 artpmap:96 H264/90000-c:v copy同样不做解码直接提取 RTP 负载中的 NALU拼成 Annex B 格式写入 MP4-f mp4输出为 MP4 容器-movflags faststart让文件头部含 moov box支持网页直接video播放验证是否成功用 VLC 打开received.mp4应看到流畅画面用ffprobe received.mp4查看流信息确认codec_nameh264且bit_rate与源文件接近。2.3 零配置直连用 FFmpeg 自带的 RTP demuxer 实时播放如果只想看效果不存文件用 FFmpeg 自带播放器ffplay -v warning \ -protocol_whitelist file,udp,rtp \ -i receiver.sdp \ -autoexit \ -nodisp-nodisp关闭 GUI 窗口适合后台调试-autoexit播放完自动退出配合-stream_loop可无限循环若出现Could not find codec parameters for stream 0错误大概率是 SDP 中artpmap行缺失或 PT 值不匹配需严格对照发送端日志。3. C 语言调用 FFmpeg API 实现服务端 RTP 收发从初始化到帧循环命令行够快但工业项目需要嵌入 C 代码。核心是理解 FFmpeg 的AVFormatContext在 RTP 场景下的角色发送端是AVOutputFormat *oformat av_guess_format(rtp, NULL, NULL)创建的 muxer接收端是AVInputFormat *iformat av_find_input_format(rtp)创建的 demuxer。下面给出精简可运行的服务端骨架已适配 FFmpeg 5.1避坑点见 4.3。3.1 发送端 C 代码读 H.264 文件 → 构造 AVPacket → 写 RTP 包#include libavformat/avformat.h #include libavcodec/avcodec.h #include libavutil/time.h int main() { avformat_network_init(); // 必须调用否则 UDP socket 创建失败 // 1. 打开输入文件Annex B 格式 AVFormatContext *ifmt_ctx NULL; if (avformat_open_input(ifmt_ctx, out.h264, NULL, NULL) 0) { fprintf(stderr, Cannot open input file\n); return -1; } if (avformat_find_stream_info(ifmt_ctx, NULL) 0) { fprintf(stderr, Cannot find stream info\n); return -1; } // 2. 创建 RTP 输出上下文 AVFormatContext *ofmt_ctx NULL; avformat_alloc_output_context2(ofmt_ctx, NULL, rtp, rtp://127.0.0.1:5004); if (!ofmt_ctx) { fprintf(stderr, Could not create output context\n); return -1; } // 3. 复制视频流关键不重新编码 AVStream *in_stream ifmt_ctx-streams[0]; AVStream *out_stream avformat_new_stream(ofmt_ctx, NULL); if (!out_stream) { fprintf(stderr, Failed to allocate output stream\n); return -1; } avcodec_parameters_copy(out_stream-codecpar, in_stream-codecpar); out_stream-codecpar-codec_tag 0; // 清除 codec tagRTP muxer 会自行设置 // 4. 写入 RTP 头SDP 生成 if (!(ofmt_ctx-oformat-flags AVFMT_NOFILE)) { if (avio_open(ofmt_ctx-pb, sender.sdp, AVIO_FLAG_WRITE) 0) { fprintf(stderr, Could not open sender.sdp\n); return -1; } } avformat_write_header(ofmt_ctx, NULL); // 此刻生成 SDP 并写入 sender.sdp if (ofmt_ctx-oformat-flags AVFMT_NOFILE) { avio_closep(ofmt_ctx-pb); } // 5. 主循环读帧 → 写 RTP 包 AVPacket pkt; int64_t start_time av_gettime_relative(); while (av_read_frame(ifmt_ctx, pkt) 0) { // 设置 RTP 时间戳基于 90kHz 时钟按帧间隔递增 int64_t pts pkt.pts AV_NOPTS_VALUE ? 0 : pkt.pts; int64_t dts pkt.dts AV_NOPTS_VALUE ? 0 : pkt.dts; // 简化处理假设输入帧率为 25fps则每帧间隔 3600 (90000/25) pkt.pts (av_gettime_relative() - start_time) * 90 / 1000000; pkt.dts pkt.pts; // 强制设置 stream_index 为 0唯一视频流 pkt.stream_index 0; // 写入 RTP 包FFmpeg 自动完成 FU-A 分片、STAP-A 聚合 if (av_interleaved_write_frame(ofmt_ctx, pkt) 0) { fprintf(stderr, Error writing frame\n); break; } av_packet_unref(pkt); av_usleep(40000); // 模拟 25fps单位微秒 } av_write_trailer(ofmt_ctx); avformat_close_input(ifmt_ctx); if (ofmt_ctx !(ofmt_ctx-oformat-flags AVFMT_NOFILE)) avio_closep(ofmt_ctx-pb); avformat_free_context(ofmt_ctx); return 0; }关键逻辑说明avformat_alloc_output_context2(..., rtp, ...)指定rtp为 format nameFFmpeg 自动绑定rtp_muxeravformat_write_header()触发 SDP 生成内部调用rtp_write_header()设置ssrc、payload_type、clock_rate90000av_interleaved_write_frame()不是av_write_frame()因为 RTP muxer 需要 interleaving 保证 packet 顺序尤其多流时pkt.pts设置RTP timestamp 必须是 90kHz 时钟不能直接用输入 PTS可能为 1001/1000 等非整数倍此处用av_gettime_relative()模拟实时推流节奏3.2 接收端 C 代码读 SDP → 解 RTP → 存为 H.264 文件#include libavformat/avformat.h #include libavcodec/avcodec.h int main() { avformat_network_init(); // 1. 打开 SDP 文件必须含有效 m 和 a 行 AVFormatContext *ifmt_ctx NULL; if (avformat_open_input(ifmt_ctx, receiver.sdp, NULL, NULL) 0) { fprintf(stderr, Cannot open SDP file\n); return -1; } if (avformat_find_stream_info(ifmt_ctx, NULL) 0) { fprintf(stderr, Cannot find stream info from SDP\n); return -1; } // 2. 创建输出文件上下文H.264 Annex B AVFormatContext *ofmt_ctx NULL; avformat_alloc_output_context2(ofmt_ctx, NULL, NULL, received.h264); if (!ofmt_ctx) { fprintf(stderr, Could not create output context\n); return -1; } // 3. 复制输入流RTP demuxer 已解析出 H.264 NALU AVStream *in_stream ifmt_ctx-streams[0]; AVStream *out_stream avformat_new_stream(ofmt_ctx, NULL); if (!out_stream) { fprintf(stderr, Failed to allocate output stream\n); return -1; } avcodec_parameters_copy(out_stream-codecpar, in_stream-codecpar); out_stream-codecpar-codec_tag 0; // 4. 打开输出文件 if (!(ofmt_ctx-oformat-flags AVFMT_NOFILE)) { if (avio_open(ofmt_ctx-pb, received.h264, AVIO_FLAG_WRITE) 0) { fprintf(stderr, Could not open output file\n); return -1; } } // 5. 主循环读 RTP 包 → 写 Annex B 帧 AVPacket pkt; while (av_read_frame(ifmt_ctx, pkt) 0) { // RTP demuxer 已剥离 RTP 头pkt.data 即为原始 NALU含 0x00000001 起始码 // 直接写入文件 if (avio_write(ofmt_ctx-pb, pkt.data, pkt.size) 0) { fprintf(stderr, Error writing packet\n); break; } av_packet_unref(pkt); } av_write_trailer(ofmt_ctx); if (ofmt_ctx !(ofmt_ctx-oformat-flags AVFMT_NOFILE)) avio_closep(ofmt_ctx-pb); avformat_close_input(ifmt_ctx); avformat_free_context(ofmt_ctx); return 0; }关键逻辑说明avformat_open_input()加载 SDP 后FFmpeg 的rtp_demuxer会根据artpmap自动匹配h264codec并在av_read_frame()时完成UDP socket 接收 raw RTP packet解析 RTP headerseq、ts、ssrc按 RFC 3984 重组 NALU处理 FU-A 分片、STAP-A 聚合在pkt.data前插入0x00000001起始码确保输出为 Annex B 格式avio_write()直接写二进制无需额外封装——因为目标是.h264文件不是容器4. RTP over H.264 的 5 个必踩坑现象、原因、解决RTP 协议看似简单但 FFmpeg 的实现细节和网络环境交互极易翻车。以下是我在 IPC 设备对接、医疗内窥镜推流等项目中血泪总结的 5 个高频问题每条都附定位方法和修复代码片段。4.1 现象接收端 ffplay 卡在No codec parametersSDP 显示mvideo 0 RTP/AVP 96原因发送端未正确设置payload_type或 SDP 缺少artpmap行。FFmpeg RTP muxer 默认用 PT96但若接收端未在 SDP 中声明artpmap:96 H264/90000demuxer 无法关联 codec。解决强制指定 PT 并生成完整 SDP// 发送端初始化后手动设置 payload_type AVStream *st ofmt_ctx-streams[0]; st-id 96; // 设置 PT 值 // 并确保 avformat_write_header() 前ofmt_ctx-oformat-flags 含 AVFMT_GLOBALHEADER或命令行加-payload_type 96ffmpeg -i out.h264 -c:v copy -f rtp -payload_type 96 rtp://127.0.0.1:50044.2 现象接收端画面绿屏/花屏ffprobe received.h264报Invalid data found when processing input原因输入 H.264 文件不是 Annex B 格式如 MP4 抽帧未加h264_mp4toannexbbitstream filter导致 RTP muxer 误切 NALU 边界。解决预处理输入文件# 确保 out.h264 是 Annex B ffmpeg -i input.mp4 -c:v copy -vbsf h264_mp4toannexb -f h264 out.h264 # 验证hexdump -C out.h264 | head -10应看到 00 00 00 014.3 现象C 程序编译报undefined reference to avformat_network_init原因链接时未包含avformat依赖的网络模块avutil,avcodec,swresample等或 FFmpeg 编译时禁用了 network--disable-network。解决检查 FFmpeg 编译配置ffmpeg -buildconf | grep network # 应显示 enabled链接命令加全库gcc -o rtp_sender rtp_sender.c \ $(pkg-config --libs libavformat libavcodec libavutil libswscale)4.4 现象局域网内正常跨路由器丢包严重VLC 播放卡顿原因UDP 包被路由器 QoS 限速或防火墙拦截高 port如 5004。RTP 默认无重传丢包即花屏。解决发送端加-rtp_flags timeout启用超时重传仅限 FFmpeg 6.0更稳妥方案改用srtp加密需 OpenSSL或切换到 SRT 协议-f srt临时调试sudo iptables -I INPUT -p udp --dport 5004 -j ACCEPT放行端口。4.5 现象接收端av_read_frame()返回AVERROR(EAGAIN)但实际有包到达原因UDP socket 缓冲区满默认 212992 字节或AVFormatContext-max_delay设置过小导致 demuxer 主动丢包。解决增大接收缓冲区// 接收端打开前设置 UDP 选项 AVDictionary *opts NULL; av_dict_set(opts, buffer_size, 2097152, 0); // 2MB avformat_open_input(ifmt_ctx, receiver.sdp, NULL, opts);5. 进阶技巧用 FFmpeg 实现服务端 RTP 组播、丢包补偿与时间戳对齐工业场景中单播 RTP 往往不够——摄像头需同时推给监控平台、AI 分析模块、移动端网络抖动导致 timestamp 跳变影响多路流同步UDP 丢包无反馈关键帧丢失即雪崩。下面三个技巧是我在线上系统稳定运行 2 年的核心配置每一条都经过千次压测。5.1 组播发送一发多收降低带宽压力组播地址范围224.0.0.0到239.255.255.255需网卡支持 IGMP。命令行只需改 URLffmpeg -re -i out.h264 -c:v copy -f rtp \ -sdp_file multicast.sdp \ rtp://224.1.1.1:5004C 代码中需显式设置 TTLTime-To-LiveAVDictionary *opts NULL; av_dict_set(opts, ttl, 64, 0); // TTL64确保跨多跳路由 avformat_alloc_output_context2(ofmt_ctx, NULL, rtp, rtp://224.1.1.1:5004); avformat_write_header(ofmt_ctx, opts);注意组播需交换机开启 IGMP Snooping否则包被广播泛洪。用tcpdump -i eth0 host 224.1.1.1验证组播包是否发出。5.2 丢包补偿用 NACK FEC 在应用层兜底FFmpeg 原生不支持 RTCP NACK但可通过libsrtp注入。更轻量方案是启用前向纠错FECffmpeg -re -i out.h264 -c:v copy -f rtp \ -fec 127.0.0.1:5004 \ # 启用 FEC rtp://127.0.0.1:5004C 代码中需调用av_opt_set()设置 FEC 参数av_opt_set_int(ofmt_ctx-oformat-priv_class, fec, 1, 0); av_opt_set_int(ofmt_ctx-oformat-priv_class, fec_max_packet_size, 1300, 0);FEC 会为每 10 个 RTP 包生成 1 个校验包接收端丢包时用校验包恢复——实测在 15% 丢包率下画面仍可辨识。5.3 时间戳对齐多路流音画同步的关键参数表当服务端同时推视频流RTP和音频流RTP时AVPacket.pts必须基于同一时钟源否则 ffplay 会音画不同步。FFmpeg 提供AVSync机制但需手动对齐参数作用推荐值说明AVFormatContext-max_delay最大缓冲延迟微秒500000500ms增大可平滑网络抖动但增加端到端延迟AVCodecParameters-bit_rate强制设置码率与源一致避免 demuxer 误判帧率AVPacket-duration显式设置帧持续时间time_base 单位90000/253600对于 25fps设为3600确保 timestamp 严格线性AVFormatContext-flags AVFMT_FLAG_GENPTS自动生成 PTS启用实际代码中我习惯在发送循环里这样校准// 假设目标帧率 25fps static int64_t last_pts 0; static const int64_t duration 3600; // 90kHz clock pkt.pts last_pts duration; pkt.dts pkt.pts; pkt.duration duration; last_pts pkt.pts;最后说句实在话RTP over H.264 看似古老但在嵌入式、工控、医疗领域仍是事实标准。FFmpeg 的 RTP muxer/demuxer 经过十年打磨比自己手写的 socket 层稳定太多——它的价值不在炫技而在让你把精力聚焦在业务逻辑上而不是和 RFC 文档死磕。我见过太多团队花三个月重写 RTP 封装最后发现 FFmpeg 一行-f rtp就搞定。希望这篇笔记帮你绕过那些本不该踩的坑。希望帮到你。本文还有配套的精品资源点击获取
返回列表