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

使用H.264编码器保存AVFrames向量为MP4时格式不支持的问题

问题分析与修复方案

核心问题排查

你的代码能运行但生成的MP4无法播放,主要存在以下关键问题:

  • 未正确引用输入帧:循环内的srcFrame未定义,实际应使用遍历的frame变量,导致编码的是无效/空白帧数据
  • 编码器参数未同步到流:设置codec_ctx后未将参数回写到stream->codecpar,容器无法识别编码参数
  • 像素格式转换逻辑错误:转换上下文的源格式误用codec_ctx->pix_fmt,未匹配输入帧的实际格式
  • 文件句柄未关闭:avio_open打开的文件未调用avio_close,可能导致文件写入不完整
  • 时间戳计算顺序错误:av_rescale_q参数顺序颠倒,时间基转换逻辑失效
  • H.264兼容性参数缺失:未设置基础编码参数,导致生成的视频无法被Windows媒体播放器等兼容

修复后的完整代码

void SaveVideo(std::string& output_filename, std::vector<AVFrame>& video)
{
    // 初始化FFmpeg
    avformat_network_init();

    // 创建输出上下文,明确指定MP4容器
    AVFormatContext* format_ctx = nullptr;
    int ret = avformat_alloc_output_context2(&format_ctx, nullptr, "mp4", output_filename.c_str());
    if (ret < 0) {
        wxMessageBox("Error creating output context: ");
        wxMessageBox(av_err2str(ret));
        return;
    }

    // 打开输出文件
    ret = avio_open(&format_ctx->pb, output_filename.c_str(), AVIO_FLAG_WRITE);
    if (ret < 0) {
        std::cerr << "Error opening output file: " << av_err2str(ret) << std::endl;
        avformat_free_context(format_ctx);
        return;
    }

    // 查找H.264编码器
    const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        std::cerr << "Error finding H.264 encoder" << std::endl;
        avio_close(format_ctx->pb);
        avformat_free_context(format_ctx);
        return;
    }

    // 创建视频流
    AVStream* stream = avformat_new_stream(format_ctx, codec);
    if (!stream) {
        std::cerr << "Error creating output stream" << std::endl;
        avio_close(format_ctx->pb);
        avformat_free_context(format_ctx);
        return;
    }

    // 初始化编码器上下文
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        std::cerr << "Error allocating codec context" << std::endl;
        avio_close(format_ctx->pb);
        avformat_free_context(format_ctx);
        return;
    }

    // 设置编码器核心参数
    codec_ctx->codec_id = AV_CODEC_ID_H264;
    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    codec_ctx->width = video.front().width;
    codec_ctx->height = video.front().height;
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    codec_ctx->bit_rate = 400000;
    AVRational framerate = {30, 1};
    codec_ctx->time_base = av_inv_q(framerate);
    codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    // 设置H.264兼容性参数,适配Windows播放器
    AVDictionary* opt = nullptr;
    av_dict_set(&opt, "preset", "medium", 0);
    av_dict_set(&opt, "crf", "23", 0);
    av_dict_set(&opt, "profile", "baseline", 0);

    // 打开编码器
    ret = avcodec_open2(codec_ctx, codec, &opt);
    av_dict_free(&opt);
    if (ret < 0) {
        wxMessageBox("Error opening codec: ");
        wxMessageBox(av_err2str(ret));
        avcodec_free_context(&codec_ctx);
        avio_close(format_ctx->pb);
        avformat_free_context(format_ctx);
        return;
    }

    // 将编码器参数同步到流,确保容器识别编码信息
    ret = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
    if (ret < 0) {
        std::cerr << "Error syncing codec parameters to stream: " << av_err2str(ret) << std::endl;
        avcodec_free_context(&codec_ctx);
        avio_close(format_ctx->pb);
        avformat_free_context(format_ctx);
        return;
    }
    stream->time_base = codec_ctx->time_base;

    // 初始化像素格式转换器(仅当输入格式不是YUV420P时)
    SwsContext* converter = nullptr;
    AVFrame* converted_frame = nullptr;
    if ((AVPixelFormat)video.front().format != AV_PIX_FMT_YUV420P) {
        converter = sws_getContext(
            codec_ctx->width, codec_ctx->height, (AVPixelFormat)video.front().format,
            codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
            SWS_BICUBIC, nullptr, nullptr, nullptr
        );
        if (!converter) {
            std::cerr << "Error initializing converter" << std::endl;
            avcodec_free_context(&codec_ctx);
            avio_close(format_ctx->pb);
            avformat_free_context(format_ctx);
            return;
        }

        // 分配转换帧缓冲区
        converted_frame = av_frame_alloc();
        if (!converted_frame) {
            std::cerr << "Error allocating converted frame" << std::endl;
            sws_freeContext(converter);
            avcodec_free_context(&codec_ctx);
            avio_close(format_ctx->pb);
            avformat_free_context(format_ctx);
            return;
        }
        converted_frame->format = AV_PIX_FMT_YUV420P;
        converted_frame->width = codec_ctx->width;
        converted_frame->height = codec_ctx->height;
        ret = av_frame_get_buffer(converted_frame, 0);
        if (ret < 0) {
            std::cerr << "Error allocating converted frame buffer: " << av_err2str(ret) << std::endl;
            av_frame_free(&converted_frame);
            sws_freeContext(converter);
            avcodec_free_context(&codec_ctx);
            avio_close(format_ctx->pb);
            avformat_free_context(format_ctx);
            return;
        }
    }

    // 写入文件头
    ret = avformat_write_header(format_ctx, nullptr);
    if (ret < 0) {
        std::cerr << "Error writing header to output file: " << av_err2str(ret) << std::endl;
        goto cleanup;
    }

    // 遍历编码所有帧
    int frame_count = 0;
    for (auto& input_frame : video) {
        AVFrame* frame_to_encode = &input_frame;
        if (converter) {
            // 转换像素格式
            sws_scale(converter,
                input_frame.data, input_frame.linesize, 0, input_frame.height,
                converted_frame->data, converted_frame->linesize
            );
            converted_frame->pts = av_rescale_q(frame_count, av_make_q(1, 30), codec_ctx->time_base);
            frame_to_encode = converted_frame;
        } else {
            frame_to_encode->pts = av_rescale_q(frame_count, av_make_q(1, 30), codec_ctx->time_base);
        }

        // 发送帧到编码器
        ret = avcodec_send_frame(codec_ctx, frame_to_encode);
        if (ret < 0) {
            std::cerr << "Error sending frame for encoding: " << av_err2str(ret) << std::endl;
            break;
        }

        // 获取编码包并写入文件
        AVPacket* pkt = av_packet_alloc();
        if (!pkt) {
            std::cerr << "Error allocating packet" << std::endl;
            break;
        }
        while (ret >= 0) {
            ret = avcodec_receive_packet(codec_ctx, pkt);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                break;
            } else if (ret < 0) {
                wxMessageBox("Error during encoding");
                wxMessageBox(av_err2str(ret));
                av_packet_free(&pkt);
                goto cleanup;
            }

            // 调整时间戳并写入
            av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
            pkt->stream_index = stream->index;
            ret = av_interleaved_write_frame(format_ctx, pkt);
            av_packet_unref(pkt);
            if (ret < 0) {
                std::cerr << "Error writing packet to output file: " << av_err2str(ret) << std::endl;
                av_packet_free(&pkt);
                goto cleanup;
            }
        }
        av_packet_free(&pkt);
        frame_count++;
    }

    // 刷新编码器剩余数据
    ret = avcodec_send_frame(codec_ctx, nullptr);
    if (ret < 0) {
        std::cerr << "Error flushing encoder: " << av_err2str(ret) << std::endl;
        goto cleanup;
    }

    while (ret >= 0) {
        AVPacket* pkt = av_packet_alloc();
        if (!pkt) {
            std::cerr << "Error allocating packet" << std::endl;
            goto cleanup;
        }
        ret = avcodec_receive_packet(codec_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            av_packet_free(&pkt);
            break;
        } else if (ret < 0) {
            std::cerr << "Error during encoding flush: " << av_err2str(ret) << std::endl;
            av_packet_unref(pkt);
            av_packet_free(&pkt);
            goto cleanup;
        }

        av_packet_rescale_ts(pkt, codec_ctx->time_base, stream->time_base);
        pkt->stream_index = stream->index;
        ret = av_interleaved_write_frame(format_ctx, pkt);
        av_packet_unref(pkt);
        av_packet_free(&pkt);
        if (ret < 0) {
            std::cerr << "Error writing flushed packet: " << av_err2str(ret) << std::endl;
            goto cleanup;
        }
    }

    // 写入文件尾
    ret = av_write_trailer(format_ctx);
    if (ret < 0) {
        std::cerr << "Error writing trailer to output file: " << av_err2str(ret) << std::endl;
    }

