Python使用UDP实现720p视频传输的操作


Posted in Python onApril 24, 2021

1. 项目背景

视频传输: 在一台电脑上播放视频(捕捉摄像头画面),同局域网内另一台电脑上实时播放,尽量不卡顿。

先放最后的照片,和用gif展示一下视频效果。

Python使用UDP实现720p视频传输的操作

Python使用UDP实现720p视频传输的操作

传输视频可以采取图片或者流的形式,本文采取传输图片的形式,在1s之内显示多张图片从而形成连续的视频画面。

经费有限,所有实验均基于笔记本电脑。

使用的视频源是本机摄像头,以及进击的巨人720p资源。

2. 解决方案

1. 使用Python的Socket,使用opencv捕捉摄像头/视频的画面。

2. 原始的图片很大(720p的大小是1920*1080*3),整图就算压缩成jpg格式其大小也非常大。而UDP最大只能传输65535字节大小的数据区,故对图片进行分块,分块过后的数据压缩成jpg格式,并对图片分块数据进行编号。

3. 实验检测表明,本文实验环境发送端不需要使用发送队列,基本上新生成的帧很快就能被socket传输掉。

4. 接收端使用多线程接收,每个线程是一个socket,接收过后的数据存储于数据片池。

5. 接收端另开一个线程,用于反复从数据片池 读取数据片,根据数据片的编号更新幕布,这里幕布是专门用于图像显示的一个数组,其维度是720p(1920*1080*3)。更新过后的结果暂存于图片池

6. 主线程反复从图片池读取图片,并显示。

3. 实现细节

3.1 TCP/UDP的选择

为了实现低延迟,毫无疑问选取无连接的UDP传输。

3.2 图片分片算法

这里其实也谈不上什么算法,就是将图片水平分割。这种做法的好处在于,分割后图片的编号可以和区域一一对应。本文没有探索更为复杂的图片分片算法。

Python使用UDP实现720p视频传输的操作

经过处理,图片变为一个个分片,如下:

Python使用UDP实现720p视频传输的操作

Python使用UDP实现720p视频传输的操作

Python使用UDP实现720p视频传输的操作

Python使用UDP实现720p视频传输的操作

对上述图片进行编号,很显然可以编号0,1,2,3,对于任意分块(例如2)在图像数组中对应的区域是frame[2*piece_size:(2+1)*piece_size],其中piece_size表示一片数据的大小。

这种对应关系方便解压后的图像还原操作。

3.3 JPG压缩

这其实是个很小的技术点,因为使用的压缩算法都是现成的。但是值得一提的是,JPG的压缩率是真的高,在实验数据上实现了10-20倍的压缩率。

使用了多线程压缩,压缩完过后,更新对应的桶,这里的桶实际上就是数据片。

Python使用UDP实现720p视频传输的操作

由主线程Main Thread反复从桶里取数据片(t1),每取1片发送一次,然后再取下一片(t2),直到所有桶都被取了一次(例子中有10片)。

至此,一张图片的分片数据被全部取完,于是开始统计一些FPS相关信息。

3.4 接收队列

接收端开了10个线程用于异步socket接收数据片。

为了保证接收端产生丝滑的视频效果,使用接收队列是个不错的选择。本文使用了2个队列的设计。实现数据接收的二级缓冲。示意图如下:

Python使用UDP实现720p视频传输的操作

这样一来,视频效果明显丝滑了很多。

4. 遇到的坑及解决办法

4.1. Windows防火墙

巨坑,最好都关了。

Python使用UDP实现720p视频传输的操作

4.2. 路由器网络频段

同一台路由器的5G和2.4G频段有时候不能互相ping通,要确保两个电脑连接在同一频段上。

4.3. Wifi配置

如果上述设置都对了,但是还是ping不通。将wifi连接设置成专用网络,也许就能解决问题。

Python使用UDP实现720p视频传输的操作

4.4. 硬件瓶颈

个人PC的性能是较大瓶颈,尤其是单机测验的时候(本地两个终端,一个发送、一个接收),CPU使用率分分钟到100%。听某个技术大哥说要使用GPU压缩。

Python使用UDP实现720p视频传输的操作

用两台电脑,一台接收一台发送之后,效果要好很多。

4.5. OpenCV读取摄像头大坑

由于摄像头驱动的关系,在我的电脑上需要设置以下两个变量,才能成功启用外置的720p摄像头。

os.environ["OPENCV_VIDEOIO_DEBUG"] = "1"
	os.environ["OPENCV_VIDEOIO_PRIORITY_MSMF"] = "0"

即使如此,如果不做额外的设置,读出来的图片将是480p的(看起来很像是720p被压缩过后的)。所以如果要传输真·720p,还需要设置读出的图像大小,如下:

self.stream = cv2.VideoCapture(1) # 读取第一个外置摄像头
	self.stream.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)   # float
	self.stream.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)   # float

4.6. Socket卡顿

不知道是不是我写的有问题,感觉多线程的socket会争抢资源(发送和接收的线程间,对应5.1节功能),造成接收端的画面显示将变得卡顿。

5. 尚未Bug Free的功能

5.1 使用TCP回传帧率信息

为了计算网络时延,采取类似伽利略测光速的方法。从数据包打包之前,到对方收到数据包之后,再将这个数据回传到发送方。

这样就不存在两台机器时间差校准的问题。

该算法的大致流程如下图所示。

Python使用UDP实现720p视频传输的操作

Python使用UDP实现720p视频传输的操作

这种计算方式应该是自己的实验环境下比较准确的方法了。

时延信息的反馈不需要特别快(比如200-500ms发送一次),所以使用TCP技术

