http://blog.csdn.net/panda1234lee/article/details/48782733
- 序列的说明
在H264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以I帧开始,到下一个I帧结束。
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。 H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
一个序列就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。当运动变化多时,可能一个序列就比较短了,比如就包含一个I帧和3、4个P帧。
- 帧内预测
场和帧:视频的一场或一帧可用来产生一个编码图像。在电视中,为减少大面积闪烁现象,把一帧分成两个隔行的场。
宏块:一个编码图像通常划分成若干宏块组成,一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个8×8 Cr彩色像素块组成。
片:每个图象中,若干宏块被排列成片的形式。片分为I片、B片、P片和其他一些片。
I片只包含I宏块,P片可包含P和I宏块,而B片可包含B和I宏块。
I宏块利用从当前片中已解码的像素作为参考进行帧内预测。
P宏块利用前面已编码图象作为参考图象进行帧内预测。
B宏块则利用双向的参考图象(前一帧和后一帧)进行帧内预测。
片的目的是为了限制误码的扩散和传输,使编码片相互间是独立的。
某片的预测不能以其它片中的宏块为参考图像,这样某一片中的预测误差才不会传播到其它片中去。
- 采样率与比特率的辨析
16位的声音和24位的画面基本已经是普通人类的极限了,更高位数就只能靠仪器才能分辨出来了
比如电话就是3000HZ取样的7位声音,而CD是44100HZ取样的16位声音,所以CD就比电话更清楚。
以电话为例,每秒3000次取样,每个取样是7比特,那么电话的比特率是21000。而CD是每秒44100次取样,两个声道,每个取样是16位PCM编码,所以CD的比特率是44100*2*16=1411200,也就是说CD每秒的数据量大约是176KB。
3.ffmpeg音视频同步
----------------------
前言:
----------------------
ffmpeg通过AVStream结构的time_base(有理数结构体——AVRational,由分子和分母两部分组成)可以获取一个参考时间单位,所有音视频流的timestamp都是基于这个时间单位顺序递增,
比如time_base.num=1,time_base.den=90000,表示把1秒分成90000等份,音视频包的PTS(显示时间戳)和DTS(解码时间戳)就表示有多少个(是第几个)1/90000 (time_base)单位时间,
更简单一点假设time_base.num=1,time_base.den=1000,就表示1秒分成1000等份,相当于1毫秒,那时间戳就表示是以毫秒为单位的,
在做音视频处理时候,如果解码的速度比按照时间戳显示的速度快,那就简单直接处理,不用丟帧(Drop Frame),当解码速度很慢时(比如手机设备),就需要丢帧处理,是每两帧丟一帧数据,还是每三帧丟一帧数据,就需要根据延时显示程度来计算丢帧的比率。
----------------------
获取视频帧的PTS:
----------------------
虽然视频流能提供视频流帧率值,但如果简单地通过 帧数/帧率 来同步视频,可能会使音视频不同步。
正确的方式是利用视频流中每个包的DTS和PTS,即包的解码时间戳(DTS--Decompressed Time Stamp)和显示时间戳(PTS--Presentation Time Stamp)。为了搞清楚这两个概念,需要知道视频的编码存储方式。正如之前提到的H264编码原理,将视频帧分为I帧、P帧、B帧,这也就是为什么调用avcodec_decode_video可能没有得到完整一帧的原因。
假设有一段视频序列,其帧排序为:I B B P。但是在播放B帧之前需要知道P帧的信息,所以帧的实际存储顺序可能是I P B B。这就是为什么我们会有一个DTS和PTS。DTS告诉我们什么时候解码的,PTS告诉我们什么时候显示。所以,在这个例子中,流可能是这样的:
解码器输入
Stream: I P B B P B B // 存储顺序
PTS: 0312645
DTS: 0123456
解码器输出
Stream: I B B P B B P
PTS: 0123456
通常只有当存在B帧时,PTS和DTS才会不一致。
值得注意的是,通过av_read_frame()得到的AVPacket中的DTS通常才有正确的值,而通过avcodec_decode_video2()得到的AVFrame的PTS并没有包含有用信息。第一种方法是利用在解码包时ffmpeg会按照PTS重新对包进行排序, 因此被avcodec_decode_video2()处理过的包的DTS和返回的帧的PTS是相同的, 这样就可以得到帧的PTS了。然而我们并不是总能获得该值(经测试影响不大),所以第二种方法是我们需要保存第一帧的第一包的PTS(之后的PTS可以根据帧率计算出来), 将其作为这一帧的PTS。因为当一帧开始发送第一包时,avcodec_decode_video()会调用相关函数为帧申请存储空间,我们可以重写这个函数,在函数中加入获取包DTS的方法,并用全局变量进行保存。通过以上两种方法就计算出了帧的PTS。
----------------------
计算视频帧实际的PTS:
----------------------
考虑重复帧的情况(甚至得不到PTS的情况),使用VideoState的video_clock字段时刻记录视频已经播放的时间,通过换算到流的时间基来计算实际正确的PTS(保存在VideoPicture的PTS字段中),并重新排序视频帧(queue_picture),这样便可以设置合适的刷新速率(比如通过简单计算前一帧和现在这一帧的时间戳来预测出下一个时间戳的时间)。接下来我们将同步视频到音频。
----------------------
计算音频实际的PTS:
----------------------
虽然音视频流都包含了播放速率的信息,音频使用采样率来表示,而视频则采用帧率来表示,所以我们不能简单使用两个数据来对音视频进行同步,而是需要使用DTS和PTS。
现在看一下怎样得到音频时钟。我们可以在音频解码函数audio_decode_frame()中更新音频时间。然而需要注意的是我们并不是每次调用这个函数的时候都在处理新的包,所以更新时钟的时刻有两个: ①当我们得到新的包的时候,我们简单的设置音频时钟为这个包的时间戳PTS。②如果一个包里有许多帧,我们通过样本数和采样率来计算:
n = 16/8 * channels; (当采样精度为16位时)
audio_clock += data_size / (n * sample_rate)(当缓冲区满时的播放时间);
然而我们不能把audio_clock直接作为音频的PTS,因为在audio_decode_frame()计算的audio_clock的是假定缓冲满的情况,而实际上可能缓冲是不满的,所以实际播放时根据缓冲大小和播放速率计算播放时间:需要减去空闲部分的时间:PTS -= (double)hw_buf_size / bytes_per_sec (hw_buf_size表示空闲缓冲大小,bytes_per_sec表示每秒播放的字节数)
----------------------
同步视频到音频:
----------------------
通过以上方式可以获取到实际的音频时间。有了这个值, 在音频和视频不同步的时候,我们会调整下次刷新的值:如果视频时间戳(类似序号)相比音频时间戳大于一定阈值(即视频播放太快),我们加倍延迟。如果视频时间戳相较于音频时间戳小于一定阈值(即视频播放太慢),我们将尽可能快的刷新。
同步流程(比喻):
有一把尺子 一只蚂蚁(视频)跟着一个标杆(音频)走, 标杆是匀速的 蚂蚁或快或慢,慢了你就抽它 让它跑起来,快了就拽它。这样音视频就能同步了。 这里最大的问题就是音频是匀速的,视频是非线性的。
----------------------
其他同步方法:
----------------------
分别获得音视频的PTS后,我们有三个选择:视频同步音频(计算音视频PTS之差,来判定视频是否有延迟)、音频同步视频(根据音视频PTS差值调整音频取的样值,即改变音频缓冲区的大小)和音频视频同步外部时钟(同前一个),因为调整音频范围过大,会造成令用户不适的尖锐声,所以通常我们选择第一种。
----------------------
time_base(时间基)的基本概念:
----------------------
AVRational的结构如下:
typedef struct AVRational{
int num; ///< numerator
int den; ///< denominator
} AVRational;
AVRational这个结构标识一个分数,num为分数,den为分母。
1.时间基的转换的概念
实际上time_base的意思就是时间的刻度:
比如,编解码器上下文的时间基为(1,25),那么时间刻度就是1/25
文件容器中的流的时间基为(1,90000),那么时间刻度就是1/90000。
那么,在刻度为1/25的体系下的time=5,转换成在刻度为1/90000体系下的时间time为(5*(1/25)) / (1/90000)= 3600*5=18000
【ffmpeg提供了av_rescale_q_rnd函数进行转换。
av_rescale_q_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd)
此函数主要用于对于不同时间戳的转换。具体来说是将原来以 "时间基b" 表示的 数值a 转换成以 "时间基c" 来表示的新值。AVRounding表示取整的策略。】
浙公网安备 33010602011771号