You need to enable JavaScript to run this app. 如何通过ffmpeg命令行读取SEI?RTMP流h264_metadata提取及API方案咨询
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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值与自定义的hello payload内容
  • 该方案仅适合临时调试,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 04:24:03