使用FFmpeg加载视频首帧时出现内存错误的问题排查
FFmpeg加载视频首帧触发内存错误的排查与修复
问题描述
调用FFmpeg实现的load_first_frame函数加载MP4首帧时,前7次调用正常,第8次调用触发malloc(): corrupted top size错误,错误发生在avcodec_send_packet阶段。Valgrind检测显示libswscale.so存在越界写操作,写入地址位于rgb_buffer分配的内存块之后,对应代码行是rgb_buffer = (uint8_t *) malloc(rgb_buffer_size);。
相关代码
uint8_t *load_first_frame(const char *file_path, AVFrame **frame) { int ret; AVFormatContext *format_ctx = NULL; if ((ret = avformat_open_input(&format_ctx, file_path, NULL, NULL)) < 0) { eprintf("could not open input file\n"); return NULL; } if ((ret = avformat_find_stream_info(format_ctx, NULL)) < 0) { eprintf("could not find stream info\n"); avformat_close_input(&format_ctx); return NULL; } int stream_idx = -1; for (unsigned int i = 0; i < format_ctx->nb_streams; i++) { if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { stream_idx = i; break; } } if (stream_idx == -1) { eprintf("could not find video stream\n"); avformat_close_input(&format_ctx); return NULL; } AVCodecParameters *codec_par = format_ctx->streams[stream_idx]->codecpar; const AVCodec *codec = avcodec_find_decoder(codec_par->codec_id); if (!codec) { eprintf("could not find decoder\n"); avformat_close_input(&format_ctx); return NULL; } AVCodecContext *codec_ctx = avcodec_alloc_context3(codec); if ((ret = avcodec_parameters_to_context(codec_ctx, codec_par)) < 0) { eprintf("could not copy codec parameters to context\n"); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return NULL; } if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0) { eprintf("could not open codec\n"); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return NULL; } AVPacket *packet = av_packet_alloc(); uint8_t *rgb_buffer = NULL; while (av_read_frame(format_ctx, packet) >= 0) { if (packet->stream_index != stream_idx) { av_packet_unref(packet); continue; } if (avcodec_send_packet(codec_ctx, packet) != 0) { av_packet_unref(packet); continue; } ret = avcodec_receive_frame(codec_ctx, *frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { av_packet_unref(packet); continue; } else if (ret < 0) { eprintf("error receiving frame: %d\n", ret); av_packet_unref(packet); av_packet_free(&packet); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return NULL; } struct SwsContext *sws_ctx = sws_getContext( (*frame)->width, (*frame)->height, codec_ctx->pix_fmt, (*frame)->width, (*frame)->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL ); if (!sws_ctx) { eprintf("failed to create SwsContext\n"); av_packet_unref(packet); av_packet_free(&packet); av_frame_free(frame); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return NULL; } int rgb_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, (*frame)->width, (*frame)->height, 1); if (rgb_buffer_size < 0) { eprintf("could not get buffer size\n"); sws_freeContext(sws_ctx); av_packet_unref(packet); av_packet_free(&packet); av_frame_free(frame); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return NULL; } rgb_buffer = (uint8_t *) malloc(rgb_buffer_size); if (rgb_buffer == NULL) { eprintf("failed to allocate RGB buffer\n"); sws_freeContext(sws_ctx); av_packet_unref(packet); av_packet_free(&packet); avformat_close_input(&format_ctx); av_frame_free(frame); avcodec_free_context(&codec_ctx); return NULL; } uint8_t *dst[4] = {rgb_buffer, NULL, NULL, NULL}; int dst_linesize[4] = {0}; av_image_fill_linesizes(dst_linesize, AV_PIX_FMT_RGB24, (*frame)->width); sws_scale(sws_ctx, (const uint8_t *const *)(*frame)->data, (*frame)->linesize, 0, (*frame)->height, dst, dst_linesize); sws_freeContext(sws_ctx); av_packet_unref(packet); break; } av_packet_unref(packet); av_packet_free(&packet); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); return rgb_buffer; }
Valgrind检测输出
==21777== Invalid write of size 8 ==21777== at 0x7426956: ??? (in /usr/lib/libswscale.so.8.1.100) ==21777== by 0x18497CBF: ??? ==21777== by 0x35E3AD3F: ??? ==21777== Address 0x37f563f0 is 0 bytes after a block of size 2,194,560 alloc'd ==21777== at 0x48447A8: malloc (vg_replace_malloc.c:446) ==21777== by 0x1113CB: load_first_frame (main.c:503) ==21777== by 0x111995: draw_preview (main.c:605) ==21777== by 0x111E7F: render_files (main.c:672) ==21777== by 0x11209E: main (main.c:704) ==21777== ==21777== Invalid write of size 8 ==21777== at 0x742695B: ??? (in /usr/lib/libswscale.so.8.1.100) ==21777== by 0x18497CBF: ??? ==21777== by 0x35E3AD3F: ??? ==21777== Address 0x37f563f8 is 8 bytes after a block of size 2,194,560 alloc'd ==21777== at 0x48447A8: malloc (vg_replace_malloc.c:446) ==21777== by 0x1113CB: load_first_frame (main.c:503) ==21777== by 0x111995: draw_preview (main.c:605) ==21777== by 0x111E7F: render_files (main.c:672) ==21777== by 0x11209E: main (main.c:704) ==21777== ==21777== Invalid write of size 8 ==21777== at 0x7426956: ??? (in /usr/lib/libswscale.so.8.1.100) ==21777== by 0x183FE37F: ??? ==21777== by 0x185D16FF: ??? ==21777== Address 0x389b94e0 is 0 bytes after a block of size 943,200 alloc'd ==21777== at 0x48447A8: malloc (vg_replace_malloc.c:446) ==21777== by 0x1113CB: load_first_frame (main.c:503) ==21777== by 0x111995: draw_preview (main.c:605) ==21777== by 0x111E7F: render_files (main.c:672) ==21777== by 0x11209E: main (main.c:704)
问题根源
- 目标图像缓冲区初始化错误:手动设置
dst数组为{rgb_buffer, NULL, NULL, NULL}并仅用av_image_fill_linesizes填充linesize,不符合FFmpeg对图像缓冲区的要求。sws_scale内部可能会根据像素格式的平面数量访问dst的多个元素,即使RGB24是单平面,错误的linesize或指针设置会导致越界写入。 - 内存对齐问题:使用标准
malloc分配缓冲区,未考虑FFmpeg处理多媒体数据时的内存对齐需求,可能导致底层库访问超出分配范围的内存。
修复方案
1. 正确初始化目标图像缓冲区
用av_image_fill_arrays替代手动设置dst和dst_linesize,该函数会根据像素格式自动计算并设置正确的平面指针和linesize,避免越界:
// 替换原有的dst和dst_linesize初始化代码 uint8_t *dst[4]; int dst_linesize[4]; ret = av_image_fill_arrays(dst, dst_linesize, rgb_buffer, AV_PIX_FMT_RGB24, (*frame)->width, (*frame)->height, 1); if (ret < 0) { eprintf("failed to fill image arrays: %d\n", ret); sws_freeContext(sws_ctx); av_packet_unref(packet); av_packet_free(&packet); av_frame_free(frame); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); free(rgb_buffer); return NULL; }
2. 使用FFmpeg内存分配函数
将malloc替换为av_malloc,确保内存对齐符合FFmpeg要求:
rgb_buffer = (uint8_t *) av_malloc(rgb_buffer_size);
注意:后续释放该缓冲区时需用av_free替代free。
3. 完善错误处理路径
在新增的av_image_fill_arrays错误分支中,需释放已分配的rgb_buffer,避免内存泄漏。
修复后代码片段(关键部分)
// ... 省略其他代码 ... rgb_buffer = (uint8_t *) av_malloc(rgb_buffer_size); if (rgb_buffer == NULL) { eprintf("failed to allocate RGB buffer\n"); sws_freeContext(sws_ctx); av_packet_unref(packet); av_packet_free(&packet); avformat_close_input(&format_ctx); av_frame_free(frame); avcodec_free_context(&codec_ctx); return NULL; } uint8_t *dst[4]; int dst_linesize[4]; ret = av_image_fill_arrays(dst, dst_linesize, rgb_buffer, AV_PIX_FMT_RGB24, (*frame)->width, (*frame)->height, 1); if (ret < 0) { eprintf("failed to fill image arrays: %d\n", ret); sws_freeContext(sws_ctx); av_packet_unref(packet); av_packet_free(&packet); av_frame_free(frame); avformat_close_input(&format_ctx); avcodec_free_context(&codec_ctx); av_free(rgb_buffer); return NULL; } sws_scale(sws_ctx, (const uint8_t *const *)(*frame)->data, (*frame)->linesize, 0, (*frame)->height, dst, dst_linesize); // ... 省略其他代码 ...
内容的提问来源于stack exchange,提问作者rakivo
相关产品推荐
相关产品推荐

