做了下实验楼的关于Python 实现局域网视频聊天工具 ,感觉还不错,作为练习opencv和socket的小脚本入门了。 本课程项目完成过程中将学习: 其中将重点介绍 socket 传输过程中对数据的压缩和处理。 首先关于实验所需要用到的包,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版本来讲,我是没有安装成功的,原因应该都是报这个错吧: 这里卡了很久,再次发现国内博客都是抄来抄去的,中间还有其它事情,直到第二天搭梯子谷歌了一下,终于找到了答案: 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类完成,一起完善为: 已经捕获到数据,接下来要发送字节流。首先我们继续编写Client,为其添加发送数据功能的实现。这里只改动了run方法。在捕获到帧后,我们使用pickle.dumps方法对其打包,并用sock.sendall方法发送。注意发送过程中我们用struct.pack方法为每批数据加了一个头,用于接收方确认接受数据的长度。 下面编写Server,在服务器端连接成功后,应当创建一个窗口用于显示接收到的视频。因为连接不一定创建成功,因此cv.destroyAllWindows()被放在一个try…catch块中防止出现错误。在接收数据过程中,我们使用payload_size记录当前从缓冲区读入的数据长度,这个长度通过struct.calcsize(‘L’)来读取。使用该变量的意义在于缓冲区中读出的数据可能不足一个帧,也可能由多个帧构成。为了准确提取每一帧,我们用payload_size区分帧的边界。在从缓冲区读出的数据流长度超过payload_size时,剩余部分和下一次读出的数据流合并,不足payload_size时将合并下一次读取的数据流到当前帧中。在接收完完整的一帧后,显示在创建的窗口中。同时我们为窗口创建一个键盘响应,当按下Esc 或 q键时退出程序。 另外当用户指定使用低画质通信,我们应当对原始数据做变换,最简单的方式即将捕获的每一帧按比例缩放,同时降低传输的帧速,在代码中体现为resize,该函数的第二个参数为缩放中心,后两个参数为缩放比例,并且根据用户指定的等级,不再传输捕获的每一帧,而是间隔几帧传输一帧。为了防止用户指定的画质过差,代码中限制了最坏情况下的缩放比例为0.3,最大帧间隔为3。此外,我们在发送每一帧的数据前使用zlib.compress对其压缩,尽量降低带宽负担。 所以完整代码为: 里面有些代码上面的大段应该解释得比较清楚,如果还有不明白的,可以看作者的推荐,还有我找到的一些资料链接: struct.pack()、struct.unpack()和struct.calcsize() 在完成视频通信的基础上,整体框架对于音频通信可以直接挪用,只需要修改其中捕获视频/音频的代码和服务器解码播放的部分。这里我们使用 PyAudio 库处理音频,在 Linux 下你也可以选择 sounddevice。关于sounddevice这里不做过多介绍,可以在这里看到它最新版本的文档。将vchat.py复制一份,重命名为achat.py,简单修改几处,最终音频捕获、传输的完整代码如下。将上面代码中的Server和Client分别加上Video和Audio前缀以区分,同时显示给用户的print输出语句也做了一定修改,对于视频加上VIDEO前缀,音频加上AUDIO前缀。 Play and Record Sound with Python 以上为参考文档,下面就需要编写启动这两个文件的主函数了。 为了提供用户参数解析,代码使用了argparse。你可能对此前几个类中初始化方法的self.setDaemon(True)有疑惑。这个方法的调用使每个线程在主线程结束之后自动退出,保证程序不会出现崩溃且无法销毁的情况。在main.py中,我们通过每隔1s做一次线程的保活检查,如果视频/音频中出现阻塞/故障,主线程会终止。 关于argparse模块,我记得在之前的一篇文章中提到过,现在基本都忘了。。。链接为: 如果有不清楚,可以进去看看我对参数画了思维导图。 因为环境都没有提供摄像头,如果需运行,要修改一下代码,让程序从一个本地视频文件读取,模拟摄像头的访问。将Video_Client中self.cap = cv2.VideoCapture(0)改为self.cap = cv2.VideoCapture(‘xxx.mp4’),即从本地视频xxx.mp4中读取。我这里拿了一个B站的MAD来试验,另外,当程序启动时,电脑接收到的声音,因为自己与自己远程,都会有回声。 那么我在Windows环境下运行为:引言
1. 内容简介
2. 实验知识点
3.实验环境
实验步骤
环境搭建
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:DownloadsPyAudio-0.2.11-cp36-cp36m-win_amd64.whl ERROR: 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将包的源码拷贝到本地重命名,不过我不喜欢这么做。代码实现
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() # TODO def 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...") # TODO class 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) # 创建一个成员变量cap def __del__(self) : self.sock.close() self.cap.release() def run(self): print("client starts...") while True: try: self.sock.connect(self.ADDR) break except: time.sleep(3) continue print("client connected...") while self.cap.isOpened(): # 如果连接上了,进入循环读相应的文件,这里也可以try一下,没有连接上接收异常,这样的语法不好排错 ret, frame = self.cap.read() # 读
def run(self): while True: try: self.sock.connect(self.ADDR) break except: time.sleep(3) continue print("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
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: pass 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") # 结果为4 cv2.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: break class 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 = level else: self.interval = 3 self.fx = 1 / (self.interval + 1) if self.fx < 0.3: # 限制最大帧间隔为3帧 self.fx = 0.3 if 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) break except: time.sleep(3) continue print("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: break for i in range(self.interval): self.cap.read()
加入音频的捕获和传输
from socket import * import threading import pyaudio import wave import sys import zlib import struct import pickle import time import numpy as np CHUNK = 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 = None 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): 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为4 self.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 = None print("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) break except: time.sleep(3) continue print("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
编写程序入口 main.py
import sys import time import argparse from vchat import Video_Server, Video_Client from achat import Audio_Server, Audio_Client parser = 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.level if __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 server vserver.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)
总结
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算