一:ffmpeg的模型
1:转码模型
转码基本流程:flv格式数据-->h264/aac数据-->yuv/pcm数据-->h264/aac数据-->mp4格式数据。
a:我们把flv格式数据或者mp4格式数据这一层叫做mux/demux层或者复用层有些人习惯于叫做封装层,这里叫做mux/demux层下面同理。
b:我们把h264/aac数据这一层叫做编解码层或者codec/decode,这里叫做codec/decode层下面同理。
c:我们把yuv/pcm数据这一层叫做原始数据层或者Raw data 层,这里叫做Raw data层下面同理。
2:摄像头采集模型
摄像头采集基本流程:yuv/pcm数据-->h264/aac数据-->flv格式数据。
3:播放器模型
播放器基本流程:flv格式数据-->h264/aac数据-->yuv/pcm数据。
从上面的三个最常用模型不难看出也可以说总结出ffmpeg基本把数据或者说结构分为了 “mux/demux层”也就是ffmpeg中的AVStream
“codec/decode层” 也就是ffmpeg中的AVCodec
“Raw data 层”这个也在AVStream 中存放着(如果是自己填写的例如ios或者android获取当前毫秒时间的可以单独放置到一个timebse的结构体中),这个。各位看官对这个分层有了大致的了解之后我们再说分层对timebase的影响。
二:ffmpeg中的timebase是什么
FFMPEG的很多结构中有AVRational time_base;这样的一个成员,它是AVRational结构的
typedef struct AVRational{
int num; ///< numerator
int den; ///< denominator
} AVRational;
AVRational这个结构标识一个分数,num为分数,den为分母。
1:简单来讲ffmpeg的timebase是为了解决当时间戳是小数的时候转化为整数和为了更好的做分层结构而设置的一种机制。
2:ffmpeg的timebse我们可以理解为单位,比如米,毫秒,秒,千克这些都是单位,但这个timebse有时候不是我们日常中能见到的单位,举个例子ffmpeg本身是以秒为基准的,1秒= 1000毫秒,那如果timebse是1:1很好理解是秒的单位,如果timebase是1:1000也很好理解是毫秒单位,但如果timebase是1:25呢,我们的常用单位就解释不了了,这要是很多人的误区。
3.有了时间戳之后,最终进行展示时还要需要将 PTS时间戳转成以秒为单位的时间;那这里需要向大家介绍一下 ffmpeg的时间基。
tbr: 是我们通常所说的帧率。time base of rate
tbn: 视频流的时间基。 time base of stream
tbc: 视频解码的时间基。time base of codec
那我们如何理解时间基呢?其实非常简单,就是时间刻度。
我们以帧率为例,如果每秒钟的帧率是 25帧,那么它的时间基(时间刻度)就是 1/25。也就是说每隔1/25 秒后,显示一帧。
所以如我们当前的时间是 100, 时间基是 1/25,那么转成秒的时间是多少呢? 100*时音基(1/25),也就是100 * 1/25 = 4秒。
再举个例子,(1,25),时间刻度就是1/25
(1,90000),时间刻度就是1/90000
那么,在刻度为1/25的体系下的time=5,转换成在刻度为1/90000体系下的时间time为(5*1/25)/(1/90000) = 3600*5=18000
如果由某个解码器产生固定帧率的码流
AVCodecContext中的AVRational根据帧率来设定,如25帧,那么num = 1,den=25
AVStream中的time_base一般根据其采样频率设定,如(1,90000)
将不同时间基的值转成按秒为单位的值计算如下:
static inline double av_q2d(AVRational a){
return a.num / (double) a.den;
}
timestamp(秒) = pts * av_q2d(time_base)
有时候我们需要在不同的时间基之间做换算。ffmpeg为我们提供了非常方便的函数。即:
av_rescale_q(a,b,c) ==a * b / c。
av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd); rnd是取整的方式
既然公式这么简单,我们自己写就OK了,为什么ffmpeg还要单独提供一个函数呢?其实这个看似简单的方法,还要考虑数值溢出的问题。所以把这块的逻辑加上之后,就没我们看到的这么简单了。
ffmpeg内部时间基:
#define AV_TIME_BASE 1000000
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
将不同时间基的值转成按秒为单位的值计算如下:
time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp
将不同时间基的值转成按秒为单位的值计算如下:
timestamp = AV_TIME_BASE * time_in_seconds
三:ffmpeg常见概念
帧率:帧率(Frame rate)也叫帧频率,帧率是视频文件中每一秒的帧数,肉眼想看到连续移动图像至少需要15帧。
码率:比特率(Bit rate)(也叫码率,数据率)是一个确定整体视频/音频质量的参数,每秒单位处理的字节数,码率和视频质量成正比,在视频文件中中比特率用bps来表达。
AV_NOPTS_VALUE:
Undefined timestamp value.
FFmpeg里有两种时间戳:DTS(Decoding TimeStamp)和PTS(Presentation TimeStamp,前者是解码的时间,后者是显示的时间。要仔细理解这两个概念,需要先了解FFmpeg中的packet和frame的概念;FFmpeg中用AVPacket结构体来描述解码前或编码后的压缩包,用AVFrame结构体来描述解码后或编码前的信号帧。 对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。 如果视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。可事实上,在大多数编解码标准(如H.264或HEVC)中,编码顺序和输入顺序并不一致。 于是才会需要PTS和DTS这两种不同的时间戳。
PTS:Presentation TimeStamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
DTS:Decode TimeStamp。DTS主要是标识读入内存中的bit流在什么时候开始送入解码器中进行解码。
下面给出一个GOP为15的例子,其解码的参照frame及其解码的顺序都在里面:
GOP如上图:I frame 的解码不依赖于任何的其它的帧.而p frame的解码则依赖于其前面的I frame或者P frame.B frame的解码则依赖于其前的最近的一个I frame或者P frame 及其后的最近的一个P frame.
常用结构体:
AVFilterLink:A link between two filters
包含内容:
AVFilterContext *src -----source filter
AVFilterContext *dst------ dest filter
AVStream:
index:0-video,1-audio
一个视频文件的PTS不一定从0开始,因此第一个PTS就是基准值,此时的播放时间就是当前帧的PTS减去第一帧的PTS。
PTS不是具体的毫秒,而是一个数值,真实的时间由timebase计算而来。
progress =av_rescale_q(enc_pkt.dts, media_muxer_->get_stream(video_stream_index_)->time_base, AV_TIME_BASE_Q);
int percent = (int)(progress *100/duration_); //可以计算进度
网友评论