Skip to content

Add Android Oboe backend and AMY wire socket transport - #1136

Open
linuxificator wants to merge 5 commits into
shorepine:mainfrom
linuxificator:upstream/android-oboe
Open

Add Android Oboe backend and AMY wire socket transport#1136
linuxificator wants to merge 5 commits into
shorepine:mainfrom
linuxificator:upstream/android-oboe

Conversation

@linuxificator

@linuxificator linuxificator commented Aug 22, 2026

Copy link
Copy Markdown

Summary

Adds a generic Android backend for AMY:

  • native Android AAR service in a separate, unexported :amy process;
  • Oboe/AAudio low-latency stereo signed-16-bit output at AMY's 48 kHz / 128-frame profile;
  • private AF_UNIX / SOCK_SEQPACKET transport at <filesDir>/amy.sock;
  • one unchanged AMY wire request per packet;
  • same-UID peer validation with SO_PEERCRED, socket mode 0600, and a bounded 64-packet queue;
  • minimal transport-only Java hello-world client that plays C4 through C5 entirely through AMY wire messages over the socket.

The client contract is framework-independent. The hello-world application does not import or call AmyService, does not start or stop AMY, does not load a JNI AMY client, and does not compile AMY source. It uses Android's public LocalSocket(SOCKET_SEQPACKET) API and sends raw AMY wire packets only. The AAR owns Android service startup through its private manifest ContentProvider, so the same client boundary can be used from Java/Kotlin, Godot, Qt, or another framework/language that can package the AAR and open the Unix-domain socket.

The AAR intentionally remains packaged into the same Android application/UID as the client because amy.sock is app-private (0600) and accepted peers are validated with SO_PEERCRED. That packaging requirement is separate from the client API: application/framework code has no AMY headers, source, JNI binding, or lifecycle call.

Realtime design

  • The socket receiver thread never calls AMY directly.
  • The audio owner drains already-queued packets at AMY block boundaries.
  • The Oboe callback adapts arbitrary Android callback numFrames values to AMY's 128-frame blocks without adding another full-block output ring.
  • amy.sock is published only after Oboe has started and its first realtime callback has executed, so a successful socket connection is also the engine-readiness boundary.
  • Test-only audio capture preallocates its buffers before audio starts; the realtime callback only copies into those buffers and never performs file I/O or takes the writer mutex.

Client boundary

application/framework code
        |
        | ordinary AMY wire packets only
        v
<filesDir>/amy.sock  (AF_UNIX / SOCK_SEQPACKET)
        |
        v
independent Android :amy process -> AMY -> Oboe/AAudio

The Java example performs no service control. On launch it retries the private socket until it exists, then sends v0w0V10.0Z followed by the C-major note-on/note-off wire packets. The previous JNI socket wrapper (libamy_hello_client.so) has been removed entirely.

Validation

The contribution includes focused transport tests and Android integration/audio-level CI:

  • bash tests/run_amy_unix_socket_test.sh validates packet round-trip, peer/ownership behavior, oversize handling, cleanup, and stale-path safeguards;
  • Android CI builds the AAR and hello-world APK for arm64-v8a and x86_64 service binaries;
  • CI explicitly verifies that the hello-world APK contains no libamy_hello_client.so and that the hello-world module has no native src/main/cpp client code;
  • an Android 35 emulator performs two independent uninstall/install/cold-launch cycles and requires AMY/Oboe startup, successful pure-Java connection to amy.sock, an output-route diagnostic, V10.0, all eight C-scale note-on packets (60, 62, 64, 65, 67, 69, 71, 72), successful completion, and no socket failure;
  • CI, rather than the example application, arms the optional test-only audio capture before launch, keeping the example strictly transport-only;
  • the integration test captures four seconds of the raw signed-16-bit AMY render stream and the exact signed-16-bit callback buffer handed to Oboe, measures peak/RMS dBFS, requires byte-for-byte AMY-to-Oboe identity, requires at least -6 dBFS peak, rejects full-scale clipping, and uploads the WAVs/statistics as a CI artifact;
  • validation run 32651283791 on corrected head commit a12c19bfdd9936dcb10ab8b8e39214041dd56cd9 completed successfully: peak -2.721 dBFS, RMS about -7.27 dBFS, zero clipped samples, and zero AMY-to-Oboe sample differences.

The audio-level regression previously caught a hello-world configuration error: AMY's V control is a 0..10 bus/master scale and the final mixer applies a 0.1 factor. The earlier example's V2.0 therefore selected only 20% linear master gain (about -14 dB relative to V10.0). The example and documentation use V10.0.

Scope

This PR remains deliberately self-contained and does not modify existing AMY core synthesis source files. The Android service contains the AMY implementation; application/framework code communicates with it only through the wire socket.

The development history is five reviewable commits, with the final commit specifically decoupling the Java hello-world from the AMY service API/JNI client and moving Android startup ownership into the AAR.

Add a fixed-size AF_UNIX/SOCK_SEQPACKET transport for local AMY wire messages, including peer credential checks, bounded queueing, cleanup safeguards, standalone regression tests, and focused CI.
Add a generic Android AAR service that renders AMY through Oboe/AAudio and accepts ordinary AMY wire packets over the private Unix transport. Include the minimal C-scale hello-world app plus Android build and emulator integration CI.
Document the generic Android AAR, Oboe backend, private SOCK_SEQPACKET client contract, readiness semantics, build requirements, and hello-world example.
Measure the raw AMY render stream and exact signed-16-bit buffer handed to Oboe during the Android hello-world integration test. Retain both WAVs and level statistics in CI, require byte-for-byte AMY-to-Oboe identity, healthy peak level, and no full-scale clipping.

Use AMY V10.0 for the audible hello-world. AMY's V control is a 0..10 bus/master scale and the final mixer applies a 0.1 factor, so the previous V2.0 setting was only 20% linear gain (about -14 dB relative to V10.0).

Validated at -2.721 dBFS peak with zero clipping and zero AMY-to-Oboe sample differences in Android AMY run 32630088165.
Make the Java hello-world a transport-only client. MainActivity no longer imports or starts AmyService and no longer loads a JNI/native client library. It uses Android LocalSocket SOCK_SEQPACKET directly and sends only ordinary AMY wire packets to filesDir/amy.sock.

Move service startup to an AAR-owned ContentProvider lifecycle hook, remove the hello-world C++/CMake client wrapper, and move test-only audio capture arming into CI so the example remains free of service/test control logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant