FFmpeg源码:av_reduce函数分析

 一、av_reduce函数的声明

av_reduce函数声明在libavutil/rational.h中:

/**
 * 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中:

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;

int sign = (num < 0) ^ (den < 0);

然后通过av_gcd函数得到输入的有理数的分子和分母的最大公约数。分数的化简的其中一种方法是:化成分数乘法,求出比值,再把比值写成比号链接的形式。即可把一个分数化成和它相等,但分子和分母都比较小的分数,叫做约分,约分时根据分数的基本性质一次性约分(用最大公因数分别去除分子、分母):

int64_t gcd = av_gcd(FFABS(num), FFABS(den));

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

/**
 * 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,让输出的有理数中的分子和分母都除以最大公约数(约分),从而让分数化简:

if (gcd) {
        num = FFABS(num) / gcd;
        den = FFABS(den) / gcd;
    }

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

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

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


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

    return den == 0;

四、av_reduce函数的使用例子

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

#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;
}

输出如下:

  • 23
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
av_dump_format函数FFmpeg中的一个非常有用的函数,可以用来打印音视频文件的信息,比如文件格式、时长、编码器等等。 该函数的定义如下: ```c void av_dump_format(AVFormatContext *ic, int index, const char *url, int is_output); ``` 其中,参数ic是一个AVFormatContext指针,表示音视频文件的上下文,它包含了音视频文件的所有信息;参数index表示要打印的流的索引,如果index为负数,则表示打印所有流的信息;参数url是一个字符串,表示音视频文件的文件名;参数is_output表示该文件是输入文件还是输出文件,如果是输入文件,则is_output为0,否则为1。 使用av_dump_format函数非常简单,只需要在打开音视频文件后调用该函数即可,例如: ```c AVFormatContext *ic = avformat_alloc_context(); if (avformat_open_input(&ic, filename, NULL, NULL) < 0) { printf("Failed to open file '%s'\n", filename); return -1; } if (avformat_find_stream_info(ic, NULL) < 0) { printf("Failed to retrieve input stream information\n"); return -1; } av_dump_format(ic, 0, filename, 0); ``` 这个例子中,我们首先使用avformat_alloc_context函数创建了一个AVFormatContext对象,然后通过avformat_open_input函数打开了音视频文件,再通过avformat_find_stream_info函数获取音视频文件的流信息,最后调用av_dump_format函数打印出文件的信息。 注意,av_dump_format函数会将信息打印到标准输出流中,如果需要将信息保存到文件中,可以重定向标准输出流。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值