Files
meetingroom-netscreen/README.md

560 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 会议投屏系统
## 项目简介
面向32台终端的内网低延迟屏幕广播系统支持网络投屏、硬件接口互通、一键自动化运维。
本项目提供 **Python (PySide6)****Rust (egui)** 两个版本的投屏控制客户端。
## 需求说明
当前会议投屏需求涉及通过视频矩阵系统向32台终端设备进行同步投屏。具体模式可分为以下两类
1. **模式1主播投屏**单台终端设备作为信号源向MediaMTX服务器投送本地画面流的形式其余31台终端设备读取MediaMTX服务器的流。
- **特殊情况**单独架设1台机器用浏览器读取流然后通过HDMI线将显示画面输出到矩阵系统矩阵再发送给第三方视频会议系统。这台机器可利用墙上的插座。
2. **模式2视频会议**在MediaMTX服务器上用采集卡和FFmpeg读取设备内容发布流全部32台终端设备用浏览器读取MediaMTX服务器的流。
技术说明上述32台终端设备均通过有线网络实现互联互通。
## 解决方案
参考文档:<https://docs.qq.com/doc/DQXFpdHN5Rk9yRGpC>
## 客户端版本对比
| 特性 | Python (PySide6) | Rust (egui) |
|------|------------------|-------------|
| **GUI 框架** | PySide6 (Qt) | egui (即时模式) |
| **编译方式** | PyInstaller | Cargo + GNU 工具链 |
| **文件大小** | ~50MB | ~4MB |
| **启动速度** | 较慢 | 快 |
| **依赖** | 需 Python 运行时 | 独立可执行文件 |
| **界面风格** | 原生 Windows 风格 | 自定义风格 |
## 整体部署步骤(细化版)
### 步骤1MediaMTX服务器端
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 服务器 IP192.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台终端、停止所有服务适合管理员操作。