欢迎光临
我们一直在努力

手把手教你搭建多摄像头RTSP推流系统(Python + ffmpeg + MediaMTX)

手把手教你搭建多摄像头RTSP推流系统(Python + ffmpeg + MediaMTX)

在安防监控、视频直播或远程教学中,我们经常需要将多个摄像头的画面实时推送到流媒体服务器,然后分发给用户观看。市面上虽然有很多商业软件,但往往价格昂贵且不够灵活。作为开发者,自己动手写一个可控、可扩展的推流工具,既能省钱,又能深入理解底层原理。

今天,我将带你从零开始,搭建一套完整的多摄像头RTSP推流系统。我们将使用 Python 编写推流控制程序,调用 ffmpeg 进行视频采集与编码,配合轻量级流媒体服务器 MediaMTX(原rtsp-simple-server)进行分发。最终,你可以在任何支持RTSP的播放器(如VLC)中实时观看多个摄像头的画面。


一、系统架构

整个系统的数据流向如下:

摄像头1 → \\
摄像头2 → → Python + ffmpeg → MediaMTX服务器 → 播放器(VLC/ffplay)
摄像头3 → /

  • 摄像头:多个USB摄像头连接到同一台推流主机。
  • 推流端:Python脚本自动检测所有摄像头,并为每个摄像头启动一个ffmpeg子进程,将视频流推送到MediaMTX。
  • 流媒体服务器:MediaMTX接收推流,并提供RTSP/RTMP/HLS等多种协议的分发服务。
  • 播放端:用户使用VLC、ffplay或其他支持RTSP的播放器,从MediaMTX拉流观看。

二、第一步:搭建MediaMTX流媒体服务器

MediaMTX是一个开源的、零依赖的流媒体服务器,支持RTSP、RTMP、HLS、WebRTC、SRT等多种协议。它的配置非常简单,解压即用。

2.1 下载与安装

Windows环境:

  • 访问MediaMTX的GitHub Releases页面:https://github.com/bluenviron/mediamtx/releases
  • 下载最新版本的 mediamtx_windows_amd64.zip
  • 解压到任意目录,例如 D:\\mediamtx

Linux环境(Ubuntu/Debian):

# 直接下载二进制
wget https://github.com/bluenviron/mediamtx/releases/download/v1.11.3/mediamtx_v1.11.3_linux_amd64.tar.gz
tar -xzf mediamtx_v1.11.3_linux_amd64.tar.gz
cd mediamtx

# 或通过snap安装(自动启动为服务)
sudo snap install mediamtx

2.2 启动服务器

进入解压后的目录,直接运行可执行文件:

# Windows
mediamtx.exe

# Linux
./mediamtx

启动成功后,你会看到类似以下的日志:

2025/01/20 10:30:15 INF MediaMTX v1.11.3
2025/01/20 10:30:15 INF configuration loaded from mediamtx.yml
2025/01/20 10:30:15 INF [RTSP] listener opened on :8554 (TCP), :8000 (UDP/RTP), :8001 (UDP/RTCP)
2025/01/20 10:30:15 INF [RTMP] listener opened on :1935
2025/01/20 10:30:15 INF [HLS] listener opened on :8888

重点关注 RTSP服务:它监听在 8554端口(TCP),同时使用8000/8001端口进行UDP音视频数据传输。后续推流的地址将是 rtsp://服务器IP:8554/流名称。

2.3 验证服务器是否正常运行

在另一台终端(或同一台机器的另一个窗口)使用ffprobe测试:

# 由于还没有流,会返回404,但说明端口通
ffprobe -rtsp_transport tcp rtsp://localhost:8554/test

如果返回 404 Not Found,说明服务器正常响应;如果连接超时或被拒绝,请检查防火墙是否开放了8554端口。

2.4 (可选)修改配置

默认配置已满足基本需求。如需调整,可编辑同目录下的 mediamtx.yml 文件。例如修改RTSP端口、开启认证等:

rtspPort: 8554

paths:
all:
# 允许任何人推流和拉流(生产环境建议设置认证)
source: publisher
publishUser: admin
publishPass: pass123
readUser: user
readPass: read123

修改后重启MediaMTX生效。


三、第二步:编写Python推流脚本

