• 引言
    • 1. 内容简介
    • 2. 实验知识点
    • 3.实验环境
  • 实验步骤
    • 环境搭建
    • 代码实现
    • 加入音频的捕获和传输
    • 编写程序入口 main.py
  • 总结

引言

做了下实验楼的关于Python 实现局域网视频聊天工具 ,感觉还不错,作为练习opencv和socket的小脚本入门了。

1. 内容简介

  • 本实验实现简易的视频通信工具
  • 在视频通信的基础上加入语音
  • 用户可以选择通信的质量,即画质、停顿等参数
  • 支持IPv6

2. 实验知识点

本课程项目完成过程中将学习:

  • Python 基于 OpenCV 对摄像头信息的捕获和压缩
  • Python 关于 线程 和 socket 通信的一些基础技巧
  • Python 基于 PyAudio 对语音信息的捕获和压缩

其中将重点介绍 socket 传输过程中对数据的压缩和处理。

3.实验环境

  • python 3.5
  • opencv-python 3.4.1.15
  • numpy 1.14.5
  • PyAudio 0.2.11

实验步骤

环境搭建

首先关于实验所需要用到的包,opencv-python和PyAudio并不是太好装,我简单讲一下我的安装步骤。

因为我自己电脑Windows下的是anaconda,关于opencv的python第三方包如果是在conda环境下查找,会发现找不到,那么使用pip安装同样会出现四次retry,然后安装失败。所以建议最好创建一个python的虚拟环境,便于直接install,而不需要找什么本地whl文件,或者换源等等,比较麻烦。

另外就是PyAudio==0.2.11,这个包是已经很久没有更新过了,但这并不意味着它适配不了新的python环境,已经编译不了了。它的安装百度到的答案,或者说pypi推荐的安装方式都是pip install PyAudio == 0.2.11,但就最新的python3.6到3.7版本来讲,我是没有安装成功的,原因应该都是报这个错吧:

 error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio":


于是继续百度,发现推荐的都是去找PyAudio-0.2.11-cp36-cp36m-win_amd64.whl文件,然后我也去找了,但可能是我python是3.7的原因,我并没有安装成功。报错为:

>>> pip install D:\Downloads\PyAudio-0.2.11-cp36-cp36m-win_amd64.whlERROR: PyAudio-0.2.11-cp36-cp36m-win_amd64.whl is not a supported wheel on this platform.

这里卡了很久,再次发现国内博客都是抄来抄去的,中间还有其它事情,直到第二天搭梯子谷歌了一下,终于找到了答案:

pip install pipwin   # 安装当前pip环境下的所有必要依赖的包pipwin install pyaudio # 成功安装


当然还有一种方式,是去GitHub将包的源码拷贝到本地重命名,不过我不喜欢这么做。

https://github.com/intxcc/pyaudio_portaudio/releases

代码实现

先为双方的通信设计 Server 类和 Client类,两个类均继承 threading.Thread并实现双向的C / S连接,只需要分别实现 init、__del__和run方法,之后对象调用.start()方法即可在独立线程中执行run方法中的内容。首先Client类需要存储远端的IP地址和端口,而Server类需要存储本地服务器监听的端口号。用户还应当可以指定通信双方使用的协议版本,即基于IPv4 还是IPv6 的TCP连接。因此Server类的初始化需要传入两个参数(端口、版本),Client类的初始化需要三个参数(远端IP、端口、版本)。新建文件vchat.py,在其中定义基础的两个类,另外捕获视频流的任务应当由Client类完成,一起完善为:

