视频通信系统

一、作业分析
作业目的:增强特色模块、 联合各模块形成一个系统,完成完整的实验报告(可以直接包含分报告内容)。
实现思路:在前面的作业中,我实现了
①播放YUV文件
②视频处理mp4文件,对视频文件进行人脸识别
③实时采集视频文件为mp4格式
④对视频文件进行编码,解码,即将yuv文件和h264文件进行相互转换
⑤用rtp协议发送并接收视频文件,实现视频传输。
在本次作业中,将增强特色模块视频处理,将人脸识别和伽马变换结合起来,使视频中人脸被识别的同时改变视频的明暗对比度。并将各模块分别封装成一个函数,在主程序中逐个调用,形成一个系统。过程将在两个vs文件中完成,一个文件负责发送部分,一个文件负责接收部分。在发送部分,我将其分为主要视频采集,视频处理,视频编码,视频发送几个部分。在接收部分,我将其主要分为视频接收,视频解码,视频播放几个部分。在每一部分,将之前作业中实现的功能的单独的模块进行封装,并在此基础上增加一些模块使其可以构成一个完整的系统。
实现环境:ffmpeg,SDL,opencv
二、原理
2.1.视频采集
视频采集(Video Capture)把模拟视频转换成数字视频,并按数字视频文件的格式保存下来。所谓视频采集就是将模拟摄像机、录像机、LD视盘机、电视机输出的视频信号,通过专用的模拟、数字转换设备,转换为二进制数字信息的过程。
cv::VideoWriter::VideoWriter( const String & filename,int apiPreference,int fourcc,double fps,Size frameSize,bool isColor = true )
2.2.视频处理
2.2.1伽马变换进行图像增强
基于幂次变换的Gamma校正[3]是图像处理中一种非常重要的非线性变换,通常Gamma校正长应用于拓展暗调的细节。伽马变换的公式为:

当γ<1时,低灰度区域动态范围扩大,进而图像对比度增强,高灰度值区域动态范围减小,图像对比度降低,图像整体灰度值增大,此时与图像的对数变换类似。
γ>1时,低灰度区域的动态范围减小进而对比度降低,高灰度区域动态范围扩大,图像的对比度提升,图像的整体灰度值变小,Gamma校正主要应用在图像增强。
2.2.2人脸识别
简单的人脸检测程序可以直接基于opencv的函数库进行实现。Haar特征分类器就是一个XML文件,是opencv官方训练好的检测器,可以直接调用,存放在opencv的安装目录下。
void detectMultiScale( const Mat& image, CV_OUT vector& objects, double scaleFactor = 1.1, int minNeighbors = 3, int flags = 0, Size minSize = Size(),Size maxSize = Size() );
人脸识别流程如下:
读取图片→加载分类器→检测→标记–在脸部画圆[8]
2.3.视频编码
将一个YUV文件编码为H264文件和H265文件。
关于编码流程图片,来源于网上