其实TCP和UDP在使用Python编程的时候代码差距可以说极小…

但是!!!

自己目前在实现信息回传的时候,会莫名卡顿起来。

接收端建立回传的socket之后,甚至还没传输数据,整个程序运行起来就变得非常卡顿,这个让我比较苦恼,目前正在找bug.

5.2 拥塞控制 (流量控制)的算法

这部分的思想是流量控制,感谢评论区指正。

5.1节如果一并回传接收端队列状态信息。如果接收端队列太满,说明来不及处理视频帧了,从而对发送端的发送速度进行控制,才是“拥塞控制”

这个本来是想着和5.1综合起来用的,已经写好了,但是还没能真正展现价值,设计是否合理也值得商榷。

控制的是发送端的发送频率,从而实现接收端的流畅播放

思想和TCP的拥塞控制一样慢增长,快下降。如果接收端的队列一直处于较空的状态,则表明还有一定的性能剩余,此时可以缓慢加快发送的频率;如果检测到接收端队列中数据较多,表明发送速度太快来不及显示,这时候就大幅下降发送的频率。

这个拥塞控制的算法基于几个假设:

1.网络情况良好,丢包率比较低;

2接收端电脑的性能足够高,来得及处理解包、显示图像。

如果5.1能够正确实现,则应该根据网络时延Python使用UDP实现720p视频传输的操作的大小来控制发送的频率。

6. 总结

这个项目是一周的时间内完成的,目前还有点bug。小组内的成员分别在不同技术方向上进行了探索,收获都还挺大的。这篇博客就当一个项目总结吧,写的难免有纰漏之处。

github地址:https://github.com/820fans/UDP-Video-Transfer

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Python 相关文章推荐
Python中django学习心得
Dec 06 Python
轻松实现TensorFlow微信跳一跳的AI
Jan 05 Python
使用python编写简单的小程序编译成exe跑在win10上
Jan 15 Python
Python OpenCV 直方图的计算与显示的方法示例
Feb 08 Python
python主线程捕获子线程的方法
Jun 17 Python
Sanic框架安装与简单入门示例
Jul 16 Python
python中如何使用分步式进程计算详解
Mar 22 Python
为什么从Python 3.6开始字典有序并效率更高
Jul 15 Python
wxPython修改文本框颜色过程解析
Feb 14 Python
Django 解决上传文件时,request.FILES为空的问题
May 20 Python
pytorch下的unsqueeze和squeeze的用法说明
Feb 06 Python
如何理解python接口自动化之logging日志模块
Jun 15 Python
python通配符之glob模块的使用详解
Apr 24 #Python
Django debug为True时,css加载失败的解决方案
Apr 24 #Python
python 模块重载的五种方法
Apr 24 #Python
写一个Python脚本自动爬取Bilibili小视频
python实现图片批量压缩
Apr 24 #Python
如何用python绘制雷达图
两行代码解决Jupyter Notebook中文不能显示的问题
You might like
精美漂亮的php分页类代码
2013/04/02 PHP
浅谈PHP解析URL函数parse_url和parse_str
2014/11/11 PHP
详解PHP序列化反序列化的方法
2015/10/27 PHP
纯php生成随机密码
2015/10/30 PHP
利用PHP自动生成印有用户信息的名片
2016/08/01 PHP
PHP入门教程之会话控制技巧(cookie与session)
2016/09/11 PHP
PHP 的Opcache加速的使用方法
2017/12/29 PHP
[原创]后缀就扩展名为js的文件是什么文件
2007/12/06 Javascript
深入认识javascript中的eval函数
2009/11/02 Javascript
通过下拉框的值来确定输入框是否可以为空的代码
2011/10/18 Javascript
实用框架(iframe)操作代码
2014/10/23 Javascript
js实现的万能flv网页播放器代码
2016/04/30 Javascript
Laydate时间组件在火狐浏览器下有多时间输入框时只能给第一个输入框赋值的解决方法
2016/08/18 Javascript
javascript中setAttribute兼容性用法分析
2016/12/12 Javascript
bootstrap如何让dropdown menu按钮式下拉框长度一致
2017/04/10 Javascript
基于Vue实例对象的数据选项
2017/08/09 Javascript
NodeJS爬虫实例之糗事百科
2017/12/14 NodeJs
JavaScript使用indexOf()实现数组去重的方法分析
2018/09/04 Javascript
vue实现的树形结构加多选框示例
2019/02/02 Javascript
利用angular自动编译andriod APK的绕坑经历分享
2019/03/08 Javascript
Egg Vue SSR 服务端渲染数据请求与asyncData
2019/11/24 Javascript
详解python中的文件与目录操作
2017/07/11 Python
Python列表推导式与生成器用法分析
2018/08/02 Python
Python代码实现删除一个list里面重复元素的方法
2019/04/02 Python
Python进程,多进程,获取进程id,给子进程传递参数操作示例
2019/10/11 Python
keras得到每层的系数方式
2020/06/15 Python
深深扎根运动世界的生活品牌:Tillys
2017/10/30 全球购物
英国一家集合了众多有才华设计师品牌的奢侈店:Wolf & Badger
2018/04/18 全球购物
关于母亲节的感言
2014/02/04 职场文书
同学会主持词
2014/03/18 职场文书
白岩松演讲
2014/05/21 职场文书
会计专业求职信
2014/08/10 职场文书
刮痧观后感
2015/06/05 职场文书
2015年社区消防安全工作总结
2015/10/14 职场文书
护士年终工作总结不会写?各科护士模板总结
2020/01/02 职场文书
Redis 操作多个数据库的配置的方法实现
2022/03/23 Redis