from socket import *
import threading
class Video_Server(threading.Thread):def __init__(self, port, version) :threading.Thread.__init__(self)self.setDaemon(True) # 设置守护线程,必须在t.start()之前设置self.ADDR = ('', port)if version == 4: # ipv4版本,client要与之对应self.sock = socket(AF_INET ,SOCK_STREAM)    # 创建TCP连接,ipv4版本else:self.sock = socket(AF_INET6 ,SOCK_STREAM)  # 创建TCP连接,ipv6版本def __del__(self):self.sock.close()# TODOdef run(self):print("server starts...")self.sock.bind(self.ADDR)self.sock.listen(1)       # 操作系统可以挂起的最大连接数,如果同一时间的连接数超过1,拒绝其他的连接conn, addr = self.sock.accept()    # 接收客户端的请求,且获取新socket对象和客户端信息print("remote client success connected...")# TODOclass Video_Client(threading.Thread):def __init__(self ,ip, port, version):threading.Thread.__init__(self)self.setDaemon(True)self.ADDR = (ip, port)if version == 4:self.sock = socket(AF_INET, SOCK_STREAM)else:self.sock = socket(AF_INET6, SOCK_STREAM)self.cap = cv2.VideoCapture(0)   # 创建一个成员变量capdef __del__(self) :self.sock.close()self.cap.release()def run(self):print("client starts...")while True:try:self.sock.connect(self.ADDR)breakexcept:time.sleep(3)continueprint("client connected...")while self.cap.isOpened():    # 如果连接上了,进入循环读相应的文件,这里也可以try一下,没有连接上接收异常,这样的语法不好排错ret, frame = self.cap.read() # 读

已经捕获到数据,接下来要发送字节流。首先我们继续编写Client,为其添加发送数据功能的实现。这里只改动了run方法。在捕获到帧后,我们使用pickle.dumps方法对其打包,并用sock.sendall方法发送。注意发送过程中我们用struct.pack方法为每批数据加了一个头,用于接收方确认接受数据的长度。

    def run(self):while True:try:self.sock.connect(self.ADDR)breakexcept:time.sleep(3)continueprint("client connected...")while self.cap.isOpened():ret, frame = self.cap.read()  data = pickle.dumps(frame)try:"""将len(data)等参数的值进行一层包装,包装的方法由fmt指定。被包装的参数必须严格符合fmt。最后返回一个包装后的字符串。"""self.sock.sendall(struct.pack("L", len(data)) + data)except:break

下面编写Server,在服务器端连接成功后,应当创建一个窗口用于显示接收到的视频。因为连接不一定创建成功,因此cv.destroyAllWindows()被放在一个try…catch块中防止出现错误。在接收数据过程中,我们使用payload_size记录当前从缓冲区读入的数据长度,这个长度通过struct.calcsize(‘L’)来读取。使用该变量的意义在于缓冲区中读出的数据可能不足一个帧,也可能由多个帧构成。为了准确提取每一帧,我们用payload_size区分帧的边界。在从缓冲区读出的数据流长度超过payload_size时,剩余部分和下一次读出的数据流合并,不足payload_size时将合并下一次读取的数据流到当前帧中。在接收完完整的一帧后,显示在创建的窗口中。同时我们为窗口创建一个键盘响应,当按下Esc 或 q键时退出程序。

另外当用户指定使用低画质通信,我们应当对原始数据做变换,最简单的方式即将捕获的每一帧按比例缩放,同时降低传输的帧速,在代码中体现为resize,该函数的第二个参数为缩放中心,后两个参数为缩放比例,并且根据用户指定的等级,不再传输捕获的每一帧,而是间隔几帧传输一帧。为了防止用户指定的画质过差,代码中限制了最坏情况下的缩放比例为0.3,最大帧间隔为3。此外,我们在发送每一帧的数据前使用zlib.compress对其压缩,尽量降低带宽负担。

所以完整代码为:

