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:
Generated
+1
-2
@@ -2407,8 +2407,6 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"jni-sys 0.3.1",
|
||||
@@ -3011,6 +3009,7 @@ dependencies = [
|
||||
"log",
|
||||
"mdns-sd",
|
||||
"ndk",
|
||||
"ndk-sys",
|
||||
"opus",
|
||||
"punktfunk-core",
|
||||
"tracing",
|
||||
|
||||
+13
-1
@@ -19,7 +19,19 @@ members = [
|
||||
"tools/loss-harness",
|
||||
]
|
||||
# Standalone PoC (built on its own; pulls usbip/tokio/libusb we don't want in the workspace).
|
||||
exclude = ["packaging/linux/steam-deck-gadget/usbip-poc"]
|
||||
# The vendored `ndk` is a [patch.crates-io] source, not a member: it only compiles for the
|
||||
# `*-linux-android` targets, so workspace membership would break host `cargo build --workspace`.
|
||||
exclude = [
|
||||
"packaging/linux/steam-deck-gadget/usbip-poc",
|
||||
"clients/android/native/vendor/ndk",
|
||||
]
|
||||
|
||||
# ndk 0.9.0 verbatim from crates.io plus ONE visibility change: `MediaCodec::as_ptr` made public
|
||||
# (upstream keeps it private and exposes no frame-rendered binding), so the Android client can
|
||||
# call `AMediaCodec_setOnFrameRenderedCallback` via ndk-sys for the HUD's `display` stage
|
||||
# (design/stats-unification.md). Drop the patch when upstream exposes the pointer or the callback.
|
||||
[patch.crates-io]
|
||||
ndk = { path = "clients/android/native/vendor/ndk" }
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.4"
|
||||
|
||||
@@ -15,12 +15,14 @@ import io.unom.punktfunk.kit.NativeBridge
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* The live stats overlay — the unified HUD (`design/stats-unification.md`, Android v1: headline is
|
||||
* `capture→decoded`, tiled by `host+network` + `decode`). Reads the 22-double layout from
|
||||
* [NativeBridge.nativeVideoStats]:
|
||||
* The live stats overlay — the unified HUD (`design/stats-unification.md`): headline is
|
||||
* `capture→displayed` tiled by `host+network` + `decode` + `display` when the platform delivered
|
||||
* OnFrameRendered render callbacks this window (`dispValid`), falling back to the v1
|
||||
* `capture→decoded` headline without the `display` term when it didn't. Reads the 26-double
|
||||
* layout from [NativeBridge.nativeVideoStats]:
|
||||
* `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skew, w, h, hz, lostTotal, bitDepth, colorPrimaries,
|
||||
* colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms, netP50Ms, lost, skipped,
|
||||
* fec, frames]`.
|
||||
* fec, frames, dispValid, displayP50Ms, e2eDispP50Ms, e2eDispP95Ms]`.
|
||||
*
|
||||
* [verbosity] selects how many lines render (each tier a superset of the last — see
|
||||
* [StatsVerbosity]):
|
||||
@@ -67,21 +69,32 @@ internal fun StatsOverlay(
|
||||
videoFeedLine(s)?.let { statLine(it, Color.White) }
|
||||
}
|
||||
if (latValid) {
|
||||
// Display stage (s[22]–s[25], from OnFrameRendered): when a render timestamp landed
|
||||
// this window the headline is the directly-measured capture→displayed pair and the
|
||||
// equation gains its `display` term; otherwise (older lib / no callbacks) the endpoint
|
||||
// honestly stays capture→decoded — the equation always tiles the headline interval.
|
||||
val dispValid = s.size >= 26 && s[22] != 0.0
|
||||
val tag = if (skew) "" else " (same-host clock)"
|
||||
val (p50, p95, endpoint) = if (dispValid) {
|
||||
Triple(s[24], s[25], "capture→displayed")
|
||||
} else {
|
||||
Triple(s[2], s[3], "capture→decoded")
|
||||
}
|
||||
statLine(
|
||||
"end-to-end ${"%.1f".format(s[2])} ms p50 · ${"%.1f".format(s[3])} p95 · capture→decoded$tag",
|
||||
"end-to-end ${"%.1f".format(p50)} ms p50 · ${"%.1f".format(p95)} p95 · $endpoint$tag",
|
||||
Color.White,
|
||||
)
|
||||
if (detailed && s.size >= 16) {
|
||||
// Phase-2 split (s[16]/s[17]): render `host + network` separately when the host
|
||||
// reported its share this window; otherwise the combined term (old host / no
|
||||
// matched 0xCF timing).
|
||||
val equation = if (s.size >= 18 && s[16] > 0) {
|
||||
"= host ${"%.1f".format(s[16])} + network ${"%.1f".format(s[17])} + decode ${"%.1f".format(s[15])}"
|
||||
val hostTerms = if (s.size >= 18 && s[16] > 0) {
|
||||
"host ${"%.1f".format(s[16])} + network ${"%.1f".format(s[17])}"
|
||||
} else {
|
||||
"= host+network ${"%.1f".format(s[14])} + decode ${"%.1f".format(s[15])}"
|
||||
"host+network ${"%.1f".format(s[14])}"
|
||||
}
|
||||
statLine(equation, Color.White)
|
||||
val displayTerm = if (dispValid) " + display ${"%.1f".format(s[23])}" else ""
|
||||
statLine("= $hostTerms + decode ${"%.1f".format(s[15])}$displayTerm", Color.White)
|
||||
}
|
||||
}
|
||||
counterLine(s, lost)?.let { statLine(it, Color(0xFFFFB0B0)) }
|
||||
@@ -101,9 +114,11 @@ private fun statLine(text: String, color: Color) {
|
||||
* one reliability signal worth surfacing even at the tersest tier.
|
||||
*/
|
||||
private fun compactLine(s: DoubleArray, latValid: Boolean): String {
|
||||
// Prefer the capture→displayed end-to-end (s[24]) when a render timestamp landed this window.
|
||||
val e2eP50 = if (s.size >= 26 && s[22] != 0.0) s[24] else s[2]
|
||||
val parts = buildList {
|
||||
add("${s[0].roundToInt()} fps")
|
||||
if (latValid) add("${"%.1f".format(s[2])} ms")
|
||||
if (latValid) add("${"%.1f".format(e2eP50)} ms")
|
||||
add("${s[1].roundToInt()} Mb/s")
|
||||
}
|
||||
val lostWindow = if (s.size >= 22) s[18].toLong() else s[9].toLong()
|
||||
|
||||
@@ -191,19 +191,23 @@ internal fun StreamScene(verbosity: StatsVerbosity = StatsVerbosity.DETAILED) {
|
||||
Brush.linearGradient(listOf(Color(0xFF2A1E5C), Color(0xFF0E1B3D), Color(0xFF06122B))),
|
||||
),
|
||||
) {
|
||||
// The full 22-double unified layout (design/stats-unification.md): [fps, mbps, e2eP50,
|
||||
// The full 26-double unified layout (design/stats-unification.md): [fps, mbps, e2eP50,
|
||||
// e2eP95, latValid, skew, w, h, hz, lostTotal, bitDepth, colorPrimaries, colorTransfer,
|
||||
// chromaFormatIdc, hostNetP50, decodeP50, hostP50, netP50, lost, skipped, fec, frames].
|
||||
// chromaFormatIdc, hostNetP50, decodeP50, hostP50, netP50, lost, skipped, fec, frames,
|
||||
// dispValid, displayP50, e2eDispP50, e2eDispP95].
|
||||
// 10/9/16/1 = a 10-bit BT.2020 PQ (HDR) 4:2:0 feed so the DETAILED HUD renders its
|
||||
// video-feed line; the Phase-2 stage terms (host 0.6 + network 0.3 + decode 0.4) tile the
|
||||
// 1.3 ms headline so it renders the full split equation, and the decoder label shows the
|
||||
// ranked low-latency decoder. Light per-window loss (lost 2 · skipped 1 · FEC 5 of 238) so
|
||||
// the reliability line (NORMAL/DETAILED) and the compact loss flag both render.
|
||||
// video-feed line; the display stage is valid (dispValid 1) so the headline is the
|
||||
// directly-measured capture→displayed pair (1.8/2.6) and the Phase-2 stage terms
|
||||
// (host 0.6 + network 0.3 + decode 0.4 + display 0.5) tile it, rendering the full split
|
||||
// equation; the decoder label shows the ranked low-latency decoder. Light per-window loss
|
||||
// (lost 2 · skipped 1 · FEC 5 of 238) so the reliability line (NORMAL/DETAILED) and the
|
||||
// compact loss flag both render.
|
||||
StatsOverlay(
|
||||
doubleArrayOf(
|
||||
238.0, 921.4, 1.3, 2.1, 1.0, 1.0, 5120.0, 1440.0, 240.0, 2.0,
|
||||
10.0, 9.0, 16.0, 1.0, 0.9, 0.4, 0.6, 0.3,
|
||||
2.0, 1.0, 5.0, 238.0,
|
||||
1.0, 0.5, 1.8, 2.6,
|
||||
),
|
||||
verbosity = verbosity,
|
||||
decoderLabel = "c2.qti.hevc.decoder · low-latency",
|
||||
|
||||
@@ -183,16 +183,22 @@ object NativeBridge {
|
||||
|
||||
/**
|
||||
* Drain ~1 s of live decode stats for the on-stream HUD, or `null` when no decode thread runs.
|
||||
* Returns 18 doubles (unified stats spec, `design/stats-unification.md`):
|
||||
* Returns 26 doubles (unified stats spec, `design/stats-unification.md`):
|
||||
* `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skewCorrected, width, height, refreshHz, framesLost,
|
||||
* bitDepth, colorPrimaries, colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms,
|
||||
* netP50Ms]`
|
||||
* (the two flags are 1.0/0.0; indexes 2/3 are the end-to-end capture→decoded headline; 10–13
|
||||
* netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow, dispValid, displayP50Ms,
|
||||
* e2eDispP50Ms, e2eDispP95Ms]`
|
||||
* (the flags are 1.0/0.0; indexes 2/3 are the end-to-end capture→decoded headline; 10–13
|
||||
* describe the negotiated video feed — bit depth 8/10, CICP primaries/transfer, and the HEVC
|
||||
* chroma_format_idc 1=4:2:0 / 3=4:4:4; 14/15 are the stage p50s tiling the headline —
|
||||
* `host+network` = capture→received, `decode` = received→decoded; 16/17 split the
|
||||
* `host+network` term via the host's per-AU 0xCF timings — `host` = the host's capture→sent,
|
||||
* `network` = the remainder — both 0.0 when no timing matched this window, i.e. an old host).
|
||||
* `network` = the remainder — both 0.0 when no timing matched this window, i.e. an old host;
|
||||
* 18–21 are the per-window reliability counters — lost/skipped/FEC/received; 22–25 are the
|
||||
* `display` stage from the OnFrameRendered render timestamps — when `dispValid` is 1.0 the
|
||||
* headline becomes the directly-measured capture→displayed pair at 24/25, tiled by
|
||||
* `host+network` + `decode` + `display` (23), and when 0.0 the HUD falls back to the
|
||||
* capture→decoded headline at 2/3 without the `display` term).
|
||||
* Poll ~1 Hz; each call resets the measurement window.
|
||||
*/
|
||||
external fun nativeVideoStats(handle: Long): DoubleArray?
|
||||
|
||||
@@ -47,6 +47,12 @@ tracing = { version = "0.1", default-features = false, features = ["std", "log"]
|
||||
# (ANativeWindow_setFrameRate) is dlsym-resolved at runtime (see decode::try_set_frame_rate), not
|
||||
# linked, so the .so still loads on API 28/29.
|
||||
ndk = { version = "0.9", features = ["media", "audio", "nativewindow", "api-level-28"] }
|
||||
# Raw FFI for the one AMediaCodec entry point the `ndk` wrapper lacks:
|
||||
# `AMediaCodec_setOnFrameRenderedCallback` (API 26, under the minSdk-28 floor ⇒ hard-linked) — the
|
||||
# per-frame render-timestamp callback behind the HUD's `display` stage (see decode::DisplayTracker).
|
||||
# Reaching it needs the codec's raw pointer, which is why the workspace pins `ndk` to the vendored
|
||||
# copy whose only patch makes `MediaCodec::as_ptr` public (vendor/ndk, wired in the root Cargo.toml).
|
||||
ndk-sys = { version = "0.6", features = ["media"] }
|
||||
# setpriority/gettid to raise the decode thread toward URGENT_DISPLAY (see decode::boost_thread_priority).
|
||||
libc = "0.2"
|
||||
# Opus decode for the host→client audio plane (0xC9: 48 kHz stereo, 5 ms frames). Same crate the
|
||||
|
||||
@@ -36,6 +36,12 @@ const IN_FLIGHT_CAP: usize = 64;
|
||||
/// this deep is a lost datagram (or an old host that never sends any) and gets evicted.
|
||||
const PENDING_SPLIT_CAP: usize = 256;
|
||||
|
||||
/// Cap on rendered frames parked in [`DisplayTracker`] awaiting their `OnFrameRendered` render
|
||||
/// timestamp: the callback trails its release by at most a vsync or two, so anything this deep
|
||||
/// means the platform stopped delivering render callbacks (allowed under load, per the docs) and
|
||||
/// gets evicted.
|
||||
const RENDERED_CAP: usize = 64;
|
||||
|
||||
/// Whether low-latency mode uses the event-driven async decode loop (default) or the synchronous
|
||||
/// poll loop. Flip to `false` to A/B the two on the HUD (`design/…`); the async loop presents a
|
||||
/// decoded frame the instant it's ready instead of waiting out a poll interval. Only consulted when
|
||||
@@ -208,6 +214,12 @@ fn run_sync(
|
||||
// host-minus-client clock offset (0 if the host didn't answer the skew handshake — then the
|
||||
// HUD flags it "(same-host clock)").
|
||||
let clock_offset = client.clock_offset_ns;
|
||||
// Display stage (spec `display` + the capture→displayed headline): frames released with
|
||||
// render = true are parked in the tracker; the OnFrameRendered callback pairs them with
|
||||
// SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount,
|
||||
// reclaimed after the codec is dropped below.
|
||||
let tracker = DisplayTracker::new(stats.clone(), clock_offset);
|
||||
let render_cb = install_render_callback(&codec, &tracker);
|
||||
// HUD stage split: receipt timestamps keyed by the pts we queue into the codec, so the decoded
|
||||
// point (output-buffer dequeue — MediaCodec round-trips presentationTimeUs) can be paired back
|
||||
// to its receipt for the `decode` stage. Only fed while the HUD is visible.
|
||||
@@ -309,6 +321,7 @@ fn run_sync(
|
||||
&stats,
|
||||
&mut in_flight,
|
||||
clock_offset,
|
||||
&tracker,
|
||||
);
|
||||
rendered += r;
|
||||
discarded += d;
|
||||
@@ -367,6 +380,11 @@ fn run_sync(
|
||||
}
|
||||
|
||||
let _ = codec.stop();
|
||||
drop(codec); // AMediaCodec_delete — after this no render callback can fire
|
||||
if let Some(ud) = render_cb {
|
||||
// SAFETY: the codec was dropped above; this registration's single reclaim.
|
||||
unsafe { release_render_callback(ud) };
|
||||
}
|
||||
log::info!("decode: stopped (fed={fed} rendered={rendered} discarded={discarded})");
|
||||
}
|
||||
|
||||
@@ -380,6 +398,145 @@ fn now_realtime_ns() -> i128 {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// `CLOCK_MONOTONIC` now in nanoseconds — the base of the `systemNano` render timestamp the
|
||||
/// `OnFrameRendered` callback reports (Android's `System.nanoTime`), read only to re-base that
|
||||
/// stamp onto `CLOCK_REALTIME` (see [`on_frame_rendered`]).
|
||||
fn now_monotonic_ns() -> i128 {
|
||||
let mut ts = libc::timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0,
|
||||
};
|
||||
// SAFETY: `clock_gettime` with a valid out-pointer is an always-safe syscall.
|
||||
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
|
||||
ts.tv_sec as i128 * 1_000_000_000 + ts.tv_nsec as i128
|
||||
}
|
||||
|
||||
/// State shared between the decode loop and the `AMediaCodec` `OnFrameRendered` callback (which
|
||||
/// fires on a codec-internal thread): rendered frames awaiting their render timestamp, so the HUD
|
||||
/// gets the spec's `display` stage (decoded→displayed) and the `capture→displayed` end-to-end
|
||||
/// headline (`design/stats-unification.md` — this replaces Android's v1 `capture→decoded`
|
||||
/// endpoint whenever the platform delivers render callbacks).
|
||||
struct DisplayTracker {
|
||||
stats: Arc<crate::stats::VideoStats>,
|
||||
/// Host-minus-client clock offset (ns) for the skew-corrected end-to-end sample.
|
||||
clock_offset: i64,
|
||||
/// `(pts_us, decoded_real_ns)` of frames released with `render = true`, in release order,
|
||||
/// awaiting their callback. Pushes are HUD-gated by the caller, so this stays empty (and the
|
||||
/// callback early-outs) while the overlay is hidden.
|
||||
rendered: Mutex<VecDeque<(u64, i128)>>,
|
||||
}
|
||||
|
||||
impl DisplayTracker {
|
||||
fn new(stats: Arc<crate::stats::VideoStats>, clock_offset: i64) -> Arc<DisplayTracker> {
|
||||
Arc::new(DisplayTracker {
|
||||
stats,
|
||||
clock_offset,
|
||||
rendered: Mutex::new(VecDeque::new()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Park one just-rendered frame's `(pts, decoded stamp)` for the render callback to pair.
|
||||
/// Caller gates on the HUD being visible.
|
||||
fn note_rendered(&self, pts_us: u64, decoded_ns: i128) {
|
||||
let mut g = self
|
||||
.rendered
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
g.push_back((pts_us, decoded_ns));
|
||||
if g.len() > RENDERED_CAP {
|
||||
g.pop_front(); // render callbacks stopped coming (allowed under load) — evict
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register [`on_frame_rendered`] on the codec (`AMediaCodec_setOnFrameRenderedCallback`, API 26 —
|
||||
/// under the minSdk-28 floor, so hard-linked via `ndk-sys`; the `ndk` wrapper has no binding, which
|
||||
/// is what the vendored crate's public `as_ptr` patch is for). Returns the userdata pointer holding
|
||||
/// a leaked `Arc<DisplayTracker>` refcount; the caller MUST reclaim it with
|
||||
/// [`release_render_callback`] AFTER dropping the codec (`AMediaCodec_delete` is what guarantees no
|
||||
/// further callback can fire). `None` (nothing to reclaim) if the platform refused — the HUD then
|
||||
/// simply has no `display` stage, exactly the pre-callback behaviour.
|
||||
fn install_render_callback(
|
||||
codec: &MediaCodec,
|
||||
tracker: &Arc<DisplayTracker>,
|
||||
) -> Option<*const DisplayTracker> {
|
||||
let ud = Arc::into_raw(tracker.clone());
|
||||
// SAFETY: `codec.as_ptr()` is the live codec this thread owns; `ud` outlives the registration
|
||||
// (reclaimed only after the codec is deleted, per this function's contract).
|
||||
let status = unsafe {
|
||||
ndk_sys::AMediaCodec_setOnFrameRenderedCallback(
|
||||
codec.as_ptr(),
|
||||
Some(on_frame_rendered),
|
||||
ud as *mut c_void,
|
||||
)
|
||||
};
|
||||
if status == ndk_sys::media_status_t::AMEDIA_OK {
|
||||
Some(ud)
|
||||
} else {
|
||||
log::warn!("decode: setOnFrameRenderedCallback failed ({status:?}) — no display stage");
|
||||
// SAFETY: registration failed, so the codec never took the reference — reclaim it now.
|
||||
unsafe { drop(Arc::from_raw(ud)) };
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Reclaim [`install_render_callback`]'s leaked `Arc` refcount.
|
||||
///
|
||||
/// # Safety
|
||||
/// Call exactly once, and only after the codec the callback was registered on has been dropped —
|
||||
/// deleting the codec stops its internal threads, so no callback can still be running (or run
|
||||
/// later) against this pointer.
|
||||
unsafe fn release_render_callback(ud: *const DisplayTracker) {
|
||||
drop(Arc::from_raw(ud));
|
||||
}
|
||||
|
||||
/// The `AMediaCodecOnFrameRendered` trampoline: fires (possibly batched) on a codec-internal
|
||||
/// thread once per output frame actually placed on the output surface, with SurfaceFlinger's
|
||||
/// render timestamp. That timestamp (`system_nano`) is on `CLOCK_MONOTONIC`, so it is re-based
|
||||
/// onto `CLOCK_REALTIME` here — against monotonic-now at callback time, which also cancels any lag
|
||||
/// between the frame rendering and the (batchable) callback delivery — to subtract against the
|
||||
/// receipt/decode stamps and the host capture pts. Records the HUD's `displayed` point:
|
||||
/// `end-to-end` = capture→displayed (skew-corrected) and `display` = decoded→displayed
|
||||
/// (single-clock local). Panic-free by construction (poison-proof lock, saturating math) — an
|
||||
/// unwind out of an `extern "C"` fn would abort the process.
|
||||
unsafe extern "C" fn on_frame_rendered(
|
||||
_codec: *mut ndk_sys::AMediaCodec,
|
||||
userdata: *mut c_void,
|
||||
media_time_us: i64,
|
||||
system_nano: i64,
|
||||
) {
|
||||
let t = &*(userdata as *const DisplayTracker);
|
||||
if !t.stats.enabled() {
|
||||
return; // HUD hidden — the ring is empty too (pushes are caller-gated)
|
||||
}
|
||||
let displayed_ns = now_realtime_ns() - (now_monotonic_ns() - system_nano as i128);
|
||||
let pts_us = media_time_us.max(0) as u64;
|
||||
// Pair the frame back to its release record, evicting older entries (their callbacks were
|
||||
// dropped by the platform, or the entry predates a HUD toggle) — same monotonic-eviction
|
||||
// discipline as `note_decoded_pts`.
|
||||
let mut decoded_ns = None;
|
||||
{
|
||||
let mut g = t
|
||||
.rendered
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
while let Some(&(p, d)) = g.front() {
|
||||
if p > pts_us {
|
||||
break; // future frame — leave it for its own callback
|
||||
}
|
||||
g.pop_front();
|
||||
if p == pts_us {
|
||||
decoded_ns = Some(d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let e2e_ns = displayed_ns + t.clock_offset as i128 - pts_us as i128 * 1000;
|
||||
let e2e_us = (e2e_ns > 0 && e2e_ns < 10_000_000_000).then_some((e2e_ns / 1000) as u64);
|
||||
let display_us = decoded_ns.map(|d| ((displayed_ns - d).max(0) / 1000) as u64);
|
||||
t.stats.note_displayed(e2e_us, display_us);
|
||||
}
|
||||
|
||||
/// The MediaCodec MIME for the codec the host resolved (`Welcome.codec`). Shared by the decode
|
||||
/// thread and `nativeVideoMime` (which tells Kotlin what to rank decoders for). AV1 uses the
|
||||
/// AOSP `video/av01` type; anything not H.264/AV1 is treated as HEVC (every pre-negotiation host
|
||||
@@ -649,6 +806,12 @@ fn run_async(
|
||||
// HUD is visible.
|
||||
let clock_offset = client.clock_offset_ns;
|
||||
let in_flight = Arc::new(Mutex::new(VecDeque::<(u64, i128)>::new()));
|
||||
// Display stage (spec `display` + the capture→displayed headline): the rendered frame is
|
||||
// parked in the tracker at release; the OnFrameRendered callback pairs it with
|
||||
// SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount,
|
||||
// reclaimed after the codec is dropped below.
|
||||
let tracker = DisplayTracker::new(stats.clone(), clock_offset);
|
||||
let render_cb = install_render_callback(&codec, &tracker);
|
||||
|
||||
// Feeder thread: block on the network so this loop doesn't (an AU's arrival becomes an event that
|
||||
// wakes us immediately, with no input-side poll latency). It also records the `received` HUD stat.
|
||||
@@ -744,6 +907,7 @@ fn run_async(
|
||||
&stats,
|
||||
&in_flight,
|
||||
clock_offset,
|
||||
&tracker,
|
||||
&mut rendered,
|
||||
&mut discarded,
|
||||
);
|
||||
@@ -796,6 +960,11 @@ fn run_async(
|
||||
if let Some(j) = feeder {
|
||||
let _ = j.join();
|
||||
}
|
||||
drop(codec); // AMediaCodec_delete — after this no render callback can fire
|
||||
if let Some(ud) = render_cb {
|
||||
// SAFETY: the codec was dropped above; this registration's single reclaim.
|
||||
unsafe { release_render_callback(ud) };
|
||||
}
|
||||
log::info!("decode: stopped (async, fed={fed} rendered={rendered} discarded={discarded})");
|
||||
}
|
||||
|
||||
@@ -938,13 +1107,17 @@ fn feed_ready(
|
||||
/// burst of stale frames on glass is worse than skipping to the freshest (the sync loop's newest-ready
|
||||
/// policy, callback-driven). Every dequeued buffer, rendered or not, is the HUD's `decoded`
|
||||
/// measurement point (it finished decoding either way); samples are recorded in pts order so the
|
||||
/// receipt-map eviction stays monotonic. `ready` is drained.
|
||||
/// receipt-map eviction stays monotonic. The presented frame's `(pts, decoded stamp)` is parked in
|
||||
/// `tracker` for the OnFrameRendered callback — the `display` stage's other endpoint. `ready` is
|
||||
/// drained.
|
||||
#[allow(clippy::too_many_arguments)] // one call site; mirrors the sync loop's drain
|
||||
fn present_ready(
|
||||
codec: &MediaCodec,
|
||||
ready: &mut Vec<OutputReady>,
|
||||
stats: &crate::stats::VideoStats,
|
||||
in_flight: &Mutex<VecDeque<(u64, i128)>>,
|
||||
clock_offset: i64,
|
||||
tracker: &DisplayTracker,
|
||||
rendered: &mut u64,
|
||||
discarded: &mut u64,
|
||||
) {
|
||||
@@ -964,7 +1137,12 @@ fn present_ready(
|
||||
for (i, o) in ready.drain(..).enumerate() {
|
||||
let render = i == last;
|
||||
match codec.release_output_buffer_by_index(o.index, render) {
|
||||
Ok(()) if render => *rendered += 1,
|
||||
Ok(()) if render => {
|
||||
*rendered += 1;
|
||||
if stats.enabled() {
|
||||
tracker.note_rendered(o.pts_us, o.decoded_ns);
|
||||
}
|
||||
}
|
||||
Ok(()) => {
|
||||
*discarded += 1;
|
||||
skipped += 1;
|
||||
@@ -1143,7 +1321,10 @@ fn feed(codec: &MediaCodec, au: &[u8], pts_us: u64) -> bool {
|
||||
/// Each dequeued buffer is also the HUD's `decoded` measurement point (rendered or not — the frame
|
||||
/// finished decoding either way): end-to-end = decoded + clock_offset − capture pts, and the
|
||||
/// `decode` stage pairs the buffer's echoed presentationTimeUs back to the receipt stamp in
|
||||
/// `in_flight` (single-clock local difference, no skew involved).
|
||||
/// `in_flight` (single-clock local difference, no skew involved). The presented frame's
|
||||
/// `(pts, decoded stamp)` is additionally parked in `tracker` for the OnFrameRendered callback —
|
||||
/// the `display` stage's other endpoint.
|
||||
#[allow(clippy::too_many_arguments)] // one call site; mirrors the async loop's present_ready
|
||||
fn drain(
|
||||
codec: &MediaCodec,
|
||||
window: &NativeWindow,
|
||||
@@ -1152,18 +1333,27 @@ fn drain(
|
||||
stats: &crate::stats::VideoStats,
|
||||
in_flight: &mut VecDeque<(u64, i128)>,
|
||||
clock_offset: i64,
|
||||
tracker: &DisplayTracker,
|
||||
) -> (u64, u64) {
|
||||
let mut held = None; // newest ready buffer so far, presented after the loop
|
||||
// Newest ready buffer so far (presented after the loop) with its HUD metadata —
|
||||
// `Some((pts_us, decoded_ns))` only while the HUD is visible (the stamp read is gated).
|
||||
let mut held: Option<(OutputBuffer<'_>, Option<(u64, i128)>)> = None;
|
||||
let mut discarded: u64 = 0;
|
||||
let mut wait = first_wait;
|
||||
loop {
|
||||
match codec.dequeue_output_buffer(wait) {
|
||||
Ok(DequeuedOutputBufferInfoResult::Buffer(buf)) => {
|
||||
wait = Duration::ZERO; // only the first dequeue may block
|
||||
if stats.enabled() {
|
||||
note_decoded(stats, in_flight, clock_offset, &buf);
|
||||
}
|
||||
if let Some(stale) = held.replace(buf) {
|
||||
let meta = if stats.enabled() {
|
||||
// The dequeue IS the sync loop's decoded-availability instant.
|
||||
let pts_us = buf.info().presentation_time_us().max(0) as u64;
|
||||
let decoded_ns = now_realtime_ns();
|
||||
note_decoded_pts(stats, in_flight, clock_offset, pts_us, decoded_ns);
|
||||
Some((pts_us, decoded_ns))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some((stale, _)) = held.replace((buf, meta)) {
|
||||
// A newer frame is ready — drop the held one without rendering.
|
||||
if let Err(e) = codec.release_output_buffer(stale, false) {
|
||||
log::warn!("decode: release_output_buffer(discard): {e}");
|
||||
@@ -1202,41 +1392,30 @@ fn drain(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Present the newest ready frame, if any.
|
||||
// Present the newest ready frame, if any, and park its metadata for the render callback.
|
||||
let mut rendered = 0;
|
||||
if let Some(buf) = held {
|
||||
if let Some((buf, meta)) = held {
|
||||
match codec.release_output_buffer(buf, true) {
|
||||
Ok(()) => rendered = 1,
|
||||
Ok(()) => {
|
||||
rendered = 1;
|
||||
if let Some((pts_us, decoded_ns)) = meta {
|
||||
tracker.note_rendered(pts_us, decoded_ns);
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("decode: release_output_buffer: {e}"),
|
||||
}
|
||||
}
|
||||
(rendered, discarded)
|
||||
}
|
||||
|
||||
/// HUD `decoded` point for one dequeued output buffer: build the end-to-end (capture→decoded,
|
||||
/// skew-corrected, clamped to (0, 10 s)) and `decode` (received→decoded, single-clock local, ≥ 0)
|
||||
/// samples and hand them to [`crate::stats::VideoStats::note_decoded`]. The codec echoes the input
|
||||
/// `presentationTimeUs` on the output buffer, which keys the receipt stamp in `in_flight`; entries
|
||||
/// older than the echoed pts are evicted (decode order == input order here — low-latency, no
|
||||
/// HUD `decoded` point for one dequeued output frame, keyed by the echoed `presentationTimeUs`:
|
||||
/// build the end-to-end (capture→decoded, skew-corrected, clamped to (0, 10 s)) and `decode`
|
||||
/// (received→decoded, single-clock local, ≥ 0) samples and hand them to
|
||||
/// [`crate::stats::VideoStats::note_decoded`]. The pts keys the receipt stamp in `in_flight`;
|
||||
/// entries older than it are evicted (decode order == input order here — low-latency, no
|
||||
/// B-frames — so anything before it was dropped inside the codec or stamped before a flush).
|
||||
fn note_decoded(
|
||||
stats: &crate::stats::VideoStats,
|
||||
in_flight: &mut VecDeque<(u64, i128)>,
|
||||
clock_offset: i64,
|
||||
buf: &OutputBuffer<'_>,
|
||||
) {
|
||||
note_decoded_pts(
|
||||
stats,
|
||||
in_flight,
|
||||
clock_offset,
|
||||
buf.info().presentation_time_us().max(0) as u64,
|
||||
now_realtime_ns(), // sync loop: the dequeue IS the availability instant
|
||||
);
|
||||
}
|
||||
|
||||
/// The [`note_decoded`] body keyed by the echoed `presentationTimeUs` directly — the async loop has
|
||||
/// the pts (from the output callback's `BufferInfo`) but no borrowed `OutputBuffer`, so it calls
|
||||
/// this with the `decoded` stamp taken in the output callback itself (the availability instant).
|
||||
/// `decoded_ns` is the availability instant: the dequeue (sync loop) or the output callback's
|
||||
/// stamp (async loop).
|
||||
fn note_decoded_pts(
|
||||
stats: &crate::stats::VideoStats,
|
||||
in_flight: &mut VecDeque<(u64, i128)>,
|
||||
|
||||
@@ -144,11 +144,12 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo(
|
||||
}
|
||||
|
||||
/// `NativeBridge.nativeVideoStats(handle): DoubleArray?` — drain ~1 s of decode stats for the HUD
|
||||
/// (unified stats spec, `design/stats-unification.md`). Returns 22 doubles
|
||||
/// (unified stats spec, `design/stats-unification.md`). Returns 26 doubles
|
||||
/// `[fps, mbps, e2eP50Ms, e2eP95Ms, latValid, skewCorrected, width, height, refreshHz, framesLost,
|
||||
/// bitDepth, colorPrimaries, colorTransfer, chromaFormatIdc, hostNetP50Ms, decodeP50Ms, hostP50Ms,
|
||||
/// netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow]`
|
||||
/// (the two flags are 1.0/0.0; indexes 0–17 match the previous 18-double layout — 0–13 the original
|
||||
/// netP50Ms, lostWindow, skippedWindow, fecWindow, framesWindow, dispValid, displayP50Ms,
|
||||
/// e2eDispP50Ms, e2eDispP95Ms]`
|
||||
/// (the flags are 1.0/0.0; indexes 0–21 match the previous 22-double layout — 0–13 the original
|
||||
/// 14-double one with the latency pair re-based to the end-to-end capture→decoded headline, 14/15
|
||||
/// the stage p50s tiling it: `host+network` = capture→received, `decode` = received→decoded; 16/17
|
||||
/// are the Phase-2 split of the `host+network` term from the per-AU 0xCF host timings — `host` =
|
||||
@@ -156,7 +157,11 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeStopVideo(
|
||||
/// window, i.e. an old host; 18–21 are the spec's per-window line-4 counters — `lost` =
|
||||
/// unrecoverable drops this window, `skipped` = client newest-wins/pacing drops, `fec` = shards
|
||||
/// recovered, `frames` = AUs received, so the HUD can compute `lost/(frames+lost)` — index 9 stays
|
||||
/// the cumulative session total for older readers), or `null` when no decode thread is running.
|
||||
/// the cumulative session total for older readers; 22–25 are the `display` stage from the
|
||||
/// OnFrameRendered render timestamps — when `dispValid` is 1.0 the HUD headline becomes the
|
||||
/// directly-measured capture→displayed pair at 24/25 with `display` = decoded→displayed p50 at 23
|
||||
/// closing the equation, and when 0.0 — no render callback landed this window — it falls back to
|
||||
/// the capture→decoded headline at 2/3), or `null` when no decode thread is running.
|
||||
/// Poll ~1 Hz from the UI; each call
|
||||
/// resets the measurement window. Not android-gated — pure `jni` + connector reads, so it links on
|
||||
/// the host build too (Kotlin only ever calls it on device).
|
||||
@@ -180,7 +185,7 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats(
|
||||
.drain(h.client.frames_dropped(), h.client.fec_recovered_shards());
|
||||
let mode = h.client.mode();
|
||||
let color = h.client.color;
|
||||
let buf: [f64; 22] = [
|
||||
let buf: [f64; 26] = [
|
||||
snap.fps,
|
||||
snap.mbps,
|
||||
snap.e2e_p50_ms,
|
||||
@@ -213,6 +218,14 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeVideoStats(
|
||||
snap.skipped as f64,
|
||||
snap.fec as f64,
|
||||
snap.frames as f64,
|
||||
// `display` stage (OnFrameRendered render timestamps): validity flag, the
|
||||
// decoded→displayed stage p50, and the directly-measured capture→displayed headline
|
||||
// pair that supersedes 2/3 whenever the flag is set (spec: the equation always tiles
|
||||
// the headline interval, so endpoint and terms move together).
|
||||
if snap.disp_valid { 1.0 } else { 0.0 },
|
||||
snap.display_p50_ms,
|
||||
snap.e2e_disp_p50_ms,
|
||||
snap.e2e_disp_p95_ms,
|
||||
];
|
||||
let arr = match env.new_double_array(buf.len() as jsize) {
|
||||
Ok(a) => a,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
//! Live decode stats for the on-stream HUD, following the unified stats spec
|
||||
//! (`design/stats-unification.md`): FPS, receive throughput, and the Android v1 stage split —
|
||||
//! headline `end-to-end` = capture→decoded (p50/p95) tiled by `host+network` = capture→received
|
||||
//! and `decode` = received→decoded (stage p50s). When the host emits per-AU 0xCF host timings, the
|
||||
//! `host+network` term further splits into `host` + `network` (Phase 2, `note_host_split`); an old
|
||||
//! host emits none and the combined term stands. The spec's line-4 counters are per-window too:
|
||||
//! (`design/stats-unification.md`): FPS, receive throughput, and the full stage split — headline
|
||||
//! `end-to-end` = capture→displayed (p50/p95, measured directly from the `OnFrameRendered` render
|
||||
//! timestamps — `note_displayed`) tiled by `host+network` = capture→received, `decode` =
|
||||
//! received→decoded, and `display` = decoded→displayed (stage p50s). When the platform delivers no
|
||||
//! render callbacks (allowed under load; `disp_valid` false), the HUD falls back to the v1
|
||||
//! capture→decoded headline without the `display` term. When the host emits per-AU 0xCF host
|
||||
//! timings, the `host+network` term further splits into `host` + `network` (Phase 2,
|
||||
//! `note_host_split`); an old host emits none and the combined term stands. The spec's line-4 counters are per-window too:
|
||||
//! `lost` / `FEC` are windowed here from the connector's cumulative counters (the caller passes the
|
||||
//! current totals into `set_enabled`/`drain`), `skipped` counts the client's own newest-wins drops
|
||||
//! (`note_skipped`). The decode thread is the sole writer
|
||||
@@ -57,6 +60,13 @@ struct Inner {
|
||||
net_us: Vec<u64>,
|
||||
/// `decode` stage = received→decoded samples, in microseconds (client-local, single clock).
|
||||
decode_us: Vec<u64>,
|
||||
/// `display` stage = decoded→displayed samples, in microseconds (client-local, single clock),
|
||||
/// from the `OnFrameRendered` render timestamps. Empty when the platform delivers no render
|
||||
/// callbacks — the HUD then drops the term and the headline endpoint moves back to `decoded`.
|
||||
display_us: Vec<u64>,
|
||||
/// `end-to-end` = capture→displayed samples, µs (skew-corrected) — the spec's headline,
|
||||
/// measured directly (not summed from stages). Empty under the same fallback as `display_us`.
|
||||
e2e_disp_us: Vec<u64>,
|
||||
/// Client-side newest-wins/pacing drops this window (decoded frames released without
|
||||
/// rendering, or parked AUs dropped on overflow) — the spec's `skipped` counter.
|
||||
skipped: u64,
|
||||
@@ -75,12 +85,22 @@ struct Inner {
|
||||
pub struct Snapshot {
|
||||
pub fps: f64,
|
||||
pub mbps: f64,
|
||||
/// Headline `end-to-end` (capture→decoded) percentiles, ms.
|
||||
/// Headline `end-to-end` (capture→decoded) percentiles, ms — the fallback headline when no
|
||||
/// render callback landed this window (`disp_valid` false).
|
||||
pub e2e_p50_ms: f64,
|
||||
pub e2e_p95_ms: f64,
|
||||
/// Stage p50s (ms): `host+network` (capture→received) and `decode` (received→decoded).
|
||||
/// The full headline: `end-to-end` = capture→displayed percentiles, ms, measured directly from
|
||||
/// the render timestamps. Meaningful only when `disp_valid`.
|
||||
pub e2e_disp_p50_ms: f64,
|
||||
pub e2e_disp_p95_ms: f64,
|
||||
/// Stage p50s (ms): `host+network` (capture→received), `decode` (received→decoded), and
|
||||
/// `display` (decoded→displayed; 0.0 when `disp_valid` is false — the HUD drops the term).
|
||||
pub hostnet_p50_ms: f64,
|
||||
pub decode_p50_ms: f64,
|
||||
pub display_p50_ms: f64,
|
||||
/// Whether any capture→displayed sample landed this window — gates the HUD's headline endpoint
|
||||
/// (`capture→displayed` vs the capture→decoded fallback) and the equation's `display` term.
|
||||
pub disp_valid: bool,
|
||||
/// Phase-2 `host` / `network` split p50s (ms) — 0.0 when no 0xCF timing matched this window
|
||||
/// (old host / no samples yet), in which case the HUD keeps the combined `host+network` term.
|
||||
pub host_p50_ms: f64,
|
||||
@@ -122,6 +142,8 @@ impl VideoStats {
|
||||
host_us: Vec::with_capacity(256),
|
||||
net_us: Vec::with_capacity(256),
|
||||
decode_us: Vec::with_capacity(256),
|
||||
display_us: Vec::with_capacity(256),
|
||||
e2e_disp_us: Vec::with_capacity(256),
|
||||
skipped: 0,
|
||||
last_dropped_total: 0,
|
||||
last_fec_total: 0,
|
||||
@@ -157,6 +179,8 @@ impl VideoStats {
|
||||
g.host_us.clear();
|
||||
g.net_us.clear();
|
||||
g.decode_us.clear();
|
||||
g.display_us.clear();
|
||||
g.e2e_disp_us.clear();
|
||||
g.skipped = 0;
|
||||
g.last_dropped_total = dropped_total;
|
||||
g.last_fec_total = fec_total;
|
||||
@@ -273,6 +297,30 @@ impl VideoStats {
|
||||
}
|
||||
}
|
||||
|
||||
/// Record one displayed frame (the `OnFrameRendered` render timestamp, re-based to the
|
||||
/// realtime clock): its capture→displayed `end-to-end` sample and its decoded→displayed
|
||||
/// `display` stage sample (either may be absent — the e2e clamp rejected an out-of-range
|
||||
/// value, or the decoded stamp for this pts was already evicted/pre-HUD). Fired from the
|
||||
/// codec's render-callback thread, not the decode thread — the lock makes that safe.
|
||||
// Driven only by the android-only decode path; unreferenced on the host build — expected.
|
||||
#[cfg_attr(not(target_os = "android"), allow(dead_code))]
|
||||
pub fn note_displayed(&self, e2e_us: Option<u64>, display_us: Option<u64>) {
|
||||
if !self.enabled.load(Ordering::Relaxed) {
|
||||
return; // HUD hidden — skip the lock (the callback already skipped the clock reads)
|
||||
}
|
||||
// Poison-proof for the same reason as `note_received`.
|
||||
let mut g = self
|
||||
.inner
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(l) = e2e_us {
|
||||
g.e2e_disp_us.push(l);
|
||||
}
|
||||
if let Some(l) = display_us {
|
||||
g.display_us.push(l);
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the window's rates + latency percentiles, then reset for the next window.
|
||||
/// `dropped_total` / `fec_total` are the connector's session-cumulative counters now; the
|
||||
/// snapshot's `lost` / `fec` are their deltas since the last drain (or the enabling show).
|
||||
@@ -291,13 +339,19 @@ impl VideoStats {
|
||||
g.host_us.sort_unstable();
|
||||
g.net_us.sort_unstable();
|
||||
g.decode_us.sort_unstable();
|
||||
g.display_us.sort_unstable();
|
||||
g.e2e_disp_us.sort_unstable();
|
||||
let snap = Snapshot {
|
||||
fps,
|
||||
mbps,
|
||||
e2e_p50_ms: pctl_ms(&g.e2e_us, 0.50),
|
||||
e2e_p95_ms: pctl_ms(&g.e2e_us, 0.95),
|
||||
e2e_disp_p50_ms: pctl_ms(&g.e2e_disp_us, 0.50),
|
||||
e2e_disp_p95_ms: pctl_ms(&g.e2e_disp_us, 0.95),
|
||||
hostnet_p50_ms: pctl_ms(&g.hostnet_us, 0.50),
|
||||
decode_p50_ms: pctl_ms(&g.decode_us, 0.50),
|
||||
display_p50_ms: pctl_ms(&g.display_us, 0.50),
|
||||
disp_valid: !g.e2e_disp_us.is_empty(),
|
||||
host_p50_ms: pctl_ms(&g.host_us, 0.50),
|
||||
net_p50_ms: pctl_ms(&g.net_us, 0.50),
|
||||
lat_valid: !g.e2e_us.is_empty(),
|
||||
@@ -315,6 +369,8 @@ impl VideoStats {
|
||||
g.host_us.clear();
|
||||
g.net_us.clear();
|
||||
g.decode_us.clear();
|
||||
g.display_us.clear();
|
||||
g.e2e_disp_us.clear();
|
||||
g.skipped = 0;
|
||||
g.last_dropped_total = dropped_total;
|
||||
g.last_fec_total = fec_total;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
# Unreleased
|
||||
|
||||
# 0.9.0 (2024-04-26)
|
||||
|
||||
- Move `MediaFormat` from `media::media_codec` to its own `media::media_format` module. (#442)
|
||||
- media_format: Expose `MediaFormat::copy()` and `MediaFormat::clear()` from API level 29. (#449)
|
||||
- **Breaking:** media_format: Mark all `fn set_*()` and `fn str()` as taking `self` by `&mut`. (#452)
|
||||
- **Breaking:** Require all `dyn Fn*` types to implement `Send` when the FFI implementation invokes them on a separate thread: (#455)
|
||||
- `audio::AudioStreamDataCallback`;
|
||||
- `audio::AudioStreamErrorCallback`;
|
||||
- `media::image_reader::BufferRemovedListener`;
|
||||
- `media::image_reader::ImageListener`;
|
||||
- `media::media_codec::ErrorCallback`;
|
||||
- `media::media_codec::FormatChangedCallback`;
|
||||
- `media::media_codec::InputAvailableCallback`;
|
||||
- `media::media_codec::OutputAvailableCallback`.
|
||||
- Drop previous `Box`ed callbacks _after_ registering new ones, instead of before. (#455)
|
||||
- input_queue: Add `from_java()` constructor, available since API level 33. (#456)
|
||||
- event: Add `from_java()` constructors to `KeyEvent` and `MotionEvent`, available since API level 31. (#456)
|
||||
- **Breaking:** image_reader: Special-case return statuses in `Image`-acquire functions. (#457)
|
||||
- **Breaking:** image_reader: Mark `ImageReader::acquire_latest_image_async()` `unsafe` to match the safety requirements on `ImageReader::acquire_next_image_async()`. (#457)
|
||||
- event: Implement `SourceClass` `bitflag` and provide `Source::class()` getter. (#458)
|
||||
- Ensure all `bitflags` implementations consider all (including unknown) bits in negation and `all()`. (#458)
|
||||
- **Breaking:** Mark all enums as `non_exhaustive` and fix `repr` types. (#459)
|
||||
- **Breaking:** native_window: Remove redundant `TRANSFORM_` prefix from `NativeWindowTransform` variants. (#460)
|
||||
- **Breaking:** hardware_buffer: Convert `HardwareBufferUsage` to `bitflags`. (#461)
|
||||
- bitmap: Guard `BitmapCompressError` behind missing `api-level-30` feature. (#462)
|
||||
- native_window: Require linking against `libnativewindow` for most API >= 26 functions. (#465)
|
||||
- **Breaking:** audio: Merge `AudioResult` variant enum into `AudioError`. (#467)
|
||||
- data_space: Add missing `DataSpaceRange::Unspecified` variant. (#468)
|
||||
- **Breaking:** looper: Require `Send` marker when adding fd event callbacks on `ForeignLooper`. (#469)
|
||||
- **Breaking:** Upgrade to [`ndk-sys 0.6.0`](../ndk-sys/CHANGELOG.md#060-2024-04-26). (#472)
|
||||
|
||||
# 0.8.0 (2023-10-15)
|
||||
|
||||
- event: Add `tool_type` getter for `Pointer`. (#323)
|
||||
- input_queue: Allow any non-zero return code from `pre_dispatch()` again, as per documentation. (#325)
|
||||
- asset: Use entire asset length when mapping buffer. (#387)
|
||||
- Bump MSRV to 1.66 for `raw-window-handle 0.5.1`, `num_enum`'s `catch_all` with arbitrary enum discriminants. (#388, #431)
|
||||
- Bump optional `jni` dependency for doctest example from `0.19` to `0.21`. (#390)
|
||||
- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#370)
|
||||
- **Breaking:** Upgrade `bitflags` crate from `1` to `2`. (#394)
|
||||
- bitmap: Add `try_format()` to `AndroidBitmapInfo` to handle unexpected formats without panicking. (#395)
|
||||
- Add `Font` bindings. (#397)
|
||||
- **Breaking:** Upgrade `num_enum` crate from `0.5.1` to `0.7`. (#398, #419)
|
||||
- **Breaking:** Renamed, moved and flattened "`media`" error types and helpers to a new `media_error` module. (#399, #432)
|
||||
- **Breaking:** media_codec: Wrap common dequeued-buffer status codes in enum. (#401)
|
||||
- **Breaking:** media_codec: Return `MaybeUninit` bytes in `buffer_mut()`. (#403)
|
||||
- native_window: Add `lock()` to blit raw pixel data. (#404)
|
||||
- hardware_buffer_format: Add `YCbCr_P010` and `R8_UNORM` variants. (#405)
|
||||
- **Breaking:** hardware_buffer_format: Add catch-all variant. (#407)
|
||||
- asset: Add missing `is_allocated()` and `open_file_descriptor()` methods. (#409)
|
||||
- **Breaking:** media_codec: Add support for asynchronous notification callbacks. (#410)
|
||||
- Add panic guards to callbacks. (#412)
|
||||
- looper: Add `remove_fd()` to unregister events/callbacks for a file descriptor. (#416)
|
||||
- **Breaking:** Use `BorrowedFd` and `OwnedFd` to clarify possible ownership transitions. (#417)
|
||||
- **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#420)
|
||||
- Add bindings for `sync.h`. (#423)
|
||||
- **Breaking:** bitmap: Provide detailed implementation for `AndroidBitmapInfoFlags`. (#424)
|
||||
- native_window: Add `set_buffers_transform()`, `try_allocate_buffers()` and `set_frame_rate*()`. (#425)
|
||||
- Add bindings for `ASharedMemory`. (#427)
|
||||
- hardware_buffer: Add `id()` to retrieve a system-wide unique identifier for a `HardwareBuffer`. (#428)
|
||||
- **Breaking:** bitmap: Strip `Android` prefix from structs and enums, and `Bitmap` from `Result`. (#430)
|
||||
- **Breaking:** `raw-window-handle 0.5` support is now behind an _optional_ `rwh_05` crate feature and `raw-window-handle` `0.4` and `0.6` support is provided via the new `rwh_04` and (default-enabled) `rwh_06` crate features. (#434)
|
||||
- **Breaking:** looper: Provide `event` value to file descriptor poll callback. (#435)
|
||||
- **Breaking:** `HardwareBufferFormat` is no longer exported from `hardware_buffer` and `native_window`, and can only be reached through the `hardware_buffer_format` module. (#436)
|
||||
- **Breaking:** `get_` prefixes have been removed from all public functions in light of the [C-GETTER](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter) convention. (#437)
|
||||
- Add `DataSpace` type and relevant functions on `Bitmap` and `NativeWindow`. (#438)
|
||||
- bitmap: Add `Bitmap::compress()` and `Bitmap::compress_raw()` functions. (#440)
|
||||
- **Breaking:** Turn `BitmapError` into a `non_exhaustive` `enum`. (#440)
|
||||
- **Breaking:** audio: Rename `AudioErrorResult` to `AudioResult` and turn into a `non_exhaustive` `enum`. (#441)
|
||||
|
||||
# 0.7.0 (2022-07-24)
|
||||
|
||||
- hardware_buffer: Make `HardwareBuffer::as_ptr()` public for interop with Vulkan. (#213)
|
||||
- **Breaking:** `Configuration::country()` now returns `None` when the country is unset (akin to `Configuration::language()`). (#220)
|
||||
- Add `MediaCodec` and `MediaFormat` bindings. (#216)
|
||||
- **Breaking:** Upgrade to [`ndk-sys 0.4.0`](../ndk-sys/CHANGELOG.md#040-2022-07-24) and use new `enum` newtype wrappers. (#245)
|
||||
- native_window: Use `release`/`acquire` for `Drop` and `Clone` respectively. (#207)
|
||||
- **Breaking:** audio: Rename from `aaudio` to `audio` and drop `A` prefix. (#273)
|
||||
- Implement `HasRawWindowHandle` directly on `NativeWindow`. (#274, #319)
|
||||
- **Breaking:** native_activity: Replace `CStr` return types with `Path`. (#279)
|
||||
- native_window: Add `format()` getter and `set_buffers_geometry()` setter. (#276)
|
||||
- native_activity: Add `set_window_format()` setter. (#277)
|
||||
- native_activity: Add `set_window_flags()` to change window behavior. (#278)
|
||||
- Add `SurfaceTexture` bindings. (#267)
|
||||
- Improve library and structure documentation, linking back to the NDK docs more rigorously. (#290)
|
||||
- **Breaking:** input_queue: `get_event()` now returns a `Result` with `std::io::Error`; `InputQueueError` has been removed. (#292)
|
||||
- **Breaking:** input_queue: `has_events()` now returns a `bool` directly without being wrapped in `Result`. (#294)
|
||||
- **Breaking:** hardware_buffer: `HardwareBufferError` has been removed and replaced with `std::io::Error` in return types. (#295)
|
||||
- Fixed `HardwareBuffer` leak on buffers returned from `AndroidBitmap::get_hardware_buffer()`. (#296)
|
||||
- Bump optional `jni` dependency for doctest example from `0.18` to `0.19`. (#300)
|
||||
- hardware_buffer: Made `HardwareBufferDesc` fields `pub`. (#313)
|
||||
- **Breaking:** Remove `hardware_buffer` and `trace` features in favour of using `api-level-26` or `api-level-23` directly. (#320)
|
||||
|
||||
# 0.6.0 (2022-01-05)
|
||||
|
||||
- **Breaking:** Upgrade to [`ndk-sys 0.3.0`](../ndk-sys/CHANGELOG.md#030-2022-01-05) and migrate to `jni-sys` types that it now directly uses in its bindings. (#209 / #214)
|
||||
|
||||
# 0.5.0 (2021-11-22)
|
||||
|
||||
- **Breaking:** Replace `add_fd_with_callback` `ident` with constant value `ALOOPER_POLL_CALLBACK`,
|
||||
as per <https://developer.android.com/ndk/reference/group/looper#alooper_addfd>.
|
||||
- **Breaking:** Accept unboxed closure in `add_fd_with_callback`.
|
||||
- aaudio: Replace "Added in" comments with missing `#[cfg(feature)]`.
|
||||
- aaudio: Add missing `fn get_allowed_capture_policy()`.
|
||||
- configuration: Add missing `api-level-30` feature to `fn screen_round()`.
|
||||
|
||||
# 0.4.0 (2021-08-02)
|
||||
|
||||
- **Breaking:** Model looper file descriptor events integer as `bitflags`.
|
||||
|
||||
# 0.3.0 (2021-01-30)
|
||||
|
||||
- **Breaking:** Looper `ident` not passed in `data` pointer anymore.
|
||||
`attach_looper` now only sets the `ident` field when attaching an
|
||||
`InputQueue` to a `ForeignLooper`.
|
||||
If you are relying on `Poll::Event::data` to tell event fd and
|
||||
input queue apart, please use `Poll::Event::ident` and the new
|
||||
constants introduced in `ndk-glue`!
|
||||
|
||||
# 0.2.1 (2020-10-15)
|
||||
|
||||
- Fix documentation build on docs.rs
|
||||
|
||||
# 0.2.0 (2020-09-15)
|
||||
|
||||
- **Breaking:** Updated to use [ndk-sys 0.2.0](../ndk-sys/CHANGELOG.md#020-2020-09-15)
|
||||
- Added `media` bindings
|
||||
- Added `bitmap` and `hardware_buffer` bindings
|
||||
- Added `aaudio` bindings
|
||||
- Fixed assets directory path to be relative to the manifest
|
||||
- Added `trace` feature for native tracing
|
||||
|
||||
# 0.1.0 (2020-04-22)
|
||||
|
||||
- Initial release! 🎉
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
authors = ["The Rust Mobile contributors"]
|
||||
description = "Safe Rust bindings to the Android NDK"
|
||||
homepage = "https://github.com/rust-mobile/ndk"
|
||||
documentation = "https://docs.rs/ndk"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"android",
|
||||
"ndk",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rust-mobile/ndk"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = [
|
||||
"jni",
|
||||
"all",
|
||||
]
|
||||
rustdoc-args = [
|
||||
"--cfg",
|
||||
"docsrs",
|
||||
]
|
||||
targets = [
|
||||
"aarch64-linux-android",
|
||||
"armv7-linux-androideabi",
|
||||
"i686-linux-android",
|
||||
"x86_64-linux-android",
|
||||
]
|
||||
|
||||
[dependencies.bitflags]
|
||||
version = "2.4"
|
||||
|
||||
[dependencies.ffi]
|
||||
version = "0.6.0"
|
||||
package = "ndk-sys"
|
||||
|
||||
[dependencies.jni]
|
||||
version = "0.21"
|
||||
optional = true
|
||||
|
||||
[dependencies.jni-sys]
|
||||
version = "0.3"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.6"
|
||||
|
||||
[dependencies.num_enum]
|
||||
version = "0.7"
|
||||
|
||||
[dependencies.rwh_04]
|
||||
version = "0.4"
|
||||
optional = true
|
||||
package = "raw-window-handle"
|
||||
|
||||
[dependencies.rwh_05]
|
||||
version = "0.5"
|
||||
optional = true
|
||||
package = "raw-window-handle"
|
||||
|
||||
[dependencies.rwh_06]
|
||||
version = "0.6"
|
||||
optional = true
|
||||
package = "raw-window-handle"
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1.0.23"
|
||||
|
||||
[dev-dependencies.libc]
|
||||
version = "0.2.3"
|
||||
|
||||
[features]
|
||||
all = [
|
||||
"audio",
|
||||
"bitmap",
|
||||
"media",
|
||||
"nativewindow",
|
||||
"sync",
|
||||
"api-level-33",
|
||||
"rwh_04",
|
||||
"rwh_05",
|
||||
"rwh_06",
|
||||
]
|
||||
api-level-23 = []
|
||||
api-level-24 = ["api-level-23"]
|
||||
api-level-25 = ["api-level-24"]
|
||||
api-level-26 = ["api-level-25"]
|
||||
api-level-27 = ["api-level-26"]
|
||||
api-level-28 = ["api-level-27"]
|
||||
api-level-29 = ["api-level-28"]
|
||||
api-level-30 = ["api-level-29"]
|
||||
api-level-31 = ["api-level-30"]
|
||||
api-level-32 = ["api-level-31"]
|
||||
api-level-33 = ["api-level-32"]
|
||||
audio = [
|
||||
"ffi/audio",
|
||||
"api-level-26",
|
||||
]
|
||||
bitmap = ["ffi/bitmap"]
|
||||
default = ["rwh_06"]
|
||||
media = ["ffi/media"]
|
||||
nativewindow = ["ffi/nativewindow"]
|
||||
sync = [
|
||||
"ffi/sync",
|
||||
"api-level-26",
|
||||
]
|
||||
test = [
|
||||
"ffi/test",
|
||||
"jni",
|
||||
"all",
|
||||
]
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
[](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml)  
|
||||
|
||||
Rust bindings to the [Android NDK](https://developer.android.com/ndk)
|
||||
|
||||
Name | Description | Badges
|
||||
--- | --- | ---
|
||||
[`ndk-sys`](./ndk-sys) | Raw FFI bindings to the NDK | [](https://crates.io/crates/ndk-sys) [](https://docs.rs/ndk-sys) [](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html)
|
||||
[`ndk`](./ndk) | Safe abstraction of the bindings | [](https://crates.io/crates/ndk) [](https://docs.rs/ndk) [](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html)
|
||||
|
||||
See these [`ndk-examples`](https://github.com/rust-mobile/cargo-apk/tree/main/examples/examples) and these [`rust-android-examples`](https://github.com/rust-mobile/rust-android-examples) for examples using the NDK.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This repository was recently [modularized](https://github.com/rust-mobile/ndk/issues/372) and the following crates were split into separate repositories:
|
||||
>
|
||||
> Crate | New Location | Notes
|
||||
> ------|--------------|------
|
||||
> ndk-context | https://github.com/rust-mobile/ndk-context |
|
||||
> ndk-glue | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity)
|
||||
> ndk-macro | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity)
|
||||
> ndk-build | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild)
|
||||
> cargo-apk | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Vendored `ndk` 0.9.0 (patched)
|
||||
|
||||
Verbatim copy of the published `ndk` 0.9.0 crate (https://crates.io/crates/ndk,
|
||||
MIT OR Apache-2.0, © the rust-mobile contributors), wired in via `[patch.crates-io]`
|
||||
in the workspace root.
|
||||
|
||||
**The only change** is in `src/media/media_codec.rs`: `MediaCodec::as_ptr` is made
|
||||
`pub` (upstream keeps it private) so the Android client can register
|
||||
`AMediaCodec_setOnFrameRenderedCallback` through `ndk-sys` — the render-timestamp
|
||||
callback behind the HUD's `display` stage (`design/stats-unification.md`), which the
|
||||
wrapper doesn't expose. Grep for `punktfunk vendored patch` to find it.
|
||||
|
||||
Drop this vendor copy when upstream exposes the raw pointer or a frame-rendered
|
||||
callback binding (tracked against https://github.com/rust-mobile/ndk).
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
//! Bindings for [`AAsset`], [`AAssetDir`] and [`AAssetManager`]
|
||||
//!
|
||||
//! [`AAsset`]: https://developer.android.com/ndk/reference/group/asset#aasset
|
||||
//! [`AAssetDir`]: https://developer.android.com/ndk/reference/group/asset#aassetdir
|
||||
//! [`AAssetManager`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
io,
|
||||
os::fd::{FromRawFd, OwnedFd},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
/// A native [`AAssetManager *`]
|
||||
///
|
||||
/// [`AAssetManager *`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "AAssetManager")]
|
||||
pub struct AssetManager {
|
||||
ptr: NonNull<ffi::AAssetManager>,
|
||||
}
|
||||
|
||||
// AAssetManager is thread safe.
|
||||
// See https://developer.android.com/ndk/reference/group/asset#aassetmanager
|
||||
unsafe impl Send for AssetManager {}
|
||||
unsafe impl Sync for AssetManager {}
|
||||
|
||||
impl AssetManager {
|
||||
/// Create an `AssetManager` from a pointer
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that the pointer is a valid pointer to a native
|
||||
/// `AAssetManager`.
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetManager>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns the pointer to the native `AAssetManager`.
|
||||
pub fn ptr(&self) -> NonNull<ffi::AAssetManager> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Open the asset. Returns [`None`] if opening the asset fails.
|
||||
///
|
||||
/// This currently always opens the asset in the streaming mode.
|
||||
#[doc(alias = "AAssetManager_open")]
|
||||
pub fn open(&self, filename: &CStr) -> Option<Asset> {
|
||||
unsafe {
|
||||
let ptr = ffi::AAssetManager_open(
|
||||
self.ptr.as_ptr(),
|
||||
filename.as_ptr(),
|
||||
ffi::AASSET_MODE_STREAMING as i32,
|
||||
);
|
||||
Some(Asset::from_ptr(NonNull::new(ptr)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Open an asset directory. Returns [`None`] if opening the directory fails.
|
||||
#[doc(alias = "AAssetManager_openDir")]
|
||||
pub fn open_dir(&self, filename: &CStr) -> Option<AssetDir> {
|
||||
unsafe {
|
||||
let ptr = ffi::AAssetManager_openDir(self.ptr.as_ptr(), filename.as_ptr());
|
||||
Some(AssetDir::from_ptr(NonNull::new(ptr)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AAssetDir *`]
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CString;
|
||||
/// # use ndk::asset::AssetManager;
|
||||
/// # let asset_manager: AssetManager = unimplemented!();
|
||||
/// use std::io::Read;
|
||||
///
|
||||
/// let mut my_dir = asset_manager
|
||||
/// .open_dir(&CString::new("my_dir").unwrap())
|
||||
/// .expect("Could not open directory");
|
||||
///
|
||||
/// // Use it as an iterator
|
||||
/// let all_files = my_dir.collect::<Vec<CString>>();
|
||||
///
|
||||
/// // Reset the iterator
|
||||
/// my_dir.rewind();
|
||||
///
|
||||
/// // Use .with_next() to iterate without allocating `CString`s
|
||||
/// while let Some(asset) = my_dir.with_next(|cstr| asset_manager.open(cstr).unwrap()) {
|
||||
/// let mut text = String::new();
|
||||
/// asset.read_to_string(&mut text);
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`AAssetDir *`]: https://developer.android.com/ndk/reference/group/asset#aassetdir
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "AAssetDir")]
|
||||
pub struct AssetDir {
|
||||
ptr: NonNull<ffi::AAssetDir>,
|
||||
}
|
||||
|
||||
// It's unclear if AAssetDir is thread safe.
|
||||
// However, AAsset is not, so there's a good chance that AAssetDir is not either.
|
||||
|
||||
impl Drop for AssetDir {
|
||||
#[doc(alias = "AAssetDir_close")]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AAssetDir_close(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetDir {
|
||||
/// Construct an `AssetDir` from the native `AAssetDir *`. This gives ownership of the
|
||||
/// `AAssetDir *` to the `AssetDir`, which will handle closing the asset. Avoid using
|
||||
/// the pointer after calling this function.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it points to a valid native `AAssetDir`.
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AAssetDir>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// The corresponding native `AAssetDir *`
|
||||
pub fn ptr(&self) -> NonNull<ffi::AAssetDir> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Get the next filename, if any, and process it. Like [`Iterator::next()`], but performs
|
||||
/// no additional allocation.
|
||||
///
|
||||
/// The filenames are in the correct format to be passed to [`AssetManager::open()`].
|
||||
#[doc(alias = "AAssetDir_getNextFileName")]
|
||||
pub fn with_next<T>(&mut self, f: impl for<'a> FnOnce(&'a CStr) -> T) -> Option<T> {
|
||||
unsafe {
|
||||
let next_name = ffi::AAssetDir_getNextFileName(self.ptr.as_ptr());
|
||||
if next_name.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(f(CStr::from_ptr(next_name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the iteration state
|
||||
#[doc(alias = "AAssetDir_rewind")]
|
||||
pub fn rewind(&mut self) {
|
||||
unsafe {
|
||||
ffi::AAssetDir_rewind(self.ptr.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for AssetDir {
|
||||
type Item = CString;
|
||||
|
||||
fn next(&mut self) -> Option<CString> {
|
||||
self.with_next(|cstr| cstr.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AAsset *`], opened in streaming mode
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use std::ffi::CString;
|
||||
/// # use ndk::asset::AssetManager;
|
||||
/// # let asset_manager: AssetManager = unimplemented!();
|
||||
/// use std::io::Read;
|
||||
///
|
||||
/// let asset = asset_manager
|
||||
/// .open(&CString::new("path/to/asset").unwrap())
|
||||
/// .expect("Could not open asset");
|
||||
///
|
||||
/// let mut data = vec![];
|
||||
/// asset.read_to_end(&mut data);
|
||||
/// // ... use data ...
|
||||
/// ```
|
||||
///
|
||||
/// [`AAsset *`]: https://developer.android.com/ndk/reference/group/asset#aasset
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "AAsset")]
|
||||
pub struct Asset {
|
||||
ptr: NonNull<ffi::AAsset>,
|
||||
}
|
||||
|
||||
// AAsset is *not* thread safe.
|
||||
// See https://developer.android.com/ndk/reference/group/asset#aasset
|
||||
|
||||
impl Drop for Asset {
|
||||
#[doc(alias = "AAsset_close")]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AAsset_close(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Asset {
|
||||
/// Construct an `Asset` from the native `AAsset *`. This gives ownership of the `AAsset *` to
|
||||
/// the `Asset`, which will handle closing the asset. Avoid using the pointer after calling
|
||||
/// this function.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it points to a valid native `AAsset`, open
|
||||
/// in the streaming mode.
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AAsset>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// The corresponding native `AAsset *`
|
||||
pub fn ptr(&self) -> NonNull<ffi::AAsset> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns the total length of the asset, in bytes
|
||||
#[doc(alias = "AAsset_getLength64")]
|
||||
pub fn length(&self) -> usize {
|
||||
unsafe { ffi::AAsset_getLength64(self.ptr.as_ptr()) as usize }
|
||||
}
|
||||
|
||||
/// Returns the remaining length of the asset, in bytes
|
||||
#[doc(alias = "AAsset_getRemainingLength64")]
|
||||
pub fn remaining_length(&self) -> usize {
|
||||
unsafe { ffi::AAsset_getRemainingLength64(self.ptr.as_ptr()) as usize }
|
||||
}
|
||||
|
||||
/// Maps all data into a buffer and returns it
|
||||
#[doc(alias = "AAsset_getBuffer")]
|
||||
pub fn buffer(&mut self) -> io::Result<&[u8]> {
|
||||
unsafe {
|
||||
let buf_ptr = ffi::AAsset_getBuffer(self.ptr.as_ptr());
|
||||
if buf_ptr.is_null() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Android Asset error creating buffer",
|
||||
))
|
||||
} else {
|
||||
Ok(std::slice::from_raw_parts(
|
||||
buf_ptr as *const u8,
|
||||
self.length(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this asset's internal buffer is allocated in ordinary RAM (i.e. not `mmap`ped).
|
||||
#[doc(alias = "AAsset_isAllocated")]
|
||||
pub fn is_allocated(&self) -> bool {
|
||||
unsafe { ffi::AAsset_isAllocated(self.ptr.as_ptr()) != 0 }
|
||||
}
|
||||
|
||||
/// Open a new file descriptor that can be used to read the asset data.
|
||||
///
|
||||
/// Returns an error if direct fd access is not possible (for example, if the asset is compressed).
|
||||
#[doc(alias = "AAsset_openFileDescriptor64")]
|
||||
pub fn open_file_descriptor(&self) -> io::Result<OpenedFileDescriptor> {
|
||||
let mut offset = 0;
|
||||
let mut size = 0;
|
||||
let res =
|
||||
unsafe { ffi::AAsset_openFileDescriptor64(self.ptr.as_ptr(), &mut offset, &mut size) };
|
||||
if res >= 0 {
|
||||
Ok(OpenedFileDescriptor {
|
||||
fd: unsafe { OwnedFd::from_raw_fd(res) },
|
||||
offset: offset as usize,
|
||||
size: size as usize,
|
||||
})
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Android Asset openFileDescriptor error",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for Asset {
|
||||
#[doc(alias = "AAsset_read")]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
unsafe {
|
||||
let res = ffi::AAsset_read(self.ptr.as_ptr(), buf.as_mut_ptr() as *mut _, buf.len());
|
||||
if res >= 0 {
|
||||
Ok(res as usize)
|
||||
} else {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Android Asset read error",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for Asset {
|
||||
#[doc(alias = "AAsset_seek64")]
|
||||
fn seek(&mut self, seek: io::SeekFrom) -> io::Result<u64> {
|
||||
unsafe {
|
||||
let res = match seek {
|
||||
io::SeekFrom::Start(x) => {
|
||||
ffi::AAsset_seek64(self.ptr.as_ptr(), x as i64, ffi::SEEK_SET as i32)
|
||||
}
|
||||
io::SeekFrom::Current(x) => {
|
||||
ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_CUR as i32)
|
||||
}
|
||||
io::SeekFrom::End(x) => {
|
||||
ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_END as i32)
|
||||
}
|
||||
};
|
||||
if res < 0 {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Android Asset seek error",
|
||||
))
|
||||
} else {
|
||||
Ok(res as u64)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the opened file descriptor returned by [`Asset::open_file_descriptor()`], together
|
||||
/// with the offset and size of the given asset within that file descriptor.
|
||||
#[derive(Debug)]
|
||||
pub struct OpenedFileDescriptor {
|
||||
pub fd: OwnedFd,
|
||||
pub offset: usize,
|
||||
pub size: usize,
|
||||
}
|
||||
+1419
File diff suppressed because it is too large
Load Diff
+489
@@ -0,0 +1,489 @@
|
||||
//! Bindings for [`AndroidBitmap`] functions
|
||||
//!
|
||||
//! These functions operate directly on a JNI [`android.graphics.Bitmap`] instance.
|
||||
//!
|
||||
//! [`AndroidBitmap`]: https://developer.android.com/ndk/reference/group/bitmap
|
||||
//! [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap
|
||||
#![cfg(feature = "bitmap")]
|
||||
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
use std::{error, fmt, mem::MaybeUninit};
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
use crate::data_space::DataSpace;
|
||||
#[cfg(feature = "api-level-30")]
|
||||
use crate::hardware_buffer::HardwareBufferRef;
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
pub enum BitmapError {
|
||||
#[doc(alias = "ANDROID_BITMAP_RESULT_ALLOCATION_FAILED")]
|
||||
AllocationFailed = ffi::ANDROID_BITMAP_RESULT_ALLOCATION_FAILED,
|
||||
#[doc(alias = "ANDROID_BITMAP_RESULT_BAD_PARAMETER")]
|
||||
BadParameter = ffi::ANDROID_BITMAP_RESULT_BAD_PARAMETER,
|
||||
#[doc(alias = "ANDROID_BITMAP_RESULT_JNI_EXCEPTION")]
|
||||
JniException = ffi::ANDROID_BITMAP_RESULT_JNI_EXCEPTION,
|
||||
|
||||
// Use the SUCCESS discriminant, as no-one will be able to call `as i32` and only has access to
|
||||
// the constants via `From` provided by `IntoPrimitive` which reads the contained value.
|
||||
// An autogenerated `<previous variant> + 1` discriminant is normally fine, except that the
|
||||
// previous variant is negative and `+ 1` would match the variant before that.
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32) = ffi::ANDROID_BITMAP_RESULT_SUCCESS,
|
||||
}
|
||||
|
||||
impl fmt::Display for BitmapError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for BitmapError {}
|
||||
|
||||
pub type Result<T, E = BitmapError> = std::result::Result<T, E>;
|
||||
|
||||
impl BitmapError {
|
||||
pub(crate) fn from_status(status: i32) -> Result<()> {
|
||||
match status {
|
||||
ffi::ANDROID_BITMAP_RESULT_SUCCESS => Ok(()),
|
||||
x => Err(Self::from(x)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn construct<T>(with_ptr: impl FnOnce(*mut T) -> i32) -> Result<T> {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let status = with_ptr(result.as_mut_ptr());
|
||||
BitmapError::from_status(status).map(|()| unsafe { result.assume_init() })
|
||||
}
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, FromPrimitive)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[doc(alias = "AndroidBitmapFormat")]
|
||||
#[non_exhaustive]
|
||||
pub enum BitmapFormat {
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_NONE")]
|
||||
NONE = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_NONE.0 as i32,
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_8888")]
|
||||
RGBA_8888 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_8888.0 as i32,
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_RGB_565")]
|
||||
RGB_565 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGB_565.0 as i32,
|
||||
#[deprecated = "Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead."]
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_4444")]
|
||||
RGBA_4444 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_4444.0 as i32,
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_A_8")]
|
||||
A_8 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_A_8.0 as i32,
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_F16")]
|
||||
RGBA_F16 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_F16.0 as i32,
|
||||
#[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_1010102")]
|
||||
RGBA_1010102 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_1010102.0 as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
/// An immediate wrapper over [`android.graphics.Bitmap`]
|
||||
///
|
||||
/// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap
|
||||
#[derive(Debug)]
|
||||
pub struct Bitmap {
|
||||
env: *mut JNIEnv,
|
||||
inner: jobject,
|
||||
}
|
||||
|
||||
impl Bitmap {
|
||||
/// Create a [`Bitmap`] wrapper from JNI pointers
|
||||
///
|
||||
/// # Safety
|
||||
/// This function should be called with a healthy JVM pointer and with a non-null
|
||||
/// [`android.graphics.Bitmap`], which must be kept alive on the Java/Kotlin side.
|
||||
///
|
||||
/// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap
|
||||
pub unsafe fn from_jni(env: *mut JNIEnv, bitmap: jobject) -> Self {
|
||||
Self { env, inner: bitmap }
|
||||
}
|
||||
|
||||
/// Fills out and returns the [`BitmapInfo`] struct for the given Java bitmap object.
|
||||
#[doc(alias = "AndroidBitmap_getInfo")]
|
||||
pub fn info(&self) -> Result<BitmapInfo> {
|
||||
let inner =
|
||||
construct(|res| unsafe { ffi::AndroidBitmap_getInfo(self.env, self.inner, res) })?;
|
||||
|
||||
Ok(BitmapInfo { inner })
|
||||
}
|
||||
|
||||
/// Returns the [`DataSpace`] of this [`Bitmap`].
|
||||
///
|
||||
/// Note that [`DataSpace`] only exposes a few values. This may return [`DataSpace::Unknown`],
|
||||
/// even for Named ColorSpaces, if they have no corresponding [`DataSpace`].
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[doc(alias = "AndroidBitmap_getDataSpace")]
|
||||
pub fn data_space(&self) -> DataSpace {
|
||||
let value = unsafe { ffi::AndroidBitmap_getDataSpace(self.env, self.inner) };
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// Attempt to lock the pixel address.
|
||||
///
|
||||
/// Locking will ensure that the memory for the pixels will not move until the
|
||||
/// [`Bitmap::unlock_pixels()`] call, and ensure that, if the pixels had been previously purged,
|
||||
/// they will have been restored.
|
||||
///
|
||||
/// If this call succeeds, it must be balanced by a call to [`Bitmap::unlock_pixels()`], after
|
||||
/// which time the address of the pixels should no longer be used.
|
||||
#[doc(alias = "AndroidBitmap_lockPixels")]
|
||||
pub fn lock_pixels(&self) -> Result<*mut std::os::raw::c_void> {
|
||||
construct(|res| unsafe { ffi::AndroidBitmap_lockPixels(self.env, self.inner, res) })
|
||||
}
|
||||
|
||||
/// Call this to balance a successful call to [`Bitmap::lock_pixels()`].
|
||||
#[doc(alias = "AndroidBitmap_unlockPixels")]
|
||||
pub fn unlock_pixels(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AndroidBitmap_unlockPixels(self.env, self.inner) };
|
||||
BitmapError::from_status(status)
|
||||
}
|
||||
|
||||
/// Retrieve the native object associated with an [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`]
|
||||
/// [`Bitmap`] (requires [`BitmapInfoFlags::is_hardware()`] on [`BitmapInfo::flags()`] to return
|
||||
/// [`true`]).
|
||||
///
|
||||
/// Client must not modify it while a [`Bitmap`] is wrapping it.
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[doc(alias = "AndroidBitmap_getHardwareBuffer")]
|
||||
pub fn hardware_buffer(&self) -> Result<HardwareBufferRef> {
|
||||
unsafe {
|
||||
let result =
|
||||
construct(|res| ffi::AndroidBitmap_getHardwareBuffer(self.env, self.inner, res))?;
|
||||
let non_null = if cfg!(debug_assertions) {
|
||||
std::ptr::NonNull::new(result).expect("result should never be null")
|
||||
} else {
|
||||
std::ptr::NonNull::new_unchecked(result)
|
||||
};
|
||||
Ok(HardwareBufferRef::from_ptr(non_null))
|
||||
}
|
||||
}
|
||||
|
||||
/// [Lock] the pixels in `self` and compress them as described by [`info()`].
|
||||
///
|
||||
/// Unlike [`compress_raw()`] this requires a [`Bitmap`] object (as `self`) backed by a
|
||||
/// [`jobject`].
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `format`: [`BitmapCompressFormat`] to compress to.
|
||||
/// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently
|
||||
/// depending on [`BitmapCompressFormat`].
|
||||
/// - `compress_callback`: Closure that writes the compressed data. Will be called on the
|
||||
/// current thread, each time the compressor has compressed more data that is ready to be
|
||||
/// written. May be called more than once for each call to this method.
|
||||
///
|
||||
/// [Lock]: Self::lock_pixels()
|
||||
/// [`info()`]: Self::info()
|
||||
/// [`compress_raw()`]: Self::compress_raw()
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[doc(alias = "AndroidBitmap_compress")]
|
||||
pub fn compress<F: FnMut(&[u8]) -> Result<(), ()>>(
|
||||
&self,
|
||||
format: BitmapCompressFormat,
|
||||
quality: i32,
|
||||
compress_callback: F,
|
||||
) -> Result<(), BitmapCompressError> {
|
||||
let info = self.info()?;
|
||||
let data_space = self.data_space();
|
||||
let pixels = self.lock_pixels()?;
|
||||
// SAFETY: When lock_pixels() succeeds, assume it returns a valid pointer that stays
|
||||
// valid until we call unlock_pixels().
|
||||
let result = unsafe {
|
||||
Self::compress_raw(
|
||||
&info,
|
||||
data_space,
|
||||
pixels,
|
||||
format,
|
||||
quality,
|
||||
compress_callback,
|
||||
)
|
||||
};
|
||||
self.unlock_pixels()?;
|
||||
result
|
||||
}
|
||||
|
||||
/// Compress `pixels` as described by `info`.
|
||||
///
|
||||
/// Unlike [`compress()`] this takes a raw pointer to pixels and does not need a [`Bitmap`]
|
||||
/// object backed by a [`jobject`].
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `info`: Description of the pixels to compress.
|
||||
/// - `data_space`: [`DataSpace`] describing the color space of the pixels. Should _not_ be
|
||||
/// [`DataSpace::Unknown`] [^1].
|
||||
/// - `pixels`: Pointer to pixels to compress.
|
||||
/// - `format`: [`BitmapCompressFormat`] to compress to.
|
||||
/// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently
|
||||
/// depending on [`BitmapCompressFormat`].
|
||||
/// - `compress_callback`: Closure that writes the compressed data. Will be called on the
|
||||
/// current thread, each time the compressor has compressed more data that is ready to be
|
||||
/// written. May be called more than once for each call to this method.
|
||||
///
|
||||
/// # Safety
|
||||
/// `pixels` must point to a valid buffer that matches the size, stride and format in `info`.
|
||||
///
|
||||
/// [`compress()`]: Self::compress()
|
||||
/// [^1]: <https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/apex/android_bitmap.cpp;l=275-279;drc=7ba5c2fb3d1e35eb37a9cc522b30ba51f49ea491>
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[doc(alias = "AndroidBitmap_compress")]
|
||||
pub unsafe fn compress_raw<F: FnMut(&[u8]) -> Result<(), ()>>(
|
||||
info: &BitmapInfo,
|
||||
data_space: DataSpace,
|
||||
pixels: *const std::ffi::c_void,
|
||||
format: BitmapCompressFormat,
|
||||
quality: i32,
|
||||
compress_callback: F,
|
||||
) -> Result<(), BitmapCompressError> {
|
||||
if data_space == DataSpace::Unknown {
|
||||
return Err(BitmapCompressError::DataSpaceUnknown);
|
||||
}
|
||||
|
||||
use std::{any::Any, ffi::c_void, panic::AssertUnwindSafe};
|
||||
struct CallbackState<F: FnMut(&[u8]) -> Result<(), ()>> {
|
||||
callback: F,
|
||||
panic: Option<Box<dyn Any + Send>>,
|
||||
}
|
||||
let mut cb_state = CallbackState::<F> {
|
||||
callback: compress_callback,
|
||||
panic: None,
|
||||
};
|
||||
|
||||
extern "C" fn compress_cb<F: FnMut(&[u8]) -> Result<(), ()>>(
|
||||
context: *mut c_void,
|
||||
data: *const c_void,
|
||||
size: usize,
|
||||
) -> bool {
|
||||
// SAFETY: This callback will only be called serially on a single thread. Both the
|
||||
// panic state and the FnMut context need to be available mutably.
|
||||
let cb_state = unsafe { context.cast::<CallbackState<F>>().as_mut() }.unwrap();
|
||||
let data = unsafe { std::slice::from_raw_parts(data.cast(), size) };
|
||||
let panic = std::panic::catch_unwind(AssertUnwindSafe(|| (cb_state.callback)(data)));
|
||||
match panic {
|
||||
Ok(r) => r.is_ok(),
|
||||
Err(e) => {
|
||||
cb_state.panic = Some(e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let status = unsafe {
|
||||
ffi::AndroidBitmap_compress(
|
||||
&info.inner,
|
||||
data_space.into(),
|
||||
pixels,
|
||||
format.into(),
|
||||
quality,
|
||||
<*mut _>::cast(&mut cb_state),
|
||||
Some(compress_cb::<F>),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(panic) = cb_state.panic {
|
||||
std::panic::resume_unwind(panic)
|
||||
}
|
||||
|
||||
Ok(BitmapError::from_status(status)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible values for [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] within [`BitmapInfoFlags`]
|
||||
#[repr(u32)]
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[derive(Clone, Copy, Debug, IntoPrimitive, FromPrimitive)]
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")]
|
||||
#[non_exhaustive]
|
||||
pub enum BitmapInfoFlagsAlpha {
|
||||
/// Pixel components are premultiplied by alpha.
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_PREMUL")]
|
||||
Premultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_PREMUL,
|
||||
/// Pixels are opaque.
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE")]
|
||||
Opaque = ffi::ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE,
|
||||
/// Pixel components are independent of alpha.
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL")]
|
||||
Unpremultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// Bitfield containing information about the bitmap.
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct BitmapInfoFlags(u32);
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
impl std::fmt::Debug for BitmapInfoFlags {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"BitmapInfoFlags({:#x}, alpha: {:?}, is_hardware: {})",
|
||||
self.0,
|
||||
self.alpha(),
|
||||
self.is_hardware()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
impl BitmapInfoFlags {
|
||||
/// Returns the alpha value contained in the [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] bit range
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")]
|
||||
pub fn alpha(self) -> BitmapInfoFlagsAlpha {
|
||||
// Note that ffi::ANDROID_BITMAP_FLAGS_ALPHA_SHIFT is 0 and hence irrelevant.
|
||||
(self.0 & ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK).into()
|
||||
}
|
||||
|
||||
/// Returns [`true`] when [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`] is set, meaning this
|
||||
/// [`Bitmap`] uses "HARDWARE Config" and its [`HardwareBufferRef`] can be retrieved via
|
||||
/// [`Bitmap::hardware_buffer()`].
|
||||
#[doc(alias = "ANDROID_BITMAP_FLAGS_IS_HARDWARE")]
|
||||
pub fn is_hardware(self) -> bool {
|
||||
// This constant is defined in a separate anonymous enum which bindgen treats as i32.
|
||||
(self.0 & ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE as u32) != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AndroidBitmapInfo`]
|
||||
///
|
||||
/// [`AndroidBitmapInfo`]: https://developer.android.com/ndk/reference/struct/android-bitmap-info#struct_android_bitmap_info
|
||||
#[derive(Clone, Copy)]
|
||||
#[doc(alias = "AndroidBitmapInfo")]
|
||||
pub struct BitmapInfo {
|
||||
inner: ffi::AndroidBitmapInfo,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BitmapInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut f = f.debug_struct("BitmapInfo");
|
||||
f.field("width", &self.width())
|
||||
.field("height", &self.height())
|
||||
.field("stride", &self.stride())
|
||||
.field("format", &self.format());
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
f.field("flags", &self.flags());
|
||||
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BitmapInfo {
|
||||
pub fn new(width: u32, height: u32, stride: u32, format: BitmapFormat) -> Self {
|
||||
Self {
|
||||
inner: ffi::AndroidBitmapInfo {
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format: format.into(),
|
||||
flags: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
pub fn new_with_flags(
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
format: BitmapFormat,
|
||||
flags: BitmapInfoFlags,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: ffi::AndroidBitmapInfo {
|
||||
flags: flags.0,
|
||||
..Self::new(width, height, stride, format).inner
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The bitmap width in pixels.
|
||||
pub fn width(&self) -> u32 {
|
||||
self.inner.width
|
||||
}
|
||||
|
||||
/// The bitmap height in pixels.
|
||||
pub fn height(&self) -> u32 {
|
||||
self.inner.height
|
||||
}
|
||||
|
||||
/// The number of byte per row.
|
||||
pub fn stride(&self) -> u32 {
|
||||
self.inner.stride
|
||||
}
|
||||
|
||||
/// Convert the internal, native [`ffi::AndroidBitmapInfo::format`] into a [`BitmapFormat`].
|
||||
pub fn format(&self) -> BitmapFormat {
|
||||
self.inner.format.into()
|
||||
}
|
||||
|
||||
/// Bitfield containing information about the bitmap.
|
||||
#[cfg(feature = "api-level-30")]
|
||||
pub fn flags(&self) -> BitmapInfoFlags {
|
||||
BitmapInfoFlags(self.inner.flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the formats that can be compressed to with [`Bitmap::compress()`] and
|
||||
/// [`Bitmap::compress_raw()`].
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "AndroidBitmapCompressFormat")]
|
||||
#[non_exhaustive]
|
||||
pub enum BitmapCompressFormat {
|
||||
/// Compress to the JPEG format.
|
||||
///
|
||||
/// quality of `0` means compress for the smallest size. `100` means compress for max visual
|
||||
/// quality.
|
||||
#[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_JPEG")]
|
||||
Jpeg = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_JPEG.0 as i32,
|
||||
/// Compress to the PNG format.
|
||||
///
|
||||
/// PNG is lossless, so quality is ignored.
|
||||
#[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_PNG")]
|
||||
Png = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_PNG.0 as i32,
|
||||
/// Compress to the WEBP lossless format.
|
||||
///
|
||||
/// quality refers to how much effort to put into compression. A value of `0` means to
|
||||
/// compress quickly, resulting in a relatively large file size. `100` means to spend more time
|
||||
/// compressing, resulting in a smaller file.
|
||||
#[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY")]
|
||||
WebPLossy =
|
||||
ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY.0 as i32,
|
||||
/// Compress to the WEBP lossy format.
|
||||
///
|
||||
/// quality of `0` means compress for the smallest size. `100` means compress for max visual quality.
|
||||
#[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS")]
|
||||
WebPLossless =
|
||||
ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS.0 as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
/// Encapsulates possible errors returned by [`Bitmap::compress()`] or [`Bitmap::compress_raw()`].
|
||||
#[cfg(feature = "api-level-30")]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BitmapCompressError {
|
||||
#[error(transparent)]
|
||||
BitmapError(#[from] BitmapError),
|
||||
/// [`Bitmap`] compression requires a known [`DataSpace`]. [`DataSpace::Unknown`] is invalid
|
||||
/// even though it is typically treated as `sRGB`, for that [`DataSpace::Srgb`] has to be passed
|
||||
/// explicitly.
|
||||
#[error("The dataspace for this Bitmap is Unknown")]
|
||||
DataSpaceUnknown,
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
//! Bindings for [`AConfiguration`]
|
||||
//!
|
||||
//! See also the [NDK docs](https://developer.android.com/ndk/reference/group/configuration) for
|
||||
//! [`AConfiguration`], as well as the [docs for providing
|
||||
//! resources](https://developer.android.com/guide/topics/resources/providing-resources.html),
|
||||
//! which explain many of the configuration values. The [`android.content.res.Configuration`
|
||||
//! javadoc](https://developer.android.com/reference/android/content/res/Configuration.html) may
|
||||
//! also have useful information.
|
||||
//!
|
||||
//! [`AConfiguration`]: https://developer.android.com/ndk/reference/group/configuration#aconfiguration
|
||||
|
||||
use crate::asset::AssetManager;
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
use std::fmt;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
/// A native [`AConfiguration *`]
|
||||
///
|
||||
/// [`Configuration`] is an opaque type used to get and set various subsystem configurations.
|
||||
///
|
||||
/// [`AConfiguration *`]: https://developer.android.com/ndk/reference/group/configuration#aconfiguration
|
||||
pub struct Configuration {
|
||||
ptr: NonNull<ffi::AConfiguration>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Configuration {}
|
||||
unsafe impl Sync for Configuration {}
|
||||
|
||||
impl Drop for Configuration {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AConfiguration_delete(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Configuration {
|
||||
fn clone(&self) -> Self {
|
||||
let mut new = Self::new();
|
||||
new.copy(self);
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Configuration {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.diff(other).0 == 0
|
||||
}
|
||||
}
|
||||
impl Eq for Configuration {}
|
||||
|
||||
impl fmt::Debug for Configuration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Configuration")
|
||||
.field("mcc", &self.mcc())
|
||||
.field("mnc", &self.mnc())
|
||||
.field("lang", &self.language())
|
||||
.field("country", &self.country())
|
||||
.field("orientation", &self.orientation())
|
||||
.field("touchscreen", &self.touchscreen())
|
||||
.field("density", &self.density())
|
||||
.field("keyboard", &self.keyboard())
|
||||
.field("navigation", &self.navigation())
|
||||
.field("keys_hidden", &self.keys_hidden())
|
||||
.field("nav_hidden", &self.nav_hidden())
|
||||
.field("sdk_version", &self.sdk_version())
|
||||
.field("screen_size", &self.screen_size())
|
||||
.field("screen_long", &self.screen_long())
|
||||
.field("ui_mode_type", &self.ui_mode_type())
|
||||
.field("ui_mode_night", &self.ui_mode_night())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
/// Construct a `Configuration` from a pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it is a valid pointer to a native
|
||||
/// `AConfiguration`, and give ownership of it to the `Configuration` instance.
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AConfiguration>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Create a new `Configuration`, with the same contents as the `AConfiguration` referenced by
|
||||
/// the pointer.
|
||||
///
|
||||
/// This is useful if you have a pointer, but not ownership of it.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it is a valid pointer to a native
|
||||
/// `AConfiguration`.
|
||||
pub unsafe fn clone_from_ptr(ptr: NonNull<ffi::AConfiguration>) -> Self {
|
||||
let conf = Self::new();
|
||||
ffi::AConfiguration_copy(conf.ptr.as_ptr(), ptr.as_ptr());
|
||||
conf
|
||||
}
|
||||
|
||||
/// The pointer to the native `AConfiguration`. Keep in mind that the `Configuration` object
|
||||
/// still has ownership, and will free it when dropped.
|
||||
pub fn ptr(&self) -> NonNull<ffi::AConfiguration> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
pub fn from_asset_manager(am: &AssetManager) -> Self {
|
||||
let config = Self::new();
|
||||
unsafe {
|
||||
ffi::AConfiguration_fromAssetManager(config.ptr().as_mut(), am.ptr().as_mut());
|
||||
}
|
||||
config
|
||||
}
|
||||
|
||||
/// Create a new `Configuration`, with none of the values set.
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
Self {
|
||||
ptr: NonNull::new(ffi::AConfiguration_new()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `dest.copy(&src)` copies the contents of `src` to `dest`
|
||||
pub fn copy(&mut self, other: &Self) {
|
||||
unsafe { ffi::AConfiguration_copy(self.ptr.as_ptr(), other.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Information about what fields differ between the two configurations
|
||||
pub fn diff(&self, other: &Self) -> DiffResult {
|
||||
unsafe {
|
||||
DiffResult(ffi::AConfiguration_diff(self.ptr.as_ptr(), other.ptr.as_ptr()) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns false if anything in `self` conflicts with `requested`
|
||||
pub fn matches(&self, requested: &Self) -> bool {
|
||||
unsafe { ffi::AConfiguration_match(self.ptr.as_ptr(), requested.ptr.as_ptr()) != 0 }
|
||||
}
|
||||
|
||||
/// Returns the country code, as a [`String`] of two characters, if set
|
||||
pub fn country(&self) -> Option<String> {
|
||||
let mut chars = [0u8; 2];
|
||||
unsafe {
|
||||
ffi::AConfiguration_getCountry(self.ptr.as_ptr(), chars.as_mut_ptr().cast());
|
||||
}
|
||||
if chars[0] == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(std::str::from_utf8(chars.as_slice()).unwrap().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the screen density in dpi.
|
||||
///
|
||||
/// On some devices it can return values outside of the density enum.
|
||||
pub fn density(&self) -> Option<u32> {
|
||||
let density = unsafe { ffi::AConfiguration_getDensity(self.ptr.as_ptr()) as u32 };
|
||||
match density {
|
||||
ffi::ACONFIGURATION_DENSITY_DEFAULT => Some(160),
|
||||
ffi::ACONFIGURATION_DENSITY_ANY => None,
|
||||
ffi::ACONFIGURATION_DENSITY_NONE => None,
|
||||
density => Some(density),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the keyboard type.
|
||||
pub fn keyboard(&self) -> Keyboard {
|
||||
unsafe { ffi::AConfiguration_getKeyboard(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
/// Returns keyboard visibility/availability.
|
||||
pub fn keys_hidden(&self) -> KeysHidden {
|
||||
unsafe { ffi::AConfiguration_getKeysHidden(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
/// Returns the language, as a [`String`] of two characters, if set
|
||||
pub fn language(&self) -> Option<String> {
|
||||
let mut chars = [0u8; 2];
|
||||
unsafe {
|
||||
ffi::AConfiguration_getLanguage(self.ptr.as_ptr(), chars.as_mut_ptr().cast());
|
||||
}
|
||||
if chars[0] == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(std::str::from_utf8(chars.as_slice()).unwrap().to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the layout direction
|
||||
pub fn layout_direction(&self) -> LayoutDir {
|
||||
unsafe { ffi::AConfiguration_getLayoutDirection(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
/// Returns the mobile country code.
|
||||
pub fn mcc(&self) -> i32 {
|
||||
unsafe { ffi::AConfiguration_getMcc(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Returns the mobile network code, if one is defined
|
||||
pub fn mnc(&self) -> Option<i32> {
|
||||
unsafe {
|
||||
match ffi::AConfiguration_getMnc(self.ptr.as_ptr()) {
|
||||
0 => None,
|
||||
x if x == ffi::ACONFIGURATION_MNC_ZERO as i32 => Some(0),
|
||||
x => Some(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nav_hidden(&self) -> NavHidden {
|
||||
unsafe { ffi::AConfiguration_getNavHidden(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn navigation(&self) -> Navigation {
|
||||
unsafe { ffi::AConfiguration_getNavigation(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn orientation(&self) -> Orientation {
|
||||
unsafe { ffi::AConfiguration_getOrientation(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn screen_height_dp(&self) -> Option<i32> {
|
||||
unsafe {
|
||||
let height = ffi::AConfiguration_getScreenHeightDp(self.ptr.as_ptr());
|
||||
if height == ffi::ACONFIGURATION_SCREEN_HEIGHT_DP_ANY as i32 {
|
||||
None
|
||||
} else {
|
||||
Some(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screen_width_dp(&self) -> Option<i32> {
|
||||
unsafe {
|
||||
let width = ffi::AConfiguration_getScreenWidthDp(self.ptr.as_ptr());
|
||||
if width == ffi::ACONFIGURATION_SCREEN_WIDTH_DP_ANY as i32 {
|
||||
None
|
||||
} else {
|
||||
Some(width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screen_long(&self) -> ScreenLong {
|
||||
unsafe { ffi::AConfiguration_getScreenLong(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
pub fn screen_round(&self) -> ScreenRound {
|
||||
unsafe { ffi::AConfiguration_getScreenRound(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn screen_size(&self) -> ScreenSize {
|
||||
unsafe { ffi::AConfiguration_getScreenSize(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn sdk_version(&self) -> i32 {
|
||||
unsafe { ffi::AConfiguration_getSdkVersion(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn smallest_screen_width_dp(&self) -> Option<i32> {
|
||||
unsafe {
|
||||
let width = ffi::AConfiguration_getSmallestScreenWidthDp(self.ptr.as_ptr());
|
||||
if width == ffi::ACONFIGURATION_SMALLEST_SCREEN_WIDTH_DP_ANY as i32 {
|
||||
None
|
||||
} else {
|
||||
Some(width)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn touchscreen(&self) -> Touchscreen {
|
||||
unsafe { ffi::AConfiguration_getTouchscreen(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn ui_mode_night(&self) -> UiModeNight {
|
||||
unsafe { ffi::AConfiguration_getUiModeNight(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
|
||||
pub fn ui_mode_type(&self) -> UiModeType {
|
||||
unsafe { ffi::AConfiguration_getUiModeType(self.ptr.as_ptr()).into() }
|
||||
}
|
||||
}
|
||||
|
||||
/// A bitfield representing the differences between two [`Configuration`]s
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct DiffResult(pub u32);
|
||||
|
||||
impl DiffResult {
|
||||
pub fn mcc(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_MCC != 0
|
||||
}
|
||||
pub fn mnc(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_MNC != 0
|
||||
}
|
||||
pub fn locale(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_LOCALE != 0
|
||||
}
|
||||
pub fn touchscreen(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_TOUCHSCREEN != 0
|
||||
}
|
||||
pub fn keyboard(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_KEYBOARD != 0
|
||||
}
|
||||
pub fn keyboard_hidden(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_KEYBOARD_HIDDEN != 0
|
||||
}
|
||||
pub fn navigation(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_NAVIGATION != 0
|
||||
}
|
||||
pub fn orientation(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_ORIENTATION != 0
|
||||
}
|
||||
pub fn density(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_DENSITY != 0
|
||||
}
|
||||
pub fn screen_size(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_SCREEN_SIZE != 0
|
||||
}
|
||||
pub fn version(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_VERSION != 0
|
||||
}
|
||||
pub fn screen_layout(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_SCREEN_LAYOUT != 0
|
||||
}
|
||||
pub fn ui_mode(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_UI_MODE != 0
|
||||
}
|
||||
pub fn smallest_screen_size(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_SMALLEST_SCREEN_SIZE != 0
|
||||
}
|
||||
pub fn layout_dir(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_LAYOUTDIR != 0
|
||||
}
|
||||
pub fn screen_round(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_SCREEN_ROUND != 0
|
||||
}
|
||||
pub fn color_mode(self) -> bool {
|
||||
self.0 & ffi::ACONFIGURATION_COLOR_MODE != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Orientation {
|
||||
Any = ffi::ACONFIGURATION_ORIENTATION_ANY as i32,
|
||||
Port = ffi::ACONFIGURATION_ORIENTATION_PORT as i32,
|
||||
Land = ffi::ACONFIGURATION_ORIENTATION_LAND as i32,
|
||||
Square = ffi::ACONFIGURATION_ORIENTATION_SQUARE as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Touchscreen {
|
||||
Any = ffi::ACONFIGURATION_TOUCHSCREEN_ANY as i32,
|
||||
NoTouch = ffi::ACONFIGURATION_TOUCHSCREEN_NOTOUCH as i32,
|
||||
Stylus = ffi::ACONFIGURATION_TOUCHSCREEN_STYLUS as i32,
|
||||
Finger = ffi::ACONFIGURATION_TOUCHSCREEN_FINGER as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Density {
|
||||
Default = ffi::ACONFIGURATION_DENSITY_DEFAULT as i32,
|
||||
Low = ffi::ACONFIGURATION_DENSITY_LOW as i32,
|
||||
Medium = ffi::ACONFIGURATION_DENSITY_MEDIUM as i32,
|
||||
TV = ffi::ACONFIGURATION_DENSITY_TV as i32,
|
||||
High = ffi::ACONFIGURATION_DENSITY_HIGH as i32,
|
||||
XHigh = ffi::ACONFIGURATION_DENSITY_XHIGH as i32,
|
||||
XXHigh = ffi::ACONFIGURATION_DENSITY_XXHIGH as i32,
|
||||
XXXHigh = ffi::ACONFIGURATION_DENSITY_XXXHIGH as i32,
|
||||
Any = ffi::ACONFIGURATION_DENSITY_ANY as i32,
|
||||
None = ffi::ACONFIGURATION_DENSITY_NONE as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl Density {
|
||||
/// The DPI associated with the density class.
|
||||
/// See [the Android screen density
|
||||
/// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp)
|
||||
///
|
||||
/// There are some [`Density`] values that have no associated DPI; these values return [`None`].
|
||||
pub fn dpi(self) -> Option<u32> {
|
||||
match self {
|
||||
Self::Default => Some(160), // Or should it be None?
|
||||
Self::Low => Some(120),
|
||||
Self::Medium => Some(160),
|
||||
Self::High => Some(240),
|
||||
Self::XHigh => Some(320),
|
||||
Self::XXHigh => Some(480),
|
||||
Self::XXXHigh => Some(640),
|
||||
Self::TV => Some(213),
|
||||
Self::Any => None,
|
||||
Self::None => None,
|
||||
// TODO
|
||||
Self::__Unknown(v) => Some(v as u32),
|
||||
}
|
||||
}
|
||||
|
||||
/// The Hi-DPI factor associated with the density class. This is the factor by which an
|
||||
/// image/resource should be scaled to match its size across devices. The baseline is a 160dpi
|
||||
/// screen (i.e., Hi-DPI factor = DPI / 160).
|
||||
/// See [the Android screen density
|
||||
/// docs](https://developer.android.com/training/multiscreen/screendensities#TaskProvideAltBmp)
|
||||
///
|
||||
/// There are some [`Density`] values that have no associated DPI; these values return [`None`].
|
||||
pub fn approx_hidpi_factor(self) -> Option<f64> {
|
||||
match self {
|
||||
Self::Default => Some(1.), // Or should it be None?
|
||||
Self::Low => Some(0.75),
|
||||
Self::Medium => Some(1.),
|
||||
Self::High => Some(1.5),
|
||||
Self::XHigh => Some(2.),
|
||||
Self::XXHigh => Some(3.),
|
||||
Self::XXXHigh => Some(4.),
|
||||
Self::TV => Some(4. / 3.),
|
||||
Self::Any => None,
|
||||
Self::None => None,
|
||||
Self::__Unknown(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Keyboard {
|
||||
Any = ffi::ACONFIGURATION_KEYBOARD_ANY as i32,
|
||||
NoKeys = ffi::ACONFIGURATION_KEYBOARD_NOKEYS as i32,
|
||||
Qwerty = ffi::ACONFIGURATION_KEYBOARD_QWERTY as i32,
|
||||
TwelveKey = ffi::ACONFIGURATION_KEYBOARD_12KEY as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum Navigation {
|
||||
Any = ffi::ACONFIGURATION_NAVIGATION_ANY as i32,
|
||||
NoNav = ffi::ACONFIGURATION_NAVIGATION_NONAV as i32,
|
||||
DPad = ffi::ACONFIGURATION_NAVIGATION_DPAD as i32,
|
||||
Trackball = ffi::ACONFIGURATION_NAVIGATION_TRACKBALL as i32,
|
||||
Wheel = ffi::ACONFIGURATION_NAVIGATION_WHEEL as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum KeysHidden {
|
||||
Any = ffi::ACONFIGURATION_KEYSHIDDEN_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_KEYSHIDDEN_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_KEYSHIDDEN_YES as i32,
|
||||
Soft = ffi::ACONFIGURATION_KEYSHIDDEN_SOFT as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum NavHidden {
|
||||
Any = ffi::ACONFIGURATION_NAVHIDDEN_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_NAVHIDDEN_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_NAVHIDDEN_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum ScreenSize {
|
||||
Any = ffi::ACONFIGURATION_SCREENSIZE_ANY as i32,
|
||||
Small = ffi::ACONFIGURATION_SCREENSIZE_SMALL as i32,
|
||||
Normal = ffi::ACONFIGURATION_SCREENSIZE_NORMAL as i32,
|
||||
Large = ffi::ACONFIGURATION_SCREENSIZE_LARGE as i32,
|
||||
XLarge = ffi::ACONFIGURATION_SCREENSIZE_XLARGE as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum ScreenLong {
|
||||
Any = ffi::ACONFIGURATION_SCREENLONG_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_SCREENLONG_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_SCREENLONG_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum ScreenRound {
|
||||
Any = ffi::ACONFIGURATION_SCREENROUND_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_SCREENROUND_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_SCREENROUND_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum WideColorGamut {
|
||||
Any = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_WIDE_COLOR_GAMUT_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum HDR {
|
||||
Any = ffi::ACONFIGURATION_HDR_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_HDR_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_HDR_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum LayoutDir {
|
||||
Any = ffi::ACONFIGURATION_LAYOUTDIR_ANY as i32,
|
||||
Ltr = ffi::ACONFIGURATION_LAYOUTDIR_LTR as i32,
|
||||
Rtl = ffi::ACONFIGURATION_LAYOUTDIR_RTL as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum UiModeType {
|
||||
Any = ffi::ACONFIGURATION_UI_MODE_TYPE_ANY as i32,
|
||||
Normal = ffi::ACONFIGURATION_UI_MODE_TYPE_NORMAL as i32,
|
||||
Desk = ffi::ACONFIGURATION_UI_MODE_TYPE_DESK as i32,
|
||||
Car = ffi::ACONFIGURATION_UI_MODE_TYPE_CAR as i32,
|
||||
Television = ffi::ACONFIGURATION_UI_MODE_TYPE_TELEVISION as i32,
|
||||
Applicance = ffi::ACONFIGURATION_UI_MODE_TYPE_APPLIANCE as i32,
|
||||
Watch = ffi::ACONFIGURATION_UI_MODE_TYPE_WATCH as i32,
|
||||
VrHeadset = ffi::ACONFIGURATION_UI_MODE_TYPE_VR_HEADSET as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[repr(i32)]
|
||||
#[non_exhaustive]
|
||||
pub enum UiModeNight {
|
||||
Any = ffi::ACONFIGURATION_UI_MODE_NIGHT_ANY as i32,
|
||||
No = ffi::ACONFIGURATION_UI_MODE_NIGHT_NO as i32,
|
||||
Yes = ffi::ACONFIGURATION_UI_MODE_NIGHT_YES as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
+648
@@ -0,0 +1,648 @@
|
||||
//! Bindings for [`ADataSpace`]
|
||||
//!
|
||||
//! [`ADataSpace`]: https://developer.android.com/ndk/reference/group/a-data-space#group___a_data_space_1ga2759ad19cae46646cc5f7002758c4a1c
|
||||
#![cfg(feature = "api-level-28")]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
|
||||
/// Describes how to interpret colors.
|
||||
///
|
||||
/// <https://developer.android.com/ndk/reference/group/a-data-space#group___a_data_space_1ga2759ad19cae46646cc5f7002758c4a1c>
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "ADataSpace")]
|
||||
#[non_exhaustive]
|
||||
pub enum DataSpace {
|
||||
/// Default-assumption data space, when not explicitly specified.
|
||||
///
|
||||
/// It is safest to assume the buffer is an image with `sRGB` primaries and encoding ranges,
|
||||
/// but the consumer and/or the producer of the data may simply be using defaults. No automatic
|
||||
/// gamma transform should be expected, except for a possible display gamma transform when drawn
|
||||
/// to a screen.
|
||||
#[doc(alias = "ADATASPACE_UNKNOWN")]
|
||||
Unknown = ffi::ADataSpace::ADATASPACE_UNKNOWN.0,
|
||||
|
||||
/// Adobe RGB.
|
||||
///
|
||||
/// Uses [full range], [gamma `2.2` transfer] and [Adobe RGB standard].
|
||||
///
|
||||
/// Note: Application is responsible for gamma encoding the data as a `2.2` gamma encoding is
|
||||
/// not supported in HW.
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [gamma `2.2` transfer]: DataSpaceTransfer::Gamma2_2
|
||||
/// [Adobe RGB standard]: DataSpaceStandard::AdobeRgb
|
||||
#[doc(alias = "ADATASPACE_ADOBE_RGB")]
|
||||
AdobeRgb = ffi::ADataSpace::ADATASPACE_ADOBE_RGB.0,
|
||||
/// ITU-R Recommendation 2020 (`BT.2020`).
|
||||
///
|
||||
/// Ultra High-definition television.
|
||||
///
|
||||
/// Uses [full range], [`SMPTE 170M` transfer] and [`BT2020` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M
|
||||
/// [`BT2020` standard]: DataSpaceStandard::Bt2020
|
||||
#[doc(alias = "ADATASPACE_BT2020")]
|
||||
Bt2020 = ffi::ADataSpace::ADATASPACE_BT2020.0,
|
||||
/// Hybrid Log Gamma encoding.
|
||||
///
|
||||
/// Uses [full range], [hybrid log gamma transfer] and [`BT2020` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [hybrid log gamma transfer]: DataSpaceTransfer::HLG
|
||||
/// [`BT2020` standard]: DataSpaceStandard::Bt2020
|
||||
#[doc(alias = "ADATASPACE_BT2020_HLG")]
|
||||
Bt2020Hlg = ffi::ADataSpace::ADATASPACE_BT2020_HLG.0,
|
||||
/// ITU Hybrid Log Gamma encoding.
|
||||
///
|
||||
/// Uses [limited range], [hybrid log gamma transfer] and [`BT2020` standard].
|
||||
///
|
||||
/// [limited range]: DataSpaceRange::Limited
|
||||
/// [hybrid log gamma transfer]: DataSpaceTransfer::HLG
|
||||
/// [`BT2020` standard]: DataSpaceStandard::Bt2020
|
||||
#[doc(alias = "ADATASPACE_BT2020_ITU_HLG")]
|
||||
Bt2020ItuHlg = ffi::ADataSpace::ADATASPACE_BT2020_ITU_HLG.0,
|
||||
/// ITU-R Recommendation 2020 (`BT.2020`).
|
||||
///
|
||||
/// Ultra High-definition television.
|
||||
///
|
||||
/// Uses [limited range], [`SMPTE 2084 (PQ)` transfer] and [`BT2020` standard].
|
||||
///
|
||||
/// [limited range]: DataSpaceRange::Limited
|
||||
/// [`SMPTE 2084 (PQ)` transfer]: DataSpaceTransfer::St2084
|
||||
/// [`BT2020` standard]: DataSpaceStandard::Bt2020
|
||||
#[doc(alias = "ADATASPACE_BT2020_ITU_PQ")]
|
||||
Bt2020ItuPq = ffi::ADataSpace::ADATASPACE_BT2020_ITU_PQ.0,
|
||||
/// ITU-R Recommendation 2020 (`BT.2020`).
|
||||
///
|
||||
/// Ultra High-definition television.
|
||||
///
|
||||
/// Uses [full range], [`SMPTE 2084 (PQ)` transfer] and [`BT2020` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [`SMPTE 2084 (PQ)` transfer]: DataSpaceTransfer::St2084
|
||||
/// [`BT2020` standard]: DataSpaceStandard::Bt2020
|
||||
#[doc(alias = "ADATASPACE_BT2020_PQ")]
|
||||
Bt2020Pq = ffi::ADataSpace::ADATASPACE_BT2020_PQ.0,
|
||||
/// ITU-R Recommendation 601 (`BT.601`) - 525-line.
|
||||
///
|
||||
/// Standard-definition television, 525 Lines (NTSC).
|
||||
///
|
||||
/// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.601_525` standard].
|
||||
///
|
||||
/// [limited range]: DataSpaceRange::Limited
|
||||
/// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M
|
||||
/// [`BT.601_525` standard]: DataSpaceStandard::Bt601_525
|
||||
#[doc(alias = "ADATASPACE_BT601_525")]
|
||||
Bt601_525 = ffi::ADataSpace::ADATASPACE_BT601_525.0,
|
||||
/// ITU-R Recommendation 601 (`BT.601`) - 625-line.
|
||||
///
|
||||
/// Standard-definition television, 625 Lines (PAL).
|
||||
///
|
||||
/// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.601_625` standard].
|
||||
///
|
||||
/// [limited range]: DataSpaceRange::Limited
|
||||
/// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M
|
||||
/// [`BT.601_625` standard]: DataSpaceStandard::Bt601_625
|
||||
#[doc(alias = "ADATASPACE_BT601_625")]
|
||||
Bt601_625 = ffi::ADataSpace::ADATASPACE_BT601_625.0,
|
||||
/// ITU-R Recommendation 709 (`BT.709`).
|
||||
///
|
||||
/// High-definition television.
|
||||
///
|
||||
/// Uses [limited range], [`SMPTE 170M` transfer] and [`BT.709` standard].
|
||||
///
|
||||
/// [limited range]: DataSpaceRange::Limited
|
||||
/// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M
|
||||
/// [`BT.709` standard]: DataSpaceStandard::Bt709
|
||||
#[doc(alias = "ADATASPACE_BT709")]
|
||||
Bt709 = ffi::ADataSpace::ADATASPACE_BT709.0,
|
||||
/// `SMPTE EG 432-1` and `SMPTE RP 431-2`.
|
||||
///
|
||||
/// Digital Cinema `DCI-P3`.
|
||||
///
|
||||
/// Uses [full range], [gamma `2.6` transfer] and [`D65` `DCI-P3` standard].
|
||||
///
|
||||
/// Note: Application is responsible for gamma encoding the data as a `2.6` gamma encoding is
|
||||
/// not supported in HW.
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [gamma `2.6` transfer]: DataSpaceTransfer::Gamma2_6
|
||||
/// [`D65` `DCI-P3` standard]: DataSpaceStandard::DciP3
|
||||
#[doc(alias = "ADATASPACE_DCI_P3")]
|
||||
DciP3 = ffi::ADataSpace::ADATASPACE_DCI_P3.0,
|
||||
/// Display P3.
|
||||
///
|
||||
/// Uses [full range], [`sRGB` transfer] and [`D65` `DCI-P3` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [`sRGB` transfer]: DataSpaceTransfer::Srgb
|
||||
/// [`D65` `DCI-P3` standard]: DataSpaceStandard::DciP3
|
||||
#[doc(alias = "ADATASPACE_DISPLAY_P3")]
|
||||
DisplayP3 = ffi::ADataSpace::ADATASPACE_DISPLAY_P3.0,
|
||||
/// JPEG File Interchange Format (`JFIF`).
|
||||
///
|
||||
/// Same model as `BT.601-625`, but all values (`Y`, `Cb`, `Cr`) range from `0` to `255`.
|
||||
///
|
||||
/// Uses [full range], [`SMPTE 170M` transfer] and [`BT.601_625` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [`SMPTE 170M` transfer]: DataSpaceTransfer::Smpte170M
|
||||
/// [`BT.601_625` standard]: DataSpaceStandard::Bt601_625
|
||||
#[doc(alias = "ADATASPACE_JFIF")]
|
||||
Jfif = ffi::ADataSpace::ADATASPACE_JFIF.0,
|
||||
/// `scRGB`.
|
||||
///
|
||||
/// The `red`, `green`, and `blue` components are stored in [extended][extended range] `sRGB`
|
||||
/// space, and gamma- encoded using the [`sRGB` transfer] function.
|
||||
///
|
||||
/// The values are floating point. A pixel value of `1.0`, `1.0`, `1.0` corresponds to `sRGB`
|
||||
/// white (`D65`) at `80` nits. Values beyond the range `[0.0 - 1.0]` would correspond to other
|
||||
/// colors spaces and/or HDR content.
|
||||
///
|
||||
/// Uses [extended range], [`sRGB` transfer] and [`BT.709` standard].
|
||||
///
|
||||
/// [extended range]: DataSpaceRange::Extended
|
||||
/// [`sRGB` transfer]: DataSpaceTransfer::Srgb
|
||||
/// [`BT.709` standard]: DataSpaceStandard::Bt709
|
||||
#[doc(alias = "ADATASPACE_SCRGB")]
|
||||
Scrgb = ffi::ADataSpace::ADATASPACE_SCRGB.0,
|
||||
/// `scRGB` linear encoding
|
||||
///
|
||||
/// The `red`, `green`, and `blue` components are stored in [extended][extended range] `sRGB`
|
||||
/// space, but are linear, not gamma-encoded.
|
||||
///
|
||||
/// The values are floating point. A pixel value of `1.0`, `1.0`, `1.0` corresponds to `sRGB`
|
||||
/// white (`D65`) at `80` nits. Values beyond the range `[0.0 - 1.0]` would correspond to other
|
||||
/// colors spaces and/or HDR content.
|
||||
///
|
||||
/// Uses [extended range], [linear transfer] and [`BT.709` standard].
|
||||
///
|
||||
/// [extended range]: DataSpaceRange::Extended
|
||||
/// [linear transfer]: DataSpaceTransfer::Linear
|
||||
/// [`BT.709` standard]: DataSpaceStandard::Bt709
|
||||
#[doc(alias = "ADATASPACE_SCRGB_LINEAR")]
|
||||
ScrgbLinear = ffi::ADataSpace::ADATASPACE_SCRGB_LINEAR.0,
|
||||
/// `sRGB` gamma encoding.
|
||||
///
|
||||
/// The `red`, `green` and `blue` components are stored in `sRGB` space, and converted to linear
|
||||
/// space when read, using the [`sRGB` transfer] function for each of the `R`, `G` and `B`
|
||||
/// components. When written, the inverse transformation is performed.
|
||||
///
|
||||
/// The `alpha` component, if present, is always stored in linear space and is left unmodified
|
||||
/// when read or written.
|
||||
///
|
||||
/// Uses [full range], [`sRGB` transfer] and [`BT.709` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [`sRGB` transfer]: DataSpaceTransfer::Srgb
|
||||
/// [`BT.709` standard]: DataSpaceStandard::Bt709
|
||||
#[doc(alias = "ADATASPACE_SRGB")]
|
||||
Srgb = ffi::ADataSpace::ADATASPACE_SRGB.0,
|
||||
/// `sRGB` linear encoding.
|
||||
///
|
||||
/// The `red`, `green`, and `blue` components are stored in `sRGB` space, but are linear, not
|
||||
/// gamma-encoded. The `RGB` primaries and the white point are the same as [`BT.709]`.
|
||||
///
|
||||
/// The values are encoded using the [full range] (`[0, 255]` for 8-bit) for all components.
|
||||
///
|
||||
/// Uses [full range], [linear transfer] and [`BT.709` standard].
|
||||
///
|
||||
/// [full range]: DataSpaceRange::Full
|
||||
/// [linear transfer]: DataSpaceTransfer::Linear
|
||||
/// [`BT.709` standard]: DataSpaceStandard::Bt709
|
||||
#[doc(alias = "ADATASPACE_SRGB_LINEAR")]
|
||||
SrgbLinear = ffi::ADataSpace::ADATASPACE_SRGB_LINEAR.0,
|
||||
|
||||
/// Depth.
|
||||
///
|
||||
/// This value is valid with formats [`HAL_PIXEL_FORMAT_Y16`] and [`HAL_PIXEL_FORMAT_BLOB`].
|
||||
///
|
||||
/// [`HAL_PIXEL_FORMAT_Y16`]: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/nativewindow/include/vndk/hardware_buffer.h;l=74-75;drc=45317f5c7c966fc816843217adc96a2ddea8bf29
|
||||
/// [`HAL_PIXEL_FORMAT_BLOB`]: super::hardware_buffer_format::HardwareBufferFormat::BLOB
|
||||
#[doc(alias = "ADATASPACE_DEPTH")]
|
||||
Depth = ffi::ADataSpace::ADATASPACE_DEPTH.0,
|
||||
/// ISO `16684-1:2011(E)` Dynamic Depth.
|
||||
///
|
||||
/// Embedded depth metadata following the dynamic depth specification.
|
||||
#[doc(alias = "ADATASPACE_DYNAMIC_DEPTH")]
|
||||
DynamicDepth = ffi::ADataSpace::ADATASPACE_DYNAMIC_DEPTH.0,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl fmt::Display for DataSpace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Unknown => "Unknown",
|
||||
Self::AdobeRgb => "AdobeRgb",
|
||||
Self::Bt2020 => "Bt2020",
|
||||
Self::Bt2020Hlg => "Bt2020Hlg",
|
||||
Self::Bt2020ItuHlg => "Bt2020ItuHlg",
|
||||
Self::Bt2020ItuPq => "Bt2020ItuPq",
|
||||
Self::Bt2020Pq => "Bt2020Pq",
|
||||
Self::Bt601_525 => "Bt601_525",
|
||||
Self::Bt601_625 => "Bt601_625",
|
||||
Self::Bt709 => "Bt709",
|
||||
Self::DciP3 => "DciP3",
|
||||
Self::DisplayP3 => "DisplayP3",
|
||||
Self::Jfif => "Jfif",
|
||||
Self::Scrgb => "Scrgb",
|
||||
Self::ScrgbLinear => "ScrgbLinear",
|
||||
Self::Srgb => "Srgb",
|
||||
Self::SrgbLinear => "SrgbLinear",
|
||||
Self::Depth => "Depth",
|
||||
Self::DynamicDepth => "DynamicDepth",
|
||||
Self::__Unknown(u) => {
|
||||
return write!(
|
||||
f,
|
||||
"Unknown DataSpace({u:x?}, standard: {:?}, transfer: {:?}, range: {:?})",
|
||||
self.standard(),
|
||||
self.transfer(),
|
||||
self.range()
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DataSpace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"DataSpace({self}, standard: {:?}, transfer: {:?}, range: {:?})",
|
||||
self.standard(),
|
||||
self.transfer(),
|
||||
self.range(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSpace {
|
||||
/// Construct a [`DataSpace`] from individual `standard`, `transfer` and `range` components.
|
||||
///
|
||||
/// Together these should correspond to a single format.
|
||||
pub fn from_parts(
|
||||
standard: DataSpaceStandard,
|
||||
transfer: DataSpaceTransfer,
|
||||
range: DataSpaceRange,
|
||||
) -> Self {
|
||||
Self::from(i32::from(standard) | i32::from(transfer) | i32::from(range))
|
||||
}
|
||||
|
||||
/// Extracts and returns the color-description aspect from this [`DataSpace`].
|
||||
#[doc(alias = "STANDARD_MASK")]
|
||||
pub fn standard(self) -> DataSpaceStandard {
|
||||
let standard = i32::from(self) & ffi::ADataSpace::STANDARD_MASK.0;
|
||||
standard.into()
|
||||
}
|
||||
|
||||
/// Extracts and returns the transfer aspect from this [`DataSpace`].
|
||||
#[doc(alias = "TRANSFER_MASK")]
|
||||
pub fn transfer(self) -> DataSpaceTransfer {
|
||||
let transfer = i32::from(self) & ffi::ADataSpace::TRANSFER_MASK.0;
|
||||
transfer.into()
|
||||
}
|
||||
|
||||
/// Extracts and returns the range aspect from this [`DataSpace`].
|
||||
#[doc(alias = "RANGE_MASK")]
|
||||
pub fn range(self) -> DataSpaceRange {
|
||||
let range = i32::from(self) & ffi::ADataSpace::RANGE_MASK.0;
|
||||
range.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Color-description aspects.
|
||||
///
|
||||
/// The following aspects define various characteristics of the color specification. These represent
|
||||
/// bitfields, so that a data space value can specify each of them independently. Standard aspect
|
||||
/// defines the chromaticity coordinates of the source primaries in terms of the CIE 1931 definition
|
||||
/// of `x` and `y` specified in ISO 11664-1.
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "STANDARD_MASK")]
|
||||
#[non_exhaustive]
|
||||
pub enum DataSpaceStandard {
|
||||
/// Chromacity coordinates are unknown or are determined by the application. Implementations
|
||||
/// shall use the following suggested standards:
|
||||
///
|
||||
/// All `YCbCr` formats: [`BT.709`] if size is `720p` or larger (since most video content is
|
||||
/// letterboxed this corresponds to width is `1280` or greater, or height
|
||||
/// is 720 or greater). [`BT.601_625`] if size is smaller than `720p` or
|
||||
/// is `JPEG`.
|
||||
/// All `RGB` formats: [`BT.709`].
|
||||
///
|
||||
/// For all other formats the standard is undefined, and implementations should use an
|
||||
/// appropriate standard for the data represented.
|
||||
///
|
||||
/// [`BT.709`]: Self::Bt709
|
||||
/// [`BT.601_625`]: Self::Bt601_625
|
||||
#[doc(alias = "STANDARD_UNSPECIFIED")]
|
||||
Unspecified = ffi::ADataSpace::STANDARD_UNSPECIFIED.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.300 | 0.600 |
|
||||
/// | blue | 0.150 | 0.060 |
|
||||
/// | red | 0.640 | 0.330 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.2126`, `KB = 0.0722` luminance interpretation for `RGB`
|
||||
/// conversion.
|
||||
#[doc(alias = "STANDARD_BT709")]
|
||||
Bt709 = ffi::ADataSpace::STANDARD_BT709.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.290 | 0.600 |
|
||||
/// | blue | 0.150 | 0.060 |
|
||||
/// | red | 0.640 | 0.330 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// `KR = 0.299`, `KB = 0.114`. This adjusts the luminance interpretation for `RGB` conversion
|
||||
/// from the one purely determined by the primaries to minimize the color shift into `RGB`
|
||||
/// space that uses [`BT.709`] primaries.
|
||||
///
|
||||
/// [`BT.709`]: Self::Bt709
|
||||
#[doc(alias = "STANDARD_BT601_625")]
|
||||
Bt601_625 = ffi::ADataSpace::STANDARD_BT601_625.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.290 | 0.600 |
|
||||
/// | blue | 0.150 | 0.060 |
|
||||
/// | red | 0.640 | 0.330 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.222`, `KB = 0.071` luminance interpretation for `RGB` conversion.
|
||||
#[doc(alias = "STANDARD_BT601_625_UNADJUSTED")]
|
||||
Bt601_625Unadjusted = ffi::ADataSpace::STANDARD_BT601_625_UNADJUSTED.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.310 | 0.595 |
|
||||
/// | blue | 0.155 | 0.070 |
|
||||
/// | red | 0.630 | 0.340 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// `KR = 0.299`, `KB = 0.114`. This adjusts the luminance interpretation for `RGB` conversion
|
||||
/// from the one purely determined by the primaries to minimize the color shift into `RGB` space
|
||||
/// that uses [`BT.709`] primaries.
|
||||
///
|
||||
/// [`BT.709`]: Self::Bt709
|
||||
#[doc(alias = "STANDARD_BT601_525")]
|
||||
Bt601_525 = ffi::ADataSpace::STANDARD_BT601_525.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.310 | 0.595 |
|
||||
/// | blue | 0.155 | 0.070 |
|
||||
/// | red | 0.630 | 0.340 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.212`, `KB = 0.087` luminance interpretation
|
||||
/// for `RGB` conversion (as in `SMPTE 240M`).
|
||||
#[doc(alias = "STANDARD_BT601_525_UNADJUSTED")]
|
||||
Bt601_525Unadjusted = ffi::ADataSpace::STANDARD_BT601_525_UNADJUSTED.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.170 | 0.797 |
|
||||
/// | blue | 0.131 | 0.046 |
|
||||
/// | red | 0.708 | 0.292 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.2627`, `KB = 0.0593` luminance interpretation for `RGB`
|
||||
/// conversion.
|
||||
#[doc(alias = "STANDARD_BT2020")]
|
||||
Bt2020 = ffi::ADataSpace::STANDARD_BT2020.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.170 | 0.797 |
|
||||
/// | blue | 0.131 | 0.046 |
|
||||
/// | red | 0.708 | 0.292 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.2627`, `KB = 0.0593` luminance interpretation for `RGB`
|
||||
/// conversion using the linear domain.
|
||||
#[doc(alias = "STANDARD_BT2020_CONSTANT_LUMINANCE")]
|
||||
Bt2020ConstantLuminance = ffi::ADataSpace::STANDARD_BT2020_CONSTANT_LUMINANCE.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | --------- | ----- | ---- |
|
||||
/// | green | 0.21 |0.71 |
|
||||
/// | blue | 0.14 |0.08 |
|
||||
/// | red | 0.67 |0.33 |
|
||||
/// | white (C) | 0.310 |0.316 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.30`, `KB = 0.11` luminance interpretation for `RGB` conversion.
|
||||
#[doc(alias = "STANDARD_BT470M")]
|
||||
Bt470M = ffi::ADataSpace::STANDARD_BT470M.0,
|
||||
/// | Primaries | x | y |
|
||||
/// | --------- | ----- | ----- |
|
||||
/// | green | 0.243 | 0.692 |
|
||||
/// | blue | 0.145 | 0.049 |
|
||||
/// | red | 0.681 | 0.319 |
|
||||
/// | white (C) | 0.310 | 0.316 |
|
||||
///
|
||||
/// Use the unadjusted `KR = 0.254`, `KB = 0.068` luminance interpretation for `RGB` conversion.
|
||||
#[doc(alias = "STANDARD_FILM")]
|
||||
Film = ffi::ADataSpace::STANDARD_FILM.0,
|
||||
/// `SMPTE EG 432-1` and `SMPTE RP 431-2`. (`DCI-P3`)
|
||||
///
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.265 | 0.690 |
|
||||
/// | blue | 0.150 | 0.060 |
|
||||
/// | red | 0.680 | 0.320 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
#[doc(alias = "STANDARD_DCI_P3")]
|
||||
DciP3 = ffi::ADataSpace::STANDARD_DCI_P3.0,
|
||||
/// Adobe RGB
|
||||
///
|
||||
/// | Primaries | x | y |
|
||||
/// | ----------- | ------ | ------ |
|
||||
/// | green | 0.210 | 0.710 |
|
||||
/// | blue | 0.150 | 0.060 |
|
||||
/// | red | 0.640 | 0.330 |
|
||||
/// | white (D65) | 0.3127 | 0.3290 |
|
||||
#[doc(alias = "STANDARD_ADOBE_RGB")]
|
||||
AdobeRgb = ffi::ADataSpace::STANDARD_ADOBE_RGB.0,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
/// Transfer aspect.
|
||||
///
|
||||
/// Transfer characteristics are the opto-electronic transfer characteristic at the source as a
|
||||
///function of linear optical intensity (luminance).
|
||||
///
|
||||
/// For digital signals, `E` corresponds to the recorded value. Normally, the transfer function is
|
||||
/// applied in `RGB` space to each of the `R`, `G` and `B` components independently. This may result
|
||||
/// in color shift that can be minimized by applying the transfer function in `Lab` space only for
|
||||
/// the `L` component. Implementation may apply the transfer function in `RGB` space for all pixel
|
||||
/// formats if desired.
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "TRANSFER_MASK")]
|
||||
#[non_exhaustive]
|
||||
pub enum DataSpaceTransfer {
|
||||
/// Transfer characteristics are unknown or are determined by the application.
|
||||
///
|
||||
/// Implementations should use the following transfer functions:
|
||||
///
|
||||
/// - For `YCbCr` formats: use [`DataSpaceTransfer::Smpte170M`]
|
||||
/// - For `RGB` formats: use [`DataSpaceTransfer::Srgb`]
|
||||
///
|
||||
/// For all other formats the transfer function is undefined, and implementations should use an
|
||||
/// appropriate standard for the data represented.
|
||||
#[doc(alias = "TRANSFER_UNSPECIFIED")]
|
||||
Unspecified = ffi::ADataSpace::TRANSFER_UNSPECIFIED.0,
|
||||
|
||||
/// Linear transfer.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = L
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_LINEAR")]
|
||||
Linear = ffi::ADataSpace::TRANSFER_LINEAR.0,
|
||||
/// `sRGB` transfer.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
///
|
||||
/// ```ignore
|
||||
/// E = 1.055 * L^(1/2.4) - 0.055 for 0.0031308 <= L <= 1
|
||||
/// = 12.92 * L for 0 <= L < 0.0031308
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_SRGB")]
|
||||
Srgb = ffi::ADataSpace::TRANSFER_SRGB.0,
|
||||
/// SMPTE 170M transfer.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = 1.099 * L ^ 0.45 - 0.099 for 0.018 <= L <= 1
|
||||
/// = 4.500 * L for 0 <= L < 0.018
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_SMPTE_170M")]
|
||||
Smpte170M = ffi::ADataSpace::TRANSFER_SMPTE_170M.0,
|
||||
/// Display gamma `2.2`.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = L ^ (1/2.2)
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_GAMMA2_2")]
|
||||
Gamma2_2 = ffi::ADataSpace::TRANSFER_GAMMA2_2.0,
|
||||
/// Display gamma `2.6`.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = L ^ (1/2.6)
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_GAMMA2_6")]
|
||||
Gamma2_6 = ffi::ADataSpace::TRANSFER_GAMMA2_6.0,
|
||||
/// Display gamma `2.8`.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = L ^ (1/2.8)
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L <= 1` for conventional colorimetry
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_GAMMA2_8")]
|
||||
Gamma2_8 = ffi::ADataSpace::TRANSFER_GAMMA2_8.0,
|
||||
/// SMPTE ST 2084 (Dolby Perceptual Quantizer).
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
|
||||
/// c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
|
||||
/// c2 = 32 * 2413 / 4096 = 18.8515625
|
||||
/// c3 = 32 * 2392 / 4096 = 18.6875
|
||||
/// m = 128 * 2523 / 4096 = 78.84375
|
||||
/// n = 0.25 * 2610 / 4096 = 0.1593017578125
|
||||
/// ```
|
||||
/// - `L`: luminance of image 0 <= L <= 1 for HDR colorimetry.
|
||||
/// `L = 1` corresponds to `10000 cd/m2`
|
||||
#[doc(alias = "TRANSFER_ST2084")]
|
||||
St2084 = ffi::ADataSpace::TRANSFER_ST2084.0,
|
||||
/// ARIB STD-B67 Hybrid Log Gamma.
|
||||
///
|
||||
/// Transfer characteristic curve:
|
||||
/// ```ignore
|
||||
/// E = r * L^0.5 for 0 <= L <= 1
|
||||
/// = a * ln(L - b) + c for 1 < L
|
||||
/// a = 0.17883277
|
||||
/// b = 0.28466892
|
||||
/// c = 0.55991073
|
||||
/// r = 0.5
|
||||
/// ```
|
||||
/// - `L`: luminance of image `0 <= L` for HDR colorimetry.
|
||||
/// `L = 1` corresponds to reference white level of `100 cd/m2`
|
||||
/// - `E`: corresponding electrical signal
|
||||
#[doc(alias = "TRANSFER_HLG")]
|
||||
HLG = ffi::ADataSpace::TRANSFER_HLG.0,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
/// Range aspect.
|
||||
///
|
||||
/// Defines the range of values corresponding to the unit range of `0-1`. This is defined for
|
||||
/// `YCbCr` only, but can be expanded to `RGB` space.
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "RANGE_MASK")]
|
||||
#[non_exhaustive]
|
||||
pub enum DataSpaceRange {
|
||||
/// Range is unknown or are determined by the application. Implementations shall use the
|
||||
/// following suggested ranges:
|
||||
///
|
||||
/// - All YCbCr formats: limited range.
|
||||
/// - All RGB or RGBA formats (including RAW and Bayer): full range.
|
||||
/// - All Y formats: full range
|
||||
///
|
||||
/// For all other formats range is undefined, and implementations should use an appropriate
|
||||
/// range for the data represented.
|
||||
#[doc(alias = "RANGE_UNSPECIFIED")]
|
||||
Unspecified = ffi::ADataSpace::RANGE_UNSPECIFIED.0,
|
||||
/// Full range uses all values for `Y`, `Cb` and `Cr` from `0` to `2^b-1`, where `b` is the bit
|
||||
/// depth of the color format.
|
||||
#[doc(alias = "RANGE_FULL")]
|
||||
Full = ffi::ADataSpace::RANGE_FULL.0,
|
||||
/// Limited range uses values `16/256*2^b` to `235/256*2^b` for `Y`, and `1/16*2^b` to
|
||||
/// `15/16*2^b` for `Cb`, `Cr`, `R`, `G` and `B`, where `b` is the bit depth of the color
|
||||
/// format.
|
||||
///
|
||||
/// E.g. For 8-bit-depth formats: Luma (`Y`) samples should range from `16` to `235`, inclusive
|
||||
/// Chroma `(Cb, Cr)` samples should range from `16` to `240`, inclusive.
|
||||
///
|
||||
/// For 10-bit-depth formats: Luma (`Y`) samples should range from `64` to `940`, inclusive
|
||||
/// Chroma `(Cb, Cr)` samples should range from `64` to `960`, inclusive.
|
||||
#[doc(alias = "RANGE_LIMITED")]
|
||||
Limited = ffi::ADataSpace::RANGE_LIMITED.0,
|
||||
/// Extended range is used for `scRGB`.
|
||||
///
|
||||
/// Intended for use with floating point pixel formats. `[0.0 - 1.0]` is the standard `sRGB`
|
||||
/// space. Values outside the range `0.0 - 1.0` can encode color outside the `sRGB` gamut. Used
|
||||
/// to blend / merge multiple dataspaces on a single display.
|
||||
#[doc(alias = "RANGE_EXTENDED")]
|
||||
Extended = ffi::ADataSpace::RANGE_EXTENDED.0,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
+1583
File diff suppressed because it is too large
Load Diff
+552
@@ -0,0 +1,552 @@
|
||||
//! Bindings for [`AFont`], [`AFontMatcher`], and [`ASystemFontIterator`]
|
||||
//!
|
||||
//! [`AFont`]: https://developer.android.com/ndk/reference/group/font
|
||||
//! [`AFontMatcher`]: https://developer.android.com/ndk/reference/group/font#afontmatcher_create
|
||||
//! [`ASystemFontIterator`]: https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open
|
||||
|
||||
#![cfg(feature = "api-level-29")]
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::{CStr, OsStr};
|
||||
use std::fmt::{self, Write};
|
||||
use std::os::unix::prelude::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use num_enum::IntoPrimitive;
|
||||
|
||||
/// An integer holding a valid font weight value between 1 and 1000.
|
||||
///
|
||||
/// See the [`Font::weight`] definition for more details.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FontWeight(u16);
|
||||
|
||||
impl FontWeight {
|
||||
pub const fn new(value: u16) -> Result<Self, FontWeightValueError> {
|
||||
if Self::MIN.0 <= value && value <= Self::MAX.0 {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(FontWeightValueError(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to_u16(self) -> u16 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// The minimum value for the font weight value. Unlike [`ffi::AFONT_WEIGHT_MIN`] being `0`,
|
||||
/// [`FontWeight::MIN`] is `1` to make the `MIN..MAX` range be inclusive, keeping consistency
|
||||
/// between [`FontWeight`] and other types like `std::num::NonZeroU*`.
|
||||
pub const MIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MIN as u16 + 1);
|
||||
|
||||
/// A font weight value for the thin weight.
|
||||
pub const THIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_THIN as u16);
|
||||
|
||||
/// A font weight value for the extra-light weight.
|
||||
pub const EXTRA_LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_LIGHT as u16);
|
||||
|
||||
/// A font weight value for the light weight.
|
||||
pub const LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_LIGHT as u16);
|
||||
|
||||
/// A font weight value for the normal weight.
|
||||
pub const NORMAL: FontWeight = FontWeight(ffi::AFONT_WEIGHT_NORMAL as u16);
|
||||
|
||||
/// A font weight value for the medium weight.
|
||||
pub const MEDIUM: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MEDIUM as u16);
|
||||
|
||||
/// A font weight value for the semi-bold weight.
|
||||
pub const SEMI_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_SEMI_BOLD as u16);
|
||||
|
||||
/// A font weight value for the bold weight.
|
||||
pub const BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BOLD as u16);
|
||||
|
||||
/// A font weight value for the extra-bold weight.
|
||||
pub const EXTRA_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_BOLD as u16);
|
||||
|
||||
/// A font weight value for the black weight.
|
||||
pub const BLACK: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BLACK as u16);
|
||||
|
||||
/// The maximum value for the font weight value.
|
||||
pub const MAX: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MAX as u16);
|
||||
}
|
||||
|
||||
impl fmt::Display for FontWeight {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match *self {
|
||||
FontWeight::THIN => "Thin",
|
||||
FontWeight::EXTRA_LIGHT => "Extra Light (Ultra Light)",
|
||||
FontWeight::LIGHT => "Light",
|
||||
FontWeight::NORMAL => "Normal (Regular)",
|
||||
FontWeight::MEDIUM => "Medium",
|
||||
FontWeight::SEMI_BOLD => "Semi Bold (Demi Bold)",
|
||||
FontWeight::BOLD => "Bold",
|
||||
FontWeight::EXTRA_BOLD => "Extra Bold (Ultra Bold)",
|
||||
FontWeight::BLACK => "Black (Heavy)",
|
||||
_ => return writeln!(f, "{}", self.0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when an invalid font weight value is passed.
|
||||
#[derive(Debug)]
|
||||
pub struct FontWeightValueError(());
|
||||
|
||||
impl fmt::Display for FontWeightValueError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.write_str("font weight must be positive and less than or equal to 1000")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FontWeightValueError {}
|
||||
|
||||
impl TryFrom<u16> for FontWeight {
|
||||
type Error = FontWeightValueError;
|
||||
|
||||
fn try_from(value: u16) -> Result<Self, Self::Error> {
|
||||
FontWeight::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 4-byte integer representing an OpenType axis tag.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AxisTag(u32);
|
||||
|
||||
impl AxisTag {
|
||||
/// Checks whether the given 4-byte array can construct a valid axis tag and returns
|
||||
/// [`Ok(AxisTag)`] if the array is valid.
|
||||
///
|
||||
/// Each byte in a tag must be in the range 0x20 to 0x7E. A space character cannot be followed
|
||||
/// by a non-space character. A tag must have one to four non-space characters. See the
|
||||
/// [OpenType spec] for more details.
|
||||
///
|
||||
/// [OpenType spec]: https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
|
||||
pub const fn from_be_bytes_checked(value: [u8; 4]) -> Result<Self, AxisTagValueError> {
|
||||
// Each byte in a tag must be in the range 0x20 to 0x7E.
|
||||
macro_rules! check_byte_range {
|
||||
($($e:expr)+) => {
|
||||
$(
|
||||
if !(value[$e] as char).is_ascii_graphic() && value[$e] != b' ' {
|
||||
return Err(AxisTagValueError::InvalidCharacter);
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
check_byte_range!(0 1 2 3);
|
||||
|
||||
if value[0] == b' ' {
|
||||
return Err(
|
||||
if value[1] == b' ' && value[2] == b' ' && value[3] == b' ' {
|
||||
// A tag must have one to four non-space characters.
|
||||
AxisTagValueError::EmptyTag
|
||||
} else {
|
||||
// A space character cannot be followed by a non-space character.
|
||||
AxisTagValueError::InvalidSpacePadding
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
macro_rules! check_if_valid {
|
||||
($e:expr ; $($f:expr)+) => {
|
||||
if value[$e] == b' ' {
|
||||
return if true $(&& value[$f] == b' ')+ {
|
||||
Ok(Self(u32::from_be_bytes(value)))
|
||||
} else {
|
||||
// A space character cannot be followed by a non-space character.
|
||||
Err(AxisTagValueError::InvalidSpacePadding)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
check_if_valid!(1; 2 3);
|
||||
check_if_valid!(2; 3);
|
||||
|
||||
// Whether or not value[3] is b' ', value is a valid axis tag.
|
||||
Ok(Self(u32::from_be_bytes(value)))
|
||||
}
|
||||
|
||||
/// Checks whether the given 4-byte array can construct a valid axis tag and returns
|
||||
/// [`Ok(AxisTag)`] if the array is valid.
|
||||
///
|
||||
/// See [`AxisTag::from_be()`] for more details.
|
||||
pub const fn from_be_checked(value: u32) -> Result<Self, AxisTagValueError> {
|
||||
Self::from_be_bytes_checked(value.to_be_bytes())
|
||||
}
|
||||
|
||||
/// Construct an axis tag from the given 4-byte array. If the resulting axis tag is invalid,
|
||||
/// this function panics.
|
||||
///
|
||||
/// See [`AxisTag::from_be()`] for more details.
|
||||
pub const fn from_be_bytes(value: [u8; 4]) -> Self {
|
||||
Self::unwrap_result(Self::from_be_bytes_checked(value))
|
||||
}
|
||||
|
||||
/// Construct an axis tag from the given 4-byte integer. If the resulting axis tag is invalid,
|
||||
/// this function panics.
|
||||
///
|
||||
/// See [`AxisTag::from_be()`] for more details.
|
||||
pub const fn from_be(value: u32) -> Self {
|
||||
Self::unwrap_result(Self::from_be_checked(value))
|
||||
}
|
||||
|
||||
/// const-version of [`Result::unwrap`]. Should be removed when [`Option::unwrap`] or
|
||||
/// [`Result::unwrap`] become `const`-stable.
|
||||
const fn unwrap_result(result: Result<Self, AxisTagValueError>) -> Self {
|
||||
match result {
|
||||
Ok(t) => t,
|
||||
Err(e) => panic!("{}", e.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn to_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub const fn to_be_bytes(self) -> [u8; 4] {
|
||||
self.0.to_be_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AxisTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let bytes = self.to_be_bytes();
|
||||
f.write_char(bytes[0] as char)?;
|
||||
f.write_char(bytes[1] as char)?;
|
||||
f.write_char(bytes[2] as char)?;
|
||||
f.write_char(bytes[3] as char)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AxisTag {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "AxisTag({} {:#x})", self, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when an invalid axis tag value is passed.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum AxisTagValueError {
|
||||
/// There is a byte not in the range 0x20 to 0x7E.
|
||||
InvalidCharacter,
|
||||
/// There is a space character followed by a non-space character.
|
||||
InvalidSpacePadding,
|
||||
/// The tag only consists of space characters.
|
||||
EmptyTag,
|
||||
}
|
||||
|
||||
impl AxisTagValueError {
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::InvalidCharacter => "each byte in an axis tag must be in the range 0x20 to 0x7E",
|
||||
Self::InvalidSpacePadding => {
|
||||
"a space character cannot be followed by a non-space character"
|
||||
}
|
||||
Self::EmptyTag => "a tag must have one to four non-space characters",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AxisTagValueError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for AxisTagValueError {}
|
||||
|
||||
/// A native [`AFont *`]
|
||||
///
|
||||
/// [`AFont *`]: https://developer.android.com/ndk/reference/group/font
|
||||
#[derive(Debug)]
|
||||
pub struct Font {
|
||||
ptr: NonNull<ffi::AFont>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Assumes ownership of `ptr`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid owning pointer to an Android [`ffi::AFont`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AFont>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns s the pointer to the native [`ffi::AFont`].
|
||||
pub fn ptr(&self) -> NonNull<ffi::AFont> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns a count of font variation settings associated with the current font.
|
||||
///
|
||||
/// The font variation settings are provided as multiple tag-value pairs.
|
||||
///
|
||||
/// For example, bold italic font may have following font variation settings: `'wght' 700`,
|
||||
/// `'slnt' -12`. In this case, [`Font::axis_count()`] returns `2` and [`Font::axis_tag_at()`] and
|
||||
/// [`Font::axis_value_at()`] return those variation names and the corresponding values.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ndk::font::Font;
|
||||
///
|
||||
/// let font: Font = todo!();
|
||||
/// for idx in 0..font.axis_count() {
|
||||
/// log::debug!("{}: {}", font.axis_tag_at(idx), font.axis_value_at(idx));
|
||||
/// }
|
||||
/// // Output:
|
||||
/// // wght: 700
|
||||
/// // slnt: -12
|
||||
/// ```
|
||||
pub fn axis_count(&self) -> usize {
|
||||
unsafe { ffi::AFont_getAxisCount(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Returns an OpenType axis tag associated with the current font.
|
||||
///
|
||||
/// See [`Font::axis_count()`] for more details.
|
||||
pub fn axis_tag_at(&self, idx: usize) -> AxisTag {
|
||||
// Android returns Axis Tag in big-endian.
|
||||
// See https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/native/android/system_fonts.cpp;l=197 for details
|
||||
AxisTag(unsafe { ffi::AFont_getAxisTag(self.ptr.as_ptr(), idx as u32) })
|
||||
}
|
||||
|
||||
/// Returns an OpenType axis value associated with the current font.
|
||||
///
|
||||
/// See [`Font::axis_count()`] for more details.
|
||||
pub fn axis_value_at(&self, idx: usize) -> f32 {
|
||||
unsafe { ffi::AFont_getAxisValue(self.ptr.as_ptr(), idx as u32) }
|
||||
}
|
||||
|
||||
/// Returns a font collection index value associated with the current font.
|
||||
///
|
||||
/// In case the target font file is a font collection (e.g. `.ttc` or `.otc`), this returns a
|
||||
/// non-negative value as a font offset in the collection. This always returns 0 if the target
|
||||
/// font file is a regular font.
|
||||
pub fn collection_index(&self) -> usize {
|
||||
unsafe { ffi::AFont_getCollectionIndex(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Returns an absolute path to the current font file.
|
||||
///
|
||||
/// Here is a list of font formats returned by this method:
|
||||
///
|
||||
/// * OpenType
|
||||
/// * OpenType Font Collection
|
||||
/// * TrueType
|
||||
/// * TrueType Collection
|
||||
///
|
||||
/// The file extension could be one of `*.otf`, `*.ttf`, `*.otc` or `*.ttc`.
|
||||
/// The font file specified by the returned path is guaranteed to be openable with `O_RDONLY`.
|
||||
pub fn path(&self) -> &Path {
|
||||
let path = unsafe { CStr::from_ptr(ffi::AFont_getFontFilePath(self.ptr.as_ptr())) };
|
||||
OsStr::from_bytes(path.to_bytes()).as_ref()
|
||||
}
|
||||
|
||||
/// Returns an IETF BCP47 compliant language tag associated with the current font.
|
||||
///
|
||||
/// For information about IETF BCP47, read [`Locale.forLanguageTag(java.lang.String)`].
|
||||
///
|
||||
/// [`Locale.forLanguageTag(java.lang.String)`]: https://developer.android.com/reference/java/util/Locale.html#forLanguageTag(java.lang.String)
|
||||
pub fn locale(&self) -> Option<&CStr> {
|
||||
let ptr = unsafe { ffi::AFont_getLocale(self.ptr.as_ptr()) };
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { CStr::from_ptr(ptr) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a weight value associated with the current font.
|
||||
///
|
||||
/// The weight values are positive and less than or equal to 1000. Here are pairs of the common
|
||||
/// names and their values.
|
||||
///
|
||||
/// | Value | Name | NDK Definition |
|
||||
/// | ----- | ------------------------- | --------------------------- |
|
||||
/// | 100 | Thin | [`FontWeight::THIN`] |
|
||||
/// | 200 | Extra Light (Ultra Light) | [`FontWeight::EXTRA_LIGHT`] |
|
||||
/// | 300 | Light | [`FontWeight::LIGHT`] |
|
||||
/// | 400 | Normal (Regular) | [`FontWeight::NORMAL`] |
|
||||
/// | 500 | Medium | [`FontWeight::MEDIUM`] |
|
||||
/// | 600 | Semi Bold (Demi Bold) | [`FontWeight::SEMI_BOLD`] |
|
||||
/// | 700 | Bold | [`FontWeight::BOLD`] |
|
||||
/// | 800 | Extra Bold (Ultra Bold) | [`FontWeight::EXTRA_BOLD`] |
|
||||
/// | 900 | Black (Heavy) | [`FontWeight::BLACK`] |
|
||||
pub fn weight(&self) -> FontWeight {
|
||||
FontWeight(unsafe { ffi::AFont_getWeight(self.ptr.as_ptr()) })
|
||||
}
|
||||
|
||||
/// Returns [`true`] if the current font is italic, otherwise returns [`false`].
|
||||
pub fn is_italic(&self) -> bool {
|
||||
unsafe { ffi::AFont_isItalic(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Font {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AFont_close(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Corresponds to [`AFAMILY_VARIANT_*`].
|
||||
///
|
||||
/// [`AFAMILY_VARIANT_*`]: https://developer.android.com/ndk/reference/group/font#group___font_1gga96a58e29e8dbf2b5bdeb775cba46556ea662aafc7016e35d6758da93416fc0833
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
pub enum FamilyVariant {
|
||||
/// A family variant value for the compact font family variant.
|
||||
/// The compact font family has Latin-based vertical metrics.
|
||||
Compact = ffi::AFAMILY_VARIANT_COMPACT,
|
||||
/// A family variant value for the system default variant.
|
||||
Default = ffi::AFAMILY_VARIANT_DEFAULT,
|
||||
/// A family variant value for the elegant font family variant.
|
||||
/// The elegant font family may have larger vertical metrics than Latin font.
|
||||
Elegant = ffi::AFAMILY_VARIANT_ELEGANT,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// A native [`AFontMatcher *`]
|
||||
///
|
||||
/// [`AFontMatcher *`]: https://developer.android.com/ndk/reference/group/font#afontmatcher_create
|
||||
#[derive(Debug)]
|
||||
pub struct FontMatcher {
|
||||
ptr: NonNull<ffi::AFontMatcher>,
|
||||
}
|
||||
|
||||
impl FontMatcher {
|
||||
/// Assumes ownership of `ptr`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid owning pointer to an Android [`ffi::AFontMatcher`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AFontMatcher>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns s the pointer to the native [`ffi::AFontMatcher`].
|
||||
pub fn ptr(&self) -> NonNull<ffi::AFontMatcher> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Creates a new [`FontMatcher`] object. [`FontMatcher`] selects the best font from the
|
||||
/// parameters set by the user.
|
||||
pub fn new() -> Self {
|
||||
let ptr = NonNull::new(unsafe { ffi::AFontMatcher_create() })
|
||||
.expect("AFontMatcher_create returned NULL");
|
||||
unsafe { FontMatcher::from_ptr(ptr) }
|
||||
}
|
||||
|
||||
/// Performs the matching from the generic font family for the text and select one font.
|
||||
///
|
||||
/// For more information about generic font families, please read the
|
||||
/// [W3C spec](https://www.w3.org/TR/css-fonts-4/#generic-font-families).
|
||||
///
|
||||
/// Even if no font can render the given text, this function will return a non-null result for
|
||||
/// drawing Tofu character.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `family_name`: A font family name.
|
||||
/// - `text`: A UTF-16 encoded text buffer to be rendered. If an empty string is given, this
|
||||
/// function will panic.
|
||||
/// - `run_length_out`: Set this to [`Some`] if you want to get the length of the text run with
|
||||
/// the font returned.
|
||||
pub fn match_font(
|
||||
&mut self,
|
||||
family_name: &CStr,
|
||||
text: &[u16],
|
||||
run_length_out: Option<&mut u32>,
|
||||
) -> Font {
|
||||
if text.is_empty() {
|
||||
panic!("text is empty");
|
||||
}
|
||||
unsafe {
|
||||
Font::from_ptr(
|
||||
NonNull::new(ffi::AFontMatcher_match(
|
||||
self.ptr.as_ptr(),
|
||||
family_name.as_ptr(),
|
||||
text.as_ptr(),
|
||||
text.len() as _,
|
||||
run_length_out.map_or(std::ptr::null_mut(), |u| u),
|
||||
))
|
||||
.expect("AFontMatcher_match returned NULL"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the family variant of the font to be matched.
|
||||
///
|
||||
/// If this function is not called, the match is performed with [`FamilyVariant::Default`].
|
||||
pub fn set_family_variant(&mut self, family_variant: FamilyVariant) {
|
||||
unsafe { ffi::AFontMatcher_setFamilyVariant(self.ptr.as_ptr(), family_variant.into()) }
|
||||
}
|
||||
|
||||
/// Sets the locale of the font to be matched.
|
||||
///
|
||||
/// If this function is not called, the match is performed with an empty locale list.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `language_tags`: comma separated IETF BCP47 compliant language tags.
|
||||
pub fn set_locales(&mut self, language_tags: &CStr) {
|
||||
unsafe { ffi::AFontMatcher_setLocales(self.ptr.as_ptr(), language_tags.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Sets the style of the font to be matched.
|
||||
///
|
||||
/// If this function is not called, the match is performed with [`FontWeight::NORMAL`] with non-italic style.
|
||||
pub fn set_style(&mut self, weight: FontWeight, italic: bool) {
|
||||
unsafe { ffi::AFontMatcher_setStyle(self.ptr.as_ptr(), weight.to_u16(), italic) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FontMatcher {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AFontMatcher_destroy(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`ASystemFontIterator *`]
|
||||
///
|
||||
/// [`ASystemFontIterator *`]: https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open
|
||||
#[derive(Debug)]
|
||||
pub struct SystemFontIterator {
|
||||
ptr: NonNull<ffi::ASystemFontIterator>,
|
||||
}
|
||||
|
||||
impl SystemFontIterator {
|
||||
/// Assumes ownership of `ptr`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid owning pointer to an Android [`ffi::ASystemFontIterator`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::ASystemFontIterator>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns the pointer to the native [`ffi::ASystemFontIterator`].
|
||||
pub fn ptr(&self) -> NonNull<ffi::ASystemFontIterator> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Creates a system font iterator.
|
||||
pub fn new() -> Option<Self> {
|
||||
NonNull::new(unsafe { ffi::ASystemFontIterator_open() })
|
||||
.map(|p| unsafe { SystemFontIterator::from_ptr(p) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SystemFontIterator {
|
||||
type Item = Font;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
NonNull::new(unsafe { ffi::ASystemFontIterator_next(self.ptr.as_ptr()) })
|
||||
.map(|p| unsafe { Font::from_ptr(p) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SystemFontIterator {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ASystemFontIterator_close(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,640 @@
|
||||
//! Bindings for [`AHardwareBuffer`]
|
||||
//!
|
||||
//! [`AHardwareBuffer`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer
|
||||
|
||||
#![cfg(feature = "api-level-26")]
|
||||
|
||||
use std::{
|
||||
io::Result,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
os::{
|
||||
fd::{AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
|
||||
raw::c_void,
|
||||
},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
|
||||
use super::{hardware_buffer_format::HardwareBufferFormat, utils::status_to_io_result};
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Buffer usage flags, specifying how the buffer will be accessed.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
#[doc(alias = "AHardwareBuffer_UsageFlags")]
|
||||
pub struct HardwareBufferUsage : u64 {
|
||||
/// The buffer will never be locked for direct CPU reads using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings is still allowed.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_NEVER")]
|
||||
const CPU_READ_NEVER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_NEVER.0;
|
||||
/// The buffer will sometimes be locked for direct CPU reads using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings does not require the presence of this flag.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_RARELY")]
|
||||
const CPU_READ_RARELY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_RARELY.0;
|
||||
/// The buffer will often be locked for direct CPU reads using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that reading the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings does not require the presence of this flag.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN")]
|
||||
const CPU_READ_OFTEN = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN.0;
|
||||
/// CPU read value mask.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_MASK")]
|
||||
const CPU_READ_MASK = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_MASK.0;
|
||||
|
||||
/// The buffer will never be locked for direct CPU writes using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings is still allowed.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER")]
|
||||
const CPU_WRITE_NEVER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER.0;
|
||||
/// The buffer will sometimes be locked for direct CPU writes using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings does not require the presence of this flag.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY")]
|
||||
const CPU_WRITE_RARELY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY.0;
|
||||
/// The buffer will often be locked for direct CPU writes using the
|
||||
/// [`HardwareBuffer::lock()`] function. Note that writing the buffer using OpenGL or Vulkan
|
||||
/// functions or memory mappings does not require the presence of this flag.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN")]
|
||||
const CPU_WRITE_OFTEN = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN.0;
|
||||
/// CPU write value mask.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK")]
|
||||
const CPU_WRITE_MASK = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK.0;
|
||||
|
||||
/// The buffer will be read from by the GPU as a texture.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE")]
|
||||
const GPU_SAMPLED_IMAGE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE.0;
|
||||
/// The buffer will be written to by the GPU as a framebuffer attachment.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER")]
|
||||
const GPU_FRAMEBUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER.0;
|
||||
/// The buffer will be written to by the GPU as a framebuffer attachment.
|
||||
///
|
||||
/// Note that the name of this flag is somewhat misleading: it does not imply that the
|
||||
/// buffer contains a color format. A buffer with depth or stencil format that will be
|
||||
/// used as a framebuffer attachment should also have this flag. Use the equivalent flag
|
||||
/// [`HardwareBufferusage::GPU_FRAMEBUFFER`] to avoid this confusion.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT")]
|
||||
const GPU_COLOR_OUTPUT = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT.0;
|
||||
/// The buffer will be used as a composer HAL overlay layer.
|
||||
///
|
||||
/// This flag is currently only needed when using [`SurfaceTransaction::set_buffer()`] to
|
||||
/// set a buffer. In all other cases, the framework adds this flag internally to buffers
|
||||
/// that could be presented in a composer overlay. [`SurfaceTransaction::set_buffer()`]
|
||||
/// is special because it uses buffers allocated directly through
|
||||
/// [`HardwareBuffer::allocate()`] instead of buffers allocated by the framework.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY")]
|
||||
const COMPOSER_OVERLAY = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY.0;
|
||||
/// The buffer is protected from direct CPU access or being read by non-secure hardware,
|
||||
/// such as video encoders.
|
||||
///
|
||||
/// This flag is incompatible with CPU read and write flags. It is mainly used when handling
|
||||
/// DRM video. Refer to the EGL extension [`EGL_EXT_protected_content`] and GL extension
|
||||
/// [`GL_EXT_protected_textures`] for more information on how these buffers are expected
|
||||
/// to behave.
|
||||
///
|
||||
/// [`EGL_EXT_protected_content`]: https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_protected_content.txt
|
||||
/// [`GL_EXT_protected_textures`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_protected_textures.txt
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT")]
|
||||
const PROTECTED_CONTENT = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT.0;
|
||||
/// The buffer will be read by a hardware video encoder.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VIDEO_ENCODE")]
|
||||
const VIDEO_ENCODE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VIDEO_ENCODE.0;
|
||||
/// The buffer will be used for direct writes from sensors. When this flag is present, the
|
||||
/// format must be [`HardwareBufferFormat::Blob`].
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA")]
|
||||
const SENSOR_DIRECT_DATA = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_SENSOR_DIRECT_DATA.0;
|
||||
/// The buffer will be used as a shader storage or uniform buffer object. When this flag is
|
||||
/// present, the format must be [`HardwareBufferFormat::Blob`].
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER")]
|
||||
const GPU_DATA_BUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER.0;
|
||||
/// The buffer will be used as a cube map texture. When this flag is present, the buffer
|
||||
/// must have a layer count that is a multiple of 6. Note that buffers with this flag must
|
||||
/// be bound to OpenGL textures using the extension [`GL_EXT_EGL_image_storage`] instead
|
||||
/// of [`GL_KHR_EGL_image`].
|
||||
///
|
||||
/// [`GL_EXT_EGL_image_storage`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt
|
||||
// TODO: This extension only exists for VG. Reported at https://issuetracker.google.com/issues/300602767#comment16
|
||||
/// [`GL_KHR_EGL_image`]: https://registry.khronos.org/OpenVG/extensions/KHR/VG_KHR_EGL_image.txt
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP")]
|
||||
const GPU_CUBE_MAP = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_CUBE_MAP.0;
|
||||
/// The buffer contains a complete mipmap hierarchy. Note that buffers with this flag must
|
||||
/// be bound to OpenGL textures using the extension [`GL_EXT_EGL_image_storage`] instead
|
||||
/// of [`GL_KHR_EGL_image`].
|
||||
///
|
||||
/// [`GL_EXT_EGL_image_storage`]: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt
|
||||
// TODO: This extension only exists for VG. Reported at https://issuetracker.google.com/issues/300602767#comment16
|
||||
/// [`GL_KHR_EGL_image`]: https://registry.khronos.org/OpenVG/extensions/KHR/VG_KHR_EGL_image.txt
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE")]
|
||||
const GPU_MIPMAP_COMPLETE = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_GPU_MIPMAP_COMPLETE.0;
|
||||
|
||||
// TODO: Only available in a newer NDK
|
||||
// /// Usage: The buffer is used for front-buffer rendering. When front-buffering rendering
|
||||
// /// is specified, different usages may adjust their behavior as a result. For example, when
|
||||
// /// used as [`HardwareBufferFormat::GPU_COLOR_OUTPUT`] the buffer will behave similar to a
|
||||
// /// single-buffered window. When used with [`HardwareBufferFormat::COMPOSER_OVERLAY`], the
|
||||
// /// system will try to prioritize the buffer receiving an overlay plane & avoid caching it
|
||||
// /// in intermediate composition buffers.
|
||||
// #[doc(alias = "AHARDWAREBUFFER_USAGE_FRONT_BUFFER")]
|
||||
// const USAGE_FRONT_BUFFER = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_FRONT_BUFFER.0;
|
||||
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_0")]
|
||||
const VENDOR_0 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_0.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_1")]
|
||||
const VENDOR_1 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_1.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_2")]
|
||||
const VENDOR_2 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_2.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_3")]
|
||||
const VENDOR_3 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_3.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_4")]
|
||||
const VENDOR_4 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_4.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_5")]
|
||||
const VENDOR_5 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_5.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_6")]
|
||||
const VENDOR_6 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_6.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_7")]
|
||||
const VENDOR_7 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_7.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_8")]
|
||||
const VENDOR_8 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_8.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_9")]
|
||||
const VENDOR_9 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_9.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_10")]
|
||||
const VENDOR_10 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_10.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_11")]
|
||||
const VENDOR_11 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_11.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_12")]
|
||||
const VENDOR_12 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_12.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_13")]
|
||||
const VENDOR_13 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_13.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_14")]
|
||||
const VENDOR_14 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_14.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_15")]
|
||||
const VENDOR_15 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_15.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_16")]
|
||||
const VENDOR_16 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_16.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_17")]
|
||||
const VENDOR_17 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_17.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_18")]
|
||||
const VENDOR_18 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_18.0;
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_VENDOR_19")]
|
||||
const VENDOR_19 = ffi::AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_VENDOR_19.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareBufferUsage {
|
||||
/// Helper to read [`HardwareBufferUsage::CPU_READ_MASK`] values.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_READ_MASK")]
|
||||
pub fn cpu_read(self) -> HardwareBufferUsage {
|
||||
self.intersection(Self::CPU_READ_MASK)
|
||||
}
|
||||
|
||||
/// Helper to read [`HardwareBufferUsage::CPU_WRITE_MASK`] values.
|
||||
#[doc(alias = "AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK")]
|
||||
pub fn cpu_write(self) -> HardwareBufferUsage {
|
||||
self.intersection(Self::CPU_WRITE_MASK)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Rect = ffi::ARect;
|
||||
|
||||
fn construct<T>(with_ptr: impl FnOnce(*mut T) -> i32) -> Result<T> {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let status = with_ptr(result.as_mut_ptr());
|
||||
status_to_io_result(status).map(|()| unsafe { result.assume_init() })
|
||||
}
|
||||
|
||||
/// A native [`AHardwareBuffer *`]
|
||||
///
|
||||
/// [`HardwareBuffer`] objects represent chunks of memory that can be accessed by various hardware
|
||||
/// components in the system.
|
||||
///
|
||||
/// It can be easily converted to the Java counterpart [`android.hardware.HardwareBuffer`] and
|
||||
/// passed between processes using Binder. All operations involving [`HardwareBuffer`] and
|
||||
/// [`android.hardware.HardwareBuffer`] are zero-copy, i.e., passing [`HardwareBuffer`] to another
|
||||
/// process creates a shared view of the same region of memory.
|
||||
///
|
||||
/// [`HardwareBuffer`] can be bound to EGL/OpenGL and Vulkan primitives. For EGL, use the extension
|
||||
/// function [`eglGetNativeClientBufferANDROID`] to obtain an `EGLClientBuffer` and pass it
|
||||
/// directly to [`eglCreateImageKHR`]. Refer to the EGL extensions
|
||||
/// [`EGL_ANDROID_get_native_client_buffer`] and [`EGL_ANDROID_image_native_buffer`] for more
|
||||
/// information. In Vulkan, the contents of the [`HardwareBuffer`] can be accessed as [external
|
||||
/// memory]. See the [`VK_ANDROID_external_memory_android_hardware_buffer`] extension for details.
|
||||
///
|
||||
/// [`AHardwareBuffer *`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer
|
||||
/// [`android.hardware.HardwareBuffer`]: https://developer.android.com/reference/android/hardware/HardwareBuffer
|
||||
/// [`eglGetNativeClientBufferANDROID`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt
|
||||
/// [`eglCreateImageKHR`]: https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_image_base.txt
|
||||
/// [`EGL_ANDROID_get_native_client_buffer`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_get_native_client_buffer.txt
|
||||
/// [`EGL_ANDROID_image_native_buffer`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
|
||||
/// [external memory]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_KHR_external_memory.html
|
||||
/// [`VK_ANDROID_external_memory_android_hardware_buffer`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_ANDROID_external_memory_android_hardware_buffer.html
|
||||
#[derive(Debug)]
|
||||
pub struct HardwareBuffer {
|
||||
inner: NonNull<ffi::AHardwareBuffer>,
|
||||
}
|
||||
|
||||
impl HardwareBuffer {
|
||||
/// Create an _unowned_ [`HardwareBuffer`] from a native pointer
|
||||
///
|
||||
/// To wrap a strong reference (that is `release`d on [`Drop`]), call
|
||||
/// [`HardwareBufferRef::from_ptr()`] instead.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it is a valid pointer to an NDK
|
||||
/// [`ffi::AHardwareBuffer`] that is kept alive externally, or retrieve a strong reference
|
||||
/// using [`HardwareBuffer::acquire()`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AHardwareBuffer>) -> Self {
|
||||
Self { inner: ptr }
|
||||
}
|
||||
|
||||
/// Returns the underlying [`ffi::AHardwareBuffer`] pointer
|
||||
///
|
||||
/// See the top-level [`HardwareBuffer`] struct documentation for (graphics) APIs that accept
|
||||
/// this pointer.
|
||||
pub fn as_ptr(&self) -> *mut ffi::AHardwareBuffer {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
/// Allocates a buffer that matches the passed [`HardwareBufferDesc`].
|
||||
///
|
||||
/// If allocation succeeds, the buffer can be used according to the usage flags specified in
|
||||
/// its description. If a buffer is used in ways not compatible with its usage flags, the
|
||||
/// results are undefined and may include program termination.
|
||||
pub fn allocate(desc: HardwareBufferDesc) -> Result<HardwareBufferRef> {
|
||||
unsafe {
|
||||
let ptr = construct(|res| ffi::AHardwareBuffer_allocate(&desc.into_native(), res))?;
|
||||
|
||||
Ok(HardwareBufferRef::from_ptr(NonNull::new_unchecked(ptr)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`HardwareBuffer`] from JNI pointers
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that these are valid pointers to JNI objects.
|
||||
///
|
||||
/// This method does not acquire any additional reference to the AHardwareBuffer that is
|
||||
/// returned. To keep the [`HardwareBuffer`] alive after the [Java `HardwareBuffer`] object
|
||||
/// is closed, explicitly or by the garbage collector, be sure to retrieve a strong reference
|
||||
/// using [`HardwareBuffer::acquire()`].
|
||||
///
|
||||
/// [Java `HardwareBuffer`]: https://developer.android.com/reference/android/hardware/HardwareBuffer
|
||||
pub unsafe fn from_jni(env: *mut JNIEnv, hardware_buffer: jobject) -> Self {
|
||||
let ptr = ffi::AHardwareBuffer_fromHardwareBuffer(env, hardware_buffer);
|
||||
|
||||
Self::from_ptr(NonNull::new_unchecked(ptr))
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`].
|
||||
pub unsafe fn to_jni(&self, env: *mut JNIEnv) -> jobject {
|
||||
ffi::AHardwareBuffer_toHardwareBuffer(env, self.as_ptr())
|
||||
}
|
||||
|
||||
/// Return a description of the [`HardwareBuffer`] in the passed [`HardwareBufferDesc`] struct.
|
||||
pub fn describe(&self) -> HardwareBufferDesc {
|
||||
let desc = unsafe {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
ffi::AHardwareBuffer_describe(self.as_ptr(), result.as_mut_ptr());
|
||||
result.assume_init()
|
||||
};
|
||||
|
||||
HardwareBufferDesc {
|
||||
width: desc.width,
|
||||
height: desc.height,
|
||||
layers: desc.layers,
|
||||
format: i32::try_from(desc.format)
|
||||
.expect("i32->u32 overflow in HardwareBuffer::describe()")
|
||||
.into(),
|
||||
usage: HardwareBufferUsage::from_bits_retain(desc.usage),
|
||||
stride: desc.stride,
|
||||
}
|
||||
}
|
||||
|
||||
/// Test whether the given format and usage flag combination is allocatable.
|
||||
///
|
||||
/// If this function returns [`true`], it means that a buffer with the given description can
|
||||
/// be allocated on this implementation, unless resource exhaustion occurs. If this function
|
||||
/// returns [`false`], it means that the allocation of the given description will never
|
||||
/// succeed.
|
||||
///
|
||||
/// The return value of this function may depend on all fields in the description, except
|
||||
/// [`HardwareBufferDesc::stride`], which is always ignored. For example, some implementations
|
||||
/// have implementation-defined limits on texture size and layer count.
|
||||
#[cfg(feature = "api-level-29")]
|
||||
pub fn is_supported(desc: HardwareBufferDesc) -> bool {
|
||||
let res = unsafe { ffi::AHardwareBuffer_isSupported(&desc.into_native()) };
|
||||
res == 1
|
||||
}
|
||||
|
||||
/// Get the system-wide unique id for this [`HardwareBuffer`].
|
||||
#[cfg(feature = "api-level-31")]
|
||||
#[doc(alias = "AHardwareBuffer_getId")]
|
||||
pub fn id(&self) -> Result<u64> {
|
||||
construct(|res| unsafe { ffi::AHardwareBuffer_getId(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
/// Lock the [`HardwareBuffer`] for direct CPU access.
|
||||
///
|
||||
/// This function can lock the buffer for either reading or writing. It may block if the
|
||||
/// hardware needs to finish rendering, if CPU caches need to be synchronized, or possibly for
|
||||
/// other implementation-specific reasons.
|
||||
///
|
||||
/// The [`HardwareBuffer`] must have one layer, otherwise the call will fail.
|
||||
///
|
||||
/// If `fence` is not [`None`], it specifies a fence file descriptor on which to wait before
|
||||
/// locking the buffer. If it's [`None`], the caller is responsible for ensuring that writes
|
||||
/// to the buffer have completed before calling this function. Using this parameter is more
|
||||
/// efficient than waiting on the fence and then calling this function.
|
||||
///
|
||||
/// The `usage` parameter may only specify `HardwareBufferUsage::CPU_*`. If set, then the
|
||||
/// address of the buffer in virtual memory is returned. The flags must also be compatible with
|
||||
/// usage flags specified at buffer creation: if a read flag is passed, the buffer must have
|
||||
/// been created with [`HardwareBufferUsage::CPU_READ_RARELY`] or
|
||||
/// [`HardwareBufferUsage::CPU_READ_OFTEN`]. If a write flag is passed, it must have been
|
||||
/// created with [`HardwareBufferUsage::CPU_WRITE_RARELY`] or
|
||||
/// [`HardwareBufferUsage::CPU_WRITE_OFTEN`].
|
||||
///
|
||||
/// If `rect` is not [`None`], the caller promises to modify only data in the area specified by
|
||||
/// `rect`. If rect is [`None`], the caller may modify the contents of the entire buffer. The
|
||||
/// content of the buffer outside of the specified rect is NOT modified by this call.
|
||||
///
|
||||
/// It is legal for several different threads to lock a buffer for read access; none of the
|
||||
/// threads are blocked.
|
||||
///
|
||||
/// Locking a buffer simultaneously for write or read/write is undefined, but will neither
|
||||
/// terminate the process nor block the caller. This function may return an error or leave the
|
||||
/// buffer's content in an indeterminate state.
|
||||
///
|
||||
/// If the buffer has [`HardwareBufferFormat::BLOB`], it is legal lock it for reading and
|
||||
/// writing in multiple threads and/or processes simultaneously, and the contents of the buffer
|
||||
/// behave like shared memory.
|
||||
pub fn lock(
|
||||
&self,
|
||||
usage: HardwareBufferUsage,
|
||||
fence: Option<OwnedFd>,
|
||||
rect: Option<Rect>,
|
||||
) -> Result<*mut c_void> {
|
||||
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
|
||||
let rect = match rect {
|
||||
Some(rect) => &rect,
|
||||
None => std::ptr::null(),
|
||||
};
|
||||
construct(|res| unsafe {
|
||||
ffi::AHardwareBuffer_lock(self.as_ptr(), usage.bits(), fence, rect, res)
|
||||
})
|
||||
}
|
||||
|
||||
/// Lock a [`HardwareBuffer`] for direct CPU access.
|
||||
///
|
||||
/// This function is the same as the above [`lock()`][Self::lock()] function, but passes back
|
||||
/// additional information about the bytes per pixel and the bytes per stride of the locked
|
||||
/// buffer. If the bytes per pixel or bytes per stride are unknown or variable, or if the
|
||||
/// underlying mapper implementation does not support returning additional information, then
|
||||
/// this call will fail with [`std::io::Error::kind()`] = [`std::io::ErrorKind::Unsupported`].
|
||||
#[cfg(feature = "api-level-29")]
|
||||
pub fn lock_and_get_info(
|
||||
&self,
|
||||
usage: HardwareBufferUsage,
|
||||
fence: Option<OwnedFd>,
|
||||
rect: Option<Rect>,
|
||||
) -> Result<LockedPlaneInfo> {
|
||||
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
|
||||
let rect = match rect {
|
||||
Some(rect) => &rect,
|
||||
None => std::ptr::null(),
|
||||
};
|
||||
let mut virtual_address = MaybeUninit::uninit();
|
||||
let mut bytes_per_pixel = MaybeUninit::uninit();
|
||||
let mut bytes_per_stride = MaybeUninit::uninit();
|
||||
let status = unsafe {
|
||||
ffi::AHardwareBuffer_lockAndGetInfo(
|
||||
self.as_ptr(),
|
||||
usage.bits(),
|
||||
fence,
|
||||
rect,
|
||||
virtual_address.as_mut_ptr(),
|
||||
bytes_per_pixel.as_mut_ptr(),
|
||||
bytes_per_stride.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
status_to_io_result(status).map(|()| unsafe {
|
||||
LockedPlaneInfo {
|
||||
virtual_address: virtual_address.assume_init(),
|
||||
bytes_per_pixel: bytes_per_pixel.assume_init() as u32,
|
||||
bytes_per_stride: bytes_per_stride.assume_init() as u32,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Lock a potentially multi-planar [`HardwareBuffer`] for direct CPU access.
|
||||
///
|
||||
/// This function is similar to [`lock()`][Self::lock()], but can lock multi-planar formats.
|
||||
/// Note, that multi-planar should not be confused with multi-layer images, which this locking
|
||||
/// function does not support.
|
||||
///
|
||||
/// YUV formats are always represented by three separate planes of data, one for each color
|
||||
/// plane. The order of planes in the array is guaranteed such that plane #0 is always `Y`,
|
||||
/// plane #1 is always `U` (`Cb`), and plane #2 is always `V` (`Cr`). All other formats are
|
||||
/// represented by a single plane.
|
||||
///
|
||||
/// Additional information always accompanies the buffers, describing the row stride and the
|
||||
/// pixel stride for each plane.
|
||||
///
|
||||
/// In case the buffer cannot be locked, this will return zero planes.
|
||||
///
|
||||
/// See the [`lock()`][Self::lock()] documentation for all other locking semantics.
|
||||
#[cfg(feature = "api-level-29")]
|
||||
pub fn lock_planes(
|
||||
&self,
|
||||
usage: HardwareBufferUsage,
|
||||
fence: Option<OwnedFd>,
|
||||
rect: Option<Rect>,
|
||||
) -> Result<HardwareBufferPlanes> {
|
||||
let fence = fence.map_or(-1, IntoRawFd::into_raw_fd);
|
||||
let rect = match rect {
|
||||
Some(rect) => &rect,
|
||||
None => std::ptr::null(),
|
||||
};
|
||||
let planes = construct(|res| unsafe {
|
||||
ffi::AHardwareBuffer_lockPlanes(self.as_ptr(), usage.bits(), fence, rect, res)
|
||||
})?;
|
||||
|
||||
Ok(HardwareBufferPlanes {
|
||||
inner: planes,
|
||||
index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Unlock the [`HardwareBuffer`] from direct CPU access.
|
||||
///
|
||||
/// Must be called after all changes to the buffer are completed by the caller. The function
|
||||
/// will block until all work is completed. See [`unlock_async()`][Self::unlock_async()] for
|
||||
/// a non-blocking variant that returns a file descriptor to be signaled on unlocking instead.
|
||||
pub fn unlock(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), std::ptr::null_mut()) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Unlock the [`HardwareBuffer`] from direct CPU access.
|
||||
///
|
||||
/// Returns a fence file descriptor that will become signaled when unlocking is completed, or
|
||||
/// [`None`] if unlocking is already finished. The caller is responsible for closing the file
|
||||
/// descriptor once it's no longer needed. See [`unlock()`][Self::unlock()] for a variant that
|
||||
/// blocks instead.
|
||||
pub fn unlock_async(&self) -> Result<Option<OwnedFd>> {
|
||||
let fence = construct(|res| unsafe { ffi::AHardwareBuffer_unlock(self.as_ptr(), res) })?;
|
||||
Ok(match fence {
|
||||
-1 => None,
|
||||
fence => Some(unsafe { OwnedFd::from_raw_fd(fence) }),
|
||||
})
|
||||
}
|
||||
|
||||
/// Receive a [`HardwareBuffer`] from an `AF_UNIX` socket.
|
||||
///
|
||||
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
|
||||
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
|
||||
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
|
||||
pub fn recv_handle_from_unix_socket(socket_fd: BorrowedFd<'_>) -> Result<Self> {
|
||||
unsafe {
|
||||
let ptr = construct(|res| {
|
||||
ffi::AHardwareBuffer_recvHandleFromUnixSocket(socket_fd.as_raw_fd(), res)
|
||||
})?;
|
||||
|
||||
Ok(Self::from_ptr(NonNull::new_unchecked(ptr)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Send the [`HardwareBuffer`] to an `AF_UNIX` socket.
|
||||
///
|
||||
/// `AF_UNIX` sockets are wrapped by [`std::os::unix::net::UnixListener`] and
|
||||
/// [`std::os::unix::net::UnixStream`] in Rust and have a corresponding
|
||||
/// [`std::os::unix::io::AsFd::as_fd()`] implementation.
|
||||
pub fn send_handle_to_unix_socket(&self, socket_fd: BorrowedFd<'_>) -> Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::AHardwareBuffer_sendHandleToUnixSocket(self.as_ptr(), socket_fd.as_raw_fd())
|
||||
};
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Acquire a reference on the given [`HardwareBuffer`] object.
|
||||
///
|
||||
/// This prevents the object from being deleted until the last strong reference, represented
|
||||
/// by [`HardwareBufferRef`], is [`drop()`]ped.
|
||||
pub fn acquire(&self) -> HardwareBufferRef {
|
||||
unsafe {
|
||||
ffi::AHardwareBuffer_acquire(self.as_ptr());
|
||||
HardwareBufferRef::from_ptr(self.inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`HardwareBuffer`] with an owned reference, that is released when dropped.
|
||||
/// It behaves much like a strong [`std::rc::Rc`] reference.
|
||||
#[derive(Debug)]
|
||||
pub struct HardwareBufferRef {
|
||||
inner: HardwareBuffer,
|
||||
}
|
||||
|
||||
impl HardwareBufferRef {
|
||||
/// Create an _owned_ [`HardwareBuffer`] from a native pointer
|
||||
///
|
||||
/// To wrap a weak reference (that is **not** `release`d on [`Drop`]), call
|
||||
/// [`HardwareBuffer::from_ptr()`] instead.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it is a valid pointer to an NDK
|
||||
/// [`ffi::AHardwareBuffer`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AHardwareBuffer>) -> Self {
|
||||
Self {
|
||||
inner: HardwareBuffer { inner: ptr },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for HardwareBufferRef {
|
||||
type Target = HardwareBuffer;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HardwareBufferRef {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AHardwareBuffer_release(self.inner.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for HardwareBufferRef {
|
||||
fn clone(&self) -> Self {
|
||||
self.acquire()
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer description.
|
||||
///
|
||||
/// Used for allocating new buffers and querying parameters of existing ones.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HardwareBufferDesc {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub layers: u32,
|
||||
pub format: HardwareBufferFormat,
|
||||
pub usage: HardwareBufferUsage,
|
||||
pub stride: u32,
|
||||
}
|
||||
|
||||
impl HardwareBufferDesc {
|
||||
fn into_native(self) -> ffi::AHardwareBuffer_Desc {
|
||||
ffi::AHardwareBuffer_Desc {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
layers: self.layers,
|
||||
format: i32::from(self.format)
|
||||
.try_into()
|
||||
.expect("i32->u32 overflow in HardwareBufferDesc::into_native()"),
|
||||
usage: self.usage.bits(),
|
||||
stride: self.stride,
|
||||
rfu0: 0,
|
||||
rfu1: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AHardwareBuffer_Plane`]
|
||||
///
|
||||
/// Contains the same fields as [`ffi::AHardwareBuffer_Plane`].
|
||||
///
|
||||
/// [`AHardwareBuffer_Plane`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_plane
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct LockedPlaneInfo {
|
||||
pub virtual_address: *mut c_void,
|
||||
pub bytes_per_pixel: u32,
|
||||
pub bytes_per_stride: u32,
|
||||
}
|
||||
|
||||
/// Iterator over [`ffi::AHardwareBuffer_Planes`], containing a list of [`LockedPlaneInfo`].
|
||||
#[derive(Debug)]
|
||||
pub struct HardwareBufferPlanes {
|
||||
inner: ffi::AHardwareBuffer_Planes,
|
||||
index: u32,
|
||||
}
|
||||
|
||||
impl Iterator for HardwareBufferPlanes {
|
||||
type Item = LockedPlaneInfo;
|
||||
|
||||
fn next(&mut self) -> Option<LockedPlaneInfo> {
|
||||
if self.index == self.inner.planeCount {
|
||||
None
|
||||
} else {
|
||||
let plane = self.inner.planes[self.index as usize];
|
||||
self.index += 1;
|
||||
Some(LockedPlaneInfo {
|
||||
virtual_address: plane.data,
|
||||
bytes_per_pixel: plane.pixelStride,
|
||||
bytes_per_stride: plane.rowStride,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//! Bindings for [`AHardwareBuffer_Format`]
|
||||
//!
|
||||
//! [`AHardwareBuffer_Format`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_format
|
||||
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
|
||||
/// Buffer pixel formats.
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[non_exhaustive]
|
||||
pub enum HardwareBufferFormat {
|
||||
/// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBA_8888`].0.
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM")]
|
||||
R8G8B8A8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM.0 as i32,
|
||||
/// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBX_8888`].0.
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM")]
|
||||
R8G8B8X8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM")]
|
||||
R8G8B8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM.0 as i32,
|
||||
/// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGB_565`].0.
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM")]
|
||||
R5G6B5_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT")]
|
||||
R16G16B16A16_FLOAT =
|
||||
ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM")]
|
||||
R10G10B10A2_UNORM =
|
||||
ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_BLOB")]
|
||||
BLOB = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_BLOB.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_D16_UNORM")]
|
||||
D16_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D16_UNORM.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM")]
|
||||
D24_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT")]
|
||||
D24_UNORM_S8_UINT =
|
||||
ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT")]
|
||||
D32_FLOAT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT")]
|
||||
D32_FLOAT_S8_UINT =
|
||||
ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_S8_UINT")]
|
||||
S8_UINT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_S8_UINT.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420")]
|
||||
Y8Cb8Cr8_420 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AHARDWAREBUFFER_FORMAT_YCbCr_P010")]
|
||||
YCbCr_P010 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_YCbCr_P010.0 as i32,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
R8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8_UNORM.0 as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
impl HardwareBufferFormat {
|
||||
/// Returns [`None`] when there is no immediate byte size available for this format, for
|
||||
/// example on planar buffer formats.
|
||||
pub fn bytes_per_pixel(self) -> Option<usize> {
|
||||
Some(match self {
|
||||
Self::R8G8B8A8_UNORM | Self::R8G8B8X8_UNORM => 4,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::R8G8B8_UNORM => 3,
|
||||
Self::R5G6B5_UNORM => 2,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::R16G16B16A16_FLOAT => 8,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::R10G10B10A2_UNORM => 4,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::BLOB => 1,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::D16_UNORM => 2,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::D24_UNORM => 3,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::D24_UNORM_S8_UINT => 4,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::D32_FLOAT => 4,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::D32_FLOAT_S8_UINT => 5,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::S8_UINT => 1,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::Y8Cb8Cr8_420 => 3,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::YCbCr_P010 => return None,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
Self::R8_UNORM => 1,
|
||||
Self::__Unknown(_) => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
//! Bindings for [`AInputQueue`]
|
||||
//!
|
||||
//! [`AInputQueue`]: https://developer.android.com/ndk/reference/group/input#ainputqueue
|
||||
|
||||
use std::io::Result;
|
||||
use std::os::raw::c_int;
|
||||
use std::ptr::{self, NonNull};
|
||||
|
||||
#[cfg(feature = "api-level-33")]
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
|
||||
use crate::event::InputEvent;
|
||||
#[cfg(doc)]
|
||||
use crate::event::KeyEvent;
|
||||
use crate::looper::ForeignLooper;
|
||||
use crate::utils::status_to_io_result;
|
||||
|
||||
/// A native [`AInputQueue *`]
|
||||
///
|
||||
/// An input queue is the facility through which you retrieve input events.
|
||||
///
|
||||
/// [`AInputQueue *`]: https://developer.android.com/ndk/reference/group/input#ainputqueue
|
||||
#[derive(Debug)]
|
||||
pub struct InputQueue {
|
||||
ptr: NonNull<ffi::AInputQueue>,
|
||||
}
|
||||
|
||||
// It gets shared between threads in `ndk-glue`
|
||||
unsafe impl Send for InputQueue {}
|
||||
unsafe impl Sync for InputQueue {}
|
||||
|
||||
impl InputQueue {
|
||||
/// Construct an [`InputQueue`] from the native pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that the pointer is a valid pointer to an NDK [`ffi::AInputQueue`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AInputQueue>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns the [`InputQueue`] object associated with the supplied
|
||||
/// [Java `InputQueue`][`android.view.InputQueue`] object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a healthy JVM pointer and with a non-null
|
||||
/// [`android.view.InputQueue`], which must be kept alive on the Java/Kotlin side.
|
||||
///
|
||||
/// The returned native object holds a weak reference to the Java object, and is only valid as
|
||||
/// long as the Java object has not yet been disposed. You should ensure that there is a strong
|
||||
/// reference to the Java object and that it has not been disposed before using the returned
|
||||
/// object.
|
||||
///
|
||||
/// [`android.view.InputQueue`]: https://developer.android.com/reference/android/view/InputQueue
|
||||
#[cfg(feature = "api-level-33")]
|
||||
#[doc(alias = "AInputQueue_fromJava")]
|
||||
pub unsafe fn from_java(env: *mut JNIEnv, input_queue: jobject) -> Option<Self> {
|
||||
let ptr = unsafe { ffi::AInputQueue_fromJava(env, input_queue) };
|
||||
Some(Self::from_ptr(NonNull::new(ptr)?))
|
||||
}
|
||||
|
||||
pub fn ptr(&self) -> NonNull<ffi::AInputQueue> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns the next available [`InputEvent`] from the queue.
|
||||
///
|
||||
/// Returns [`None`] if no event is available.
|
||||
#[doc(alias = "AInputQueue_getEvent")]
|
||||
pub fn event(&self) -> Result<Option<InputEvent>> {
|
||||
let mut out_event = ptr::null_mut();
|
||||
let status = unsafe { ffi::AInputQueue_getEvent(self.ptr.as_ptr(), &mut out_event) };
|
||||
match status_to_io_result(status) {
|
||||
Ok(()) => {
|
||||
debug_assert!(!out_event.is_null());
|
||||
Ok(Some(unsafe {
|
||||
InputEvent::from_ptr(NonNull::new_unchecked(out_event))
|
||||
}))
|
||||
}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [`true`] if there are one or more events available in the input queue.
|
||||
#[doc(alias = "AInputQueue_hasEvents")]
|
||||
pub fn has_events(&self) -> bool {
|
||||
match unsafe { ffi::AInputQueue_hasEvents(self.ptr.as_ptr()) } {
|
||||
0 => false,
|
||||
1 => true,
|
||||
r => unreachable!("AInputQueue_hasEvents returned non-boolean {}", r),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the key for standard pre-dispatching that is, possibly deliver it to the current IME
|
||||
/// to be consumed before the app.
|
||||
///
|
||||
/// Returns [`Some`] if it was not pre-dispatched, meaning you can process it right now. If
|
||||
/// [`None`] is returned, you must abandon the current event processing and allow the event to
|
||||
/// appear again in the event queue (if it does not get consumed during pre-dispatching).
|
||||
///
|
||||
/// Also returns [`None`] if `event` is not a [`KeyEvent`].
|
||||
#[doc(alias = "AInputQueue_preDispatchEvent")]
|
||||
pub fn pre_dispatch(&self, event: InputEvent) -> Option<InputEvent> {
|
||||
match unsafe { ffi::AInputQueue_preDispatchEvent(self.ptr.as_ptr(), event.ptr().as_ptr()) }
|
||||
{
|
||||
0 => Some(event),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Report that dispatching has finished with the given [`InputEvent`].
|
||||
///
|
||||
/// This must be called after receiving an event with [`InputQueue::event()`].
|
||||
#[doc(alias = "AInputQueue_finishEvent")]
|
||||
pub fn finish_event(&self, event: InputEvent, handled: bool) {
|
||||
unsafe {
|
||||
ffi::AInputQueue_finishEvent(self.ptr.as_ptr(), event.ptr().as_ptr(), handled as c_int)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add this input queue to a [`ForeignLooper`] for processing.
|
||||
///
|
||||
/// See [`ForeignLooper::add_fd()`] for information on the `ident`, `callback`, and `data` params.
|
||||
#[doc(alias = "AInputQueue_attachLooper")]
|
||||
pub fn attach_looper(&self, looper: &ForeignLooper, id: i32) {
|
||||
unsafe {
|
||||
ffi::AInputQueue_attachLooper(
|
||||
self.ptr.as_ptr(),
|
||||
looper.ptr().as_ptr(),
|
||||
id,
|
||||
None,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove this input queue from the [`ForeignLooper`] it is currently attached to.
|
||||
#[doc(alias = "AInputQueue_detachLooper")]
|
||||
pub fn detach_looper(&self) {
|
||||
unsafe { ffi::AInputQueue_detachLooper(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
//! # Android NDK
|
||||
//!
|
||||
//! Bindings to the [Android NDK].
|
||||
//!
|
||||
//! [Android NDK]: https://developer.android.com/ndk/reference
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
rust_2018_idioms,
|
||||
trivial_casts,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
pub mod asset;
|
||||
pub mod audio;
|
||||
pub mod bitmap;
|
||||
pub mod configuration;
|
||||
pub mod data_space;
|
||||
pub mod event;
|
||||
pub mod font;
|
||||
pub mod hardware_buffer;
|
||||
pub mod hardware_buffer_format;
|
||||
pub mod input_queue;
|
||||
pub mod looper;
|
||||
pub mod media;
|
||||
pub mod media_error;
|
||||
pub mod native_activity;
|
||||
pub mod native_window;
|
||||
pub mod shared_memory;
|
||||
pub mod surface_texture;
|
||||
pub mod sync;
|
||||
pub mod trace;
|
||||
mod utils;
|
||||
+459
@@ -0,0 +1,459 @@
|
||||
//! Bindings for [`ALooper`]
|
||||
//!
|
||||
//! In Android, [`ALooper`]s are inherently thread-local. Due to this, there are two different
|
||||
//! [`ALooper`] interfaces exposed in this module:
|
||||
//!
|
||||
//! * [`ThreadLooper`], which has methods for the operations performable with a looper in one's own
|
||||
//! thread; and
|
||||
//! * [`ForeignLooper`], which has methods for the operations performable with any thread's looper.
|
||||
//!
|
||||
//! [`ALooper`]: https://developer.android.com/ndk/reference/group/looper#alooper
|
||||
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::os::{
|
||||
fd::{AsRawFd, BorrowedFd, RawFd},
|
||||
raw::c_void,
|
||||
};
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::utils::abort_on_panic;
|
||||
|
||||
/// A thread-local native [`ALooper *`]. This promises that there is a looper associated with the
|
||||
/// current thread.
|
||||
///
|
||||
/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadLooper {
|
||||
_marker: std::marker::PhantomData<*mut ()>, // Not send or sync
|
||||
foreign: ForeignLooper,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for file descriptor events that a looper can monitor.
|
||||
///
|
||||
/// These flag bits can be combined to monitor multiple events at once.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FdEvent : u32 {
|
||||
/// The file descriptor is available for read operations.
|
||||
#[doc(alias = "ALOOPER_EVENT_INPUT")]
|
||||
const INPUT = ffi::ALOOPER_EVENT_INPUT;
|
||||
/// The file descriptor is available for write operations.
|
||||
#[doc(alias = "ALOOPER_EVENT_OUTPUT")]
|
||||
const OUTPUT = ffi::ALOOPER_EVENT_OUTPUT;
|
||||
/// The file descriptor has encountered an error condition.
|
||||
///
|
||||
/// The looper always sends notifications about errors; it is not necessary to specify this
|
||||
/// event flag in the requested event set.
|
||||
#[doc(alias = "ALOOPER_EVENT_ERROR")]
|
||||
const ERROR = ffi::ALOOPER_EVENT_ERROR;
|
||||
/// The file descriptor was hung up.
|
||||
///
|
||||
/// For example, indicates that the remote end of a pipe or socket was closed.
|
||||
///
|
||||
/// The looper always sends notifications about hangups; it is not necessary to specify this
|
||||
/// event flag in the requested event set.
|
||||
#[doc(alias = "ALOOPER_EVENT_HANGUP")]
|
||||
const HANGUP = ffi::ALOOPER_EVENT_HANGUP;
|
||||
/// The file descriptor is invalid.
|
||||
///
|
||||
/// For example, the file descriptor was closed prematurely.
|
||||
///
|
||||
/// The looper always sends notifications about invalid file descriptors; it is not
|
||||
/// necessary to specify this event flag in the requested event set.
|
||||
#[doc(alias = "ALOOPER_EVENT_INVALID")]
|
||||
const INVALID = ffi::ALOOPER_EVENT_INVALID;
|
||||
|
||||
// https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags
|
||||
const _ = !0;
|
||||
}
|
||||
}
|
||||
|
||||
/// The poll result from a [`ThreadLooper`].
|
||||
#[derive(Debug)]
|
||||
pub enum Poll<'fd> {
|
||||
/// This looper was woken using [`ForeignLooper::wake()`]
|
||||
Wake,
|
||||
/// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback.
|
||||
Callback,
|
||||
/// For [`ThreadLooper::poll_*_timeout()`][ThreadLooper::poll_once_timeout()], the requested timeout was reached before any events.
|
||||
Timeout,
|
||||
/// An event was received
|
||||
Event {
|
||||
ident: i32,
|
||||
/// # Safety
|
||||
/// The caller should guarantee that this file descriptor remains open after it was added
|
||||
/// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`].
|
||||
fd: BorrowedFd<'fd>,
|
||||
events: FdEvent,
|
||||
data: *mut c_void,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Error)]
|
||||
#[error("Android Looper error")]
|
||||
pub struct LooperError;
|
||||
|
||||
impl ThreadLooper {
|
||||
/// Prepares a looper for the current thread and returns it
|
||||
pub fn prepare() -> Self {
|
||||
unsafe {
|
||||
let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _);
|
||||
let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null"));
|
||||
Self {
|
||||
_marker: std::marker::PhantomData,
|
||||
foreign,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the looper associated with the current thread, if any.
|
||||
pub fn for_thread() -> Option<Self> {
|
||||
Some(Self {
|
||||
_marker: std::marker::PhantomData,
|
||||
foreign: ForeignLooper::for_thread()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Polls the looper, blocking on processing an event, but with a timeout in milliseconds.
|
||||
/// Give a timeout of `0` to make this non-blocking.
|
||||
fn poll_once_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
|
||||
let mut fd = -1;
|
||||
let mut events = -1;
|
||||
let mut data: *mut c_void = ptr::null_mut();
|
||||
match unsafe { ffi::ALooper_pollOnce(ms, &mut fd, &mut events, &mut data) } {
|
||||
ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
|
||||
ffi::ALOOPER_POLL_CALLBACK => Ok(Poll::Callback),
|
||||
ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
|
||||
ffi::ALOOPER_POLL_ERROR => Err(LooperError),
|
||||
ident if ident >= 0 => Ok(Poll::Event {
|
||||
ident,
|
||||
// SAFETY: Even though this FD at least shouldn't outlive self, a user could have
|
||||
// closed it after calling add_fd or add_fd_with_callback.
|
||||
fd: unsafe { BorrowedFd::borrow_raw(fd) },
|
||||
events: FdEvent::from_bits(events as u32)
|
||||
.expect("poll event contains unknown bits"),
|
||||
data,
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls the looper, blocking on processing an event.
|
||||
#[inline]
|
||||
pub fn poll_once(&self) -> Result<Poll<'_>, LooperError> {
|
||||
self.poll_once_ms(-1)
|
||||
}
|
||||
|
||||
/// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of
|
||||
/// [`Duration::ZERO`] to make this non-blocking.
|
||||
///
|
||||
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
|
||||
/// days).
|
||||
#[inline]
|
||||
pub fn poll_once_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
|
||||
self.poll_once_ms(
|
||||
timeout
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Supplied timeout is too large"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout in
|
||||
/// milliseconds. Give a timeout of `0` to make this non-blocking.
|
||||
///
|
||||
/// This function will never return [`Poll::Callback`].
|
||||
fn poll_all_ms(&self, ms: i32) -> Result<Poll<'_>, LooperError> {
|
||||
let mut fd = -1;
|
||||
let mut events = -1;
|
||||
let mut data: *mut c_void = ptr::null_mut();
|
||||
match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } {
|
||||
ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake),
|
||||
ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout),
|
||||
ffi::ALOOPER_POLL_ERROR => Err(LooperError),
|
||||
ident if ident >= 0 => Ok(Poll::Event {
|
||||
ident,
|
||||
// SAFETY: Even though this FD at least shouldn't outlive self, a user could have
|
||||
// closed it after calling add_fd or add_fd_with_callback.
|
||||
fd: unsafe { BorrowedFd::borrow_raw(fd) },
|
||||
events: FdEvent::from_bits(events as u32)
|
||||
.expect("poll event contains unknown bits"),
|
||||
data,
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Repeatedly polls the looper, blocking on processing an event.
|
||||
///
|
||||
/// This function will never return [`Poll::Callback`].
|
||||
#[inline]
|
||||
pub fn poll_all(&self) -> Result<Poll<'_>, LooperError> {
|
||||
self.poll_all_ms(-1)
|
||||
}
|
||||
|
||||
/// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a
|
||||
/// timeout of [`Duration::ZERO`] to make this non-blocking.
|
||||
///
|
||||
/// This function will never return [`Poll::Callback`].
|
||||
///
|
||||
/// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25
|
||||
/// days).
|
||||
#[inline]
|
||||
pub fn poll_all_timeout(&self, timeout: Duration) -> Result<Poll<'_>, LooperError> {
|
||||
self.poll_all_ms(
|
||||
timeout
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Supplied timeout is too large"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds a file descriptor to be polled, with a callback that is invoked when any of the
|
||||
/// [`FdEvent`]s described in `events` is triggered.
|
||||
///
|
||||
/// The callback receives the file descriptor it is associated with and a bitmask of the poll
|
||||
/// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to
|
||||
/// continue receiving callbacks, or [`false`] to have the callback unregistered.
|
||||
///
|
||||
/// See also [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
|
||||
///
|
||||
/// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
|
||||
/// itself.
|
||||
///
|
||||
/// # Threading
|
||||
/// This function will be called on the current thread when this [`ThreadLooper`] is
|
||||
/// polled. A callback can also be registered from other threads via the equivalent
|
||||
/// [`ForeignLooper::add_fd_with_callback()`] function, which requires a [`Send`] bound.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller should guarantee that this file descriptor stays open until it is removed via
|
||||
/// [`remove_fd()`][ForeignLooper::remove_fd()] or by returning [`false`] from the callback,
|
||||
/// and for however long the caller wishes to use this file descriptor inside and after the
|
||||
/// callback.
|
||||
#[doc(alias = "ALooper_addFd")]
|
||||
pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
|
||||
&self,
|
||||
fd: BorrowedFd<'_>,
|
||||
events: FdEvent,
|
||||
callback: F,
|
||||
) -> Result<(), LooperError> {
|
||||
unsafe {
|
||||
self.foreign
|
||||
.add_fd_with_callback_assume_send(fd, events, callback)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`ForeignLooper`] that is associated with the current thread.
|
||||
pub fn as_foreign(&self) -> &ForeignLooper {
|
||||
&self.foreign
|
||||
}
|
||||
|
||||
pub fn into_foreign(self) -> ForeignLooper {
|
||||
self.foreign
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`ALooper *`], not necessarily allocated with the current thread.
|
||||
///
|
||||
/// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper
|
||||
#[derive(Debug)]
|
||||
pub struct ForeignLooper {
|
||||
ptr: ptr::NonNull<ffi::ALooper>,
|
||||
}
|
||||
|
||||
unsafe impl Send for ForeignLooper {}
|
||||
unsafe impl Sync for ForeignLooper {}
|
||||
|
||||
impl Drop for ForeignLooper {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ALooper_release(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ForeignLooper {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe {
|
||||
ffi::ALooper_acquire(self.ptr.as_ptr());
|
||||
Self { ptr: self.ptr }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignLooper {
|
||||
/// Returns the looper associated with the current thread, if any.
|
||||
#[inline]
|
||||
pub fn for_thread() -> Option<Self> {
|
||||
ptr::NonNull::new(unsafe { ffi::ALooper_forThread() })
|
||||
.map(|ptr| unsafe { Self::from_ptr(ptr) })
|
||||
}
|
||||
|
||||
/// Construct a [`ForeignLooper`] object from the given pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you guarantee that the pointer is a valid, non-null pointer to an
|
||||
/// NDK [`ffi::ALooper`].
|
||||
#[inline]
|
||||
pub unsafe fn from_ptr(ptr: ptr::NonNull<ffi::ALooper>) -> Self {
|
||||
ffi::ALooper_acquire(ptr.as_ptr());
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Returns a pointer to the NDK `ALooper` object.
|
||||
#[inline]
|
||||
pub fn ptr(&self) -> ptr::NonNull<ffi::ALooper> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Wakes the looper. An event of [`Poll::Wake`] will be sent.
|
||||
pub fn wake(&self) {
|
||||
unsafe { ffi::ALooper_wake(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Adds a file descriptor to be polled, without a callback.
|
||||
///
|
||||
/// See also [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller should guarantee that this file descriptor stays open until it is removed via
|
||||
/// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file
|
||||
/// descriptor when it is returned in [`Poll::Event::fd`].
|
||||
|
||||
// `ALooper_addFd` won't dereference `data`; it will only pass it on to the event.
|
||||
// Optionally dereferencing it there already enforces `unsafe` context.
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn add_fd(
|
||||
&self,
|
||||
fd: BorrowedFd<'_>,
|
||||
ident: i32,
|
||||
events: FdEvent,
|
||||
data: *mut c_void,
|
||||
) -> Result<(), LooperError> {
|
||||
match unsafe {
|
||||
ffi::ALooper_addFd(
|
||||
self.ptr.as_ptr(),
|
||||
fd.as_raw_fd(),
|
||||
ident,
|
||||
events.bits() as i32,
|
||||
None,
|
||||
data,
|
||||
)
|
||||
} {
|
||||
1 => Ok(()),
|
||||
-1 => Err(LooperError),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a file descriptor to be polled, with a callback that is invoked when any of the
|
||||
/// [`FdEvent`]s described in `events` is triggered.
|
||||
///
|
||||
/// The callback receives the file descriptor it is associated with and a bitmask of the poll
|
||||
/// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to
|
||||
/// continue receiving callbacks, or [`false`] to have the callback unregistered.
|
||||
///
|
||||
/// See also [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd).
|
||||
///
|
||||
/// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister
|
||||
/// itself.
|
||||
///
|
||||
/// # Threading
|
||||
/// This function will be called on the looper thread where and when it is polled.
|
||||
/// For registering callbacks without [`Send`] requirement, call the equivalent
|
||||
/// [`ThreadLooper::add_fd_with_callback()`] function on the Looper thread.
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller should guarantee that this file descriptor stays open until it is removed via
|
||||
/// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for
|
||||
/// however long the caller wishes to use this file descriptor inside and after the callback.
|
||||
#[doc(alias = "ALooper_addFd")]
|
||||
pub fn add_fd_with_callback<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool + Send>(
|
||||
&self,
|
||||
fd: BorrowedFd<'_>,
|
||||
events: FdEvent,
|
||||
callback: F,
|
||||
) -> Result<(), LooperError> {
|
||||
unsafe { self.add_fd_with_callback_assume_send(fd, events, callback) }
|
||||
}
|
||||
|
||||
/// Private helper to deduplicate/commonize the implementation behind
|
||||
/// [`ForeignLooper::add_fd_with_callback()`] and [`ThreadLooper::add_fd_with_callback()`],
|
||||
/// as both have their own way of guaranteeing thread-safety. The former, [`ForeignLooper`],
|
||||
/// requires the closure to be [`Send`]. The latter, [`ThreadLooper`], can only exist on the
|
||||
/// thread where polling happens and where the closure will end up being invoked, and does not
|
||||
/// require [`Send`].
|
||||
///
|
||||
/// # Safety
|
||||
/// The caller must guarantee that `F` is [`Send`] or that `F` will only run on the current
|
||||
/// thread. See the explanation above about why this function exists.
|
||||
unsafe fn add_fd_with_callback_assume_send<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
|
||||
&self,
|
||||
fd: BorrowedFd<'_>,
|
||||
events: FdEvent,
|
||||
callback: F,
|
||||
) -> Result<(), LooperError> {
|
||||
extern "C" fn cb_handler<F: FnMut(BorrowedFd<'_>, FdEvent) -> bool>(
|
||||
fd: RawFd,
|
||||
events: i32,
|
||||
data: *mut c_void,
|
||||
) -> i32 {
|
||||
abort_on_panic(|| unsafe {
|
||||
let mut cb = ManuallyDrop::new(Box::<F>::from_raw(data as *mut _));
|
||||
let events = FdEvent::from_bits_retain(
|
||||
events.try_into().expect("Unexpected sign bit in `events`"),
|
||||
);
|
||||
let keep_registered = cb(BorrowedFd::borrow_raw(fd), events);
|
||||
if !keep_registered {
|
||||
ManuallyDrop::into_inner(cb);
|
||||
}
|
||||
keep_registered as i32
|
||||
})
|
||||
}
|
||||
let data = Box::into_raw(Box::new(callback)) as *mut _;
|
||||
match unsafe {
|
||||
ffi::ALooper_addFd(
|
||||
self.ptr.as_ptr(),
|
||||
fd.as_raw_fd(),
|
||||
ffi::ALOOPER_POLL_CALLBACK,
|
||||
events.bits() as i32,
|
||||
Some(cb_handler::<F>),
|
||||
data,
|
||||
)
|
||||
} {
|
||||
1 => Ok(()),
|
||||
-1 => Err(LooperError),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a previously added file descriptor from the looper.
|
||||
///
|
||||
/// Returns [`true`] if the file descriptor was removed, [`false`] if it was not previously
|
||||
/// registered.
|
||||
///
|
||||
/// # Safety
|
||||
/// When this method returns, it is safe to close the file descriptor since the looper will no
|
||||
/// longer have a reference to it. However, it is possible for the callback to already be
|
||||
/// running or for it to run one last time if the file descriptor was already signalled.
|
||||
/// Calling code is responsible for ensuring that this case is safely handled. For example, if
|
||||
/// the callback takes care of removing itself during its own execution either by returning `0`
|
||||
/// or by calling this method, then it can be guaranteed to not be invoked again at any later
|
||||
/// time unless registered anew.
|
||||
///
|
||||
/// Note that unregistering a file descriptor with callback will leak a [`Box`] created in
|
||||
/// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`]
|
||||
/// from the callback instead to drop it.
|
||||
pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result<bool, LooperError> {
|
||||
match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } {
|
||||
1 => Ok(true),
|
||||
0 => Ok(false),
|
||||
-1 => Err(LooperError),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
//! Bindings for [`AImageReader`] and [`AImage`]
|
||||
//!
|
||||
//! [`AImageReader`]: https://developer.android.com/ndk/reference/group/media#aimagereader
|
||||
//! [`AImage`]: https://developer.android.com/ndk/reference/group/media#aimage
|
||||
#![cfg(feature = "api-level-24")]
|
||||
|
||||
use crate::media_error::{construct, construct_never_null, MediaError, Result};
|
||||
use crate::native_window::NativeWindow;
|
||||
use crate::utils::abort_on_panic;
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
fmt::{self, Debug, Formatter},
|
||||
mem::MaybeUninit,
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd};
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
use crate::hardware_buffer::{HardwareBuffer, HardwareBufferUsage};
|
||||
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[non_exhaustive]
|
||||
pub enum ImageFormat {
|
||||
RGBA_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_8888.0 as i32,
|
||||
RGBX_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBX_8888.0 as i32,
|
||||
RGB_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_888.0 as i32,
|
||||
RGB_565 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_565.0 as i32,
|
||||
RGBA_FP16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_FP16.0 as i32,
|
||||
YUV_420_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_YUV_420_888.0 as i32,
|
||||
JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_JPEG.0 as i32,
|
||||
RAW16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW16.0 as i32,
|
||||
RAW_PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW_PRIVATE.0 as i32,
|
||||
RAW10 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW10.0 as i32,
|
||||
RAW12 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW12.0 as i32,
|
||||
DEPTH16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH16.0 as i32,
|
||||
DEPTH_POINT_CLOUD = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_POINT_CLOUD.0 as i32,
|
||||
PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_PRIVATE.0 as i32,
|
||||
Y8 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_Y8.0 as i32,
|
||||
HEIC = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_HEIC.0 as i32,
|
||||
DEPTH_JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_JPEG.0 as i32,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
pub type ImageListener = Box<dyn FnMut(&ImageReader) + Send>;
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub type BufferRemovedListener = Box<dyn FnMut(&ImageReader, &HardwareBuffer) + Send>;
|
||||
|
||||
/// Result returned by:
|
||||
/// - [`ImageReader::acquire_next_image()`]`
|
||||
/// - [`ImageReader::acquire_next_image_async()`]`
|
||||
/// - [`ImageReader::acquire_latest_image()`]`
|
||||
/// - [`ImageReader::acquire_latest_image_async()`]`
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum AcquireResult<T> {
|
||||
/// Returned if there is no buffers currently available in the reader queue.
|
||||
#[doc(alias = "AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE")]
|
||||
NoBufferAvailable,
|
||||
/// Returned if the number of concurrently acquired images has reached the limit.
|
||||
#[doc(alias = "AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED")]
|
||||
MaxImagesAcquired,
|
||||
|
||||
/// Returned if an [`Image`] (optionally with fence) was successfully acquired.
|
||||
Image(T),
|
||||
}
|
||||
|
||||
impl<T> AcquireResult<T> {
|
||||
fn map<U>(self, f: impl FnOnce(T) -> U) -> AcquireResult<U> {
|
||||
match self {
|
||||
AcquireResult::Image(img) => AcquireResult::Image(f(img)),
|
||||
AcquireResult::NoBufferAvailable => AcquireResult::NoBufferAvailable,
|
||||
AcquireResult::MaxImagesAcquired => AcquireResult::MaxImagesAcquired,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AcquireResult<Image> {
|
||||
/// Inlined version of [`construct_never_null()`] with IMGREADER-specific result mapping.
|
||||
fn construct_never_null(
|
||||
with_ptr: impl FnOnce(*mut *mut ffi::AImage) -> ffi::media_status_t,
|
||||
) -> Result<Self> {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let status = with_ptr(result.as_mut_ptr());
|
||||
match status {
|
||||
ffi::media_status_t::AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE => {
|
||||
Ok(Self::NoBufferAvailable)
|
||||
}
|
||||
ffi::media_status_t::AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED => {
|
||||
Ok(Self::MaxImagesAcquired)
|
||||
}
|
||||
status => MediaError::from_status(status).map(|()| {
|
||||
let result = unsafe { result.assume_init() };
|
||||
Self::Image(Image {
|
||||
inner: if cfg!(debug_assertions) {
|
||||
NonNull::new(result).expect("result should never be null")
|
||||
} else {
|
||||
unsafe { NonNull::new_unchecked(result) }
|
||||
},
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AImageReader *`]
|
||||
///
|
||||
/// [`AImageReader *`]: https://developer.android.com/ndk/reference/group/media#aimagereader
|
||||
pub struct ImageReader {
|
||||
inner: NonNull<ffi::AImageReader>,
|
||||
image_cb: Option<Box<ImageListener>>,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
buffer_removed_cb: Option<Box<BufferRemovedListener>>,
|
||||
}
|
||||
|
||||
impl Debug for ImageReader {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ImageReader")
|
||||
.field("inner", &self.inner)
|
||||
.field(
|
||||
"image_cb",
|
||||
match &self.image_cb {
|
||||
Some(_) => &"Some(_)",
|
||||
None => &"None",
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageReader {
|
||||
fn from_ptr(inner: NonNull<ffi::AImageReader>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
image_cb: None,
|
||||
#[cfg(feature = "api-level-26")]
|
||||
buffer_removed_cb: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *mut ffi::AImageReader {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
pub fn new(width: i32, height: i32, format: ImageFormat, max_images: i32) -> Result<Self> {
|
||||
let inner = construct_never_null(|res| unsafe {
|
||||
ffi::AImageReader_new(width, height, format.into(), max_images, res)
|
||||
})?;
|
||||
|
||||
Ok(Self::from_ptr(inner))
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn new_with_usage(
|
||||
width: i32,
|
||||
height: i32,
|
||||
format: ImageFormat,
|
||||
usage: HardwareBufferUsage,
|
||||
max_images: i32,
|
||||
) -> Result<Self> {
|
||||
let inner = construct_never_null(|res| unsafe {
|
||||
ffi::AImageReader_newWithUsage(
|
||||
width,
|
||||
height,
|
||||
format.into(),
|
||||
usage.bits(),
|
||||
max_images,
|
||||
res,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Self::from_ptr(inner))
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_setImageListener")]
|
||||
pub fn set_image_listener(&mut self, listener: ImageListener) -> Result<()> {
|
||||
let mut boxed = Box::new(listener);
|
||||
let ptr: *mut ImageListener = &mut *boxed;
|
||||
|
||||
unsafe extern "C" fn on_image_available(
|
||||
context: *mut c_void,
|
||||
reader: *mut ffi::AImageReader,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader));
|
||||
let listener: *mut ImageListener = context.cast();
|
||||
(*listener)(&reader);
|
||||
std::mem::forget(reader);
|
||||
})
|
||||
}
|
||||
|
||||
let mut listener = ffi::AImageReader_ImageListener {
|
||||
context: ptr as _,
|
||||
onImageAvailable: Some(on_image_available),
|
||||
};
|
||||
let status = unsafe { ffi::AImageReader_setImageListener(self.as_ptr(), &mut listener) };
|
||||
|
||||
// keep listener alive until Drop or new listener is assigned
|
||||
self.image_cb = Some(boxed);
|
||||
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AImageReader_setBufferRemovedListener")]
|
||||
pub fn set_buffer_removed_listener(&mut self, listener: BufferRemovedListener) -> Result<()> {
|
||||
let mut boxed = Box::new(listener);
|
||||
let ptr: *mut BufferRemovedListener = &mut *boxed;
|
||||
|
||||
unsafe extern "C" fn on_buffer_removed(
|
||||
context: *mut c_void,
|
||||
reader: *mut ffi::AImageReader,
|
||||
buffer: *mut ffi::AHardwareBuffer,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader));
|
||||
let buffer = HardwareBuffer::from_ptr(NonNull::new_unchecked(buffer));
|
||||
let listener: *mut BufferRemovedListener = context.cast();
|
||||
(*listener)(&reader, &buffer);
|
||||
std::mem::forget(reader);
|
||||
})
|
||||
}
|
||||
|
||||
let mut listener = ffi::AImageReader_BufferRemovedListener {
|
||||
context: ptr as _,
|
||||
onBufferRemoved: Some(on_buffer_removed),
|
||||
};
|
||||
let status =
|
||||
unsafe { ffi::AImageReader_setBufferRemovedListener(self.as_ptr(), &mut listener) };
|
||||
|
||||
// keep listener alive until Drop or new listener is assigned
|
||||
self.buffer_removed_cb = Some(boxed);
|
||||
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
/// Get a [`NativeWindow`] that can be used to produce [`Image`]s for this [`ImageReader`].
|
||||
///
|
||||
/// <https://developer.android.com/ndk/reference/group/media#aimagereader_getwindow>
|
||||
#[doc(alias = "AImageReader_getWindow")]
|
||||
pub fn window(&self) -> Result<NativeWindow> {
|
||||
unsafe {
|
||||
let ptr = construct_never_null(|res| ffi::AImageReader_getWindow(self.as_ptr(), res))?;
|
||||
Ok(NativeWindow::clone_from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_getWidth")]
|
||||
pub fn width(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImageReader_getWidth(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_getHeight")]
|
||||
pub fn height(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImageReader_getHeight(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_getFormat")]
|
||||
pub fn format(&self) -> Result<ImageFormat> {
|
||||
let format = construct(|res| unsafe { ffi::AImageReader_getFormat(self.as_ptr(), res) })?;
|
||||
Ok(format.into())
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_getMaxImages")]
|
||||
pub fn max_images(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImageReader_getMaxImages(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_acquireNextImage")]
|
||||
pub fn acquire_next_image(&self) -> Result<AcquireResult<Image>> {
|
||||
AcquireResult::construct_never_null(|res| unsafe {
|
||||
ffi::AImageReader_acquireNextImage(self.as_ptr(), res)
|
||||
})
|
||||
}
|
||||
|
||||
/// Acquire the next [`Image`] from the image reader's queue asynchronously.
|
||||
///
|
||||
/// # Safety
|
||||
/// If the returned file descriptor is not [`None`], it must be awaited before attempting to
|
||||
/// access the [`Image`] returned.
|
||||
///
|
||||
/// <https://developer.android.com/ndk/reference/group/media#aimagereader_acquirenextimageasync>
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AImageReader_acquireNextImageAsync")]
|
||||
pub unsafe fn acquire_next_image_async(
|
||||
&self,
|
||||
) -> Result<AcquireResult<(Image, Option<OwnedFd>)>> {
|
||||
let mut fence = MaybeUninit::uninit();
|
||||
AcquireResult::construct_never_null(|res| {
|
||||
ffi::AImageReader_acquireNextImageAsync(self.as_ptr(), res, fence.as_mut_ptr())
|
||||
})
|
||||
.map(|result| {
|
||||
result.map(|image| match fence.assume_init() {
|
||||
-1 => (image, None),
|
||||
fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageReader_acquireLatestImage")]
|
||||
pub fn acquire_latest_image(&self) -> Result<AcquireResult<Image>> {
|
||||
AcquireResult::construct_never_null(|res| unsafe {
|
||||
ffi::AImageReader_acquireLatestImage(self.as_ptr(), res)
|
||||
})
|
||||
}
|
||||
|
||||
/// Acquire the latest [`Image`] from the image reader's queue asynchronously, dropping older images.
|
||||
///
|
||||
/// # Safety
|
||||
/// If the returned file descriptor is not [`None`], it must be awaited before attempting to
|
||||
/// access the [`Image`] returned.
|
||||
///
|
||||
/// <https://developer.android.com/ndk/reference/group/media#aimagereader_acquirelatestimageasync>
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AImageReader_acquireLatestImageAsync")]
|
||||
pub unsafe fn acquire_latest_image_async(
|
||||
&self,
|
||||
) -> Result<AcquireResult<(Image, Option<OwnedFd>)>> {
|
||||
let mut fence = MaybeUninit::uninit();
|
||||
AcquireResult::construct_never_null(|res| {
|
||||
ffi::AImageReader_acquireLatestImageAsync(self.as_ptr(), res, fence.as_mut_ptr())
|
||||
})
|
||||
.map(|result| {
|
||||
result.map(|image| match fence.assume_init() {
|
||||
-1 => (image, None),
|
||||
fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ImageReader {
|
||||
#[doc(alias = "AImageReader_delete")]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AImageReader_delete(self.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`AImage *`]
|
||||
///
|
||||
/// [`AImage *`]: https://developer.android.com/ndk/reference/group/media#aimage
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "AImage")]
|
||||
pub struct Image {
|
||||
inner: NonNull<ffi::AImage>,
|
||||
}
|
||||
|
||||
#[doc(alias = "AImageCropRect")]
|
||||
pub type CropRect = ffi::AImageCropRect;
|
||||
|
||||
impl Image {
|
||||
fn as_ptr(&self) -> *mut ffi::AImage {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getPlaneData")]
|
||||
pub fn plane_data(&self, plane_idx: i32) -> Result<&[u8]> {
|
||||
let mut result_ptr = MaybeUninit::uninit();
|
||||
let mut result_len = MaybeUninit::uninit();
|
||||
let status = unsafe {
|
||||
ffi::AImage_getPlaneData(
|
||||
self.as_ptr(),
|
||||
plane_idx,
|
||||
result_ptr.as_mut_ptr(),
|
||||
result_len.as_mut_ptr(),
|
||||
)
|
||||
};
|
||||
|
||||
MediaError::from_status(status).map(|()| unsafe {
|
||||
std::slice::from_raw_parts(result_ptr.assume_init(), result_len.assume_init() as _)
|
||||
})
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getPlanePixelStride")]
|
||||
pub fn plane_pixel_stride(&self, plane_idx: i32) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImage_getPlanePixelStride(self.as_ptr(), plane_idx, res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getPlaneRowStride")]
|
||||
pub fn plane_row_stride(&self, plane_idx: i32) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImage_getPlaneRowStride(self.as_ptr(), plane_idx, res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getCropRect")]
|
||||
pub fn crop_rect(&self) -> Result<CropRect> {
|
||||
construct(|res| unsafe { ffi::AImage_getCropRect(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getWidth")]
|
||||
pub fn width(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImage_getWidth(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getHeight")]
|
||||
pub fn height(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImage_getHeight(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getFormat")]
|
||||
pub fn format(&self) -> Result<ImageFormat> {
|
||||
let format = construct(|res| unsafe { ffi::AImage_getFormat(self.as_ptr(), res) })?;
|
||||
Ok(format.into())
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getTimestamp")]
|
||||
pub fn timestamp(&self) -> Result<i64> {
|
||||
construct(|res| unsafe { ffi::AImage_getTimestamp(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
#[doc(alias = "AImage_getNumberOfPlanes")]
|
||||
pub fn number_of_planes(&self) -> Result<i32> {
|
||||
construct(|res| unsafe { ffi::AImage_getNumberOfPlanes(self.as_ptr(), res) })
|
||||
}
|
||||
|
||||
/// Get the hardware buffer handle of the input image intended for GPU and/or hardware access.
|
||||
///
|
||||
/// Note that no reference on the returned [`HardwareBuffer`] handle is acquired automatically.
|
||||
/// Once the [`Image`] or the parent [`ImageReader`] is deleted, the [`HardwareBuffer`] handle
|
||||
/// from previous [`Image::hardware_buffer()`] becomes invalid.
|
||||
///
|
||||
/// If the caller ever needs to hold on a reference to the [`HardwareBuffer`] handle after the
|
||||
/// [`Image`] or the parent [`ImageReader`] is deleted, it must call
|
||||
/// [`HardwareBuffer::acquire()`] to acquire an extra reference, and [`drop()`] it when
|
||||
/// finished using it in order to properly deallocate the underlying memory managed by
|
||||
/// [`HardwareBuffer`]. If the caller has acquired an extra reference on a [`HardwareBuffer`]
|
||||
/// returned from this function, it must also register a listener using
|
||||
/// [`ImageReader::set_buffer_removed_listener()`] to be notified when the buffer is no longer
|
||||
/// used by [`ImageReader`].
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AImage_getHardwareBuffer")]
|
||||
pub fn hardware_buffer(&self) -> Result<HardwareBuffer> {
|
||||
unsafe {
|
||||
let ptr =
|
||||
construct_never_null(|res| ffi::AImage_getHardwareBuffer(self.as_ptr(), res))?;
|
||||
Ok(HardwareBuffer::from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
#[doc(alias = "AImage_deleteAsync")]
|
||||
pub fn delete_async(self, release_fence_fd: OwnedFd) {
|
||||
unsafe { ffi::AImage_deleteAsync(self.as_ptr(), release_fence_fd.into_raw_fd()) };
|
||||
std::mem::forget(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Image {
|
||||
#[doc(alias = "AImage_delete")]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::AImage_delete(self.as_ptr()) };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
//! Bindings for [`AMediaCodec`]
|
||||
//!
|
||||
//! [`AMediaCodec`]: https://developer.android.com/ndk/reference/group/media#amediacodec
|
||||
|
||||
#[deprecated = "MediaFormat should be referenced directly from the media_format module"]
|
||||
pub use super::media_format::MediaFormat;
|
||||
use crate::media_error::{MediaError, Result};
|
||||
use crate::native_window::NativeWindow;
|
||||
use crate::utils::abort_on_panic;
|
||||
use std::{
|
||||
ffi::{c_char, c_void, CStr, CString},
|
||||
fmt,
|
||||
mem::MaybeUninit,
|
||||
pin::Pin,
|
||||
ptr::{self, NonNull},
|
||||
slice,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MediaCodecDirection {
|
||||
Decoder,
|
||||
Encoder,
|
||||
}
|
||||
|
||||
/// A native [`AMediaCodec *`]
|
||||
///
|
||||
/// [`AMediaCodec *`]: https://developer.android.com/ndk/reference/group/media#amediacodec
|
||||
#[derive(Debug)]
|
||||
pub struct MediaCodec {
|
||||
inner: NonNull<ffi::AMediaCodec>,
|
||||
async_notify_callback: Option<Pin<Box<AsyncNotifyCallback>>>,
|
||||
}
|
||||
|
||||
pub struct AsyncNotifyCallback {
|
||||
/// Called when an input buffer becomes available.
|
||||
///
|
||||
/// The specified index is the index of the available input buffer.
|
||||
pub on_input_available: Option<InputAvailableCallback>,
|
||||
|
||||
/// Called when an output buffer becomes available.
|
||||
///
|
||||
/// The specified index is the index of the available output buffer. The specified
|
||||
/// [`BufferInfo`] contains information regarding the available output buffer.
|
||||
pub on_output_available: Option<OutputAvailableCallback>,
|
||||
|
||||
/// Called when the output format has changed.
|
||||
///
|
||||
/// The specified format contains the new output format.
|
||||
pub on_format_changed: Option<FormatChangedCallback>,
|
||||
|
||||
/// Called when the [`MediaCodec`] encountered an error.
|
||||
///
|
||||
/// The specified [`ActionCode`] indicates the possible actions that client can take, and it can
|
||||
/// be checked by calling [`ActionCode::is_recoverable`] or [`ActionCode::is_transient`]. If
|
||||
/// both [`ActionCode::is_recoverable`] and [`ActionCode::is_transient`] return [`false`], then
|
||||
/// the codec error is fatal and the codec must be deleted. The specified detail string may
|
||||
/// contain more detailed messages about this error.
|
||||
pub on_error: Option<ErrorCallback>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AsyncNotifyCallback {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AsyncNotifyCallback")
|
||||
.field(
|
||||
"on_input_available",
|
||||
match &self.on_input_available {
|
||||
Some(_) => &"Some(_)",
|
||||
None => &"None",
|
||||
},
|
||||
)
|
||||
.field(
|
||||
"on_output_available",
|
||||
match &self.on_output_available {
|
||||
Some(_) => &"Some(_)",
|
||||
None => &"None",
|
||||
},
|
||||
)
|
||||
.field(
|
||||
"on_format_changed",
|
||||
match &self.on_format_changed {
|
||||
Some(_) => &"Some(_)",
|
||||
None => &"None",
|
||||
},
|
||||
)
|
||||
.field(
|
||||
"on_error",
|
||||
match &self.on_error {
|
||||
Some(_) => &"Some(_)",
|
||||
None => &"None",
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub type InputAvailableCallback = Box<dyn FnMut(usize) + Send>;
|
||||
pub type OutputAvailableCallback = Box<dyn FnMut(usize, &BufferInfo) + Send>;
|
||||
pub type FormatChangedCallback = Box<dyn FnMut(&MediaFormat) + Send>;
|
||||
pub type ErrorCallback = Box<dyn FnMut(MediaError, ActionCode, &CStr) + Send>;
|
||||
|
||||
impl MediaCodec {
|
||||
/// [punktfunk vendored patch — the ONLY change to this crate] Public so callers can bind
|
||||
/// `AMediaCodec_*` entry points the wrapper doesn't expose yet (here:
|
||||
/// `AMediaCodec_setOnFrameRenderedCallback` for the HUD's display stage). The pointer is valid
|
||||
/// for `&self`'s lifetime; callers must not delete or re-configure the codec through it.
|
||||
pub fn as_ptr(&self) -> *mut ffi::AMediaCodec {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
pub fn from_codec_name(name: &str) -> Option<Self> {
|
||||
let c_string = CString::new(name).unwrap();
|
||||
Some(Self {
|
||||
inner: NonNull::new(unsafe { ffi::AMediaCodec_createCodecByName(c_string.as_ptr()) })?,
|
||||
async_notify_callback: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_decoder_type(mime_type: &str) -> Option<Self> {
|
||||
let c_string = CString::new(mime_type).unwrap();
|
||||
Some(Self {
|
||||
inner: NonNull::new(unsafe {
|
||||
ffi::AMediaCodec_createDecoderByType(c_string.as_ptr())
|
||||
})?,
|
||||
async_notify_callback: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_encoder_type(mime_type: &str) -> Option<Self> {
|
||||
let c_string = CString::new(mime_type).unwrap();
|
||||
Some(Self {
|
||||
inner: NonNull::new(unsafe {
|
||||
ffi::AMediaCodec_createEncoderByType(c_string.as_ptr())
|
||||
})?,
|
||||
async_notify_callback: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set an asynchronous callback for actionable [`MediaCodec`] events.
|
||||
///
|
||||
/// When asynchronous callback is enabled, it is an error for the client to call
|
||||
/// [`MediaCodec::dequeue_input_buffer()`] or [`MediaCodec::dequeue_output_buffer()`].
|
||||
///
|
||||
/// [`MediaCodec::flush()`] behaves differently in asynchronous mode. After calling
|
||||
/// [`MediaCodec::flush()`], the client must call [`MediaCodec::start()`] to "resume" receiving
|
||||
/// input buffers. Even if the client does not receive
|
||||
/// [`AsyncNotifyCallback::on_input_available`] callbacks from video encoders configured with an
|
||||
/// input surface, the client still needs to call [`MediaCodec::start()`] to resume the input
|
||||
/// surface to send buffers to the encoders.
|
||||
///
|
||||
/// When called with [`None`] callback, this method unregisters any previously set callback.
|
||||
///
|
||||
/// Refer to the definition of [`AsyncNotifyCallback`] on how each callback function is called
|
||||
/// and what are specified.
|
||||
///
|
||||
/// Once the callback is unregistered or the codec is reset / released, the previously
|
||||
/// registered callback will not be called.
|
||||
///
|
||||
/// All callbacks are fired on one NDK internal thread.
|
||||
/// [`MediaCodec::set_async_notify_callback()`] should not be called on the callback thread. No
|
||||
/// heavy duty task should be performed on callback thread.
|
||||
#[cfg(feature = "api-level-28")]
|
||||
pub fn set_async_notify_callback(
|
||||
&mut self,
|
||||
callback: Option<AsyncNotifyCallback>,
|
||||
) -> Result<()> {
|
||||
unsafe extern "C" fn ffi_on_input_available(
|
||||
_codec: *mut ffi::AMediaCodec,
|
||||
user_data: *mut c_void,
|
||||
index: i32,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
|
||||
if let Some(f) = callback.on_input_available.as_mut() {
|
||||
f(index as usize);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ffi_on_output_available(
|
||||
_codec: *mut ffi::AMediaCodec,
|
||||
user_data: *mut c_void,
|
||||
index: i32,
|
||||
buffer_info: *mut ffi::AMediaCodecBufferInfo,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
|
||||
if let Some(f) = callback.on_output_available.as_mut() {
|
||||
let buffer_info = BufferInfo {
|
||||
inner: *buffer_info,
|
||||
};
|
||||
f(index as usize, &buffer_info);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ffi_on_format_changed(
|
||||
_codec: *mut ffi::AMediaCodec,
|
||||
user_data: *mut c_void,
|
||||
format: *mut ffi::AMediaFormat,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
// Ownership of the format is not documented, but the implementation allocates a new instance and does
|
||||
// not free it, so assume it is ok for us to do so
|
||||
// https://cs.android.com/android/platform/superproject/main/+/refs/heads/main:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=248-254;drc=5e15c3e22f3fa32d64e57302201123ce41589adf
|
||||
let format = MediaFormat::from_ptr(NonNull::new_unchecked(format));
|
||||
|
||||
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
|
||||
if let Some(f) = callback.on_format_changed.as_mut() {
|
||||
f(&format);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn ffi_on_error(
|
||||
_codec: *mut ffi::AMediaCodec,
|
||||
user_data: *mut c_void,
|
||||
error: ffi::media_status_t,
|
||||
action_code: i32,
|
||||
detail: *const c_char,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
|
||||
if let Some(f) = callback.on_error.as_mut() {
|
||||
f(
|
||||
MediaError::from_status(error).unwrap_err(),
|
||||
ActionCode(action_code),
|
||||
CStr::from_ptr(detail),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let (callback, ffi_callback, user_data) = if let Some(callback) = callback {
|
||||
// On Android 12 and earlier, due to faulty null checks, if a callback is not set, but at least one other
|
||||
// callback *is* set, then it will segfault in when trying to invoke the unset callback. See for example:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-12.0.0_r34:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=161-162;drc=ef058464777739e2d9ffad5f00d0e57b186d9a13
|
||||
// To work around this we just enable all callbacks and do nothing if the corresponding callback is not set
|
||||
// in AsyncNotifyCallback
|
||||
let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback {
|
||||
onAsyncInputAvailable: Some(ffi_on_input_available),
|
||||
onAsyncOutputAvailable: Some(ffi_on_output_available),
|
||||
onAsyncFormatChanged: Some(ffi_on_format_changed),
|
||||
onAsyncError: Some(ffi_on_error),
|
||||
};
|
||||
|
||||
let mut boxed = Box::pin(callback);
|
||||
let ptr: *mut AsyncNotifyCallback = &mut *boxed;
|
||||
|
||||
(Some(boxed), ffi_callback, ptr as *mut c_void)
|
||||
} else {
|
||||
let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback {
|
||||
onAsyncInputAvailable: None,
|
||||
onAsyncOutputAvailable: None,
|
||||
onAsyncFormatChanged: None,
|
||||
onAsyncError: None,
|
||||
};
|
||||
|
||||
(None, ffi_callback, ptr::null_mut())
|
||||
};
|
||||
|
||||
let status = unsafe {
|
||||
ffi::AMediaCodec_setAsyncNotifyCallback(self.as_ptr(), ffi_callback, user_data)
|
||||
};
|
||||
let result = MediaError::from_status(status);
|
||||
|
||||
// This behavior is not documented, but the implementation always clears the callback on failure, so we must
|
||||
// clear any callback that may have been previously registered
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/av/media/ndk/NdkMediaCodec.cpp;l=581-584;drc=8c4e619c7461ac1a8c20c55364643662e9185e4d
|
||||
if result.is_ok() {
|
||||
self.async_notify_callback = callback;
|
||||
} else {
|
||||
self.async_notify_callback = None;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn configure(
|
||||
&self,
|
||||
format: &MediaFormat,
|
||||
surface: Option<&NativeWindow>,
|
||||
direction: MediaCodecDirection,
|
||||
) -> Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::AMediaCodec_configure(
|
||||
self.as_ptr(),
|
||||
format.as_ptr(),
|
||||
surface.map_or(ptr::null_mut(), |s| s.ptr().as_ptr()),
|
||||
ptr::null_mut(),
|
||||
if direction == MediaCodecDirection::Encoder {
|
||||
ffi::AMEDIACODEC_CONFIGURE_FLAG_ENCODE as u32
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
};
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn create_input_surface(&self) -> Result<NativeWindow> {
|
||||
use crate::media_error::construct_never_null;
|
||||
unsafe {
|
||||
let ptr = construct_never_null(|res| {
|
||||
ffi::AMediaCodec_createInputSurface(self.as_ptr(), res)
|
||||
})?;
|
||||
Ok(NativeWindow::from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn create_persistent_input_surface() -> Result<NativeWindow> {
|
||||
use crate::media_error::construct_never_null;
|
||||
unsafe {
|
||||
let ptr =
|
||||
construct_never_null(|res| ffi::AMediaCodec_createPersistentInputSurface(res))?;
|
||||
Ok(NativeWindow::from_ptr(ptr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dequeue_input_buffer(&self, timeout: Duration) -> Result<DequeuedInputBufferResult<'_>> {
|
||||
let result = unsafe {
|
||||
ffi::AMediaCodec_dequeueInputBuffer(
|
||||
self.as_ptr(),
|
||||
timeout
|
||||
.as_micros()
|
||||
.try_into()
|
||||
.expect("Supplied timeout is too large"),
|
||||
)
|
||||
};
|
||||
|
||||
if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize {
|
||||
Ok(DequeuedInputBufferResult::TryAgainLater)
|
||||
} else {
|
||||
let index = MediaError::from_status_if_negative(result)? as usize;
|
||||
Ok(DequeuedInputBufferResult::Buffer(InputBuffer {
|
||||
codec: self,
|
||||
index,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dequeue_output_buffer(
|
||||
&self,
|
||||
timeout: Duration,
|
||||
) -> Result<DequeuedOutputBufferInfoResult<'_>> {
|
||||
let mut info = MaybeUninit::uninit();
|
||||
|
||||
let result = unsafe {
|
||||
ffi::AMediaCodec_dequeueOutputBuffer(
|
||||
self.as_ptr(),
|
||||
info.as_mut_ptr(),
|
||||
timeout
|
||||
.as_micros()
|
||||
.try_into()
|
||||
.expect("Supplied timeout is too large"),
|
||||
)
|
||||
};
|
||||
|
||||
if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize {
|
||||
Ok(DequeuedOutputBufferInfoResult::TryAgainLater)
|
||||
} else if result == ffi::AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED as isize {
|
||||
Ok(DequeuedOutputBufferInfoResult::OutputFormatChanged)
|
||||
} else if result == ffi::AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED as isize {
|
||||
Ok(DequeuedOutputBufferInfoResult::OutputBuffersChanged)
|
||||
} else {
|
||||
let index = MediaError::from_status_if_negative(result)? as usize;
|
||||
Ok(DequeuedOutputBufferInfoResult::Buffer(OutputBuffer {
|
||||
codec: self,
|
||||
index,
|
||||
info: BufferInfo {
|
||||
inner: unsafe { info.assume_init() },
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaCodec_flush(self.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn input_buffer(&self, index: usize) -> Option<&mut [MaybeUninit<u8>]> {
|
||||
unsafe {
|
||||
let mut out_size = 0;
|
||||
let buffer_ptr = ffi::AMediaCodec_getInputBuffer(self.as_ptr(), index, &mut out_size);
|
||||
if buffer_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(slice::from_raw_parts_mut(buffer_ptr.cast(), out_size))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_buffer(&self, index: usize) -> Option<&[u8]> {
|
||||
unsafe {
|
||||
let mut out_size = 0;
|
||||
let buffer_ptr = ffi::AMediaCodec_getOutputBuffer(self.as_ptr(), index, &mut out_size);
|
||||
if buffer_ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(slice::from_raw_parts(buffer_ptr, out_size))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
pub fn input_format(&self) -> MediaFormat {
|
||||
let inner = NonNull::new(unsafe { ffi::AMediaCodec_getInputFormat(self.as_ptr()) })
|
||||
.expect("AMediaCodec_getInputFormat returned NULL");
|
||||
unsafe { MediaFormat::from_ptr(inner) }
|
||||
}
|
||||
|
||||
pub fn output_format(&self) -> MediaFormat {
|
||||
let inner = NonNull::new(unsafe { ffi::AMediaCodec_getOutputFormat(self.as_ptr()) })
|
||||
.expect("AMediaCodec_getOutputFormat returned NULL");
|
||||
unsafe { MediaFormat::from_ptr(inner) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
pub fn name(&self) -> Result<String> {
|
||||
use crate::media_error::construct;
|
||||
unsafe {
|
||||
let name_ptr = construct(|name| ffi::AMediaCodec_getName(self.as_ptr(), name))?;
|
||||
let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_owned();
|
||||
ffi::AMediaCodec_releaseName(self.as_ptr(), name_ptr);
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_input_buffer(
|
||||
&self,
|
||||
buffer: InputBuffer<'_>,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
time: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
debug_assert!(ptr::eq(self, buffer.codec));
|
||||
self.queue_input_buffer_by_index(buffer.index, offset, size, time, flags)
|
||||
}
|
||||
|
||||
pub fn queue_input_buffer_by_index(
|
||||
&self,
|
||||
buffer_index: usize,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
time: u64,
|
||||
flags: u32,
|
||||
) -> Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::AMediaCodec_queueInputBuffer(
|
||||
self.as_ptr(),
|
||||
buffer_index,
|
||||
offset as ffi::off_t,
|
||||
size,
|
||||
time,
|
||||
flags,
|
||||
)
|
||||
};
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn release_output_buffer(&self, buffer: OutputBuffer<'_>, render: bool) -> Result<()> {
|
||||
debug_assert!(ptr::eq(self, buffer.codec));
|
||||
self.release_output_buffer_by_index(buffer.index, render)
|
||||
}
|
||||
|
||||
pub fn release_output_buffer_by_index(&self, buffer_index: usize, render: bool) -> Result<()> {
|
||||
let status =
|
||||
unsafe { ffi::AMediaCodec_releaseOutputBuffer(self.as_ptr(), buffer_index, render) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn release_output_buffer_at_time(
|
||||
&self,
|
||||
buffer: OutputBuffer<'_>,
|
||||
timestamp_ns: i64,
|
||||
) -> Result<()> {
|
||||
debug_assert!(ptr::eq(self, buffer.codec));
|
||||
self.release_output_buffer_at_time_by_index(buffer.index, timestamp_ns)
|
||||
}
|
||||
|
||||
pub fn release_output_buffer_at_time_by_index(
|
||||
&self,
|
||||
buffer_index: usize,
|
||||
timestamp_ns: i64,
|
||||
) -> Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::AMediaCodec_releaseOutputBufferAtTime(self.as_ptr(), buffer_index, timestamp_ns)
|
||||
};
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn set_input_surface(&self, surface: &NativeWindow) -> Result<()> {
|
||||
let status =
|
||||
unsafe { ffi::AMediaCodec_setInputSurface(self.as_ptr(), surface.ptr().as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn set_output_surface(&self, surface: &NativeWindow) -> Result<()> {
|
||||
let status =
|
||||
unsafe { ffi::AMediaCodec_setOutputSurface(self.as_ptr(), surface.ptr().as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn set_parameters(&self, params: MediaFormat) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaCodec_setParameters(self.as_ptr(), params.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub fn set_signal_end_of_input_stream(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaCodec_signalEndOfInputStream(self.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaCodec_start(self.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaCodec_stop(self.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MediaCodec {
|
||||
fn drop(&mut self) {
|
||||
let status = unsafe { ffi::AMediaCodec_delete(self.as_ptr()) };
|
||||
MediaError::from_status(status).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputBuffer<'a> {
|
||||
codec: &'a MediaCodec,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl InputBuffer<'_> {
|
||||
pub fn buffer_mut(&mut self) -> &mut [MaybeUninit<u8>] {
|
||||
self.codec.input_buffer(self.index).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"AMediaCodec_getInputBuffer returned NULL for index {}",
|
||||
self.index
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DequeuedInputBufferResult<'a> {
|
||||
Buffer(InputBuffer<'a>),
|
||||
TryAgainLater,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OutputBuffer<'a> {
|
||||
codec: &'a MediaCodec,
|
||||
index: usize,
|
||||
info: BufferInfo,
|
||||
}
|
||||
|
||||
impl OutputBuffer<'_> {
|
||||
pub fn buffer(&self) -> &[u8] {
|
||||
self.codec.output_buffer(self.index).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"AMediaCodec_getOutputBuffer returned NULL for index {}",
|
||||
self.index
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
pub fn format(&self) -> MediaFormat {
|
||||
let inner = NonNull::new(unsafe {
|
||||
ffi::AMediaCodec_getBufferFormat(self.codec.as_ptr(), self.index)
|
||||
})
|
||||
.expect("AMediaCodec_getBufferFormat returned NULL");
|
||||
unsafe { MediaFormat::from_ptr(inner) }
|
||||
}
|
||||
|
||||
pub fn info(&self) -> &BufferInfo {
|
||||
&self.info
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DequeuedOutputBufferInfoResult<'a> {
|
||||
Buffer(OutputBuffer<'a>),
|
||||
TryAgainLater,
|
||||
OutputFormatChanged,
|
||||
OutputBuffersChanged,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct BufferInfo {
|
||||
inner: ffi::AMediaCodecBufferInfo,
|
||||
}
|
||||
|
||||
impl BufferInfo {
|
||||
pub fn offset(&self) -> i32 {
|
||||
self.inner.offset
|
||||
}
|
||||
|
||||
pub fn size(&self) -> i32 {
|
||||
self.inner.size
|
||||
}
|
||||
|
||||
pub fn presentation_time_us(&self) -> i64 {
|
||||
self.inner.presentationTimeUs
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u32 {
|
||||
self.inner.flags
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ActionCode(pub i32);
|
||||
|
||||
impl ActionCode {
|
||||
pub fn is_recoverable(self) -> bool {
|
||||
unsafe { ffi::AMediaCodecActionCode_isRecoverable(self.0) }
|
||||
}
|
||||
|
||||
pub fn is_transient(self) -> bool {
|
||||
unsafe { ffi::AMediaCodecActionCode_isTransient(self.0) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
//! Bindings for [`AMediaFormat`]
|
||||
//!
|
||||
//! [`AMediaFormat`]: https://developer.android.com/ndk/reference/group/media#amediaformat
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
fmt,
|
||||
ptr::{self, NonNull},
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::media_error::{MediaError, Result};
|
||||
|
||||
/// A native [`AMediaFormat *`]
|
||||
///
|
||||
/// [`AMediaFormat *`]: https://developer.android.com/ndk/reference/group/media#amediaformat
|
||||
#[doc(alias = "AMediaFormat")]
|
||||
pub struct MediaFormat {
|
||||
inner: NonNull<ffi::AMediaFormat>,
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaFormat {
|
||||
/// Human readable representation of the format.
|
||||
#[doc(alias = "AMediaFormat_toString")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let c_str = unsafe { CStr::from_ptr(ffi::AMediaFormat_toString(self.as_ptr())) };
|
||||
f.write_str(c_str.to_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MediaFormat {
|
||||
#[doc(alias = "AMediaFormat_toString")]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "MediaFormat({:?}: {})", self.inner, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MediaFormat {
|
||||
#[doc(alias = "AMediaFormat_new")]
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaFormat {
|
||||
/// Assumes ownership of `ptr`
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid pointer to an Android [`ffi::AMediaFormat`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::AMediaFormat>) -> Self {
|
||||
Self { inner: ptr }
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut ffi::AMediaFormat {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_new")]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: NonNull::new(unsafe { ffi::AMediaFormat_new() }).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_getInt32")]
|
||||
pub fn i32(&self, key: &str) -> Option<i32> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = 0;
|
||||
if unsafe { ffi::AMediaFormat_getInt32(self.as_ptr(), name.as_ptr(), &mut out) } {
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_getInt64")]
|
||||
pub fn i64(&self, key: &str) -> Option<i64> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = 0;
|
||||
if unsafe { ffi::AMediaFormat_getInt64(self.as_ptr(), name.as_ptr(), &mut out) } {
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_getFloat")]
|
||||
pub fn f32(&self, key: &str) -> Option<f32> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = 0.0;
|
||||
if unsafe { ffi::AMediaFormat_getFloat(self.as_ptr(), name.as_ptr(), &mut out) } {
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_getSize")]
|
||||
pub fn usize(&self, key: &str) -> Option<usize> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = 0;
|
||||
if unsafe { ffi::AMediaFormat_getSize(self.as_ptr(), name.as_ptr(), &mut out) } {
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_getBuffer")]
|
||||
pub fn buffer(&self, key: &str) -> Option<&[u8]> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out_buffer = ptr::null_mut();
|
||||
let mut out_size = 0;
|
||||
unsafe {
|
||||
ffi::AMediaFormat_getBuffer(
|
||||
self.as_ptr(),
|
||||
name.as_ptr(),
|
||||
&mut out_buffer,
|
||||
&mut out_size,
|
||||
)
|
||||
}
|
||||
.then(|| unsafe { slice::from_raw_parts(out_buffer.cast(), out_size) })
|
||||
}
|
||||
|
||||
/// The returned `&str` borrow is only valid until the next call to [`MediaFormat::str()`] for
|
||||
/// the same key.
|
||||
#[doc(alias = "AMediaFormat_getString")]
|
||||
pub fn str(&mut self, key: &str) -> Option<&str> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = ptr::null();
|
||||
unsafe { ffi::AMediaFormat_getString(self.as_ptr(), name.as_ptr(), &mut out) }
|
||||
.then(|| unsafe { CStr::from_ptr(out) }.to_str().unwrap())
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_setInt32")]
|
||||
pub fn set_i32(&mut self, key: &str, value: i32) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setInt32(self.as_ptr(), name.as_ptr(), value) }
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_setInt64")]
|
||||
pub fn set_i64(&mut self, key: &str, value: i64) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setInt64(self.as_ptr(), name.as_ptr(), value) }
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_setFloat")]
|
||||
pub fn set_f32(&mut self, key: &str, value: f32) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setFloat(self.as_ptr(), name.as_ptr(), value) }
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_setString")]
|
||||
pub fn set_str(&mut self, key: &str, value: &str) {
|
||||
let name = CString::new(key).unwrap();
|
||||
let c_string = CString::new(value).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setString(self.as_ptr(), name.as_ptr(), c_string.as_ptr()) }
|
||||
}
|
||||
|
||||
#[doc(alias = "AMediaFormat_setBuffer")]
|
||||
pub fn set_buffer(&mut self, key: &str, value: &[u8]) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe {
|
||||
ffi::AMediaFormat_setBuffer(
|
||||
self.as_ptr(),
|
||||
name.as_ptr(),
|
||||
value.as_ptr().cast(),
|
||||
value.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
#[doc(alias = "AMediaFormat_getDouble")]
|
||||
pub fn f64(&self, key: &str) -> Option<f64> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut out = 0.0;
|
||||
if unsafe { ffi::AMediaFormat_getDouble(self.as_ptr(), name.as_ptr(), &mut out) } {
|
||||
Some(out)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns (left, top, right, bottom)
|
||||
#[cfg(feature = "api-level-28")]
|
||||
#[doc(alias = "AMediaFormat_getRect")]
|
||||
pub fn rect(&self, key: &str) -> Option<(i32, i32, i32, i32)> {
|
||||
let name = CString::new(key).unwrap();
|
||||
let mut left = 0;
|
||||
let mut top = 0;
|
||||
let mut right = 0;
|
||||
let mut bottom = 0;
|
||||
if unsafe {
|
||||
ffi::AMediaFormat_getRect(
|
||||
self.as_ptr(),
|
||||
name.as_ptr(),
|
||||
&mut left,
|
||||
&mut top,
|
||||
&mut right,
|
||||
&mut bottom,
|
||||
)
|
||||
} {
|
||||
Some((left, top, right, bottom))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
#[doc(alias = "AMediaFormat_setDouble")]
|
||||
pub fn set_f64(&mut self, key: &str, value: f64) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setDouble(self.as_ptr(), name.as_ptr(), value) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
#[doc(alias = "AMediaFormat_setRect")]
|
||||
pub fn set_rect(&mut self, key: &str, left: i32, top: i32, right: i32, bottom: i32) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setRect(self.as_ptr(), name.as_ptr(), left, top, right, bottom) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-28")]
|
||||
#[doc(alias = "AMediaFormat_setSize")]
|
||||
pub fn set_usize(&mut self, key: &str, value: usize) {
|
||||
let name = CString::new(key).unwrap();
|
||||
unsafe { ffi::AMediaFormat_setSize(self.as_ptr(), name.as_ptr(), value) }
|
||||
}
|
||||
|
||||
/// Copy one [`MediaFormat`] to another.
|
||||
#[cfg(feature = "api-level-29")]
|
||||
#[doc(alias = "AMediaFormat_copy")]
|
||||
pub fn copy(&self, to: &mut Self) -> Result<()> {
|
||||
let status = unsafe { ffi::AMediaFormat_copy(to.as_ptr(), self.as_ptr()) };
|
||||
MediaError::from_status(status)
|
||||
}
|
||||
|
||||
/// Clones this [`MediaFormat`] into a [`MediaFormat::new()`] object using
|
||||
/// [`MediaFormat::copy()`].
|
||||
#[cfg(feature = "api-level-29")]
|
||||
#[doc(alias = "AMediaFormat_new")]
|
||||
#[doc(alias = "AMediaFormat_copy")]
|
||||
pub fn try_clone(&self) -> Result<Self> {
|
||||
let mut copy = Self::new();
|
||||
self.copy(&mut copy)?;
|
||||
Ok(copy)
|
||||
}
|
||||
|
||||
/// Remove all key/value pairs from this [`MediaFormat`].
|
||||
#[cfg(feature = "api-level-29")]
|
||||
#[doc(alias = "AMediaFormat_clear")]
|
||||
pub fn clear(&mut self) {
|
||||
unsafe { ffi::AMediaFormat_clear(self.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MediaFormat {
|
||||
#[doc(alias = "AMediaFormat_delete")]
|
||||
fn drop(&mut self) {
|
||||
let status = unsafe { ffi::AMediaFormat_delete(self.as_ptr()) };
|
||||
MediaError::from_status(status).unwrap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Bindings for the NDK media classes.
|
||||
//!
|
||||
//! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media)
|
||||
#![cfg(feature = "media")]
|
||||
|
||||
pub mod image_reader;
|
||||
pub mod media_codec;
|
||||
pub mod media_format;
|
||||
@@ -0,0 +1,140 @@
|
||||
//! Bindings for NDK media status codes.
|
||||
//!
|
||||
//! Also used outside of `libmediandk.so` in `libamidi.so` for example.
|
||||
#![cfg(feature = "media")]
|
||||
// The cfg(feature) bounds for some pub(crate) fn uses are non-trivial and will become even more
|
||||
// complex going forward. Allow them to be unused when compiling with certain feature combinations.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{fmt, mem::MaybeUninit, ptr::NonNull};
|
||||
|
||||
use num_enum::{FromPrimitive, IntoPrimitive};
|
||||
|
||||
pub type Result<T, E = MediaError> = std::result::Result<T, E>;
|
||||
|
||||
/// Media Status codes for [`media_status_t`](https://developer.android.com/ndk/reference/group/media#group___media_1ga009a49041fe39f7bdc6d8b5cddbe760c)
|
||||
#[repr(i32)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
|
||||
#[doc(alias = "media_status_t")]
|
||||
#[non_exhaustive]
|
||||
pub enum MediaError {
|
||||
#[doc(alias = "AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE")]
|
||||
CodecErrorInsufficientResource = ffi::media_status_t::AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE.0,
|
||||
#[doc(alias = "AMEDIACODEC_ERROR_RECLAIMED")]
|
||||
CodecErrorReclaimed = ffi::media_status_t::AMEDIACODEC_ERROR_RECLAIMED.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_UNKNOWN")]
|
||||
ErrorUnknown = ffi::media_status_t::AMEDIA_ERROR_UNKNOWN.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_MALFORMED")]
|
||||
ErrorMalformed = ffi::media_status_t::AMEDIA_ERROR_MALFORMED.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_UNSUPPORTED")]
|
||||
ErrorUnsupported = ffi::media_status_t::AMEDIA_ERROR_UNSUPPORTED.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_INVALID_OBJECT")]
|
||||
ErrorInvalidObject = ffi::media_status_t::AMEDIA_ERROR_INVALID_OBJECT.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_INVALID_PARAMETER")]
|
||||
ErrorInvalidParameter = ffi::media_status_t::AMEDIA_ERROR_INVALID_PARAMETER.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_INVALID_OPERATION")]
|
||||
ErrorInvalidOperation = ffi::media_status_t::AMEDIA_ERROR_INVALID_OPERATION.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_END_OF_STREAM")]
|
||||
ErrorEndOfStream = ffi::media_status_t::AMEDIA_ERROR_END_OF_STREAM.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_IO")]
|
||||
ErrorIo = ffi::media_status_t::AMEDIA_ERROR_IO.0,
|
||||
#[doc(alias = "AMEDIA_ERROR_WOULD_BLOCK")]
|
||||
ErrorWouldBlock = ffi::media_status_t::AMEDIA_ERROR_WOULD_BLOCK.0,
|
||||
#[doc(alias = "AMEDIA_DRM_ERROR_BASE")]
|
||||
DrmErrorBase = ffi::media_status_t::AMEDIA_DRM_ERROR_BASE.0,
|
||||
#[doc(alias = "AMEDIA_DRM_NOT_PROVISIONED")]
|
||||
DrmNotProvisioned = ffi::media_status_t::AMEDIA_DRM_NOT_PROVISIONED.0,
|
||||
#[doc(alias = "AMEDIA_DRM_RESOURCE_BUSY")]
|
||||
DrmResourceBusy = ffi::media_status_t::AMEDIA_DRM_RESOURCE_BUSY.0,
|
||||
#[doc(alias = "AMEDIA_DRM_DEVICE_REVOKED")]
|
||||
DrmDeviceRevoked = ffi::media_status_t::AMEDIA_DRM_DEVICE_REVOKED.0,
|
||||
#[doc(alias = "AMEDIA_DRM_SHORT_BUFFER")]
|
||||
DrmShortBuffer = ffi::media_status_t::AMEDIA_DRM_SHORT_BUFFER.0,
|
||||
#[doc(alias = "AMEDIA_DRM_SESSION_NOT_OPENED")]
|
||||
DrmSessionNotOpened = ffi::media_status_t::AMEDIA_DRM_SESSION_NOT_OPENED.0,
|
||||
#[doc(alias = "AMEDIA_DRM_TAMPER_DETECTED")]
|
||||
DrmTamperDetected = ffi::media_status_t::AMEDIA_DRM_TAMPER_DETECTED.0,
|
||||
#[doc(alias = "AMEDIA_DRM_VERIFY_FAILED")]
|
||||
DrmVerifyFailed = ffi::media_status_t::AMEDIA_DRM_VERIFY_FAILED.0,
|
||||
#[doc(alias = "AMEDIA_DRM_NEED_KEY")]
|
||||
DrmNeedKey = ffi::media_status_t::AMEDIA_DRM_NEED_KEY.0,
|
||||
#[doc(alias = "AMEDIA_DRM_LICENSE_EXPIRED")]
|
||||
DrmLicenseExpired = ffi::media_status_t::AMEDIA_DRM_LICENSE_EXPIRED.0,
|
||||
#[doc(alias = "AMEDIA_IMGREADER_ERROR_BASE")]
|
||||
ImgreaderErrorBase = ffi::media_status_t::AMEDIA_IMGREADER_ERROR_BASE.0,
|
||||
#[doc(alias = "AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE")]
|
||||
ImgreaderCannotLockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE.0,
|
||||
#[doc(alias = "AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE")]
|
||||
ImgreaderCannotUnlockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE.0,
|
||||
#[doc(alias = "AMEDIA_IMGREADER_IMAGE_NOT_LOCKED")]
|
||||
ImgreaderImageNotLocked = ffi::media_status_t::AMEDIA_IMGREADER_IMAGE_NOT_LOCKED.0,
|
||||
|
||||
/// This error code is unknown to the [`ndk`][crate] crate. Please report an issue if you
|
||||
/// believe this code needs to be added to our mapping.
|
||||
// Use the OK discriminant, as no-one will be able to call `as i32` and only has access to the
|
||||
// constants via `From` provided by `IntoPrimitive` which reads the contained value.
|
||||
// An autogenerated `<previous variant> + 1` discriminant is normally fine, except that the
|
||||
// previous variant is negative and `+1` would match the variant before that.
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32) = ffi::media_status_t::AMEDIA_OK.0,
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for MediaError {}
|
||||
|
||||
impl MediaError {
|
||||
/// Returns [`Ok`] on [`ffi::media_status_t::AMEDIA_OK`], [`Err`] otherwise (including positive
|
||||
/// values).
|
||||
///
|
||||
/// Note that some known error codes (currently only for `AMediaCodec`) are positive.
|
||||
pub(crate) fn from_status(status: ffi::media_status_t) -> Result<()> {
|
||||
match status {
|
||||
ffi::media_status_t::AMEDIA_OK => Ok(()),
|
||||
x => Err(Self::from(x.0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the original value in [`Ok`] if it is not negative, [`Err`] otherwise.
|
||||
///
|
||||
/// Note that some [`ffi::media_status_t`] codes are positive but will never be returned as
|
||||
/// [`Err`] from this function. As of writing these codes are specific to the `AMediaCodec` API
|
||||
/// and should not be handled generically.
|
||||
pub(crate) fn from_status_if_negative<T: Into<isize> + Copy>(value: T) -> Result<T> {
|
||||
let v = value.into();
|
||||
if v >= 0 {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(Self::from(
|
||||
i32::try_from(v).expect("Error code out of bounds"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the `with_ptr` construction function with a pointer to uninitialized stack memory,
|
||||
/// expecting `with_ptr` to initialize it or otherwise return an error code.
|
||||
pub(crate) fn construct<T>(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result<T> {
|
||||
let mut result = MaybeUninit::uninit();
|
||||
let status = with_ptr(result.as_mut_ptr());
|
||||
MediaError::from_status(status).map(|()| unsafe { result.assume_init() })
|
||||
}
|
||||
|
||||
/// Calls the `with_ptr` construction function with a pointer to a pointer, and expects `with_ptr`
|
||||
/// to initialize the second pointer to a valid address. That address is returned in the form of a
|
||||
/// [`NonNull`] object.
|
||||
pub(crate) fn construct_never_null<T>(
|
||||
with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t,
|
||||
) -> Result<NonNull<T>> {
|
||||
let result = construct(with_ptr)?;
|
||||
Ok(if cfg!(debug_assertions) {
|
||||
NonNull::new(result).expect("result should never be null")
|
||||
} else {
|
||||
unsafe { NonNull::new_unchecked(result) }
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
//! Bindings for [`ANativeActivity`]
|
||||
//!
|
||||
//! [`ANativeActivity`]: https://developer.android.com/ndk/reference/group/native-activity#anativeactivity
|
||||
|
||||
use super::hardware_buffer_format::HardwareBufferFormat;
|
||||
use std::{
|
||||
ffi::{CStr, OsStr},
|
||||
os::{raw::c_void, unix::prelude::OsStrExt},
|
||||
path::Path,
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Window flags, as per the Java API at [`android.view.WindowManager.LayoutParams`].
|
||||
///
|
||||
/// <https://developer.android.com/ndk/reference/group/native-activity#group___native_activity_1ga2f1398dba5e4a5616b83437528bdb28e>
|
||||
///
|
||||
/// [`android.view.WindowManager.LayoutParams`]: https://developer.android.com/reference/android/view/WindowManager.LayoutParams
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct WindowFlags : u32 {
|
||||
const ALLOW_LOCK_WHILE_SCREEN_ON = ffi::AWINDOW_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
|
||||
const DIM_BEHIND = ffi::AWINDOW_FLAG_DIM_BEHIND;
|
||||
#[deprecated = "Deprecated. Blurring is no longer supported."]
|
||||
const BLUR_BEHIND = ffi::AWINDOW_FLAG_BLUR_BEHIND;
|
||||
const NOT_FOCUSABLE = ffi::AWINDOW_FLAG_NOT_FOCUSABLE;
|
||||
const NOT_TOUCHABLE = ffi::AWINDOW_FLAG_NOT_TOUCHABLE;
|
||||
const NOT_TOUCH_MODAL = ffi::AWINDOW_FLAG_NOT_TOUCH_MODAL;
|
||||
#[deprecated = "This constant was deprecated in API level 20. This flag has no effect."]
|
||||
const TOUCHABLE_WHEN_WAKING = ffi::AWINDOW_FLAG_TOUCHABLE_WHEN_WAKING;
|
||||
const KEEP_SCREEN_ON = ffi::AWINDOW_FLAG_KEEP_SCREEN_ON;
|
||||
const LAYOUT_IN_SCREEN = ffi::AWINDOW_FLAG_LAYOUT_IN_SCREEN;
|
||||
const LAYOUT_NO_LIMITS = ffi::AWINDOW_FLAG_LAYOUT_NO_LIMITS;
|
||||
const FULLSCREEN = ffi::AWINDOW_FLAG_FULLSCREEN;
|
||||
#[cfg_attr(feature = "api-level-30", deprecated = "This constant was deprecated in API level 30. This value became API \"by accident\", and shouldn't be used by 3rd party applications.")]
|
||||
const FORCE_NOT_FULLSCREEN = ffi::AWINDOW_FLAG_FORCE_NOT_FULLSCREEN;
|
||||
#[deprecated = "This constant was deprecated in API level 17. This flag is no longer used."]
|
||||
const DITHER = ffi::AWINDOW_FLAG_DITHER;
|
||||
const SECURE = ffi::AWINDOW_FLAG_SECURE;
|
||||
const SCALED = ffi::AWINDOW_FLAG_SCALED;
|
||||
const IGNORE_CHEEK_PRESSES = ffi::AWINDOW_FLAG_IGNORE_CHEEK_PRESSES;
|
||||
const LAYOUT_INSET_DECOR = ffi::AWINDOW_FLAG_LAYOUT_INSET_DECOR;
|
||||
const ALT_FOCUSABLE_IM = ffi::AWINDOW_FLAG_ALT_FOCUSABLE_IM;
|
||||
const WATCH_OUTSIDE_TOUCH = ffi::AWINDOW_FLAG_WATCH_OUTSIDE_TOUCH;
|
||||
const SHOW_WHEN_LOCKED = ffi::AWINDOW_FLAG_SHOW_WHEN_LOCKED;
|
||||
const SHOW_WALLPAPER = ffi::AWINDOW_FLAG_SHOW_WALLPAPER;
|
||||
const TURN_SCREEN_ON = ffi::AWINDOW_FLAG_TURN_SCREEN_ON;
|
||||
#[cfg_attr(feature = "api-level-26", deprecated = "This constant was deprecated in API level 26. Use `SHOW_WHEN_LOCKED` instead.")]
|
||||
const DISMISS_KEYGUARD = ffi::AWINDOW_FLAG_DISMISS_KEYGUARD;
|
||||
const ATTACHED_IN_DECOR = 0x40000000;
|
||||
|
||||
// https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags
|
||||
const _ = !0;
|
||||
}
|
||||
}
|
||||
|
||||
/// A native [`ANativeActivity *`]
|
||||
///
|
||||
/// This is either provided in [`ffi::ANativeActivity_onCreate()`], or accessible through
|
||||
/// `ndk_glue::native_activity()`.
|
||||
///
|
||||
/// [`ANativeActivity *`]: https://developer.android.com/ndk/reference/struct/a-native-activity
|
||||
#[derive(Debug)]
|
||||
pub struct NativeActivity {
|
||||
ptr: NonNull<ffi::ANativeActivity>,
|
||||
}
|
||||
|
||||
// It gets shared between threads in `ndk-glue`
|
||||
unsafe impl Send for NativeActivity {}
|
||||
unsafe impl Sync for NativeActivity {}
|
||||
|
||||
impl NativeActivity {
|
||||
/// Create a [`NativeActivity`] from a pointer
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that it is a valid pointer to a native
|
||||
/// [`ffi::ANativeActivity`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::ANativeActivity>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// The pointer to the native `ANativeActivity`
|
||||
pub fn ptr(&self) -> NonNull<ffi::ANativeActivity> {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods that relate to fields of the struct itself
|
||||
///
|
||||
/// The relevant NDK docs can be found
|
||||
/// [here](https://developer.android.com/ndk/reference/struct/a-native-activity).
|
||||
impl NativeActivity {
|
||||
/// The platform's SDK version code
|
||||
pub fn sdk_version(&self) -> i32 {
|
||||
unsafe { self.ptr.as_ref().sdkVersion }
|
||||
}
|
||||
|
||||
/// Path to this application's internal data directory
|
||||
pub fn internal_data_path(&self) -> &Path {
|
||||
OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().internalDataPath) }.to_bytes())
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
/// Path to this application's external (removable, mountable) data directory
|
||||
pub fn external_data_path(&self) -> &Path {
|
||||
OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().externalDataPath) }.to_bytes())
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
/// This app's asset manager, which can be used to access assets from the `.apk` file.
|
||||
pub fn asset_manager(&self) -> crate::asset::AssetManager {
|
||||
unsafe {
|
||||
crate::asset::AssetManager::from_ptr(
|
||||
NonNull::new(self.ptr.as_ref().assetManager).unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Instance data associated with the activity
|
||||
pub fn instance(&self) -> *mut c_void {
|
||||
unsafe { self.ptr.as_ref().instance }
|
||||
}
|
||||
|
||||
/// Set the instance data associated with the activity
|
||||
///
|
||||
/// # Safety
|
||||
/// This can invalidate assumptions held by `ndk-glue`, as well as cause data
|
||||
/// races with concurrent access to the instance data.
|
||||
pub unsafe fn set_instance(&mut self, data: *mut c_void) {
|
||||
// FIXME Does this create undefined behavior by creating a mutable reference to what could
|
||||
// also be accessed immutably at the same time?
|
||||
//
|
||||
// I think that as long as we warn the users to avoid concurrent access, and we pass along
|
||||
// the `unsafe` burden, it's OK.
|
||||
self.ptr.as_mut().instance = data;
|
||||
}
|
||||
|
||||
/// This process's `JavaVM` object.
|
||||
///
|
||||
/// Usage with [__jni__](https://crates.io/crates/jni) crate:
|
||||
/// ```no_run
|
||||
/// # use ndk::native_activity::NativeActivity;
|
||||
/// # let native_activity: NativeActivity = unimplemented!();
|
||||
/// let vm_ptr = native_activity.vm();
|
||||
/// let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }.unwrap();
|
||||
/// let env = vm.attach_current_thread();
|
||||
/// // Do JNI with env ...
|
||||
/// ```
|
||||
pub fn vm(&self) -> *mut jni_sys::JavaVM {
|
||||
unsafe { self.ptr.as_ref() }.vm
|
||||
}
|
||||
|
||||
/// The [`android.app.NativeActivity`] instance
|
||||
///
|
||||
/// In the JNI, this is named `clazz`; however, as the docs say, "it should really be named
|
||||
/// 'activity' instead of 'clazz', since it's a reference to the NativeActivity instance".
|
||||
///
|
||||
/// [`android.app.NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity
|
||||
pub fn activity(&self) -> jni_sys::jobject {
|
||||
unsafe { self.ptr.as_ref() }.clazz
|
||||
}
|
||||
|
||||
/// Path to the directory with the application's OBB files.
|
||||
///
|
||||
/// # Safety
|
||||
/// Only available as of Honeycomb (Android 3.0+, API level 11+)
|
||||
pub unsafe fn obb_path(&self) -> &Path {
|
||||
OsStr::from_bytes(CStr::from_ptr(self.ptr.as_ref().obbPath).to_bytes()).as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods that relate to `ANativeActivity_*` functions.
|
||||
///
|
||||
/// The relevant NDK docs can be found
|
||||
/// [here](https://developer.android.com/ndk/reference/group/native-activity).
|
||||
impl NativeActivity {
|
||||
/// Sends a destroy event to the activity and stops it.
|
||||
pub fn finish(&self) {
|
||||
unsafe { ffi::ANativeActivity_finish(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Shows the IME (the on-screen keyboard).
|
||||
///
|
||||
/// If `force` is true, the `SHOW_FORCED` flag is used; otherwise, the `SHOW_IMPLICIT` flag is
|
||||
/// used. Depending on the value of this flag, the `hide_soft_input` method with behave
|
||||
/// differently. See [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2)
|
||||
/// for more information.
|
||||
pub fn show_soft_input(&self, force: bool) {
|
||||
let flag = if force {
|
||||
ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED
|
||||
} else {
|
||||
ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
};
|
||||
unsafe { ffi::ANativeActivity_showSoftInput(self.ptr.as_ptr(), flag) }
|
||||
}
|
||||
|
||||
/// Hides the IME (the on-screen keyboard).
|
||||
///
|
||||
/// If `not_always` is true, the `HIDE_NOT_ALWAYS` flag is used; otherwise, the
|
||||
/// `HIDE_IMPLICIT_ONLY` flag is used. Depending on the value of this flag and the way the IME
|
||||
/// was shown, it may or may not be hidden. See [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2)
|
||||
/// for more information.
|
||||
pub fn hide_soft_input(&self, not_always: bool) {
|
||||
let flag = if not_always {
|
||||
ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS
|
||||
} else {
|
||||
ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
};
|
||||
unsafe { ffi::ANativeActivity_hideSoftInput(self.ptr.as_ptr(), flag) }
|
||||
}
|
||||
|
||||
/// Change the window format of the given activity.
|
||||
///
|
||||
/// Calls [`getWindow().setFormat()`] of the given activity. Note that this method can be
|
||||
/// called from any thread; it will send a message to the main thread of the process where the
|
||||
/// Java finish call will take place.
|
||||
///
|
||||
/// [`getWindow().setFormat()`]: https://developer.android.com/reference/android/view/Window#setFormat(int)
|
||||
pub fn set_window_format(&self, format: HardwareBufferFormat) {
|
||||
unsafe { ffi::ANativeActivity_setWindowFormat(self.ptr.as_ptr(), format.into()) }
|
||||
}
|
||||
|
||||
/// Change the window flags of the given activity.
|
||||
///
|
||||
/// Calls [`getWindow().setFlags()`] of the given activity.
|
||||
///
|
||||
/// Note that this method can be called from any thread; it will send a message to the main
|
||||
/// thread of the process where the Java finish call will take place.
|
||||
///
|
||||
/// [`getWindow().setFlags()`]: https://developer.android.com/reference/android/view/Window#setFlags(int,%20int)
|
||||
pub fn set_window_flags(&self, add_flags: WindowFlags, remove_flags: WindowFlags) {
|
||||
unsafe {
|
||||
ffi::ANativeActivity_setWindowFlags(
|
||||
self.ptr.as_ptr(),
|
||||
add_flags.bits(),
|
||||
remove_flags.bits(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
//! Bindings for [`ANativeWindow`]
|
||||
//!
|
||||
//! [`ANativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow
|
||||
|
||||
use std::{ffi::c_void, io, mem::MaybeUninit, ptr::NonNull};
|
||||
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
|
||||
use super::{hardware_buffer_format::HardwareBufferFormat, utils::status_to_io_result};
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-28"))]
|
||||
use crate::data_space::DataSpace;
|
||||
|
||||
pub type Rect = ffi::ARect;
|
||||
|
||||
// [`NativeWindow`] represents the producer end of an image queue
|
||||
///
|
||||
/// It is the C counterpart of the [`android.view.Surface`] object in Java, and can be converted
|
||||
/// both ways. Depending on the consumer, images submitted to [`NativeWindow`] can be shown on the
|
||||
/// display or sent to other consumers, such as video encoders.
|
||||
///
|
||||
/// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface
|
||||
#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct NativeWindow {
|
||||
ptr: NonNull<ffi::ANativeWindow>,
|
||||
}
|
||||
|
||||
unsafe impl Send for NativeWindow {}
|
||||
unsafe impl Sync for NativeWindow {}
|
||||
|
||||
impl Drop for NativeWindow {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ANativeWindow_release(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for NativeWindow {
|
||||
fn clone(&self) -> Self {
|
||||
unsafe { ffi::ANativeWindow_acquire(self.ptr.as_ptr()) }
|
||||
Self { ptr: self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_04")]
|
||||
unsafe impl rwh_04::HasRawWindowHandle for NativeWindow {
|
||||
fn raw_window_handle(&self) -> rwh_04::RawWindowHandle {
|
||||
let mut handle = rwh_04::AndroidNdkHandle::empty();
|
||||
handle.a_native_window = self.ptr.as_ptr().cast();
|
||||
rwh_04::RawWindowHandle::AndroidNdk(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_05")]
|
||||
unsafe impl rwh_05::HasRawWindowHandle for NativeWindow {
|
||||
fn raw_window_handle(&self) -> rwh_05::RawWindowHandle {
|
||||
let mut handle = rwh_05::AndroidNdkWindowHandle::empty();
|
||||
handle.a_native_window = self.ptr.as_ptr().cast();
|
||||
rwh_05::RawWindowHandle::AndroidNdk(handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rwh_06")]
|
||||
impl rwh_06::HasWindowHandle for NativeWindow {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let handle = rwh_06::AndroidNdkWindowHandle::new(self.ptr.cast());
|
||||
let handle = rwh_06::RawWindowHandle::AndroidNdk(handle);
|
||||
// SAFETY: All fields of the "raw" `AndroidNdkWindowHandle` struct are filled out. The
|
||||
// returned pointer is also kept valid by `NativeWindow` (until `Drop`), which is lifetime-
|
||||
// borrowed in the returned `WindowHandle<'_>` and cannot be outlived. Its value won't
|
||||
// change throughout the lifetime of this `NativeWindow`.
|
||||
Ok(unsafe { rwh_06::WindowHandle::borrow_raw(handle) })
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeWindow {
|
||||
/// Assumes ownership of `ptr`
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::ANativeWindow>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Acquires ownership of `ptr`
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`].
|
||||
pub unsafe fn clone_from_ptr(ptr: NonNull<ffi::ANativeWindow>) -> Self {
|
||||
ffi::ANativeWindow_acquire(ptr.as_ptr());
|
||||
Self::from_ptr(ptr)
|
||||
}
|
||||
|
||||
pub fn ptr(&self) -> NonNull<ffi::ANativeWindow> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i32 {
|
||||
unsafe { ffi::ANativeWindow_getHeight(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
unsafe { ffi::ANativeWindow_getWidth(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Return the current pixel format ([`HardwareBufferFormat`]) of the window surface.
|
||||
pub fn format(&self) -> HardwareBufferFormat {
|
||||
let value = unsafe { ffi::ANativeWindow_getFormat(self.ptr.as_ptr()) };
|
||||
value.into()
|
||||
}
|
||||
|
||||
/// Change the format and size of the window buffers.
|
||||
///
|
||||
/// The width and height control the number of pixels in the buffers, not the dimensions of the
|
||||
/// window on screen. If these are different than the window's physical size, then its buffer
|
||||
/// will be scaled to match that size when compositing it to the screen. The width and height
|
||||
/// must be either both zero or both non-zero.
|
||||
///
|
||||
/// For all of these parameters, if `0` or [`None`] is supplied then the window's base value
|
||||
/// will come back in force.
|
||||
pub fn set_buffers_geometry(
|
||||
&self,
|
||||
width: i32,
|
||||
height: i32,
|
||||
format: Option<HardwareBufferFormat>,
|
||||
) -> io::Result<()> {
|
||||
let format = format.map_or(0i32, |f| f.into());
|
||||
let status = unsafe {
|
||||
ffi::ANativeWindow_setBuffersGeometry(self.ptr.as_ptr(), width, height, format)
|
||||
};
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Set a transform that will be applied to future buffers posted to the window.
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-26"))]
|
||||
#[doc(alias = "ANativeWindow_setBuffersTransform")]
|
||||
pub fn set_buffers_transform(&self, transform: NativeWindowTransform) -> io::Result<()> {
|
||||
let status =
|
||||
unsafe { ffi::ANativeWindow_setBuffersTransform(self.ptr.as_ptr(), transform.bits()) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// All buffers queued after this call will be associated with the dataSpace parameter
|
||||
/// specified.
|
||||
///
|
||||
/// `data_space` specifies additional information about the buffer. For example, it can be used
|
||||
/// to convey the color space of the image data in the buffer, or it can be used to indicate
|
||||
/// that the buffers contain depth measurement data instead of color images. The default
|
||||
/// dataSpace is `0`, [`DataSpace::Unknown`], unless it has been overridden by the producer.
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-28"))]
|
||||
#[doc(alias = "ANativeWindow_setBuffersDataSpace")]
|
||||
pub fn set_buffers_data_space(&self, data_space: DataSpace) -> io::Result<()> {
|
||||
let status =
|
||||
unsafe { ffi::ANativeWindow_setBuffersDataSpace(self.ptr.as_ptr(), data_space.into()) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Get the dataspace of the buffers in this [`NativeWindow`].
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-28"))]
|
||||
#[doc(alias = "ANativeWindow_getBuffersDataSpace")]
|
||||
pub fn buffers_data_space(&self) -> io::Result<DataSpace> {
|
||||
let status = unsafe { ffi::ANativeWindow_getBuffersDataSpace(self.ptr.as_ptr()) };
|
||||
if status >= 0 {
|
||||
Ok(status.into())
|
||||
} else {
|
||||
Err(status_to_io_result(status).unwrap_err())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the intended frame rate for this window.
|
||||
///
|
||||
/// Same as [`set_frame_rate_with_change_strategy(window, frame_rate, compatibility, ChangeFrameRateStrategy::OnlyIfSeamless)`][`NativeWindow::set_frame_rate_with_change_strategy()`].
|
||||
///
|
||||
#[cfg_attr(
|
||||
not(feature = "api-level-31"),
|
||||
doc = "[`NativeWindow::set_frame_rate_with_change_strategy()`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow_setframeratewithchangestrategy"
|
||||
)]
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-30"))]
|
||||
#[doc(alias = "ANativeWindow_setFrameRate")]
|
||||
pub fn set_frame_rate(
|
||||
&self,
|
||||
frame_rate: f32,
|
||||
compatibility: FrameRateCompatibility,
|
||||
) -> io::Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::ANativeWindow_setFrameRate(self.ptr.as_ptr(), frame_rate, compatibility as i8)
|
||||
};
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Sets the intended frame rate for this window.
|
||||
///
|
||||
/// On devices that are capable of running the display at different refresh rates, the system
|
||||
/// may choose a display refresh rate to better match this window's frame rate. Usage of this
|
||||
/// API won't introduce frame rate throttling, or affect other aspects of the application's
|
||||
/// frame production pipeline. However, because the system may change the display refresh rate,
|
||||
/// calls to this function may result in changes to Choreographer callback timings, and changes
|
||||
/// to the time interval at which the system releases buffers back to the application.
|
||||
///
|
||||
/// Note that this only has an effect for windows presented on the display. If this
|
||||
/// [`NativeWindow`] is consumed by something other than the system compositor, e.g. a media
|
||||
/// codec, this call has no effect.
|
||||
///
|
||||
/// You can register for changes in the refresh rate using
|
||||
/// [`ffi::AChoreographer_registerRefreshRateCallback()`].
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `frame_rate`: The intended frame rate of this window, in frames per second. `0` is a
|
||||
/// special value that indicates the app will accept the system's choice for the display
|
||||
/// frame rate, which is the default behavior if this function isn't called. The `frame_rate`
|
||||
/// param does not need to be a valid refresh rate for this device's display - e.g., it's
|
||||
/// fine to pass `30`fps to a device that can only run the display at `60`fps.
|
||||
/// - `compatibility`: The frame rate compatibility of this window. The compatibility value may
|
||||
/// influence the system's choice of display refresh rate. See the [`FrameRateCompatibility`]
|
||||
/// values for more info. This parameter is ignored when `frame_rate` is `0`.
|
||||
/// - `change_frame_rate_strategy`: Whether display refresh rate transitions caused by this
|
||||
/// window should be seamless. A seamless transition is one that doesn't have any visual
|
||||
/// interruptions, such as a black screen for a second or two. See the
|
||||
/// [`ChangeFrameRateStrategy`] values. This parameter is ignored when `frame_rate` is `0`.
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-31"))]
|
||||
#[doc(alias = "ANativeWindow_setFrameRateWithChangeStrategy")]
|
||||
pub fn set_frame_rate_with_change_strategy(
|
||||
&self,
|
||||
frame_rate: f32,
|
||||
compatibility: FrameRateCompatibility,
|
||||
change_frame_rate_strategy: ChangeFrameRateStrategy,
|
||||
) -> io::Result<()> {
|
||||
let status = unsafe {
|
||||
ffi::ANativeWindow_setFrameRateWithChangeStrategy(
|
||||
self.ptr.as_ptr(),
|
||||
frame_rate,
|
||||
compatibility as i8,
|
||||
change_frame_rate_strategy as i8,
|
||||
)
|
||||
};
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Provides a hint to the window that buffers should be preallocated ahead of time.
|
||||
///
|
||||
/// Note that the window implementation is not guaranteed to preallocate any buffers, for
|
||||
/// instance if an implementation disallows allocation of new buffers, or if there is
|
||||
/// insufficient memory in the system to preallocate additional buffers
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-30"))]
|
||||
pub fn try_allocate_buffers(&self) {
|
||||
unsafe { ffi::ANativeWindow_tryAllocateBuffers(self.ptr.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Return the [`NativeWindow`] associated with a JNI [`android.view.Surface`] pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`] and
|
||||
/// `surface` is a valid pointer to an [`android.view.Surface`].
|
||||
///
|
||||
/// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface
|
||||
pub unsafe fn from_surface(env: *mut JNIEnv, surface: jobject) -> Option<Self> {
|
||||
let ptr = ffi::ANativeWindow_fromSurface(env, surface);
|
||||
Some(Self::from_ptr(NonNull::new(ptr)?))
|
||||
}
|
||||
|
||||
/// Return a JNI [`android.view.Surface`] pointer derived from this [`NativeWindow`].
|
||||
///
|
||||
/// # Safety
|
||||
/// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`].
|
||||
///
|
||||
/// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface
|
||||
#[cfg(feature = "api-level-26")]
|
||||
pub unsafe fn to_surface(&self, env: *mut JNIEnv) -> jobject {
|
||||
ffi::ANativeWindow_toSurface(env, self.ptr().as_ptr())
|
||||
}
|
||||
|
||||
/// Lock the window's next drawing surface for writing.
|
||||
///
|
||||
/// Optionally pass the region you intend to draw into `dirty_bounds`. When this function
|
||||
/// returns it is updated (commonly enlarged) with the actual area the caller needs to redraw.
|
||||
pub fn lock(
|
||||
&self,
|
||||
dirty_bounds: Option<&mut Rect>,
|
||||
) -> io::Result<NativeWindowBufferLockGuard<'_>> {
|
||||
let dirty_bounds = match dirty_bounds {
|
||||
Some(dirty_bounds) => dirty_bounds,
|
||||
None => std::ptr::null_mut(),
|
||||
};
|
||||
let mut buffer = MaybeUninit::uninit();
|
||||
let status = unsafe {
|
||||
ffi::ANativeWindow_lock(self.ptr.as_ptr(), buffer.as_mut_ptr(), dirty_bounds)
|
||||
};
|
||||
status_to_io_result(status)?;
|
||||
|
||||
Ok(NativeWindowBufferLockGuard {
|
||||
window: self,
|
||||
buffer: unsafe { buffer.assume_init() },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Lock holding the next drawing surface for writing. It is unlocked and posted on [`drop()`].
|
||||
#[derive(Debug)]
|
||||
pub struct NativeWindowBufferLockGuard<'a> {
|
||||
window: &'a NativeWindow,
|
||||
buffer: ffi::ANativeWindow_Buffer,
|
||||
}
|
||||
|
||||
impl<'a> NativeWindowBufferLockGuard<'a> {
|
||||
/// The number of pixels that are shown horizontally.
|
||||
pub fn width(&self) -> usize {
|
||||
usize::try_from(self.buffer.width).unwrap()
|
||||
}
|
||||
|
||||
// The number of pixels that are shown vertically.
|
||||
pub fn height(&self) -> usize {
|
||||
usize::try_from(self.buffer.height).unwrap()
|
||||
}
|
||||
|
||||
/// The number of _pixels_ that a line in the buffer takes in memory.
|
||||
///
|
||||
/// This may be `>= width`.
|
||||
pub fn stride(&self) -> usize {
|
||||
usize::try_from(self.buffer.stride).unwrap()
|
||||
}
|
||||
|
||||
/// The format of the buffer. One of [`HardwareBufferFormat`].
|
||||
pub fn format(&self) -> HardwareBufferFormat {
|
||||
self.buffer.format.into()
|
||||
}
|
||||
|
||||
/// The actual bits.
|
||||
///
|
||||
/// This points to a memory segment of [`stride()`][Self::stride()] *
|
||||
/// [`height()`][Self::height()] * [`HardwareBufferFormat::bytes_per_pixel()`] bytes.
|
||||
///
|
||||
/// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()]
|
||||
/// line of pixels in the buffer.
|
||||
///
|
||||
/// See [`bytes()`][Self::bytes()] for safe access to these bytes.
|
||||
pub fn bits(&mut self) -> *mut c_void {
|
||||
self.buffer.bits
|
||||
}
|
||||
|
||||
/// Safe write access to likely uninitialized pixel buffer data.
|
||||
///
|
||||
/// Returns [`None`] when there is no [`HardwareBufferFormat::bytes_per_pixel()`] size
|
||||
/// available for this [`format()`][Self::format()].
|
||||
///
|
||||
/// The returned slice consists of [`stride()`][Self::stride()] * [`height()`][Self::height()]
|
||||
/// \* [`HardwareBufferFormat::bytes_per_pixel()`] bytes.
|
||||
///
|
||||
/// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()]
|
||||
/// line of pixels in the buffer.
|
||||
pub fn bytes(&mut self) -> Option<&mut [MaybeUninit<u8>]> {
|
||||
let num_pixels = self.stride() * self.height();
|
||||
let num_bytes = num_pixels * self.format().bytes_per_pixel()?;
|
||||
Some(unsafe { std::slice::from_raw_parts_mut(self.bits().cast(), num_bytes) })
|
||||
}
|
||||
|
||||
/// Returns a slice of bytes for each line of visible pixels in the buffer, ignoring any
|
||||
/// padding pixels incurred by the stride.
|
||||
///
|
||||
/// See [`bits()`][Self::bits()] and [`bytes()`][Self::bytes()] for contiguous access to the
|
||||
/// underlying buffer.
|
||||
pub fn lines(&mut self) -> Option<impl Iterator<Item = &mut [MaybeUninit<u8>]>> {
|
||||
let bpp = self.format().bytes_per_pixel()?;
|
||||
let scanline_bytes = bpp * self.stride();
|
||||
let width_bytes = bpp * self.width();
|
||||
let bytes = self.bytes()?;
|
||||
|
||||
Some(
|
||||
bytes
|
||||
.chunks_exact_mut(scanline_bytes)
|
||||
.map(move |scanline| &mut scanline[..width_bytes]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for NativeWindowBufferLockGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
let ret = unsafe { ffi::ANativeWindow_unlockAndPost(self.window.ptr.as_ptr()) };
|
||||
assert_eq!(ret, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-26"))]
|
||||
bitflags::bitflags! {
|
||||
/// Transforms that can be applied to buffers as they are displayed to a window.
|
||||
///
|
||||
/// Supported transforms are any combination of horizontal mirror, vertical mirror, and
|
||||
/// clockwise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up
|
||||
/// of those basic transforms.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
#[doc(alias = "ANativeWindowTransform")]
|
||||
pub struct NativeWindowTransform : i32 {
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_IDENTITY")]
|
||||
const IDENTITY = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY.0 as i32;
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL")]
|
||||
const MIRROR_HORIZONTAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL.0 as i32;
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL")]
|
||||
const MIRROR_VERTICAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL.0 as i32;
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_90")]
|
||||
const ROTATE_90 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90.0 as i32;
|
||||
/// Defined as [`Self::MIRROR_HORIZONTAL`] `|` [`Self::MIRROR_VERTICAL`].
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_180")]
|
||||
const ROTATE_180 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180.0 as i32;
|
||||
/// Defined as [`Self::ROTATE_180`] `|` [`Self::ROTATE_90`].
|
||||
#[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_270")]
|
||||
const ROTATE_270 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270.0 as i32;
|
||||
|
||||
// https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags
|
||||
const _ = !0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compatibility value for [`NativeWindow::set_frame_rate()`]
|
||||
#[cfg_attr(
|
||||
feature = "api-level-31",
|
||||
doc = " and [`NativeWindow::set_frame_rate_with_change_strategy()`]"
|
||||
)]
|
||||
/// .
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-30"))]
|
||||
#[repr(i8)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
#[doc(alias = "ANativeWindow_FrameRateCompatibility")]
|
||||
#[non_exhaustive]
|
||||
pub enum FrameRateCompatibility {
|
||||
/// There are no inherent restrictions on the frame rate of this window.
|
||||
///
|
||||
/// When the system selects a frame rate other than what the app requested, the app will be
|
||||
/// able to run at the system frame rate without requiring pull down. This value should be used
|
||||
/// when displaying game content, UIs, and anything that isn't video.
|
||||
#[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT")]
|
||||
Default =
|
||||
ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT.0 as i8,
|
||||
/// This window is being used to display content with an inherently fixed frame rate, e.g. a
|
||||
/// video that has a specific frame rate.
|
||||
///
|
||||
/// When the system selects a frame rate other than what the app requested, the app will need
|
||||
/// to do pull down or use some other technique to adapt to the system's frame rate. The user
|
||||
/// experience is likely to be worse (e.g. more frame stuttering) than it would be if the
|
||||
/// system had chosen the app's requested frame rate. This value should be used for video
|
||||
/// content.
|
||||
#[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE")]
|
||||
FixedSource = ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE.0 as i8,
|
||||
}
|
||||
|
||||
/// Change frame rate strategy value for [`NativeWindow::set_frame_rate_with_change_strategy()`].
|
||||
#[cfg(all(feature = "nativewindow", feature = "api-level-31"))]
|
||||
#[repr(i8)]
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
#[doc(alias = "ANativeWindow_ChangeFrameRateStrategy")]
|
||||
#[non_exhaustive]
|
||||
pub enum ChangeFrameRateStrategy {
|
||||
/// Change the frame rate only if the transition is going to be seamless.
|
||||
#[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS")]
|
||||
OnlyIfSeamless =
|
||||
ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS
|
||||
.0 as i8,
|
||||
/// Change the frame rate even if the transition is going to be non-seamless, i.e. with visual interruptions for the user.
|
||||
#[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS")]
|
||||
Always =
|
||||
ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS.0 as i8,
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
//! Bindings for [`ASharedMemory`]
|
||||
//!
|
||||
//! [`ASharedMemory`]: https://developer.android.com/ndk/reference/group/memory
|
||||
#![cfg(feature = "api-level-26")]
|
||||
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
io::{Error, Result},
|
||||
// TODO: Import from std::os::fd::{} since Rust 1.66
|
||||
os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
|
||||
ptr,
|
||||
};
|
||||
|
||||
#[cfg(feature = "api-level-27")]
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
|
||||
/// Enables the creation, mapping, and protection control over anonymous shared memory.
|
||||
#[derive(Debug)]
|
||||
#[doc(alias = "ASharedMemory")]
|
||||
pub struct SharedMemory(OwnedFd);
|
||||
|
||||
impl SharedMemory {
|
||||
/// Create a shared memory region.
|
||||
///
|
||||
/// Creates shared memory region and returns a file descriptor. The resulting file descriptor
|
||||
/// can be `mmap`'ed to process memory space with `PROT_READ | PROT_WRITE | PROT_EXEC`. Access
|
||||
/// to this shared memory region can be restricted with [`set_prot()`][Self::set_prot()].
|
||||
///
|
||||
/// Use [`android.os.ParcelFileDescriptor`] to pass the file descriptor to another process.
|
||||
/// File descriptors may also be sent to other processes over a Unix domain socket with
|
||||
/// `sendmsg` and `SCM_RIGHTS`. See `sendmsg(3)` and `cmsg(3)` man pages for more information.
|
||||
///
|
||||
/// If you intend to share this file descriptor with a child process after calling `exec(3)`,
|
||||
/// note that you will need to use `fcntl(2)` with `F_SETFD` to clear the `FD_CLOEXEC` flag for
|
||||
/// this to work on all versions of Android.
|
||||
///
|
||||
/// [`android.os.ParcelFileDescriptor`]: https://developer.android.com/reference/android/os/ParcelFileDescriptor
|
||||
#[doc(alias = "ASharedMemory_create")]
|
||||
pub fn create(name: Option<&CStr>, size: usize) -> Result<Self> {
|
||||
let fd =
|
||||
unsafe { ffi::ASharedMemory_create(name.map_or(ptr::null(), |p| p.as_ptr()), size) };
|
||||
if fd < 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(unsafe { Self::from_raw_fd(fd) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `dup`'d FD from the given Java [`android.os.SharedMemory`] object.
|
||||
///
|
||||
/// The returned file descriptor has all the same properties & capabilities as the FD returned
|
||||
/// from [`create()`][Self::create()], however the protection flags will be the same as those
|
||||
/// of the [`android.os.SharedMemory`] object.
|
||||
///
|
||||
/// [`android.os.SharedMemory`]: https://developer.android.com/reference/android/os/SharedMemory
|
||||
#[cfg(feature = "api-level-27")]
|
||||
#[doc(alias = "ASharedMemory_dupFromJava")]
|
||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
pub fn dup_from_java(env: *mut JNIEnv, shared_memory: jobject) -> Result<Self> {
|
||||
let fd = unsafe { ffi::ASharedMemory_dupFromJava(env, shared_memory) };
|
||||
if fd < 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(unsafe { Self::from_raw_fd(fd) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the size of the shared memory region.
|
||||
#[doc(alias = "ASharedMemory_getSize")]
|
||||
pub fn size(&self) -> usize {
|
||||
unsafe { ffi::ASharedMemory_getSize(self.as_raw_fd()) }
|
||||
}
|
||||
|
||||
/// Restrict access of shared memory region.
|
||||
///
|
||||
/// This function restricts access of a shared memory region. Access can only be removed. The
|
||||
/// effect applies globally to all file descriptors in all processes across the system that
|
||||
/// refer to this shared memory region. Existing memory mapped regions are not affected.
|
||||
///
|
||||
/// It is a common use case to create a shared memory region, map it read/write locally to
|
||||
/// initialize content, and then send the shared memory to another process with read only
|
||||
/// access. Code example as below:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use ndk::shared_memory::SharedMemory;
|
||||
/// # // TODO: Import from std::os::fd::{} since Rust 1.66
|
||||
/// # use std::os::unix::io::AsRawFd;
|
||||
/// # use std::ffi::CStr;
|
||||
/// # unsafe {
|
||||
/// let mem = SharedMemory::create(Some(CStr::from_bytes_with_nul_unchecked(b"memory\0")), 127).unwrap();
|
||||
/// // By default it has PROT_READ | PROT_WRITE | PROT_EXEC.
|
||||
/// let size = mem.size();
|
||||
/// let buffer = libc::mmap(
|
||||
/// std::ptr::null_mut(),
|
||||
/// size,
|
||||
/// libc::PROT_READ | libc::PROT_WRITE,
|
||||
/// libc::MAP_SHARED,
|
||||
/// mem.as_raw_fd(),
|
||||
/// 0,
|
||||
/// );
|
||||
/// let buffer_slice = std::slice::from_raw_parts_mut(buffer.cast(), size);
|
||||
///
|
||||
/// // trivially initialize content
|
||||
/// buffer_slice[..7].copy_from_slice(b"hello!\0");
|
||||
///
|
||||
/// // Existing mappings will retain their protection flags (PROT_WRITE here) after set_prod()
|
||||
/// // unless it is unmapped:
|
||||
/// libc::munmap(buffer, size);
|
||||
///
|
||||
/// // limit access to read only
|
||||
/// mem.set_prot(libc::PROT_READ);
|
||||
///
|
||||
/// // share fd with another process here and the other process can only map with PROT_READ.
|
||||
/// # }
|
||||
/// ```
|
||||
#[doc(alias = "ASharedMemory_setProt")]
|
||||
pub fn set_prot(&self, prot: i32) -> Result<()> {
|
||||
let status = unsafe { ffi::ASharedMemory_setProt(self.as_raw_fd(), prot) };
|
||||
if status < 0 {
|
||||
Err(Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for SharedMemory {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.0.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for SharedMemory {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for SharedMemory {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.0.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for SharedMemory {
|
||||
/// # Safety
|
||||
///
|
||||
/// The resource pointed to by `fd` must be open and suitable for assuming
|
||||
/// ownership. The resource must not require any cleanup other than `close`.
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
Self(OwnedFd::from_raw_fd(fd))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//! Bindings for [`ASurfaceTexture`]
|
||||
//!
|
||||
//! See <https://source.android.com/devices/graphics/arch-st> for an architectural overview of
|
||||
//! [`SurfaceTexture`] internals.
|
||||
//!
|
||||
//! [`ASurfaceTexture`]: https://developer.android.com/ndk/reference/group/surface-texture
|
||||
#![cfg(feature = "api-level-28")]
|
||||
|
||||
use crate::{native_window::NativeWindow, utils::status_to_io_result};
|
||||
use jni_sys::{jobject, JNIEnv};
|
||||
use std::{io::Result, ptr::NonNull, time::Duration};
|
||||
|
||||
/// An opaque type to manage [`android.graphics.SurfaceTexture`] from native code
|
||||
///
|
||||
/// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture
|
||||
#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub struct SurfaceTexture {
|
||||
ptr: NonNull<ffi::ASurfaceTexture>,
|
||||
}
|
||||
|
||||
unsafe impl Send for SurfaceTexture {}
|
||||
|
||||
impl Drop for SurfaceTexture {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::ASurfaceTexture_release(self.ptr.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SurfaceTexture {
|
||||
/// Assumes ownership of `ptr`
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be a valid pointer to an Android [`ffi::ASurfaceTexture`].
|
||||
pub unsafe fn from_ptr(ptr: NonNull<ffi::ASurfaceTexture>) -> Self {
|
||||
Self { ptr }
|
||||
}
|
||||
|
||||
/// Get a reference to the native [`SurfaceTexture`] from the corresponding Java object.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a healthy JVM pointer and with a non-null
|
||||
/// [`android.graphics.SurfaceTexture`], which must be kept alive on the Java/Kotlin side.
|
||||
///
|
||||
/// The caller must keep a reference to the Java [`android.graphics.SurfaceTexture`] during the
|
||||
/// lifetime of the returned [`SurfaceTexture`]. Failing to do so could result in the
|
||||
/// [`SurfaceTexture`] to stop functioning properly once the Java object gets finalized.
|
||||
/// However, this will not result in program termination.
|
||||
///
|
||||
/// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture
|
||||
pub unsafe fn from_surface_texture(env: *mut JNIEnv, surface_texture: jobject) -> Option<Self> {
|
||||
let a_surface_texture_ptr = ffi::ASurfaceTexture_fromSurfaceTexture(env, surface_texture);
|
||||
let s = NonNull::new(a_surface_texture_ptr)?;
|
||||
Some(SurfaceTexture::from_ptr(s))
|
||||
}
|
||||
|
||||
/// Returns a pointer to the native [`ffi::ASurfaceTexture`].
|
||||
pub fn ptr(&self) -> NonNull<ffi::ASurfaceTexture> {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Returns a reference to a [`NativeWindow`] (i.e. the Producer) for this [`SurfaceTexture`].
|
||||
///
|
||||
/// This is equivalent to Java's:
|
||||
/// ```java
|
||||
/// Surface sur = new Surface(surfaceTexture);
|
||||
/// ```
|
||||
pub fn acquire_native_window(&self) -> Option<NativeWindow> {
|
||||
let native_window = unsafe { ffi::ASurfaceTexture_acquireANativeWindow(self.ptr.as_ptr()) };
|
||||
let n = NonNull::new(native_window)?;
|
||||
Some(unsafe { NativeWindow::from_ptr(n) })
|
||||
}
|
||||
|
||||
/// Attach the [`SurfaceTexture`] to the OpenGL ES context that is current on the calling
|
||||
/// thread.
|
||||
///
|
||||
/// A new OpenGL ES texture object is created and populated with the [`SurfaceTexture`] image
|
||||
/// frame that was current at the time of the last call to
|
||||
/// [`detach_from_gl_context()`][Self::detach_from_gl_context()]. This new texture is bound to
|
||||
/// the `GL_TEXTURE_EXTERNAL_OES` texture target.
|
||||
///
|
||||
/// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES
|
||||
/// contexts. Note, however, that the image contents are only accessible from one OpenGL ES
|
||||
/// context at a time.
|
||||
pub fn attach_to_gl_context(&self, tex_name: u32) -> Result<()> {
|
||||
let status = unsafe { ffi::ASurfaceTexture_attachToGLContext(self.ptr.as_ptr(), tex_name) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Detach the [`SurfaceTexture`] from the OpenGL ES context that owns the OpenGL ES texture
|
||||
/// object.
|
||||
///
|
||||
/// This call must be made with the OpenGL ES context current on the calling thread. The OpenGL
|
||||
/// ES texture object will be deleted as a result of this call. After calling this method all
|
||||
/// calls to [`update_tex_image()`][Self::update_tex_image()] will fail until a successful call
|
||||
/// to [`attach_to_gl_context()`][Self::attach_to_gl_context()] is made.
|
||||
///
|
||||
/// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES
|
||||
/// contexts. Note, however, that the image contents are only accessible from one OpenGL ES
|
||||
/// context at a time.
|
||||
pub fn detach_from_gl_context(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::ASurfaceTexture_detachFromGLContext(self.ptr.as_ptr()) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
|
||||
/// Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set
|
||||
/// by the most recent call to [`update_tex_image()`][Self::update_tex_image()].
|
||||
///
|
||||
/// This transform matrix maps 2D homogeneous texture coordinates of the form `(s, t, 0, 1)`
|
||||
/// with `s` and `t` in the inclusive range `[0, 1]` to the texture coordinate that should be
|
||||
/// used to sample that location from the texture. Sampling the texture outside of the range of
|
||||
/// this transform is undefined.
|
||||
///
|
||||
/// The matrix is stored in column-major order so that it may be passed directly to OpenGL ES
|
||||
/// via the [`glLoadMatrixf()`] or [`glUniformMatrix4fv()`] functions.
|
||||
///
|
||||
/// [`glLoadMatrixf()`]: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadMatrix.xml
|
||||
/// [`gluniformmatrix4fv()`]: https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glUniform.xhtml
|
||||
pub fn transform_matrix(&self) -> [f32; 16] {
|
||||
let mut r = [0f32; 16];
|
||||
unsafe { ffi::ASurfaceTexture_getTransformMatrix(self.ptr.as_ptr(), r.as_mut_ptr()) };
|
||||
r
|
||||
}
|
||||
|
||||
/// Retrieve the timestamp associated with the texture image set by the most recent call to
|
||||
/// [`update_tex_image()`][Self::update_tex_image()].
|
||||
///
|
||||
/// This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
|
||||
/// should be unaffected by time-of-day adjustments, and for a camera should be strictly
|
||||
/// monotonic but for a [`MediaPlayer`] may be reset when the position is set. The specific
|
||||
/// meaning and zero point of the timestamp depends on the source providing images to the
|
||||
/// [`SurfaceTexture`]. Unless otherwise specified by the image source, timestamps cannot
|
||||
/// generally be compared across [`SurfaceTexture`] instances, or across multiple program
|
||||
/// invocations. It is mostly useful for determining time offsets between subsequent frames.
|
||||
///
|
||||
/// For EGL/Vulkan producers, this timestamp is the desired present time set with the
|
||||
/// [`EGL_ANDROID_presentation_time`] or [`VK_GOOGLE_display_timing`] extensions.
|
||||
///
|
||||
/// [`MediaPlayer`]: https://developer.android.com/reference/android/media/MediaPlayer
|
||||
/// [`EGL_ANDROID_presentation_time`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
|
||||
/// [`VK_GOOGLE_display_timing`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html
|
||||
pub fn timestamp(&self) -> Duration {
|
||||
Duration::from_nanos(
|
||||
unsafe { ffi::ASurfaceTexture_getTimestamp(self.ptr.as_ptr()) }
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Update the texture image to the most recent frame from the image stream.
|
||||
///
|
||||
/// This may only be called while the OpenGL ES context that owns the texture is current on the
|
||||
/// calling thread. It will implicitly bind its texture to the `GL_TEXTURE_EXTERNAL_OES`
|
||||
/// texture target.
|
||||
pub fn update_tex_image(&self) -> Result<()> {
|
||||
let status = unsafe { ffi::ASurfaceTexture_updateTexImage(self.ptr.as_ptr()) };
|
||||
status_to_io_result(status)
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
//! Bindings for [sync functions]
|
||||
//!
|
||||
//! [sync functions]: https://developer.android.com/ndk/reference/group/sync
|
||||
#![cfg(feature = "sync")]
|
||||
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fmt::Debug,
|
||||
// TODO: Import from std::os::fd::{} since Rust 1.66
|
||||
os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
#[doc(alias = "sync_file_info")]
|
||||
#[repr(transparent)]
|
||||
pub struct SyncFileInfo {
|
||||
inner: NonNull<ffi::sync_file_info>,
|
||||
}
|
||||
|
||||
impl Debug for SyncFileInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SyncFileInfo")
|
||||
.field("name", &self.name())
|
||||
.field("status", &self.status())
|
||||
.field("flags", &self.flags())
|
||||
.field("num_fences", &self.num_fences())
|
||||
.field("fence_info", &self.fence_info())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncFileInfo {
|
||||
/// Retrieve detailed information about a sync file and its fences.
|
||||
#[doc(alias = "sync_file_info")]
|
||||
pub fn new(fd: BorrowedFd<'_>) -> Option<Self> {
|
||||
let inner = NonNull::new(unsafe { ffi::sync_file_info(fd.as_raw_fd()) })?;
|
||||
Some(Self { inner })
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &CStr {
|
||||
let inner = unsafe { self.inner.as_ref() };
|
||||
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
||||
// https://github.com/ash-rs/ash/pull/746
|
||||
unsafe { CStr::from_ptr(inner.name.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn status(&self) -> i32 {
|
||||
let inner = unsafe { self.inner.as_ref() };
|
||||
inner.status
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u32 {
|
||||
let inner = unsafe { self.inner.as_ref() };
|
||||
inner.flags
|
||||
}
|
||||
|
||||
pub fn num_fences(&self) -> usize {
|
||||
let inner = unsafe { self.inner.as_ref() };
|
||||
inner.num_fences as usize
|
||||
}
|
||||
|
||||
/// Get the array of fence infos from the sync file's info.
|
||||
#[doc(alias = "sync_get_fence_info")]
|
||||
pub fn fence_info(&self) -> &[SyncFenceInfo] {
|
||||
let inner = unsafe { self.inner.as_ref() };
|
||||
|
||||
if inner.num_fences == 0 {
|
||||
&[]
|
||||
} else {
|
||||
let sync_fence_info = NonNull::new(inner.sync_fence_info as *mut _)
|
||||
.expect("sync_fence_info cannot be null if num_fences > 0");
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(sync_fence_info.as_ptr(), inner.num_fences as usize)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SyncFileInfo {
|
||||
/// Free a [`struct@ffi::sync_file_info`] structure.
|
||||
#[doc(alias = "sync_file_info_free")]
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::sync_file_info_free(self.inner.as_ptr()) }
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "sync_fence_info")]
|
||||
#[repr(transparent)]
|
||||
pub struct SyncFenceInfo(ffi::sync_fence_info);
|
||||
|
||||
impl Debug for SyncFenceInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SyncFenceInfo")
|
||||
.field("obj_name", &self.obj_name())
|
||||
.field("driver_name", &self.driver_name())
|
||||
.field("status", &self.status())
|
||||
.field("flags", &self.flags())
|
||||
.field("timestamp_ns", &self.timestamp_ns())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncFenceInfo {
|
||||
pub fn obj_name(&self) -> &CStr {
|
||||
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
||||
unsafe { CStr::from_ptr(self.0.obj_name.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn driver_name(&self) -> &CStr {
|
||||
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
||||
unsafe { CStr::from_ptr(self.0.driver_name.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn status(&self) -> i32 {
|
||||
self.0.status
|
||||
}
|
||||
|
||||
pub fn flags(&self) -> u32 {
|
||||
self.0.flags
|
||||
}
|
||||
|
||||
pub fn timestamp_ns(&self) -> u64 {
|
||||
self.0.timestamp_ns
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge two sync files.
|
||||
///
|
||||
/// This produces a new sync file with the given name which has the union of the two original sync
|
||||
/// file's fences; redundant fences may be removed.
|
||||
///
|
||||
/// If one of the input sync files is signaled or invalid, then this function may behave like
|
||||
/// `dup()`: the new file descriptor refers to the valid/unsignaled sync file with its original
|
||||
/// name, rather than a new sync file.
|
||||
pub fn sync_merge(name: &CStr, fd1: BorrowedFd<'_>, fd2: BorrowedFd<'_>) -> OwnedFd {
|
||||
unsafe {
|
||||
OwnedFd::from_raw_fd(ffi::sync_merge(
|
||||
name.as_ptr(),
|
||||
fd1.as_raw_fd(),
|
||||
fd2.as_raw_fd(),
|
||||
))
|
||||
}
|
||||
}
|
||||
+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) }
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
//! Internal utilities
|
||||
use log::{error, log_enabled, Level};
|
||||
use std::ffi::{c_int, CStr, CString};
|
||||
use std::io::{Error, Result};
|
||||
|
||||
/// Turns standard `<errno.h>` status codes - typically rewrapped by Android's [`Errors.h`] - into
|
||||
/// Rust's [`std::io::Error`].
|
||||
///
|
||||
/// [`Errors.h`]: https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/include/utils/Errors.h
|
||||
pub(crate) fn status_to_io_result(status: i32) -> Result<()> {
|
||||
match status {
|
||||
0 => Ok(()),
|
||||
r if r < 0 => Err(Error::from_raw_os_error(-r)),
|
||||
r => unreachable!("Status is positive integer {}", r),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
|
||||
let prio = match level {
|
||||
Level::Error => ffi::android_LogPriority::ANDROID_LOG_ERROR,
|
||||
Level::Warn => ffi::android_LogPriority::ANDROID_LOG_WARN,
|
||||
Level::Info => ffi::android_LogPriority::ANDROID_LOG_INFO,
|
||||
Level::Debug => ffi::android_LogPriority::ANDROID_LOG_DEBUG,
|
||||
Level::Trace => ffi::android_LogPriority::ANDROID_LOG_VERBOSE,
|
||||
};
|
||||
unsafe {
|
||||
ffi::__android_log_write(prio.0 as c_int, tag.as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
|
||||
fn log_panic(panic_str: &str) {
|
||||
const RUST_PANIC_TAG: &CStr =
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
|
||||
|
||||
let panic_str = CString::new(panic_str).unwrap_or_default();
|
||||
|
||||
// Use the Rust logger if installed and enabled, otherwise fall back to the Android system
|
||||
// logger so there is at least some record of the panic
|
||||
if log_enabled!(Level::Error) {
|
||||
error!("RustPanic: {}", panic_str.to_string_lossy());
|
||||
log::logger().flush();
|
||||
} else {
|
||||
android_log(Level::Error, RUST_PANIC_TAG, &panic_str);
|
||||
}
|
||||
}
|
||||
|
||||
match panic.downcast::<String>() {
|
||||
Ok(panic_string) => log_panic(&panic_string),
|
||||
Err(panic) => match panic.downcast::<&str>() {
|
||||
Ok(panic_str) => log_panic(&panic_str),
|
||||
Err(_) => log_panic("Unknown panic message type"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a closure and abort the program if it panics.
|
||||
///
|
||||
/// This is generally used to ensure Rust callbacks won't unwind past the FFI boundary, which leads
|
||||
/// to undefined behaviour.
|
||||
pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| {
|
||||
// Try logging the panic before aborting
|
||||
//
|
||||
// Just in case our attempt to log a panic could itself cause a panic we use a
|
||||
// second catch_unwind here.
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic)));
|
||||
std::process::abort();
|
||||
})
|
||||
}
|
||||
@@ -74,7 +74,7 @@ always spelled out rather than pretending:
|
||||
| client | headline | why |
|
||||
|---|---|---|
|
||||
| Windows, macOS/iOS (Metal presenter), Linux | `capture→on-glass` / `capture→displayed` | present instant available (GTK measures at hand-off to the compositor, which adds about one compositor cycle after it) |
|
||||
| Android | `capture→decoded` | the display hand-off happens inside MediaCodec/SurfaceView where precise present timing isn't exposed |
|
||||
| Android | `capture→displayed` | MediaCodec's per-frame render callback reports SurfaceFlinger's render timestamp; on the rare window where no callback is delivered (the platform may drop them under load) the HUD falls back to `capture→decoded` |
|
||||
| macOS/iOS fallback presenter | `capture→received` | the system video layer hides decode and present timing entirely |
|
||||
|
||||
A shorter chain means the number is **smaller because it measures less** — check the
|
||||
|
||||
Reference in New Issue
Block a user