Skip to content

Migrate from "oboe" to "ndk::audio" #961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 15, 2025
Merged
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
18 changes: 10 additions & 8 deletions .github/workflows/cpal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Run clippy
run: cargo clippy --all --all-features
- name: Run clippy for Android target
run: cargo clippy --all --features asio --features oboe/fetch-prebuilt --target armv7-linux-androideabi
run: cargo clippy --all --features asio --target armv7-linux-androideabi

rustfmt-check:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -203,15 +203,16 @@ jobs:
with:
target: armv7-linux-androideabi
- name: Check android
run: cargo check --example android --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
working-directory: examples/android
run: cargo check --target armv7-linux-androideabi --verbose
- name: Check beep
run: cargo check --example beep --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
run: cargo check --example beep --target armv7-linux-androideabi --verbose
- name: Check enumerate
run: cargo check --example enumerate --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
run: cargo check --example enumerate --target armv7-linux-androideabi --verbose
- name: Check feedback
run: cargo check --example feedback --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
run: cargo check --example feedback --target armv7-linux-androideabi --verbose
- name: Check record_wav
run: cargo check --example record_wav --target armv7-linux-androideabi --features oboe/fetch-prebuilt --verbose
run: cargo check --example record_wav --target armv7-linux-androideabi --verbose

android-apk-build:
runs-on: ubuntu-latest
Expand All @@ -223,11 +224,12 @@ jobs:
targets: armv7-linux-androideabi,aarch64-linux-android,i686-linux-android,x86_64-linux-android
- name: Set Up Android tools
run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "platforms;android-30"
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT --install "build-tools;30.0.2" "platforms;android-30"
- name: Install Cargo APK
run: cargo install cargo-apk
- name: Build APK
run: cargo apk build --example android
working-directory: examples/android
run: cargo apk build

ios-build:
runs-on: macOS-latest
Expand Down
14 changes: 3 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ rust-version = "1.70"

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

[dependencies]
dasp_sample = "0.11"
Expand All @@ -22,9 +21,6 @@ hound = "3.5"
ringbuf = "0.4.1"
clap = { version = "4.0", features = ["derive"] }

[target.'cfg(target_os = "android")'.dev-dependencies]
ndk-glue = "0.7"

[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.54.0", features = [
"Win32_Media_Audio",
Expand Down Expand Up @@ -69,15 +65,11 @@ js-sys = { version = "0.3.35" }
web-sys = { version = "0.3.35", features = [ "AudioContext", "AudioContextOptions", "AudioBuffer", "AudioBufferSourceNode", "AudioNode", "AudioDestinationNode", "Window", "AudioContextState"] }

[target.'cfg(target_os = "android")'.dependencies]
oboe = { version = "0.6", features = [ "java-interface" ] }
ndk = { version = "0.8", default-features = false }
ndk = { version = "0.9", default-features = false, features = ["audio", "api-level-26"]}
ndk-context = "0.1"
jni = "0.21"

[[example]]
name = "android"
path = "examples/android.rs"
crate-type = ["cdylib"]
num-derive = "0.4"
num-traits = "0.2"

[[example]]
name = "beep"
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Currently, supported hosts include:
- Windows (via WASAPI by default, see ASIO instructions below)
- macOS (via CoreAudio)
- iOS (via CoreAudio)
- Android (via Oboe)
- Android (via AAudio)
- Emscripten

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

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

## ASIO on Windows

[ASIO](https://en.wikipedia.org/wiki/Audio_Stream_Input/Output) is an audio
Expand Down
6 changes: 6 additions & 0 deletions examples/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/target
/Cargo.lock
.cargo/
.DS_Store
recorded.wav
rls*.log
30 changes: 30 additions & 0 deletions examples/android/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "android"
version = "0.1.0"
edition = "2024"

[dependencies]
cpal = { path = "../../" }
anyhow = "1.0"
ndk-glue = "0.7"

[lib]
name = "android"
path = "src/lib.rs"
crate-type = ["cdylib"]

[package.metadata.android]
# Specifies the package property of the manifest.
package = "com.foo.bar"

# Specifies the array of targets to build for.
build_targets = [ "armv7-linux-androideabi", "aarch64-linux-android", "i686-linux-android", "x86_64-linux-android" ]

# Name for final APK file.
# Defaults to package name.
apk_name = "myapp"

[package.metadata.android.sdk]
min_sdk_version = 26
target_sdk_version = 30
max_sdk_version = 29
14 changes: 14 additions & 0 deletions examples/android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## How to install

```sh
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
```

## How to build apk

```sh
# Builds the project in release mode and places it into a `apk` file.
cargo apk build --release
```

more information at: https://github.com/rust-mobile/cargo-apk
File renamed without changes.
File renamed without changes.
47 changes: 23 additions & 24 deletions src/host/oboe/convert.rs → src/host/aaudio/convert.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::convert::TryInto;
use std::time::Duration;

extern crate oboe;
extern crate ndk;

use crate::{
BackendSpecificError, BuildStreamError, PauseStreamError, PlayStreamError, StreamError,
Expand All @@ -15,22 +15,21 @@ pub fn to_stream_instant(duration: Duration) -> StreamInstant {
)
}

pub fn stream_instant<T: oboe::AudioStreamSafe + ?Sized>(stream: &mut T) -> StreamInstant {
const CLOCK_MONOTONIC: i32 = 1;
pub fn stream_instant(stream: &ndk::audio::AudioStream) -> StreamInstant {
let ts = stream
.get_timestamp(CLOCK_MONOTONIC)
.unwrap_or(oboe::FrameTimestamp {
position: 0,
timestamp: 0,
.timestamp(ndk::audio::Clockid::Monotonic)
.unwrap_or(ndk::audio::Timestamp {
frame_position: 0,
time_nanoseconds: 0,
});
to_stream_instant(Duration::from_nanos(ts.timestamp as u64))
to_stream_instant(Duration::from_nanos(ts.time_nanoseconds as u64))
}

impl From<oboe::Error> for StreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
impl From<ndk::audio::AudioError> for StreamError {
fn from(error: ndk::audio::AudioError) -> Self {
use self::ndk::audio::AudioError::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
Disconnected | Unavailable => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
Expand All @@ -39,11 +38,11 @@ impl From<oboe::Error> for StreamError {
}
}

impl From<oboe::Error> for PlayStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
impl From<ndk::audio::AudioError> for PlayStreamError {
fn from(error: ndk::audio::AudioError) -> Self {
use self::ndk::audio::AudioError::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
Disconnected | Unavailable => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
Expand All @@ -52,11 +51,11 @@ impl From<oboe::Error> for PlayStreamError {
}
}

impl From<oboe::Error> for PauseStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
impl From<ndk::audio::AudioError> for PauseStreamError {
fn from(error: ndk::audio::AudioError) -> Self {
use self::ndk::audio::AudioError::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
Disconnected | Unavailable => Self::DeviceNotAvailable,
e => (BackendSpecificError {
description: e.to_string(),
})
Expand All @@ -65,11 +64,11 @@ impl From<oboe::Error> for PauseStreamError {
}
}

impl From<oboe::Error> for BuildStreamError {
fn from(error: oboe::Error) -> Self {
use self::oboe::Error::*;
impl From<ndk::audio::AudioError> for BuildStreamError {
fn from(error: ndk::audio::AudioError) -> Self {
use self::ndk::audio::AudioError::*;
match error {
Disconnected | Unavailable | Closed => Self::DeviceNotAvailable,
Disconnected | Unavailable => Self::DeviceNotAvailable,
NoFreeHandles => Self::StreamIdOverflow,
InvalidFormat | InvalidRate => Self::StreamConfigNotSupported,
IllegalArgument => Self::InvalidArgument,
Expand Down
7 changes: 7 additions & 0 deletions src/host/aaudio/java_interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod audio_features;
mod definitions;
mod devices_info;
mod utils;

pub use self::audio_features::*;
pub use self::definitions::*;
56 changes: 56 additions & 0 deletions src/host/aaudio/java_interface/audio_features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use super::{
utils::{
get_context, get_package_manager, has_system_feature, with_attached, JNIEnv, JObject,
JResult,
},
PackageManager,
};

/**
* The Android audio features
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AudioFeature {
LowLatency,
Output,
Pro,
Microphone,
Midi,
}

impl From<AudioFeature> for &'static str {
fn from(feature: AudioFeature) -> Self {
use AudioFeature::*;
match feature {
LowLatency => PackageManager::FEATURE_AUDIO_LOW_LATENCY,
Output => PackageManager::FEATURE_AUDIO_OUTPUT,
Pro => PackageManager::FEATURE_AUDIO_PRO,
Microphone => PackageManager::FEATURE_MICROPHONE,
Midi => PackageManager::FEATURE_MIDI,
}
}
}

impl AudioFeature {
/**
* Check availability of an audio feature using Android Java API
*/
pub fn has(&self) -> Result<bool, String> {
let context = get_context();

with_attached(context, |env, activity| {
try_check_system_feature(env, &activity, (*self).into())
})
.map_err(|error| error.to_string())
}
}

fn try_check_system_feature<'j>(
env: &mut JNIEnv<'j>,
activity: &JObject<'j>,
feature: &str,
) -> JResult<bool> {
let package_manager = get_package_manager(env, activity)?;

has_system_feature(env, &package_manager, feature)
}
Loading
Loading