现在我们来编写Python程序,它需要完成以下功能:

  • 自动检测摄像头:在Windows上通过DirectShow,在Linux上通过V4L2。
  • 多线程并发推流:每个摄像头独立线程,互不影响。
  • 断线重连:当ffmpeg进程意外退出时,自动重启。
  • 优雅退出:按Ctrl+C能清理所有子进程。

3.1 检测摄像头(跨平台)

import subprocess
import re
import platform
import glob

system_platform = platform.system().lower()

def find_cameras():
"""返回摄像头名称列表(Windows下为设备名,Linux下为设备路径)"""
cameras = []
if system_platform == 'windows':
cmd = ['ffmpeg', '-list_devices', 'true', '-f', 'dshow', '-i', 'dummy']
result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='ignore')
in_video = False
for line in result.stderr.split('\\n'):
if 'DirectShow video devices' in line:
in_video = True
continue
if 'DirectShow audio devices' in line:
break
if in_video and '"' in line:
match = re.search(r'\\"(.+?)\\"', line)
if match:
cameras.append(match.group(1))
else: # Linux
devices = glob.glob('/dev/video*')
for dev in devices:
try:
# 快速测试设备是否可用
test_cmd = ['ffmpeg', '-f', 'v4l2', '-list_formats', 'all', '-i', dev, '-t', '0.1']
subprocess.run(test_cmd, capture_output=True, timeout=2)
cameras.append(dev)
except:
pass
return cameras

3.2 构建ffmpeg推流命令

ffmpeg是核心工具,我们需要根据操作系统构造合适的命令。以下命令经过优化,适合实时推流:

  • 输入:Windows下用dshow,Linux下用v4l2。
  • 编码:H.264(libx264),ultrafast预设降低延迟。
  • 码率控制:crf 18保证质量,maxrate 800k限制带宽。
  • 分辨率:640×360,帧率15fps,平衡画质与性能。
  • 输出:RTSP over UDP(延迟低)。

def build_ffmpeg_cmd(device, stream_name, server_ip, base_path):
"""构建ffmpeg命令列表"""
rtsp_url = f"rtsp://{server_ip}:8554/{base_path}/{stream_name}"

if system_platform == 'windows':
return [
'ffmpeg',
'-f', 'dshow',
'-i', f'video={device}',
'-c:v', 'libx264',
'-preset', 'ultrafast',
'-tune', 'zerolatency',
'-crf', '18',
'-maxrate', '800k',
'-bufsize', '1600k',
'-g', '30',
'-keyint_min', '30',
'-pix_fmt', 'yuv420p',
'-r', '15',
'-s', '640×360',
'-flags', '+global_header',
'-fflags', 'nobuffer',
'-f', 'rtsp',
'-rtsp_transport', 'udp',
'-loglevel', 'quiet',
rtsp_url
]
else: # Linux
return [
'ffmpeg',
'-f', 'v4l2',
'-input_format', 'mjpeg', # 如果摄像头支持MJPEG,可降低CPU负载
'-i', device,
'-c:v', 'libx264',
'-preset', 'ultrafast',
'-tune', 'zerolatency',
'-crf', '18',
'-maxrate', '800k',
'-bufsize', '1600k',
'-g', '30',
'-keyint_min', '30',
'-pix_fmt', 'yuv420p',
'-r', '15',
'-s', '640×360',
'-flags', '+global_header',
'-fflags', 'nobuffer',
'-f', 'rtsp',
'-rtsp_transport', 'udp',
'-loglevel', 'quiet',
rtsp_url
]

3.3 单摄像头推流线程

每个摄像头运行在一个独立线程中,内部循环启动ffmpeg进程,并在进程退出后自动重启(使用指数退避策略)。

import threading
import time
import subprocess

stop_all = False # 全局停止标志
processes = {} # 存储子进程对象,键为通道号
lock = threading.Lock() # 保护processes字典

def stream_worker(device, channel, server_ip, base_path):
global stop_all
stream_name = f"cam{channel}"
cmd = build_ffmpeg_cmd(device, stream_name, server_ip, base_path)
print(f"[通道{channel}] 启动推流,目标地址: rtsp://{server_ip}:8554/{base_path}/{stream_name}")

restart_delay = 1
while not stop_all:
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with lock:
processes[channel] = proc

# 等待进程结束或被终止
while not stop_all:
if proc.poll() is not None:
break
time.sleep(0.5)

