【记录一个问题】使用ffmpeg api 读取一个已经完全载入内存的mp4文件失败
作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!
我一开始写了一段代码,从磁盘上加载一个mp4文件,并且输出每个视频帧的 size:
int show_frame_detail(const char *input_file) { int ret = 0; AVFormatContext *fmt_ctx = NULL; AVCodecContext *codec_ctx = NULL; AVPacket *pkt = av_packet_alloc(); // ret = avformat_open_input(&fmt_ctx, input_file, NULL, NULL); if (ret < 0) { P("Could not open input file %s, ret=%d", input_file, ret); return ret; } ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { P("Could not find stream information, ret=%d", ret); goto save_first_frame_on_error; } int video_stream_index = -1; for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { P("Could not find video stream"); goto save_first_frame_on_error; } AVCodecParameters *codec_params = fmt_ctx->streams[video_stream_index]->codecpar; const AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id); if (NULL == decoder) { P("Could not find decoder"); goto save_first_frame_on_error; } codec_ctx = avcodec_alloc_context3(decoder); if (NULL == codec_ctx) { P("Could not allocate codec context"); goto save_first_frame_on_error; } ret = avcodec_parameters_to_context(codec_ctx, codec_params); if (ret < 0) { P("Could not copy codec parameters to context,ret=%d", ret); goto save_first_frame_on_error; } ret = avcodec_open2(codec_ctx, decoder, NULL); if (ret < 0) { P("Could not open codec, ret=%d", ret); goto save_first_frame_on_error; } int frame_index = 0; printf("frame_index\tpkt_type\tsize\tpos\tpts\tdts\tduration\n"); for (;;) { ret = av_read_frame(fmt_ctx, pkt); if (ret < 0) { P("av_image_fill_arrays error, ret=%d", ret); goto save_first_frame_on_error; } if (pkt->stream_index != video_stream_index) { continue; } // 开始输出信息 frame_index++; printf("%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\n", frame_index, pkt->flags, pkt->size, pkt->pos, pkt->pts, pkt->dts, pkt->duration); } P("success"); save_first_frame_on_error: av_packet_free(&pkt); avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); return ret; }
基于上面的程序,我修改为:当整个mp4已经在内存中时,能不能从内存中解析每个视频帧:
struct buffer_data { uint8_t *ptr; size_t size; }; static int read_packet_1(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); if (!buf_size) return AVERROR_EOF; P("ptr:%p size:%zu, buf size=%d", bd->ptr, bd->size, buf_size); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; bd->size -= buf_size; return buf_size; } enum { SuccessOfShow, ErrOfAlloc, ErrOfAllocContext, }; int show_frame_detail_from_mem(void *data, uint64_t len) { av_log_set_level(AV_LOG_DEBUG); int ret = 0; AVFormatContext *fmt_ctx = NULL; AVIOContext *avio_ctx = NULL; uint8_t *avio_ctx_buffer = NULL; const size_t avio_ctx_buffer_size = 1024 * 4; // 缓冲区大小 struct buffer_data bd = { .ptr = (uint8_t *)data, .size = (size_t)len}; avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { P("av_malloc fail"); return ErrOfAlloc; } P(""); // 创建自定义 AVIOContext avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, read_packet_1, NULL, NULL); if (!avio_ctx) { P("Could not allocate AVIOContext"); ret = ErrOfAllocContext; goto on_error; } P(""); // fmt_ctx = avformat_alloc_context(); if (!fmt_ctx) { P("Could not allocate format context"); ret = ErrOfAllocContext; goto on_error; } fmt_ctx->pb = avio_ctx; fmt_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; // 打开输入文件 P(""); // AVDictionary *options = NULL; // av_dict_set(&options, "analyzeduration", "10000000", 0); // 10秒 // av_dict_set(&options, "probesize", "5000000", 0); // 5MB ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); if (ret < 0) { P("Could not open input"); goto on_error; } P(""); AVCodecContext *codec_ctx = NULL; int video_stream_index = -1; for (int i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } P(""); if (video_stream_index == -1) { P("Could not find video stream"); ret = -1; goto on_error; } // AVCodecParameters *codec_params = fmt_ctx->streams[video_stream_index]->codecpar; const AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id); if (NULL == decoder) { P("Could not find decoder"); ret = -1; goto on_error; } P(""); codec_ctx = avcodec_alloc_context3(decoder); if (NULL == codec_ctx) { P("Could not allocate codec context"); ret = -1; goto on_error; } ret = avcodec_parameters_to_context(codec_ctx, codec_params); if (ret < 0) { P("Could not copy codec parameters to context,ret=%d", ret); ret = -1; goto on_error; } P(""); ret = avcodec_open2(codec_ctx, decoder, NULL); if (ret < 0) { P("Could not open codec, ret=%d", ret); ret = -1; goto on_error; } // AVPacket *pkt = av_packet_alloc(); ret = av_new_packet(pkt, codec_ctx->width * codec_ctx->height); if (ret < 0) { P("av_new_packet error, ret=%d", ret); ret = -1; goto on_error; } int frame_index = 0; //bd.ptr = data; //bd.size = len; printf("frame_index\tpkt_type\tsize\tpos\tpts\tdts\tduration\n"); for (;;) { P(""); ret = av_read_frame(fmt_ctx, pkt); if (ret < 0) { P("av_read_frame error, ret=%d", ret); goto on_error; } if (pkt->stream_index != video_stream_index) { av_packet_unref(pkt); continue; } // 开始输出信息 frame_index++; printf("%d\t%d\t%d\t%lld\t%lld\t%lld\t%lld\n", frame_index, pkt->flags, pkt->size, pkt->pos, pkt->pts, pkt->dts, pkt->duration); av_packet_unref(pkt); } P("success"); on_error: if (pkt != NULL) { av_packet_free(&pkt); } if (codec_ctx != NULL) { avcodec_free_context(&codec_ctx); } if (avio_ctx_buffer != NULL) { av_free(avio_ctx_buffer); } if (fmt_ctx != NULL) { avformat_close_input(&fmt_ctx); // avformat_free_context(fmt_ctx); } if (avio_ctx != NULL) { avio_context_free(&avio_ctx); } return ret; }
上述的代码出现两个问题:
- 当一个视频不是 faststart 格式时(mdat在前,moov在后)
- avformat_open_input(&fmt_ctx, NULL, NULL, NULL); 这行代码读完了 buffer 中的所有数据。(其实也合理,毕竟 moov 在尾部)
- 使用 av_read_frame() 的时候返回错误码:
- 当视频是 faststart 格式时:
- 可以正常输出每一帧的字节数,但是出现大量错误提醒:“[NULL @ 0x14a904080] Invalid NAL unit size (-161925121 > 57227).”
原本以为 ffmpeg api 已经无比成熟,各种例子也应该是完美无瑕,看来有的坑必须还是要踩一踩。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
2019-01-15 visp库中解决lapack库的问题