libav 生成具有极高帧速率的 MP4 文件

Posted

技术标签:

【中文标题】libav 生成具有极高帧速率的 MP4 文件【英文标题】:libav producing MP4 file with extremely high frame rate 【发布时间】:2021-12-31 17:19:23 【问题描述】:

我正在尝试编写一个程序来生成要通过 ffmpeg/libav 编码到具有单个 h264 流的 mp4 文件的帧。我找到了这两个示例,并试图将它们合并在一起以制作我想要的东西:[video transcoder] [raw MPEG1 encoder]

我已经能够获得视频输出(绿色圆圈改变大小),但无论我如何设置帧的 PTS 值或我在 AVCodecContextAVStream 中指定的 time_base,我获得大约 7000-15000 而不是 60 的帧速率,从而产生持续 70 毫秒而不是 1000 帧/60 fps = 166 秒的视频文件。每次我更改一些代码时,帧速率都会发生一点变化,就好像它正在从未初始化的内存中读取一样。 *** 上对此类问题的其他引用似乎与错误设置的 PTS 值有关;但是,我尝试打印出我能找到的所有 PTS、DTS 和时基值,它们看起来都很正常。这是我的概念验证代码(为了清楚起见,删除了捕获 libav 调用周围的东西的错误):

#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>

extern "C" 
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/timestamp.h>


using namespace cv;

int main(int argc, char *argv[]) 
    const char *filename = "testvideo.mp4";
    
    AVFormatContext *avfc;
    avformat_alloc_output_context2(&avfc, NULL, NULL, filename);
    
    AVStream *stream = avformat_new_stream(avfc, NULL);
    AVCodec *h264 = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVCodecContext *avcc = avcodec_alloc_context3(h264);
    
    av_opt_set(avcc->priv_data, "preset", "fast", 0);
    av_opt_set(avcc->priv_data, "crf", "20", 0);
    avcc->thread_count = 1;
    avcc->width = 1920;
    avcc->height = 1080;
    avcc->pix_fmt = AV_PIX_FMT_YUV420P;
    avcc->time_base = av_make_q(1, 60);
    stream->time_base = avcc->time_base;
    
    if(avfc->oformat->flags & AVFMT_GLOBALHEADER)
        avcc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    
    avcodec_open2(avcc, h264, NULL);
    avcodec_parameters_from_context(stream->codecpar, avcc);
    
    avio_open(&avfc->pb, filename, AVIO_FLAG_WRITE);
    
    avformat_write_header(avfc, NULL);
    
    Mat frame, nothing = Mat::zeros(1080, 1920, CV_8UC1);
    AVFrame *avf = av_frame_alloc();
    AVPacket *avp = av_packet_alloc();
    int ret;
    
    avf->format = AV_PIX_FMT_YUV420P;
    avf->width = 1920;
    avf->height = 1080;
    avf->linesize[0] = 1920;
    avf->linesize[1] = 1920;
    avf->linesize[2] = 1920;
    
    for(int x=0; x<1000; x++) 
        frame = Mat::zeros(1080, 1920, CV_8UC1);
        circle(frame, Point(1920/2, 1080/2), 250*(sin(2*M_PI*x/1000*3)+1.01), Scalar(255), 10);
        
        avf->data[0] = frame.data;
        avf->data[1] = nothing.data;
        avf->data[2] = nothing.data;
        avf->pts = x;
        
        ret = 0;
        do 
            if(ret == AVERROR(EAGAIN)) 
                av_packet_unref(avp);
                ret = avcodec_receive_packet(avcc, avp);
                if(ret) break; // deal with error
                av_write_frame(avfc, avp);
             //else if(ret) deal with error
            ret = avcodec_send_frame(avcc, avf);
         while(ret);
    
    
    // flush the rest of the packets
    avcodec_send_frame(avcc, NULL);
    do 
        av_packet_unref(avp);
        ret = avcodec_receive_packet(avcc, avp);
        if(!ret)
            av_write_frame(avfc, avp);
     while(!ret);
    
    av_frame_free(&avf);
    av_packet_free(&avp);
    
    av_write_trailer(avfc);
    avformat_close_input(&avfc);
    avformat_free_context(avfc);
    avcodec_free_context(&avcc);
    return 0;

