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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:54:20