一、引言
在本文中,我们将详细介绍如何使用 python 进行视频的推流操作。我们将通过两个不同的实现方式,即单线程推流和多线程推流,来展示如何利用 cv2
(opencv)和 subprocess
等库将视频帧推送到指定的 rtmp 地址。这两种方式都涉及到从摄像头读取视频帧,以及使用 ffmpeg
命令行工具将视频帧进行编码和推流的过程。
二、单线程推流
以下是单线程推流的代码:
import cv2 as cv import subprocess as sp def push_stream(): # 视频读取对象 cap = cv.videocapture(0) fps = int(cap.get(cv.cap_prop_fps)) w = int(cap.get(cv.cap_prop_frame_width)) h = int(cap.get(cv.cap_prop_frame_height)) ret, frame = cap.read() # 推流地址 rtmpurl = "rtmp://192.168.3.33:1935/live/" # 推流参数 command = ['ffmpeg', '-y', '-f', 'rawvideo', '-vcodec','rawvideo', '-pix_fmt', 'bgr24', '-s', "{}x{}".format(w, h), '-r', str(fps), '-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-preset', 'ultrafast', '-f', 'flv', rtmpurl] # 创建、管理子进程 pipe = sp.popen(command, stdin=sp.pipe, bufsize=10 ** 8) # 循环读取 while cap.isopened(): # 读取一帧 ret, frame = cap.read() if frame is none: print('read frame err!') continue # 显示一帧 cv.imshow("frame", frame) # 按键退出 if cv.waitkey(1) & 0xff == ord('q'): break # 读取尺寸、推流 # img=cv.resize(frame,size) pipe.stdin.write(frame) # 关闭窗口 cv.destroyallwindows() # 停止读取 cap.release()
在这个单线程的实现中,我们执行以下步骤:
初始化视频读取对象:
- 使用
cv2.videocapture(0)
来打开默认的摄像头设备。 - 获取摄像头的帧率
fps
、宽度w
和高度h
等参数。
设置推流地址和参数:
- 定义
rtmpurl
作为推流的目标地址。 - 构造
ffmpeg
的命令列表command
,该列表包含了一系列的参数,如-y
表示覆盖输出文件、-f rawvideo
表示输入格式为原始视频等。 - 使用
sp.popen
创建一个子进程,将ffmpeg
命令作为子进程运行,并且将其输入管道stdin
连接到我们的程序。
循环读取和推流:
- 在一个
while
循环中,不断读取摄像头的帧。 - 若读取失败,打印错误信息并继续。
- 使用
cv2.imshow
显示当前帧,同时监听q
键,按下q
键时退出程序。 - 将读取到的帧通过管道发送给
ffmpeg
进行推流。
三、多线程推流
以下是多线程推流的代码:
import queue import threading import cv2 as cv import subprocess as sp class live(object): def __init__(self): self.frame_queue = queue.queue() self.command = "" # 自行设置 self.rtmpurl = "" self.camera_path = "" def read_frame(self): print("开启推流") cap = cv.videocapture(self.camera_path) # get video information fps = int(cap.get(cv.cap_prop_fps)) width = int(cap.get(cv.cap_prop_frame_width)) height = int(cap.get(cv.cap_prop_frame_height)) # ffmpeg command self.command = ['ffmpeg', '-y', '-f', 'rawvideo', '-vcodec','rawvideo', '-pix_fmt', 'bgr24', '-s', "{}x{}".format(width, height), '-r', str(fps), '-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-preset', 'ultrafast', '-f', 'flv', self.rtmpurl] # read webcamera while(cap.isopened()): ret, frame = cap.read() if not ret: print("opening camera is failed") break # put frame into queue self.frame_queue.put(frame) def push_frame(self): # 防止多线程时 command 未被设置 while true: if len(self.command) > 0: # 管道配置 p = sp.popen(self.command, stdin=sp.pipe) break while true: if self.frame_queue.empty()!= true: frame = self.frame_queue.get() # process frame # 你处理图片的代码 # write to pipe p.stdin.write(frame.tostring()) def run(self): threads = [ threading.thread(target=live.read_frame, args=(self,)), threading.thread(target=live.push_frame, args=(self,)) ] [thread.setdaemon(true) for thread in threads] [thread.start() for thread in threads]
在这个多线程的实现中,我们使用了 threading
和 queue
库:
类的初始化:
- 创建一个
live
类,在__init__
方法中初始化帧队列frame_queue
、command
、rtmpurl
和camera_path
等变量。
读取帧的线程方法:
read_frame
方法中,使用cv2.videocapture(self.camera_path)
打开摄像头。- 获取摄像头的参数,并构造
ffmpeg
命令。 - 不断从摄像头读取帧,并将帧放入队列
frame_queue
中。
推流的线程方法:
push_frame
方法中,等待command
被设置,然后使用sp.popen
启动ffmpeg
子进程。- 从帧队列中取出帧,并将其写入
ffmpeg
的输入管道进行推流。
启动线程:
run
方法创建并启动两个线程,一个用于读取帧,一个用于推流,并且将它们设置为守护线程。
四、代码解释和注意事项
单线程推流:
- 这种方式相对简单,适合初学者理解。但由于是单线程操作,在处理复杂任务时可能会导致性能瓶颈,特别是在同时进行视频显示、读取和推流的情况下,可能会出现卡顿现象。
多线程推流:
- 利用多线程可以将不同的任务分配给不同的线程,提高性能。
frame_queue
是一个线程安全的队列,用于在两个线程之间传递帧数据,避免了数据竞争问题。setdaemon(true)
使得线程在主线程结束时自动终止,防止程序无法正常退出。
五、总结
通过上述代码和解释,我们可以看到如何使用 python 进行单线程和多线程的视频推流操作。单线程代码简单明了,但性能可能受限;多线程代码可以更好地处理高负载,但也需要注意线程安全和资源管理等问题。在实际应用中,我们可以根据具体的需求和硬件性能来选择合适的推流方式。同时,我们可以进一步优化代码,例如添加异常处理、优化帧处理逻辑等,以提高程序的稳定性和性能。
到此这篇关于python结合ffmpeg 实现单线程和多线程推流的文章就介绍到这了,更多相关python ffmpeg 单线程和多线程推流内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论