From c3a48fbd8b99f171256f3772c8a8660b2ae47e04 Mon Sep 17 00:00:00 2001 From: xiaji Date: Wed, 18 Mar 2026 15:19:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=EF=BC=9A=E6=B7=BB=E5=8A=A0README=E5=92=8C=E6=A0=B8=E5=BF=83Pyt?= =?UTF-8?q?hon=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 32 ++++++++++++++++ README.md | 68 +++++++++++++++++++++++++++++++++ auto_receive.py | 38 +++++++++++++++++++ manage_server.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ push_screen.py | 50 +++++++++++++++++++++++++ 5 files changed, 285 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 auto_receive.py create mode 100644 manage_server.py create mode 100644 push_screen.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a51db7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# 虚拟环境 +venv/ +env/ +*.venv/ + +# 编译产物 +*.pyc +__pycache__/ + +# IDE相关 +.vscode/ +.idea/ + +# 系统文件 +.DS_Store +Thumbs.db + +# 日志文件 +*.log + +# 配置文件 +.env +.local + +# 测试文件 +.pytest_cache/ +.coverage + +# 构建文件 +build/ +dist/ +*.egg-info/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..80d108d --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# 会议投屏系统 + +## 项目简介 + +面向32台终端的内网低延迟屏幕广播系统,支持网络投屏、硬件接口互通、一键自动化运维。 + +## 需求说明 + +当前会议投屏需求涉及通过视频矩阵系统向32台终端设备进行同步投屏。具体模式可分为以下三类: + +1. **模式1**:单台终端设备作为信号源,向其余31台终端设备进行投屏传输。 +2. **模式2**:主控设备同时向31台终端设备传输本地视频信号,并同步接入远程视频会议内容。 +3. **模式3**:视频会议内容直接投屏至全部32台终端设备的显示界面。 + +技术说明:上述32台终端设备均通过有线网络实现互联互通。 + +## 解决方案 + +参考文档:`https://docs.qq.com/flowchart-addon` + +## 前置准备(仅需配置1次) + +### 环境部署(Win10服务器 + 32台终端) + +#### 服务器端(部署 MediaMTX+Python+FFmpeg) +1. 下载并解压以下工具到 D:\ScreenCast 目录(统一路径方便脚本调用): + - MediaMTX:`https://github.com/bluenviron/mediamtx/releases`(windows-amd64版,解压后改名为mediamtx) + - FFmpeg:`https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z`(解压后改名为ffmpeg) + - Python:`https://www.python.org/downloads/windows/`(安装时勾选「Add Python to PATH」) + +2. 安装 Python 依赖(管理员 cmd 执行): + ``` + pip install pyautogui requests subprocess32 psutil + ``` + +3. 给服务器开启「屏幕录制权限」(设置→隐私和安全性→屏幕录制→允许 Python/CMD)。 + +#### 终端端(32台Win10) +1. 安装 Python(同上),仅需执行1次依赖安装: + ``` + pip install pyautogui + ``` + +2. 确保 Chrome/Edge 浏览器安装在默认路径(C:\Program Files\Google\Chrome\Application\chrome.exe)。 + +### 核心文件结构(统一放在服务器D:\ScreenCast) + +``` +D:\ScreenCast +├── mediamtx/ # MediaMTX解压目录 +│ └── mediamtx.exe +├── ffmpeg/ # FFmpeg解压目录 +│ └── bin/ffmpeg.exe +├── push_screen.py # 投屏源推流脚本(一键采集) +├── auto_receive.py # 终端自动打开浏览器脚本 +└── manage_server.py # 运维总控脚本(启动/切换模式/停止) +``` + +## 核心Python脚本 + +### 脚本1:投屏源推流脚本(push_screen.py) +功能:双击即可采集Win10屏幕,自动推流到MediaMTX,无需敲命令;支持「全屏/指定窗口」投屏,带可视化提示。 + +### 脚本2:终端自动打开浏览器脚本(auto_receive.py) +功能:双击脚本自动打开Chrome浏览器(全屏),直接跳转到投屏/会议流页面;支持批量部署到32台终端,无需手动输入地址。 + +### 脚本3:运维总控脚本(manage_server.py) +功能:一站式运维(启动MediaMTX、切换三种模式、批量控制32台终端、停止所有服务),适合管理员操作。 \ No newline at end of file diff --git a/auto_receive.py b/auto_receive.py new file mode 100644 index 0000000..4863678 --- /dev/null +++ b/auto_receive.py @@ -0,0 +1,38 @@ +import subprocess +import time +import sys +import os + +# 配置项(可通过命令行传参切换模式) +SERVER_IP = "192.168.1.100" +BROWSER_PATH = r"C:\Program Files\Google\Chrome\Application\chrome.exe" + +# 自动打开浏览器(模式1/2:投屏流;模式3:会议流) +def open_receive_browser(mode="screen"): + # 关闭已有Chrome窗口(避免多开) + try: + subprocess.run(["taskkill", "/f", "/im", "chrome.exe"], capture_output=True) + except: + pass + time.sleep(1) + + # 拼接流地址(全屏+无地址栏) + if mode == "screen": + url = f"http://{SERVER_IP}:8889/webrtc.html?src=screen" + elif mode == "meeting": + url = f"http://{SERVER_IP}:8889/webrtc.html?src=meeting" + + # 打开Chrome(全屏 kiosk模式,无操作栏) + subprocess.Popen([ + BROWSER_PATH, + "--kiosk", # 全屏模式 + "--start-maximized", + "--disable-infobars", + url + ]) + print(f"已打开{mode}流接收页面:{url}") + +if __name__ == "__main__": + # 支持命令行传参切换模式(比如:python auto_receive.py meeting) + mode = sys.argv[1] if len(sys.argv) > 1 else "screen" + open_receive_browser(mode) \ No newline at end of file diff --git a/manage_server.py b/manage_server.py new file mode 100644 index 0000000..1341e6d --- /dev/null +++ b/manage_server.py @@ -0,0 +1,97 @@ +import subprocess +import psutil +import time +import os +import tkinter as tk +from tkinter import messagebox + +# 基础配置 +SERVER_IP = "192.168.1.100" +MEDIAMTX_PATH = r"D:\ScreenCast\mediamtx\mediamtx.exe" +FFMPEG_PATH = r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe" +# 32台终端IP列表(替换为实际终端IP) +TERMINAL_IPS = [f"192.168.1.{i}" for i in range(11, 43)] # 示例:192.168.1.11~43 + +# 停止所有相关进程 +def stop_all(): + # 停止MediaMTX + for proc in psutil.process_iter(['name']): + if proc.info['name'] in ['mediamtx.exe', 'ffmpeg.exe']: + proc.kill() + # 停止终端Chrome(可选) + for ip in TERMINAL_IPS: + try: + subprocess.run(f"ssh {ip} taskkill /f /im chrome.exe", shell=True, capture_output=True) + except: + pass + messagebox.showinfo("提示", "所有服务已停止!") + +# 模式1:1台投屏→31台接收 +def mode1(): + # 启动投屏源推流(本地执行push_screen.py) + subprocess.Popen(["python", r"D:\ScreenCast\push_screen.py"], creationflags=subprocess.CREATE_NEW_CONSOLE) + time.sleep(5) + # 批量启动31台终端接收(排除投屏源IP,比如192.168.1.10) + for ip in TERMINAL_IPS: + if ip != "192.168.1.10": # 投屏源IP + # 远程执行auto_receive.py(需开启终端SSH/共享脚本) + subprocess.run(f"ssh {ip} python D:\ScreenCast\auto_receive.py screen", shell=True) + messagebox.showinfo("提示", "模式1已启动:1台投屏→31台接收!") + +# 模式2:投屏+第三方会议(投屏流→31台+HDMI输出到矩阵) +def mode2(): + # 启动投屏推流 + subprocess.Popen([ + FFMPEG_PATH, + "-f", "gdigrab", "-framerate", "30", "-i", "desktop", + "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", + "-f", "rtsp", f"rtsp://{SERVER_IP}:8554/screen" + ], creationflags=subprocess.CREATE_NEW_CONSOLE) + # 启动HDMI输出到矩阵(mpv需提前解压到D:\ScreenCast) + subprocess.Popen([ + r"D:\ScreenCast\mpv.exe", + f"rtsp://{SERVER_IP}:8554/screen", + "--vo=direct3d", "--screen=1", "--fullscreen" + ], creationflags=subprocess.CREATE_NEW_CONSOLE) + # 批量启动31台终端接收 + for ip in TERMINAL_IPS: + if ip != "192.168.1.10": + subprocess.run(f"ssh {ip} python D:\ScreenCast\auto_receive.py screen", shell=True) + messagebox.showinfo("提示", "模式2已启动:投屏→31台+HDMI输出到会议!") + +# 模式3:会议流→32台接收(采集卡采集会议流) +def mode3(): + # 采集卡采集矩阵的会议流,推到MediaMTX + subprocess.Popen([ + FFMPEG_PATH, + "-f", "dshow", "-i", "video=采集卡设备名称", + "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", + "-f", "rtsp", f"rtsp://{SERVER_IP}:8554/meeting" + ], creationflags=subprocess.CREATE_NEW_CONSOLE) + # 批量启动32台终端接收会议流 + for ip in TERMINAL_IPS: + subprocess.run(f"ssh {ip} python D:\ScreenCast\auto_receive.py meeting", shell=True) + messagebox.showinfo("提示", "模式3已启动:会议流→32台接收!") + +# 主界面 +def main(): + root = tk.Tk() + root.title("会议投屏运维总控") + root.geometry("400x300") + + btn1 = tk.Button(root, text="模式1:1台投屏→31台接收", command=mode1, width=30, height=2) + btn1.pack(pady=10) + + btn2 = tk.Button(root, text="模式2:投屏+HDMI输出到矩阵", command=mode2, width=30, height=2) + btn2.pack(pady=10) + + btn3 = tk.Button(root, text="模式3:会议流→32台接收", command=mode3, width=30, height=2) + btn3.pack(pady=10) + + btn4 = tk.Button(root, text="停止所有服务", command=stop_all, width=30, height=2) + btn4.pack(pady=10) + + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/push_screen.py b/push_screen.py new file mode 100644 index 0000000..e7dc176 --- /dev/null +++ b/push_screen.py @@ -0,0 +1,50 @@ +import subprocess +import psutil +import os +import time +import tkinter as tk +from tkinter import messagebox + +# 配置项(仅需改这里的服务器IP) +SERVER_IP = "192.168.1.100" # MediaMTX服务器内网IP +MEDIAMTX_PATH = r"D:\ScreenCast\mediamtx\mediamtx.exe" +FFMPEG_PATH = r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe" +STREAM_PATH = "screen" # 投屏流路径(模式1/2) + +# 检查MediaMTX是否运行 +def check_mediamtx_running(): + for proc in psutil.process_iter(['name']): + if proc.info['name'] == 'mediamtx.exe': + return True + return False + +# 启动MediaMTX(如果未运行) +def start_mediamtx(): + if not check_mediamtx_running(): + subprocess.Popen([MEDIAMTX_PATH], cwd=r"D:\ScreenCast\mediamtx", creationflags=subprocess.CREATE_NEW_CONSOLE) + time.sleep(3) # 等待启动 + messagebox.showinfo("提示", "MediaMTX已启动!") + +# 屏幕推流(全屏) +def push_full_screen(): + start_mediamtx() + # 封装FFmpeg命令,低延迟推流 + cmd = [ + FFMPEG_PATH, + "-f", "gdigrab", "-framerate", "30", "-i", "desktop", + "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", + "-f", "rtsp", f"rtsp://{SERVER_IP}:8554/{STREAM_PATH}" + ] + subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE) + messagebox.showinfo("提示", f"全屏投屏已启动!接收端可打开浏览器访问:\n http://{SERVER_IP}:8889/webrtc.html?src=screen ") + +# 主界面(点击按钮即可) +if __name__ == "__main__": + root = tk.Tk() + root.title("投屏源控制") + root.geometry("300x150") + + btn1 = tk.Button(root, text="一键全屏投屏", command=push_full_screen, width=20, height=2) + btn1.pack(pady=20) + + root.mainloop() \ No newline at end of file