这是ffprobe在输出视频文件上运行的输出

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'testvideo.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.76.100
  Duration: 00:00:00.07, start: 0.000000, bitrate: 115192 kb/s
  Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 115389 kb/s, 15375.38 fps, 15360 tbr, 15360 tbn, 120 tbc (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]

什么可能导致我的帧速率如此之高?提前感谢您的帮助。

【问题讨论】:

【参考方案1】:

由于您未能设置数据包持续时间,因此您获得了高帧率。

如here 所述,将time_base 设置为更高分辨率(如1/60000):

 avcc->time_base = av_make_q(1, 60000);

按照here的描述设置avp-&gt;duration

 AVRational avg_frame_rate = av_make_q(60, 1);   //60 fps
 avp->duration = avcc->time_base.den / avcc->time_base.num / avg_frame_rate.num * avg_frame_rate.den;    //avp->duration = 1000 (60000/60)

并相应地设置pts


完整代码:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>

extern "C" 
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libavutil/timestamp.h>


using namespace cv;

int main(int argc, char* argv[]) 
    const char* filename = "testvideo.mp4";

    AVFormatContext* avfc;
    avformat_alloc_output_context2(&avfc, NULL, NULL, filename);

    AVStream* stream = avformat_new_stream(avfc, NULL);
    AVCodec* h264 = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVCodecContext* avcc = avcodec_alloc_context3(h264);

    av_opt_set(avcc->priv_data, "preset", "fast", 0);
    av_opt_set(avcc->priv_data, "crf", "20", 0);
    avcc->thread_count = 1;
    avcc->width = 1920;
    avcc->height = 1080;
    avcc->pix_fmt = AV_PIX_FMT_YUV420P;
    //Sey the time_base to higher resolution like 1/60000
    avcc->time_base = av_make_q(1, 60000); //avcc->time_base = av_make_q(1, 60);
    stream->time_base = avcc->time_base;

    if (avfc->oformat->flags & AVFMT_GLOBALHEADER)
        avcc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    avcodec_open2(avcc, h264, NULL);
    avcodec_parameters_from_context(stream->codecpar, avcc);

    avio_open(&avfc->pb, filename, AVIO_FLAG_WRITE);

    avformat_write_header(avfc, NULL);

    Mat frame, nothing = Mat::zeros(1080, 1920, CV_8UC1);
    AVFrame* avf = av_frame_alloc();
    AVPacket* avp = av_packet_alloc();
    int ret;

    avf->format = AV_PIX_FMT_YUV420P;
    avf->width = 1920;
    avf->height = 1080;
    avf->linesize[0] = 1920;
    avf->linesize[1] = 1920;
    avf->linesize[2] = 1920;

    for (int x = 0; x < 1000; x++) 
        frame = Mat::zeros(1080, 1920, CV_8UC1);
        circle(frame, Point(1920 / 2, 1080 / 2), (int)(250.0 * (sin(2 * M_PI * x / 1000 * 3) + 1.01)), Scalar(255), 10);

        AVRational avg_frame_rate = av_make_q(60, 1);   //60 fps

        int64_t avp_duration = avcc->time_base.den / avcc->time_base.num / avg_frame_rate.num * avg_frame_rate.den;

        avf->data[0] = frame.data;
        avf->data[1] = nothing.data;
        avf->data[2] = nothing.data;
        avf->pts = (int64_t)x * avp_duration; // avp->duration = 1000

        ret = 0;
        do 
            if (ret == AVERROR(EAGAIN)) 
                av_packet_unref(avp);
                ret = avcodec_receive_packet(avcc, avp);
                if (ret) break; // deal with error

                ////////////////////////////////////////////////////////////////
                //avp->duration was zero.
                avp->duration = avp_duration;    //avp->duration = 1000 (60000/60)

                //avp->pts = (int64_t)x * avp->duration;
                ////////////////////////////////////////////////////////////////

                av_write_frame(avfc, avp);
             //else if(ret) deal with error
            ret = avcodec_send_frame(avcc, avf);
         while (ret);
    

    // flush the rest of the packets
    avcodec_send_frame(avcc, NULL);
    do 
        av_packet_unref(avp);
        ret = avcodec_receive_packet(avcc, avp);
        if (!ret)
            av_write_frame(avfc, avp);
     while (!ret);

    av_frame_free(&avf);
    av_packet_free(&avp);

    av_write_trailer(avfc);
    avformat_close_input(&avfc);
    avformat_free_context(avfc);
    avcodec_free_context(&avcc);
    return 0;


