使用 ffmpeg libav 和 libx264 从图像创建视频?

Posted

技术标签:

【中文标题】使用 ffmpeg libav 和 libx264 从图像创建视频?【英文标题】:Creating a video from images using ffmpeg libav and libx264? 【发布时间】:2013-07-22 21:32:01 【问题描述】:

我正在尝试使用 ffmpeg 库从图像创建视频。这些图像的大小为 1920x1080,应该使用 .mkv 容器使用 H.264 进行编码。 我遇到了各种各样的问题,以为我越来越接近解决方案了,但是我真的坚持下去了。使用我使用的设置,我的视频的前 X 帧(大约 40 个,取决于我用于视频的图像和数量)没有被编码。 avcodec_encode_video2 在 got_picture_ptr = 0 时不返回任何错误(返回值为 0)。 结果是一个实际上看起来与预期一样的视频,但前几秒却出奇地跳动。

这就是我创建视频文件的方式:

// m_codecContext is an instance variable of type AVCodecContext *
// m_formatCtx is an instance variable of type AVFormatContext *

// outputFileName is a valid filename ending with .mkv
AVOutputFormat *oformat = av_guess_format(NULL, outputFileName, NULL);
if (oformat == NULL)

    oformat = av_guess_format("mpeg", NULL, NULL);


// oformat->video_codec is AV_CODEC_ID_H264
AVCodec *codec = avcodec_find_encoder(oformat->video_codec);

m_codecContext = avcodec_alloc_context3(codec);
m_codecContext->codec_id = oformat->video_codec;
m_codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
m_codecContext->gop_size = 30;
m_codecContext->bit_rate = width * height * 4
m_codecContext->width = width;
m_codecContext->height = height;
m_codecContext->time_base = (AVRational)1,frameRate;
m_codecContext->max_b_frames = 1;
m_codecContext->pix_fmt = AV_PIX_FMT_YUV420P;

m_formatCtx = avformat_alloc_context();
m_formatCtx->oformat = oformat;
m_formatCtx->video_codec_id = oformat->video_codec;

snprintf(m_formatCtx->filename, sizeof(m_formatCtx->filename), "%s", outputFileName);

AVStream *videoStream = avformat_new_stream(m_formatCtx, codec);
if(!videoStream)

   printf("Could not allocate stream\n");

videoStream->codec = m_codecContext;

if(m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER)

   m_codecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;


avcodec_open2(m_codecContext, codec, NULL) < 0);
avio_open(&m_formatCtx->pb, outputFileName.toStdString().c_str(), AVIO_FLAG_WRITE);
avformat_write_header(m_formatCtx, NULL);

这是添加框架的方式:

