单线程Flask实现多端口摄像头视频流推送方案问询
单线程Flask架构实现多摄像头分端口推流
核心思路
放弃多Flask实例的方案,用单线程Flask作为统一服务,搭配独立的摄像头处理线程,再通过反向代理实现不同端口对应不同摄像头流:
- 摄像头线程负责拉流、处理帧,将最新处理后的帧存入线程安全的全局缓冲区
- 单Flask服务提供不同路由对应每个摄像头的推流接口
- 用反向代理(比如Nginx)把5000-5003端口分别映射到Flask的对应路由,实现用户访问不同端口看对应摄像头
代码实现
1. 摄像头处理与帧缓冲区
import cv2 import threading from flask import Flask, Response # 线程安全的帧缓冲区,key为摄像头ID,value为最新帧 frame_buffers = { "cam1": None, "cam2": None, "cam3": None, "cam4": None } buffer_locks = { "cam1": threading.Lock(), "cam2": threading.Lock(), "cam3": threading.Lock(), "cam4": threading.Lock() } def camera_worker(cam_id, rtsp_url): cap = cv2.VideoCapture(rtsp_url) if not cap.isOpened(): print(f"Failed to open {cam_id} stream") return while True: ret, frame = cap.read() if not ret: print(f"{cam_id} stream disconnected, retrying...") cap.release() cap = cv2.VideoCapture(rtsp_url) continue # 这里替换成你的OpenCV图像处理逻辑 processed_frame = frame # 线程安全更新缓冲区 with buffer_locks[cam_id]: frame_buffers[cam_id] = processed_frame cap.release()
2. 单线程Flask服务
app = Flask(__name__) def generate_frames(cam_id): while True: with buffer_locks[cam_id]: frame = frame_buffers[cam_id] if frame is None: continue # 编码为JPEG格式 ret, buffer = cv2.imencode('.jpg', frame) if not ret: continue frame_bytes = buffer.tobytes() # 生成MJPEG流格式 yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_bytes + b'\r\n') # 为每个摄像头定义推流路由 @app.route('/cam1/video') def cam1_video(): return Response(generate_frames("cam1"), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/cam2/video') def cam2_video(): return Response(generate_frames("cam2"), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/cam3/video') def cam3_video(): return Response(generate_frames("cam3"), mimetype='multipart/x-mixed-replace; boundary=frame') @app.route('/cam4/video') def cam4_video(): return Response(generate_frames("cam4"), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': # 启动所有摄像头处理线程 threading.Thread(target=camera_worker, args=("cam1", "rtsp://cam1_url"), daemon=True).start() threading.Thread(target=camera_worker, args=("cam2", "rtsp://cam2_url"), daemon=True).start() threading.Thread(target=camera_worker, args=("cam3", "rtsp://cam3_url"), daemon=True).start() threading.Thread(target=camera_worker, args=("cam4", "rtsp://cam4_url"), daemon=True).start() # 启动单线程Flask服务,监听5004端口(可自定义) app.run(host='0.0.0.0', port=5004, threaded=False) # threaded=False确保单线程运行
3. Nginx反向代理配置
在Nginx配置文件中添加以下server块,实现不同端口映射到对应路由:
server { listen 5000; server_name 192.168.0.50; location / { proxy_pass http://127.0.0.1:5004/cam1/video; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 5001; server_name 192.168.0.50; location / { proxy_pass http://127.0.0.1:5004/cam2/video; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 5002; server_name 192.168.0.50; location / { proxy_pass http://127.0.0.1:5004/cam3/video; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } server { listen 5003; server_name 192.168.0.50; location / { proxy_pass http://127.0.0.1:5004/cam4/video; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
配置完成后重启Nginx,用户访问192.168.0.50:5000即可看到CAMERA1的流,以此类推。
方案优势
- 减少资源竞争:单线程Flask统一处理所有推流请求,避免多Flask实例同时占用CPU、内存导致的性能下降
- 帧复用优化:摄像头线程仅做一次图像处理,Flask直接复用缓冲区的帧编码推流,避免重复计算
- 线程安全保障:用Lock保护帧缓冲区的读写操作,避免多线程冲突导致的帧损坏
内容的提问来源于stack exchange,提问作者Vikrant
相关产品推荐
相关产品推荐