class Video_Server(threading.Thread):def __init__(self, port, version) :threading.Thread.__init__(self)self.setDaemon(True)self.ADDR = ('', port)if version == 4:self.sock = socket(AF_INET ,SOCK_STREAM)else:self.sock = socket(AF_INET6 ,SOCK_STREAM)def __del__(self):self.sock.close()try:cv2.destroyAllWindows()except:passdef run(self):print("VIDEO server starts...")self.sock.bind(self.ADDR)self.sock.listen(1)conn, addr = self.sock.accept()print("remote VIDEO client success connected...")data = "".encode("utf-8")payload_size = struct.calcsize("L")        # 结果为4cv2.namedWindow('Remote', cv2.WINDOW_NORMAL)while True:while len(data) < payload_size:data += conn.recv(81920)packed_size = data[:payload_size]data = data[payload_size:]msg_size = struct.unpack("L", packed_size)[0]while len(data) < msg_size:data += conn.recv(81920)zframe_data = data[:msg_size]data = data[msg_size:]frame_data = zlib.decompress(zframe_data)frame = pickle.loads(frame_data)cv2.imshow('Remote', frame)if cv2.waitKey(1) & 0xFF == 27:breakclass Video_Client(threading.Thread):def __init__(self ,ip, port, level, version):threading.Thread.__init__(self)self.setDaemon(True)self.ADDR = (ip, port)if level <= 3:self.interval = levelelse:self.interval = 3self.fx = 1 / (self.interval + 1)if self.fx < 0.3:   # 限制最大帧间隔为3帧self.fx = 0.3if version == 4:self.sock = socket(AF_INET, SOCK_STREAM)else:self.sock = socket(AF_INET6, SOCK_STREAM)self.cap = cv2.VideoCapture(0)def __del__(self) :self.sock.close()self.cap.release()def run(self):print("VIDEO client starts...")while True:try:self.sock.connect(self.ADDR)breakexcept:time.sleep(3)continueprint("VIDEO client connected...")while self.cap.isOpened():ret, frame = self.cap.read()sframe = cv2.resize(frame, (0,0), fx=self.fx, fy=self.fx)data = pickle.dumps(sframe)zdata = zlib.compress(data, zlib.Z_BEST_COMPRESSION)try:self.sock.sendall(struct.pack("L", len(zdata)) + zdata)except:breakfor i in range(self.interval):self.cap.read()

里面有些代码上面的大段应该解释得比较清楚,如果还有不明白的,可以看作者的推荐,还有我找到的一些资料链接:

struct.pack()、struct.unpack()和struct.calcsize()

Python 网络编程

加入音频的捕获和传输

在完成视频通信的基础上,整体框架对于音频通信可以直接挪用,只需要修改其中捕获视频/音频的代码和服务器解码播放的部分。这里我们使用 PyAudio 库处理音频,在 Linux 下你也可以选择 sounddevice。关于sounddevice这里不做过多介绍,可以在这里看到它最新版本的文档。将vchat.py复制一份,重命名为achat.py,简单修改几处,最终音频捕获、传输的完整代码如下。将上面代码中的Server和Client分别加上Video和Audio前缀以区分,同时显示给用户的print输出语句也做了一定修改,对于视频加上VIDEO前缀,音频加上AUDIO前缀。

from socket import *
import threading
import pyaudio
import wave
import sys
import zlib
import struct
import pickle
import time
import numpy as npCHUNK = 1024
FORMAT = pyaudio.paInt16    # 格式
CHANNELS = 2    # 输入/输出通道数
RATE = 44100    # 音频数据的采样频率
RECORD_SECONDS = 0.5    # 记录秒class Audio_Server(threading.Thread):def __init__(self, port, version) :threading.Thread.__init__(self)self.setDaemon(True)self.ADDR = ('', port)if version == 4:self.sock = socket(AF_INET ,SOCK_STREAM)else:self.sock = socket(AF_INET6 ,SOCK_STREAM)self.p = pyaudio.PyAudio()  # 实例化PyAudio,并于下面设置portaudio参数self.stream = Nonedef __del__(self):self.sock.close()   # 关闭套接字if self.stream is not None:self.stream.stop_stream()   # 暂停播放 / 录制self.stream.close()     # 终止流self.p.terminate()      # 终止会话def run(self):print("Video server starts...")self.sock.bind(self.ADDR)self.sock.listen(1)conn, addr = self.sock.accept()print("remote Video client success connected...")data = "".encode("utf-8")payload_size = struct.calcsize("L")     # 返回对应于格式字符串fmt的结构,L为4self.stream = self.p.open(format=FORMAT,channels=CHANNELS,rate=RATE,output=True,frames_per_buffer = CHUNK)while True:while len(data) < payload_size:data += conn.recv(81920)packed_size = data[:payload_size]data = data[payload_size:]msg_size = struct.unpack("L", packed_size)[0]while len(data) < msg_size:data += conn.recv(81920)frame_data = data[:msg_size]data = data[msg_size:]frames = pickle.loads(frame_data)for frame in frames:self.stream.write(frame, CHUNK)class Audio_Client(threading.Thread):def __init__(self ,ip, port, version):threading.Thread.__init__(self)self.setDaemon(True)self.ADDR = (ip, port)if version == 4:self.sock = socket(AF_INET, SOCK_STREAM)else:self.sock = socket(AF_INET6, SOCK_STREAM)self.p = pyaudio.PyAudio()self.stream = Noneprint("AUDIO client starts...")def __del__(self) :self.sock.close()if self.stream is not None:self.stream.stop_stream()self.stream.close()self.p.terminate()def run(self):while True:try:self.sock.connect(self.ADDR)breakexcept:time.sleep(3)continueprint("AUDIO client connected...")self.stream = self.p.open(format=FORMAT, channels=CHANNELS,rate=RATE,input=True,frames_per_buffer=CHUNK)while self.stream.is_active():frames = []for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):data = self.stream.read(CHUNK)frames.append(data)senddata = pickle.dumps(frames)try:self.sock.sendall(struct.pack("L", len(senddata)) + senddata)except:break

