【记录一个问题】使用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;
}

上述的代码出现两个问题:

  1. 当一个视频不是 faststart 格式时(mdat在前,moov在后)
  • avformat_open_input(&fmt_ctx, NULL, NULL, NULL); 这行代码读完了 buffer 中的所有数据。(其实也合理,毕竟 moov 在尾部)
  • 使用 av_read_frame() 的时候返回错误码:
  1. 当视频是 faststart 格式时:
  • 可以正常输出每一帧的字节数,但是出现大量错误提醒:“[NULL @ 0x14a904080] Invalid NAL unit size (-161925121 > 57227).”

原本以为 ffmpeg api 已经无比成熟,各种例子也应该是完美无瑕,看来有的坑必须还是要踩一踩。

posted on   ahfuzhang  阅读(18)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
历史上的今天:
2019-01-15 visp库中解决lapack库的问题
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示