首先,我们查看一下后面要用到的枚举变量AVRounding。
它定义了舍入法,即如何将含有小数部分的数字转化为整数的方法。
enum AVRounding {
AV_ROUND_ZERO = 0, ///< Round toward zero.
AV_ROUND_INF = 1, ///< Round away from zero.
AV_ROUND_DOWN = 2, ///< Round toward -infinity.
AV_ROUND_UP = 3, ///< Round toward +infinity.
AV_ROUND_NEAR_INF = 5, ///< Round to nearest and halfway cases away from zero.
AV_ROUND_PASS_MINMAX = 8192,
};
AV_ROUND_ZERO
朝向零点舍入
AV_ROUND_INF
背向零点方向舍入
AV_ROUND_DOWN
朝向负轴方向舍入
AV_ROUND_UP
朝向正轴方向舍入
AV_ROUND_NEAR_INF
四舍五入法,舍入到离当前最近的整数
AV_ROUND_PASS_MINMAX
这个Flag并不表示一个舍入法,而是针对 INT64_MIN 和 INT64_MIN 这两个特殊值设立的,表示当针对这两个特殊值做舍入时,直接返回这两个特殊值即可,不做舍入。
AV_NOPTS_VALUE 等同于 INT64_MIN
以上5个Flag是互斥的,同时只能使用一个,而想要使用这个Flag时,要使用 or 位运算符来组合上述Flag使用。
示例代码:
av_rescale_rnd(3, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
// Rescaling 3:
// Calculating 3 * 1 / 2
// 3 / 2 is rounded up to 2
// => 2
av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX);
// Rescaling AV_NOPTS_VALUE:
// AV_NOPTS_VALUE == INT64_MIN
// AV_NOPTS_VALUE is passed through
// => AV_NOPTS_VALUE
我们介绍一下AVRational类型。我们知道,在计算机中,我们是无法准确表示一个有小数的有理数的。而在FFmpeg中,则需要精确地表示有理数,为了避免这个问题,提供了AVRational以分数的形式来保存有理数,这样就可以准确表示要表达的值。
如AVRational{1,3},就表示三分之一,如果转而用浮点数表示,那么就只能表示为 0.33333 这样的只有有限位的数,并不准确。
FFmpeg还提供了针对AVRational诸多计算函数,我们下面要介绍的函数在底层可能就使用了它们。
下面我们详细了解一些这些API:
int64_t av_const av_gcd(int64_t a, int64_t b);
/*
求整数a和整数b的最大公约数。
当a和b均大于0时,返回值也大于0;当a和b有一个为0是,返回0。
注意:负数没有最大公约数。
*/
int64_t av_rescale(int64_t a, int64_t b, int64_t c) av_const;
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd) av_const;
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq) av_const;
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,enum AVRounding rnd) av_const;
/*
以上4个函数是非常类似的,都是对整数a做一个缩放,准确来说是:
a * b / c
不同的是:
1、b和c的类型不同,可以是整数,也可以是AVRational
2、由于计算结果不是整数,而返回值是整数,因此需要指定舍入法。不能指定舍入法的函数使用四舍五入法。
为什么要提供这几个函数?它们所做的事情貌似非常的简单,这是因为我们在计算 a*b/c
这个表达式时,先执行 a*b ,此时可能会导致值超出了int64_t的取值范围而导致错误,
而这个函数则规避了这个隐藏的问题。
*/
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
/*
比较两个以不同时基为单位的时间戳。
如果 ts_a 在 ts_b 前,返回-1
ts_a 在 ts_b 后,返回1
ts_a 和 ts_b 表示同一个时间点,返回0
*/
int64_t av_compare_mod(uint64_t a, uint64_t b, uint64_t mod);
/*
比较两个数的余数。
如果 `a % mod < b % mod`,返回负值
`a % mod > b % mod`,返回正值
`a % mod == b % mod`,返回0
注意:除数mod,必须是2的幂,不能是任意整数。
*/
int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb);
/*
和av_rescale_*()的类似,返回 in_ts * in_tb / out_tb。
但其使用的场景却不同,这个函数被设计为在如下情况使用:当一个流中的每个音频packet需要将input时间戳转化到其他不同的时基时,
就使用这个函数。与简单的av_rescale_q()相比,这个函数对可能的非一致frame duration
(即同一个流中获取的帧中保存的采样数可能会变化)具有更好的健壮性。
参数last是一个私有的状态值,同一个流上的按照顺序获取的packet必须使用同一个last。在第一次调用时,last应被初始化为AV_NOPTS_VALUE。
参数fs_tb:duration的时基,一般要比in_tb和out_tb更加的细粒度化。
参数duration:直到下次调用这个函数的时长,也就是当前packet或frame的duration
参数last:指向一个时间戳,时基是fs_tb,是一个私有变量
注意:在这个函数的上下文中,duration是以采样为单位的,而非秒。
*/
int64_t av_add_stable(AVRational ts_tb, int64_t ts, AVRational inc_tb, int64_t inc);
/*
为时间戳添加一个值。
可以注意到添加的值是以另一个时基为单位的。
注意:这个函数保证,当同一个时间戳被重复添加值时不会累积舍入误差。
代码:
AVRational inRat = (AVRational){1,5};
AVRational outRat = (AVRational){1,15};
nRet = 0;
for(int i = 0;i < 6;i++)
{
nRet = av_add_stable(inRat,nRet,outRat,4);
printf("ret = %d.\n",nRet);
//值依次为:1,3,4,5,7,8
}
printf("ret = %d.\n",nRet); //最终值为8而非6
*/