feat(clients/android): OnFrameRendered display stage — HUD headline becomes capture→displayed
The long-deferred Android display stage (design/stats-unification.md; plan 4.1 of design/client-parity-and-network-resilience.md): AMediaCodec_setOnFrameRenderedCallback (API 26, under the minSdk-28 floor ⇒ hard-linked via ndk-sys) reports SurfaceFlinger's per-frame render timestamp, giving the HUD the spec's `display` = decoded→displayed term and the directly-measured capture→displayed end-to-end headline on both decode loops. Falls back per spec to the v1 capture→decoded endpoint on any window without render callbacks (the platform may drop them under load), and to it permanently if registration is refused. - The render timestamp arrives on CLOCK_MONOTONIC; it's re-based onto CLOCK_REALTIME against monotonic-now at callback time, which also cancels the (batchable) callback delivery lag. - The `ndk` crate exposes neither the callback nor the codec pointer needed to bind it raw, so the workspace pins `ndk` 0.9.0 to a vendored copy (clients/android/native/ vendor/ndk) whose ONLY change makes MediaCodec::as_ptr public — the "as_ptr patch". Workspace-excluded so host builds never compile it; drop when upstream exposes either. - nativeVideoStats grows to 26 doubles (22–25: dispValid, displayP50, e2eDispP50/P95; 0–21 unchanged for older readers); StatsOverlay moves headline endpoint + equation together so the equation always tiles the headline interval. Verified: host cargo check/test/clippy, aarch64-linux-android check/clippy, Kotlin app+kit+tests compile, roborazzi HUD render shows the full 4-term equation. Device verification rides plan 4.2's phone A/B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+92
@@ -0,0 +1,92 @@
|
||||
//! Bindings for the NDK tracing API.
|
||||
//!
|
||||
//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/tracing)
|
||||
#![cfg(feature = "api-level-23")]
|
||||
use std::ffi::{CString, NulError};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub fn is_trace_enabled() -> bool {
|
||||
unsafe { ffi::ATrace_isEnabled() }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Section {
|
||||
// Section is !Sync and !Send
|
||||
_pd: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
pub fn new(name: &str) -> Result<Self, NulError> {
|
||||
let section_name = CString::new(name)?;
|
||||
unsafe { ffi::ATrace_beginSection(section_name.as_ptr()) };
|
||||
|
||||
Ok(Self { _pd: PhantomData })
|
||||
}
|
||||
|
||||
pub fn end(self) {
|
||||
drop(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Section {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ATrace_endSection() };
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique identifier for distinguishing simultaneous events
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "api-level-29")]
|
||||
pub struct Cookie(pub i32);
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "api-level-29")]
|
||||
pub struct AsyncSection {
|
||||
section_name: CString,
|
||||
cookie: Cookie,
|
||||
// AsyncSection is !Sync
|
||||
_pd: PhantomData<&'static ()>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-29")]
|
||||
impl AsyncSection {
|
||||
pub fn new(name: &str, cookie: Cookie) -> Result<Self, NulError> {
|
||||
let section_name = CString::new(name)?;
|
||||
unsafe { ffi::ATrace_beginAsyncSection(section_name.as_ptr(), cookie.0) };
|
||||
|
||||
Ok(Self {
|
||||
section_name,
|
||||
cookie,
|
||||
_pd: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn end(self) {
|
||||
drop(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-29")]
|
||||
impl Drop for AsyncSection {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ATrace_endAsyncSection(self.section_name.as_ptr(), self.cookie.0) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-29")]
|
||||
#[derive(Debug)]
|
||||
pub struct Counter {
|
||||
name: CString,
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-29")]
|
||||
impl Counter {
|
||||
pub fn new(name: &str) -> Result<Self, NulError> {
|
||||
let name = CString::new(name)?;
|
||||
Ok(Self { name })
|
||||
}
|
||||
|
||||
pub fn set_value(&self, value: i64) {
|
||||
unsafe { ffi::ATrace_setCounter(self.name.as_ptr(), value) }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user