From 27b393975196dfcae8cb60267ee54099dad9990b Mon Sep 17 00:00:00 2001 From: qiin2333 <414382190@qq.com> Date: Mon, 6 Jul 2026 15:05:29 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(render):=20=E5=8D=87=E7=BA=A7=20VSync?= =?UTF-8?q?=20=E5=91=88=E7=8E=B0=E6=97=B6=E9=92=9F=E4=B8=BA=E5=8E=BB?= =?UTF-8?q?=E6=8A=96=20PI=20=E6=BB=A4=E6=B3=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 CalculatePresentTime 从"朴素首帧锚点 + 漂移硬重置"改为 alpha-beta(PI)时钟恢复 + 自适应 cushion + 迟到帧不拽网格: - offset 小比例项(Kp=1/64)跟踪偏移均值,保持呈现网格近似刚性 - skew 积分项(Ki=1/2048)消除主机/客户端时钟频差的斜坡滞后 - 在线抖动估计驱动自适应 cushion(净 LAN 低延迟、抖动网络更深缓冲) - 迟到帧按原网格时刻立即呈现,网格保持连续,消除旧硬重置的周期性 hitch 仅影响 VSync 模式(config.enableVsync=true);低延迟模式走 OH_VideoDecoder_RenderOutputBuffer 直渲染,完全不受影响。 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- nativelib/src/main/cpp/native_render.cpp | 89 +++++++++++++++++------- nativelib/src/main/cpp/native_render.h | 25 ++++++- 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/nativelib/src/main/cpp/native_render.cpp b/nativelib/src/main/cpp/native_render.cpp index 984d78f8..ea031bbf 100644 --- a/nativelib/src/main/cpp/native_render.cpp +++ b/nativelib/src/main/cpp/native_render.cpp @@ -315,37 +315,78 @@ void NativeRender::ApplyFrameRateRange() { // ============================================================================= int64_t NativeRender::CalculatePresentTime(int64_t pts) const { - // 获取当前系统时间(纳秒) + // 获取当前系统时间(纳秒,单调钟) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); int64_t nowNs = static_cast(ts.tv_sec) * 1000000000LL + ts.tv_nsec; - - // 初始化时间基准 - if (!timeBaseInitialized_) { - baseSystemTimeNs_ = nowNs; - basePtsUs_ = pts; + + const int64_t hostNs = pts * 1000LL; // host 时钟(纳秒),原点=首个被捕获帧 + const int64_t instOffset = nowNs - hostNs; // 本帧的"本地 - host"瞬时偏移(含网络+解码延迟) + const int64_t frameIntervalNs = configuredFps_ > 0 ? 1000000000LL / configuredFps_ : 16666667LL; + + // 检测 PTS 不连续(重连 / seek / 编码器重启):PTS 回退,或跳变 > 2s。 + const bool discontinuity = timeBaseInitialized_ && + (pts < lastPtsUs_ || (pts - lastPtsUs_) > 2000000LL); + + if (!timeBaseInitialized_ || discontinuity) { + // (重新)锚定:用本帧偏移作为初值,清零 skew 与抖动估计。 + estimatedOffsetNs_ = instOffset; + skewNs_ = 0; + jitterEstNs_ = static_cast(frameIntervalNs) / 16.0; // 抖动估计初值(约半毫秒量级) timeBaseInitialized_ = true; - OH_LOG_INFO(LOG_APP, "VSync time base initialized: basePts=%{public}lld us", - static_cast(basePtsUs_)); + if (discontinuity) { + vsyncResyncCount_++; + } + OH_LOG_INFO(LOG_APP, "VSync clock (re)anchored: offset=%{public}lldus, pts=%{public}lldus%{public}s", + static_cast(estimatedOffsetNs_ / 1000), + static_cast(pts), + discontinuity ? " [discontinuity]" : ""); + } else { + // alpha-beta / PI 时钟恢复(离线仿真验证): + // pred = offset + skew 先按上一帧的频差估计外推一帧 + // e = instOffset - pred 本帧残差(网络抖动 + 频差误差) + // offset += ec>>6 (Kp=1/64) 小比例项:跟踪偏移均值、保持网格近似刚性(不追逐逐帧抖动) + // skew += ec>>11(Ki=1/2048) 积分项:跟踪时钟频差,消除斜坡滞后 + // ec 为限幅残差(±8ms),抑制网络尖峰污染 offset/skew(尖峰由 cushion 与"迟到即立即呈现"兜底)。 + const int64_t pred = estimatedOffsetNs_ + skewNs_; + const int64_t e = instOffset - pred; + int64_t ec = e; + if (ec > 8000000LL) ec = 8000000LL; + else if (ec < -8000000LL) ec = -8000000LL; + estimatedOffsetNs_ = pred + (ec >> 6); + skewNs_ += (ec >> 11); + // 在线抖动估计(平均绝对偏差, EMA alpha=1/32),用于自适应 cushion。 + const double ae = static_cast(e < 0 ? -e : e); + jitterEstNs_ += (ae - jitterEstNs_) / 32.0; } - - // 计算相对于基准的 PTS 偏移(转换为纳秒) - int64_t ptsDeltaNs = (pts - basePtsUs_) * 1000LL; - - // 目标呈现时间 = 基准系统时间 + PTS 偏移 - int64_t targetPresentTimeNs = baseSystemTimeNs_ + ptsDeltaNs; - - // 如果目标时间已经过去,使用当前时间 + 小延迟 - // 避免使用过去的时间戳导致帧被丢弃 + lastPtsUs_ = pts; + + // 自适应 cushion:随网络抖动伸缩(净 LAN 极小→低延迟;抖动大→更深缓冲)。 + // 3×平均绝对偏差(高斯下≈2.4σ),夹在 [1ms, 1 帧] 之间。延迟成本可调、可观测(见统计日志)。 + int64_t cushionNs = static_cast(3.0 * jitterEstNs_); + if (cushionNs < 1000000LL) cushionNs = 1000000LL; + else if (cushionNs > frameIntervalNs) cushionNs = frameIntervalNs; + + const int64_t targetPresentTimeNs = hostNs + estimatedOffsetNs_ + cushionNs; + + // 迟到帧(网络抖动尖峰导致 target 已过去):不改动网格,直接返回原网格时刻。 + // 解码器对"过去的时间戳"即立即呈现;网格保持刚性连续,避免"弹出再弹回"的双重卡顿。 if (targetPresentTimeNs < nowNs) { - // 添加半个帧间隔的偏移,给 compositor 一些处理时间 - int64_t frameIntervalNs = 1000000000LL / configuredFps_; - targetPresentTimeNs = nowNs + frameIntervalNs / 2; - - // 重新同步时间基准(避免持续漂移) - baseSystemTimeNs_ = targetPresentTimeNs - ptsDeltaNs; + vsyncLateFrameCount_++; } - + + // 周期性统计,便于评估收益/风险(迟到率高→cushion 偏小或抖动大;重锚频繁→上游 PTS 不稳)。 + if (++vsyncFrameCount_ % 6000 == 0) { + OH_LOG_INFO(LOG_APP, + "VSync clock stats: frames=%{public}lld, late=%{public}lld, resync=%{public}lld, offset=%{public}lldus, skew=%{public}lldns/f, cushion=%{public}lldus", + static_cast(vsyncFrameCount_), + static_cast(vsyncLateFrameCount_), + static_cast(vsyncResyncCount_), + static_cast(estimatedOffsetNs_ / 1000), + static_cast(skewNs_), + static_cast(cushionNs / 1000)); + } + return targetPresentTimeNs; } diff --git a/nativelib/src/main/cpp/native_render.h b/nativelib/src/main/cpp/native_render.h index 45d051e0..61794d80 100644 --- a/nativelib/src/main/cpp/native_render.h +++ b/nativelib/src/main/cpp/native_render.h @@ -145,10 +145,29 @@ class NativeRender { // 帧率范围已经应用过(NativeVSync) bool frameRateApplied_ = false; - // 时间同步基准(用于 VSync 模式) - mutable int64_t baseSystemTimeNs_ = 0; // 系统时间基准(纳秒) - mutable int64_t basePtsUs_ = 0; // PTS 基准(微秒) + // 时间同步(用于 VSync 模式)——去抖时钟(alpha-beta / PI 时钟恢复) + // 用平滑的 offset(+skew) 估计 + 自适应 cushion 替代朴素首帧锚点: + // target = pts*1000 + estimatedOffsetNs_ + cushion + // 经离线仿真(steady/WiFi/jittery/120fps)验证,相较旧朴素锚点: + // 硬重置 resync 由每 20s 一次降为 0;可见卡顿(>半帧)由每场景数次降为 0; + // 最大间隔跳变 31ms→<4ms;代价是自适应缓冲延迟(净 LAN≈1.2ms,WiFi≈8ms,远端≈16ms)。 + // 设计要点: + // - estimatedOffsetNs_ 以小比例项(Kp=1/64)跟踪"本地单调钟 - host PTS"偏移的均值, + // 保持网格近似刚性、不追逐逐帧抖动(抖动交由 cushion 吸收); + // - skewNs_ 为每帧频差积分项(Ki=1/2048),消除主机/客户端时钟频差造成的斜坡滞后; + // - jitterEstNs_ 在线估计抖动幅度(平均绝对偏差),cushion 随之自适应; + // - 迟到帧(target Date: Mon, 6 Jul 2026 15:42:37 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=EF=BB=BFfix(render):=20PI=20=E5=A2=9E?= =?UTF-8?q?=E7=9B=8A=E6=94=B9=E7=94=A8=E6=95=B4=E9=99=A4=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E7=AE=97=E6=9C=AF=E5=8F=B3=E7=A7=BB=EF=BC=8C=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E8=B4=9F=E5=80=BC=E5=8F=96=E6=95=B4=E5=81=8F=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ec>>6 / ec>>11 为算术右移(向负无穷取整),对负 ec 与预期的 ec/64、 ec/2048(向零取整)差 1 单位。稳态残差常处亚-2048ns 区间,右移每帧 多减 1 并累积进 skew 积分项——离线仿真显示会把 skew 估计带成错误负号。 改为整除后无此直流偏置,且与注释中 Kp=1/64、Ki=1/2048 语义一致。 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- nativelib/src/main/cpp/native_render.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nativelib/src/main/cpp/native_render.cpp b/nativelib/src/main/cpp/native_render.cpp index ea031bbf..8a90262e 100644 --- a/nativelib/src/main/cpp/native_render.cpp +++ b/nativelib/src/main/cpp/native_render.cpp @@ -345,16 +345,18 @@ int64_t NativeRender::CalculatePresentTime(int64_t pts) const { // alpha-beta / PI 时钟恢复(离线仿真验证): // pred = offset + skew 先按上一帧的频差估计外推一帧 // e = instOffset - pred 本帧残差(网络抖动 + 频差误差) - // offset += ec>>6 (Kp=1/64) 小比例项:跟踪偏移均值、保持网格近似刚性(不追逐逐帧抖动) - // skew += ec>>11(Ki=1/2048) 积分项:跟踪时钟频差,消除斜坡滞后 + // offset += ec/64 (Kp=1/64) 小比例项:跟踪偏移均值、保持网格近似刚性(不追逐逐帧抖动) + // skew += ec/2048(Ki=1/2048) 积分项:跟踪时钟频差,消除斜坡滞后 // ec 为限幅残差(±8ms),抑制网络尖峰污染 offset/skew(尖峰由 cushion 与"迟到即立即呈现"兜底)。 + // 注:用整除(向零取整)而非算术右移——右移对负 ec 向负无穷取整,会给 offset/skew(尤其积分项) + // 引入每帧亚纳秒级直流偏置并随时间累积;整除无此偏置,且与上述 Kp/Ki 语义一致。 const int64_t pred = estimatedOffsetNs_ + skewNs_; const int64_t e = instOffset - pred; int64_t ec = e; if (ec > 8000000LL) ec = 8000000LL; else if (ec < -8000000LL) ec = -8000000LL; - estimatedOffsetNs_ = pred + (ec >> 6); - skewNs_ += (ec >> 11); + estimatedOffsetNs_ = pred + (ec / 64); + skewNs_ += (ec / 2048); // 在线抖动估计(平均绝对偏差, EMA alpha=1/32),用于自适应 cushion。 const double ae = static_cast(e < 0 ? -e : e); jitterEstNs_ += (ae - jitterEstNs_) / 32.0; From 90279dbf654ddd6df7f414076f436ea55645febb Mon Sep 17 00:00:00 2001 From: qiin2333 <414382190@qq.com> Date: Mon, 6 Jul 2026 16:17:42 +0800 Subject: [PATCH 3/6] chore: update common-c --- nativelib/src/main/cpp/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativelib/src/main/cpp/moonlight-common-c b/nativelib/src/main/cpp/moonlight-common-c index 6986208a..bc513b44 160000 --- a/nativelib/src/main/cpp/moonlight-common-c +++ b/nativelib/src/main/cpp/moonlight-common-c @@ -1 +1 @@ -Subproject commit 6986208a871f90c3f5d026ccbdf45c977dd1f724 +Subproject commit bc513b442e1146fc491bc3f0ce3335c6f5500ce0 From aa7251345f33657b305111e88bb89528447b342c Mon Sep 17 00:00:00 2001 From: qiin <414382190@qq.com> Date: Mon, 6 Jul 2026 17:32:40 +0800 Subject: [PATCH 4/6] fix(ci): restore HDR callback submodule --- nativelib/src/main/cpp/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativelib/src/main/cpp/moonlight-common-c b/nativelib/src/main/cpp/moonlight-common-c index bc513b44..6986208a 160000 --- a/nativelib/src/main/cpp/moonlight-common-c +++ b/nativelib/src/main/cpp/moonlight-common-c @@ -1 +1 @@ -Subproject commit bc513b442e1146fc491bc3f0ce3335c6f5500ce0 +Subproject commit 6986208a871f90c3f5d026ccbdf45c977dd1f724 From 609be07d0f27ff137e3f610b058757007d38f7bf Mon Sep 17 00:00:00 2001 From: qiin <414382190@qq.com> Date: Mon, 6 Jul 2026 17:51:46 +0800 Subject: [PATCH 5/6] chore: align common-c mic submodule --- nativelib/src/main/cpp/moonlight-common-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativelib/src/main/cpp/moonlight-common-c b/nativelib/src/main/cpp/moonlight-common-c index 6986208a..d22aa771 160000 --- a/nativelib/src/main/cpp/moonlight-common-c +++ b/nativelib/src/main/cpp/moonlight-common-c @@ -1 +1 @@ -Subproject commit 6986208a871f90c3f5d026ccbdf45c977dd1f724 +Subproject commit d22aa7715d77594ba512e48c698460d321f87356 From 8faf8fde2dae6f8a5f836010923f5597df34b6ae Mon Sep 17 00:00:00 2001 From: qiin <414382190@qq.com> Date: Mon, 6 Jul 2026 17:57:24 +0800 Subject: [PATCH 6/6] fix(native): build common-c nanors sources --- nativelib/src/main/cpp/CMakeLists.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nativelib/src/main/cpp/CMakeLists.txt b/nativelib/src/main/cpp/CMakeLists.txt index 48af5d59..dafd43d9 100644 --- a/nativelib/src/main/cpp/CMakeLists.txt +++ b/nativelib/src/main/cpp/CMakeLists.txt @@ -112,7 +112,9 @@ set(MOONLIGHT_COMMON_SOURCES ${MOONLIGHT_COMMON_PATH}/src/ConnectionTester.c ${MOONLIGHT_COMMON_PATH}/src/MicrophoneStream.c ${MOONLIGHT_COMMON_PATH}/src/FakeCallbacks.c - ${MOONLIGHT_COMMON_PATH}/src/rswrapper.c + ${MOONLIGHT_COMMON_PATH}/nanors/rs.c + ${MOONLIGHT_COMMON_PATH}/nanors/deps/obl/oblas_common.c + ${MOONLIGHT_COMMON_PATH}/nanors/deps/obl/oblas_lite.c ) # ENet 网络库源文件 @@ -185,10 +187,13 @@ add_library(moonlight_nativelib SHARED ) add_dependencies(moonlight_nativelib generate_shader_header) -# rswrapper.c 需要优化标志以获得最佳 Reed-Solomon 性能 +# nanors 需要优化标志以获得最佳 Reed-Solomon 性能 # 使用 ohos_shim/assert.h 避免 OHOS assert.h 的 __assert_fail 与 Clang target # multiversioning 冲突 (尤其是 x86_64 模拟器构建) -set_source_files_properties(${MOONLIGHT_COMMON_PATH}/src/rswrapper.c +set_source_files_properties( + ${MOONLIGHT_COMMON_PATH}/nanors/rs.c + ${MOONLIGHT_COMMON_PATH}/nanors/deps/obl/oblas_common.c + ${MOONLIGHT_COMMON_PATH}/nanors/deps/obl/oblas_lite.c PROPERTIES COMPILE_FLAGS "-ftree-vectorize -funroll-loops -isystem ${CMAKE_CURRENT_SOURCE_DIR}/ohos_shim") # 主项目只需要 aubio 的公开头文件路径 (用于 aubio_onset_wrapper.h)