# 会议投屏系统 ## 项目简介 面向32台终端的内网低延迟屏幕广播系统,支持网络投屏、硬件接口互通、一键自动化运维。 本项目提供 **Python (PySide6)** 和 **Rust (egui)** 两个版本的投屏控制客户端。 ## 需求说明 当前会议投屏需求涉及通过视频矩阵系统向32台终端设备进行同步投屏。具体模式可分为以下两类: 1. **模式1(主播投屏)**:单台终端设备作为信号源,向MediaMTX服务器投送本地画面(流的形式),其余31台终端设备读取MediaMTX服务器的流。 - **特殊情况**:单独架设1台机器,用浏览器读取流,然后通过HDMI线将显示画面输出到矩阵系统,矩阵再发送给第三方视频会议系统。这台机器可利用墙上的插座。 2. **模式2(视频会议)**:在MediaMTX服务器上用采集卡和FFmpeg读取设备内容,发布流,全部32台终端设备用浏览器读取MediaMTX服务器的流。 技术说明:上述32台终端设备均通过有线网络实现互联互通。 ## 解决方案 参考文档: ## 客户端版本对比 | 特性 | Python (PySide6) | Rust (egui) | |------|------------------|-------------| | **GUI 框架** | PySide6 (Qt) | egui (即时模式) | | **编译方式** | PyInstaller | Cargo + GNU 工具链 | | **文件大小** | ~50MB | ~4MB | | **启动速度** | 较慢 | 快 | | **依赖** | 需 Python 运行时 | 独立可执行文件 | | **界面风格** | 原生 Windows 风格 | 自定义风格 | ## 整体部署步骤(细化版) ### 步骤1:MediaMTX服务器端 1. **下载并解压MediaMTX** - 下载地址:`https://github.com/bluenviron/mediamtx/releases`(windows-amd64版) - 解压到 `D:\ScreenCast\mediamtx` 目录 2. **配置MediaMTX** - 编辑 `D:\ScreenCast\mediamtx\mediamtx.yml` 文件: - 设置 `webRTCAddress` 和 `rtmpAddress` - 添加 `screen` 路径配置,暂时不设鉴权 3. **启动MediaMTX** - 运行 `mediamtx.exe` - 观察控制台是否监听对应端口 4. **安装依赖** - 安装Python:`https://www.python.org/downloads/windows/`(勾选「Add Python to PATH」) - 安装FFmpeg:`https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z`(解压到 `D:\ScreenCast\ffmpeg`) - 安装Python依赖(管理员cmd执行): ``` pip install pyautogui requests subprocess32 psutil mss numpy ``` 5. **开启权限** - 给服务器开启「屏幕录制权限」(设置→隐私和安全性→屏幕录制→允许 Python/CMD) ## 模式1:主播投屏(本机屏幕 → MediaMTX → 多终端) 单台终端作为信号源,将本机屏幕画面推送到MediaMTX服务器,其他终端通过浏览器观看。 ### 主播端:推送本机屏幕 **方案A:使用 Rust 客户端(推荐)** 1. **下载预编译版本** - 从 Releases 下载 `push_screen.exe` - 或使用源码自行编译(见下方编译说明) 2. **运行程序** - 双击运行 `push_screen.exe` - 点击「设置」按钮配置服务器IP和FFmpeg路径 - 点击「开始全屏投屏」按钮开始推流 **方案B:使用 Python 客户端** - 双击运行 `push_screen.py` - 点击「一键全屏投屏」按钮 - 脚本会自动启动MediaMTX并开始推流 **方案C:使用mss抓屏+FFmpeg推流** ```python import subprocess import time import mss import numpy as np # ---------- 配置 ---------- RTMP_URL = "rtmp://服务器IP:1935/screen" # MediaMTX 的推流地址 FPS = 24 WIDTH, HEIGHT = 1280, 720 # 初始化屏幕捕获 with mss.mss() as sct: monitor = sct.monitors[1] # 主显示器 # FFmpeg 命令,编码为 H.264 并推 RTMP command = [ 'ffmpeg', '-y', '-f', 'rawvideo', '-vcodec', 'rawvideo', '-pix_fmt', 'bgr24', '-s', f'{WIDTH}x{HEIGHT}', '-r', str(FPS), '-i', '-', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-preset', 'ultrafast', '-f', 'flv', RTMP_URL ] process = subprocess.Popen(command, stdin=subprocess.PIPE) last_time = time.time() try: while True: img = np.array(sct.grab(monitor)) frame = img[..., :3] process.stdin.write(frame.tobytes()) elapsed = time.time() - last_time sleep_time = (1.0 / FPS) - elapsed if sleep_time > 0: time.sleep(sleep_time) last_time = time.time() except KeyboardInterrupt: print("用户停止推流") finally: process.stdin.close() process.wait() ``` ### 观众端:通过浏览器观看 1. **打开浏览器** - 在任意观众机上打开 Chrome 浏览器 2. **访问流地址** - 输入地址:`http://服务器IP:8889/screen` - MediaMTX 会自动处理 WebRTC 协商,把 RTMP 流转成 WebRTC 给浏览器 - 确认能看到主播机屏幕画面 3. **批量部署** - 在31台终端上运行 `auto_receive.py` 脚本 - 脚本会自动打开Chrome并跳转到流地址 ### 特殊情况:HDMI矩阵输出 单独架设1台机器,通过HDMI线将显示画面输出到视频矩阵系统。 1. **准备一台小机器** - 配置要求较低,能运行Chrome浏览器即可 2. **访问流地址** - 打开浏览器访问:`http://服务器IP:8889/screen` 3. **HDMI输出到矩阵** - 通过HDMI线将小机器连接到视频矩阵系统 - 小机器播放的流画面将输出到矩阵系统 ## 模式2:视频会议(采集卡 → MediaMTX → 多终端) 在MediaMTX服务器上用采集卡和FFmpeg读取外部视频信号,发布流,全部32台终端用浏览器观看。 ### 步骤1:安装FFmpeg - 下载FFmpeg:`https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z` - 解压到 `D:\ScreenCast\ffmpeg` 目录 - 确保 `D:\ScreenCast\ffmpeg\bin\ffmpeg.exe` 可用 ### 步骤2:列出DirectShow设备 在命令行执行以下命令,列出所有 DirectShow 设备: ```bat ffmpeg -list_devices true -f dshow -i dummy ``` 输出示例: ``` [dshow @ ...] DirectShow video devices [dshow @ ...] "USB Video Device" [dshow @ ...] "AVerMedia HD Capture" ``` 记下要使用的设备名称。 ### 步骤3:配置MediaMTX 编辑 `mediamtx.yml`,添加 `hdmi` 路径配置: ```yaml paths: hdmi: # source 不写,或者写 publisher,表示"由客户端推流进来" # source: publisher # 局域网可以先不开认证 # publishUser: "" # publishPass: "" # 有读者时才拉源(这里用不到,因为是 publisher 模式) sourceOnDemand: no ``` ### 步骤4:启动MediaMTX ```bat ./mediamtx ``` 看到日志里 RTMP/RTSP/WebRTC 端口监听就表示启动成功。 ### 步骤5:用FFmpeg采集采集卡并推流 假设: - MediaMTX 服务器 IP:192.168.1.100 - 推流路径名:hdmi - DirectShow 设备名称:"AVerMedia HD Capture" ```bat ffmpeg -f dshow -i video="AVerMedia HD Capture" ^ -c:v libx264 -preset ultrafast -tune zerolatency ^ -profile:v baseline -pix_fmt yuv420p ^ -b:v 4000k -maxrate 4000k -bufsize 8000k ^ -g 30 ^ -f flv rtmp://192.168.1.100/hdmi ``` ### 步骤6:客户端观看 在浏览器中直接访问: ``` http://192.168.1.100:8889/hdmi ``` MediaMTX 会返回内置的 WebRTC 播放页面,浏览器会自动播放HDMI信号。 所有32台终端均可通过浏览器访问上述地址观看视频会议内容。 ## Rust 客户端编译指南 ### 环境准备 1. **安装 Rust** - 下载地址:`https://rustup.rs/` - 安装时选择 `x86_64-pc-windows-gnu` 目标 2. **安装 MinGW-w64**(GNU 工具链) - 推荐通过 MSYS2 安装:`pacman -S mingw-w64-x86_64-toolchain` - 或下载 winlibs MinGW-w64 3. **添加 Rust 目标** ```bash rustup target add x86_64-pc-windows-gnu ``` ### 项目结构 ``` push_screen_rust/ ├── Cargo.toml # 项目配置 ├── build.rs # 构建脚本(用于嵌入图标) ├── app.rc # Windows 资源文件 ├── vi.ico # 应用程序图标 └── src/ └── main.rs # 主程序 ``` ### 依赖库对照(PySide6 vs Rust) | Python/PySide6 | Rust 等效库 | 用途 | |----------------|-------------|------| | PySide6 (Qt) | `eframe` + `egui` | GUI 框架 | | `QThread` | `std::thread` + `mpsc` | 多线程 | | `QTimer` | `std::time::Duration` + 线程休眠 | 定时器 | | `socket` | `std::net::TcpStream` | 网络检测 | | `subprocess` | `std::process::Command` | 进程管理 | | `json` | `serde` + `serde_json` | JSON 序列化 | | `logging` | `tracing` + `tracing-subscriber` | 日志记录 | | `requests` | `reqwest` | HTTP 客户端 | ### 编译参数对照(PyInstaller vs Cargo) | PyInstaller 参数 | Cargo/Rust 等效配置 | 说明 | |------------------|---------------------|------| | `--onefile` | `lto = true` + `strip = true` | 优化体积 | | `--windowed` | `#![windows_subsystem = "windows"]` | 隐藏控制台 | | `--icon=vi.ico` | `windres app.rc -O coff -o app.res` + 链接 | 嵌入图标 | | `--noconsole` | `windows_subsystem` 属性 | 无控制台窗口 | ### 编译步骤 1. **进入项目目录** ```bash cd push_screen_rust ``` 2. **编译资源文件(图标)** ```bash windres app.rc -O coff -o app.res ``` 3. **配置构建脚本**(`build.rs`) ```rust use std::env; fn main() { let target = env::var("TARGET").unwrap(); if target.contains("windows") { println!("cargo:rustc-link-arg=app.res"); } println!("cargo:rerun-if-changed=app.rc"); println!("cargo:rerun-if-changed=vi.ico"); } ``` 4. **配置 Cargo.toml** ```toml [package] name = "push_screen_rust" version = "0.1.0" edition = "2021" build = "build.rs" [dependencies] eframe = { version = "0.24", features = ["default"] } egui = "0.24" tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" reqwest = { version = "0.11", features = ["json"] } tracing = "0.1" tracing-subscriber = "0.3" winapi = { version = "0.3", features = ["winuser", "windef", "wingdi", ...] } [profile.release] opt-level = 3 lto = true # strip = true # 取消注释以进一步减小体积 [[bin]] name = "push_screen" path = "src/main.rs" ``` 5. **编译发布版本** ```bash cargo build --release --target x86_64-pc-windows-gnu ``` 6. **输出文件** - 位置:`target/x86_64-pc-windows-gnu/release/push_screen.exe` - 大小:约 4-5 MB ### 关键代码说明 **隐藏控制台窗口** ```rust #![windows_subsystem = "windows"] ``` **设置 UTF-8 编码(解决中文乱码)** ```rust #[cfg(windows)] extern "system" { fn SetConsoleOutputCP(wCodePageID: u32) -> i32; fn SetConsoleCP(wCodePageID: u32) -> i32; } fn set_utf8_encoding() { #[cfg(windows)] unsafe { SetConsoleOutputCP(65001); // UTF-8 SetConsoleCP(65001); } } ``` **加载中文字体** ```rust fn configure_fonts(ctx: &egui::Context) { let mut fonts = egui::FontDefinitions::default(); // 从系统加载中文字体 let font_paths = [ r"C:\Windows\Fonts\msyh.ttc", // 微软雅黑 r"C:\Windows\Fonts\simsun.ttc", // 宋体 ]; for font_path in &font_paths { if let Ok(font_data) = std::fs::read(font_path) { // 加载字体... } } ctx.set_fonts(fonts); } ``` ## 测试方案 使用1台台式机模拟MediaMTX服务器,2台笔记本分别模拟信号源和观众,测试两种场景。 ### 设备准备 | 设备 | 角色 | IP地址示例 | |------|------|-----------| | 台式机 | MediaMTX服务器 | 192.168.1.100 | | 笔记本A | 信号源(主播) | 192.168.1.101 | | 笔记本B | 观众端 | 192.168.1.102 | ### 前置条件 1. 三台设备处于同一局域网 2. 台式机已安装MediaMTX和FFmpeg 3. 笔记本A已安装Python和FFmpeg 4. 所有设备关闭防火墙或开放对应端口 ### 场景1测试:主播投屏 **拓扑**:笔记本A(信号源)→ 台式机(MediaMTX)→ 笔记本B(观众) **步骤**: 1. **台式机启动MediaMTX** ``` D:\ScreenCast\mediamtx\mediamtx.exe ``` 确认控制台显示端口监听正常 2. **笔记本A启动推流** - 运行 `push_screen.exe` (Rust) 或 `push_screen.py` (Python) - 或使用命令行推流: ```bat ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -tune zerolatency -f rtsp rtsp://192.168.1.100:8554/screen ``` 3. **笔记本B观看** - 打开Chrome浏览器 - 访问:`http://192.168.1.100:8889/screen` - 确认能看到笔记本A的屏幕画面 **验证点**: - [ ] 推流端无错误 - [ ] MediaMTX日志显示有客户端连接 - [ ] 观众端能流畅播放画面 - [ ] 延迟在可接受范围内(<1秒) ### 场景2测试:视频会议(采集卡模式) **拓扑**:外部HDMI信号 → 台式机(采集卡+FFmpeg)→ 台式机(MediaMTX)→ 笔记本A/B(观众) **前置条件**:台式机已连接视频采集卡 **步骤**: 1. **列出采集卡设备** ```bat ffmpeg -list_devices true -f dshow -i dummy ``` 记录设备名称 2. **台式机启动MediaMTX** ``` D:\ScreenCast\mediamtx\mediamtx.exe ``` 3. **台式机启动FFmpeg推流** ```bat ffmpeg -f dshow -i video="采集卡设备名称" -c:v libx264 -preset ultrafast -tune zerolatency -f flv rtmp://192.168.1.100/hdmi ``` 4. **笔记本A和B观看** - 打开Chrome浏览器 - 访问:`http://192.168.1.100:8889/hdmi` - 确认能看到采集卡的HDMI输入画面 **验证点**: - [ ] 采集卡设备被正确识别 - [ ] FFmpeg推流无错误 - [ ] MediaMTX正确转发流 - [ ] 多客户端能同时观看 ### 测试记录表 | 测试项 | 预期结果 | 实际结果 | 是否通过 | |--------|----------|----------|----------| | MediaMTX启动 | 端口监听正常 | | | | 场景1推流 | 无错误 | | | | 场景1观看 | 画面流畅 | | | | 场景2推流 | 无错误 | | | | 场景2多客户端 | 至少2台同时观看 | | | ## 核心文件结构(统一放在服务器D:\ScreenCast) ``` D:\ScreenCast ├── mediamtx/ # MediaMTX解压目录 │ └── mediamtx.exe ├── ffmpeg/ # FFmpeg解压目录 │ └── bin/ffmpeg.exe ├── push_screen_rust/ # Rust 客户端源码 │ ├── Cargo.toml │ ├── build.rs │ ├── app.rc │ ├── vi.ico │ └── src/main.rs ├── push_screen.exe # Rust 编译后的可执行文件(推荐) ├── push_screen.py # Python 投屏源推流脚本 ├── auto_receive.py # 终端自动打开浏览器脚本 └── manage_server.py # 运维总控脚本 ``` ## 核心脚本说明 ### Rust 客户端(push_screen_rust) **功能**:轻量级投屏控制客户端,单文件可执行,无需运行时依赖。 **特性**: - 实时状态检测(FFmpeg、服务器、端口) - 配置持久化(config.json) - 一键开始/停止推流 - 中文字体支持 - 自定义应用程序图标 **编译命令**: ```bash cd push_screen_rust windres app.rc -O coff -o app.res cargo build --release --target x86_64-pc-windows-gnu ``` ### Python 客户端(push_screen.py) **功能**:基于 PySide6 的投屏控制界面,功能完善。 **特性**: - 可视化配置界面 - 实时状态指示器 - 日志显示 - 设置对话框 **运行命令**: ```bash python push_screen.py ``` ### 终端自动打开浏览器脚本(auto_receive.py) **功能**:双击脚本自动打开Chrome浏览器(全屏),直接跳转到投屏/会议流页面;支持批量部署到32台终端,无需手动输入地址。 ### 运维总控脚本(manage_server.py) **功能**:一站式运维(启动MediaMTX、切换两种模式、批量控制32台终端、停止所有服务),适合管理员操作。