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

基于FFmpeg C++将YUV文件编码为可播放H.264 MP4的问题

解决YUV420P转MP4(VLC可播放)的FFmpeg C++编码问题

你遇到的输出异常,核心原因是从本地YUV文件读取的帧没有适配FFmpeg编码器的要求,必须对输入帧做**格式填充、时间戳设置、格式转换(按需)**等处理,以下是具体实现步骤和代码:

核心处理要点

  • 帧数据正确填充:YUV420P是平面格式,需将读取的Y、U、V分量分别填充到AVFrame的data[0](Y)、data[1](U)、data[2](V)平面,同时设置对应linesize(Y行字节数为宽度,U/V行字节数为宽度/2)
  • 时间戳设置:必须给每个帧设置pts(显示时间戳),否则VLC会出现卡顿、跳帧甚至无法播放,按帧率累加计算即可
  • 格式转换(按需):若编码器要求的像素格式与输入YUV420P不一致,需用sws_scale做格式转换
  • 完整帧读取:YUV420P单帧大小固定为width*height*3/2,读取时必须确保每次读取完整一帧数据

完整C++代码实现

#include <iostream>
#include <fstream>
#include <string>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "用法: " << argv[0] << " <输入YUV路径> <输出MP4路径>" << std::endl;
        return -1;
    }

    const char* input_yuv = argv[1];
    const char* output_mp4 = argv[2];
    const int width = 1920;
    const int height = 1080;
    const int fps = 30;

    // 初始化FFmpeg组件
    av_register_all();
    avcodec_register_all();
    avformat_network_init();

    // 创建输出格式上下文
    AVFormatContext* fmt_ctx = nullptr;
    if (avformat_alloc_output_context2(&fmt_ctx, nullptr, "mp4", output_mp4) < 0) {
        std::cerr << "创建输出格式上下文失败" << std::endl;
        return -1;
    }

    // 查找H.264编码器
    const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        std::cerr << "未找到H.264编码器" << std::endl;
        return -1;
    }

    // 创建视频流并设置编码器参数
    AVStream* stream = avformat_new_stream(fmt_ctx, codec);
    if (!stream) {
        std::cerr << "创建视频流失败" << std::endl;
        return -1;
    }

    AVCodecContext* codec_ctx = stream->codec;
    codec_ctx->codec_id = AV_CODEC_ID_H264;
    codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
    codec_ctx->width = width;
    codec_ctx->height = height;
    codec_ctx->time_base = av_make_q(1, fps);
    codec_ctx->framerate = av_make_q(fps, 1);
    codec_ctx->bit_rate = 5000000; // 5Mbps码率
    codec_ctx->gop_size = 30; // 每30帧一个关键帧
    codec_ctx->max_b_frames = 0; // 关闭B帧,提升VLC兼容性

    // 设置全局头部(MP4格式要求)
    if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
        codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    // 打开编码器
    if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
        std::cerr << "打开编码器失败" << std::endl;
        return -1;
    }

    // 打开输出文件
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&fmt_ctx->pb, output_mp4, AVIO_FLAG_WRITE) < 0) {
            std::cerr << "打开输出文件失败" << std::endl;
            return -1;
        }
    }

    // 写入文件头
    if (avformat_write_header(fmt_ctx, nullptr) < 0) {
        std::cerr << "写入文件头失败" << std::endl;
        return -1;
    }

    // 初始化编码帧
    AVFrame* frame = av_frame_alloc();
    frame->format = codec_ctx->pix_fmt;
    frame->width = width;
    frame->height = height;
    if (av_frame_get_buffer(frame, 32) < 0) {
        std::cerr << "分配帧缓冲区失败" << std::endl;
        return -1;
    }

    // 初始化格式转换上下文(如果编码器格式不匹配)
    struct SwsContext* sws_ctx = nullptr;
    if (codec_ctx->pix_fmt != AV_PIX_FMT_YUV420P) {
        sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P,
                                width, height, codec_ctx->pix_fmt,
                                SWS_BILINEAR, nullptr, nullptr, nullptr);
    }

    // 读取YUV文件并编码
    std::ifstream yuv_file(input_yuv, std::ios::binary);
    if (!yuv_file.is_open()) {
        std::cerr << "打开YUV文件失败" << std::endl;
        return -1;
    }

    int frame_count = 0;
    const int frame_size = width * height * 3 / 2;
    uint8_t* yuv_buf = new uint8_t[frame_size];

    while (yuv_file.read(reinterpret_cast<char*>(yuv_buf), frame_size)) {
        // 填充YUV数据到AVFrame
        int y_plane_size = width * height;
        memcpy(frame->data[0], yuv_buf, y_plane_size);
        memcpy(frame->data[1], yuv_buf + y_plane_size, y_plane_size / 4);
        memcpy(frame->data[2], yuv_buf + y_plane_size + y_plane_size / 4, y_plane_size / 4);

        // 设置帧时间戳
        frame->pts = frame_count;
        frame_count++;

        // 格式转换(按需)
        AVFrame* enc_frame = frame;
        if (sws_ctx) {
            AVFrame* tmp_frame = av_frame_alloc();
            tmp_frame->format = codec_ctx->pix_fmt;
            tmp_frame->width = width;
            tmp_frame->height = height;
            av_frame_get_buffer(tmp_frame, 32);
            sws_scale(sws_ctx, frame->data, frame->linesize, 0, height,
                     tmp_frame->data, tmp_frame->linesize);
            tmp_frame->pts = frame->pts;
            enc_frame = tmp_frame;
        }

        // 发送帧到编码器
        if (avcodec_send_frame(codec_ctx, enc_frame) < 0) {
            std::cerr << "发送帧到编码器失败" << std::endl;
            break;
        }

        // 接收编码后的数据包并写入文件
        AVPacket pkt;
        av_init_packet(&pkt);
        pkt.data = nullptr;
        pkt.size = 0;

        while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
            av_packet_rescale_ts(&pkt, codec_ctx->time_base, stream->time_base);
            pkt.stream_index = stream->index;
            if (av_interleaved_write_frame(fmt_ctx, &pkt) < 0) {
                std::cerr << "写入数据包失败" << std::endl;
                break;
            }
            av_packet_unref(&pkt);
        }

        // 释放临时转换帧
        if (sws_ctx && enc_frame != frame) {
            av_frame_free(&enc_frame);
        }
    }

    // 发送结束信号,处理剩余数据包
    avcodec_send_frame(codec_ctx, nullptr);
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = nullptr;
    pkt.size = 0;
    while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
        av_packet_rescale_ts(&pkt, codec_ctx->time_base, stream->time_base);
        pkt.stream_index = stream->index;
        av_interleaved_write_frame(fmt_ctx, &pkt);
        av_packet_unref(&pkt);
    }

    // 写入文件尾
    av_write_trailer(fmt_ctx);

    // 释放资源
    delete[] yuv_buf;
    yuv_file.close();
    av_frame_free(&frame);
    if (sws_ctx) sws_freeContext(sws_ctx);
    avcodec_close(codec_ctx);
    if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) avio_close(fmt_ctx->pb);
    avformat_free_context(fmt_ctx);

    std::cout << "编码完成" << std::endl;
    return 0;
}

额外注意事项

  • 编译链接:编译时需链接FFmpeg相关库,示例编译命令:g++ -o yuv2mp4 yuv2mp4.cpp -lavformat -lavcodec -lavutil -lswscale
  • VLC兼容性:关闭B帧(max_b_frames=0)可避免部分旧版本VLC的播放异常;设置全局头部是MP4格式的强制要求
  • 资源释放:所有FFmpeg结构体(AVFrame、AVPacket等)必须调用对应释放函数,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:22:04