在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
相关产品推荐
相关产品推荐

