一、引言
在本文中,我们将详细介绍如何使用 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 进行单线程和多线程的视频推流操作。单线程代码简单明了,但性能可能受限;多线程代码可以更好地处理高负载,但也需要注意线程安全和资源管理等问题。在实际应用中,我们可以根据具体的需求和硬件性能来选择合适的推流方式。同时,我们可以进一步优化代码,例如添加异常处理、优化帧处理逻辑等,以提高程序的稳定性和性能。