Play and Record Sound with Python

PyAudio Documentation¶

以上为参考文档,下面就需要编写启动这两个文件的主函数了。

编写程序入口 main.py

为了提供用户参数解析,代码使用了argparse。你可能对此前几个类中初始化方法的self.setDaemon(True)有疑惑。这个方法的调用使每个线程在主线程结束之后自动退出,保证程序不会出现崩溃且无法销毁的情况。在main.py中,我们通过每隔1s做一次线程的保活检查,如果视频/音频中出现阻塞/故障,主线程会终止。

import sys
import time
import argparse
from vchat import Video_Server, Video_Client
from achat import Audio_Server, Audio_Clientparser = argparse.ArgumentParser()parser.add_argument('--host', type=str, default='127.0.0.1')
parser.add_argument('--port', type=int, default=10087)
parser.add_argument('--level', type=int, default=1)
parser.add_argument('-v', '--version', type=int, default=4)args = parser.parse_args()IP = args.host
PORT = args.port
VERSION = args.version
LEVEL = args.levelif __name__ == '__main__':vclient = Video_Client(IP, PORT, LEVEL, VERSION)vserver = Video_Server(PORT, VERSION)aclient = Audio_Client(IP, PORT+1, VERSION)aserver = Audio_Server(PORT+1, VERSION)vclient.start()aclient.start()time.sleep(1)    # make delay to start servervserver.start()aserver.start()while True:time.sleep(1)if not vserver.isAlive() or not vclient.isAlive():print("Video connection lost...")sys.exit(0)if not aserver.isAlive() or not aclient.isAlive():print("Audio connection lost...")sys.exit(0)

关于argparse模块,我记得在之前的一篇文章中提到过,现在基本都忘了。。。链接为:

Python利用argparse模块图片转字符文件

如果有不清楚,可以进去看看我对参数画了思维导图。

总结

因为环境都没有提供摄像头,如果需运行,要修改一下代码,让程序从一个本地视频文件读取,模拟摄像头的访问。将Video_Client中self.cap = cv2.VideoCapture(0)改为self.cap = cv2.VideoCapture(‘xxx.mp4’),即从本地视频xxx.mp4中读取。我这里拿了一个B站的MAD来试验,另外,当程序启动时,电脑接收到的声音,因为自己与自己远程,都会有回声。

那么我在Windows环境下运行为:

