通过python消费者和生产者队列,实现视频流逐帧播放
参考
https://blog.csdn.net/Int93/article/details/78804918
原理
通过opencv读入RTSP或RTMP流,采用消费者-生产者模型,通过生产者线程,每次读入一个视频帧(生产)存入queue队列,再通过消费者线程对视频帧进行展示(消费)。
代码(即时停止)
import copy
from queue import Queue, Empty
import threading
import cv2
# 定义一个共享的退出标志
exit_flag = False
class FrameProducer(threading.Thread):
def __init__(self, frame_queue, url):
super().__init__()
self.frame_queue = frame_queue
self.url = url
self._cap = None
self.daemon = True
def run(self):
global exit_flag
log('in producer')
self._cap = cv2.VideoCapture(self.url)
while not exit_flag and self._cap.isOpened():
ret, image = self._cap.read()
log(f'get frame = {ret}')
if ret:
self.frame_queue.put(image)
else:
self._cap.release()
self._cap = cv2.VideoCapture(self.url)
class FrameConsumer(threading.Thread):
def __init__(self, frame_queue):
super().__init__()
self.frame_queue = frame_queue
self.daemon = True
def run(self):
global exit_flag
log('in consumer')
while not exit_flag:
try:
log(f'frame_queue size= {self.frame_queue.qsize()}')
frame = self.frame_queue.get(timeout=1) # 非阻塞地尝试从队列中获取帧
if frame is None: # 如果队列为空,则退出循环
break
cv2.imshow('cap video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 检查用户是否在opencv窗口中最近的 1 毫秒内按下了某个键,按下 'q' 键退出
self.frame_queue.put(frame) # 将帧放回队列
self.frame_queue.task_done()
exit_flag = True
except Empty:
pass # 队列为空,不做任何操作
def log(message):
"""线程安全的日志输出函数"""
with logging_lock:
print(message)
if __name__ == '__main__':
logging_lock = threading.Lock()
frame_queue = Queue(maxsize=60)
url = 'rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid'
producer = FrameProducer(frame_queue, url)
producer.start()
consumer = FrameConsumer(frame_queue)
consumer.start()
producer.join()
consumer.join()
log("程序已结束")
代码(确保队列中所有帧播放完再停止)
from queue import Queue, Empty
import threading
import cv2
# 定义一个共享的退出标志
exit_flag = False
logging_lock = threading.Lock()
class FrameProducer(threading.Thread):
def __init__(self, frame_queue, url):
super().__init__()
self.frame_queue = frame_queue
self.url = url
self.video = None
self.daemon = True
def run(self):
global exit_flag
log('in producer')
self.video = cv2.VideoCapture(self.url)
while not exit_flag and self.video.isOpened():
ret, image = self.video.read()
log(f'get frame = {ret}')
if ret:
self.frame_queue.put(image)
self.video.release()
class FrameConsumer(threading.Thread):
def __init__(self, frame_queue):
super().__init__()
self.frame_queue = frame_queue
self.daemon = True
def run(self):
global exit_flag
log('in consumer')
while not exit_flag or not self.frame_queue.empty():
try:
log(f'frame_queue size= {self.frame_queue.qsize()}')
frame = self.frame_queue.get(timeout=1) # 非阻塞地尝试从队列中获取帧
if frame is None: # 如果队列为空,则退出循环
break
cv2.imshow('cap video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): # 检查用户是否在opencv窗口中最近的 1 毫秒内按下了某个键,按下 'q' 键退出
exit_flag = True
except Empty:
pass # 队列为空,不做任何操作
def log(message):
"""线程安全的日志输出函数"""
with logging_lock:
print(message)
def main(url: str = 'rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid'):
frame_queue = Queue(maxsize=60)
producer = FrameProducer(frame_queue, url)
producer.start()
consumer = FrameConsumer(frame_queue)
consumer.start()
producer.join()
consumer.join()
log(f'frame_queue size= {frame_queue.qsize()}')
frame_queue.task_done()
log("程序已结束")
if __name__ == '__main__':
main()