ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Unity全景视频播放:投影、编码与流畅度优化实践

Unity全景视频播放:投影、编码与流畅度优化实践 简介面向Unity开发者和VR爱好者资源聚焦Unity 2017环境下的全景视频播放实现系统梳理了使用内置MovieTexture组件加载OGG/OVG视频、创建3D球体并放置主相机、通过Renderer映射纹理以及利用AudioSource同步音轨的完整流程。文档仅1个docx文件大小563KB包含步骤截图和可直接套用的C#代码片段适合需要快速上手全景视频功能的初中级开发者。内容还指出MovieTexture不支持Android平台的局限对比Handheld.PlayFullScreenMovie的不足并给出第三方插件选型建议帮助读者规避常见坑点。已有116人学习若想减少移动端VR全景播放的排查时间这份简明笔记能提供清晰指引。1. 全景视频播放不是把文件拖进场景那么简单把一个 4K 全景 MP4 拖进 Unity直接挂 VideoPlayer大概率看到球体表面被拉伸的半张脸。全景视频播放的技术核心不是 VideoPlayer而是投影把 2:1 的等距柱状画面映射回球面把相机放到球心再让解码、渲染纹理、丢帧策略对齐。这条链路用引擎原生组件就能搭不依赖插件但有三个反直觉边界渲染纹理分辨率不等于清晰度、硬解能力由系统解码器决定、拖进度条卡顿主要怪关键帧间隔。下文按落地顺序展开先确认文件格式再搭最小播放器调完参数后用脚本把卡顿量化出来。2. 全景视频的投影与编码边界先搞清楚文件再动手拿到素材先别急着开 Unity 编辑器。全景视频一半的坑在文件参数里投影方式、分辨率、编码器、Profile、关键帧间隔。这些属性不归 Unity 管但 VideoPlayer 能不能播、播得顺不顺全由它们决定。先用一套固定检查流程把文件摸清后面所有排障都围绕它展开。2.1 为什么 2:1 等距柱状投影是默认格式全景相机Insta360、GoPro 360 模式、商用全景采集设备输出的标准格式是等距柱状投影equirectangular宽高比固定 2:1横轴对应 0 到 360 度经度纵轴对应纬度等效把球面沿经线展开摊平。赤道附近一像素对应的角度小两极一像素对应的角度大所以靠近上下边缘的画面会被横向拉伸这是投影的数学性质不是编码问题。选择它的理由很实际编码器不需要理解球面把它当普通矩形视频处理压缩效率高、兼容性最好采集端到剪辑端Premiere、Final Cut再到播放端都按 2:1 交接流程没有转换损耗。相比之下立方体贴图cubemap把球面切成六个面极点区域没有拉伸但六个面分开进码流码率分配不均Unity 端还得自己处理面间接缝与 Mipmap鱼眼是两个圆那是相机原始输出通常先校正转成等距柱状再进播放链路。落地项目里绝大多数素材最终都会归一到 2:1。由此得到一个关键推论素材是 2:1渲染纹理就建 2:1素材是左右立体每只眼睛各一张 2:1 拼成一张帧渲染纹理就按对应比例建。宽高比错位是“画面被压扁或拉长”的头号原因和分辨率高低无关排查时先看比例再看分辨率。2.2 容器与编码MP4/H.264 之外的选择Unity VideoPlayer 本身不解码它调用目标平台的系统解码器Windows 走系统媒体框架macOS/iOS 走系统播放器框架Android 走 MediaCodec 硬解栈。所以“Unity 支不支持这个视频”的真正问题是“目标设备的系统解码器支不支持”。跨平台最稳的组合是 MP4 容器加 H.264 编码所有平台的硬解都覆盖需要更高压缩率时用 H.265/HEVC但有两个前提Windows 桌面要装系统 HEVC 扩展Android 设备要确认 SoC 的硬解支持 H.265 Main Profile 8bit。常见失败案例是素材用 H.265 Main1010bit压出来一体机硬解直接不支持表现是黑屏或 errorReceived。容器自由度也要掂量按组合整理成一张对照表容器 编码平台覆盖结论MP4 H.264全平台硬解发布基线MP4 H.265 Main 8bit桌面试系统扩展移动端看 SoC8K 首选MP4 H.265 Main10一体机普遍不支持重压成 8bitMKV/WebM VP9/AV1桌面部分可解一体机不可用避开音频轨建议统一 AAC省去音画同步的额外工作。我的通用发布规格是H.264 High Profile Level 5.1 或 H.265 Main Profile 的 MP4音频 AAC关键帧间隔 2 秒具体参考码率在第 4 章给出。文件里带字幕轨或额外音轨时记得把 VideoPlayer 的音频轨道选择参数指对否则默认取第一轨。2.3 用 ffprobe 一秒钟读出能不能播进编辑器之前先做一次文件体检。ffprobe 是 FFmpeg 工具族里的流探测器只读文件不改内容三个平台都能装。执行下面的命令可以看到决定播放命运的全部参数ffprobe -v error \ -select_streams v:0 \ -show_entries streamcodec_name,profile,level,width,height,avg_frame_rate,bit_rate \ -show_entries formatformat_name,duration \ -of defaultnoprint_wrappers1 panorama.mp4输出大致长这样codec_nameh264 profileHigh level51 width3840 height1920 avg_frame_rate30000/1001 bit_rate48000000 format_namemov,mp4,m4a,3gp,3g2,mj2 duration180.5逐行解读codec_name 和 profile 决定解码头能不能认level51 表示该文件按 H.264 Level 5.1 编码对应 4K 30fps 这一档8K 素材会显示 h265 与 level 130 或更高avg_frame_rate 给出真实帧率全景视频常见 29.97 或 30bit_rate 是编码码率后面调参以它为基线。加-show_entries streamside_data_list能看到文件是否带 VR 投影标注但很多素材没有这个 tag播放器也不依赖它不必较真。要测全片解码速度用ffmpeg -v error -i panorama.mp4 -f null -跑一遍结束时间就是本机全片解码耗时除以视频时长就是实时倍率低于 1.0 说明这台机器解码跟不上后面做性能取舍时必须知道这个数字。注意ffprobe 显示 H.265 Main10 时除非确认目标设备支持 10bit 硬解否则先转成 8bit 再进 Unity能省一整轮黑屏排障。3. 用 VideoPlayer 加 RenderTexture 搭最小全景播放器素材确认没问题之后搭建最小可复现的播放器。场景只需要三样东西一个球体、一个材质、一个放在球心的相机再加上 VideoPlayer 与一块 RenderTexture。这套结构同时兼容桌面、安卓一体机和 WebGL不引入任何第三方依赖。3.1 场景三件套球体、内表面材质、球心相机在场景里创建一个 Sphere放大到 100 倍位置保持原点。材质使用下面 3.2 节的方向映射 shader关键开关是剔除正面、只渲染内表面Cull Front否则相机在球心会看不到任何东西。相机位置放到 (0, 0, 0)near clip 设 0.01far clip 必须大于球的半径否则近距离会被球面遮挡背景色随便反正会被球面完全覆盖。相机控制上全景播放只让相机做 Yaw/Pitch 旋转不要位移。位移会让画面整体滑动视频内容本身没有视差移动相机在视觉上相当于“镜头漂移”用户马上会晕。极点区域在相机正仰角 90 度和俯角 90 度时是画面最容易被拉伸的地方素材本身两极就是拉伸的播放端无法修复只能在采集端保证垂直视野素材足够。材质有两个容易踩的点。第一不要用默认的 PBR 光照材质球面一旦接收光照用户会看到明显的高光带与阴影边界这是全景播放器最常见的“为什么画面有一块亮/一块暗”的来源改用 Unlit 思路的材质Unity 内置的 Unlit/Texture 可以应急但接缝与翻转要靠方向映射 shader 才可控。第二球体的阴影接收与投射在渲染设置里关掉场景里任何平行光都不应该影响它。3.2 方向映射 shader接缝与 Y 轴翻转一起解决内置 Sphere 自带的 UV 按经纬度生成接缝处会出现一条纵向的像素错位极点也会被压缩采样。更稳妥的做法是不用原始 UV在顶点着色器里把物体空间位置归一化成方向向量片元着色器用 atan2 与 asin 反解经纬度坐标Shader Custom/EquirectVideo { Properties { _MainTex (Video RT, 2D) black {} _FlipY (Flip Y (0/1), Float) 0 } SubShader { Tags { QueueGeometry RenderTypeOpaque } Cull Front Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; float _FlipY; struct appdata { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; float3 dir : TEXCOORD0; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.dir normalize(v.vertex.xyz); return o; } half2 DirToUV (float3 dir) { half2 uv; uv.x atan2(dir.z, dir.x) / 6.28318530718 0.5; uv.y asin(clamp(dir.y, -1.0, 1.0)) / 3.14159265359 0.5; if (_FlipY 0.5) uv.y 1.0 - uv.y; return uv; } fixed4 frag (v2f i) : SV_Target { half2 uv DirToUV(i.dir); return tex2D(_MainTex, uv); } ENDCG } } }这段着色器没有采样模型 UV而是把球面上每个顶点的物体坐标归一化为方向atan2 得到经度范围 -π 到 π除以 2π 后映射到 0~1asin 得到纬度范围 -π/2 到 π/2除以 π 映射到 0~1。同一方向上所有片元拿到同一个 uv接缝只剩下经度环绕 0/1 边界的一次硬切不像内置 UV 那样在一条经线上整条错位。_FlipY是用来兜底的VideoPlayer 写 RenderTexture 的方向在 WindowsD3D与 Android/OpenGL 之间存在历史性差异表现是画面上下颠倒先在材质面板把这个参数拨到 1 试试比改代码快。提示换成 URP 或 HDRP 管线时把这段 CGPROGRAM 逻辑平移到 Shader Graph 即可Vertex 节点输出 normalize(normalWS)Fragment 里用 ATan2 与 Asin 搭建同样的经纬度映射Cull Front 在 Surface Options 里设置。核心思路不变。3.3 播放控制脚本prepare 之后重建渲染纹理VideoPlayer 的 targetTexture 必须在 Prepare 之前绑好否则画面出不来而视频的真实宽高只有 Prepare 完成后才知道。常见做法是先用一个默认 4K 的渲染纹理去 Prepare拿到真实尺寸后按需重建using UnityEngine; using UnityEngine.Video; [RequireComponent(typeof(VideoPlayer))] public class PanoramaVideoPlayer : MonoBehaviour { [SerializeField] private string videoUrl; [SerializeField] private RenderTexture videoRT; [SerializeField] private Material sphereMaterial; private VideoPlayer vp; private bool rePreparing; void Awake() { vp GetComponentVideoPlayer(); vp.source VideoSource.Url; vp.url videoUrl; vp.renderMode VideoRenderMode.RenderTexture; vp.targetTexture videoRT; vp.isLooping false; vp.skipOnDrop true; vp.playOnAwake false; vp.prepareCompleted OnPrepared; vp.errorReceived OnError; vp.loopPointReached OnReachEnd; } void Start() { vp.Prepare(); } void OnPrepared(VideoPlayer source) { if (source.texture null) return; // 视频尺寸与 RT 不一致时重建 RT 后重新 Prepare if (videoRT null || videoRT.width ! source.texture.width || videoRT.height ! source.texture.height) { if (rePreparing) return; rePreparing true; source.targetTexture null; if (videoRT ! null) videoRT.Release(); videoRT new RenderTexture( source.texture.width, source.texture.height, 0, RenderTextureFormat.ARGB32); videoRT.Create(); source.targetTexture videoRT; rePreparing false; source.Prepare(); return; } sphereMaterial.mainTexture videoRT; source.Play(); } void OnError(VideoPlayer source, string message) { Debug.LogError([PanoramaVideo] message); } void OnReachEnd(VideoPlayer source) { Debug.Log(播放结束); } }这段代码最关键的是 OnPrepared 里的重建分支先断开 targetTextureRelease 旧 RT按源视频真实宽高创建新 RT重新绑回再 Prepare 一次。rePreparing是防递归开关否则 prepareCompleted 里再次 Prepare 会无限回调。目标纹理创建时用 ARGB32如果是 10bit HDR 素材把格式换成 RGBAHalf并且项目切到线性色彩空间否则高光区域会直接曝白。renderMode 用 RenderTexture 而不是 CameraNearPlane是因为后者在双机渲染时每只眼睛都会重复渲染球面显存与带宽翻倍一体机场景不要用。3.4 VideoPlayer 全景场景参数速查把常用参数按全景场景的推荐值整理成一张表照着抄即可。这套配置在 Unity 2022 LTS 与 Unity 6 的 VideoPlayer API 层面保持一致参数全景场景建议说明playOnAwakefalse先 Prepare 再 Play避免首帧黑屏renderModeRenderTexture输出进材质双屏 VR 只解码一次skipOnDroptrue解码跟不上时跳帧保流畅逐帧录制才设 falseisLooping按需求循环场景 true注意循环时 loopPointReached 不触发playbackSpeed1.0变速在全景里会晕平台支持也参差audioOutputModeAudioDirect全景音轨是普通立体声直接输出最简单controlledAudioTrackCount1多音轨素材时手动指定目标轨skipOnDrop 是全景场景最需要理解的参数它为 true 时解码落后于播放时钟就直接丢帧保证画面时间轴向前走为 false 时播放器会等解码线程音频照走于是出现音画不同步。全景直播与一体机场景我一般保持 true只有做逐帧分析或录制时才关掉。4. Unity 全景播放的参数取舍渲染纹理、码率与硬解播放器能出画面之后真正拉开体验差距的是资源参数。四个变量渲染纹理尺寸、编码码率、Profile/Level、关键帧间隔它们互相制约调错任何一个都会出现明显症状但位置各不相同有的卡渲染有的卡解码有的卡 seek。4.1 渲染纹理为什么不能比源视频大一个高频误用是“把渲染纹理建到 8K 想提升清晰度”。解码器输出的分辨率等于源视频分辨率渲染纹理建得再大采样的还是同一张源图清晰度毫无变化代价是显存暴涨。单张 ARGB32 渲染纹理的内存是宽乘高乘 4 字节4K3840×1920约 29.5MB8K8192×4096约 134MB。加上解码器自身的输出缓冲和播放管线的帧拷贝一体机上直接吃掉几百兆显存播放十秒后崩溃的案例多半来自这里而不是素材本身。所以规则是渲染纹理尺寸贴着源视频分辨率建想要更清晰只能换更高质量的源文件靠升分辨率补不了缺失的信息。在同一块渲染纹理上反复实验时记得在 Release 后重新 CreateUnity 的 RenderTexture 有池化机制不显式 Release 会导致显存只增不减长时间轮播场景尤其明显。4.2 码率、Profile、Level 与关键帧间隔的取舍下表是全景项目里常用的编码参考按目标平台分档场景编码Profile/Level参考码率备注4K30 本地H.264High L5.140-60 Mbps兼容性最好4K60 本地H.265Main L5.145-70 Mbps帧率优先时选8K30 高端头显H.265Main 8bit L6.080-120 Mbps接近硬解上限网络串流H.264/H.265High L5.120-40 Mbps弱网再降码率音频AAC-192 kbps配立体声足够Level 是容易被忽略的隐性门槛H.264 Level 5.1 只保证 4K30 这个级别8K 素材至少要 Level 6.0/6.1而并非所有设备的硬解都宣称支持 Level 6所以压缩 8K 时显式指定 level 比让编码器自动选更可控。关键帧间隔GOP直接决定 seek 体验全景交互场景用户会频繁拖动进度条2 秒一个关键帧拖动后几乎即时出画面关键帧拉到 10 秒以上seek 后解码器必须从最近的关键帧重新解起用户会看到半秒到一秒的卡顿等待。GOP 也不是越短越好越短码率越高2 秒是全景项目里比较均衡的取值。4.3 一体机与手机端的硬解现实Pico 4、Quest 2 这一代设备都用骁龙 XR2 平台它的硬解上限约在 H.265 8K30 和 4K60 这两档之间8K60 超出常规能力硬塞的结果是从硬件解码回退到 CPU 解码帧率掉到个位数。落地到这种设备我一般把规格定为 4K30、码率 30 到 45 Mbps画质在分辨率有限时靠高码率撑住细节比盲目上 8K 更稳。桌面 Windows 的变数是系统是否装了 HEVC 扩展没装时 VideoPlayer 对 H.265 直接报错表现是 errorReceived 收到类似文件无法打开的消息排查顺序永远是先查系统扩展再查素材编码。注意Android 9 及以上的设备默认禁止明文 HTTP 流量局域网内用 http 地址直接访问全景视频会立刻失败。本地调试可以在 AndroidManifest 里临时开 usesCleartextTraffic发布版本不要保留。4.4 用 ffmpeg 压一条符合规格的视频给出手边素材不合规时最常用的重压命令参数就是上面几节的落地版ffmpeg -i source.mov \ -c:v libx265 -crf 22 -preset medium \ -profile:v main -level 5.1 \ -pix_fmt yuv420p \ -x265-params keyint60:min-keyint60 \ -c:a aac -b:a 192k \ -movflags faststart \ -y panorama.mp4参数说明libx265 配 crf 22 控制画质preset medium 是速度与压缩率的平衡点profile:v main 保证 8bit 兼容配合 level 5.1 锁定 4K30 能力档yuv420p 把 10bit 源降到 8bit 4:2:0这是硬解最通用的色度格式keyint60 对 30fps 就是 2 秒一个关键帧faststart 把 moov 元数据移到文件头网络播放可以提早出首帧。同样的参数把 libx265 换成 libx264 就是 H.264 版本。压完后用 2.3 节的 ffprobe 命令复查一遍确认 profile、level、帧率全部符合预期再进 Unity。5. 立体全景、动态换源与运行期错误处理单视频能播只是起点。生产环境里还有三件事必须处理立体素材的双眼采样、播放过程中换视频源、以及错误回调怎么读。这三块做不好演示没问题一上真机就露馅。5.1 上下/左右布局的立体全景采样立体全景素材把左右眼的等距柱状帧合成到同一张画面里常见左右布局SBS和上下布局TB。播放时不能让左右眼看到同一张全图否则 3D 效果完全丢失。在方向映射 shader 里根据当前渲染的是哪只眼睛对 uv 做半幅偏移half2 uv DirToUV(i.dir); #if defined(UNITY_SINGLE_PASS_STEREO) || defined(UNITY_STEREO_INSTANCING_ENABLED) float eye unity_StereoEyeIndex; // 0左眼, 1右眼 uv.x uv.x * 0.5 eye * 0.5; // 左右布局:各取半幅 #endif这段逻辑的前提是单通道立体渲染Project Settings 里勾选 Single Pass Instanced左眼与右眼各渲染一次unity_StereoEyeIndex告诉片元当前是哪只眼睛。左右布局只拆 x上下布局把 uv.y 按同样方式拆并注意翻转方向。多通道渲染multi-pass下不需要这段偏移因为每只眼睛的相机各自以不同视角渲染整张球面再叠加偏移会重复计算。立体感还依赖相机的瞳距。Camera 的 stereoSeparation 属性控制双眼位置偏移默认 0.022 米适合多数素材180 度或特写类素材有时需要加大到 0.06具体以用户不头晕为准。只有 UV 偏移而没有瞳距偏移画面是“纸片立体”没有真实的双眼视差。5.2 动态换源渲染纹理的释放顺序播放列表、AB 切换、直播拉流都会遇到运行时换源。直接改 vp.url 再 Play 是最常见的错误做法新旧视频共用一块渲染纹理prepare 期间旧画面残留在球面上看起来像卡死。规范顺序是先停、再断、后释放、最后重新绑定void SwitchTo(string newUrl, int newWidth, int newHeight) { vp.Stop(); // 1. 停止解码,释放内部缓冲 vp.url newUrl; vp.targetTexture null; // 2. 先断开旧绑定 videoRT.Release(); // 3. 再释放显存 videoRT new RenderTexture( newWidth, newHeight, 0, RenderTextureFormat.ARGB32); videoRT.Create(); // 4. 创建新 RT vp.targetTexture videoRT; // 5. 重新绑定 vp.Prepare(); // 6. 重新准备 }顺序不能颠倒先 Release 再 Stop 的话解码器可能仍持有旧 RT 的引用Release 把显存还回池子解码线程下一帧写入的就是无主内存表现为偶发闪黑或崩溃。换源期间球面上残留的画面可以通过把材质主纹理临时换成一张 1×1 黑色纹理盖住prepare 完成后再换回新 RT视觉上更干净。频繁换源的场景建议准备两块 RT 轮换避免每次 new 与 Release 带来的分配抖动。5.3 errorReceived 带回来的三类典型失败VideoPlayer 的 errorReceived 回调是排障第一入口错误字符串通常包含问题域但不会精确到原因需要结合环境判断错误现象最常见原因对策文件或 URL 无法打开路径错误、中文文件名编码、404、明文 HTTP 被拦截用 Application.streamingAssetsPath 拼接路径URL 用 https 或加明文许可解码失败或直接黑屏编码或 profile 不被平台硬解支持如 H.265 Main10、VP9ffprobe 复查编码重压 H.264 High L5.1播放后崩溃或花屏码率过高打爆解码器、RT 尺寸超出显存降低码率档位缩 RT 到源分辨率第一条里 Android 的路径坑最常见Application.streamingAssetsPath 在 Android 上是指向压缩包内的路径不能直接喂给 VideoPlayer要先用 UnityWebRequest 把文件拷到 persistentDataPath 再播。第二条里 VP9 在一体机上基本无解统一重压 H.264/H.265。第三条出现时先看操作系统日志Android 用 logcat 抓 MediaCodec 报错能直接看到解码器返回的错误码比猜更快。6. 帧号对账脚本把全景播放卡顿量化出来卡顿是最难复现的 bug因为它发生在解码线程肉眼只能感知结果要么画面停一下要么音画不同步。与其拍视频数秒不如在播放器旁边挂一个对账脚本用帧号、时间与名义帧率之间的数学关系判断卡顿的真伪。6.1 帧号与时间为什么对不上VideoPlayer 暴露两个时钟frame 是解码器端当前帧号time 是播放时钟。正常稳定播放时time 约等于 frame 除以 frameRate。当解码跟不上时行为由 skipOnDrop 决定为 false播放时钟被解码拖慢time 落后、frame 也落后表现是画面停顿为 true播放时钟继续走解码器直接跳帧表现是 time 正常但 frame 数突跳。两种症状对应两种排查方向不做对账只靠观察很难分清是解码慢还是渲染慢。6.2 一个每秒采样的健康检查脚本在 VideoPlayer 同一物体上挂一个每秒采样一次帧号增量的脚本与名义帧率对比持续偏离超过阈值就报警using UnityEngine; using UnityEngine.Video; public class PlaybackHealthMeter : MonoBehaviour { [SerializeField] private VideoPlayer target; private int lastFrame; private float secondTimer; private int sampleCount; void Update() { if (target null || !target.isPlaying) return; secondTimer Time.unscaledDeltaTime; if (secondTimer 1.0f) return; secondTimer - 1.0f; int advanced (int)(target.frame - lastFrame); lastFrame (int)target.frame; double drift target.time - target.frame / (double)target.frameRate; Debug.Log(string.Format( 采样 {0}: 每秒推进 {1} 帧 | 名义帧率 {2:F1} | 偏差 {3:F3}s, sampleCount, advanced, target.frameRate, drift)); if (advanced target.frameRate * 0.85f) Debug.LogWarning(播放帧率低于名义值 15%,疑似解码跟不上); } }使用 Time.unscaledDeltaTime 是因为慢动作或 Time.timeScale 不为 1 时会干扰采样第一次采样因为包含 Prepare 到 Play 之间的等待通常偏低忽略第一秒数据看后续趋势。偏差持续拉大说明 time 与 frame 背离配合 skipOnDrop 的设置就能定位是解码慢还是跳帧策略生效。如果是跳帧导致的偏差累积那是设计行为不用修如果是 time 本身落后则要回到第 4 章的码率与 Profile 表格重新核对素材。6.3 用 Profiler 和 FrameDebugger 定位解码瓶颈对账确认“确实在丢帧”之后用 Profiler 的帧时间视图看三处Main Thread 的 Update 耗时、解码相关线程的占用、以及 GPU 的帧时间。解码线程接近满载而主线程空闲说明素材码率或分辨率超出硬解能力回到编码参数去调GPU 帧时间明显高于主线程去看球面细分与渲染纹理尺寸通常 24 段细分的球体已经足够继续加高只增加过绘制量画面提升可以忽略。FrameDebugger 里找到球体对应的 draw call看它是否出现大面积重复写入是的话把细分降回默认值。对齐第 1 秒采样与第 10 秒采样如果每秒推进帧数稳定在名义帧率的 95% 以上说明解码、渲染、RT 三个环节都健康如果逐秒衰减则是内存或显存水位在缓慢上升优先检查是否有人反复 new RenderTexture 没有 Release。把采样周期从 1 秒改成 0.5 秒这套对账逻辑可以直接搬去直播流和播放列表场景用来定位“到底从哪一次换源开始丢帧”的具体位置。本文还有配套的精品资源点击获取
返回列表