音视频入门基础:H.264专题(15)——FFmpeg源码中通过SPS属性获取视频帧率的实现

=================================================================

音视频入门基础:H.264专题系列文章:

音视频入门基础:H.264专题(1)------H.264官方文档下载

音视频入门基础:H.264专题(2)------使用FFmpeg命令生成H.264裸流文件

音视频入门基础:H.264专题(3)------EBSP, RBSP和SODB

音视频入门基础:H.264专题(4)------NALU Header:forbidden_zero_bit、nal_ref_idc、nal_unit_type简介

音视频入门基础:H.264专题(5)------FFmpeg源码中 解析NALU Header的函数分析

音视频入门基础:H.264专题(6)------FFmpeg源码:从H.264码流中提取NALU Header、EBSP、RBSP和SODB

音视频入门基础:H.264专题(7)------FFmpeg源码中 指数哥伦布编码的解码实现

音视频入门基础:H.264专题(8)------H.264官方文档的描述符

音视频入门基础:H.264专题(9)------SPS简介

音视频入门基础:H.264专题(10)------FFmpeg源码中,存放SPS属性的结构体和解码SPS的函数分析

音视频入门基础:H.264专题(11)------计算视频分辨率的公式

音视频入门基础:H.264专题(12)------FFmpeg源码中通过SPS属性计算视频分辨率的实现

音视频入门基础:H.264专题(13)------FFmpeg源码中通过SPS属性获取视频色彩格式的实现

音视频入门基础:H.264专题(14)------计算视频帧率的公式

音视频入门基础:H.264专题(15)------FFmpeg源码中通过SPS属性获取视频帧率的实现

音视频入门基础:H.264专题(16)------FFmpeg源码中,判断某文件是否为H.264裸流文件的实现

音视频入门基础:H.264专题(17)------FFmpeg源码获取H.264裸流文件信息(视频压缩编码格式、色彩格式、视频分辨率、帧率)的总流程

=================================================================

一、引言

在上一节《音视频入门基础:H.264专题(14)------计算视频帧率的公式》中,讲述了通过SPS中的属性计算H.264编码的视频的帧率的公式。本文讲解FFmpeg源码中计算视频帧率的实现。

二、FFmpeg源码中计算视频帧率的实现

从文章《音视频入门基础:H.264专题(10)------FFmpeg源码中,存放SPS属性的结构体和解码SPS的函数分析》中,我们可以知道,FFmpeg源码中通过ff_h264_decode_seq_parameter_set函数解码SPS,从而拿到SPS中的属性。

计算视频帧率所需的属性在SPS的VUI parameters(视频可用参数)中。ff_h264_decode_seq_parameter_set函数通过调用decode_vui_parameters函数解码VUI parameters:

cpp 复制代码
int ff_h264_decode_seq_parameter_set(GetBitContext *gb, AVCodecContext *avctx,
                                     H264ParamSets *ps, int ignore_truncation)
{
//...

    sps->vui_parameters_present_flag = get_bits1(gb);
    if (sps->vui_parameters_present_flag) {
        int ret = decode_vui_parameters(gb, avctx, sps);
        if (ret < 0)
            goto fail;
    }

//...
}

decode_vui_parameters函数中通过下面的这部分代码拿到计算视频帧率所需的属性(timing_info_present_flag、num_units_in_tick、time_scale):

cpp 复制代码
static inline int decode_vui_parameters(GetBitContext *gb, void *logctx,
                                        SPS *sps)
{
//...

    sps->timing_info_present_flag = get_bits1(gb);
    if (sps->timing_info_present_flag) {
        unsigned num_units_in_tick = get_bits_long(gb, 32);
        unsigned time_scale        = get_bits_long(gb, 32);
        if (!num_units_in_tick || !time_scale) {
            av_log(logctx, AV_LOG_ERROR,
                   "time_scale/num_units_in_tick invalid or unsupported (%u/%u)\n",
                   time_scale, num_units_in_tick);
            sps->timing_info_present_flag = 0;
        } else {
            sps->num_units_in_tick = num_units_in_tick;
            sps->time_scale = time_scale;
        }
        sps->fixed_frame_rate_flag = get_bits1(gb);
    }

//...
}

然后在FFmpeg源码的源文件libavcodec/h264_parser.c的parse_nal_units函数中,通过如下代码,得到视频帧率:

cpp 复制代码
static inline int parse_nal_units(AVCodecParserContext *s,
                                  AVCodecContext *avctx,
                                  const uint8_t * const buf, int buf_size)
{
    //...
    
    for (;;) {
        switch (nal.type) {
        case H264_NAL_SPS:
            ff_h264_decode_seq_parameter_set(&nal.gb, avctx, &p->ps, 0);
            break;
         
        //...
 
        case H264_NAL_IDR_SLICE:
        //...
        if (sps->timing_info_present_flag) {
            int64_t den = sps->time_scale;
            if (p->sei.unregistered.x264_build < 44U)
                den *= 2;
            av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * avctx->ticks_per_frame, den, 1 << 30);
            }
        //... 
        }
        //...
    }
}

