Bootstrap

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

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

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

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

FFmpeg源码:av_reduce函数分析

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

一、引言

有理数是整数(正整数、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的结果作为返回值返回。

;