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

调用avformat_write_header()崩溃,求解决FFmpeg保存BGR内存图像序列为MP4的问题

调用avformat_write_header()崩溃,求解决FFmpeg保存BGR内存图像序列为MP4的问题

我瞅了你的代码,发现几个关键问题导致了avformat_write_header()崩溃,咱们一步步来修复:

问题1:没打开输出文件的IO通道(最致命)

avformat_alloc_output_context2只是帮你创建了格式上下文的壳子,但并没有实际打开磁盘上的output.mp4文件。没有IO读写通道的情况下,调用写入头的函数肯定会直接崩掉,必须补上打开文件的步骤。

问题2:手动修改流ID搞乱了内部状态

代码里outStream->id = outFormatCtx->nb_streams++;完全是多余操作,avformat_new_stream会自动给新流分配正确的ID,手动改这个值会破坏FFmpeg内部的计数逻辑,导致后续操作出错,直接删掉就行。

问题3:MP4需要全局头参数

MP4容器要求视频流使用全局头,你得在打开编码器之前,给编码器上下文加上AV_CODEC_FLAG_GLOBAL_HEADER标志,不然写入头信息时会出现兼容性问题。

问题4:编码器帧率参数没设置全

你只设置了time_base,但没设置framerate,这会导致流的帧率参数异常,需要补上outCodecCtx->framerate = av_make_q(fps, 1);。

问题5:冗余的CodecPar手动赋值

你已经调用了avcodec_parameters_from_context(outStream->codecpar, outCodecCtx)来同步编码器参数到流,之前手动给outStream->codecpar赋值的代码完全多余,还可能和自动同步的参数冲突,直接删掉就行。


修改后的完整代码

#include <iostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <opencv2/opencv.hpp>

extern "C" {
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavio/avio.h> // 新增:需要avio_open的头文件
}

using namespace std;
using namespace cv;

int main()
{
    // Set up input frames as BGR byte arrays
    vector<Mat> frames;
    int width = 640;
    int height = 480;
    int num_frames = 100;
    Scalar black(0, 0, 0);
    Scalar white(255, 255, 255);
    int font = FONT_HERSHEY_SIMPLEX;
    double font_scale = 1.0;
    int thickness = 2;

    for (int i = 0; i < num_frames; i++) {
        Mat frame = Mat::zeros(height, width, CV_8UC3);
        putText(frame, std::to_string(i), Point(width / 2 - 50, height / 2), font, font_scale, white, thickness);
        frames.push_back(frame);
    }

    // Initialize FFmpeg
    av_register_all(); // 可选:部分旧版本FFmpeg需要这个初始化

    // Set up output file
    AVFormatContext* outFormatCtx = nullptr;
    AVCodecContext* outCodecCtx = nullptr;
    AVStream* outStream = nullptr;
    AVPacket* outPacket = av_packet_alloc(); // 改用av_packet_alloc更安全
    const char* outFile = "output.mp4";
    int outWidth = frames[0].cols;
    int outHeight = frames[0].rows;
    int fps = 30;

    // Open output file
    int ret = avformat_alloc_output_context2(&outFormatCtx, nullptr, nullptr, outFile);
    if (!outFormatCtx) {
        cerr << "Error: Could not allocate output format context" << endl;
        return -1;
    }

    // 新增:打开输出文件的IO通道
    if (!(outFormatCtx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&outFormatCtx->pb, outFile, AVIO_FLAG_WRITE);
        if (ret < 0) {
            cerr << "Error: Could not open output file" << endl;
            avformat_free_context(outFormatCtx);
            return -1;
        }
    }

    // Set up output codec
    const AVCodec* outCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!outCodec) {
        cerr << "Error: Could not find H.264 codec" << endl;
        return -1;
    }

    outCodecCtx = avcodec_alloc_context3(outCodec);
    if (!outCodecCtx) {
        cerr << "Error: Could not allocate output codec context" << endl;
        return -1;
    }

    outCodecCtx->codec_id = AV_CODEC_ID_H264;
    outCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    outCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    outCodecCtx->width = outWidth;
    outCodecCtx->height = outHeight;
    outCodecCtx->time_base = { 1, fps };
    outCodecCtx->framerate = av_make_q(fps, 1); // 新增:设置帧率
    outCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 新增:设置全局头

    // Open output codec
    if (avcodec_open2(outCodecCtx, outCodec, nullptr) < 0) {
        cerr << "Error: Could not open output codec" << endl;
        return -1;
    }

    // Create output stream
    outStream = avformat_new_stream(outFormatCtx, outCodec);
    if (!outStream) {
        cerr << "Error: Could not allocate output stream" << endl;
        return -1;
    }

    // 移除:之前手动设置codecpar的冗余代码,用下面的函数自动同步
    ret = avcodec_parameters_from_context(outStream->codecpar, outCodecCtx);
    if (ret < 0) {
        cerr << "Error: Could not copy codec parameters to output stream" << endl;
        return -1;
    }

    outStream->avg_frame_rate = outCodecCtx->framerate;
    // 移除:手动设置outStream->id的错误代码

    ret = avformat_write_header(outFormatCtx, nullptr);
    if (ret < 0) {
        char err_buf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(ret, err_buf, AV_ERROR_MAX_STRING_SIZE);
        cerr << "Error: Could not write output header, detail: " << err_buf << endl;
        return -1;
    }

    // Convert frames to YUV format and write to output file
    for (int i = 0; i < frames.size(); i++) {
        const auto& frame = frames[i];
        AVFrame* yuvFrame = av_frame_alloc();
        if (!yuvFrame) {
            cerr << "Error: Could not allocate YUV frame" << endl;
            return -1;
        }

        ret = av_image_alloc(yuvFrame->data, yuvFrame->linesize, outWidth, outHeight, AV_PIX_FMT_YUV420P, 32);
        if (ret < 0) {
            cerr << "Error: Could not allocate YUV frame buffer" << endl;
            av_frame_free(&yuvFrame);
            return -1;
        }

        // Convert BGR frame to YUV format
        Mat yuvMat;
        cvtColor(frame, yuvMat, COLOR_BGR2YUV_I420);

        memcpy(yuvFrame->data[0], yuvMat.data, outWidth * outHeight);
        memcpy(yuvFrame->data[1], yuvMat.data + outWidth * outHeight, outWidth * outHeight / 4);
        memcpy(yuvFrame->data[2], yuvMat.data + outWidth * outHeight * 5 / 4, outWidth * outHeight / 4);

        yuvFrame->pts = i; // 新增:设置帧的pts,否则视频播放会有问题

        // Encode frame and write to output file
        ret = avcodec_send_frame(outCodecCtx, yuvFrame);
        if (ret < 0) {
            cerr << "Error: Could not send frame to output codec" << endl;
            av_frame_free(&yuvFrame);
            return -1;
        }

        while (ret >= 0) {
            ret = avcodec_receive_packet(outCodecCtx, outPacket);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                char err_buf[AV_ERROR_MAX_STRING_SIZE];
                av_strerror(ret, err_buf, AV_ERROR_MAX_STRING_SIZE);
                cerr << "Error: Could not receive packet from output codec, detail: " << err_buf << endl;
                av_frame_free(&yuvFrame);
                return -1;
            }

            av_packet_rescale_ts(outPacket, outCodecCtx->time_base, outStream->time_base);
            outPacket->stream_index = outStream->index;

            ret = av_interleaved_write_frame(outFormatCtx, outPacket);
            if (ret < 0) {
                char err_buf[AV_ERROR_MAX_STRING_SIZE];
                av_strerror(ret, err_buf, AV_ERROR_MAX_STRING_SIZE);
                cerr << "Error: Could not write packet to output file, detail: " << err_buf << endl;
                av_frame_free(&yuvFrame);
                return -1;
            }
            av_packet_unref(outPacket); // 新增:释放packet的引用,避免内存泄漏
        }

        av_freep(&yuvFrame->data[0]); // 新增:释放av_image_alloc分配的缓冲区
        av_frame_free(&yuvFrame);
    }

    // Write output trailer
    av_write_trailer(outFormatCtx);

    // Clean up
    if (!(outFormatCtx->oformat->flags & AVFMT_NOFILE)) {
        avio_close(outFormatCtx->pb); // 新增:关闭IO通道
    }
    avcodec_close(outCodecCtx);
    avcodec_free_context(&outCodecCtx);
    av_packet_free(&outPacket); // 新增:释放packet
    avformat_free_context(outFormatCtx);

    cout << "Output file saved successfully!" << endl;
    return 0;
}

额外补充

  • 我还新增了帧的pts设置,否则视频播放时可能出现帧率异常的问题;
  • 加上了错误信息的详细打印(用av_strerror),方便后续排查问题;
  • 修复了内存泄漏的问题,比如释放av_image_alloc分配的缓冲区,以及AVPacket的引用释放。

备注:内容来源于stack exchange,提问作者ollydbg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 07:52:58