可以看到在FFmpeg源码的parse_nal_units函数中,最终是通过语句

cpp 复制代码
av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * avctx->ticks_per_frame, den, 1 << 30);

计算出视频帧率的。

上述函数av_reduce的实参avctx->ticks_per_frame是结构体AVCodecContext的成员变量,它会被设置为每帧的时基的时钟数。默认值为1,如果编解码器是H.264或MPEG-2,会被设置为2:

cpp 复制代码
typedef struct AVCodecContext {

    /**
     * For some codecs, the time base is closer to the field rate than the frame rate.
     * Most notably, H.264 and MPEG-2 specify time_base as half of frame duration
     * if no telecine is used ...
     *
     * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2.
     */
    int ticks_per_frame;
}

用户需要获取H.264编码的视频的媒体信息时,会调用avformat_find_stream_info函数,而该函数内部会调用h264_decode_init函数,让avctx->ticks_per_frame被初始化为2(也就是说对于H.264,avctx->ticks_per_frame的值就是2):

cpp 复制代码
static av_cold int h264_decode_init(AVCodecContext *avctx)
{
//...
    if (avctx->ticks_per_frame == 1) {
        if(h->avctx->time_base.den < INT_MAX/2) {
            h->avctx->time_base.den *= 2;
        } else
            h->avctx->time_base.num /= 2;
    }
    avctx->ticks_per_frame = 2;
//...
}

所以在parse_nal_units函数中,语句:

cpp 复制代码
av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * avctx->ticks_per_frame, den, 1 << 30);

等价于:

cpp 复制代码
av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * 2, den, 1 << 30);

而den的值为sps->time_scale。所以上述语句等价于:

cpp 复制代码
av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * 2, sps->time_scale, 1 << 30);

av_reduce函数是用来计算视频帧率的,其源码定义在FFmpeg源码libavutil/rational.c中:

cpp 复制代码
int av_reduce(int *dst_num, int *dst_den,
              int64_t num, int64_t den, int64_t max)
{
    AVRational a0 = { 0, 1 }, a1 = { 1, 0 };
    int sign = (num < 0) ^ (den < 0);
    int64_t gcd = av_gcd(FFABS(num), FFABS(den));

    if (gcd) {
        num = FFABS(num) / gcd;
        den = FFABS(den) / gcd;
    }
    if (num <= max && den <= max) {
        a1 = (AVRational) { num, den };
        den = 0;
    }

    while (den) {
        uint64_t x        = num / den;
        int64_t next_den  = num - den * x;
        int64_t a2n       = x * a1.num + a0.num;
        int64_t a2d       = x * a1.den + a0.den;

        if (a2n > max || a2d > max) {
            if (a1.num) x =          (max - a0.num) / a1.num;
            if (a1.den) x = FFMIN(x, (max - a0.den) / a1.den);

            if (den * (2 * x * a1.den + a0.den) > num * a1.den)
                a1 = (AVRational) { x * a1.num + a0.num, x * a1.den + a0.den };
            break;
        }

        a0  = a1;
        a1  = (AVRational) { a2n, a2d };
        num = den;
        den = next_den;
    }
    av_assert2(av_gcd(a1.num, a1.den) <= 1U);
    av_assert2(a1.num <= max && a1.den <= max);

    *dst_num = sign ? -a1.num : a1.num;
    *dst_den = a1.den;

    return den == 0;
}

所以语句:

cpp 复制代码
av_reduce(&avctx->framerate.den, &avctx->framerate.num,
                          sps->num_units_in_tick * 2, sps->time_scale, 1 << 30);

相当于执行了公式:视频帧率 = time_scale / (2 * num_units_in_tick)。然后把得到的视频帧率的分子和分母分别存放到avctx->framerate.den和avctx->framerate.num中返回。

相关推荐
安步当歌2 小时前
【WebRTC】视频发送链路中类的简单分析(下)
网络·音视频·webrtc·视频编解码·video-codec
lqj_本人3 小时前
鸿蒙next版开发:音频并发策略扩展(ArkTS)
音视频
红米饭配南瓜汤17 小时前
WebRTC视频 04 - 视频采集类 VideoCaptureDS 中篇
音视频·webrtc·媒体
橘子味的茶二18 小时前
ffmpeg内存模型
ffmpeg
TPCloud18 小时前
windows 11编译安装ffmpeg(包含ffplay)
windows·ffmpeg·源码安装·mysys
杨武博20 小时前
音频格式转换
android·音视频
Learning改变世界21 小时前
ubuntu24.04播放语音视频
音视频
棱角~~1 天前
10款PDF合并工具的使用体验与推荐!!
经验分享·其他·音视频·实时音视频·学习方法
红米饭配南瓜汤1 天前
WebRTC视频 02 - 视频采集类 VideoCaptureModule
音视频·webrtc·媒体
gomogomono1 天前
HDR视频技术之二:光电转换与 HDR 图像显示
音视频·hdr