build: Android APK builds working — debug (8.9MB) and release (2.0MB)
- Fix C++ std::std:: double namespace in oboe_bridge.cpp - Auto-fetch Oboe headers from GitHub in build.rs - Configure cargo cross-compilation (.cargo/config.toml) with NDK linkers - Fix Gradle settings (dependencyResolutionManagement), signing configs, Compose LinearProgressIndicator API, and Android manifest theme - Add Gradle wrapper, .gitignore for build artifacts - arm64-v8a only (raptorq crate incompatible with armv7 32-bit) - Release APK: 2.0MB signed with wzp-release key - Debug APK: 8.9MB signed with wzp-debug key Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.cargo/config.toml
Normal file
5
.cargo/config.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[target.aarch64-linux-android]
|
||||||
|
linker = "aarch64-linux-android21-clang"
|
||||||
|
|
||||||
|
[target.armv7-linux-androideabi]
|
||||||
|
linker = "armv7a-linux-androideabi21-clang"
|
||||||
6
android/.gitignore
vendored
Normal file
6
android/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
app/build/
|
||||||
|
app/src/main/jniLibs/
|
||||||
|
local.properties
|
||||||
|
keystore/*.jks
|
||||||
@@ -13,11 +13,31 @@ android {
|
|||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.0"
|
||||||
ndk { abiFilters += listOf("arm64-v8a", "armeabi-v7a") }
|
ndk { abiFilters += listOf("arm64-v8a") }
|
||||||
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file("${project.rootDir}/keystore/wzp-release.jks")
|
||||||
|
storePassword = "wzphone2024"
|
||||||
|
keyAlias = "wzp-release"
|
||||||
|
keyPassword = "wzphone2024"
|
||||||
|
}
|
||||||
|
getByName("debug") {
|
||||||
|
storeFile = file("${project.rootDir}/keystore/wzp-debug.jks")
|
||||||
|
storePassword = "android"
|
||||||
|
keyAlias = "wzp-debug"
|
||||||
|
keyPassword = "android"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
isDebuggable = true
|
||||||
|
}
|
||||||
release {
|
release {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
@@ -46,7 +66,7 @@ tasks.register<Exec>("cargoNdkBuild") {
|
|||||||
workingDir = file("${project.rootDir}/..")
|
workingDir = file("${project.rootDir}/..")
|
||||||
commandLine(
|
commandLine(
|
||||||
"cargo", "ndk",
|
"cargo", "ndk",
|
||||||
"-t", "arm64-v8a", "-t", "armeabi-v7a",
|
"-t", "arm64-v8a",
|
||||||
"-o", "${project.projectDir}/src/main/jniLibs",
|
"-o", "${project.projectDir}/src/main/jniLibs",
|
||||||
"build", "--release", "-p", "wzp-android"
|
"build", "--release", "-p", "wzp-android"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
android:name=".WzpApplication"
|
android:name=".WzpApplication"
|
||||||
android:label="WZ Phone"
|
android:label="WZ Phone"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Material3.DayNight">
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.call.CallActivity"
|
android:name=".ui.call.CallActivity"
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ private fun AudioLevelBar(framesEncoded: Long) {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
progress = { level },
|
progress = level,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.6f)
|
.fillMaxWidth(0.6f)
|
||||||
.height(6.dp)
|
.height(6.dp)
|
||||||
|
|||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
5
android/gradlew
vendored
Executable file
5
android/gradlew
vendored
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Gradle wrapper script
|
||||||
|
APP_HOME=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
CLASSPATH="$APP_HOME/gradle/wrapper/gradle-wrapper.jar"
|
||||||
|
exec java -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
@@ -6,7 +6,8 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolution {
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@@ -1,15 +1,36 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let target = std::env::var("TARGET").unwrap_or_default();
|
let target = std::env::var("TARGET").unwrap_or_default();
|
||||||
|
|
||||||
if target.contains("android") {
|
if target.contains("android") {
|
||||||
// Real Oboe build for Android targets
|
// On Android, try to build with Oboe. If Oboe is not available,
|
||||||
cc::Build::new()
|
// fall back to the stub (audio will need to be provided via JNI).
|
||||||
.cpp(true)
|
let oboe_dir = fetch_oboe();
|
||||||
.std("c++17")
|
match oboe_dir {
|
||||||
.file("cpp/oboe_bridge.cpp")
|
Some(oboe_path) => {
|
||||||
.include("cpp")
|
println!("cargo:warning=Building with Oboe from {:?}", oboe_path);
|
||||||
.compile("oboe_bridge");
|
cc::Build::new()
|
||||||
|
.cpp(true)
|
||||||
|
.std("c++17")
|
||||||
|
.file("cpp/oboe_bridge.cpp")
|
||||||
|
.include("cpp")
|
||||||
|
.include(oboe_path.join("include"))
|
||||||
|
.define("WZP_HAS_OBOE", None)
|
||||||
|
.compile("oboe_bridge");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("cargo:warning=Oboe not found, building with stub");
|
||||||
|
cc::Build::new()
|
||||||
|
.cpp(true)
|
||||||
|
.std("c++17")
|
||||||
|
.file("cpp/oboe_stub.cpp")
|
||||||
|
.include("cpp")
|
||||||
|
.compile("oboe_bridge");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Stub for host builds / testing
|
// Non-Android: always use stub
|
||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
.cpp(true)
|
.cpp(true)
|
||||||
.std("c++17")
|
.std("c++17")
|
||||||
@@ -18,3 +39,37 @@ fn main() {
|
|||||||
.compile("oboe_bridge");
|
.compile("oboe_bridge");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to find or fetch Oboe headers.
|
||||||
|
/// Returns the path to the Oboe source root (containing include/ directory).
|
||||||
|
fn fetch_oboe() -> Option<PathBuf> {
|
||||||
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||||
|
let oboe_dir = out_dir.join("oboe");
|
||||||
|
|
||||||
|
// Check if already fetched
|
||||||
|
if oboe_dir.join("include").join("oboe").join("Oboe.h").exists() {
|
||||||
|
return Some(oboe_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to clone Oboe from GitHub
|
||||||
|
let status = std::process::Command::new("git")
|
||||||
|
.args([
|
||||||
|
"clone",
|
||||||
|
"--depth=1",
|
||||||
|
"--branch=1.8.1",
|
||||||
|
"https://github.com/google/oboe.git",
|
||||||
|
oboe_dir.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.status();
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Ok(s) if s.success() => {
|
||||||
|
if oboe_dir.join("include").join("oboe").join("Oboe.h").exists() {
|
||||||
|
Some(oboe_dir)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public:
|
|||||||
oboe::AudioStream* stream,
|
oboe::AudioStream* stream,
|
||||||
void* audioData,
|
void* audioData,
|
||||||
int32_t numFrames) override {
|
int32_t numFrames) override {
|
||||||
if (!g_running.load(std::std::memory_order_relaxed) || !g_rings) {
|
if (!g_running.load(std::memory_order_relaxed) || !g_rings) {
|
||||||
return oboe::DataCallbackResult::Stop;
|
return oboe::DataCallbackResult::Stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ public:
|
|||||||
auto result = stream->calculateLatencyMillis();
|
auto result = stream->calculateLatencyMillis();
|
||||||
if (result) {
|
if (result) {
|
||||||
g_capture_latency_ms.store(static_cast<float>(result.value()),
|
g_capture_latency_ms.store(static_cast<float>(result.value()),
|
||||||
std::std::memory_order_relaxed);
|
std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
return oboe::DataCallbackResult::Continue;
|
||||||
@@ -115,7 +115,7 @@ public:
|
|||||||
oboe::AudioStream* stream,
|
oboe::AudioStream* stream,
|
||||||
void* audioData,
|
void* audioData,
|
||||||
int32_t numFrames) override {
|
int32_t numFrames) override {
|
||||||
if (!g_running.load(std::std::memory_order_relaxed) || !g_rings) {
|
if (!g_running.load(std::memory_order_relaxed) || !g_rings) {
|
||||||
memset(audioData, 0, numFrames * sizeof(int16_t));
|
memset(audioData, 0, numFrames * sizeof(int16_t));
|
||||||
return oboe::DataCallbackResult::Stop;
|
return oboe::DataCallbackResult::Stop;
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,7 @@ public:
|
|||||||
auto result = stream->calculateLatencyMillis();
|
auto result = stream->calculateLatencyMillis();
|
||||||
if (result) {
|
if (result) {
|
||||||
g_playout_latency_ms.store(static_cast<float>(result.value()),
|
g_playout_latency_ms.store(static_cast<float>(result.value()),
|
||||||
std::std::memory_order_relaxed);
|
std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return oboe::DataCallbackResult::Continue;
|
return oboe::DataCallbackResult::Continue;
|
||||||
@@ -155,7 +155,7 @@ static PlayoutCallback g_playout_cb;
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
||||||
if (g_running.load(std::std::memory_order_relaxed)) {
|
if (g_running.load(std::memory_order_relaxed)) {
|
||||||
LOGW("wzp_oboe_start: already running");
|
LOGW("wzp_oboe_start: already running");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -200,13 +200,13 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
|||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_running.store(true, std::std::memory_order_release);
|
g_running.store(true, std::memory_order_release);
|
||||||
|
|
||||||
// Start both streams
|
// Start both streams
|
||||||
result = g_capture_stream->requestStart();
|
result = g_capture_stream->requestStart();
|
||||||
if (result != oboe::Result::OK) {
|
if (result != oboe::Result::OK) {
|
||||||
LOGE("Failed to start capture: %s", oboe::convertToText(result));
|
LOGE("Failed to start capture: %s", oboe::convertToText(result));
|
||||||
g_running.store(false, std::std::memory_order_release);
|
g_running.store(false, std::memory_order_release);
|
||||||
g_capture_stream->close();
|
g_capture_stream->close();
|
||||||
g_playout_stream->close();
|
g_playout_stream->close();
|
||||||
g_capture_stream.reset();
|
g_capture_stream.reset();
|
||||||
@@ -217,7 +217,7 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
|||||||
result = g_playout_stream->requestStart();
|
result = g_playout_stream->requestStart();
|
||||||
if (result != oboe::Result::OK) {
|
if (result != oboe::Result::OK) {
|
||||||
LOGE("Failed to start playout: %s", oboe::convertToText(result));
|
LOGE("Failed to start playout: %s", oboe::convertToText(result));
|
||||||
g_running.store(false, std::std::memory_order_release);
|
g_running.store(false, std::memory_order_release);
|
||||||
g_capture_stream->requestStop();
|
g_capture_stream->requestStop();
|
||||||
g_capture_stream->close();
|
g_capture_stream->close();
|
||||||
g_playout_stream->close();
|
g_playout_stream->close();
|
||||||
@@ -232,7 +232,7 @@ int wzp_oboe_start(const WzpOboeConfig* config, const WzpOboeRings* rings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void wzp_oboe_stop(void) {
|
void wzp_oboe_stop(void) {
|
||||||
g_running.store(false, std::std::memory_order_release);
|
g_running.store(false, std::memory_order_release);
|
||||||
|
|
||||||
if (g_capture_stream) {
|
if (g_capture_stream) {
|
||||||
g_capture_stream->requestStop();
|
g_capture_stream->requestStop();
|
||||||
@@ -250,15 +250,15 @@ void wzp_oboe_stop(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float wzp_oboe_capture_latency_ms(void) {
|
float wzp_oboe_capture_latency_ms(void) {
|
||||||
return g_capture_latency_ms.load(std::std::memory_order_relaxed);
|
return g_capture_latency_ms.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
float wzp_oboe_playout_latency_ms(void) {
|
float wzp_oboe_playout_latency_ms(void) {
|
||||||
return g_playout_latency_ms.load(std::std::memory_order_relaxed);
|
return g_playout_latency_ms.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
int wzp_oboe_is_running(void) {
|
int wzp_oboe_is_running(void) {
|
||||||
return g_running.load(std::std::memory_order_relaxed) ? 1 : 0;
|
return g_running.load(std::memory_order_relaxed) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|||||||
Reference in New Issue
Block a user