=================================================================
AVRational结构体和其相关的函数分析:
FFmpeg有理数相关的源码:AVRational结构体和其相关的函数分析
FFmpeg源码:av_reduce函数分析
=================================================================
一、av_reduce函数的声明
av_reduce函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件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函数得到输入的有理数的分子和分母的最大公约数(关于av_gcd函数的用法可以参考:《FFmpeg源码: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_ctypedef struct AVRational{int num; ///< Numeratorint 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];
}
#endifint64_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;
}
输出如下: