
本文详解如何基于 aiortc 构建 webrtc 实时桌面流服务,通过自定义 `videostreamtrack` 集成 `mss` 屏幕捕获,替代传统静态视频文件播放,实现低延迟、高帧率的浏览器端实时桌面共享。
在使用 aiortc 构建 WebRTC 应用时,MediaPlayer 是快速启动视频流的便捷选择——但它仅支持预存文件(如 MP4)或设备输入(如摄像头),无法直接对接动态画面源(如屏幕捕获)。要实现真正的实时桌面共享,必须绕过 MediaPlayer,转而继承 aiortc.VideoStreamTrack,构建一个可按需生成帧的自定义视频轨道。
以下是完整、可运行的解决方案,已适配最新 aiortc(v1.6+)、mss 和 av(v12+)版本,并兼顾线程安全与资源释放:
✅ 核心组件:ScreenCapturing 自定义轨道类
from aiortc import VideoStreamTrack
from av import VideoFrame
import numpy as np
import threading
import asyncio
import queue
import mss
class ScreenCapturing(VideoStreamTrack):
"""
基于 mss 的实时桌面捕获轨道,兼容 aiortc 异步帧推送模型。
支持多显示器选择(默认主屏 monitor[1]),自动丢弃旧帧防卡顿。
"""
def __init__(self, monitor_index: int = 1) -> None:
super().__init__()
self.monitor_index = monitor_index
self.queue = queue.Queue(maxsize=10) # 限容队列,避免内存堆积
self._thread = None
self._stopped = False
async def recv(self) -> VideoFrame:
"""aiortc 调用此方法获取下一帧(协程)"""
try:
# 阻塞等待新帧(超时避免永久挂起)
img = self.queue.get(timeout=5.0)
except queue.Empty:
# 超时则返回上一帧(保持画面连续性)或抛异常
raise Exception("Screen capture timeout — check mss thread health")
# mss 返回 RGBA,需转为 RGB 并适配 av 的 bgr24 格式(注意通道顺序)
img_rgb = img[:, :, :3] # 去 alpha
frame = VideoFrame.from_ndarray(img_rgb, format="rgb24")
# 注意:aiortc 1.6+ 推荐使用 rgb24;若需 bgr24,请 cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
pts, time_base = await self.next_timestamp()
frame.pts = pts
frame.time_base = time_base
return frame
def start_capture(self):
"""在独立线程中持续捕获屏幕"""
def _capture_loop():
with mss.mss() as sct:
monitor = sct.monitors[self.monitor_index]
while not self._stopped:
try:
# grab() 返回 PIL.Image-like 对象,转为 numpy array
im = sct.grab(monitor)
im_np = np.array(im)
# 尝试放入队列;若满则丢弃最旧帧(非阻塞)
try:
self.queue.put_nowait(im_np)
except queue.Full:
try:
self.queue.get_nowait() # 弹出旧帧
self.queue.put_nowait(im_np)
except queue.Empty:
pass
except Exception as e:
print(f"[ScreenCapture] Error: {e}")
break
self._thread = threading.Thread(target=_capture_loop, daemon=True)
self._thread.start()
def stop(self):
"""优雅停止捕获线程"""
self._stopped = True
if self._thread and self._thread.is_alive():
self._thread.join(timeout=1.0)✅ 集成到 Web 服务器逻辑
替换原 create_local_tracks() 函数,并更新 offer 处理逻辑:
# 替换原 create_local_tracks 函数
async def create_local_tracks():
track = ScreenCapturing(monitor_index=1) # 可设为 0 获取所有显示器拼接图
track.start_capture()
return track
# 在 offer 处理中调用
async def offer(request):
params = await request.json()
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
pcs.add(pc)
# ✅ 关键变更:使用自定义屏幕轨道,而非 MediaPlayer
video = await create_local_tracks()
pc.addTrack(video)
@pc.on("iceconnectionstatechange")
async def on_iceconnectionstatechange():
print(f"ICE state: {pc.iceConnectionState}")
if pc.iceConnectionState == "failed":
await pc.close()
pcs.discard(pc)
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return web.Response(
content_type="application/json",
text=json.dumps(
{"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
),
)⚠️ 重要注意事项与优化建议
- 显示器索引:mss.monitors 是从 0 开始的列表,monitors[0] 表示所有屏幕拼接区域,monitors[1] 通常为主屏。可通过 print(mss.mss().monitors) 确认。
- 帧率与性能:mss 默认无帧率限制,实际取决于 CPU/GPU 和网络带宽。如需限帧(如 30fps),可在 _capture_loop 中添加 time.sleep(1/30)。
- 色彩空间一致性:VideoFrame.from_ndarray(..., format="rgb24") 是推荐方式;若出现颜色异常(如偏紫),检查是否误用了 bgr24 或未正确转换通道。
- 资源清理:务必在 on_shutdown 中显式调用 track.stop()(需在 ScreenCapturing 中增强 stop() 方法并暴露引用),否则捕获线程可能残留。
- 跨平台兼容性:mss 在 Windows/macOS/Linux 均可用,但 macOS 需提前授权「屏幕录制」权限(系统设置 → 隐私与安全性 → 屏幕录制)。
- 错误处理增强:生产环境应捕获 mss 初始化失败、显示器断开等异常,并提供降级策略(如返回黑帧或断开连接)。
通过以上改造,你的服务即可从「播放视频文件」升级为「实时桌面广播」,真正发挥 WebRTC 的实时通信能力。整个流程不依赖 FFmpeg 进程,纯 Python 实现,轻量可控,适合嵌入远程协作、在线教学或监控看板等场景。










