2025-03-06 ffmpeg提取SPS/PPS/SEI ( extradata )
一、需求
在某些情况下,可能需要直接使用H264/H265等原始数据流进行解码,比较常用的udp下的h264/h265。这时需要 av_parser_parse2
来组AVPacket
,但对于视频的信息:宽高、格式等,可以根据 AVCodecParserContext
来获取,也可以直接提取sps/pps/sei这些原始数据extradata
本文讲如何从原始数据流(AVPacket
)中找出 extradata
信息
二、ffmpeg5.0版本以下
在旧版本的ffmpeg中, AVCodecParserContext 内部有个 split 函数,可以直接返回 extradata
在数据流中的位置
AVCodecParserContext* m_parser = nullptr;
// 初始化 m_parser
// ........
//
// m_ctx : AVCodecContext*
// pkt : AVPacket*
// 其中第一个参数 m_ctx 可以不设置,直接设置为nullptr也可
auto re = m_parser->parser->split(m_ctx, pkt->data, pkt->size);
if (re > 0) {
if (m_ctx->extradata_size <= 0 && m_ctx->extradata == nullptr) {
//存放于解码器的上下文中,,在m_ctx释放的时候会自动释放
m_ctx->extradata_size = re;
m_ctx->extradata = (uint8_t*) av_malloc(m_ctx->extradata_size
+ AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(m_ctx->extradata, pkt->data, m_ctx->extradata_size);
}
}
三、ffmpeg5.0版本以上
ffmpeg5.0后 AVCodecParserContext
的 split 函数已删除
但可以使用 av_bsf_get_by_name("extract_extradata")
以下代码可供参考
bool VideoStreamUdpH26X::setupExtraData(const AVCodecParserContext* parser,
AVCodecContext* ctx,
AVPacket* pkt)
{
bool need = false;
// 检查输入参数及必要信息
if (!parser || !pkt || !ctx)
return need;
if (parser->width <= 0 || parser->height <= 0)
return need;
// 获取 extract_extradata BSF
const AVBitStreamFilter* bsf = av_bsf_get_by_name("extract_extradata");
if (!bsf) {
LOG_DEBUG() << "extract_extradata BSF not found";
return need;
}
AVBSFContext* bsf_ctx = nullptr;
auto ret = av_bsf_alloc(bsf, &bsf_ctx);
if (ret < 0) {
LOG_DEBUG() << "Failed to allocate BSF context, ret = " << ret;
return need;
}
// 设置 BSF 的输入参数(使用 parser 的部分信息)
bsf_ctx->par_in->codec_id = (AVCodecID) parser->parser->codec_ids[0];
bsf_ctx->par_in->codec_type = AVMEDIA_TYPE_VIDEO;
bsf_ctx->par_in->width = parser->width;
bsf_ctx->par_in->height = parser->height;
ret = av_bsf_init(bsf_ctx);
if (ret < 0) {
LOG_DEBUG() << "Failed to initialize BSF context, ret = " << ret;
av_bsf_free(&bsf_ctx);
return need;
}
// 将包送入 BSF 提取 extradata
ret = av_bsf_send_packet(bsf_ctx, pkt);
if (ret < 0) {
LOG_DEBUG() << "Failed to send packet to BSF, ret = " << ret << printError(ret);
av_bsf_free(&bsf_ctx);
return need;
}
// 从 BSF 中取出过滤后的包
ret = av_bsf_receive_packet(bsf_ctx, pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
LOG_DEBUG() << "Failed to receive packet from BSF, ret = " << ret << printError(ret);
av_bsf_free(&bsf_ctx);
return need;
}
size_t extradata_size = 0;
uint8_t* side_extradata = av_packet_get_side_data(pkt,
AV_PKT_DATA_NEW_EXTRADATA,
&extradata_size);
if (side_extradata && extradata_size > 0) {
// 更新 extradata
if (ctx->extradata_size != extradata_size) {
if (ctx->extradata) {
av_freep(&ctx->extradata);
ctx->extradata_size = 0;
}
ctx->extradata = (uint8_t*) av_malloc(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!ctx->extradata) {
LOG_DEBUG() << "Failed to allocate memory for extradata";
av_bsf_free(&bsf_ctx);
return need;
}
ctx->extradata_size = extradata_size;
memcpy(ctx->extradata, side_extradata, ctx->extradata_size);
LOG_DEBUG() << "Extracted extradata: "
<< QByteArray((char*) ctx->extradata, ctx->extradata_size).toHex();
}
need = true;
}
av_bsf_free(&bsf_ctx);
return need;
}