使用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); }
关键修复点说明
- 明确指定MP4容器:调用
avformat_alloc_output_context2时直接指定"mp4"格式,避免自动检测出错 - 修复输入帧引用:将循环内的无效
srcFrame替换为遍历的input_frame,确保编码真实输入数据 - 同步编码器参数:使用
avcodec_parameters_from_context将编码器参数同步到流,让容器正确识别编码信息 - 添加H.264兼容参数:设置
baselineprofile等参数,确保生成的视频能被Windows媒体播放器等老旧设备兼容 - 正确处理像素格式转换:仅在输入格式非YUV420P时初始化转换器,且源格式匹配输入帧实际格式
- 修复时间戳计算:调整
av_rescale_q参数顺序,确保时间基转换逻辑正确 - 完整关闭文件句柄:添加
avio_close确保文件写入完整,避免文件损坏 - 优化资源释放:使用goto统一处理资源释放,避免遗漏
内容的提问来源于stack exchange,提问作者nokla
相关产品推荐
相关产品推荐

