FFmpeg学习(四)视频基础
一:视频入门
(一)视频定义(什么是视频)
(二)图像
1.像素:图像由像素组成(如下图图片中的一个个小格子)。
对于每个像素,还有位深的概念:用多少个位来表示位深。类似于音频中的采样大小
RGB888:对于R、G、B中每个元素占8位
RGBA:同上,多了一个A(透明度)
2.RGB:每个像素是由RGB三元素组成;如下图格子表示一个像素,内部由RGB(三个发光二极管)组成。
通过RGB(三个发光二极管不同亮度组成)可以组合成多种色彩:
3.分辨率:用来表示图像在X、Y轴上各有多少个像素点。
(三)屏幕显示器
1.显示器和图像都具有像素!!!
对于屏幕而言,每一个像素如下所示:
根据图像的数据,来控制屏幕中每一个像素点的发光二极管的强度。从而实现不同颜色点,拼接为最终图像!!
2.RGB的色彩问题(部分情况使用的BGR顺序,使得与显示器的驱动程序处理顺序不一致,导致图像渲染出错)
3.屏幕指标
PPI:每英寸下的像素数
DPI:每英寸下的点数
基本上两者相等,极少情况下DPI每个点中存在多个像素
PPI > 300 :视网膜级别(人眼无法区别是由像素组成,认为是一整块图像)
(四)码流的计算
1.分辨率(清晰度)
常见的视频宽高比是16:9,部分老式显示器可能是4:3;如果一个视频的宽高比不是这两种,则需要进行转换。 对于360P,16:9中是指高360P,宽640P
2.帧率(平滑、流畅度)
对于直播系统中,为了减少所占码流的大小,通常采用15帧/S;
录课:30帧/S
视频播放60帧/S
3.未编码视频的RGB码流
其中3Byte表示一个像素RGB所占大小
(五)图像的显示(图像和显示器分辨率不一致情况)
二:YUV了解
各种格式解析:https://www.fourcc.org/rgb.php
(一)什么是YUV(有了RGB图像之后,为什么还需要YUV?)
由电视系统发展而来,逐步从黑白--->彩色;YUV可以兼容这两种模式。当获取Y信号之后就可以正常的进行播放,此时颜色为黑白(黑白电视机可以播放)。UV用于描述影像的色彩和饱和度(彩色电视机播放需要)。最标准YUV格式为YUV4:2:0
在DVD、摄像机、数字电视等消费类视频产品中,常用的色彩编码方案是YCbCr,其中Y是指亮度分量,Cb指蓝色色度分量,而Cr指红色色度分量。
人的肉眼对视频的Y分量更敏感,因此在通过对色度分量进行子采样来减少色度分量后,肉眼将察觉不到的图像质量的变化。
1.YUV图像各个分量
YUV原始图像:
YUV图像的Y分量(只有明亮度、没有色彩和饱和度):
YUV图像的U分量(只有Cb分量,图片蓝色分量):
YUV图像的V分量(只有Cr分量,图片红色分量):
只有在电视中使用了YUV格式,在手机、屏幕、显示器中使用RGB格式,所以YUV显示在屏幕需要进行转换!
2.RGB与YUV的关系
RGB转YUV:
YUV转RGB:
3.YUV常见格式
YUV4:4:4 一个Y元素对应一个U元素对应一个V元素,对于1280×720图像,数据量为:1280×720×3---其中3同RGB888一致,表示3个字节(24位) YUV4:2:2 对于每一个横行,隔一列有一个UV,数据量为1280×720×1+2×1280×720/2 = 1280×720×2--- ×1表示Y占1字节,/2是对于UV来说是每隔一列才有一个UV YUV4:2:0 最标准,最广泛; 隔行分U或者V分量,隔列分是否有UV分量;数据量为1280×720×1+2×1280×720/4 = 1280×720×1.5
各个格式详细见下面图片!!!
YUV4:2:0
对于YUV4:2:0而言满足下面的计算:
YUV4:2:0优点:
1.YUV兼容以前的图像
2.相比较RGB而言,数据量减少1半,存储更加具有优势!
补充:对齐知识!!!!
对于已经对齐的分辨率如:1280*720,640*480这样的分辨率它的YUV420P数据格式是完全对齐(16位对齐) 而像176*144这样的size它的YUV420P不是16位对齐,需要补空白位(0补齐),使得176*144这样的size能16位对齐。
YUV4:2:2
此格式的打包格式为,YUYV YUYV YUYV ……(YUYV422格式);此格式的存储格式为:首先全是Y,然后全是U,最后全是V。
YUV4:4:4
更多格式见:https://blog.csdn.net/cgwang_1580/article/details/79595958
(二)YUV4:2:0存储格式
YUV数据是分层存储的,所以可以更加方便的与黑白电视机相兼容,只需要读取前面的Y数据即可,后面的UV数据舍弃即可。对于彩色电视机,将后面的UV数据一块读取。
4:2:0,是指4个Y对应纵向1个U,1个V;如下图(色彩对应)
不同系统下,存储格式可能不同:比如IOS使用YV12,android使用NV21
YUV4:2:0 码流计算:
(三)YUV命令行采集数据
原始mp4参数:
1.根据参数进行命令行采集YUV数据
ffmpeg -i gfxm.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
ffplay -pix_fmt yuv420p -s 864*486 out.yuv #-s是说明播放时的分辨率 -pix_fmt指定格式 如果两个参数不一致,会导致播放出现花屏等问题
播放YUV图像的Y分量(其他分量也可以播放):
ffplay -pix_fmt yuv420p -s 864*486 -vf extractplanes='y' out.yuv #-vf 表示滤波器,vf属于简单滤波
提取单独分量:
ffmpeg -i gfxm.mp4 -filter_complex 'extractplanes=y+u+v[y][u][v]' -map '[y]' y.yuv -map '[u]' u.yuv -map '[v]' v.yuv
#其中[]表示别名,map可以引用别名
播放出现问题:
ffplay -s 864*486 y.yuv
需要指定格式pix_fmt为单色,而不是按默认YUV去读取
ffplay y.yuv -pix_fmt gray -s 864*486
注意:对于U、V分量,由于是YUV4:2:0,其中U、V分量只占1/2(即X,Y轴分辨率各自减少一半)
ffplay u.yuv -pix_fmt gray -s 432*243
(四)FFmpeg编程采集
x11grab见:https://www.cnblogs.com/ssyfj/p/14576359.html
#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> void rec_video(){ char* devicename = ":0.0"; //:前面为空,表示录制本地;:后面,如果NumA为0, 则表示连接到6000端口; 使用unix socket方式连接时则表示连接的unix socket的路径, 如果为0, 则表示连接到/tmp/.X11-unix/X0 . NumB则几乎总是0. char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; //格式上下文获取-----av_read_frame获取packet AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 //获取输入(采集)格式 iformat = av_find_input_format("x11grab"); //驱动,用来录制桌面 //设置参数 av_dict_set(&options,"video_size","1024*768",0); av_dict_set(&options,"framerate","15",0); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); //----打开输入设备,初始化格式上下文和选项 if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open video device\n"); //打开文件 fp = fopen("./video.yuv","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); fwrite(packet.data,1,packet.size,fp); fflush(fp); //释放空间 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); rec_video(); return 0; }
gcc -o gd 01GetData.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample
由于没有摄像头,所以使用了屏幕录制,所以采集的数据是BGR0类型
ffplay video.yuv -s 1024*768 -pix_fmt bgr0
注意:在下面代码中直接写入packet.size大小,可能在处理摄像头YUV数据时出错
fwrite(packet.data,1,packet.size,fp);
写入时包大小:3145728
读取时数据采用BGR0(最后0是指A透明度为不透明)大小4字节:1024×768×4=3145728;
和前面的读取包大小packet.size一致,所以不会出错,如果两者不一致,那么需要修改packet.size为我们想要的数据大小!!
另外:主动选择像素格式
如果驱动允许我们自己设置pixel_format,那么最好进行主动设置:https://ffmpeg.org/ffmpeg-devices.html#avfoundation
补充:使用摄像头(借到了)
1.查看摄像头所支持的视频格式和分辨率
ffmpeg -hide_banner -f v4l2 -list_formats all -i /dev/video0
发现只支持yuyv422格式....
2.测试摄像头
ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 320*240 -framerate 15 -i /dev/video0 out.yuv
ffplay out.yuv -s 320*240 -pixel_format yuyv422
3.代码实现
#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> void rec_video(){ char* devicename = "/dev/video0"; //设备文件描述符 char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; //格式上下文获取-----av_read_frame获取packet AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 //获取输入(采集)格式 iformat = av_find_input_format("video4linux2"); //驱动,用来录制视频 //设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480 -framerate 15 -i /dev/video0 out.yuv av_dict_set(&options,"video_size","640*480",0); av_dict_set(&options,"framerate","30",0); av_dict_set(&options,"pixel_format","yuyv422",0); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); //----打开输入设备,初始化格式上下文和选项 if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open video device\n"); //打开文件 fp = fopen("./video.yuv","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); fwrite(packet.data,1,packet.size,fp); fflush(fp); //释放空间 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); rec_video(); return 0; }
三:H264编码
(一)H264压缩码率
YUV420下的码流大小为:640×480×1.5(RBG为3字节,YUV420为1.5字节)×15= 6912000 bytes = 55.296 mbps(换算为bits)
H264编码而言,建议的码流大小为500kbps,约为YUV420下的1/100!!!
通信实时性要求高,所以码率相比较于直播会低很多,从而实现流畅度增加
(二)GOP(Group of Pictures)
原始视频数据1s内,包含25帧,帧间隔时间为40ms:
当视频时间跨度为10min后,数据帧数变多25×10×60=15000帧;不易压缩!!----引入分组(按照相关性),如下图所有帧中只包含“玩电脑”、“望远镜”两个动作,在此两个动作上进行微调;所以每一个组都是描述一个图像目标的细微差别。所以,同一个组中的视频是强相关的。而在不同组之间的相关性非常小。以此来进行分组。
由下图可以看出,GOP中帧与帧之间差别很小。将无差别的背景图用一张图表示,将中间人物相同部分放置到一张图,将人物中间望远镜位置等差值进行存储。由此使得GOP这一组帧存储空间被压缩得很小!!!
(三)I/P/B帧
举例:I帧,是关键帧,如果缺失了该帧,则可能后面无法解析
对于前面所提到的GOP中,一组强相关帧中的第一帧,我们一般称为IDR帧(一种特殊的I帧);
对于GOP中,如果包含过多的帧(超过一定范围),H264会强制加入一些I帧到GOP中,用来防止一旦出现错误,导致错误串联的情况!!
对于I帧,不依赖其他任何帧,所以属于帧内压缩!!(编码、解码都只靠自己)
举例:P帧,参考前面的帧,前面解码,当前帧才可以解码;前面编码,当前帧才可以编码。P帧只参考前面,不参考后面!!!属于帧间压缩技术,与前面的帧有关系。
举例:B帧,双向参考帧,压缩率最高,耗时严重,延迟大;所以对于实时性要求高的应用中,基本使用I帧和P帧;对于音视频转码应用,为了节省空间,使用B帧多。
在GOP中,如果含有B帧数据,那么B帧一定是最后才进行解码的。但是播放的时候是按照帧出现的顺序来播放的!!!
对于B帧来说,后面的P帧一定优于B帧先进行解码的???见下面补充:帧与分组的关系(默认H264下)
补充:IDR帧和I帧的区别和联系
补充:帧与分组的关系(默认H264下)
注意,如下图:对于在I帧和P帧之间的B帧,需要参考前面的I帧和后面的P帧。所以解码顺序依次是I帧-->P帧-->中间3个B帧
如下图,对于在两个P帧之间的B帧,参考前后的P帧即可
如下图,对于的B帧,参考前面GOP的P帧和后面GOP的I帧
注意:GOP中所有P帧率都是先于B帧的!!
补充:SPS与PPS
(四)H264压缩技术(一系列压缩技术集合)
帧内压缩(有损压缩技术):解决空域数据冗余问题; ----- 空域:一张图片是占用一定的空间,帧内压缩技术,是用来解决图片内的数据压缩问题。 (对于背景,主体等采用不同方法压缩,使得总数据下降)
帧间压缩(有损压缩技术):解决时域数据冗余问题; ----- 时域:帧与帧之间的数据之间有参考,利用残差值
-------帧内压缩,是将不必要的数据去除,相比于亮度,色度要求更低,所以压缩时可以除去部分色度数据,达到减少数据的要求!!
-------帧间压缩,由于依赖前一帧的数据,而前一帧已经是有损的,所以参考前一帧的当前帧也是有损的!!
整数离散余弦变换(无损压缩技术): ----- 经过上面两种压缩后,数据已经很小了,但是还可以通过DCT变换,将有用数据集中,其他位置为0,进行压缩。可以减少复杂度,利于后面的无损压缩
CABAC压缩(无损压缩技术): ----- 根据上下文进行数据的压缩
(五)宏块---最基础知识点
原始图像:
H264宏块划分:如下图,按像素划分一个8×8像素的宏块,由于每个像素包含一个值,所以可以看作右侧的3维图像!!!
宏块划分完成后:
对于每个宏块,我们还可以进一步划分为子块:如下图:宏块大小16×16,划分为4个8×8,进一步划分为4×4、4×8、8×4一系列子块(注意:子块也是宏块)。
如果宏块特别大,则控制力弱,但是处理速度快;
对于细节多,纹理强的图,宏块需要划分较小,控制力更强,压缩比会更高。
可以对比下图右图,H264划分子块后的压缩比要高于MPEG2的压缩比。H264数据量小于MPEG。
宏块尺寸:(可以进一步划分,用来增强压缩率)
四:I/P/B帧和音视频同步
转载:https://m.imooc.com/mip/article/91381
(一)I/P/B帧
对于 I帧,B帧,P帧,前面三中已经进行了讲解。
- I帧是关键帧,它采用帧内压缩技术;
- B帧是前后参考帧,它属由帧间压缩技术。也就是说在压缩成 B帧前,它会参考它前面的非压缩视频帧,和后面的非压缩的视频帧,记录下前后两帧都不存放的“残差值”,这样可以达到更好的压缩率;
- P帧是向前参考帧,也就是它参考的是前一个关键帧的数据。P帧也属于帧间压缩技术,相对于 B帧来说,P帧的压缩率要比B帧低。
但在实时互动直播系统中,很少使用B帧。主要的原因是压缩和解码B帧时,由于要双向参考,所以它需要缓冲更多的数据,且使用的CPU也会更高。由于实时性的要求,所以一般不使用它。不过对于播放器来说,遇到带有B帧的H264数据是常有的事儿。
(二)PTS/DTS
有了上面 I/B/P帧的概念,我们再来理解 PTS/DTS 就非常容易了。
PTS(Presentation TimeStamp)是渲染用的时间戳,也就是说,我们的视频帧是按照 PTS 的时间戳来展示的。
DTS(Decoding TimeStamp)解码时间戳,是用于视频解码的。
那为什么有了 PTS 还要有 DTS呢?这就与我们上面所讲的 I/B/P帧有关了。
如果我们的视频中没有B帧,那显示的帧的顺序与存放的帧的顺序是一样的,此时PTS与DTS 的值就是一样的,也就没有存在两个时间戳的必要了。
但有了B帧之后,就不是这个样子了。我们举个简单的例子:I B B P
实际应展示的顺序:I B B P
实际在解码的顺序:I P B B
按实际顺序号解码:1 4 2 3
按实际顺序号展示:1 2 3 4
对于上面这个例子我们作下说明:
- 我们实际应该展示的帧的顺序是 I, B, B, P 帧解码后的视频帧。
- 但实际上,这些帧到达之后,在缓冲区里就按照第二行的样子存放的。为什么会这样呢?这是由于我上面所讲的,P帧参考的是 I帧,B帧是双向参考帧。也就是说,如果 I帧和P帧没有解码的话,B帧是无法进行解码的。基于此,为了解决这个问题就出现了 PTS和DTS两个时间戳。
- 第三行是视频帧真正的解码顺序,先解 I帧,然后是P帧,然后是第一个B帧,最后是第二个B帧。
- 最终的展示顺序是 I帧解码后的视频帧,第一个B帧解码后的视频帧,第二个B帧解码后的视频帧,最后是P帧解码后的视频帖。
(三)时间基
有了时间戳之后,最终进行展示时还要需要将 PTS时间戳转成以秒为单位的时间。那这里需要向大家介绍一下 ffmpeg的时间基。
我们在执行 ffmpeg/ffplay命令时,可以通过控制台看到几个参数,分别是 tbr, tbn, tbc。这几个值是什么含义呢?其实就是不同的时间基。
tbr: 是我们通常所说的帧率。time base of rate tbn: 视频流的时间基。 time base of stream tbc: 视频解码的时间基。 time base of codec
在ffmpeg中,不同的时间戳对应不同的时间基。对于视频的渲染我们使用的是视频流的时间基,也就是 tbn。
那我们如何理解时间基呢?时间刻度(间隔)
其实非常简单,就是时间刻度。我们以帧率为例,如果每秒钟的帧率是 25帧,那么它的时间基(时间刻度)就是 1/25。也就是说每隔1/25 秒后,显示一帧。
所以如我们当前的时间是 100, 时间基是 1/25,那么转成秒的时间是多少呢?
100*时音基(1/25),也就是100 * 1/25 = 4秒。
(四)ffmpeg内部时间基
除了我上面所讲的几个时间基之外,ffmpeg内部还有一个时间基。即我们通过所见到的 AV_TIME_BASE
。它在ffmpeg内部定义如下:
#define AV_TIME_BASE 1000000
它还有一种分数所表式法:(这里就是时间间隔的数据存放结构,时间基由下面的函数av_q2d获取)
#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}
在 ffmpeg中进行换算,将不同时间基的值转成按秒为单位的值计算如下:
timestamp(秒) = pts * av_q2d(time_base) #可以看出pts是以自己的时间基为间隔×pts
这里引入了 av_q2d 这个函数,它的定义非常简单:(获取时间基)
typedef struct AVRational{ int num; //numerator int den; //denominator } AVRational; static inline double av_q2d(AVRational a){ /** * Convert rational to double. * @param a rational to convert **/ return a.num / (double) a.den; }
从这里我们可以看到,它与我上面所讲的公式是一样的。
(五)不同时间基的换算
在上面我向大家介绍了 ffmpeg有好几种不同的时间基,有时候我们需要在不同的时间基之间做换算。ffmpeg为我们提供了非常方便的函数。即
av_rescale_q() 把时间戳从一个时基调整到另外一个时基时候用的函数。其中,a 表式要换算的值;b 表式原来的时间基;c表式要转换的时间基。其计算公式为 a * b / c
。
既然公式这么简单,我们自己写就OK了,为什么ffmpeg还要单独提供一个函数呢?其实这个看似简单的方法,还要考虑数值溢出的问题。所以把这块的逻辑加上之后,就没我们看到的这么简单了。不过没关系,我们只要清楚 av_rescale_q 是做什么的,怎么用就可以了。
下面我再给出两个算计公式:
- 时间戳转秒
time_in_seconds = av_q2d(AV_TIME_BASE_Q) * timestamp #时间戳×时间基=n秒
- 秒转时间戳
timestamp = AV_TIME_BASE * time_in_seconds #秒/时间基 = 时间戳
五:H264压缩技术
(一)帧内压缩
1.相邻宏块之间的像素差别不大,可以进行预测。(以宏块为单位,而不是以像素为单位处理,后者效率太低;宏块最小为4×4小块)
2.黑白即可了解图片信息,色度为辅助(如黑白电视)。所以即便颜色有所偏差也无所谓。
3.YUV的优点就是直接获取Y亮度数据,UV数据进行单独处理。
通过预测的方法进行压缩:
当我们不知道下一个宏块的数据(如4×4像素大小),可以根据之前的像素或者当前宏块的像素,来推断出下一个宏块的像素具体是多少!!!
帧内预测(9种模式):详解H.264之帧内预测
4×4亮度块的上方和左方像素A~M为已编码和重构像素,用作编解码器中的预测参考像素(临近像素,也就是前面已经编码完成的)。 a~p为待预测像素,利用A~M值和9种模式(0~8)实现预测。
其中模式2(DC预测)根据A~M中已编码像素预测,而其余模式只有在所需预测像素全部提供才能使用。 图2.b箭头表明了每种模式预测方向。 对模式3~8,预测像素由A~M加权平均而得。 例如,模式4中,d=round(B/4+C/2+D/4)。
0 1 2
3 4 5
6 7 8
模式0:垂直预测,每列数据都与头部数据相同
a b c d
a b c d
a b c d
a b c d
模式1:横向预测,每行数据与左侧数据相同
I I I I
J J J J
K K K K
L L L L
模式2:求平均值,每个值都是(头部a b c d + 左侧 I J K L)的平均值
对于预测模式3-8,倾斜方向的,各个像素是由A到L像素通过权重不等的公式加权计算的
....详见https://blog.csdn.net/u014253011/article/details/79970582
模式选取:在上面9种模式选取时,会先进行预判;以某点(宏块)为基础,推算其相邻的下一个宏块是哪一种模式。
选取方式(一种快速算法):在9种预测方法,看哪一种模式最接近原来宏块像素,则选取该预测模式!!
如下图右侧中,快速获取每一个模块应该使用的预测方式:3表示使用模式3处理,....
帧内预测举例:(与原始图结果相似)
但是有一定区别:
因此需要获取预测值与原始值之间获取残差值:(下灰色图)
将预测模式信息和残差值进行压缩:
对方根据预测模式及残差值和前面的参考宏块,就可以还原为原始数据(有损压缩)!!
帧内压缩的帧类型:
(二)帧间压缩技术
1.GOP:帧间压缩一定是在同一个GOP之内的相邻帧之间进行帧间压缩(差异性小、强相关) 2.参考帧:后面的帧要参考前面的帧进行帧间压缩 3.运动估计:宏块匹配+运动矢量 通过前者去找到后者 4.运动补偿:通过残差值,在解码时进行补偿
参考帧: 在一组GOP中,后面的帧要参考前面的帧(下图中首个图片标红为I帧)
帧与帧之间的区别很小,实际压缩中,只需要存储望远镜的运动矢量。解码时根据原来的基本图像和运动矢量就可以还原该图片!
宏块查找:以下图黄色球所在宏块进行查找(实际会查找所有宏块)。右侧为帧1,左侧为帧2。
通过逐行扫描(实际算法不是这个),查找在下一帧(帧2)中相似度最高的宏块(或者达到阈值);记录新的坐标,同时有原始帧1中的坐标信息。由此获取了距离和方向信息(运动矢量)
宏块查找算法:
运动估计:左侧为上面帧1帧2之间的运动矢量,我们获取一组GOP中所有相邻帧之间的运动矢量,从而获取右侧红色箭头运动轨迹数据
实际解码过程还需要残差值,通过运动矢量(先大致还原)+残差值(进行补偿)
帧间压缩的帧类型:
实际应用中出现问题:
有帧丢失,会导致残差值和运动矢量丢失;如果I帧丢失,后面GOP中数据都无法解析;但这里主要是P、B帧丢失
因此,花屏和卡顿无法兼容....,通过插入I帧来减少卡顿时间,但是I帧是帧内压缩,压缩比没有帧间压缩效率高!!(考虑带宽)
(三)无损压缩
DCT变换,使得数据从分散--->到集中的过程。方便后面进行进一步压缩!
VLC压缩(MPEG2使用):可变长编码。Huffman编码是可变字长编码(VLC)的一种。按频率编码
上面图中使用字符进行编码,实际中使用数据块进行编解码
CABAC压缩(H264使用):上下文适配的二进制算术编码
上图对比了VLC压缩:左侧白色块为原始图像,右侧为压缩后的数据。下面只讨论由此压缩后的数据
右侧为压缩后的数据,越靠近右侧,是越早压缩的数据。所以对比两种方法,发现最右侧压缩都较长,而随着时间推移,CABAC向左侧(后面开始压缩的)压缩数据较少。
因为CABAC由于有上下文的原因,所以后面压缩的数据压缩率非常高
(四)H264编解码流程
ME:运动评估---对每一个宏块进行匹配查找
MC:通过运动评估获取得到运动矢量
T、Q是无损压缩中的DCT和CABAC等转换和压缩算法