if stop_all:
break

# 进程意外退出,准备重启
print(f"[通道{channel}] 进程退出,{restart_delay}秒后重启")
time.sleep(restart_delay)
restart_delay = min(restart_delay * 2, 60) # 最大60秒

# 清理进程
with lock:
if channel in processes:
proc = processes.pop(channel)
if proc.poll() is None:
proc.terminate()
proc.wait(timeout=3)

3.4 主函数与多线程启动

主函数负责检测摄像头、过滤可用设备,然后为每个摄像头创建一个线程并启动。

def main():
global stop_all
server_ip = "8.140.248.23" # 替换为你的MediaMTX服务器IP
base_path = "cameras" # RTSP路径基础名称

print("正在检测摄像头…")
cameras = find_cameras()
if not cameras:
print("未找到任何摄像头")
return

print(f"找到 {len(cameras)} 个摄像头")

# 可选:测试摄像头是否真的可用(此处省略,可自行实现)

threads = []
for idx, cam in enumerate(cameras, 1):
t = threading.Thread(target=stream_worker, args=(cam, idx, server_ip, base_path))
t.start()
threads.append(t)

print(f"已启动 {len(threads)} 个推流线程,按 Ctrl+C 停止")

try:
for t in threads:
t.join()
except KeyboardInterrupt:
print("\\n收到中断,正在停止…")
stop_all = True
for t in threads:
t.join(timeout=5)

print("程序退出")

3.5 进程清理与信号处理

为了确保程序退出时ffmpeg子进程被正确终止,我们需要注册信号处理器和atexit函数。

import signal
import atexit
import sys

def cleanup():
global stop_all
stop_all = True
with lock:
for proc in processes.values():
if proc.poll() is None:
proc.terminate()
processes.clear()

atexit.register(cleanup)
signal.signal(signal.SIGINT, lambda sig, frame: cleanup() or sys.exit(0))
signal.signal(signal.SIGTERM, lambda sig, frame: cleanup() or sys.exit(0))

3.6 完整代码

将以上所有片段整合到一个文件 camera_stream.py 中,并确保开头导入所有必要模块。完整代码已上传至 Gitee(链接可附在文末),你也可以直接复制下面的精简版:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import subprocess, threading, time, platform, re, glob, signal, atexit, sys

stop_all = False
processes = {}
lock = threading.Lock()
system_platform = platform.system().lower()
SERVER_IP = "你的服务器IP"
BASE_PATH = "cameras"

def find_cameras():
# … 前面定义的函数 …

def build_ffmpeg_cmd(device, stream_name):
# … 前面定义的函数 …

def stream_worker(device, channel):
global stop_all
stream_name = f"cam{channel}"
cmd = build_ffmpeg_cmd(device, stream_name)
print(f"[通道{channel}] 启动推流: rtsp://{SERVER_IP}:8554/{BASE_PATH}/{stream_name}")
restart_delay = 1
while not stop_all:
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with lock:
processes[channel] = proc
while not stop_all:
if proc.poll() is not None:
break
time.sleep(0.5)
if stop_all:
break
print(f"[通道{channel}] 进程退出,{restart_delay}秒后重启")
time.sleep(restart_delay)
restart_delay = min(restart_delay * 2, 60)
with lock:
if channel in processes:
proc = processes.pop(channel)
if proc.poll() is None:
proc.terminate()
proc.wait(timeout=3)

def cleanup():
global stop_all
stop_all = True
with lock:
for proc in processes.values():
if proc.poll() is None:
proc.terminate()
processes.clear()

def main():
atexit.register(cleanup)
signal.signal(signal.SIGINT, lambda s, f: cleanup() or sys.exit(0))
signal.signal(signal.SIGTERM, lambda s, f: cleanup() or sys.exit(0))

cameras = find_cameras()
if not cameras:
print("未找到摄像头")
return
print(f"找到 {len(cameras)} 个摄像头")

threads = []
for idx, cam in enumerate(cameras, 1):
t = threading.Thread(target=stream_worker, args=(cam, idx))
t.start()
threads.append(t)

print(f"已启动 {len(threads)} 个推流线程,按 Ctrl+C 停止")
try:
for t in threads:
t.join()
except KeyboardInterrupt:
print("\\n正在停止…")
stop_all = True
for t in threads:
t.join(timeout=5)

