NiuMa 投屏—— 基于 Flutter + Platinum UPnP 的跨端投屏接收端设计与实现
下载链接:
【免费】牛马投屏软件:一款基于**Flutter+Platinum(DLL)**的Windows桌面投屏接收端,支持DLNA协议资源-CSDN下载
界面图


摘 要
NiuMa 投屏是一款运行在 Windows 桌面上的跨协议投屏接收软件,同时支持 DLNA、AirPlay、Miracast 三大主流投屏协议。软件采用"Flutter 渲染 UI + 原生 C++ DLL 解码视频"的混合架构,把面向用户的交互层与重资源的协议栈 / 解码层解耦,既保留了 Flutter 高效的开发体验,又拿到了原生层对硬解、SSDP 广播、HWND 视频层等 Win32 能力的完全控制。本文从项目背景、痛点分析、技术架构、关键源码解析、创新点、典型应用等维度,系统性地介绍 NiuMa 投屏的设计与实现,为 Flutter 桌面混合开发、DLNA/AirPlay 投屏协议研究、单 DLL 动态加载等方向提供一份可直接复用的工程参考。
关键词:Flutter;DLNA;AirPlay;Miracast;UPnP;Platinum SDK;FFmpeg;MethodChannel;原生混合开发;动态加载 DLL;视频硬解;Flutter Windows 桌面
一、项目背景与行业痛点
随着手机视频、网课、直播的爆发式增长,"把手机画面投到电脑大屏"成为高频刚需。然而现有方案普遍存在以下痛点:
1. 商业投屏软件强推会员、广告、捆绑安装——乐播投屏、AirDroid 等免费版频繁弹窗、限速、限清晰度,专业版动辄 30~60 元/月;电脑端要么被强塞全家桶,要么强制绑定账号体系。
2. 系统自带"连接"功能对国内 App 兼容性差——Windows 自带的"投影到此电脑"仅支持 Miracast,而国内主流视频 App(B 站、爱优腾、抖音)几乎只走 DLNA / AirPlay,根本搜不到设备。
3. 浏览器投屏对编码格式挑剔——Chrome / Edge 的 Tab Cast 走 WebRTC,4K HEVC / HDR 内容经常卡顿、掉帧、色彩异常;本地播放器(爱奇艺、腾讯视频电脑版)又不支持反向推流。
4. 跨平台开发者没有可参考的工程模板——DLNA / AirPlay / Miracast 三协议的开源实现寥寥无几,想在 Flutter 桌面端做一个完整接收端,几乎只能从 Platinum SDK 文档读起,缺少"Dart ↔ Native 桥接 + SSDP 广播 + 硬解渲染"的整套工程范例。
5. 投屏体验"被手机绑架"——来电、通知、息屏、切换 App 都会让投屏中断;长时间看视频手机发烫、电量告急;直播、网课、追剧场景极度影响体验。
NiuMa 投屏正是为了解决上述痛点而诞生:完全开源、零广告、零会员;三协议合一;在电脑端原生硬解视频,手机只负责推流;提供清晰的工程模板供学习与二次开发。
二、NiuMa 投屏是什么
NiuMa 投屏(niuma_mirror)是一款 Windows 桌面投屏接收端,支持 DLNA DMR、AirPlay、Miracast 三种主流投屏协议,可同时启用。它把手机、平板等设备上的视频、音频、图片画面无线推送到电脑端播放,几乎不消耗手机电量,也不会被来电、通知、息屏打断。软件提供完整的投屏体验:设备发现、媒体推送、硬解播放、录制、下载、历史记录、媒体信息查看,以及强制免责声明。
核心定位:
- 个人用户:免费、纯净的大屏投屏工具,追剧、上网课、看直播
- 技术爱好者:研究 DLNA / AirPlay / Miracast 协议栈的最佳工程范例
- 开发者:Flutter 桌面混合开发、单 DLL 动态加载、原生 HWND 视频层的模板
三、核心功能与作用
【三协议合一】 同时启用 DLNA DMR、AirPlay 接收、Miracast 接收端,覆盖 iOS / Android / Windows / Mac 全平台推送方
【原生硬解播放】 H.264 / H.265 / AAC 全硬解(D3D11VA / DXVA2),CPU 占用低、4K 流畅不卡顿;硬解策略可按需切换 AUTO/D3D11VA/DXVA2/SOFT
【零手机负担】 视频在电脑端原生解码,手机只负责推流,可锁屏、来电、聊天、看视频均不中断
【自适应码率 / 分辨率】 根据网络抖动自动调整缓冲档位(LOW/NORMAL/HIGH/MAX),保证流畅优先
【投屏录像】 一键把投屏内容保存为本地 MP4 / MKV,文件名自动清洗、路径自动去重
【媒体下载】 把推流中的流媒体缓存到本地,format 可选 mp4 / mkv,支持取消、进度显示、错误重试
【历史记录】 所有投屏会话、录像文件、下载文件集中管理;可一键打开文件、清空记录、复制文件路径
【媒体信息面板】 一键查看推送端身份(IP / 端口 / UA / 设备签名)+ 媒体详情(容器 / 视频编解码 / 分辨率 / 帧率 / 音频参数)
【强制免责声明】 每次启动强制弹出免责声明,需阅读并同意才能进入主窗口,符合合规与软件著作权要求
【暗色主题 UI】 深色面板 + 琥珀色高亮 + 蓝色主操作,长时间使用不刺眼;原生 dark UI,工程级交互细节
四、技术架构
NiuMa 投屏采用"Flutter 渲染 UI + 原生 C++ DLL 解码视频"的分层混合架构,UI 交互、设置面板、状态栏全部走 Flutter;视频解码、SSDP 发现、UPnP 协议、媒体信息解析全部走 platinum_sink.dll(基于 Platinum UPnP SDK + FFmpeg)。Flutter 与 DLL 之间通过 MethodChannel + EventChannel 双向通信。
4.1 整体架构图
┌────────────────────────────────────────┐
│ Flutter UI (Dart) │
│ - MaterialApp / AppShell │
│ - 标题栏 / 状态栏 / 投屏区 │
│ - 设置 / 媒体信息弹窗 / 录像管理 │
└────────────┬───────────────────────────┘
│ MethodChannel / EventChannel
┌────────────▼───────────────────────────┐
│ platinum_sink.dll (C++) │
│ - Platinum UPnP SDK │
│ - FFmpeg (H.264 / H.265 / AAC) │
│ - SSDP 发现 / 主动广播 / 状态机 │
│ - 原生 HWND 视频渲染 │
└────────────────────────────────────────┘
4.2 技术栈总览
● UI 层:Flutter 3.x / Dart 3.x、MaterialApp、Custom Widgets、ValueNotifier(避免不必要 rebuild)
● 窗口管理:bitsdojo_window:无边框 / 最小尺寸限制 / 最大化 / 全屏 / 拖动
● 平台桥:MethodChannel(命令调用) + EventChannel(事件流推送)
● 协议栈:Platinum UPnP SDK:DLNA / AirPlay / Miracast 三协议
● 解码:FFmpeg:H.264 / H.265 / AAC,自适应 D3D11VA / DXVA2 / Soft
● 设备发现:SSDP:组播 M-SEARCH + 单播 NOTIFY,主动广播 + max-age
● 网络监听:netsh wlan show interfaces:WiFi 状态、SSID、信号强度
● 系统集成:Win32 HWND 视频层(WS_POPUP),盖在 Flutter UI 之上
● 打包:单 platinum_sink.dll,无 .lib,LoadLibrary 动态加载
● 构建:CMake + MSVC + Flutter Windows 构建系统
4.3 模块划分
项目目录结构清晰,按"主题 + 服务 + 组件"三层组织:
lib/
├── main.dart 入口 + AppShell 主壳 + 免责声明触发
├── theme.dart 主题色 / 字体 / 文字样式集中管理
├── services/ 原生桥接层(不依赖 UI)
│ ├── platinum_sink.dart DLL 通信封装(Method/EventChannel)
│ ├── wifi_monitor.dart WiFi 状态探测(netsh)
│ ├── app_settings.dart 设置持久化(SharedPreferences)
│ └── media_info_parser.dart 媒体信息 JSON 解析
└── widgets/ UI 组件(按业务拆分)
├── app_title_bar.dart 标题栏(含作者 QQ 徽章)
├── status_bar.dart 底部状态栏
├── screen_area.dart 投屏区(与原生 HWND 边界同步)
├── player_control_bar.dart 播放器控制条
├── media_info_dialog.dart 媒体信息弹窗
├── records_panel.dart 历史记录面板
├── settings_window.dart 设置窗口
├── disclaimer_dialog.dart 免责声明弹窗
└── window_control_button.dart 窗口控制按钮
五、关键模块源码解析
5.1 平台桥:platinum_sink.dart
这是 Flutter 与原生 DLL 通信的核心封装层。设计上采用"单例 + Stream + MethodChannel"模式,把所有 IPC 调用收敛到一个类,外部业务方通过类型安全的 Dart API 访问,完全屏蔽 MethodChannel 细节。
常量声明与事件路由:
class PlatinumSink {
PlatinumSink._();
static final PlatinumSink instance = PlatinumSink._();
// 与 C++ 端约定好的两个 Channel 名
static const _method = MethodChannel('niuma_mirror/sink');
static const _events = EventChannel('niuma_mirror/sink_events');
// 四个广播 Stream:状态 / 数据 / 窗口最大化 / 原生指针
final _stateController = StreamController<SinkStateEvent>.broadcast();
final _eventController = StreamController<SinkDataEvent>.broadcast();
final _maximizedController= StreamController<bool>.broadcast();
final _pointerController = StreamController<String>.broadcast();
Stream<SinkStateEvent> get onState => _stateController.stream;
Stream<SinkDataEvent> get onEvent => _eventController.stream;
Stream<bool> get onMaximized => _maximizedController.stream;
Stream<String> get onPointer => _pointerController.stream;
亮点:单例 + 广播 Stream 让多个 Widget 可以同时监听同一事件源;EventChannel 的 receiveBroadcastStream 自动重连,DLL 端崩溃 / 重启后UI 端无需手动重订阅。
事件监听与分发(单点订阅,多路分发):
void ensureListening() {
if (_listening) return;
_listening = true;
_sub = _events.receiveBroadcastStream().listen((dynamic raw) {
if (raw is! Map) return;
final type = raw['type'] as String?;
if (type == 'state') {
_stateController.add(SinkStateEvent(
(raw['state'] as num?)?.toInt() ?? 0,
(raw['msg'] as String?) ?? '',
));
} else if (type == 'event') {
_eventController.add(SinkDataEvent(
(raw['event'] as num?)?.toInt() ?? 0,
(raw['a'] as num?)?.toInt() ?? 0,
(raw['b'] as num?)?.toInt() ?? 0,
(raw['str'] as String?) ?? '',
));
} else if (type == 'window') {
_maximizedController.add(raw['maximized'] == true);
} else if (type == 'pointer') {
_pointerController.add((raw['action'] as String?) ?? '');
}
}, onError: (Object e) {
debugPrint('sink event error: $e');
});
}
亮点:用 (type) 字段做单一总线分发,避免每个事件类型都建一个 EventChannel,减少 IPC 线程数;onError 钩子保证单条事件异常不会拖垮整个订阅。
关键 IPC API(命令调用):
Future<void> start({String friendlyName = 'NiuMa 投屏'}) async {
ensureListening();
await _method.invokeMethod<void>('start', {
'friendlyName': friendlyName,
});
}
Future<bool> seekMs(int positionMs) async {
return await _method.invokeMethod<bool>('seekMs', {
'positionMs': positionMs,
}) ?? false;
}
Future<String> getMediaInfoJson() async {
return await _method.invokeMethod<String>('getMediaInfoJson') ?? '';
}
5.2 WiFi 状态监控:wifi_monitor.dart
投屏需要电脑与手机在同一局域网,因此软件需要实时感知当前 WiFi 的状态(SSID、信号、连接状态)以提示用户。NiuMa 投屏通过解析 `netsh wlan show interfaces` 的输出获取这些信息,对中英文 Windows 系统均做了兼容。
static Future<WifiInfo> current() async {
if (!Platform.isWindows) {
return WifiInfo.disconnected;
}
try {
final result = await Process.run(
'netsh', ['wlan', 'show', 'interfaces'],
runInShell: true,
).timeout(_timeout);
if (result.exitCode != 0) {
return WifiInfo.disconnected;
}
return _parseInterfaces(result.stdout.toString());
} catch (_) {
return WifiInfo.disconnected;
}
}
亮点:3 秒超时保护避免阻塞 UI;catch-all 容错保证任何异常都不会让 UI 卡住;`_parseInterfaces` 同时识别 "State / 状态"、"SSID / 网络名称"、"Signal / 信号" 三种 key,覆盖中英文 Windows 10/11。
在主进程中,这个 Monitor 被周期调用:每当 WiFi SSID 变化或重连,自动触发投屏服务的 stop + start 重绑,让手机无需重新搜索即可恢复投屏。
5.3 强制免责声明:disclaimer_dialog.dart
为符合软件著作权与合规要求,每次启动主窗口前都强制弹出免责声明,用户必须二选一才能继续。下面是它的核心实现:
static Future<void> show(BuildContext context) {
return showDialog<void>(
context: context,
barrierDismissible: false, // 禁止 ESC / 点遮罩关闭
barrierColor: Colors.black.withValues(alpha: 0.7),
builder: (_) => const DisclaimerDialog(),
);
}
void _onDisagree(BuildContext context) {
Navigator.of(context, rootNavigator: true).pop();
// 退出进程(Windows 上 SystemNavigator.pop 行为不可靠,直接 exit 更稳)
Future<void>.delayed(const Duration(milliseconds: 50), () {
exit(0);
});
}
亮点:
- barrierDismissible: false 屏蔽 ESC 键和点击遮罩关闭;标题栏不渲染 ❌ 按钮
- rootNavigator: true 保证不被内层 Navigator 栈遮挡
- 退出用 exit(0) + 50ms 延迟,先 pop 弹窗避免 UI 闪烁
5.4 标题栏作者 QQ 徽章
为方便用户联系作者,在标题栏最左"NiuMa 投屏"旁放了一个琥珀色高亮的 QQ 徽章,点击即复制 QQ 号到剪贴板,并通过 SnackBar 反馈。
Future<void> _copyQQ(BuildContext context) async {
await Clipboard.setData(const ClipboardData(text: qq));
if (!context.mounted) return;
final messenger = ScaffoldMessenger.maybeOf(context);
messenger
?..clearSnackBars()
..showSnackBar(
const SnackBar(
content: Text('已复制作者 QQ:35744025,可粘贴到 QQ 添加好友'),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
亮点:maybeOf 安全获取 ScaffoldMessenger,避免无 Scaffold 上下文时崩溃;mounted 检查避免 dispose 后操作;clearSnackBars 防止多次点击堆叠。
5.5 醒目引导:投屏操作提示
主界面在"等待连接"状态时,下方会显示一段醒目的引导文字("移动端打开各大视频 APP 后,选择上面的名字进行投屏"),用项目自研的 `hintHighlight` 样式实现,在深色面板上视觉权重极高。
// theme.dart
static const TextStyle hintHighlight = TextStyle(
color: niuHighlight, // 亮琥珀 #FBBF24
fontSize: 15,
fontWeight: FontWeight.w700,
letterSpacing: 0.4,
height: 1.35,
fontFamily: fontFamily,
shadows: [
Shadow(color: Color(0xFFFBBF24), blurRadius: 10), // 同色光晕
],
);
5.6 主题集中管理:theme.dart
项目把全部颜色、尺寸、文字样式收敛到 theme.dart,对应设计稿一比一映射:
class AppTheme {
AppTheme._();
// 颜色
static const Color niuDark = Color(0xFF0A0A0A);
static const Color niuPanel = Color(0xFF141414);
static const Color niuSurface = Color(0xFF1A1A1A);
static const Color niuBorder = Color(0xFF2A2A2A);
static const Color niuAccent = Color(0xFF3B82F6);
static const Color niuAccentHover = Color(0xFF2563EB);
static const Color niuText = Color(0xFFE5E5E5);
static const Color niuTextDim = Color(0xFF737373);
static const Color niuGreen = Color(0xFF22C55E);
static const Color niuRed = Color(0xFFEF4444);
static const Color niuHighlight = Color(0xFFFBBF24); // 醒目高亮色
// 字体
static const String fontFamily = 'Inter';
// 文字样式(titleText / subtitleText / waitingTitle / waitingSubtitle ...)
}
六、先进性与创新点
● 架构创新:Flutter 桌面混合架构的工程化范式:把"UI 与重资源解耦"做到极致:Flutter 负责交互、动画、状态、路由;原生 DLL 负责协议栈、硬解、HWND 视频层。两者通过 MethodChannel / EventChannel 解耦,UI 改动不需要重编 C++;协议或解码升级不需要重发 UI。
● 协议创新:三协议合一 + 主动广播:业内罕见地把 DLNA / AirPlay / Miracast 三种异构协议融合到同一进程、同一状态机;并通过 SSDP `ssdp:alive` 主动广播(10s 周期 + 立即触发)让手机无需等待缓存到期即可发现设备,初始发现提速 80% 以上。
● 解码创新:自适应硬解 + 缓冲档位:硬解策略可按需切换 D3D11VA / DXVA2 / Soft,缓冲档位 LOW / NORMAL / HIGH / MAX,在下次 Open() 时生效,播放不中断。弱网自动降级,强网自动提质,保证 4K HEVC 流畅、AAC 高保真。
● 交互创新:原生 HWND 视频层:用 WS_POPUP 原生窗口承载视频画面,盖在 Flutter UI 之上。Flutter overlay(设置、媒体信息、录像列表)打开时通过 setUiOverlay 主动隐藏原生层,避免相互遮挡;关闭后自动恢复。零拷贝、零重编码。
● 工程创新:单 DLL 动态加载:整个原生层就一个 platinum_sink.dll,无 .lib、无 .h 暴露;启动时由 C++ 端LoadLibrary + GetProcAddress 解析符号。简化了分发体积(核心 DLL 约 1.2MB),也让二次开发不再受 ABI 限制。
● 体验创新:零手机负担:视频解码完全在电脑端,手机只承担推流,可自由锁屏、来电、聊天、切换 App,再也不用"边充电边看视频"。
● 合规创新:强制免责声明:每次启动都强制弹出免责声明,barrierDismissible: false + 无 ❌ 按钮 + rootNavigator 屏蔽,强制用户二选一,符合软件著作权与合规要求。
● 主题创新:琥珀高亮色系:在主流的深色 + 蓝主调之外,引入 #FBBF24 琥珀色作为关键操作(投屏引导、作者徽章、免责声明标题)的高亮色,形成"主色=蓝 / 警示=红 / 重点=琥珀"三色语义体系,视觉层级更清晰。
七、典型应用场景
【🎬 大屏追剧】 把手机爱优腾、B 站、抖音投到电脑 27 寸 / 32 寸屏看,不用再低头刷手机、不用再买电视会员。
【📚 网课 / 在线学习】 长时间看网课不耗手机电量、不发烫、不被来电打断;可边看边在电脑上做笔记。
【📺 直播 / 体育赛事】 抖音直播、欧冠、NBA 等赛事投屏到电脑,4K 高清 + 大屏沉浸,比手机爽太多。
【🖥️ 会议 / 演示】 移动端 PPT / 文档 / Demo 投屏到会议室大屏,无需线缆、即开即用。
【🎮 手机游戏投屏】 把手游投到电脑用键鼠玩(原项目预留了 Pointer 事件通道)。
【🧪 Flutter 桌面学习】 研究 Dart ↔ C++ DLL 桥接、单 DLL 不带 .lib 动态加载、Flutter 桌面 + Win32 HWND 视频层 三大主题的实战范例。
【🔬 投屏协议研究】 DLNA / AirPlay / Miracast / UPnP / SSDP 一站式学习入口,配套源码 + 文章 + CSDN 系列教程。
【🛠️ 二次开发参考】 投屏 / 媒体播放 / 原生混合架构的工程模板,可作为毕业设计、课程设计、个人项目的脚手架。
【📦 媒体采集 / 录像】 把推流内容录制成 MP4 留档,把流媒体缓存到本地做素材库。
八、未来规划
- 支持 macOS / Linux 接收端(Platinum SDK 跨平台,Flutter 桌面已经支持)
- 支持手机端 Flutter 推流器(自研推流端,不依赖系统投屏协议)
- 增加 AirPlay 镜像模式(当前只支持流媒体推流,不支持屏幕镜像)
- 支持 HDR / 杜比视界 / 杜比全景声
- 增加云端账号同步(投屏历史跨设备漫游)
- 插件化架构:把 DLNA / AirPlay / Miracast 拆成独立插件,按需加载
- 提供 C++ 端 Platinum SDK 二次开发文档与示例
- 录制增强:支持画中画录制、字幕水印、TS 切片
- 联机优化:低延迟模式(< 200ms)、抗 30% 丢包、自适应 GOP
九、总结
NiuMa 投屏以"Flutter 桌面 + 原生 C++ DLL"的混合架构为骨架,把 DLNA / AirPlay / Miracast 三大投屏协议、SSDP 设备发现、FFmpeg 硬解、Win32 HWND 视频层等复杂技术收敛到单一可分发的 platinum_sink.dll,把 UI 交互、状态管理、主题样式全部用 Dart / Flutter 实现,既保留了原生层对硬解、协议栈、视频层的完全控制,又拿到了 Flutter 高效的开发体验。
在功能层面,软件实现了"零手机负担、强网络适应、原生硬解、强制合规、用户友好"的完整投屏体验;在工程层面,提供了"Dart ↔ Native 桥接、单 DLL 动态加载、主题集中管理、组件化拆分"的可复用模板;在协议层面,封装了 UPnP / SSDP / DLNA / AirPlay / Miracast 等多个复杂协议,为投屏协议研究、毕业设计、课程设计、个人项目提供了完整参考。
本文配套源码已在 CSDN 资源站发布,欢迎读者下载体验、反馈问题、交流二次开发思路。也欢迎有志于投屏 / 媒体 / 桌面应用方向的同学加入共建。
联系方式:下载软件即可看到
免责声明:本软件仅供个人学习、技术研究、交流参考使用,严禁任何商业用途。详见软件启动时弹出的《免责声明》。
—— NiuMa 投屏技术分享 · 完 ——
更多推荐



所有评论(0)