cleanup:
    // 统一释放所有资源
    if (converted_frame) av_frame_free(&converted_frame);
    if (converter) sws_freeContext(converter);
    avcodec_free_context(&codec_ctx);
    avio_close(format_ctx->pb);
    avformat_free_context(format_ctx);
}

关键修复点说明

  1. 明确指定MP4容器:调用avformat_alloc_output_context2时直接指定"mp4"格式,避免自动检测出错
  2. 修复输入帧引用:将循环内的无效srcFrame替换为遍历的input_frame,确保编码真实输入数据
  3. 同步编码器参数:使用avcodec_parameters_from_context将编码器参数同步到流,让容器正确识别编码信息
  4. 添加H.264兼容参数:设置baseline profile等参数,确保生成的视频能被Windows媒体播放器等老旧设备兼容
  5. 正确处理像素格式转换:仅在输入格式非YUV420P时初始化转换器,且源格式匹配输入帧实际格式
  6. 修复时间戳计算:调整av_rescale_q参数顺序,确保时间基转换逻辑正确
  7. 完整关闭文件句柄:添加avio_close确保文件写入完整,避免文件损坏
  8. 优化资源释放:使用goto统一处理资源释放,避免遗漏

内容的提问来源于stack exchange,提问作者nokla

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 15:27:01