diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d05fdc831..da0cf01e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -229,6 +229,9 @@ - Build: chiaki's build script starts the native build clean when chiaki's build files change. The OpenSSL upgrade otherwise failed on a machine that had built before (old configured headers left in the cache). ## Unreleased +- **PS5 decode latency (Saleh: PXPlay decodes in under 1 ms on the Odin 2, MoonCast showed up to 5 ms):** the Odin's Snapdragon 8 Gen 2 has a separate low-latency HEVC/H.264 decoder (`c2.qti.hevc.decoder.low_latency`) next to the default one, and the PS5 player always got the default one, which holds frames back. With Decoder set to Low latency or Ultra low latency, MoonCast now uses the low-latency decoder when the device has one (Snapdragon `.low_latency`, MediaTek `.lowlatency`). Also: Qualcomm decoders run at full speed (operating-rate), and the thread that hands frames to the screen has display priority. + - **Measured the same way as other players now:** Decode time is from a frame being queued to the decoder until the decoder hands it back, taken before the frame goes to the screen (it included the hand-off before). Detailed stats add **Render wait**: from decoded to really on screen (the wait for the next refresh, plus Frame pacing's hold), on Android 13 and newer. + - Measured on the Odin 2 Portal (PS5-423 home screen, 1080p60 HEVC, mostly still, ~0.3 Mbps): decode Standard 3.9 ms, Low 3.7 ms (low-latency decoder), Ultra 2.8 ms; render wait 0.2 ms with Frame pacing Lowest delay, 7.4 ms Balanced, 12.3 ms Smoothest. The PS5's earlier "up to 5 ms" also counted handing the frame to the screen. Frame pacing is the bigger delay: keep Lowest delay for the quickest picture. - **PS5 up to 100 Mbps (Saleh: PXPlay does 100, MoonCast stopped at 50):** Settings, PlayStation: Bitrate has 50, 80 and 100 too, and Custom goes from 2 to 100 Mbps (home and away), on the handheld and the TV. MoonCast's own 50 Mbps cap is gone; chiaki hands the number to the PS5 as it is. The row warns that 80–100 Mbps needs a strong 5 GHz / 6 GHz link (or cable). Auto is unchanged (30 or 20 Mbps by the link). - CONTRIBUTING.md: how to report problems, build, and send changes (one topic per pull request, CHANGELOG in each, lint and CI, the chiaki-ng submodule, no game pictures or secrets, licences). SECURITY.md: report privately through GitHub's security advisories, and what MoonCast keeps where. - README: rewritten for MoonCast (it was still Moonlight's). It covers what the two apps play and how, the features, install and Sunshine's gamepad setting, building with the chiaki-ng submodule, the licences and the credits. diff --git a/app/src/main/java/com/limelight/Ps5StreamActivity.kt b/app/src/main/java/com/limelight/Ps5StreamActivity.kt index abb46385f9..fd7ee745fe 100644 --- a/app/src/main/java/com/limelight/Ps5StreamActivity.kt +++ b/app/src/main/java/com/limelight/Ps5StreamActivity.kt @@ -806,7 +806,8 @@ class Ps5StreamActivity : Activity(), SurfaceHolder.Callback, com.limelight.home lastLossPct = 100.0 * (v[5] - lastLost) / (v[4] + v[5] - lastShown - lastLost) lastShown = v[4]; lastLost = v[5]; lastAt = now val decodeMs = if (v.size > 6) v[6] else 0.0 - Log.d(TAG, "stats mbps=${v[0]} loss=${v[1]} rtt=${v[2]} rtt0=${v[3]} shown=${v[4]} lost=${v[5]} decode=${"%.2f".format(java.util.Locale.US, decodeMs)}ms") + val renderMs = if (v.size > 7) v[7] else 0.0 // decoded -> on screen (Android 13+); 0 = not known + Log.d(TAG, "stats mbps=${v[0]} loss=${v[1]} rtt=${v[2]} rtt0=${v[3]} shown=${v[4]} lost=${v[5]} decode=${"%.2f".format(java.util.Locale.US, decodeMs)}ms render=${"%.2f".format(java.util.Locale.US, renderMs)}ms") val loss = maxOf(v[1] * 100.0, lastLossPct) val rtt = if (v[3] > 1.0) v[3] else 0.0 // chiaki uses 1 ms when it couldn't measure return listOf( @@ -814,7 +815,8 @@ class Ps5StreamActivity : Activity(), SurfaceHolder.Callback, com.limelight.home (if (rtt > 0) "%.0f ms".format(java.util.Locale.US, rtt) else "…") to "Network delay", (if (v[0] > 0) "%.1f Mbps".format(java.util.Locale.US, v[0]) else "…") to "Picture quality", "%.1f%%".format(java.util.Locale.US, loss) to "Lost packets", - (if (decodeMs > 0) "%.1f ms".format(java.util.Locale.US, decodeMs) else "…") to "Decode time") + (if (decodeMs > 0) "%.1f ms".format(java.util.Locale.US, decodeMs) else "…") to "Decode time", + (if (renderMs > 0) "%.1f ms".format(java.util.Locale.US, renderMs) else "…") to "Render wait") } private fun openMenu() { diff --git a/app/src/main/java/com/limelight/home/StatusCorner.kt b/app/src/main/java/com/limelight/home/StatusCorner.kt index 42753e6a70..329c45adcb 100644 --- a/app/src/main/java/com/limelight/home/StatusCorner.kt +++ b/app/src/main/java/com/limelight/home/StatusCorner.kt @@ -76,14 +76,16 @@ class StatusCorner(private val act: Activity, private val info: () -> String = { "Network delay" -> StatsPill.Chip(v, "network delay") "Picture quality" -> StatsPill.Chip(v, "bitrate") "Lost packets" -> StatsPill.Chip(v, "packets lost on the network") - "Decode time" -> StatsPill.Chip(v, "decode") + "Decode time" -> StatsPill.Chip(v, "decode (queued to decoded)") + "Render wait" -> StatsPill.Chip(v, "render wait (decoded to on screen)") else -> StatsPill.Chip(v, label.lowercase()) } } return StatsPill.detailed(info().ifEmpty { "Streaming" }, rows, StatsPill.health(rtt, loss)) } - val chips = known.map { (v, label) -> + val chips = known.mapNotNull { (v, label) -> when (label) { + "Render wait" -> null // Lite keeps to one row; Detailed shows it "Frames per second" -> StatsPill.Chip(v, "fps") "Network delay" -> { rtt = StatsPill.num(v); StatsPill.Chip(v.removeSuffix(" ms"), "ms delay") } "Lost packets" -> { loss = StatsPill.num(v); StatsPill.Chip(v, "lost") } diff --git a/third-party/chiaki-ng b/third-party/chiaki-ng index c5fa7fad63..227e79dd96 160000 --- a/third-party/chiaki-ng +++ b/third-party/chiaki-ng @@ -1 +1 @@ -Subproject commit c5fa7fad63d56e08fc503c3b746bec4824dfaabc +Subproject commit 227e79dd96b881321d66f4c61d236f5cd5a4be85