16 KiB
会议投屏系统
项目简介
面向32台终端的内网低延迟屏幕广播系统,支持网络投屏、硬件接口互通、一键自动化运维。
本项目提供 Python (PySide6) 和 Rust (egui) 两个版本的投屏控制客户端。
需求说明
当前会议投屏需求涉及通过视频矩阵系统向32台终端设备进行同步投屏。具体模式可分为以下两类:
- 模式1(主播投屏):单台终端设备作为信号源,向MediaMTX服务器投送本地画面(流的形式),其余31台终端设备读取MediaMTX服务器的流。
- 特殊情况:单独架设1台机器,用浏览器读取流,然后通过HDMI线将显示画面输出到矩阵系统,矩阵再发送给第三方视频会议系统。这台机器可利用墙上的插座。
- 模式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 风格 | 自定义风格 |
整体部署步骤(细化版)
步骤1:MediaMTX服务器端
- 下载并解压MediaMTX
- 下载地址:
https://github.com/bluenviron/mediamtx/releases(windows-amd64版) - 解压到
D:\ScreenCast\mediamtx目录
- 下载地址:
- 配置MediaMTX
- 编辑
D:\ScreenCast\mediamtx\mediamtx.yml文件:- 设置
webRTCAddress和rtmpAddress - 添加
screen路径配置,暂时不设鉴权
- 设置
- 编辑
- 启动MediaMTX
- 运行
mediamtx.exe - 观察控制台是否监听对应端口
- 运行
- 安装依赖
- 安装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
- 安装Python:
- 开启权限
- 给服务器开启「屏幕录制权限」(设置→隐私和安全性→屏幕录制→允许 Python/CMD)
模式1:主播投屏(本机屏幕 → MediaMTX → 多终端)
单台终端作为信号源,将本机屏幕画面推送到MediaMTX服务器,其他终端通过浏览器观看。
主播端:推送本机屏幕
方案A:使用 Rust 客户端(推荐)
-
下载预编译版本
- 从 Releases 下载
push_screen.exe - 或使用源码自行编译(见下方编译说明)
- 从 Releases 下载
-
运行程序
- 双击运行
push_screen.exe - 点击「设置」按钮配置服务器IP和FFmpeg路径
- 点击「开始全屏投屏」按钮开始推流
- 双击运行
方案B:使用 Python 客户端
- 双击运行
push_screen.py - 点击「一键全屏投屏」按钮
- 脚本会自动启动MediaMTX并开始推流
方案C:使用mss抓屏+FFmpeg推流
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()
观众端:通过浏览器观看
- 打开浏览器
- 在任意观众机上打开 Chrome 浏览器
- 访问流地址
- 输入地址:
http://服务器IP:8889/screen - MediaMTX 会自动处理 WebRTC 协商,把 RTMP 流转成 WebRTC 给浏览器
- 确认能看到主播机屏幕画面
- 输入地址:
- 批量部署
- 在31台终端上运行
auto_receive.py脚本 - 脚本会自动打开Chrome并跳转到流地址
- 在31台终端上运行
特殊情况:HDMI矩阵输出
单独架设1台机器,通过HDMI线将显示画面输出到视频矩阵系统。
- 准备一台小机器
- 配置要求较低,能运行Chrome浏览器即可
- 访问流地址
- 打开浏览器访问:
http://服务器IP:8889/screen
- 打开浏览器访问:
- 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 设备:
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 路径配置:
paths:
hdmi:
# source 不写,或者写 publisher,表示"由客户端推流进来"
# source: publisher
# 局域网可以先不开认证
# publishUser: ""
# publishPass: ""
# 有读者时才拉源(这里用不到,因为是 publisher 模式)
sourceOnDemand: no
步骤4:启动MediaMTX
./mediamtx
看到日志里 RTMP/RTSP/WebRTC 端口监听就表示启动成功。
步骤5:用FFmpeg采集采集卡并推流
假设:
- MediaMTX 服务器 IP:192.168.1.100
- 推流路径名:hdmi
- DirectShow 设备名称:"AVerMedia HD Capture"
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 客户端编译指南
环境准备
-
安装 Rust
- 下载地址:
https://rustup.rs/ - 安装时选择
x86_64-pc-windows-gnu目标
- 下载地址:
-
安装 MinGW-w64(GNU 工具链)
- 推荐通过 MSYS2 安装:
pacman -S mingw-w64-x86_64-toolchain - 或下载 winlibs MinGW-w64
- 推荐通过 MSYS2 安装:
-
添加 Rust 目标
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 属性 |
无控制台窗口 |
编译步骤
-
进入项目目录
cd push_screen_rust -
编译资源文件(图标)
windres app.rc -O coff -o app.res -
配置构建脚本(
build.rs)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"); } -
配置 Cargo.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" -
编译发布版本
cargo build --release --target x86_64-pc-windows-gnu -
输出文件
- 位置:
target/x86_64-pc-windows-gnu/release/push_screen.exe - 大小:约 4-5 MB
- 位置:
关键代码说明
隐藏控制台窗口
#![windows_subsystem = "windows"]
设置 UTF-8 编码(解决中文乱码)
#[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);
}
}
加载中文字体
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 |
前置条件
- 三台设备处于同一局域网
- 台式机已安装MediaMTX和FFmpeg
- 笔记本A已安装Python和FFmpeg
- 所有设备关闭防火墙或开放对应端口
场景1测试:主播投屏
拓扑:笔记本A(信号源)→ 台式机(MediaMTX)→ 笔记本B(观众)
步骤:
-
台式机启动MediaMTX
D:\ScreenCast\mediamtx\mediamtx.exe确认控制台显示端口监听正常
-
笔记本A启动推流
- 运行
push_screen.exe(Rust) 或push_screen.py(Python) - 或使用命令行推流:
ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -tune zerolatency -f rtsp rtsp://192.168.1.100:8554/screen - 运行
-
笔记本B观看
- 打开Chrome浏览器
- 访问:
http://192.168.1.100:8889/screen - 确认能看到笔记本A的屏幕画面
验证点:
- 推流端无错误
- MediaMTX日志显示有客户端连接
- 观众端能流畅播放画面
- 延迟在可接受范围内(<1秒)
场景2测试:视频会议(采集卡模式)
拓扑:外部HDMI信号 → 台式机(采集卡+FFmpeg)→ 台式机(MediaMTX)→ 笔记本A/B(观众)
前置条件:台式机已连接视频采集卡
步骤:
-
列出采集卡设备
ffmpeg -list_devices true -f dshow -i dummy记录设备名称
-
台式机启动MediaMTX
D:\ScreenCast\mediamtx\mediamtx.exe -
台式机启动FFmpeg推流
ffmpeg -f dshow -i video="采集卡设备名称" -c:v libx264 -preset ultrafast -tune zerolatency -f flv rtmp://192.168.1.100/hdmi -
笔记本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)
- 一键开始/停止推流
- 中文字体支持
- 自定义应用程序图标
编译命令:
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 的投屏控制界面,功能完善。
特性:
- 可视化配置界面
- 实时状态指示器
- 日志显示
- 设置对话框
运行命令:
python push_screen.py
终端自动打开浏览器脚本(auto_receive.py)
功能:双击脚本自动打开Chrome浏览器(全屏),直接跳转到投屏/会议流页面;支持批量部署到32台终端,无需手动输入地址。
运维总控脚本(manage_server.py)
功能:一站式运维(启动MediaMTX、切换两种模式、批量控制32台终端、停止所有服务),适合管理员操作。