void VideoCreator::writeImageToVideo(const QSharedPointer<QImage> &img, int frameIndex)

    AVFrame *frame = avcodec_alloc_frame();

    /* alloc image and output buffer */

    int size = m_codecContext->width * m_codecContext->height;
    int numBytes = avpicture_get_size(m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height);

    uint8_t *outbuf = (uint8_t *)malloc(numBytes);
    uint8_t *picture_buf = (uint8_t *)av_malloc(numBytes);

    int ret = av_image_fill_arrays(frame->data, frame->linesize, picture_buf, m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height, 1);

    frame->data[0] = picture_buf;
    frame->data[1] = frame->data[0] + size;
    frame->data[2] = frame->data[1] + size/4;
    frame->linesize[0] = m_codecContext->width;
    frame->linesize[1] = m_codecContext->width/2;
    frame->linesize[2] = m_codecContext->width/2;

    fflush(stdout);


    for (int y = 0; y < m_codecContext->height; y++)
    
        for (int x = 0; x < m_codecContext->width; x++)
        
            unsigned char b = img->bits()[(y * m_codecContext->width + x) * 4 + 0];
            unsigned char g = img->bits()[(y * m_codecContext->width + x) * 4 + 1];
            unsigned char r = img->bits()[(y * m_codecContext->width + x) * 4 + 2];

            unsigned char Y = (0.257 * r) + (0.504 * g) + (0.098 * b) + 16;

            frame->data[0][y * frame->linesize[0] + x] = Y;

            if (y % 2 == 0 && x % 2 == 0)
            
                unsigned char V = (0.439 * r) - (0.368 * g) - (0.071 * b) + 128;
                unsigned char U = -(0.148 * r) - (0.291 * g) + (0.439 * b) + 128;

                frame->data[1][y/2 * frame->linesize[1] + x/2] = U;
                frame->data[2][y/2 * frame->linesize[2] + x/2] = V;
            
        
    

    int pts = frameIndex;//(1.0 / 30.0) * 90.0 * frameIndex;

    frame->pts = pts;//av_rescale_q(m_codecContext->coded_frame->pts, m_codecContext->time_base, formatCtx->streams[0]->time_base); //(1.0 / 30.0) * 90.0 * frameIndex;

    int got_packet_ptr;
    AVPacket packet;
    av_init_packet(&packet);
    packet.data = outbuf;
    packet.size = numBytes;
    packet.stream_index = formatCtx->streams[0]->index;
    packet.flags |= AV_PKT_FLAG_KEY;
    packet.pts = packet.dts = pts;
    m_codecContext->coded_frame->pts = pts;

    ret = avcodec_encode_video2(m_codecContext, &packet, frame, &got_packet_ptr);
    if (got_packet_ptr != 0)
    
        m_codecContext->coded_frame->pts = pts;  // Set the time stamp

        if (m_codecContext->coded_frame->pts != (0x8000000000000000LL))
        
            pts = av_rescale_q(m_codecContext->coded_frame->pts, m_codecContext->time_base, formatCtx->streams[0]->time_base);
        
        packet.pts = pts;
        if(m_codecContext->coded_frame->key_frame)
        
           packet.flags |= AV_PKT_FLAG_KEY;
        

        std::cout << "pts: " << packet.pts << ", dts: "  << packet.dts << std::endl;

        av_interleaved_write_frame(formatCtx, &packet);
        av_free_packet(&packet);
    

    free(picture_buf);
    free(outbuf);
    av_free(frame);
    printf("\n");

这是清理工作:

int numBytes = avpicture_get_size(m_codecContext->pix_fmt, m_codecContext->width, m_codecContext->height);
int got_packet_ptr = 1;

int ret;
//        for(; got_packet_ptr != 0; i++)
while (got_packet_ptr)

    uint8_t *outbuf = (uint8_t *)malloc(numBytes);

    AVPacket packet;
    av_init_packet(&packet);
    packet.data = outbuf;
    packet.size = numBytes;

    ret = avcodec_encode_video2(m_codecContext, &packet, NULL, &got_packet_ptr);
    if (got_packet_ptr)
    
        av_interleaved_write_frame(m_formatCtx, &packet);
    

    av_free_packet(&packet);
    free(outbuf);


av_write_trailer(formatCtx);

avcodec_close(m_codecContext);
av_free(m_codecContext);
printf("\n");

我认为它可能与 PTS 和 DTS 值有关,但我已经尝试了一切。帧索引似乎是最有意义的。图像是正确的,我可以毫无问题地将它们保存到文件中。我的想法不多了。 如果有人比我更了解,我将非常感激......

干杯, 马里卡纳

更新:

如果这有帮助,这是视频编码结束时的输出:

[libx264 @ 0x7fffc00028a0] frame I:19    Avg QP:14.24  size:312420
[libx264 @ 0x7fffc00028a0] frame P:280   Avg QP:19.16  size:148867
[libx264 @ 0x7fffc00028a0] frame B:181   Avg QP:21.31  size: 40540
[libx264 @ 0x7fffc00028a0] consecutive B-frames: 24.6% 75.4%
[libx264 @ 0x7fffc00028a0] mb I  I16..4: 30.9% 45.5% 23.7%
[libx264 @ 0x7fffc00028a0] mb P  I16..4:  4.7%  9.1%  4.5%  P16..4: 23.5% 16.6% 12.6%  0.0%  0.0%    skip:28.9%
[libx264 @ 0x7fffc00028a0] mb B  I16..4:  0.6%  0.5%  0.3%  B16..8: 26.7% 11.0%  5.5%  direct: 3.9%  skip:51.5%  L0:39.4% L1:45.0% BI:15.6%
[libx264 @ 0x7fffc00028a0] final ratefactor: 19.21
[libx264 @ 0x7fffc00028a0] 8x8 transform intra:48.2% inter:47.3%
[libx264 @ 0x7fffc00028a0] coded y,uvDC,uvAC intra: 54.9% 53.1% 30.4% inter: 25.4% 13.5% 4.2%
[libx264 @ 0x7fffc00028a0] i16 v,h,dc,p: 41% 29% 11% 19%
[libx264 @ 0x7fffc00028a0] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 16% 26% 31%  3%  4%  3%  7%  3%  6%
[libx264 @ 0x7fffc00028a0] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 26% 14%  4%  5%  4%  7%  4%  7%
[libx264 @ 0x7fffc00028a0] i8c dc,h,v,p: 58% 26% 13%  3%
[libx264 @ 0x7fffc00028a0] Weighted P-Frames: Y:17.1% UV:3.6%
[libx264 @ 0x7fffc00028a0] ref P L0: 63.1% 21.4% 11.4%  4.1%  0.1%    
[libx264 @ 0x7fffc00028a0] ref B L0: 85.7% 14.3%
[libx264 @ 0x7fffc00028a0] kb/s:27478.30

