426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
124 lines
6.4 KiB
Markdown
124 lines
6.4 KiB
Markdown
# Android live voice pipeline: `audioFrame` → speaker-attributed turns
|
|
|
|
This document maps the **end-to-end on-device** path that turns the Android
|
|
`audioFrame` PCM stream (see [`AUDIO_FRAMES.md`](./AUDIO_FRAMES.md)) into live,
|
|
VAD-segmented, speaker-attributed voice turns.
|
|
|
|
There are two transports, and they target different builds. For the **normal
|
|
Android APK** (`ai.elizaos.app`) the canonical path is the **in-process
|
|
JNI/bionic host** — the four fused voice classifiers run inside the Capacitor
|
|
app process via `libelizainference.so`, with no separate agent process and no
|
|
HTTP hop. The legacy **musl bun-agent transport** remains only for the
|
|
privileged AOSP build (where the embedded bun agent runs platform-signed); it is
|
|
not the path the normal APK uses.
|
|
|
|
## The canonical pipeline (normal APK: in-process JNI/bionic host)
|
|
|
|
```
|
|
Android native AudioRecord (plugin-native-talkmode, Kotlin)
|
|
│ emits `audioFrame` Capacitor event: base64 LE-s16 16 kHz mono PCM,
|
|
│ 20 ms/frame, { sampleRate, channels, samples, rms, timestamp, frameIndex }
|
|
▼
|
|
Capacitor WebView (JS renderer)
|
|
│ TalkMode.addListener("audioFrame", …) → JniVoicePipeline
|
|
│ (packages/ui/src/voice/jni-voice-pipeline.ts)
|
|
│ batches ~1 s of frames → ElizaVoice.pipelineProcess({ handle, pcm16 })
|
|
▼
|
|
ElizaVoice JNI host (ai.elizaos.app process, BIONIC — same process, NO agent)
|
|
│ packages/app-core/platforms/android/app/src/main/elizavoice-jni/
|
|
│ libelizavoicejni.so → libelizainference.so (fused, ABI v7, all four runtimes)
|
|
│ 1. native VAD hot-loop + turn segmentation (ported VadDetector state machine):
|
|
│ streams the PCM through eliza_inference_vad_process, applies the
|
|
│ onset/offset/pause/end-hangover thresholds, buffers the turn PCM
|
|
│ (+ pre-roll) between speech-start and speech-end — ZERO per-512-window
|
|
│ JS↔native bridge calls.
|
|
│ 2. on speech-end: eliza_inference_speaker_embed (256-d WeSpeaker embedding)
|
|
│ + eliza_inference_diariz_segment (293 pyannote frame labels), natively.
|
|
│ 3. returns a turn-level result (base64 embedding + int8 labels) to JS.
|
|
▼
|
|
JS (JniVoicePipeline)
|
|
│ decodes the embedding + labels, runs the injected speaker resolver
|
|
│ (embedding → enrolled entity) and buildVoiceTurnSignal (the ambient gate),
|
|
│ and surfaces a JniAttributedTurn.
|
|
▼
|
|
voiceTurnSignal → the `core.voice_turn_signal` server gate decides
|
|
whether the agent speaks (owner / bystander / wake word).
|
|
```
|
|
|
|
The native ops the JNI host wraps (all `eliza_inference_*`, fused into the one
|
|
`libelizainference.so`):
|
|
|
|
| Stage | JNI surface | native runtime |
|
|
|---|---|---|
|
|
| Silero VAD (turn segmentation) | `vad open/processBatch/reset/close` + the streaming `pipeline*` | `eliza_inference_vad_*` |
|
|
| openWakeWord ("hey eliza") | `wakeword open/scoreBatch/reset/close` | `eliza_inference_wakeword_*` |
|
|
| WeSpeaker encoder (speaker embedding) | `speaker open/embed/close` | `eliza_inference_speaker_*` |
|
|
| pyannote diarizer (segment by speaker) | `diariz open/segment/close` | `eliza_inference_diariz_*` |
|
|
|
|
Each resolves its GGUF from the on-device bundle
|
|
(`<files>/eliza-1/bundle/{vad,wake,speaker,diariz}/…`). The split is: the VAD
|
|
hot-loop + turn segmentation + speaker/diariz forward passes run natively; the
|
|
speaker-match-against-enrolled-profiles + the ambient gate stay in JS (per-turn,
|
|
infrequent).
|
|
|
|
## What is verified on-device (Pixel 9a, `ai.elizaos.app`)
|
|
|
|
The whole pipeline runs in the bionic app process — proven via CDP + logcat
|
|
(the in-process verification channel; every line is emitted by the
|
|
`ai.elizaos.app` pid, never the agent):
|
|
|
|
- **ABI + capability**: `eliza_inference_abi_version() = 7`, and all four
|
|
classifiers report supported in-process: `vad=1 wakeword=1 speaker=1 diariz=1`.
|
|
- **Full pipeline on real speech** (`freeman.wav` + 2 s trailing silence, fed in
|
|
1 s batches via `pipelineSelfTest`):
|
|
```
|
|
pipelineSelfTest: feeding 308224 samples (19.26s), chunk=16000
|
|
TURN jni_0: samples=285184 (17.82s) | speaker: embDim=256 norm=1.0000 |
|
|
diariz: frames=293 distinctClasses=1
|
|
```
|
|
The 17.82 s turn (< 19.26 s fed) is a **real VAD speech-end** firing at the
|
|
silence boundary — turn segmentation, the 256-d L2-normalized speaker
|
|
embedding, and the 293-frame diarizer all ran in-process.
|
|
- **Wake-word** (`wakewordSelfTest`, "hey eliza" clip vs silence):
|
|
`posMax=1.0000 negMax=0.0000`.
|
|
|
|
The JS-side consumer (`JniVoicePipeline`) has a host unit test
|
|
(`packages/ui/src/voice/jni-voice-pipeline.test.ts`, 5 cases): lifecycle,
|
|
runtime-unavailable refusal, frame batching (one bridge call per ~1 s), turn
|
|
embedding/label decode, and the confident-bystander suppression gate.
|
|
|
|
The platform-agnostic agent-side consumer
|
|
(`plugins/plugin-local-inference/src/services/voice/audio-frame-consumer.ts`)
|
|
also has its host unit test + the real-model smoke
|
|
(`packages/app-core/scripts/voice-attribution-smoke.ts`), shared by both
|
|
transports.
|
|
|
|
## On-device verification surface
|
|
|
|
`window.__jniVoice` (installed on Android by `main.tsx` →
|
|
`installJniVoiceHarness`) drives the in-process pipeline from CDP:
|
|
|
|
```
|
|
window.__jniVoice.start() → open native pipeline + start mic + pump
|
|
window.__jniVoice.status() → { running, framesSent, turnsObserved, abi, recentTurns }
|
|
window.__jniVoice.stop() → stop capture, flush the open turn, free handles
|
|
```
|
|
|
|
## Legacy: musl bun-agent transport (AOSP build only)
|
|
|
|
Before the JNI host, the four classifiers ran in the **embedded bun agent**
|
|
(a separate musl process), reached from the WebView by POSTing batched frames to
|
|
`POST /api/voice/audio-frames` (`AudioFramePump` →
|
|
`LiveDiarizationSession` → `AudioFrameConsumer`). That path dlopened standalone
|
|
musl libs (`libsilero_vad.so`, `libvoice_classifier.so`, cross-compiled with
|
|
`zig cc --target=aarch64-linux-musl`) via bun:ffi, pointed at by the
|
|
`ELIZA_SILERO_VAD_LIB` / `ELIZA_VOICE_CLASSIFIER_LIB` env vars
|
|
`ElizaAgentService` exports when those `.so` are present in `nativeLibraryDir`.
|
|
|
|
This transport is **superseded for the normal APK** by the JNI/bionic host
|
|
above. The standalone musl voice `.so` and their zig cross-build script
|
|
(`packages/native/scripts/build-voice-libs-android-musl.mjs`) have been removed.
|
|
The musl `libllama.so` (the text agent — a separate concern) is untouched, and
|
|
the bun-agent voice path remains available on the privileged AOSP build (out of
|
|
scope here — to be unified with the JNI host later).
|