使用 FFMPEG 将 RGB 图像序列保存到 .mp4 时遇到问题

Posted

技术标签:

【中文标题】使用 FFMPEG 将 RGB 图像序列保存到 .mp4 时遇到问题【英文标题】:Have problems using FFMPEG to save RGB image sequence to .mp4 【发布时间】:2020-02-21 13:31:03 【问题描述】:

我用 OpenGL 渲染了一些图像,需要将它们组合成一个视频文件。每个图像都是一个 uint8_t 值序列,代表一个 sRGB 颜色分量(图像数组看起来像 ...rgbrgbrgb ...)

我对视频处理知之甚少,完全没有使用 ffmpeg 库的经验。我使用这些资源作为参考做了一个小测试程序:

https://ffmpeg.org/doxygen/trunk/encode_video_8c-example.html

How to convert RGB from YUV420p for ffmpeg encoder?

测试程序应该制作一个关于生长绿色垂直条纹的视频。我只是想弄清楚如何使用一些原始 RGB 数据源制作视频。

这是我的代码:

#include <iostream>
#include <vector>
#include <algorithm>

extern "C" 
    #include <libavcodec/avcodec.h>
    #include <libavutil/opt.h>
    #include <libavutil/imgutils.h>
    #include <libswscale/swscale.h>


static void encode( AVCodecContext* enc_ctx,
                    AVFrame* frame, AVPacket* pkt,
                    FILE* outfile                  )

    int ret;
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) 
        std::cerr << "Error sending a frame for encoding\n";
        return;
    
    while (ret >= 0) 
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) 
            fprintf(stderr, "Error during encoding\n");
            exit(1);
        
        fwrite(pkt->data, 1, pkt->size, outfile);
        av_packet_unref(pkt);
    


static constexpr int w = 1920, h = 1080;
static constexpr float fps = 20.f, time = 5.f;
static constexpr int nFrames = static_cast<int>(fps * time);
static std::vector<uint8_t> imageRGB(w * h * 3, 0);

static void UpdateImageRGB()

    static int d = 50;
    imageRGB.assign(w * h * 3, 0);
    for (int i = 0; i < h; ++i)
        for ( int j = std::max(0, w / 2 - d);
              j < std::min(w, w / 2 + d);
              ++j                             )
        
            imageRGB[(w * i + j) * 3 + 0] = 50;
            imageRGB[(w * i + j) * 3 + 1] = 200;
            imageRGB[(w * i + j) * 3 + 2] = 50;
        
    d += 5;


int main()

    int ret = 0;
    auto filename = "test.mp4";

    auto codec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) 
        std::cerr << "Codec \"x.264\" not found\n";
        return 1;
    
    auto c = avcodec_alloc_context3(codec);
    if (!c) 
        std::cerr << "Could not allocate video codec context\n";
        return 1;
    
    auto pkt = av_packet_alloc();
    if (!pkt) return 1;

    // 1.8 bits / (pixel * frame)
    c->bit_rate = static_cast<int64_t>(1.8f * w * h * fps);
    /* resolution must be a multiple of two */
    c->width = w;
    c->height = h;
    /* frames per second */
    c->time_base = AVRational 1, static_cast<int>(fps) ;
    c->framerate = AVRational static_cast<int>(fps), 1 ;

    c->gop_size = 10;
    c->max_b_frames = 1;
    c->pix_fmt = AV_PIX_FMT_YUV420P;
    av_opt_set(c->priv_data, "preset", "slow", 0);
    av_opt_set(c->priv_data, "preset", "slow", 0);

    ret = avcodec_open2(c, codec, NULL);
    if (ret < 0) 
        char str[AV_ERROR_MAX_STRING_SIZE];
        std::cerr << "Could not open codec: "
                  << av_make_error_string(str, AV_ERROR_MAX_STRING_SIZE, ret)
                  << "\n";
        return 1;
    

    FILE * f;
    fopen_s(&f, filename, "wb");
    if (!f) 
        std::cerr << "Could not open " << filename << '\n';
        return 1;
    

    auto frame = av_frame_alloc();
    if (!frame) 
        std::cerr << "Could not allocate video frame\n";
        return 1;
    
    frame->format = c->pix_fmt;
    frame->width = c->width;
    frame->height = c->height;
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) 
        std::cerr << stderr, "Could not allocate the video frame data\n";
        return 1;
    

    SwsContext* ctx = sws_getContext( w, h, AV_PIX_FMT_RGB24,
                                      w, h, AV_PIX_FMT_YUV420P,
                                      0, 0, 0, 0                );

    for (int i = 0; i < nFrames; i++)
    
        ret = av_frame_make_writable(frame);
        UpdateImageRGB();
        static const uint8_t* rgbData[1] =  &imageRGB[0] ;
        static constexpr int rgbLinesize[1] =  3 * w ;
        sws_scale( ctx, rgbData, rgbLinesize, 0, h,
                   frame->data, frame->linesize     );
        frame->pts = i;
        /* encode the image */
        encode(c, frame, pkt, f);
    
    encode(c, NULL, pkt, f);

    fclose(f);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    return 0;

