Skip to content

Commit c5a163e

Browse files
authored
Migrate from "oboe" to "ndk::audio" (#961)
* Migrate from "oboe" to "ndk::audio" * trying to pass github workflow for android packing * rename mod oboe -> aaudio * Clarify Android audio channel support and note AAudio support * using newer build-tools * move android example to a dedicated crate * cargo fmt --all * removing --features "oboe/fetch-prebuilt" from workflow * adapt workflow to check new android example
1 parent 33b8919 commit c5a163e

19 files changed

+752
-424
lines changed

.github/workflows/cpal.yml

+10-8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Run clippy
2323
run: cargo clippy --all --all-features
2424
- name: Run clippy for Android target
25-
run: cargo clippy --all --features asio --features oboe/fetch-prebuilt --target armv7-linux-androideabi
25+
run: cargo clippy --all --features asio --target armv7-linux-androideabi
2626

2727
rustfmt-check:
2828
runs-on: ubuntu-latest
@@ -203,15 +203,16 @@ jobs:
203203
with:
204204
target: armv7-linux-androideabi
205205
- name: Check android
206-
run: cargo check --example android --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
206+
working-directory: examples/android
207+
run: cargo check --target armv7-linux-androideabi --verbose
207208
- name: Check beep
208-
run: cargo check --example beep --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
209+
run: cargo check --example beep --target armv7-linux-androideabi --verbose
209210
- name: Check enumerate
210-
run: cargo check --example enumerate --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
211+
run: cargo check --example enumerate --target armv7-linux-androideabi --verbose
211212
- name: Check feedback
212-
run: cargo check --example feedback --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
213+
run: cargo check --example feedback --target armv7-linux-androideabi --verbose
213214
- name: Check record_wav
214-
run: cargo check --example record_wav --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
215+
run: cargo check --example record_wav --target armv7-linux-androideabi --verbose
215216

216217
android-apk-build:
217218
runs-on: ubuntu-latest
@@ -223,11 +224,12 @@ jobs:
223224
targets: armv7-linux-androideabi,aarch64-linux-android,i686-linux-android,x86_64-linux-android
224225
- name: Set Up Android tools
225226
run: |
226-
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "platforms;android-30"
227+
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "build-tools;30.0.2" "platforms;android-30"
227228
- name: Install Cargo APK
228229
run: cargo install cargo-apk
229230
- name: Build APK
230-
run: cargo apk build --example android
231+
working-directory: examples/android
232+
run: cargo apk build
231233

232234
ios-build:
233235
runs-on: macOS-latest

Cargo.toml

+3-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ rust-version = "1.70"
1111

1212
[features]
1313
asio = ["asio-sys", "num-traits"] # Only available on Windows. See README for setup instructions.
14-
oboe-shared-stdcxx = ["oboe/shared-stdcxx"] # Only available on Android. See README for what it does.
1514

1615
[dependencies]
1716
dasp_sample = "0.11"
@@ -22,9 +21,6 @@ hound = "3.5"
2221
ringbuf = "0.4.1"
2322
clap = { version = "4.0", features = ["derive"] }
2423

25-
[target.'cfg(target_os = "android")'.dev-dependencies]
26-
ndk-glue = "0.7"
27-
2824
[target.'cfg(target_os = "windows")'.dependencies]
2925
windows = { version = "0.54.0", features = [
3026
"Win32_Media_Audio",
@@ -69,15 +65,11 @@ js-sys = { version = "0.3.35" }
6965
web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }
7066

7167
[target.'cfg(target_os = "android")'.dependencies]
72-
oboe = { version = "0.6", features = [ "java-interface" ] }
73-
ndk = { version = "0.8", default-features = false }
68+
ndk = { version = "0.9", default-features = false, features = ["audio", "api-level-26"]}
7469
ndk-context = "0.1"
7570
jni = "0.21"
76-
77-
[[example]]
78-
name = "android"
79-
path = "examples/android.rs"
80-
crate-type = ["cdylib"]
71+
num-derive = "0.4"
72+
num-traits = "0.2"
8173

8274
[[example]]
8375
name = "beep"

README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Currently, supported hosts include:
2020
- Windows (via WASAPI by default, see ASIO instructions below)
2121
- macOS (via CoreAudio)
2222
- iOS (via CoreAudio)
23-
- Android (via Oboe)
23+
- Android (via AAudio)
2424
- Emscripten
2525

2626
Note that on Linux, the ALSA development files are required. These are provided
@@ -38,10 +38,6 @@ Some audio backends are optional and will only be compiled with a [feature flag]
3838
- JACK (on Linux): `jack`
3939
- ASIO (on Windows): `asio`
4040

41-
Oboe can either use a shared or static runtime. The static runtime is used by default, but activating the
42-
`oboe-shared-stdcxx` feature makes it use the shared runtime, which requires `libc++_shared.so` from the Android NDK to
43-
be present during execution.
44-
4541
## ASIO on Windows
4642

4743
[ASIO](https://en.wikipedia.org/wiki/Audio_Stream_Input/Output) is an audio

examples/android/.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/target
2+
/Cargo.lock
3+
.cargo/
4+
.DS_Store
5+
recorded.wav
6+
rls*.log

examples/android/Cargo.toml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "android"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
cpal = { path = "../../" }
8+
anyhow = "1.0"
9+
ndk-glue = "0.7"
10+
11+
[lib]
12+
name = "android"
13+
path = "src/lib.rs"
14+
crate-type = ["cdylib"]
15+
16+
[package.metadata.android]
17+
# Specifies the package property of the manifest.
18+
package = "com.foo.bar"
19+
20+
# Specifies the array of targets to build for.
21+
build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android", "x86_64-linux-android" ]
22+
23+
# Name for final APK file.
24+
# Defaults to package name.
25+
apk_name = "myapp"
26+
27+
[package.metadata.android.sdk]
28+
min_sdk_version = 26
29+
target_sdk_version = 30
30+
max_sdk_version = 29

examples/android/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## How to install
2+
3+
```sh
4+
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
5+
```
6+
7+
## How to build apk
8+
9+
```sh
10+
# Builds the project in release mode and places it into a `apk` file.
11+
cargo apk build --release
12+
```
13+
14+
more information at: https://github.com/rust-mobile/cargo-apk
File renamed without changes.
File renamed without changes.

src/host/oboe/convert.rs renamed to src/host/aaudio/convert.rs

+23-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::convert::TryInto;
22
use std::time::Duration;
33

4-
extern crate oboe;
4+
extern crate ndk;
55

66
use crate::{
77
BackendSpecificError, BuildStreamError, PauseStreamError, PlayStreamError, StreamError,
@@ -15,22 +15,21 @@ pub fn to_stream_instant(duration: Duration) -> StreamInstant {
1515
)
1616
}
1717

18-
pub fn stream_instant<T: oboe::AudioStreamSafe + ?Sized>(stream: &mut T) -> StreamInstant {
19-
const CLOCK_MONOTONIC: i32 = 1;
18+
pub fn stream_instant(stream: &ndk::audio::AudioStream) -> StreamInstant {
2019
let ts = stream
21-
.get_timestamp(CLOCK_MONOTONIC)
22-
.unwrap_or(oboe::FrameTimestamp {
23-
position: 0,
24-
timestamp: 0,
20+
.timestamp(ndk::audio::Clockid::Monotonic)
21+
.unwrap_or(ndk::audio::Timestamp {
22+
frame_position: 0,
23+
time_nanoseconds: 0,
2524
});
26-
to_stream_instant(Duration::from_nanos(ts.timestamp as u64))
25+
to_stream_instant(Duration::from_nanos(ts.time_nanoseconds as u64))
2726
}
2827

29-
impl From<oboe::Error> for StreamError {
30-
fn from(error: oboe::Error) -> Self {
31-
use self::oboe::Error::*;
28+
impl From<ndk::audio::AudioError> for StreamError {
29+
fn from(error: ndk::audio::AudioError) -> Self {
30+
use self::ndk::audio::AudioError::*;
3231
match error {
33-
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
32+
Disconnected | Unavailable => Self::DeviceNotAvailable,
3433
e => (BackendSpecificError {
3534
description: e.to_string(),
3635
})
@@ -39,11 +38,11 @@ impl From<oboe::Error> for StreamError {
3938
}
4039
}
4140

42-
impl From<oboe::Error> for PlayStreamError {
43-
fn from(error: oboe::Error) -> Self {
44-
use self::oboe::Error::*;
41+
impl From<ndk::audio::AudioError> for PlayStreamError {
42+
fn from(error: ndk::audio::AudioError) -> Self {
43+
use self::ndk::audio::AudioError::*;
4544
match error {
46-
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
45+
Disconnected | Unavailable => Self::DeviceNotAvailable,
4746
e => (BackendSpecificError {
4847
description: e.to_string(),
4948
})
@@ -52,11 +51,11 @@ impl From<oboe::Error> for PlayStreamError {
5251
}
5352
}
5453

55-
impl From<oboe::Error> for PauseStreamError {
56-
fn from(error: oboe::Error) -> Self {
57-
use self::oboe::Error::*;
54+
impl From<ndk::audio::AudioError> for PauseStreamError {
55+
fn from(error: ndk::audio::AudioError) -> Self {
56+
use self::ndk::audio::AudioError::*;
5857
match error {
59-
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
58+
Disconnected | Unavailable => Self::DeviceNotAvailable,
6059
e => (BackendSpecificError {
6160
description: e.to_string(),
6261
})
@@ -65,11 +64,11 @@ impl From<oboe::Error> for PauseStreamError {
6564
}
6665
}
6766

68-
impl From<oboe::Error> for BuildStreamError {
69-
fn from(error: oboe::Error) -> Self {
70-
use self::oboe::Error::*;
67+
impl From<ndk::audio::AudioError> for BuildStreamError {
68+
fn from(error: ndk::audio::AudioError) -> Self {
69+
use self::ndk::audio::AudioError::*;
7170
match error {
72-
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
71+
Disconnected | Unavailable => Self::DeviceNotAvailable,
7372
NoFreeHandles => Self::StreamIdOverflow,
7473
InvalidFormat | InvalidRate => Self::StreamConfigNotSupported,
7574
IllegalArgument => Self::InvalidArgument,

src/host/aaudio/java_interface.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod audio_features;
2+
mod definitions;
3+
mod devices_info;
4+
mod utils;
5+
6+
pub use self::audio_features::*;
7+
pub use self::definitions::*;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use super::{
2+
utils::{
3+
get_context, get_package_manager, has_system_feature, with_attached, JNIEnv, JObject,
4+
JResult,
5+
},
6+
PackageManager,
7+
};
8+
9+
/**
10+
* The Android audio features
11+
*/
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13+
pub enum AudioFeature {
14+
LowLatency,
15+
Output,
16+
Pro,
17+
Microphone,
18+
Midi,
19+
}
20+
21+
impl From<AudioFeature> for &'static str {
22+
fn from(feature: AudioFeature) -> Self {
23+
use AudioFeature::*;
24+
match feature {
25+
LowLatency => PackageManager::FEATURE_AUDIO_LOW_LATENCY,
26+
Output => PackageManager::FEATURE_AUDIO_OUTPUT,
27+
Pro => PackageManager::FEATURE_AUDIO_PRO,
28+
Microphone => PackageManager::FEATURE_MICROPHONE,
29+
Midi => PackageManager::FEATURE_MIDI,
30+
}
31+
}
32+
}
33+
34+
impl AudioFeature {
35+
/**
36+
* Check availability of an audio feature using Android Java API
37+
*/
38+
pub fn has(&self) -> Result<bool, String> {
39+
let context = get_context();
40+
41+
with_attached(context, |env, activity| {
42+
try_check_system_feature(env, &activity, (*self).into())
43+
})
44+
.map_err(|error| error.to_string())
45+
}
46+
}
47+
48+
fn try_check_system_feature<'j>(
49+
env: &mut JNIEnv<'j>,
50+
activity: &JObject<'j>,
51+
feature: &str,
52+
) -> JResult<bool> {
53+
let package_manager = get_package_manager(env, activity)?;
54+
55+
has_system_feature(env, &package_manager, feature)
56+
}

0 commit comments

Comments
 (0)