Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions .github/workflows/godot-android-raw-wire.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
name: Godot Android raw-wire POC
name: Godot Android socket-only POC

on:
push:
branches:
- feature/godot-android-raw-wire-poc
- feature/godot-android-api-wire-poc
pull_request:
branches:
- upstream/android-oboe
Expand Down Expand Up @@ -39,24 +40,31 @@ jobs:
with:
gradle-version: "8.13"

- name: Build AMY AAR
- name: Build AMY service AAR
run: bash godot/android-hello-world/prepare.sh

- name: Verify AMY AAR contents
- name: Verify AMY AAR contains service only
run: |
AAR=godot/android-hello-world/addons/amy_android/amy-service-debug.aar
test -s "$AAR"
unzip -l "$AAR" | tee /tmp/amy-aar.txt
grep -q 'jni/arm64-v8a/libamy_android.so' /tmp/amy-aar.txt
grep -q 'jni/arm64-v8a/libamy_android_client.so' /tmp/amy-aar.txt
grep -q 'jni/x86_64/libamy_android.so' /tmp/amy-aar.txt
grep -q 'jni/x86_64/libamy_android_client.so' /tmp/amy-aar.txt
! grep -q 'libamy_android_client.so' /tmp/amy-aar.txt
unzip -p "$AAR" classes.jar > /tmp/amy-classes.jar
unzip -l /tmp/amy-classes.jar | tee /tmp/amy-classes.txt
grep -q 'org/amy/audio/AmyService.class' /tmp/amy-classes.txt
grep -q 'org/amy/audio/AmyClient.class' /tmp/amy-classes.txt
grep -q 'org/amy/audio/AmyAutoStartProvider.class' /tmp/amy-classes.txt

- name: Guard against Android AMY GDExtension
run: |
# Desktop/web AMY integration may continue to exist in godot/, but
# Android must use only the independent service AAR and wire socket.
! grep -Eq '^android\.' godot/amy.gdextension
test ! -e godot/android-hello-world/amy.gdextension
test ! -d godot/android-hello-world/addons/amy/bin

- name: Install Godot 4.7.2 and Android export templates
run: |
curl -L --fail --retry 3 \
Expand All @@ -79,7 +87,10 @@ jobs:
godot --version

- name: Import Godot project
run: godot --headless --path godot/android-hello-world --import --quit
run: |
godot --headless --path godot/android-hello-world --import --quit 2>&1 | tee /tmp/godot-import.log
! grep -q 'SCRIPT ERROR' /tmp/godot-import.log
! grep -q 'ERROR: Failed to load script' /tmp/godot-import.log

- name: Export ARM64 phone APK
run: |
Expand All @@ -97,22 +108,26 @@ jobs:
--export-debug "Android CI x86_64" build/amy-godot-raw-wire-x86_64.apk
test -s godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk

- name: Verify APK packaging
- name: Verify Android APK has exactly one AMY native implementation
run: |
ARM=godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk
X86=godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk

unzip -l "$ARM" | tee /tmp/arm-apk.txt
grep -q 'lib/arm64-v8a/libgodot_android.so' /tmp/arm-apk.txt
grep -q 'lib/arm64-v8a/libamy_android.so' /tmp/arm-apk.txt
grep -q 'lib/arm64-v8a/libamy_android_client.so' /tmp/arm-apk.txt
! grep -q 'libamy_android_client.so' /tmp/arm-apk.txt
! grep -q 'lib/x86_64/' /tmp/arm-apk.txt
test "$(grep -Ec 'lib/arm64-v8a/libamy[^ ]*\.so' /tmp/arm-apk.txt)" -eq 1
! zipinfo -1 "$ARM" | grep -q 'amy.gdextension'

unzip -l "$X86" | tee /tmp/x86-apk.txt
grep -q 'lib/x86_64/libgodot_android.so' /tmp/x86-apk.txt
grep -q 'lib/x86_64/libamy_android.so' /tmp/x86-apk.txt
grep -q 'lib/x86_64/libamy_android_client.so' /tmp/x86-apk.txt
! grep -q 'libamy_android_client.so' /tmp/x86-apk.txt
! grep -q 'lib/arm64-v8a/' /tmp/x86-apk.txt
test "$(grep -Ec 'lib/x86_64/libamy[^ ]*\.so' /tmp/x86-apk.txt)" -eq 1
! zipinfo -1 "$X86" | grep -q 'amy.gdextension'

for apk in "$ARM" "$X86"; do
for dex in $(zipinfo -1 "$apk" | grep -E '^classes([0-9]+)?\.dex$'); do
Expand Down
15 changes: 3 additions & 12 deletions android/amy-service/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,16 @@ set(AMY_SOURCES
${AMY_SRC}/cv_trigger.c
)

# The only native library built for Android is the independent AMY/Oboe
# service implementation. Client/framework code uses Android LocalSocket and
# never links AMY or a Godot AMY GDExtension.
add_library(amy_android SHARED
amy_android.cpp
amy_android_capture.cpp
amy_android_profile.cpp
${AMY_SOURCES}
)

# Tiny client used by non-AMY processes (Godot in this proof of concept).
# It contains no synth/audio engine: only one AF_UNIX/SOCK_SEQPACKET connection
# to the separate :amy service process.
add_library(amy_android_client SHARED
amy_android_client.cpp
)

target_include_directories(amy_android PRIVATE
${AMY_SRC}
${CMAKE_CURRENT_SOURCE_DIR}
Expand All @@ -68,12 +64,7 @@ target_compile_options(amy_android PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-O3;-Wall;-Wextra;-Wno-unused-parameter>
)

target_compile_options(amy_android_client PRIVATE
-O3 -Wall -Wextra -Wno-unused-parameter
)

target_compile_features(amy_android PRIVATE c_std_11 cxx_std_17)
target_compile_features(amy_android_client PRIVATE cxx_std_17)

target_link_libraries(amy_android PRIVATE
oboe::oboe
Expand Down
73 changes: 0 additions & 73 deletions android/amy-service/src/main/cpp/amy_android_client.cpp

This file was deleted.

69 changes: 48 additions & 21 deletions android/amy-service/src/main/java/org/amy/audio/AmyClient.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package org.amy.audio;

import android.content.Context;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

/** Minimal client for the private AMY SOCK_SEQPACKET socket. */
/**
* Minimal pure-Java client for the private AMY SOCK_SEQPACKET socket.
*
* This class is transport only: it contains no AMY engine code, does not start
* or stop AmyService, and does not load any JNI/native client library.
*/
public final class AmyClient {
private static final int EINVAL = 22;
private static final int EIO = 5;
private static final int ENOTCONN = 107;
private static int nativeFd = -1;

static {
System.loadLibrary("amy_android_client");
}
private static LocalSocket socket;
private static OutputStream output;

private AmyClient() {}

Expand All @@ -22,37 +31,55 @@ private static String socketPath(Context context) {
.getAbsolutePath();
}

/** Connect once to filesDir/amy.sock. Returns 0 or negative errno. */
/** Connect once to filesDir/amy.sock. Returns 0 or a negative errno-style value. */
public static synchronized int connect(Context context) {
if (context == null) return -EINVAL;
closeLocked();
int fd = nativeConnect(socketPath(context));
if (fd < 0) return fd;
nativeFd = fd;
return 0;

LocalSocket candidate = new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
LocalSocketAddress address = new LocalSocketAddress(
socketPath(context), LocalSocketAddress.Namespace.FILESYSTEM);
try {
candidate.connect(address);
output = candidate.getOutputStream();
socket = candidate;
return 0;
} catch (IOException ex) {
try {
candidate.close();
} catch (IOException ignored) {
}
return -EIO;
}
}

/** Send exactly one AMY wire command as one SOCK_SEQPACKET packet. */
public static synchronized int sendWire(String wire) {
if (nativeFd < 0) return -ENOTCONN;
if (socket == null || output == null) return -ENOTCONN;
if (wire == null || wire.isEmpty()) return -EINVAL;
int result = nativeSend(nativeFd, wire);
if (result < 0) closeLocked();
return result;

try {
output.write(wire.getBytes(StandardCharsets.US_ASCII));
output.flush();
return 0;
} catch (IOException ex) {
closeLocked();
return -EIO;
}
}

public static synchronized void close() {
closeLocked();
}

private static void closeLocked() {
if (nativeFd >= 0) {
nativeClose(nativeFd);
nativeFd = -1;
output = null;
if (socket != null) {
try {
socket.close();
} catch (IOException ignored) {
}
socket = null;
}
}

private static native int nativeConnect(String socketPath);
private static native int nativeSend(int fd, String wire);
private static native void nativeClose(int fd);
}
Loading
Loading