如何通过ffmpeg命令行读取SEI?RTMP流h264_metadata提取及API方案咨询
FFMPEG 读取RTMP流中自定义H264 SEI数据的实现方案
核心结论
首先明确:FFmpeg原生命令行没有内置专门的SEI结构化提取能力,你之前使用h264_metadata bitstream filter尝试读取SEI未生效的根本原因是,该过滤器仅支持SEI的写入、修改操作,没有内置SEI内容解析输出的功能,无参数执行时仅会透传码流,不会触发SEI内容的提取逻辑。
下面分别提供适合快速调试的命令行变通方案,以及适合生产环境长期运行的API实现方案。
快速调试用的命令行实现
利用FFmpeg内置的解码器调试日志能力,可以直接解析并打印流中的SEI内容,不需要修改源码,对应命令为:
ffmpeg -loglevel debug \ -i rtmp://127.0.0.1:1935/live/h264_metadata \ -c:v rawvideo -f null -
命令参数说明
-loglevel debug打开最高层级的调试日志输出,触发H264软解路径下的SEI单元完整解析逻辑- 不指定硬解参数时,默认走软件解码流程,在解码前置的码流解析阶段就会自动识别并打印所有SEI单元的内容,包括你之前插入的自定义用户数据SEI
-f null -丢弃所有解码后的视频帧,不做实际的编码、文件写入操作,仅触发码流解析流程
注意事项
- 调试日志的输出量极大,建议将控制台日志重定向到本地文件,方便后续筛选SEI相关内容(比如执行时追加
2> sei_debug.log) - 筛选日志时直接搜索关键字
sei,可以快速定位user_data_unregistered类型的SEI块,即可看到你之前插入的UUID值与自定义的hellopayload内容 - 该方案仅适合临时调试,SEI内容会和其他H264码流解析日志混杂,无法做稳定的结构化提取,不适合生产场景长期运行
生产环境用的FFmpeg API实现
如果需要稳定、结构化的从RTMP流中提取SEI内容,建议基于FFmpeg的C API做轻量开发,核心逻辑是逐包扫描码流中的NAL单元,识别SEI类型的NAL后解析对应payload,不需要做完整的视频解码,CPU占用极低。
核心实现代码
#include <libavformat/avformat.h> #include <libavcodec/bsf.h> #include <string.h> int main(int argc, char **argv) { AVFormatContext *fmt_ctx = NULL; const char *stream_url = "rtmp://127.0.0.1:1935/live/h264_metadata"; int video_stream_idx = -1; AVPacket *pkt = av_packet_alloc(); // 1. 打开RTMP输入流 if (avformat_open_input(&fmt_ctx, stream_url, NULL, NULL) < 0) { return -1; } avformat_find_stream_info(fmt_ctx, NULL); // 定位H264视频流索引 for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; break; } } if (video_stream_idx < 0) { return -1; } // 2. 初始化bitstream filter,把FLV封装的长度前缀NAL转换为带起始码的Annex-B格式,方便解析 AVBSFContext *bsf_ctx = NULL; const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb"); av_bsf_alloc(bsf, &bsf_ctx); avcodec_parameters_copy(bsf_ctx->par_in, fmt_ctx->streams[video_stream_idx]->codecpar); av_bsf_init(bsf_ctx); // 3. 循环读取码流包 while (av_read_frame(fmt_ctx, pkt) >= 0) { if (pkt->stream_index != video_stream_idx) { av_packet_unref(pkt); continue; } // 转换为Annex-B格式的NAL单元 av_bsf_send_packet(bsf_ctx, pkt); AVPacket *annexb_pkt = av_packet_alloc(); while (av_bsf_receive_packet(bsf_ctx, annexb_pkt) == 0) { const uint8_t *buf = annexb_pkt->data; int buf_len = annexb_pkt->size; int pos = 0; // 逐位置扫描NAL起始码 while (pos < buf_len - 4) { // 匹配3字节起始码 0x00 0x00 0x01 if (buf[pos] == 0 && buf[pos+1] == 0 && buf[pos+2] == 1) { int nal_type = buf[pos+3] & 0x1F; // NAL类型5对应SEI单元 if (nal_type == 5) { int sei_pos = pos + 4; // 按H264规范解析SEI payload while (sei_pos < buf_len) { uint32_t payload_type = 0; // 读取payload类型,连续0xFF代表值累加 while (buf[sei_pos] == 0xFF) { payload_type += 0xFF; sei_pos++; } payload_type += buf[sei_pos++]; // 读取payload长度 uint32_t payload_size = 0; while (buf[sei_pos] == 0xFF) { payload_size += 0xFF; sei_pos++; } payload_size += buf[sei_pos++]; // 识别payload类型为5的未注册用户数据SEI if (payload_type == 5 && payload_size >= 16) { // 提取前16字节的UUID char uuid_str[33] = {0}; snprintf(uuid_str, sizeof(uuid_str), "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", buf[sei_pos], buf[sei_pos+1], buf[sei_pos+2], buf[sei_pos+3], buf[sei_pos+4], buf[sei_pos+5], buf[sei_pos+6], buf[sei_pos+7], buf[sei_pos+8], buf[sei_pos+9], buf[sei_pos+10], buf[sei_pos+11], buf[sei_pos+12], buf[sei_pos+13], buf[sei_pos+14], buf[sei_pos+15]); // 提取UUID后的自定义内容 const char *custom_data = (const char*)(buf + sei_pos + 16); printf("捕获SEI: UUID=%s, 自定义内容=%.*s\n", uuid_str, payload_size - 16, custom_data); } // 移动指针到下一个SEI payload sei_pos += payload_size; } } } pos++; } av_packet_unref(annexb_pkt); } av_packet_free(&annexb_pkt); av_packet_unref(pkt); } // 资源释放 av_packet_free(&pkt); av_bsf_free(&bsf_ctx); avformat_close_input(&fmt_ctx); return 0; }
方案优势
- 仅做轻量的NAL单元扫描,不需要对视频帧做完整解码,CPU占用极低,适合7*24小时的拉流服务场景
- 可以直接匹配你插入的固定UUID值
086f3693-b7b3-4f2c-9653-21492feee5b8,精准过滤无关SEI内容,实现结构化输出 - 完全基于FFmpeg原生API,无第三方依赖
之前尝试命令失效的原因说明
你之前执行的
ffmpeg -i rtmp://127.0.0.1:1935/live/h264_metadata -c:v copy -bsf:v h264_metadata -f null -命令没有任何SEI提取效果,核心原因是:h264_metadata过滤器的所有配置参数都是用于SEI写入、修改的(比如sei_user_data参数就是专门用来插入自定义SEI的),它没有提供任何用于读取、输出SEI内容的配置项,不带参数执行该过滤器时,仅会做无意义的码流透传,根本不会触发SEI的解析流程。
内容的提问来源于stack exchange,提问作者glowry
相关产品推荐
相关产品推荐

