繁体   English   中英

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

[英]libav producing MP4 file with extremely high frame rate

我正在尝试编写一个程序来生成要通过 ffmpeg/libav 编码到具有单个 h264 stream 的 mp4 文件的帧。 我找到了这两个示例,并试图将它们合并在一起以制作我想要的:[ 视频转码器] [ 原始 MPEG1 编码器]

我已经能够获得视频 output (绿色圆圈改变大小),但无论我如何设置帧的 PTS 值或我在AVCodecContexttime_base中指定的AVStream ,我得到的帧速率约为 7000-15000 60,导致视频文件持续 70 毫秒,而不是 1000 帧/60 fps = 166 秒。 每次我更改一些代码时,帧速率都会发生一点变化,就好像它是从未初始化的 memory 读取的一样。 StackOverflow 上对此类问题的其他引用似乎与错误设置的 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在ffprobe视频文件上运行的output

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]

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

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

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

     avcc->time_base = av_make_q(1, 60000);
  • 设置avp->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) > level limit (16711680)我没有修复。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM