引入
首先介绍一下基础名词
DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。当数据没b帧时,dts = pts,有兴趣可参阅我前面视频知识类文章。
PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
time_base : 时间基用于表示时间基准,即时间戳的单位。它也是用来度量时间的,在我们生活世界中,有各种时间单位比如年月日时分秒,这都可用理解为时间基。在ffmpeg是这样设计的
typedef struct AVRational{
int num; ///< Numerator
int den; ///< Denominator
} AVRational;
转化成double类型 方便计算 比如原子单位是秒 则分表示AVRational{1,60};
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
不同时间基与相同时间戳组合,就把时间具体化了,比如2小时后买菜,那是不是可以说120分钟或者7200秒后买菜,这种转化过程在人类世界看着很鸡肋,但是可以转化成“原子时间”(最终单位 不可再往下面转换),这对于计算机处理音视频打下根基,因为不同的封装格式,timebase是不一样的。
time_base={1,60} pts = 2h
显示未来时间戳预测值= pts * time_base =120分钟
编程处理则是:pts*av_q2d(time_base) 是帧的显示时间戳。
结合音视频实践讨论
在一个视频播放时会进行解复用得到音视频分离的压缩数据,再进行解码得到原始数据,进行播放(这里暂且跳过队列,同步讨论)。为什么要这么麻烦? 假设电影大小压缩是5g 那原始数据可能高达30g,所以这个编解码过程是很有必要的。(ffmpeg的时间基都是以秒为单位)
在ffmpeg中对应的结构体为AVFrame,它的时间基为AVCodecContext 的time_base ,AVRational{1,25}
压缩后的数据(对应的结构体为AVPacket)对应的时间基为AVStream的time_base,AVRational{1,90000}。
因为数据状态不同,时间基不一样,所以我们必须转换,在1/25时间刻度下占10格,在1/90000下是占多少格。这就是pts的转换。
常用数学公式
每帧采样点数 即是帧讨论
音视频很多是相通但不相同知识点,具体是有差异的比如
音频 | 视频 |
---|---|
采样率常见的采样率有 44.1 kHz、48 kHz | 帧率常见的帧率有 24 fps、30 fps、60 fps |
采样精度(sample depth)指的是每个音频样本的位数,常见的有 16 位、24 位等。 | 视频色深(bit depth)指的是每个像素点的颜色位数,常见的有 8 位(256 色)、10 位(1024 色)、12 位(4096 色)等 |
声道数(channels)指的是音频的声道数,常见的有单声道(1)、立体声(2)、5.1 声道(6)等 | 分辨率(resolution)指的是视频图像的宽度和高度,常见的有 1920x1080(Full HD)、3840x2160(4K)等 |
timestamp(秒) = pts * av_q2d(st->time_base)
根据pts来计算一帧在整个视频中的时间位置:
time(秒) = st->duration * av_q2d(st->time_base)
计算视频长度
af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
⾳频播放时间计算:
每帧持续时间(秒) = 每帧采样点数 / 采样频率(HZ)
以采样率44100Hz来计算,每秒44100个sample,⽽正常⼀帧为1024个sample,可知每帧播放时间/1024=44100,得到
每帧播放时间=1024/44100=0.02321995464852608s (23.21995464852608 ms)。
如果我们在处理浮点数不当,直接截取前n位,这里就存在音视频同步问题,
比如如果累计10万帧,误差>1199毫秒,那视频的音视频不对称了。
int av_get_audio_frame_duration(AVCodecContext *avctx, int nb_samples)
比特率 = 采样率 * 采样精度 * 声道数
例如,如果采样率是44100Hz,采样精度是16位,双声道立体声,则比特率为 44100 × 16 × 2 = 1,372,800bps。
data_size = av_samples_get_buffer_size(NULL, af->frame->channels,
af->frame->nb_samples,
af->frame->format, 1);
⾳频帧数据量 = 每帧采样点数 * 采样精度 * 声道数
例如,一个音频帧如果有1024个样本,每个样本是16位(2字节),并且有两个声道,则该帧的数据量为1024样本 × 2字节样本 × 2声道 = 4096字节。
以上两者关系: 音频帧数据量 * 播放时长 = 比特率
frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
输出采样点 = (输入采样点 * 输出采样率/ 输入采样率)
,即上面提到的pkt由于数据量较小,而frame数据量大,所以需要pts时间戳转化。
关于音频pts的计算 很好理解 因为分成了原子块,那不就是等差数列吗:
pts = n*duration = n*nb_samples
next_pts-current_pts=current_duration
根据数学等差公式an=a1+(n-1)*d可得pts=n*d
码率 = 音频文件大小/时长
int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align)
一帧图像大小 = 宽 * 高 * 像素格式大小字节数
具体音视频采样点这些基础知识,看一看我之前的文章,这里不详细深入。
不同结构体的time_base/duration分析
ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不⼀样, ffmpeg提供函数在各个time_base中进⾏切换。
AVFormatContext
duration:整个码流的时⻓,获取正常时⻓的时候要除以AV_TIME_BASE,得到的结果单位是秒AVStream
time_base:单位为秒,⽐如AAC⾳频流,他可能是{1,44100} TS流,按{1, 90000} duration:表示该数据流的时⻓,以AVStream->time_base 为单位
AVPacket
pts:以AVStream->time_base为单位
dts:以AVStream->time_base为单位
duration:以AVStream->time_base为单位
AVFrame
pts:以AVStream->time_base为单位
pkt_pts和pkt_dts:拷⻉⾃AVPacket,同样以AVStream->time_base为单位
duration:以AVStream->time_base为单位
总结
总结:ffmpeg提供了很多现成的api函数,我们在使用前需要知道它们之间的关系,不然在求学的道路上,很难快速学习。必须佩服开发人员对时间的理解,我们在求学也要多留意世界的奥妙,参悟宇宙法则,再结合实际进行创新。
学习资料分享
0voice · GitHub