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网络编程学习笔记(一)
Jun 09 Python
python实现的简单文本类游戏实例
Apr 28 Python
深入解析Python中的变量和赋值运算符
Oct 12 Python
python下载文件记录黑名单的实现代码
Oct 24 Python
Python数据结构与算法之列表(链表,linked list)简单实现
Oct 30 Python
谈谈python中GUI的选择
Mar 01 Python
Python面向对象程序设计之继承与多继承用法分析
Jul 13 Python
python抓取网页内容并进行语音播报的方法
Dec 24 Python
对pyqt5之menu和action的使用详解
Jun 20 Python
详解使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件
Aug 23 Python
python字典和json.dumps()的遇到的坑分析
Mar 11 Python
Python 使用Opencv实现目标检测与识别的示例代码
Sep 08 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简单对象与数组的转换函数代码(php多层数组和对象的转换)
2011/05/18 PHP
phpexcel导出excel的颜色和网页中的颜色显示不一致
2012/12/11 PHP
Smarty foreach控制循环次数的实现详解
2013/07/03 PHP
php+mysqli预处理技术实现添加、修改及删除多条数据的方法
2015/01/30 PHP
PHP版QQ互联OAuth示例代码分享
2015/07/05 PHP
深入理解Yii2.0乐观锁与悲观锁的原理与使用
2017/07/26 PHP
可以将word转成html的js代码
2010/04/11 Javascript
firefox下jquery ajax返回object XMLDocument处理方法
2014/01/26 Javascript
更高效的使用JQuery 这里总结了8个小技巧
2016/04/13 Javascript
JQuery DIV 动态隐藏和显示的方法
2016/06/23 Javascript
jQuery仿写百度百科的目录树
2017/01/03 Javascript
原生js实现淘宝购物车功能
2020/06/23 Javascript
Javascript之图片的延迟加载的实例详解
2017/07/24 Javascript
JS实现不用中间变量temp 实现两个变量值得交换方法
2018/02/04 Javascript
vue兄弟组件传递数据的实例
2018/09/06 Javascript
vue实现密码显示与隐藏按钮的自定义组件功能
2019/04/23 Javascript
layui实现checkbox的目录树tree的例子
2019/09/12 Javascript
使用vue-router切换页面时实现设置过渡动画
2019/10/31 Javascript
Python中第三方库Requests库的高级用法详解
2017/03/12 Python
python3.6连接MySQL和表的创建与删除实例代码
2017/12/28 Python
python:接口间数据传递与调用方法
2018/12/17 Python
python实现函数极小值
2019/07/10 Python
pytorch梯度剪裁方式
2020/02/04 Python
Python range与enumerate函数区别解析
2020/02/28 Python
在django admin中配置搜索域是一个外键时的处理方法
2020/05/20 Python
Pycharm2020.1安装中文语言插件的详细教程(不需要汉化)
2020/08/07 Python
Python在后台自动解压各种压缩文件的实现方法
2020/11/10 Python
使用CSS3的rem属性制作响应式页面布局的要点解析
2016/05/24 HTML / CSS
从一次项目重构说起CSS3自定义变量在项目的使用方法
2021/03/01 HTML / CSS
法学专业个人求职信
2013/09/26 职场文书
股指期货心得体会
2014/09/13 职场文书
中级会计大学生职业生涯规划书
2014/09/16 职场文书
法英专业大学生职业生涯规划范文:衡外情,量己力!
2014/09/23 职场文书
2015年法院工作总结范文
2015/04/28 职场文书
行政后勤人员工作计划应该怎么写?
2019/08/16 职场文书
Python+uiautomator2实现自动刷抖音视频功能
2021/04/29 Python