简介:这份资源面向需要在Unity WebGL环境下接入海康监控设备实时画面的开发者,重点解决浏览器端无法直接播放RTSP流、AVProVideo在WebGL平台配置繁琐的问题。作者重写了Http请求模块,并附带无水印版AVProVideo的WebGL发布设置指导配置文件,使用者只需手动修改Http请求配表中的字段值,即可发布WebGL并正常运行,本地服务部署采用nginx,有需要也可向作者咨询。压缩包共701个文件,约80.17MB,包含99个cs脚本、30个asset资源、28个so配置、26个dll动态库、25个shader着色器、14个aar与6个jar安卓依赖、12个mp4与12个png素材,以及avprovideo、jslib、asmdef等平台相关文件,覆盖代码、配置、依赖与演示素材。目前已有1213人学习下载,适合具备一定Unity与C#基础、需要快速验证WebGL监控播放方案的开发者参考,可省去从零调试请求模块与发布配置的时间。
1. 从一次监控画面接入翻车说起:这个 Demo 到底解决什么问题
去年帮一个做智慧园区的团队看问题,他们用 Unity 做了一套三维可视化大屏,想把园区里几路海康摄像头的实时画面贴到场景里的监控大屏模型上。结果卡了整整一周:Unity 自带的 VideoPlayer 拉 RTSP 流要么黑屏,要么延迟高到画面里人走了半分钟才动一下,切流的时候还容易把主线程卡死。后来换成 AVProVideo 才把这事跑通。这份「WebGL 平台基于 AVProVideo 播放海康设备监控实时画面 Demo 源码」,讲的就是这条链路怎么搭起来——用 Unity/C# 把海康设备的视频流接进来,通过 AVProVideo 在 WebGL 端渲染播放。它适合两类人:一类是正在做安防可视化、数字孪生大屏,需要把真实监控画面嵌进三维场景的开发者;另一类是已经踩过 VideoPlayer 的坑,想找一个能跑通的参考实现来对照改造的人。下面我按「这项目是什么 → 怎么跑起来 → 怎么改」的顺序,把源码拆开讲一遍。
2. 技术选型与整体架构:为什么是 AVProVideo 而不是 VideoPlayer
2.1 三个核心组件各自扮演什么角色
这个 Demo 的架构其实不复杂,拆开看就是三层:视频源层(海康设备)、播放中间层(AVProVideo)、渲染输出层(Unity WebGL)。难点不在单层,而在三层之间的衔接。
海康设备这边,主流取流方式有三种:RTSP 实时流、HTTP-FLV、HLS(m3u8)。RTSP 延迟最低,通常能压到 200~500ms,是监控场景的首选;HTTP-FLV 延迟略高但穿透性好;HLS 延迟最大,一般不用在实时监控里。这个 Demo 走的是 RTSP 为主的路子,因为标题里明确是「实时画面」,延迟是硬指标。
AVProVideo 是中间层的关键。它和 Unity 自带 VideoPlayer 最大的区别在于:VideoPlayer 在 WebGL 平台上的解码能力非常有限,很多编码格式直接不支持,而且它没有针对直播流的缓冲策略。AVProVideo 提供了更完整的流媒体支持、更细的缓冲控制,以及跨平台(Windows / Android / WebGL / iOS)统一的 API。对于「同一套代码要跑在 PC 端预览、又要发布到 WebGL」这种需求,AVProVideo 的抽象层能省掉大量平台判断代码。
渲染输出层就是 Unity 场景本身。视频最终要贴到一个 RenderTexture 上,再赋给场景里某个 Mesh 的材质,这样监控画面才能出现在三维大屏模型上,而不是简单地铺满屏幕。
2.2 目录结构长什么样
拿到源码后,先别急着点运行,把目录过一遍能省很多事。这类 Demo 的典型结构大致是这样:
AVProVideoHikvisionDemo/ ├── Assets/ │ ├── Scripts/ │ │ ├── VideoStreamController.cs // 核心播放控制 │ │ ├── HikvisionStreamConfig.cs // 流地址与参数配置 │ │ └── UIStatusPanel.cs // 状态显示与切换 │ ├── Materials/ │ │ └── MonitorScreen.mat // 监控大屏材质 │ ├── RenderTextures/ │ │ └── MonitorRT.renderTexture // 视频渲染目标 │ ├── Scenes/ │ │ └── MainScene.unity │ └── Plugins/ │ └── AVProVideo/ // AVProVideo 插件本体 ├── ProjectSettings/ └── Packages/Scripts目录是改造的主战场,Plugins/AVProVideo是第三方插件,一般不动。RenderTextures里那张 RT 是视频画面和三维场景之间的桥梁,分辨率设置直接影响性能和清晰度。
2.3 数据流是怎么走的
整个链路可以这样理解:海康设备推 RTSP 流 → AVProVideo 的 MediaPlayer 组件拉流并解码 → 解码后的帧写入 RenderTexture → RenderTexture 作为材质贴图赋给场景中的监控屏幕 Mesh → 摄像机渲染出最终画面。
这里有个容易被忽略的点:AVProVideo 在 WebGL 平台上的解码实际是交给浏览器底层能力做的,所以 WebGL 端能播什么格式,取决于浏览器支持什么。这也是为什么 WebGL 端经常需要额外的转流服务——把 RTSP 转成浏览器友好的 HLS 或 WebRTC。这个 Demo 在 PC 端可以直接拉 RTSP,但发布到 WebGL 时,通常需要在中间加一层流媒体服务做协议转换。常见做法是用流媒体服务把 RTSP 转成 HLS,然后 AVProVideo 在 WebGL 端播 HLS 地址。
提示:如果你的项目只在 PC 端跑,RTSP 直连就够了;一旦要发布 WebGL,务必提前规划好转流方案,否则会在发布阶段卡住。
3. 核心模块拆解:播放控制、流配置与状态管理三段代码
3.1 播放控制器:一个请求从配置到出画面的完整链路
VideoStreamController.cs是整个 Demo 的心脏。它负责初始化 MediaPlayer、设置视频路径、控制播放暂停、处理事件回调。下面这段是核心逻辑的还原:
using UnityEngine; using RenderHeads.Media.AVProVideo; public class VideoStreamController : MonoBehaviour { [SerializeField] private MediaPlayer mediaPlayer; // AVProVideo 播放器组件 [SerializeField] private RenderTexture targetTexture; // 视频渲染目标 [SerializeField] private string streamUrl; // 海康设备流地址 private void Start() { if (mediaPlayer == null) { Debug.LogError("MediaPlayer 未绑定,检查 Inspector 引用"); return; } // 订阅事件:播放就绪、首帧渲染、错误回调 mediaPlayer.Events.AddListener(OnVideoEvent); // 关键:WebGL 平台需要显式指定平台选项 mediaPlayer.PlatformOptionsWebGL.externalLibrary = ExternalLibrary.ExoPlayer; // 按实际插件版本选择 // 打开流地址,autoPlay 设为 true 表示加载完自动播放 mediaPlayer.OpenMedia( MediaPathType.AbsolutePathOrURL, streamUrl, autoPlay: true ); } private void OnVideoEvent(MediaPlayer mp, MediaPlayerEvent.EventType eventType, ErrorCode code) { switch (eventType) { case MediaPlayerEvent.EventType.ReadyToPlay: Debug.Log("流已就绪,开始播放"); break; case MediaPlayerEvent.EventType.FirstFrameReady: // 首帧到达后把画面写入 RenderTexture mp.FillTexture(targetTexture); break; case MediaPlayerEvent.EventType.Error: Debug.LogError($"播放出错,错误码:{code}"); // 这里可以触发重连逻辑 break; } } private void OnDestroy() { // 释放资源,避免 WebGL 端内存泄漏 if (mediaPlayer != null) { mediaPlayer.CloseMedia(); mediaPlayer.Events.RemoveListener(OnVideoEvent); } } }逐段说明。mediaPlayer和targetTexture通过 Inspector 拖拽绑定,这是 Unity 里最常见的引用方式,比代码里Find更稳。streamUrl是海康设备的取流地址,格式通常是rtsp://用户名:密码@IP:554/Streaming/Channels/101,其中101表示通道 1 的主码流,102是子码流。主码流清晰但码率高,子码流适合多路同时播放的场景。
OnVideoEvent是事件回调,FirstFrameReady这个事件很关键——只有首帧真正到达后,FillTexture才能拿到有效画面。如果在这里之前就调用,会得到一张黑图。Error分支里我一般会加一个重连计时器,因为监控流断线是常态,不能一出错就让整个画面卡死。
OnDestroy里的资源释放是血泪经验。WebGL 端的内存管理比 PC 端严格得多,不主动CloseMedia,切几次流之后浏览器标签页就可能因为内存溢出崩掉。
3.2 流配置模块:把地址和参数从代码里抽出来
HikvisionStreamConfig.cs做的是配置分离。硬编码流地址在 Demo 里能跑,但一旦要接多路摄像头就没法维护了。这个模块通常用一个 ScriptableObject 或者 JSON 来存配置:
using System; using UnityEngine; [Serializable] public class StreamEntry { public string deviceName; // 设备名称,用于 UI 显示 public string rtspUrl; // PC 端直连地址 public string hlsUrl; // WebGL 端转流后地址 public bool isMainStream; // 是否主码流 } [CreateAssetMenu(fileName = "StreamConfig", menuName = "监控/流配置")] public class HikvisionStreamConfig : ScriptableObject { public StreamEntry[] streams; // 根据运行平台返回对应地址 public string GetUrlForPlatform(StreamEntry entry) { #if UNITY_WEBGL && !UNITY_EDITOR return entry.hlsUrl; // WebGL 端走转流地址 #else return entry.rtspUrl; // 编辑器与 PC 端直连 #endif } }这里的设计意图是:同一份配置里同时存 RTSP 和 HLS 两个地址,运行时根据平台宏UNITY_WEBGL自动切换。这样开发阶段在编辑器里用 RTSP 调试,发布 WebGL 后自动走 HLS,不用改代码。isMainStream字段可以用来做码流切换,比如画面小的时候用子码流省带宽,双击放大时切主码流看细节。
3.3 状态面板:多路切换与异常提示
UIStatusPanel.cs负责把播放状态反馈给用户,同时处理多路流的切换。核心是一个状态机和一组按钮回调:
using UnityEngine; using UnityEngine.UI; public class UIStatusPanel : MonoBehaviour { [SerializeField] private VideoStreamController controller; [SerializeField] private Text statusText; [SerializeField] private Button[] streamButtons; private int currentIndex = -1; private void Awake() { for (int i = 0; i < streamButtons.Length; i++) { int idx = i; // 闭包捕获,避免所有按钮都指向最后一个 streamButtons[i].onClick.AddListener(() => SwitchStream(idx)); } } private void SwitchStream(int index) { if (index == currentIndex) return; currentIndex = index; statusText.text = $"正在切换到第 {index + 1} 路..."; controller.SwitchTo(index); // 内部先 CloseMedia 再 OpenMedia } public void UpdateStatus(string msg) { statusText.text = msg; } }Awake里那个int idx = i是经典坑:C# 闭包捕获的是变量引用而不是值,不这样写的话所有按钮点击都会切到最后一 路。SwitchStream里先判断是否重复点击,避免无谓的重新拉流。controller.SwitchTo内部的标准做法是先CloseMedia()释放当前流,再OpenMedia()打开新流,中间加一个短暂的状态提示,否则用户会以为卡死了。
4. 从零跑起来:环境准备、参数配置与发布验证
4.1 环境与依赖清单
跑这个 Demo 之前,先把环境对齐。下面这张表是这类项目的典型依赖:
| 项目 | 版本要求 | 说明 |
|---|---|---|
| Unity | 2020.3 LTS 及以上 | WebGL 模块需勾选安装 |
| AVProVideo | 2.x 版本 | 需确认支持 WebGL 平台 |
| 海康设备 | 支持 RTSP 取流 | 需开启 RTSP 并配置账号 |
| 浏览器 | 支持 WebGL 2.0 | Chrome / Edge 较新版本 |
| 转流服务 | 可选 | WebGL 端播 RTSP 时需要 |
AVProVideo 是付费插件,导入后要确认Plugins目录下 WebGL 相关的库文件完整。海康设备这边,需要在设备后台开启 RTSP 服务,并创建一个有取流权限的账号。取流地址的格式前面提过,101主码流、102子码流,端口默认 554。
4.2 配置流地址并首次运行
第一步,在 Unity 里找到HikvisionStreamConfig资源文件,把streams数组填上你的设备信息。如果只是先验证能不能跑通,可以只填一路:
{ "deviceName": "测试摄像头01", "rtspUrl": "rtsp://admin:你的密码@192.168.1.100:554/Streaming/Channels/101", "hlsUrl": "http://你的转流服务地址/live/stream01.m3u8", "isMainStream": true }第二步,打开MainScene,检查VideoStreamController上的MediaPlayer和TargetTexture引用是否为空。如果源码里已经绑好,跳过这步。
第三步,直接点 Play 在编辑器里运行。如果画面出不来,先看 Console 有没有报错,再看MediaPlayer组件上的状态指示。AVProVideo 的 Inspector 面板会显示当前播放状态和错误码,这是排查的第一现场。
第四步,发布 WebGL。在Build Settings里切换到 WebGL 平台,Player Settings里注意两点:Color Space建议用Linear,Graphics API在 WebGL 2.0 下选WebGL2。发布后用本地服务器打开(不能直接双击 html 文件,浏览器安全策略会拦截),推荐用python -m http.server起一个。
4.3 验证播放是否真的通了
怎么判断是真通了而不是假象?看三个指标:首帧时间、延迟、切流恢复速度。首帧时间从点击播放到画面出现,PC 端直连 RTSP 一般在 1~2 秒内;延迟可以用手机拍屏幕对比,正常在 500ms 以内;切流恢复速度指从点击切换到新画面出现,应该在 2 秒内。如果首帧超过 5 秒或者延迟超过 2 秒,多半是码流选错了或者网络带宽不够,换成子码流试试。
注意:WebGL 端如果播的是 HLS,延迟天然比 RTSP 高,通常 3~5 秒起步,这是协议决定的,不是代码问题。实时性要求高的场景要考虑 WebRTC 方案。
5. 避坑与排查:那些让我熬夜的监控流问题
5.1 画面全黑但状态显示正在播放
现象是MediaPlayer状态是 Playing,但 RenderTexture 上什么都没有。原因通常是FillTexture调用时机不对,在首帧到达前就调用了。解决方法是把填充逻辑放到FirstFrameReady事件回调里,而不是Start里直接调。另外检查 RenderTexture 的Depth是否为 0,有些版本需要设为 16 或 24 才能正常写入。
5.2 WebGL 端报跨域错误
现象是浏览器 Console 里一堆 CORS 报错,流加载失败。原因是 WebGL 端所有网络请求都受浏览器同源策略限制,转流服务没有配置跨域头。解决方法是在转流服务的响应头里加上Access-Control-Allow-Origin: *,或者把页面和流服务部署在同一域名下。这个坑在 PC 端完全不会出现,只在 WebGL 端暴露。
5.3 切流几次后浏览器崩溃
现象是切换三四路之后标签页直接崩掉。原因是每次OpenMedia都申请了新资源,但旧的没释放。解决方法是在SwitchTo里严格先CloseMedia()再OpenMedia(),并且把MediaPlayer的AutoOpen关掉,手动控制生命周期。WebGL 端内存回收比 PC 端慢,必要时在切流之间加一个短暂延迟。
5.4 多路同时播放时帧率骤降
现象是单路流畅,开到四路就卡成幻灯片。原因是每路视频都在独立解码和渲染,GPU 和 CPU 都吃满。解决方法是多路场景统一用子码流,降低单路分辨率;同时把不聚焦的画面降低更新频率,比如用MediaPlayer.Pause()暂停非活动路,只保留音频或静态帧。这个优化在数字孪生大屏里几乎是必做的。
5.5 编辑器里正常,发布后黑屏
现象是编辑器 Play 一切正常,Build 出来就黑屏。原因通常是平台宏判断写反了,或者 WebGL 端缺少对应的解码库。解决方法是检查GetUrlForPlatform里的#if分支,确认 WebGL 端走的是 HLS 地址;同时确认 AVProVideo 的 WebGL 插件文件在 Build 时被正确包含,有时候需要手动在Plugins目录下勾选对应平台。
6. 二次开发:多路管理、延迟优化与一个我常用的验证习惯
拿到这个 Demo 之后,真正要落地到项目里,通常要做三件事:多路管理、延迟优化、异常自愈。
多路管理方面,我一般会把VideoStreamController改造成一个池化管理器,维护一个MediaPlayer列表,每路流对应一个实例,用一个Dictionary<int, MediaPlayer>来索引。切换时只激活目标路,其余路Pause而不是Close,这样切回来时首帧几乎瞬间到达。下面是一个简化的池化结构:
public class StreamPool : MonoBehaviour { [SerializeField] private MediaPlayer playerPrefab; private Dictionary<int, MediaPlayer> pool = new Dictionary<int, MediaPlayer>(); public MediaPlayer GetOrCreate(int streamId, string url) { if (pool.TryGetValue(streamId, out var mp)) { if (!mp.IsPlaying()) mp.Play(); // 已存在则恢复播放 return mp; } var newMp = Instantiate(playerPrefab, transform); newMp.OpenMedia(MediaPathType.AbsolutePathOrURL, url, autoPlay: true); pool[streamId] = newMp; return newMp; } public void PauseAllExcept(int activeId) { foreach (var kv in pool) { if (kv.Key != activeId && kv.Value.IsPlaying()) kv.Value.Pause(); // 暂停而非关闭,保留缓冲 } } }延迟优化方面,几个实测有效的点:优先用子码流、把 RenderTexture 分辨率降到实际显示尺寸的 1.5 倍以内、关闭 AVProVideo 里不必要的后处理。如果延迟还是压不下来,就要考虑在转流服务层做优化,比如用 WebRTC 替代 HLS,能把延迟从秒级压到百毫秒级。
异常自愈这块,我习惯给每路流加一个心跳检测:定时检查MediaPlayer.Control.GetCurrentTimeMs()是否在增长,如果连续几秒没变化就判定为断流,触发重连。重连要有退避策略,第一次 1 秒后重试,第二次 3 秒,第三次 10 秒,避免设备端被频繁请求打挂。
最后说一个我自己的验证习惯。每次改完播放相关代码,我不会只看编辑器里的画面,而是强制走一遍「编辑器 Play → Build WebGL → 本地服务器打开 → 切三路流 → 断网重连」这个完整链路。因为监控流的问题八成只在真实发布环境里暴露,编辑器里一切正常往往是最危险的假象。从那以后我每次动视频相关代码,都强制走一遍这个流程,再也没在交付现场翻过车。希望帮到你。
本文还有配套的精品资源,点击获取