如何使用libav/ffmpeg的av_parser_parse2()解析解码H264文件?
解决av_parser_parse2解码MP4文件的错误问题
问题根源
- MP4是容器格式,包含文件结构、音频流、索引等非视频码流数据,
av_parser_parse2仅能处理裸H.264码流(即纯NAL单元序列),直接读取MP4原始二进制数据喂给解析器会导致无效数据被解析,最终触发avcodec_send_packet错误。 - 你注释了AVFormat相关的容器解析代码,跳过了容器解封装步骤,这是核心错误。
parser->flags = PARSER_FLAG_COMPLETE_FRAMES;设置不当,H.264码流的NAL单元可能分散在多个数据包中,该标志会强制解析器只输出完整帧,导致解析逻辑异常。
修正方案
- 恢复AVFormatContext的容器解析逻辑,分离出视频流的裸H.264数据
- 将视频流的
codecpar参数导入到AVCodecContext,确保解码器拿到正确的宽高、编码参数等信息 - 移除错误的解析器标志设置
- 从AVPacket中提取裸码流数据喂给
av_parser_parse2
修正后的完整代码
extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } #define INBUF_SIZE 256000 void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt); int main(int argc, char** argv) { const char* filename = "D:\\test.mp4"; const AVCodec* codec; AVFormatContext* formatCtx = NULL; AVCodecParserContext* parser; AVCodecContext* c = NULL; AVStream* videoStream = NULL; AVFrame* frame; uint8_t* data; size_t data_size; int ret; AVPacket* pkt = av_packet_alloc(); AVPacket* parser_pkt = av_packet_alloc(); if (!pkt || !parser_pkt) exit(1); // 初始化容器上下文,解封装MP4 if (avformat_open_input(&formatCtx, filename, nullptr, nullptr) < 0) { fprintf(stderr, "Could not open source file\n"); exit(1); } if (avformat_find_stream_info(formatCtx, nullptr) < 0) { fprintf(stderr, "Could not find stream information\n"); exit(1); } // 查找视频流 int video_stream_idx = -1; for (int i = 0; i < formatCtx->nb_streams; i++) { if (formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_idx = i; videoStream = formatCtx->streams[i]; break; } } if (video_stream_idx == -1) { fprintf(stderr, "No video stream found\n"); exit(1); } // 查找H.264解码器 codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); } parser = av_parser_init(codec->id); if (!parser) { fprintf(stderr, "parser not found\n"); exit(1); } // 初始化解码器上下文 c = avcodec_alloc_context3(codec); if (!c) { fprintf(stderr, "Could not allocate video codec context\n"); exit(1); } // 导入视频流的编码参数 if (avcodec_parameters_to_context(c, videoStream->codecpar) < 0) { fprintf(stderr, "Could not copy codec parameters to context\n"); exit(1); } // 打开解码器 if (avcodec_open2(c, codec, NULL) < 0) { fprintf(stderr, "Could not open codec\n"); exit(1); } frame = av_frame_alloc(); if (!frame) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } // 读取容器中的数据包,提取视频流数据喂给解析器 while (av_read_frame(formatCtx, pkt) == 0) { if (pkt->stream_index != video_stream_idx) { av_packet_unref(pkt); continue; } data = pkt->data; data_size = pkt->size; while (data_size > 0) { ret = av_parser_parse2(parser, c, &parser_pkt->data, &parser_pkt->size, data, data_size, pkt->pts, pkt->dts, pkt->pos); if (ret < 0) { fprintf(stderr, "Error while parsing\n"); exit(1); } data += ret; data_size -= ret; if (parser_pkt->size) { // 传递解析后的数据包给解码器 decode(c, frame, parser_pkt); av_packet_unref(parser_pkt); } } av_packet_unref(pkt); } // 刷新解码器 decode(c, frame, NULL); // 释放资源 av_parser_close(parser); avcodec_free_context(&c); av_frame_free(&frame); av_packet_free(&pkt); av_packet_free(&parser_pkt); avformat_close_input(&formatCtx); return 0; } void decode(AVCodecContext* dec_ctx, AVFrame* frame, AVPacket* pkt) { int ret; ret = avcodec_send_packet(dec_ctx, pkt); if (ret < 0) { char buff[255]{ 0 }; av_make_error_string(buff, 255, ret); fprintf(stderr, "Error sending a packet for decoding: %s\n", buff); exit(1); } while (ret >= 0) { ret = avcodec_receive_frame(dec_ctx, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) return; else if (ret < 0) { char buff[255]{ 0 }; av_make_error_string(buff, 255, ret); fprintf(stderr, "Error during decoding: %s\n", buff); exit(1); } printf("saving frame %3d\n", dec_ctx->frame_number); fflush(stdout); // 处理解码后的帧,比如保存为图片等 } }
关键说明
- 先用
avformat_open_input和avformat_find_stream_info解析MP4容器,分离出视频流,确保只处理视频相关的裸码流数据。 - 解析器
av_parser_parse2的作用是将连续的裸码流拆分为单个NAL单元对应的AVPacket,再传递给解码器。 - 每次处理完数据包后调用
av_packet_unref释放资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者isrepeat
相关产品推荐
相关产品推荐

