You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在libav中使用dump_extra插入SPS/PPS失效,WebRTC流媒体求助

问题解决:libav中dump_extra过滤器无法插入SPS/PPS的问题

核心原因

你遇到的问题本质是:libav的dump_extra过滤器完全依赖AVCodecContext的extradata字段生成SPS/PPS数据包,但你的上下文里该字段为空(extradata为null、extradata_size为0)。而FFmpeg命令行工具会自动从原始H264流中解析并提取SPS/PPS,填充到extradata中,这就是两者行为不一致的原因。

解决方案

方案1:手动提取SPS/PPS并填充extradata

在初始化阶段,从原始H264流的开头数据包中提取SPS和PPS,按照AVCC格式拼接成extradata写入AVCodecContext,之后dump_extra过滤器就能正常工作。

代码示例:

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/byte.h>

// 从H264裸流数据包中提取SPS和PPS,填充到codec_ctx的extradata
int extract_h264_extradata(AVCodecContext *codec_ctx, AVPacket *pkt) {
    uint8_t *buf = pkt->data;
    int buf_size = pkt->size;
    int start_pos = 0;

    // 遍历查找NALU起始码(支持0x000001和0x00000001两种格式)
    while (start_pos < buf_size - 4) {
        bool is_start = false;
        int nalu_offset = 0;
        // 检查4字节起始码
        if (buf[start_pos] == 0 && buf[start_pos+1] == 0 && buf[start_pos+2] == 0 && buf[start_pos+3] == 1) {
            is_start = true;
            nalu_offset = 4;
        }
        // 检查3字节起始码
        else if (buf[start_pos] == 0 && buf[start_pos+1] == 0 && buf[start_pos+2] == 1) {
            is_start = true;
            nalu_offset = 3;
        }

        if (is_start) {
            int nalu_type = buf[start_pos + nalu_offset] & 0x1F;
            if (nalu_type == 7) { // 找到SPS
                int sps_len = buf_size - start_pos - nalu_offset;
                // 继续查找后续的PPS
                int pps_pos = start_pos + nalu_offset + sps_len;
                while (pps_pos < buf_size - 4) {
                    bool is_pps_start = false;
                    int pps_nalu_offset = 0;
                    if (buf[pps_pos] == 0 && buf[pps_pos+1] == 0 && buf[pps_pos+2] == 0 && buf[pps_pos+3] == 1) {
                        is_pps_start = true;
                        pps_nalu_offset = 4;
                    } else if (buf[pps_pos] == 0 && buf[pps_pos+1] == 0 && buf[pps_pos+2] == 1) {
                        is_pps_start = true;
                        pps_nalu_offset = 3;
                    }

                    if (is_pps_start) {
                        int pps_type = buf[pps_pos + pps_nalu_offset] & 0x1F;
                        if (pps_type == 8) { // 找到PPS
                            int pps_len = buf_size - pps_pos - pps_nalu_offset;
                            // 按照AVCC格式拼接extradata
                            int extradata_size = 8 + sps_len + pps_len;
                            uint8_t *extradata = av_mallocz(extradata_size);
                            if (!extradata) return AVERROR(ENOMEM);

                            // AVCC头部结构:版本、profile、兼容位、level、预留位、SPS数量、SPS长度、SPS数据、PPS数量、PPS长度、PPS数据
                            extradata[0] = 0x01;
                            extradata[1] = buf[start_pos + nalu_offset + 1]; // profile_idc
                            extradata[2] = buf[start_pos + nalu_offset + 2]; // profile_compatibility
                            extradata[3] = buf[start_pos + nalu_offset + 3]; // level_idc
                            extradata[4] = 0xFC | 3; // 6位预留位 + 2位SPS数量(固定为1)
                            extradata[5] = (sps_len >> 8) & 0xFF;
                            extradata[6] = sps_len & 0xFF;
                            memcpy(extradata + 7, buf + start_pos + nalu_offset, sps_len);
                            extradata[7 + sps_len] = 0x01; // PPS数量(固定为1)
                            extradata[8 + sps_len] = (pps_len >> 8) & 0xFF;
                            extradata[9 + sps_len] = pps_len & 0xFF;
                            memcpy(extradata + 10 + sps_len, buf + pps_pos + pps_nalu_offset, pps_len);

                            // 赋值给codec_ctx
                            codec_ctx->extradata = extradata;
                            codec_ctx->extradata_size = extradata_size;
                            return 0;
                        }
                    }
                    pps_pos++;
                }
            }
        }
        start_pos++;
    }
    return AVERROR(EINVAL);
}