2.4.视频传输
2.4.1RTP(实时传输协议),顾名思义它是用来提供实时传输的,因而可以看成是传输层的一个子层。RTP报文由两部分组成:报头和有效载荷。
RTP会话过程:
当应用程序建立一个RTP会话时,应用程序将确定一对目的传输地址。目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给RTP包,一个给RTCP包,使得RTP/RTCP数据能够正确发送。RTP数据发向偶数的UDP端口,而对应的控制信号RTCP数据发向相邻的奇数UDP端口(偶数的UDP端口+1),这样就构成一个UDP端口对。 RTP的发送过程如下,接收过程则相反。
1)RTP协议从上层接收流媒体信息码流(如H.263),封装成RTP数据包;
2)RTCP从上层接收控制信息,封装成RTCP控制包。
2)RTP将RTP 数据包发往UDP端口对中偶数端口;RTCP将RTCP控制包发往UDP端口对中的接收端口。
2.4.2.Socket传输
服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,接收到一个客户端时,服务器显示该客户端的IP地址,并将字串返回给客户端。
客户端的工作流程:首先调用socket函数创建一个Socket,填写服务器地址及端口号,从标准输入设备中取得字符串,将字符串传送给服务器端,并接收服务器端返回的字符串。最后关闭该socket。
2.5解码
将一个H264文件和H265文件解码为YUV文件关于解码,来源于网上2.6.播放
SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。
YUV模型是根据一个亮度(Y分量)和两个色度(UV分量)来定义颜色空间,常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等,其中比较常见的YUV420分为两种:YUV420P和YUV420SP。
我们最常见的YUV420P和YUV420SP都是基于4:2:0采样的,所以如果图片的宽为width,高为heigth,在内存中占的空间为width * height * 3 / 2,其中前width * height的空间存放Y分量,接着width * height / 4存放U分量,最后width * height / 4存放V分量。
YUV 4:4:4采样,每一个Y对应一组UV分量。
YUV 4:2:2采样,每两个Y共用一组UV分量。 (相比444格式,宽度减半,高度不变)
YUV 4:2:0采样,每四个Y共用一组UV分量。 (相比444格式,宽度减半,高度减半)
三、具体实现及程序设计
3.1具体实现过程如下图所示:
在增强模块过程中,由于之前在做的时候,做了视频人脸识别和伽马变换,现在将这两个功能联合到一起。在将其联合到一起的过程中,需要改变输入输出的参数,使其输入输出的格式相匹配。
在实现过程中,之前的采集视频仅仅将视频采集并未保存,需要增加保存采集到的视频部分。视频处理之后的文件格式为mp4,而之前编码的程序将yuv编码为h264,于是决定增加mp4转为yuv的转码部分。这样发送端的所有程序准备就绪。由于之前的RTP接收是在VCL完成,但是要形成一个完整的通信系统,RTP解码是必不可少的,于是增加了RTP解码的模块,原理和RTP协议编码类似。设置socket端口号一致,使其可以相互连接。接收到文件之后,需要将接流程图
收到的h264文件转换为yuv文件,再利用第一次作业的播放Yuv文件进行播放

3.2视频采集
①设置需要录制的时间
我们设置要保存的视频的帧率为25,每秒25帧,即将fps的值设置为25。由此可以算出时间t需要保存的视频的帧为25*t。我们可以根据不同的需求来调整帧率。例如在电影中帧率为24,在电视中设置为25。
②设置保存的路径
③指定一个编码器
④读取要保存文件的尺寸
Size(int width, int height),Size在使用的时候,默认是先宽后高,即先列后行。

⑤将得到的信息带入函数中

⑥将摄像头读取的每一帧循环保存,在指定位置生成视频

⑦释放空间
⑧读取存储位置的视频文件,播放录制的视频,检验是否成功录制。
3.3视频处理
读取图片→加载分类器→检测→标记–在脸部画圆[8]

3.4视频编码
①我们可以通过设置不同的编码器从而将视频编码为不同的格式,以下,我们将YUN视频分别编码为H264和H265。当TEST_HEVC等于1的时候,设置其编码器为H265的编码器,反之,为H264的编码器。在最后一次作业中,用到了编码为H264。

②我们需要注册FFmpeg所有的编码器,然后查找编码器,做出判断是否查找编码器成功。

③在编码之前,我们需要申请AVCodecContext空间,需要传递一个编码器。

④打开编码器

⑤pFrame用来存储一帧未编码的数据,得到指定的长宽高来分析图像内存。

⑥用fread函数读取YUN原始数据

⑦编码一帧视频,将AVFrame(存储YUV像素数据)编码为AVPacket(存储H.264等格式的码流数据),用avcodec_encode_video2函数进行一帧视频的编码存储到pkt中,再用fwrite函数将编码的结果写入到指定地址中。然后循环此操作进行编码。

⑧写入可能存在的剩余帧信息

⑨释放内存,关闭编码器。

3.5 RTP协议传输