【问题讨论】:

【参考方案1】:

至少对我来说添加

frame->width = m_codecContext->width;
frame->height = m_codecContext->height;
frame->format = m_codecContext->pix_fmt;

使此示例代码按预期工作。

【讨论】:

【参考方案2】:

Libav 可能会延迟初始帧的处理。一个好的做法是在处理完所有帧后检查任何延迟的帧。这样做如下:

int i=NUMBER_OF_FRAMES_PREVIOUSLY_ENCODED
for(; got_packet_ptr; i++)
   ret = avcodec_encode_video2(m_codecContext, &packet, NULL, &got_packet_ptr);
//Write the packets to a container after this.

关键是传递一个 NULL 指针来代替要编码的帧,并继续这样做,直到你得到的数据包是非空的。代码示例见this link - “获取延迟帧”下的部分。

一个更简单的方法是将b帧的数量设置为0。

m_codecContext->max_b_frames = 0;

让我知道这是否正常。

另外,您根本没有使用 libx264 API。您可以使用 libx264 API 对视频进行编码,它们的语法更简单、更清晰。此外,它还可以让您更好地控制设置并提高性能。

要将视频流写入 mkv 容器,您仍然必须使用 libav 库。不过。

【讨论】:

非常感谢您抽出宝贵的时间。不幸的是,无论是设置 b 帧的数量还是写入延迟的帧似乎都不起作用。虽然明显存在延迟帧,但随着程序进入循环。该视频实际上似乎不那么跳跃,但仍然不正确。 2 秒后似乎有一个洞,在 2 秒内只显示 2 张静止图像,就好像中间的帧丢失了一样。 您能否指定生成的视频中的帧数以及您打算编码的图像总数?您可以检查对 av_interleaved_write() 的调用次数(根据您的更新应该是 480)。另外,frameIndex 的计算方法是什么? 是的,我要缩进编码的图像数量是 480。frameIndex 只是一个整数,每帧从 0 递增到 479。 av_interleaved_write() 在 avcodec_encode_video2 之后使用实际帧调用 442 次,在 avcodec_encode_video2 之后使用 NULL 调用 38 次。 当时似乎是正确的。如果所有 480 帧都被编码,那么生成的输出视频应该没有任何问题吗?在 virtualDub 之类的工具中查看生成的视频,该工具允许您逐帧处理并检查是否缺少任何输入图像。如果你能提供输出,我会更容易想象问题。 帧的延迟处理仅在您允许 b 帧编码时发生。您在更新中输入的输出显示 181 b 帧编码,这是将 max_bframes 设置为 0 还是正值?

以上是关于使用 ffmpeg libav 和 libx264 从图像创建视频?的主要内容,如果未能解决你的问题,请参考以下文章

Android FFMPeg——FFMpeg+libx264编译

尝试在 FFMPEG C 项目中使用 openH264 作为 libX264 的替代品

ffmpeg/libx264 C API:从短 MP4 末尾丢弃的帧

如何在 macos 上为 ffmpeg 启用 libx264

ffmpeg缺少 libx264依赖时的安装方法 (linux 下)

iOS利用ffmpeg 转码hevc到h264 ,以及 保存h265 h264流