FFmpeg将BGR编码为YUV420P H.264视频生成空文件问题求助
问题排查:DirectX捕获BGR编码H.264 MP4无法播放
问题描述
我是FFmpeg新手,尝试将DirectX捕获的BGR格式屏幕数据编码为H.264 YUV420P的MP4视频。程序包含初始化setup函数和逐帧写入addImage函数,通过sws_scale转换像素格式后写入视频。运行时文件大小正常增长,但VLC无法播放,无法读取时长和编码信息。FFmpeg日志提示时间戳未设置等警告,多数API调用返回成功(avcodec_receive_packet偶尔返回-11符合文档预期)。
环境:Windows 10,clang++ 12.0.1,基于FFmpeg 5.1库开发。
相关代码
VideoService.h
#ifndef VIDEO_SERVICE_H #define VIDEO_SERVICE_H extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libswscale/swscale.h> } class VideoService { public: void setup(); void addImage(unsigned char* data, int lineSize, int width, int height, int align); private: AVCodecContext* context; AVFormatContext* formatContext; AVFrame* bgrFrame; AVFrame* yuvFrame; AVStream* videoStream; SwsContext* swsContext; }; #endif
VideoService.cpp
#include "VideoService.h" #include <stdio.h> void FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vargs) { FILE* f = fopen("ffmpeg.txt", "a"); fprintf(f, fmt, vargs); fclose(f); } void VideoService::setup() { int result = 0; av_log_set_level(AV_LOG_VERBOSE); av_log_set_callback(FfmpegLogCallback); bgrFrame = av_frame_alloc(); bgrFrame->width = 1920; bgrFrame->height = 1080; bgrFrame->format = AV_PIX_FMT_BGRA; bgrFrame->time_base.num = 1; bgrFrame->time_base.den = 60; result = av_frame_get_buffer(bgrFrame, 1); yuvFrame = av_frame_alloc(); yuvFrame->width = 1920; yuvFrame->height = 1080; yuvFrame->format = AV_PIX_FMT_YUV420P; yuvFrame->time_base.num = 1; yuvFrame->time_base.den = 60; result = av_frame_get_buffer(yuvFrame, 1); const AVOutputFormat* outputFormat = av_guess_format("mp4", "video.mp4", "video/mp4"); result = avformat_alloc_output_context2( &formatContext, outputFormat, "mp4", "video.mp4" ); formatContext->oformat = outputFormat; const AVCodec* codec = avcodec_find_encoder(AVCodecID::AV_CODEC_ID_H264); result = avio_open2(&formatContext->pb, "video.mp4", AVIO_FLAG_WRITE, NULL, NULL); videoStream = avformat_new_stream(formatContext, codec); AVCodecParameters* codecParameters = videoStream->codecpar; codecParameters->codec_type = AVMediaType::AVMEDIA_TYPE_VIDEO; codecParameters->codec_id = AVCodecID::AV_CODEC_ID_HEVC; codecParameters->width = 1920; codecParameters->height = 1080; codecParameters->format = AVPixelFormat::AV_PIX_FMT_YUV420P; videoStream->time_base.num = 1; videoStream->time_base.den = 60; result = avformat_write_header(formatContext, NULL); codec = avcodec_find_encoder(videoStream->codecpar->codec_id); context = avcodec_alloc_context3(codec); context->time_base.num = 1; context->time_base.den = 60; avcodec_parameters_to_context(context, videoStream->codecpar); result = avcodec_open2(context, codec, nullptr); swsContext = sws_getContext(1920, 1080, AV_PIX_FMT_BGRA, 1920, 1080, AV_PIX_FMT_YUV420P, 0, 0, 0, 0); } void VideoService::addImage(unsigned char* data, int lineSize, int width, int height, int align) { int result = 0; result = av_image_fill_arrays(bgrFrame->data, bgrFrame->linesize, data, AV_PIX_FMT_BGRA, 1920, 1080, 1); sws_scale(swsContext, bgrFrame->data, bgrFrame->linesize, 0, 1080, &yuvFrame->data[0], yuvFrame->linesize); result = avcodec_send_frame(context, yuvFrame); AVPacket *packet = av_packet_alloc(); result = avcodec_receive_packet(context, packet); if (result != 0) { return; } result = av_interleaved_write_frame(formatContext, packet); }
问题原因分析
- 编码器ID不匹配:初始化时先查找H.264编码器,但随后给流参数设置的是
AV_CODEC_ID_HEVC(H.265),编码器与实际编码格式冲突,导致编码数据异常。 - 时间戳缺失:
AVFrame和AVPacket未设置正确的时间戳(pts/dts),MP4容器依赖时间戳解析播放顺序和时长,缺失会导致播放器无法识别文件结构。 - 缺少收尾流程:编码结束后未刷新编码器剩余数据包,也未写入文件尾(trailer),MP4文件没有尾段的话,播放器无法正确解析元数据。
- AVFrame内存管理错误:
bgrFrame先调用av_frame_get_buffer分配内存,随后addImage中用av_image_fill_arrays覆盖数据指针,导致内存泄漏且可能引发对齐问题。 - 编码器上下文初始化冗余:重复查找编码器,且未补充必要的编码参数(如比特率、关键帧间隔),可能导致编码器工作异常。
修复步骤
1. 修正编码器ID
将流参数中的编码ID改为H.264,与查找的编码器一致:
codecParameters->codec_id = AVCodecID::AV_CODEC_ID_H264;
2. 添加时间戳管理
- 在
VideoService类中添加私有成员int64_t frameCount = 0;用于帧计数 - 在
addImage中设置yuvFrame的时间戳,并转换数据包的时间戳到流的时间基准:
// 设置yuvFrame的pts(基于编码器时间基准) yuvFrame->pts = frameCount++; // 发送帧到编码器 result = avcodec_send_frame(context, yuvFrame); // 接收数据包后转换时间戳 if (result == 0) { AVPacket *packet = av_packet_alloc(); while ((result = avcodec_receive_packet(context, packet)) == 0) { // 转换时间戳到流的时间基准 av_packet_rescale_ts(packet, context->time_base, videoStream->time_base); packet->stream_index = videoStream->index; av_interleaved_write_frame(formatContext, packet); av_packet_unref(packet); // 释放数据包引用 } av_packet_free(&packet); }
3. 添加收尾清理函数
新增cleanup函数,用于刷新编码器、写入文件尾并释放资源:
void VideoService::cleanup() { // 刷新编码器剩余数据包 avcodec_send_frame(context, nullptr); AVPacket* packet = av_packet_alloc(); int result; while ((result = avcodec_receive_packet(context, packet)) == 0) { av_packet_rescale_ts(packet, context->time_base, videoStream->time_base); packet->stream_index = videoStream->index; av_interleaved_write_frame(formatContext, packet); av_packet_unref(packet); } av_packet_free(&packet); // 写入文件尾 av_write_trailer(formatContext); // 关闭IO句柄 if (formatContext->pb) { avio_close(formatContext->pb); } // 释放所有资源 avcodec_free_context(&context); av_frame_free(&bgrFrame); av_frame_free(&yuvFrame); sws_freeContext(swsContext); avformat_free_context(formatContext); }
在程序结束时调用cleanup()完成收尾。
4. 修正AVFrame内存管理
bgrFrame用于绑定外部捕获数据,无需提前分配内存,在setup中去掉av_frame_get_buffer(bgrFrame, 1);:
void VideoService::setup() { // ... 其他代码 bgrFrame = av_frame_alloc(); bgrFrame->width = 1920; bgrFrame->height = 1080; bgrFrame->format = AV_PIX_FMT_BGRA; bgrFrame->time_base.num = 1; bgrFrame->time_base.den = 60; // 去掉av_frame_get_buffer调用 // ... 其他代码 }
5. 优化编码器上下文初始化
去掉冗余的编码器查找,补充必要的编码参数:
// 创建流后直接初始化编码器上下文 context = avcodec_alloc_context3(codec); avcodec_parameters_to_context(context, videoStream->codecpar); // 补充编码参数 context->bit_rate = 5000000; // 设置比特率为5Mbps context->gop_size = 12; // 每12帧生成一个关键帧 context->max_b_frames = 2; context->pix_fmt = AV_PIX_FMT_YUV420P; context->time_base = videoStream->time_base; // 打开编码器 result = avcodec_open2(context, codec, nullptr);
内容的提问来源于stack exchange,提问作者Cogentleman
相关产品推荐
相关产品推荐

