You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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)

问题根源

  1. 目标图像缓冲区初始化错误:手动设置dst数组为{rgb_buffer, NULL, NULL, NULL}并仅用av_image_fill_linesizes填充linesize,不符合FFmpeg对图像缓冲区的要求。sws_scale内部可能会根据像素格式的平面数量访问dst的多个元素,即使RGB24是单平面,错误的linesize或指针设置会导致越界写入。
  2. 内存对齐问题:使用标准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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:03:09