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 Trie树实现字典排序
Mar 28 Python
11个并不被常用但对开发非常有帮助的Python库
Mar 31 Python
给Python的Django框架下搭建的BLOG添加RSS功能的教程
Apr 08 Python
Python Sql数据库增删改查操作简单封装
Apr 18 Python
Python Sqlite3以字典形式返回查询结果的实现方法
Oct 03 Python
python利用OpenCV2实现人脸检测
Apr 16 Python
Python命令行参数解析工具 docopt 安装和应用过程详解
Sep 26 Python
python实现while循环打印星星的四种形状
Nov 23 Python
Python抓包程序mitmproxy安装和使用过程图解
Mar 02 Python
pycharm部署、配置anaconda环境的教程
Mar 24 Python
学python需要去培训机构吗
Jul 01 Python
Flask处理Web表单的实现方法
Jan 31 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
Yii中实现处理前后台登录的新方法
2015/12/28 PHP
使用phpexcel类实现excel导入mysql数据库功能(实例代码)
2016/05/12 PHP
PHP反射学习入门示例
2019/06/14 PHP
JavaScript 精粹读书笔记(1,2)
2010/02/07 Javascript
Jquery 改变radio/checkbox选中状态,获取选中的值(示例代码)
2013/12/12 Javascript
jquery中map函数遍历数组用法实例
2015/05/18 Javascript
jQuery横向擦除焦点图特效代码分享
2015/09/06 Javascript
微信小程序 wxapp内容组件 icon详细介绍
2016/10/31 Javascript
JS日程管理插件FullCalendar简单实例
2017/02/07 Javascript
ES6新特性:使用export和import实现模块化详解
2017/07/31 Javascript
JS声明对象时属性名加引号与不加引号的问题及解决方法
2018/02/16 Javascript
微信小程序自定义组件封装及父子间组件传值的方法
2018/08/28 Javascript
vue data恢复初始化数据的实现方法
2019/10/31 Javascript
JavaScript 面向对象程序设计详解【类的创建、实例对象、构造函数、原型等】
2020/05/12 Javascript
闭包在python中的应用之translate和maketrans用法详解
2014/08/27 Python
python实现比较两段文本不同之处的方法
2015/05/30 Python
详解Django中的权限和组以及消息
2015/07/23 Python
Python算法应用实战之队列详解
2017/02/04 Python
详谈python http长连接客户端
2017/06/12 Python
用Python编写一个简单的CS架构后门的方法
2018/11/20 Python
利用Python模拟登录pastebin.com的实现方法
2019/07/12 Python
Django 项目重命名的实现步骤解析
2019/08/14 Python
python 并发下载器实现方法示例
2019/11/22 Python
python实现飞机大战项目
2020/03/11 Python
python3.x中安装web.py步骤方法
2020/06/23 Python
利用scikitlearn画ROC曲线实例
2020/07/02 Python
如何用Matlab和Python读取Netcdf文件
2021/02/19 Python
10分钟理解CSS3 Grid布局
2018/12/20 HTML / CSS
CSS3 毛玻璃效果
2019/08/14 HTML / CSS
英国奢侈皮具品牌:Aspinal of London
2018/09/02 全球购物
成教自我鉴定
2013/10/27 职场文书
股东授权委托书范本
2014/09/13 职场文书
自我推荐信怎么写
2015/03/24 职场文书
初任公务员培训心得体会
2016/01/08 职场文书
2016年大学生社区服务活动总结
2016/04/06 职场文书
fastdfs+nginx集群搭建的实现
2021/03/31 Servers