Python 实现局域网视频聊天工具相关推荐

  1. python语言视频-Python语言之Python3 实现简易局域网视频聊天工具

    本文主要向大家介绍了Python语言之Python3 实现简易局域网视频聊天工具,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助. 操作系统为 Ubuntu 16.04,OpenCV ...

  2. Python3 实现简易局域网视频聊天工具

    Python3 实现简易局域网视频聊天工具 1.环境 操作系统为 Ubuntu 16.04 python 3.5 opencv-python 3.4.1.15 numpy 1.14.5 PyAudio ...

  3. 企业如何远程招聘到靠谱的程序员?--Codassium网页视频聊天工具

    招聘合适的人才一直是很多企业的痛,而要招聘到靠谱的程序员更不是那么容易的一件事,特别是对于创业公司来说更是如此.你需要了解对方的气质是否契合公司的文化氛围,还要了解他有没有真实的编程能力. 如果对方没 ...

  4. 局域网即时聊天工具都有哪些?

    在互联网普及的今天,使用即时聊天工具沟通交流在人们的日常生活和工作当中已经十分普及.但由于企业内部敏感信息通过互联网泄露的现象频发,不少企业出于安全性考虑只能转用局域网环境办公,在转用局域网环境办公后 ...

  5. 基于UDP协议的局域网网络聊天工具

    /* * 本程序实现了基于UDP协议的局域网网络聊天工具. * 参考网上的源码,发现一个calss就可以搞定. * ChatFrame类创建窗口,包含JTextField和TextArea. * 前者 ...

  6. 5个人审查5开源视频聊天工具

    像世界上大多数其他国家一样,卡在室内的是一群Opensource.com编辑和通讯员- 塞斯·肯隆 ( Seth Kenlon) , 马特 ·布罗伯格 ( Matt Broberg) , 艾伦·福迪· ...

  7. python爬取视频的工具_Python爬取视频(其实是一篇福利)

    原博文 2018-01-09 00:14 − 窗外下着小雨,作为单身程序员的我逛着逛着发现一篇好东西,来自知乎 你都用 Python 来做什么?的第一个高亮答案. 到上面去看了看,地址都是明文的,得, ...

  8. Python 抽取剔除视频帧工具

    前言 正好有人问我怎么把视频某几帧去掉,正好有时间,正好写了,正好发出来. 大家想用的话可以拿去参考. 代码 下面是我使用opencv对视频中间几帧抽取的方法. 主要的思路是在读取frame的时候,顺 ...

  9. python实现简易聊天需要登录_python编写简易聊天室实现局域网内聊天功能

    本文实例为大家分享了python实现局域网内聊天功能的具体代码,供大家参考,具体内容如下 功能: 可以向局域网内开启接收信息功能的ip进行发送信息,我们可以写两段端口不同的代码来实现在一台电脑上与自己 ...

  10. 基于Trtc的内贸站视频聊天服务

    基于Trtc的内贸站视频聊天服务分享 说到视频聊天,大家第一个想到的是啥,QQ! 其实最早的视频聊天工具应该是 : Netmeeting(我能找到的最早聊天工具) Netmeeting是Windows ...

最新文章

  1. 东田纳西州立大学计算机排名,2019东田纳西州立大学世界排名
  2. Java CopyOnWriteArrayList
  3. 问题集锦(26-29)
  4. Java I/O系统学习系列一:File和RandomAccessFile
  5. html表格数据循环展示,MVC在View中循环数据在table中显示
  6. PHP—str_replace()替换函数的使用
  7. python填充数组到指定长度
  8. php ==gt;,谈谈PHP中的 -gt;、=gt; 和 :: 符号 - 易采站长站
  9. html css浮动标签,12种超酷HTML5 SVG和CSS3浮动标签效果
  10. springboot配置请求头大小
  11. PowerAI DDL
  12. php5.6/7.0,浅谈PHP5.6 与 PHP7.0 区别
  13. python三维转换教程_Python科学计算三维可视化【完结】
  14. 《谭浩强C语言程序设计》 · 素数 7-3
  15. iOS架构-cocoaPods之Podfile语法(18)
  16. 草根程序员进入BAT
  17. 动态创建style标签样式
  18. 点亮LED灯(LED)
  19. 教你如何合并pdf文件
  20. Oracle中to_char函数的速度问题

热门文章

  1. 刚开始参加工作的45条建议
  2. k8s重要概念及部署k8s集群(一)
  3. 常用软件分类 精选列表(一)
  4. ASP.NET网站部署详细步骤
  5. 2021东阳高考成绩查询,2021金华市地区高考成绩排名查询,金华市高考各高中成绩喜报榜单...
  6. 简单的玻璃材质效果——UnityShader学习笔记
  7. git 不abandon的办法
  8. 文明IV模组(MOD)制作指南
  9. 天大18年c语言离线作业,2018春 Python语言程序设计(天津大学仁爱学院)-中国大学mooc-题库零氪...
  10. C#学员管理系统(源代码)