FFprobe 的结果:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'testvideo.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.76.100
  Duration: 00:00:16.65, start: 0.000000, bitrate: 456 kb/s
  Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 450 kb/s, 60.06 fps, 60 tbr, 60k tbn, 120k tbc (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]

注意事项:

我不知道为什么 fps 是 60.06 而不是 60。 有一条警告消息 MB rate (734400000) &gt; level limit (16711680) 我没有修复。

【讨论】:

谢谢!查看示例时,我错过了持续时间。出于好奇,为什么我们需要将时基设置为 1/60000?在示例中,我看到它设置为video_avcc-&gt;time_base = av_inv_q(input_framerate),我假设它设置为 1/60。当我尝试这个(1/60 时基,将 pts 增加 1,数据包持续时间为 1)时,它会恢复到超高速。 (另外 60.06fps 是因为您没有在我的第二个刷新循环中的最后一个数据包中设置持续时间。) 将时基设置为 1/60000 是本教程中使用的值。如果只有视频,(没有音频),设置时基为1/60大概是可以的,但是当有音频流需要与视频同步时,我们需要更高分辨率的时基(所以不是将其设置为 1/60 的好习惯)。旧时基曾经是 1/27000(与模拟视频有关)。探测一些视频文件,我可以找到值为 1/16384、1/1000、1/11988、125/2997(具体值可能并不重要)。【参考方案2】:

虽然我接受的答案解决了我遇到的问题,但这里有一些我发现可能有用的更多信息:

time_base 字段根据容器格式对其值有一些限制(例如 1/10000 有效,但 1/9999 无效),这似乎是我遇到的根本问题。当时基设置为 1/60 时,对 avformat_write_header() 的调用将其更改为 1/15360。因为我已将 PTS 增量硬编码为 1,这导致了 15360 FPS 视频。奇怪的分母 1​​5360 似乎是由于给定的分母反复乘以 2 直到达到某个最小值。我不知道这个算法是如何工作的。 This SO question 让我明白了这一点。

通过将时基设置为 1/60000 并使 PTS 每帧增加 1000,解决了快速视频问题。设置数据包持续时间似乎没有必要,但可能是个好主意。

这里的主要教训是使用 time_base libav 给你的任何东西,而不是假设你设置的值保持不变。 @Rotem 的更新代码会执行此操作,因此会以 1/60 的时基“工作”,因为 PTS 和数据包持续时间实际上将基于 time_base 更改为的 1/15360 值。

【讨论】:

以上是关于libav 生成具有极高帧速率的 MP4 文件的主要内容,如果未能解决你的问题,请参考以下文章

在 c++ 中以可变帧速率使用 FFmpeg 库(不是 libav 分支)以编程方式捕获视频

使用libav编码时根据经过时间计算帧PTS

使用 Media Foundation .NET 从 MP4 获取帧速率

复制 avcodec 参数

au怎么导出视频mp4格式

ffmpeg、avconv 和 sameq