You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派5上如何监控ffplay输出并在其挂起时重启进程?

树莓派5上自动监控并重启ffplay RTSP流方案

我在树莓派5上开启4个终端窗口,每个窗口运行以下命令播放监控摄像头RTSP流:

ffplay -left 0 -top 0 -x 960 -y 540 -rtsp_transport tcp "rtsp://user:pass@camera-IP:port/videoSub"

已通过-rtsp_transport tcp解决了之前的流错误问题,但偶尔会出现单个或多个流挂起的情况,只能手动重启。现在需要实现两个需求:监控ffplay的输出、检测到挂起时自动杀死进程并重启,优先采用Shell脚本或Python方案,避开Mono和C#。


一、Shell脚本轻量方案(单流独立监控)

适合快速部署,每个流对应一个独立脚本,逻辑简单易修改。

1. 单流监控脚本示例(复制4份适配不同流)

创建monitor_ffplay_1.sh:

#!/bin/bash
# 配置当前流的参数
WINDOW_LEFT=0
WINDOW_TOP=0
WINDOW_WIDTH=960
WINDOW_HEIGHT=540
RTSP_URL="rtsp://user:pass@camera-IP:port/videoSub"
LOG_FILE="/tmp/ffplay_monitor_1.log"
CHECK_INTERVAL=10  # 每10秒检查一次输出
TIMEOUT_THRESHOLD=30  # 超过30秒无输出判定为挂起

# 循环重启逻辑
while true; do
    # 启动ffplay并将输出写入日志
    ffplay -left $WINDOW_LEFT -top $WINDOW_TOP -x $WINDOW_WIDTH -y $WINDOW_HEIGHT -rtsp_transport tcp "$RTSP_URL" 2>&1 | tee $LOG_FILE &
    PLAY_PID=$!

    # 监控输出更新
    while kill -0 $PLAY_PID 2>/dev/null; do
        LAST_MODIFIED=$(stat -c %Y $LOG_FILE)
        CURRENT_TIME=$(date +%s)
        # 检查日志是否超时未更新
        if [ $((CURRENT_TIME - LAST_MODIFIED)) -gt $TIMEOUT_THRESHOLD ]; then
            echo "[$(date)] ffplay进程 $PLAY_PID 无输出,重启中..."
            kill -9 $PLAY_PID
            break
        fi
        sleep $CHECK_INTERVAL
    done
    wait $PLAY_PID 2>/dev/null
    echo "[$(date)] ffplay进程已退出,准备重新启动..."
    sleep 2
done

2. 使用步骤

  • 复制4份脚本,分别修改WINDOW_LEFT/WINDOW_TOP(比如第二个窗口设为-left 960 -top 0)、RTSP_URL和LOG_FILE名称(如ffplay_monitor_2.log)
  • 给脚本添加执行权限:chmod +x monitor_ffplay_*.sh
  • 后台运行脚本(避免终端关闭后进程终止):nohup ./monitor_ffplay_1.sh > /dev/null 2>&1 &,其余3个流同理执行

二、Python统一监控方案(多流集中管理)

更灵活的集中管理方案,适合需要扩展功能(如告警、日志归档)的场景。

1. 统一监控脚本示例(ffplay_monitor.py)

import subprocess
import time
import os
import signal

# 配置4个流的参数,按需修改
STREAMS = [
    {"left": 0, "top": 0, "width": 960, "height": 540, "url": "rtsp://user:pass@camera-IP1:port/videoSub", "name": "stream1"},
    {"left": 960, "top": 0, "width": 960, "height": 540, "url": "rtsp://user:pass@camera-IP2:port/videoSub", "name": "stream2"},
    {"left": 0, "top": 540, "width": 960, "height": 540, "url": "rtsp://user:pass@camera-IP3:port/videoSub", "name": "stream3"},
    {"left": 960, "top": 540, "width": 960, "height": 540, "url": "rtsp://user:pass@camera-IP4:port/videoSub", "name": "stream4"},
]

CHECK_INTERVAL = 10  # 检查间隔(秒)
TIMEOUT_THRESHOLD = 30  # 无输出超时阈值(秒)

class StreamMonitor:
    def __init__(self, config):
        self.config = config
        self.process = None
        self.last_output_time = time.time()

    def start_stream(self):
        # 构建ffplay启动命令
        cmd = [
            "ffplay",
            "-left", str(self.config["left"]),
            "-top", str(self.config["top"]),
            "-x", str(self.config["width"]),
            "-y", str(self.config["height"]),
            "-rtsp_transport", "tcp",
            self.config["url"]
        ]
        # 启动子进程并捕获输出
        self.process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1,
            universal_newlines=True
        )
        self.last_output_time = time.time()
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 启动流 {self.config['name']},PID: {self.process.pid}")

    def check_status(self):
        if not self.process:
            return False
        # 读取一行输出(非阻塞)
        try:
            line = self.process.stdout.readline()
            if line:
                self.last_output_time = time.time()
                # 可选:取消注释打印输出到控制台
                # print(f"[{self.config['name']}] {line.strip()}")
        except:
            pass
        # 检查进程是否存活或超时无输出
        if self.process.poll() is not None:
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 流 {self.config['name']} 进程退出,退出码: {self.process.returncode}")
            return False
        if time.time() - self.last_output_time > TIMEOUT_THRESHOLD:
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 流 {self.config['name']} 超时无输出,重启中...")
            self.kill_process()
            return False
        return True

    def kill_process(self):
        if self.process and self.process.poll() is None:
            os.kill(self.process.pid, signal.SIGKILL)
            self.process.wait()
            print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 已杀死流 {self.config['name']} 进程 {self.process.pid}")

def main():
    monitors = [StreamMonitor(stream) for stream in STREAMS]
    # 逐个启动流,避免资源瞬间占用过高
    for monitor in monitors:
        monitor.start_stream()
        time.sleep(1)

    while True:
        for monitor in monitors:
            if not monitor.check_status():
                time.sleep(2)
                monitor.start_stream()
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    main()

2. 使用步骤

  • 修改STREAMS列表中的窗口位置和RTSP地址
  • 后台运行脚本:nohup python3 ffplay_monitor.py > ffplay_monitor.log 2>&1 &

方案选择建议

  • Shell脚本:轻量无依赖,适合快速部署,每个流独立管理,修改成本低
  • Python脚本:集中管理所有流,逻辑扩展性强,方便后续添加告警、日志分析等功能

内容的提问来源于stack exchange,提问作者Deekay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:33:22