=================================================================
AVRational结构体和其相关的函数分析:
FFmpeg有理数相关的源码:AVRational结构体和其相关的函数分析
FFmpeg源码:av_rescale_rnd、av_rescale_q_rnd、av_rescale_q、av_add_stable函数分析
=================================================================
一、引言
有理数是整数(正整数、0、负整数)和分数的统称,是整数和分数的集合。整数也可看作是分母是1的分数。不是有理数的实数称为无理数,即无理数的小数部分是无限不循环的数。
AVRational是FFmpeg源码中的一个结构体,用来抽象有理数。为什么不使用double类型(浮点数)呢?虽然有理数可以表示为浮点数,但浮点操作的转换过程是一个有损过程,使用浮点数计算可能会导致有理数出现精度误差;另一方面,FFmpeg的特性要求高精度的时间戳计算。AVRational结构体和其相关函数可以作为一个通用接口,用于操作作为分子和分母对的有理数。
AVRational相关的函数都带有`_q`后缀,表示数学符号“ℚ”(Q),表示所有有理数的集合。
二、AVRational结构体的声明
AVRational结构体定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavutil/rational.h中:
/**
* Rational number (pair of numerator and denominator).
*/
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
FFmpeg源码中使用AVRational结构体来表示有理数。注意:执行AVRational结构体相关的函数时最好都要在函数外部判断其分母是否为0。有理数的分母是不能等于0的,分母等于0是没有意义的。分母为0可能会导致除数为0,从而导致程序崩溃。这些函数内部都不会对分母为0的情况进行处理。
成员变量num:有理数的分子
成员变量den:有理数的分母
三、av_make_q函数的定义
av_make_q函数定义在libavutil/rational.h中:
/**
* Create an AVRational.
*
* Useful for compilers that do not support compound literals.
*
* @note The return value is not reduced.
* @see av_reduce()
*/
static inline AVRational av_make_q(int num, int den)
{
AVRational r = { num, den };
return r;
}
其作用是:给有理数的分子和分母赋值并返回。
形参num:输入型参数。传递给有理数分子的值。执行av_make_q函数后,有理数分子的值为num。
形参den:输入型参数。传递给有理数分母的值。执行av_make_q函数后,有理数分母的值为den。
返回值:返回被赋值的AVRational类型的有理数。
四、av_cmp_q函数的定义
(一)av_cmp_q函数的定义
av_cmp_q函数定义在libavutil/rational.h中:
/**
* Compare two rationals.
*
* @param a First rational
* @param b Second rational
*
* @return One of the following values:
* - 0 if `a == b`
* - 1 if `a > b`
* - -1 if `a < b`
* - `INT_MIN` if one of the values is of the form `0 / 0`
*/
static inline int av_cmp_q(AVRational a, AVRational b){
const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
else if(b.den && a.den) return 0;
else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
else return INT_MIN;
}
其作用是:比较两个有理数的大小。
形参a:输入型参数。被比较的第一个有理数。
形参b:输入型参数。被比较的第二个有理数。
返回值:如果a的值等于b,返回0;如果a>b,返回1;如果a<b,返回-1;如果a或b中有一个有理数的分子和分母同时为0,返回`INT_MIN`(-2147483648)。注意:这函数非常奇怪,如果a或b中有一个有理数的分子不为0但分母为0,这函数不会返回`INT_MIN`;只有当a或b中有一个有理数的分子和分母同时为0,才会返回`INT_MIN`。av_cmp_q函数内部没有对a和b中分子不为0但分母为0的情况进行处理。所以在使用该函数之前最好要先在其外部判断a和b分母是否为0,避免被比较的有理数分母出现0的情况。
(二)av_cmp_q函数的使用例子
编写测试例子main.c,在Ubuntu中使用9.4.0版本的gcc编译通过:
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
static inline AVRational av_make_q(int num, int den)
{
AVRational r = { num, den };
return r;
}
static inline int av_cmp_q(AVRational a, AVRational b){
const int64_t tmp= a.num * (int64_t)b.den - b.num * (int64_t)a.den;
if(tmp) return (int)((tmp ^ a.den ^ b.den)>>63)|1;
else if(b.den && a.den) return 0;
else if(a.num && b.num) return (a.num>>31) - (b.num>>31);
else return INT_MIN;
}
int main()
{
AVRational a = av_make_q(1, 2);
AVRational b = av_make_q(1, 3);
printf("av_cmp_q(a, b): %d\n", av_cmp_q(a, b));
AVRational c = av_make_q(65536, 1);
AVRational d = av_make_q(2, 0);
printf("av_cmp_q(c, d): %d\n", av_cmp_q(c, d));
AVRational e = av_make_q(0, 0);
AVRational f = av_make_q(1, 3);
printf("av_cmp_q(e, f): %d\n", av_cmp_q(e, f));
return 0;
}
输出如下:
五、av_q2d函数的定义
av_q2d函数定义在libavutil/rational.h中:
/**
* Convert an AVRational to a `double`.
* @param a AVRational to convert
* @return `a` in floating-point form
* @see av_d2q()
*/
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
该函数作用是:将有理数从AVRational类型转换为double类型(双精度浮点数)。该操作可能会造成精度丢失。
形参a:输入型参数。被转换的有理数。
返回值:由形参a转换出来的double类型数值。
六、av_reduce函数的定义
详情请看《FFmpeg源码:av_reduce函数分析》
七、av_mul_q函数的定义
av_mul_q函数定义在libavutil/rational.c中:
/**
* Multiply two rationals.
* @param b First rational
* @param c Second rational
* @return b*c
*/
AVRational av_mul_q(AVRational b, AVRational c)
{
av_reduce(&b.num, &b.den,
b.num * (int64_t) c.num,
b.den * (int64_t) c.den, INT_MAX);
return b;
}
该函数作用是:将有理数b * c的结果作为返回值返回。可以看到它内部调用了av_reduce函数。
八、av_div_q函数的定义
av_div_q函数定义在libavutil/rational.c中:
/**
* Divide one rational by another.
* @param b First rational
* @param c Second rational
* @return b/c
*/
AVRational av_div_q(AVRational b, AVRational c)
{
return av_mul_q(b, (AVRational) { c.den, c.num });
}
该函数作用是:将有理数b ÷ c的结果作为返回值返回。
九、av_add_q函数的定义
av_add_q函数定义在libavutil/rational.c中:
/**
* Add two rationals.
* @param b First rational
* @param c Second rational
* @return b+c
*/
AVRational av_add_q(AVRational b, AVRational c) {
av_reduce(&b.num, &b.den,
b.num * (int64_t) c.den +
c.num * (int64_t) b.den,
b.den * (int64_t) c.den, INT_MAX);
return b;
}
该函数作用是:将有理数b + c的结果作为返回值返回。
十、av_sub_q函数的定义
av_sub_q函数定义在libavutil/rational.c中:
/**
* Subtract one rational from another.
* @param b First rational
* @param c Second rational
* @return b-c
*/
AVRational av_sub_q(AVRational b, AVRational c)
{
return av_add_q(b, (AVRational) { -c.num, c.den });
}
该函数作用是:将有理数b - c的结果作为返回值返回。