FFmpeg源码:av_reduce函数分析

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

AVRational结构体和其相关的函数分析:

FFmpeg有理数相关的源码:AVRational结构体和其相关的函数分析

FFmpeg源码:av_reduce函数分析

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

一、av_reduce函数的声明

av_reduce函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavutil/rational.h中:

cpp 复制代码
/**
 * Reduce a fraction.
 *
 * This is useful for framerate calculations.
 *
 * @param[out] dst_num Destination numerator
 * @param[out] dst_den Destination denominator
 * @param[in]      num Source numerator
 * @param[in]      den Source denominator
 * @param[in]      max Maximum allowed values for `dst_num` & `dst_den`
 * @return 1 if the operation is exact, 0 otherwise
 */
int av_reduce(int *dst_num, int *dst_den, int64_t num, int64_t den, int64_t max);

该函数作用是:化简有理数(一般用来化简分数)。比如分数4/6(六分之四),化简后为2/3(三分之二)。需要对AVRational结构体进行加减乘除(四则运算)时会调用该函数;av_reduce函数也可以用来计算视频帧率。具体可以参考:《音视频入门基础:H.264专题(15)------FFmpeg源码中通过SPS属性获取视频帧率的实现》。

形参dst_num:输出型参数。执行av_reduce函数后,dst_num指向的整形变量值会变为"被化简后的有理数中的分子"。

形参dst_den:输出型参数。执行av_reduce函数后,dst_den指向的整形变量值会变为"被化简后的有理数中的分母"。

形参num:输入型参数。需要被化简的有理数中的分子。

形参den:输入型参数。需要被化简的有理数中的分母。

形参max:输入型参数,用于进行限制。被化简后的有理数中的分子和分母的绝对值都不能超过该值。

返回值:1:化简结果是准确的;0:化简结果不准确。

二、av_reduce函数的定义

av_reduce函数定义在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;
}

三、av_reduce函数的内部实现分析

av_reduce函数中,首先通过异或(^)运算符判断输入的有理数的正负。变量sign的值为0:输入的有理数为正数;sign值为1:输入的有理数为负数或0;

cpp 复制代码
int sign = (num < 0) ^ (den < 0);

然后通过av_gcd函数得到输入的有理数的分子和分母的最大公约数(关于av_gcd函数的用法可以参考:《FFmpeg源码:av_gcd函数分析》):

cpp 复制代码
int64_t gcd = av_gcd(FFABS(num), FFABS(den));

FFABS是宏,定义在libavutil/common.h中,作用是得到绝对值:

cpp 复制代码
/**
 * Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as they
 * are not representable as absolute values of their type. This is the same
 * as with *abs()
 * @see FFNABS()
 */
#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))

如果最大公约数大于0,让输出的有理数中的分子和分母都除以最大公约数(约分),从而让分数化简。分数的化简的其中一种方法是:化成分数乘法,求出比值,再把比值写成比号链接的形式。即可把一个分数化成和它相等,但分子和分母都比较小的分数,叫做约分,约分时根据分数的基本性质一次性约分(用最大公因数分别去除分子、分母):

cpp 复制代码
if (gcd) {
        num = FFABS(num) / gcd;
        den = FFABS(den) / gcd;
    }

限制化简后的有理数中的分子和分母的绝对值都不能超过形参max的值:

cpp 复制代码
    if (num <= max && den <= max) {
        a1 = (AVRational) { num, den };
        den = 0;
    }

让输出型参数dst_num和dst_den分别得到化简后的有理数的分子和分母:

cpp 复制代码
    *dst_num = sign ? -a1.num : a1.num;
    *dst_den = a1.den;

    return den == 0;

四、av_reduce函数的使用例子

编写测试例子main.c,在Ubuntu中使用9.4.0版本的gcc编译通过:

cpp 复制代码
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
#include <features.h>


#ifdef __GNUC__
#    define AV_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y))
#    define AV_GCC_VERSION_AT_MOST(x,y)  (__GNUC__ < (x) || __GNUC__ == (x) && __GNUC_MINOR__ <= (y))
#else
#    define AV_GCC_VERSION_AT_LEAST(x,y) 0
#    define AV_GCC_VERSION_AT_MOST(x,y)  0
#endif


#ifndef av_always_inline
#if AV_GCC_VERSION_AT_LEAST(3,1)
#    define av_always_inline __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#    define av_always_inline __forceinline
#else
#    define av_always_inline inline
#endif
#endif


#if AV_GCC_VERSION_AT_LEAST(2,6) || defined(__clang__)
#    define av_const __attribute__((const))
#else
#    define av_const
#endif


#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
#define FFABS(a) ((a) >= 0 ? (a) : (-(a)))
#define av_assert2(cond) ((void)0)


#ifdef __USE_ISOC99
__extension__ extern long long int llabs (long long int __x)
     __THROW __attribute__ ((__const__)) __wur;
#endif


#ifndef ff_ctzll
#define ff_ctzll ff_ctzll_c


typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;


/* We use the De-Bruijn method outlined in:
 * http://supertech.csail.mit.edu/papers/debruijn.pdf. */
static av_always_inline av_const int ff_ctzll_c(long long v)
{
    static const uint8_t debruijn_ctz64[64] = {
        0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28,
        62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11,
        63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,
        51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12
    };
    return debruijn_ctz64[(uint64_t)((v & -v) * 0x022FDD63CC95386DU) >> 58];
}
#endif


int64_t av_gcd(int64_t a, int64_t b) {
    int za, zb, k;
    int64_t u, v;
    if (a == 0)
        return b;
    if (b == 0)
        return a;
    za = ff_ctzll(a);
    zb = ff_ctzll(b);
    k  = FFMIN(za, zb);
    u = llabs(a >> za);
    v = llabs(b >> zb);
    while (u != v) {
        if (u > v)
            FFSWAP(int64_t, v, u);
        v -= u;
        v >>= ff_ctzll(v);
    }
    return (uint64_t)u << k;
}


/**
 * Reduce a fraction.
 *
 * This is useful for framerate calculations.
 *
 * @param[out] dst_num Destination numerator
 * @param[out] dst_den Destination denominator
 * @param[in]      num Source numerator
 * @param[in]      den Source denominator
 * @param[in]      max Maximum allowed values for `dst_num` & `dst_den`
 * @return 1 if the operation is exact, 0 otherwise
 */
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;
}


int main()
{
    int dst_num1 = 0;
    int dst_den1 = 0;
    int ret = av_reduce(&dst_num1, &dst_den1, 4, 6, 5);
    printf("ret:%d, dst_num1:%d, dst_den1:%d\n", ret, dst_num1, dst_den1);

    int dst_num2 = 0;
    int dst_den2 = 0;
    ret = av_reduce(&dst_num2, &dst_den2, -4, 6, 5);
    printf("ret:%d, dst_num2:%d, dst_den2:%d\n", ret, dst_num2, dst_den2);

    int dst_num3 = 0;
    int dst_den3 = 0;
    ret = av_reduce(&dst_num3, &dst_den3, -4, 6, 1);
    printf("ret:%d, dst_num3:%d, dst_den3:%d\n", ret, dst_num3, dst_den3);

    return 0;
}

输出如下:

相关推荐
kkoral19 小时前
【FFmpeg 智慧园区场景应用】5.企业微信 / 钉钉告警一键切换脚本(含静默周期 + 恢复通知)
ffmpeg·钉钉·企业微信
来鸟 鸣间21 小时前
音视频之ffmpeg基本信息查询命令
ffmpeg·音视频
kkoral1 天前
【FFmpeg 智慧园区场景应用】4.企业微信 / 钉钉机器人告警配置(替换邮件告警)
ffmpeg·钉钉·企业微信
kkoral1 天前
【FFmpeg 智慧园区场景应用】3.Shell脚本增强:故障自动重启 + 磁盘监控告警
ffmpeg
大大祥2 天前
Android FFmpeg集成
android·ffmpeg·kotlin·音视频·jni·ndk·音视频编解码
好多渔鱼好多2 天前
【流媒体】FFmpeg技术介绍与构建方法
ffmpeg
没有羊的王K2 天前
下载安装FFmpeg教程
ffmpeg
kkoral2 天前
【FFmpeg 智慧园区场景应用】1.实战命令清单
ffmpeg
天虎2 天前
使用VS2019编译ShiftMediaProject版本FFmpeg
ffmpeg
kkoral2 天前
【FFmpeg 智慧园区场景应用】2.自动化处理 Shell 脚本
运维·ffmpeg·自动化