①建立连接
在建立连接时,我们设置地址为电脑的回传地址127.0.0.1,然后设置与VCL链接定端口号。我们设置sin_family为AF_INIT,表示套接字使用的协议簇为TCP/IP – IPv4。设置套接字类型为SOCK_DGRAM,表示我们将要传输的是UDP数据报。

②将H264封装为RTP
当一个NALU小于1400字节的时候,采用一个单RTP包发送.
当一个NALU大于1400字节的时候,得到该nalu需要用多少长度为1400字节的RTP包来发送。我们将采用原理中提到的分片分包模式。我们先添加rtp header包头,然后依次添加fu indicater 和fu header。其中rtp header占12个字节,fu indicater 和fu header分别占1个字节。发送每一个分片的流程相似。
同一个NALU分包厚的FU indicator头是完全一致的,FU header只有S以及E位有区别,分别标记开始和结束,它们的RTP分包的序列号依次递增,时间戳一致,负载数据为NALU包去掉1个字节的NALU头后对剩余数据的拆分, NALU头被拆分成了FU indicator和FU header,不再需要1字节的NALU头。
设置 rtp header,设置发送的数据格式为H264

设置fu indicater

设置fu header

③发送rtp包
关于send函数:第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数,获得sendbuf的长度,为nalu的长度1400加上rtp_header,fu_ind,fu_hdr的固定长度14字节,对于一个单RTP包发送方式为12个字节。

RTP解包是封包的逆过程。
3.6 视频解码
①读取输入文件

②解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据

③解码一帧数据

④将数据写入输出地址中

3.7 YUV播放
如何用SDL播放视频

初始化→创建窗口→创建渲染器→创建纹理→设置纹理的数据→纹理复制给渲染目标→显示。
在本次实践中,我们只需要对YUN420进行播放
①初始化

②创建窗口

③创建渲染器

④创建纹理

⑤打开文件

⑥创建一个线程

⑦读取文件

⑧设置纹理的像素数据

⑨清空渲染目标,纹理复制给渲染目标

⑩显示

四、结果
在运行的时候,配置接收端和发送端的参数,IP地址,端口地址等,使其一致,运行接收的文件,保持接收的状态。然后运行发送端的程序,进行视频采集,处理,转码,编码,发送。接收端接收到文件进行解码,播放。
1.发送端:
在调用函数时,每一个头文件中的cpp文件需要有一个.h文件。collect为采集摄像头信息的函数,encoding为编码的函数,mp42yuv为将mp4文件转码成yuv文件的函数,processing为视频处理的函数,RTPsend为用RTP协议发送的函数。在主函数work6中调用。

2.接收端
broadcast为播放的函数,decode为解码的函数,receive为RTP接收的函数。Work7为主函数,在主函数中调用其他的函数。

3.发送进行图片处理,右边是采集到的视频最后一帧,左边是经过人脸识别和伽马变换之后的视频帧,可以看到其明暗对比度发生了变化。经过伽马变化之后右边的图片看起来更加有立体感。并且完美识别出了人脸。

4.解码播放的结果
这是解码播放的结果,肉眼可见与原视频没有很大的区别。也将处理后的视频传达到了接收端。

5.对比文件大小
比较发送与接收到的h264文件大小,我们发现,在过程中由于使用的是电脑的内部环流地址,所以没有丢包出现,均为40kb。在解码部分略有失真。

6.运行结果界面
运行接收程序,接收端一直保持接收状态,直到接收到RTP包。

运行发送程序,以下每一张图均为每一个小模块之间相联系的部分

接收到数据

五、遇到问题及解决方案
1.由于是每一次的模块进行封装,出现了在执行完上一个程序之后没有跳出程序导致后面的程序没有被执行的情况。在这种情况下,我设置了等待时间,如果在视频处理之后以及接收RTP包之后几秒之后没有新的帧接收与处理,则跳出程序。
2.两个程序地址设置错误。由于电脑有固定的回传地址,在设置错误之后找不到问题因此耗费了很多的时间。


版权声明:本文为qq_57221504原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。