初始化项目:添加README和核心Python脚本
This commit is contained in:
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@@ -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/
|
||||||
68
README.md
Normal file
68
README.md
Normal file
@@ -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台终端、停止所有服务),适合管理员操作。
|
||||||
38
auto_receive.py
Normal file
38
auto_receive.py
Normal file
@@ -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)
|
||||||
97
manage_server.py
Normal file
97
manage_server.py
Normal file
@@ -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()
|
||||||
50
push_screen.py
Normal file
50
push_screen.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user