程序生成 33.9k 视频文件,并带有进一步的控制台输出:

[libx264 @ 0000020c18681800] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 0000020c18681800] profile High, level 5.0, 4:2:0, 8-bit
[libx264 @ 0000020c18681800] frame I:11    Avg QP: 0.00  size:   639
[libx264 @ 0000020c18681800] frame P:74    Avg QP: 0.32  size:   174
[libx264 @ 0000020c18681800] frame B:15    Avg QP: 2.26  size:   990
[libx264 @ 0000020c18681800] consecutive B-frames: 70.0% 30.0%
[libx264 @ 0000020c18681800] mb I  I16..4: 100.0%  0.0%  0.0%
[libx264 @ 0000020c18681800] mb P  I16..4:  0.6%  0.0%  0.0%  P16..4:  2.1%  0.0%  0.0%  0.0%  0.0%    skip:97.3%
[libx264 @ 0000020c18681800] mb B  I16..4:  0.1%  0.0%  0.0%  B16..8:  0.6%  0.0%  0.0%  direct: 0.6%  skip:98.7%  L0:39.8% L1:60.2% BI: 0.0%
[libx264 @ 0000020c18681800] final ratefactor: -46.47
[libx264 @ 0000020c18681800] 8x8 transform intra:0.0%
[libx264 @ 0000020c18681800] direct mvs  spatial:0.0% temporal:100.0%
[libx264 @ 0000020c18681800] coded y,uvDC,uvAC intra: 0.0% 0.1% 0.1% inter: 0.0% 0.1% 0.1%
[libx264 @ 0000020c18681800] i16 v,h,dc,p: 99%  0%  1%  0%
[libx264 @ 0000020c18681800] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu:  0%  0% 100%  0%  0%  0%  0%  0%  0%
[libx264 @ 0000020c18681800] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 46%  0% 54%  0%  0%  0%  0%  0%  0%
[libx264 @ 0000020c18681800] i8c dc,h,v,p: 96%  1%  3%  0%
[libx264 @ 0000020c18681800] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 0000020c18681800] ref P L0: 70.2%  0.0% 29.8%  0.0%  0.0%
[libx264 @ 0000020c18681800] kb/s:55.61
    Windows 上的“Media Player Classic”播放此视频,但时间滑块不移动,视频无法快进到某些帧 VLC 根本无法播放视频。它启动,显示 VLC 徽标,时间滑块(异常大)从左向右跳跃,对我的点击没有响应 如果我设置 time = 0.05 来制作只有 1 帧的视频,即使使用“Media Player Classic”也无法播放。我想制定一种算法,将任意数量的原始 RGB 图像转换为视频文件,即使只有一个图像,并且图像大小任意(即宽度和高度可能是奇数)。 正如我所说,我真的不明白我在做什么。第 83-84 行有低级编解码器设置。他们还好吗? 是否必须手动设置比特率(第 75 行)?不是应该由编解码器自动计算吗?

【问题讨论】:

我对视频也不太了解,但认为您可能需要一些(更多)关键帧供玩家寻找。还在学习自己... 第 229 行可能在这里? ffmpeg.org/doxygen/trunk/encoding-example_8c-source.html 嗯,谢谢你的猜测,但这并没有帮助。据我所知,“关键帧”是“帧内”的另一个名称,而 gop_size=0 表示“仅帧内”。我玩过 c->gop_size 和 c->max_b_frames(上面代码中的第 83-84 行)。没有区别( 好的,感谢您试用它们。我现在会闭嘴,直到有合适的工程师出现,然后希望也能学到一些东西:-)祝你好运。 FWIW ffplay 似乎可以在我的 Mac 上播放。 【参考方案1】:

嘿,我通过引用此处使用的方法解决了这个问题。我不知道你是否可以在你的代码中应用它,但我建议你检查一下:

https://superuser.com/questions/469273/ffmpeg-convert-rgb-images-to-video

【讨论】:

这不是将 RGB 文件 转换为视频。原始 RGB 图像直接在 RAM 中生成。无论如何,我都尝试了 huffyuv 和 rawvideo 编解码器。第一个使输出视频比 x264 大 3000 倍,第二个使其大 10000 倍。我宁愿留在x264。而且他们都没有制作我的 VLC 可以播放的视频【参考方案2】:

最终的 .mp4 文件称为 Container。 一个容器有它的所有者格式。 您直接将编码数据写入 .mp4 文件是不对的。 您需要从 ffmpeg 源代码中阅读复用示例代码。 muxing.c文件在ffmpeg源码目录:doc/examples/muxing.c

【讨论】:

以上是关于使用 FFMPEG 将 RGB 图像序列保存到 .mp4 时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUVBGR24或RGB24等图像像素数据

(高分求代码)基于ffmpeg 获取视频帧保存成图像转成yuv图像序列

从 YUV420P 到 RGB 的 FFMPEG Api 转换产生奇怪的输出

FFmpeg 将YUV数据转RGB

Matlab - 转换图像并将其保存到磁盘

FFmpeg H264码流格式说明