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:44 一个Y元素对应一个U元素对应一个V元素,对于1280×720图像,数据量为:1280×720×3---其中3同RGB888一致,表示3个字节(24位)
YUV4:22 对于每一个横行,隔一列有一个UV,数据量为1280×720×1+2×1280×720/2 = 1280×720×2--- ×1表示Y占1字节,/2是对于UV来说是每隔一列才有一个UV
YUV4:20 最标准,最广泛; 隔行分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;
}
View Code
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;
}
View Code

三:H264编码

(一)H264压缩码率

YUV420下的码流大小为:640×480×1.5(RBG为3字节,YUV420为1.5字节)×15= 6912000 bytes = 55.296 mbps(换算为bits)
H264编码而言,建议的码流大小为500kbps,约为YUV420下的1/100!!!

参考码率:https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1video_1_1_video_encoder_configuration.html#a4b090cd0e9f6d98bcf89cb1c4c2066e8

通信实时性要求高,所以码率相比较于直播会低很多,从而实现流畅度增加

(二)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

对于上面这个例子我们作下说明:

  1. 我们实际应该展示的帧的顺序是 I, B, B, P 帧解码后的视频帧。
  2. 但实际上,这些帧到达之后,在缓冲区里就按照第二行的样子存放。为什么会这样呢?这是由于我上面所讲的,P帧参考的是 I帧,B帧是双向参考帧。也就是说,如果 I帧和P帧没有解码的话,B帧是无法进行解码的。基于此,为了解决这个问题就出现了 PTS和DTS两个时间戳。
  3. 第三行是视频帧真正的解码顺序,先解 I帧,然后是P帧,然后是第一个B帧,最后是第二个B帧。
  4. 最终的展示顺序是 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等转换和压缩算法

 

posted @ 2021-04-25 21:18  山上有风景  阅读(2399)  评论(1编辑  收藏  举报