if __name__ == "__main__":
main()

记得将 SERVER_IP 替换为你的MediaMTX服务器的实际IP地址。


四、第三步:测试与运行

4.1 启动MediaMTX服务器

在服务器上(可以是同一台机器,也可以是另一台)运行MediaMTX,确保控制台没有错误。

4.2 运行Python推流脚本

python camera_stream.py

预期输出:

正在检测摄像头…
找到 2 个摄像头
[通道1] 启动推流: rtsp://8.140.248.23:8554/cameras/cam1
[通道2] 启动推流: rtsp://8.140.248.23:8554/cameras/cam2
已启动 2 个推流线程,按 Ctrl+C 停止

4.3 检查服务器日志

MediaMTX窗口会显示新的推流连接:

2025/01/20 11:00:05 INF [RTSP] [conn 192.168.1.100:54321] is publishing to path 'cameras/cam1', 1 track (H264)

4.4 播放测试

使用ffplay:

ffplay -rtsp_transport tcp rtsp://8.140.248.23:8554/cameras/cam1

使用VLC:

  • 打开VLC → 媒体 → 打开网络串流
  • 输入 rtsp://8.140.248.23:8554/cameras/cam1
  • 点击播放

如果一切顺利,你就能看到摄像头的实时画面了。


五、常见问题与解决方案

5.1 无法连接到MediaMTX

  • 防火墙:确保服务器8554端口(TCP)和8000-8001端口(UDP)对外开放。
  • IP地址:确认SERVER_IP设置正确,本机测试可用127.0.0.1。
  • 服务器状态:查看MediaMTX日志,确认服务正常运行。

5.2 ffmpeg报错“Device not found”

  • Windows:摄像头名称可能包含特殊字符,用ffmpeg -list_devices true -f dshow -i dummy查看准确名称,并在代码中匹配。
  • Linux:检查/dev/video*权限,可能需要sudo运行脚本或添加用户到video组。

5.3 播放有延迟或卡顿

  • 降低分辨率/帧率:修改ffmpeg命令中的-s和-r参数。
  • 切换传输协议:将-rtsp_transport udp改为tcp(更稳定,延迟稍高)。
  • 检查网络带宽:确保上行带宽足够(每个流约800kbps)。

5.4 推流进程频繁重启

  • 检查ffmpeg错误:暂时去掉-loglevel quiet,观察ffmpeg输出。
  • 摄像头稳定性:尝试用ffmpeg直接录制一段时间,看是否正常。

六、进阶扩展

6.1 动态检测摄像头热插拔

可以使用pyudev(Linux)或wmi(Windows)监听USB事件,当摄像头插入或移除时动态增加/停止推流线程。

6.2 添加Web管理界面

MediaMTX提供了HTTP API,可以查询当前推流状态。结合Flask可以快速搭建一个简单的控制面板,查看在线摄像头、手动重启等。

6.3 启用推流认证

在mediamtx.yml中配置用户名密码:

paths:
all:
publishUser: admin
publishPass: secure123

推流时需要在RTSP URL中添加认证信息?MediaMTX的RTSP不支持URL内嵌认证,需使用RTMP推流(支持URL内嵌)或通过FFmpeg的-headers参数添加Authorization头。

6.4 多服务器负载均衡

当摄像头数量很多时,可以部署多台MediaMTX服务器,并用Nginx做RTSP反向代理。


七、结语

通过本文,你学会了如何从零搭建一套完整的多摄像头RTSP推流系统。我们使用了轻量级的MediaMTX作为流媒体服务器,用Python + ffmpeg实现自动化的多路推流。这套方案完全开源、免费,且易于扩展,无论是用于个人学习还是小型项目,都非常合适。

如果你在实践过程中遇到任何问题,欢迎在评论区留言交流。也欢迎分享本文给更多需要的朋友!


附录:

  • MediaMTX GitHub:https://github.com/bluenviron/mediamtx
  • 本文Python脚本完整版:https://gitee.com/tools_dev_1/camera_stream/tree/master

本文为原创技术教程,转载请注明出处。

赞(0)
未经允许不得转载:171主机测评 » 手把手教你搭建多摄像头RTSP推流系统(Python + ffmpeg + MediaMTX)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址