// 使用示例:在读取第一个视频数据包时调用
int main() {
    AVFormatContext *fmt_ctx = NULL;
    avformat_open_input(&fmt_ctx, "input.h264", NULL, NULL);
    avformat_find_stream_info(fmt_ctx, NULL);
    int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    AVCodecContext *codec_ctx = avcodec_alloc_context3(avcodec_find_decoder(AV_CODEC_ID_H264));
    avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_idx]->codecpar);
    avcodec_open2(codec_ctx, NULL, NULL);

    AVPacket pkt;
    while (av_read_frame(fmt_ctx, &pkt) == 0) {
        if (pkt.stream_index == video_stream_idx) {
            // 仅在extradata为空时提取
            if (!codec_ctx->extradata) {
                extract_h264_extradata(codec_ctx, &pkt);
            }
            // 后续通过dump_extra过滤器处理数据包
            // ... 过滤器处理逻辑 ...
            av_packet_unref(&pkt);
        }
    }

    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);
    return 0;
}

方案2:跳过dump_extra,手动在IDR帧前插入SPS/PPS

如果不想依赖extradata,可以直接在每个IDR帧(关键帧)前主动发送SPS和PPS数据包,确保中途加入的观众能在收到关键帧时拿到序列参数。

代码示例:

// 假设已提前提取到SPS和PPS的数据及长度:sps_data、sps_size、pps_data、pps_size
void inject_sps_pps_before_idr(AVPacket *pkt, uint8_t *sps_data, int sps_size, uint8_t *pps_data, int pps_size) {
    // 判断当前数据包是否为IDR帧
    int nalu_type = pkt->data[0] & 0x1F;
    if (nalu_type != 5) return;

    // 构造SPS数据包(带4字节起始码)
    AVPacket sps_pkt = {0};
    av_new_packet(&sps_pkt, sps_size + 4);
    memcpy(sps_pkt.data, "\x00\x00\x00\x01", 4);
    memcpy(sps_pkt.data + 4, sps_data, sps_size);
    sps_pkt.stream_index = pkt->stream_index;
    sps_pkt.flags |= AV_PKT_FLAG_KEY;
    // 替换为你的WebRTC发送逻辑
    send_webrtc_packet(&sps_pkt);
    av_packet_unref(&sps_pkt);

    // 构造PPS数据包(带4字节起始码)
    AVPacket pps_pkt = {0};
    av_new_packet(&pps_pkt, pps_size + 4);
    memcpy(pps_pkt.data, "\x00\x00\x00\x01", 4);
    memcpy(pps_pkt.data + 4, pps_data, pps_size);
    pps_pkt.stream_index = pkt->stream_index;
    // 替换为你的WebRTC发送逻辑
    send_webrtc_packet(&pps_pkt);
    av_packet_unref(&pps_pkt);
}

方案3:对齐libav与FFmpeg的版本或库

部分旧版本的libav在H264流的自动解析逻辑上与FFmpeg存在差异,建议升级到最新版libav,或者直接切换使用FFmpeg的libavcodec/libavformat库(两者API基本兼容),避免版本带来的功能缺失。


内容的提问来源于stack exchange,提问作者Hououin_kyouma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 12:12:33