├── .envrc ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── Makefile ├── README.md ├── default.nix ├── demo ├── .gitignore ├── Cargo.toml ├── README.md ├── mobile.toml ├── oboe-demo.png ├── release.keystore ├── src │ ├── audio.rs │ └── lib.rs └── target ├── src ├── audio_stream.rs ├── audio_stream_base.rs ├── audio_stream_builder.rs ├── audio_stream_callback.rs ├── definitions.rs ├── java_interface.rs ├── java_interface │ ├── audio_features.rs │ ├── definitions.rs │ ├── devices_info.rs │ ├── stream_defaults.rs │ └── utils.rs ├── lib.rs ├── private.rs ├── type_guide.rs └── version.rs └── sys ├── Cargo.toml ├── Makefile ├── README.md ├── build.rs ├── oboe-ext ├── CMakeLists.txt ├── include │ └── oboe │ │ └── OboeExt.h └── src │ ├── AudioStreamBuilderWrapper.cpp │ ├── AudioStreamCallbackWrapper.cpp │ └── AudioStreamWrapper.cpp └── src ├── bindings_aarch64.rs ├── bindings_armv7.rs ├── bindings_i686.rs ├── bindings_x86_64.rs └── lib.rs /.envrc: -------------------------------------------------------------------------------- 1 | use nix 2 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - '[0-9]+.[0-9]+.[0-9]+' 8 | pull_request: 9 | env: 10 | SDK_BUILD_TOOLS_VERSION: '34.0.0' 11 | NDK_VERSION: '25.2.9519653' 12 | ANDROID_PLATFORM: '34' 13 | jobs: 14 | format: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | - name: Setup Rust 21 | uses: dtolnay/rust-toolchain@v1 22 | with: 23 | toolchain: stable 24 | components: rustfmt 25 | - uses: Swatinem/rust-cache@v2 26 | - name: Format 27 | run: cargo fmt --all -- --check 28 | 29 | docs: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | with: 34 | submodules: true 35 | - name: Setup Rust 36 | uses: dtolnay/rust-toolchain@v1 37 | with: 38 | toolchain: nightly 39 | targets: x86_64-linux-android 40 | components: rust-docs 41 | - uses: Swatinem/rust-cache@v2 42 | - name: Documentation 43 | env: 44 | DOCS_RS: 1 45 | run: cargo doc --target x86_64-linux-android --features java-interface,doc-cfg 46 | 47 | check: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | with: 52 | submodules: true 53 | - name: Setup JDK 54 | uses: actions/setup-java@v4 55 | with: 56 | distribution: temurin 57 | java-version: 17 58 | - name: Setup Android SDK 59 | uses: android-actions/setup-android@v3 60 | with: 61 | log-accepted-android-sdk-licenses: false 62 | packages: build-tools;${{ env.SDK_BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }} platforms;android-${{ env.ANDROID_PLATFORM }} 63 | - name: Config Android NDK 64 | env: 65 | TRIPLE: x86_64-linux-android 66 | run: | 67 | echo "$ANDROID_SDK_ROOT/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH 68 | for var in ANDROID_NDK ANDROID_NDK_HOME ANDROID_NDK_LATEST_HOME ANDROID_NDK_ROOT NDK_HOME; do 69 | echo "$var=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> $GITHUB_ENV 70 | done 71 | TRIPLE_ENV=$(echo $TRIPLE | tr '-' '_') 72 | echo "CC_${TRIPLE_ENV}=${TRIPLE}21-clang" >> $GITHUB_ENV 73 | echo "CXX_${TRIPLE_ENV}=${TRIPLE}21-clang++" >> $GITHUB_ENV 74 | echo "AR_${TRIPLE_ENV}=llvm-ar" >> $GITHUB_ENV 75 | - uses: dtolnay/rust-toolchain@v1 76 | with: 77 | toolchain: nightly 78 | target: x86_64-linux-android 79 | components: clippy 80 | - uses: Swatinem/rust-cache@v2 81 | - run: cargo clippy --all --features java-interface --all-targets --target x86_64-linux-android 82 | 83 | build: 84 | needs: 85 | - format 86 | - docs 87 | - check 88 | - cargo-ndk 89 | runs-on: ubuntu-latest 90 | strategy: 91 | fail-fast: ${{ github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/') }} 92 | matrix: 93 | rust: 94 | - stable 95 | - nightly 96 | profile: 97 | - debug 98 | - release 99 | target: 100 | - armv7-linux-androideabi 101 | - aarch64-linux-android 102 | - i686-linux-android 103 | - x86_64-linux-android 104 | steps: 105 | - uses: actions/checkout@v4 106 | with: 107 | submodules: true 108 | - name: Setup JDK 109 | uses: actions/setup-java@v4 110 | with: 111 | distribution: temurin 112 | java-version: 17 113 | - name: Setup Android SDK 114 | uses: android-actions/setup-android@v3 115 | with: 116 | log-accepted-android-sdk-licenses: false 117 | packages: build-tools;${{ env.SDK_BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }} platforms;android-${{ env.ANDROID_PLATFORM }} 118 | - name: Config Android NDK 119 | run: | 120 | echo "$ANDROID_SDK_ROOT/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH 121 | for var in ANDROID_NDK ANDROID_NDK_HOME ANDROID_NDK_LATEST_HOME ANDROID_NDK_ROOT NDK_HOME; do 122 | echo "$var=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> $GITHUB_ENV 123 | done 124 | - name: Setup Rust ${{ matrix.rust }} [${{ matrix.target }}] 125 | uses: dtolnay/rust-toolchain@v1 126 | with: 127 | toolchain: ${{ matrix.rust }} 128 | target: ${{ matrix.target }} 129 | components: rustfmt 130 | - name: Setup Cargo ndk 131 | uses: actions/cache@v4 132 | with: 133 | path: ~/.cargo/bin/cargo-ndk 134 | key: ${{ runner.os }}-cargo-ndk 135 | - uses: Swatinem/rust-cache@v2 136 | - name: Prepare config 137 | id: config 138 | run: | 139 | echo "android-target=$(case "${{ matrix.target }}" in 140 | armv7-linux-androideabi) printf armeabi-v7a;; 141 | aarch64-linux-android) printf arm64-v8a;; 142 | i686-linux-android) printf x86;; 143 | x86_64-linux-android) printf x86_64;; 144 | esac)" >> $GITHUB_OUTPUT 145 | echo "android-arch=$(case "${{ matrix.target }}" in 146 | armv7-linux-androideabi) printf arm;; 147 | aarch64-linux-android) printf arm64;; 148 | i686-linux-android) printf x86;; 149 | x86_64-linux-android) printf x86_64;; 150 | esac)" >> $GITHUB_OUTPUT 151 | if [[ "${{ matrix.target }}" =~ "64" ]]; then 152 | echo "android-api=21" >> $GITHUB_OUTPUT 153 | else 154 | echo "android-api=19" >> $GITHUB_OUTPUT 155 | fi 156 | if [[ "${{ matrix.profile }}" == "release" ]]; then 157 | echo "cargo-args=--release" >> $GITHUB_OUTPUT 158 | else 159 | echo "cargo-args=" >> $GITHUB_OUTPUT 160 | fi 161 | - name: Build target ${{ matrix.target }} 162 | run: cargo ndk --platform ${{ steps.config.outputs.android-api }} --target ${{ steps.config.outputs.android-target }} --bindgen -- build ${{ steps.config.outputs.cargo-args }} --features generate-bindings 163 | - name: Get latest build path 164 | id: result 165 | run: | 166 | echo "build-path=$(ls -td target/${{ matrix.target }}/${{ matrix.profile }}/build/oboe-sys-*/ | head -1)" >> $GITHUB_OUTPUT 167 | - name: Copy bindings 168 | if: matrix.rust == 'stable' && matrix.profile == 'release' 169 | run: | 170 | mkdir -p bindings 171 | cp ${{ steps.result.outputs.build-path }}out/bindings.rs bindings/bindings_$(echo ${{ matrix.target }} | sed -r 's/^([^-]+).*$/\1/').rs 172 | - name: Upload bindings 173 | if: matrix.rust == 'stable' && matrix.profile == 'release' 174 | uses: actions/upload-artifact@v4 175 | with: 176 | name: bindings-${{ steps.config.outputs.android-arch }} 177 | path: bindings 178 | overwrite: true 179 | - name: Archive library 180 | if: matrix.rust == 'stable' 181 | run: tar -czf liboboe-ext_${{ matrix.target }}_${{ matrix.profile }}.tar.gz -C ${{ steps.result.outputs.build-path }}/out/library liboboe-ext.a 182 | - name: Upload library 183 | if: matrix.rust == 'stable' 184 | uses: actions/upload-artifact@v4 185 | with: 186 | name: library-${{ steps.config.outputs.android-arch }}-${{ matrix.profile }} 187 | path: liboboe-ext_${{ matrix.target }}_${{ matrix.profile }}.tar.gz 188 | overwrite: true 189 | 190 | update-bindings: 191 | if: ${{ !startsWith(github.ref, 'refs/tags/') }} 192 | needs: 193 | - build 194 | runs-on: ubuntu-latest 195 | steps: 196 | - uses: actions/checkout@v4 197 | with: 198 | submodules: true 199 | - name: Download bindings 200 | uses: actions/download-artifact@v4 201 | with: 202 | pattern: bindings-* 203 | merge-multiple: true 204 | path: sys/src 205 | - name: Create Pull Request 206 | uses: peter-evans/create-pull-request@v6 207 | with: 208 | base: ${{ github.head_ref }} 209 | commit-message: Updated bindings 210 | branch: update-bindings 211 | delete-branch: true 212 | title: Update bindings 213 | body: | 214 | Bindings should be updated to be consistent with latest changes 215 | 216 | build-demo: 217 | needs: 218 | - build 219 | - cargo-mobile2 220 | runs-on: ubuntu-latest 221 | steps: 222 | - uses: actions/checkout@v4 223 | with: 224 | submodules: true 225 | - name: Setup JDK 226 | uses: actions/setup-java@v4 227 | with: 228 | distribution: temurin 229 | java-version: 17 230 | - name: Setup Android SDK 231 | uses: android-actions/setup-android@v3 232 | with: 233 | log-accepted-android-sdk-licenses: false 234 | packages: build-tools;${{ env.SDK_BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }} platforms;android-${{ env.ANDROID_PLATFORM }} 235 | - name: Config Android NDK 236 | run: | 237 | echo "$ANDROID_SDK_ROOT/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH 238 | for var in ANDROID_NDK ANDROID_NDK_HOME ANDROID_NDK_LATEST_HOME ANDROID_NDK_ROOT NDK_HOME; do 239 | echo "$var=$ANDROID_SDK_ROOT/ndk/$NDK_VERSION" >> $GITHUB_ENV 240 | done 241 | - name: Setup Rust 242 | uses: dtolnay/rust-toolchain@v1 243 | with: 244 | toolchain: stable 245 | targets: armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android 246 | - name: Setup Cargo mobile 247 | uses: actions/cache@v4 248 | with: 249 | path: | 250 | ~/.cargo/bin/cargo-android 251 | ~/.cargo/bin/cargo-mobile 252 | ~/.cargo/.cargo-mobile2 253 | key: ${{ runner.os }}-cargo-mobile2 254 | - uses: Swatinem/rust-cache@v2 255 | - name: Create signing key 256 | run: | 257 | if [ -z "${{ secrets.APK_KEYSTORE_PASSWORD }}" ]; then 258 | # create temporary keystore to sign apk 259 | rm -f demo/release.keystore 260 | keytool -genkey -dname "cn=Nobrand, ou=RnD, o=example.com, c=US" -v -keystore demo/release.keystore -alias demo -keyalg RSA -keysize 2048 -validity 20000 -storepass android 261 | else 262 | # use existing keystore to sign apk 263 | sed -i 's/keystore_password = "android"/keystore_password = "${{ secrets.APK_KEYSTORE_PASSWORD }}"/' demo/Cargo.toml 264 | fi 265 | - name: Init demo 266 | run: | 267 | cd demo && cargo mobile init && sed -ri 's/((compile|target)Sdk *= *)33/\134/g' gen/android/app/build.gradle.kts 268 | - name: Build demo 269 | run: cd demo && cargo mobile android apk build --release --split-per-abi 270 | - name: Upload demo x86 271 | uses: actions/upload-artifact@v4 272 | with: 273 | name: demo-x86 274 | path: demo/gen/android/app/build/outputs/apk/x86/release/*.apk 275 | - name: Upload demo x86_64 276 | uses: actions/upload-artifact@v4 277 | with: 278 | name: demo-x86_64 279 | path: demo/gen/android/app/build/outputs/apk/x86_64/release/*.apk 280 | - name: Upload demo arm 281 | uses: actions/upload-artifact@v4 282 | with: 283 | name: demo-arm 284 | path: demo/gen/android/app/build/outputs/apk/arm/release/*.apk 285 | - name: Upload demo arm64 286 | uses: actions/upload-artifact@v4 287 | with: 288 | name: demo-arm64 289 | path: demo/gen/android/app/build/outputs/apk/arm64/release/*.apk 290 | 291 | release: 292 | if: github.repository == 'katyo/oboe-rs' && startsWith(github.ref, 'refs/tags/') 293 | needs: 294 | - build 295 | - build-demo 296 | runs-on: ubuntu-latest 297 | steps: 298 | - name: Download libraries 299 | uses: actions/download-artifact@v4 300 | with: 301 | pattern: library-* 302 | merge-multiple: true 303 | - name: Download demo apk 304 | uses: actions/download-artifact@v4 305 | with: 306 | pattern: demo-* 307 | merge-multiple: true 308 | - name: Create release 309 | uses: softprops/action-gh-release@v1 310 | env: 311 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 312 | with: 313 | files: | 314 | *.tar.gz 315 | *.apk 316 | #prerelease: true 317 | #draft: true 318 | fail_on_unmatched_files: true 319 | 320 | publish: 321 | if: github.repository == 'katyo/oboe-rs' && startsWith(github.ref, 'refs/tags/') 322 | needs: 323 | - release 324 | runs-on: ubuntu-latest 325 | steps: 326 | - uses: actions/checkout@v4 327 | with: 328 | submodules: true 329 | - name: Setup Rust 330 | uses: dtolnay/rust-toolchain@v1 331 | with: 332 | toolchain: stable 333 | - name: Publish crates 334 | uses: katyo/publish-crates@v2 335 | with: 336 | registry-token: ${{ secrets.CRATES_TOKEN }} 337 | args: --no-verify 338 | #dry-run: true 339 | 340 | cargo-ndk: 341 | runs-on: ubuntu-latest 342 | steps: 343 | - name: Prepare cache 344 | uses: actions/cache@v4 345 | id: cache 346 | with: 347 | path: ~/.cargo/bin/cargo-ndk 348 | key: ${{ runner.os }}-cargo-ndk 349 | - name: Setup cargo ndk 350 | if: steps.cache.outputs.cache-hit != 'true' 351 | run: cargo install cargo-ndk --force 352 | 353 | cargo-mobile2: 354 | runs-on: ubuntu-latest 355 | steps: 356 | - name: Prepare cache 357 | uses: actions/cache@v4 358 | id: cache 359 | with: 360 | path: | 361 | ~/.cargo/bin/cargo-android 362 | ~/.cargo/bin/cargo-mobile 363 | ~/.cargo/.cargo-mobile2 364 | key: ${{ runner.os }}-cargo-mobile2 365 | - name: Setup cargo mobile2 366 | if: steps.cache.outputs.cache-hit != 'true' 367 | run: cargo install cargo-mobile2 368 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | **/lib*.a 5 | .direnv 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "oboe"] 2 | path = sys/oboe 3 | url = https://github.com/google/oboe 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["sys", "demo"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.6.1" 7 | keywords = ["oboe", "android", "audio", "aaudio", "opensles"] 8 | authors = ["K. "] 9 | license = "Apache-2.0" 10 | repository = "https://github.com/katyo/oboe-rs" 11 | homepage = "https://github.com/katyo/oboe-rs" 12 | edition = "2021" 13 | 14 | [workspace.dependencies] 15 | num-traits = "0.2" 16 | num-derive = "0.4" 17 | atomic_float = "0.1" 18 | ndk-context = "0.1" 19 | ndk-glue = "0.7" 20 | jni = "0.21" 21 | cc = "1" 22 | bindgen = "0.69" 23 | fetch_unroll = "0.3" 24 | glutin = "0.31" 25 | log = "0.4" 26 | android_logger = "0.13" 27 | 28 | [workspace.dependencies.oboe-sys] 29 | version = "0.6" 30 | path = "sys" 31 | 32 | [workspace.dependencies.oboe] 33 | version = "0.6" 34 | path = "" 35 | 36 | [workspace.dependencies.egui-winit] 37 | version = "0.26" 38 | default-features = false 39 | 40 | [workspace.dependencies.eframe] 41 | version = "0.26" 42 | default-features = false 43 | 44 | [workspace.dependencies.ndk] 45 | version = "0.8" 46 | default-features = false 47 | 48 | [package] 49 | name = "oboe" 50 | version.workspace = true 51 | description = "Safe interface for oboe an android library for low latency audio IO" 52 | categories = ["api-bindings", "multimedia::audio"] 53 | keywords.workspace = true 54 | authors.workspace = true 55 | license.workspace = true 56 | readme = "README.md" 57 | repository.workspace = true 58 | homepage.workspace = true 59 | edition.workspace = true 60 | include = ["/src/**/*.rs", "/README.md"] 61 | 62 | [dependencies] 63 | num-traits.workspace = true 64 | num-derive.workspace = true 65 | oboe-sys.workspace = true 66 | 67 | [dependencies.ndk] 68 | workspace = true 69 | optional = true 70 | default-features = false 71 | 72 | [dependencies.ndk-context] 73 | workspace = true 74 | optional = true 75 | 76 | [dependencies.jni] 77 | workspace = true 78 | optional = true 79 | 80 | [features] 81 | shared-link = ["oboe-sys/shared-link"] 82 | shared-stdcxx = ["oboe-sys/shared-stdcxx"] 83 | generate-bindings = ["oboe-sys/generate-bindings"] 84 | fetch-prebuilt = ["oboe-sys/fetch-prebuilt"] 85 | java-interface = ["ndk", "ndk-context", "jni"] 86 | doc-cfg = [] 87 | 88 | [package.metadata.docs.rs] 89 | features = ["java-interface", "doc-cfg"] 90 | targets = [ 91 | "aarch64-linux-android", 92 | "armv7-linux-androideabi", 93 | "i686-linux-android", 94 | "x86_64-linux-android", 95 | ] 96 | 97 | [profile.release] 98 | opt-level = 'z' 99 | strip = true 100 | debug = false 101 | rpath = false 102 | lto = true 103 | debug-assertions = false 104 | codegen-units = 1 105 | panic = 'unwind' 106 | incremental = false 107 | overflow-checks = false 108 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | doc: 2 | @DOCS_RS=1 cargo +nightly doc --features java-interface,doc-cfg --target x86_64-linux-android 3 | 4 | ANDROID_TARGETS := \ 5 | armv7-linux-androideabi \ 6 | aarch64-linux-android \ 7 | i686-linux-android \ 8 | x86_64-linux-android 9 | 10 | ANDROID_API-armv7-linux-androideabi := 16 11 | ANDROID_API-i686-linux-android := 16 12 | ANDROID_API-aarch64-linux-android := 21 13 | ANDROID_API-x86_64-linux-android := 21 14 | 15 | define bindgen-rules 16 | bindgen: bindgen-$(1) 17 | bindgen-$(1): 18 | @cargo ndk --android-platform $$(ANDROID_API-$(1)) --target $(1) -- build --release --features generate-bindings 19 | @cp target/$(1)/release/build/oboe-sys-*/out/bindings.rs sys/src/bindings_`echo $(1) | sed -r 's/^([^-]+).*$$$$/\1/'`.rs 20 | endef 21 | 22 | $(foreach target,$(ANDROID_TARGETS),$(eval $(call bindgen-rules,$(target)))) 23 | 24 | keygen: 25 | @keytool -genkey -dname "cn=Illumium, ou=RnD, o=illumium.org, c=US" -v -keystore demo/release.keystore -alias demo -keyalg RSA -keysize 2048 -validity 20000 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust bindings for Oboe library 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/oboe--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/oboe-rs) 4 | [![Crates.io Package](https://img.shields.io/crates/v/oboe.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/oboe) 5 | [![Docs.rs API Docs](https://img.shields.io/badge/docs.rs-oboe-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/oboe) 6 | [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) 7 | [![CI Status](https://img.shields.io/github/actions/workflow/status/katyo/oboe-rs/rust.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/oboe-rs/actions?query=workflow%3ARust) 8 | 9 | Safe Rust interface for [Oboe](https://github.com/google/oboe) High-Performance Audio library for Android. 10 | Also it provides interface for some platform APIs significant to Audio IO. 11 | 12 | __Oboe__ is a C++ library which makes it easy to build high-performance audio apps on Android. It was created primarily to allow developers to target a simplified API that works across multiple API levels back to API level 16 (Jelly Bean). 13 | 14 | ## Crate features 15 | 16 | - __java-interface__ Add interface for some Android platform APIs. 17 | - __generate-bindings__ Generate bindings at compile-time. By default the pregenerated bindings will be used. 18 | - __compile-library__ Compile _oboe_ C++ library at compile-time using __cmake__. By default the precompiled library will be used. 19 | - __shared-link__ Use shared linking. By default the static Oboe libarary will be used. 20 | 21 | The crate already has pregenerated bindings and precompiled static libraries for the following Android targets: 22 | 23 | - __armv7__ 24 | - __aarch64__ 25 | - __i686__ 26 | - __x86_64__ 27 | 28 | ## Build issues 29 | 30 | The **[clang-sys](https://crates.io/crates/clang-sys)** crate uses **[llvm-config](http://llvm.org/docs/CommandGuide/llvm-config.html)** for searching [libclang](https://clang.llvm.org/docs/Tooling.html) library and preparing _C_/_C++_ compiler configuration. In order to get proper setup you should add *llvm-config* to your executables search path. 31 | 32 | In case of using tools with libclang under the hood like __bindgen__ you must be sure in proper your setup. Otherwise you get an errors related to missing headers or definitions. 33 | 34 | To build applications you need recent version of __cargo-apk__, which supports latest Android [SDK](https://developer.android.com/studio#command-tools) (28+) and [NDK](https://developer.android.com/ndk) (20+). Don't forget to set ANDROID_SDK_ROOT environment variable with paths to installed SDK. 35 | 36 | For building host crates which requires C-compiler you may also set __HOST_CC__ environment variable with path to your C-compiler. 37 | 38 | ## Usage example 39 | 40 | Playing sine wave in asynchronous (callback-driven) mode: 41 | 42 | ```rust 43 | use oboe::{ 44 | AudioOutputCallback, 45 | AudioOutputStream, 46 | AudioStreamBuilder, 47 | DataCallbackResult, 48 | PerformanceMode, 49 | SharingMode, 50 | Mono, 51 | }; 52 | 53 | // Structure for sound generator 54 | pub struct SineWave { 55 | frequency: f32, 56 | gain: f32, 57 | phase: f32, 58 | delta: Option, 59 | } 60 | 61 | // Default constructor for sound generator 62 | impl Default for SineWave { 63 | fn default() -> Self { 64 | Self { 65 | frequency: 440.0, 66 | gain: 0.5, 67 | phase: 0.0, 68 | delta: None, 69 | } 70 | } 71 | } 72 | 73 | // Audio output callback trait implementation 74 | impl AudioOutputCallback for SineWave { 75 | // Define type for frames which we would like to process 76 | type FrameType = (f32, Mono); 77 | 78 | // Implement sound data output callback 79 | fn on_audio_ready(&mut self, stream: &mut dyn AudioOutputStream, frames: &mut [f32]) -> DataCallbackResult { 80 | // Configure out wave generator 81 | if self.delta.is_none() { 82 | let sample_rate = stream.get_sample_rate() as f32; 83 | self.delta = (self.frequency * 2.0 * PI / sample_rate).into(); 84 | println!("Prepare sine wave generator: samplerate={}, time delta={}", sample_rate, self.delta.unwrap()); 85 | } 86 | 87 | let delta = self.delta.unwrap(); 88 | 89 | // Generate audio frames to fill the output buffer 90 | for frame in frames { 91 | *frame = self.gain * self.phase.sin(); 92 | self.phase += delta; 93 | while self.phase > 2.0 * PI { 94 | self.phase -= 2.0 * PI; 95 | } 96 | } 97 | 98 | // Notify the oboe that stream is continued 99 | DataCallbackResult::Continue 100 | } 101 | } 102 | 103 | // ... 104 | 105 | // Create playback stream 106 | let mut sine = AudioStreamBuilder::default() 107 | // select desired performance mode 108 | .set_performance_mode(PerformanceMode::LowLatency) 109 | // select desired sharing mode 110 | .set_sharing_mode(SharingMode::Shared) 111 | // select sound sample format 112 | .set_format::() 113 | // select channels configuration 114 | .set_channel_count::() 115 | // set our generator as callback 116 | .set_callback(SineWave::default()) 117 | // open the output stream 118 | .open_stream() 119 | .unwrap(); 120 | 121 | // Start playback 122 | sine.start().unwrap(); 123 | 124 | // ... 125 | ``` 126 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | with pkgs; 3 | let 4 | androidComposition = androidenv.composeAndroidPackages { 5 | #toolsVersion = "25.2.5"; 6 | #platformToolsVersion = "34.0.1"; 7 | buildToolsVersions = [ "34.0.0" "30.0.3" ]; 8 | #includeEmulator = true; 9 | #emulatorVersion = "27.2.0"; 10 | platformVersions = [ "16" "28" "33" "34" ]; 11 | #includeSources = false; 12 | #includeDocs = false; 13 | includeSystemImages = false; 14 | systemImageTypes = [ "default" ]; 15 | abiVersions = [ "x86" "x86_64" "armeabi-v7a" "arm64-v8a" ]; 16 | #lldbVersions = [ "2.0.2558144" ]; 17 | #cmakeVersions = [ "3.6.4111459" ]; 18 | includeNDK = true; 19 | #ndkVersions = [ "21.4.7075529" ]; 20 | ndkVersions = [ "25.2.9519653" ]; 21 | #useGoogleAPIs = false; 22 | #useGoogleTVAddOns = false; 23 | includeExtras = [ 24 | #"extras;google;gcm" 25 | ]; 26 | }; 27 | androidsdk = androidComposition.androidsdk; 28 | sdk_root = "${androidsdk}/libexec/android-sdk"; 29 | ndk_root = "${sdk_root}/ndk-bundle"; 30 | ndk_path = "${ndk_root}/toolchains/llvm/prebuilt/linux-x86_64/bin"; 31 | in mkShell rec { 32 | # cargo apk 33 | #ANDROID_SDK_ROOT = "${sdk_root}"; 34 | ANDROID_NDK_ROOT = "${ndk_root}"; 35 | NDK_HOME = "${ndk_root}"; 36 | 37 | # cargo ndk 38 | ANDROID_HOME = "${sdk_root}"; 39 | 40 | # llvm-config for libclang 41 | ##PATH = "${ndk_path}:${builtins.getEnv "PATH"}"; 42 | shellHook = '' 43 | buildToolsVersion=$(ls -1 ${sdk_root}/build-tools | head -n1) 44 | export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk_root}/build-tools/$buildToolsVersion/aapt2" 45 | export PATH="${ndk_path}:${androidsdk}/bin:$PATH"; 46 | ''; 47 | 48 | # reduce resources usage 49 | #DART_VM_OPTIONS = "--old_gen_heap_size=256 --observe"; 50 | #GRADLE_OPTS = "-Xmx64m -Dorg.gradle.jvmargs='-Xmx256m -XX:MaxPermSize=64m'"; 51 | 52 | buildInputs = [ pkg-config openssl zlib ncurses5 cmake libssh2 libgit2 ]; 53 | } 54 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | target/ 3 | **/*.rs.bk 4 | 5 | # cargo-mobile 6 | .cargo/ 7 | /gen 8 | 9 | # macOS 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oboe-demo" 3 | description = "Simple sine-wave playing demo" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | publish = false 10 | 11 | [lib] 12 | crate-type = ["staticlib", "cdylib", "rlib"] 13 | 14 | #[[bin]] 15 | #name = "oboe-demo-desktop" 16 | #path = "gen/bin/desktop.rs" 17 | 18 | [package.metadata.cargo-android] 19 | app-dependencies = [ 20 | "com.google.android.material:material:1.11.0", 21 | ] 22 | project-dependencies = [ "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" ] 23 | app-plugins = [ "org.jetbrains.kotlin.android" ] 24 | app-permissions = [ 25 | # "android.permission.WRITE_EXTERNAL_STORAGE", 26 | "android.permission.RECORD_AUDIO", 27 | ] 28 | app-features = [ 29 | "android.hardware.audio.output", 30 | "android.hardware.microphone", 31 | "android.hardware.audio.low_latency", 32 | ] 33 | app-theme-parent = "Theme.MaterialComponents.DayNight.DarkActionBar" 34 | 35 | [dependencies] 36 | log.workspace = true 37 | atomic_float.workspace = true 38 | android_logger.workspace = true 39 | 40 | [dependencies.oboe] 41 | workspace = true 42 | features = [ 43 | #"generate-bindings", 44 | "java-interface", 45 | "shared-stdcxx", 46 | ] 47 | 48 | [dependencies.egui-winit] 49 | workspace = true 50 | 51 | [dependencies.eframe] 52 | workspace = true 53 | features = ["wgpu", "default_fonts"] 54 | 55 | [features] 56 | default = ["android-native-activity"] 57 | android-native-activity = ["eframe/android-native-activity", "egui-winit/android-native-activity"] 58 | android-game-activity = ["eframe/android-native-activity", "egui-winit/android-game-activity"] 59 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo of Rust bindings for Oboe library 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/oboe--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/oboe-rs) 4 | [![Crates.io Package](https://img.shields.io/crates/v/oboe.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/oboe) 5 | [![Docs.rs API Docs](https://img.shields.io/badge/docs.rs-oboe-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/oboe) 6 | [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) 7 | [![CI Status](https://img.shields.io/github/actions/workflow/status/katyo/oboe-rs/rust.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/oboe-rs/actions?query=workflow%3ARust) 8 | 9 | Demo of Rust interface for [Oboe](https://github.com/google/oboe) High-Performance Audio library for Android. 10 | Also it provides interface for some platform APIs significant to Audio IO. 11 | 12 | __Oboe__ is a C++ library which makes it easy to build high-performance audio apps on Android. It was created primarily to allow developers to target a simplified API that works across multiple API levels back to API level 16 (Jelly Bean). 13 | 14 | [![Oboe Demo](oboe-demo.png)](oboe-demo.png) 15 | 16 | ## Building and running 17 | 18 | First install `cargo-mobile2` tool: 19 | ``` 20 | $ cargo install cargo-mobile2 21 | ``` 22 | 23 | Next generate project data: 24 | ``` 25 | $ cargo mobile init 26 | ``` 27 | 28 | Connect to your Android device using `adb`: 29 | ``` 30 | adb connect 31 | ``` 32 | 33 | Build and run demo on connected device: 34 | ``` 35 | $ cargo mobile android run 36 | ``` 37 | 38 | Alternatively build apk binaries to install it manually: 39 | ``` 40 | $ cargo mobile android apk build --release --split-per-abi 41 | ``` 42 | 43 | To skip build for all supported targets specify needed only as following: 44 | ``` 45 | $ cargo mobile android apk build --release aarch64 46 | ``` 47 | -------------------------------------------------------------------------------- /demo/mobile.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | name = "oboe-demo" 3 | stylized-name = "Oboe Demo" 4 | domain = "illumium.org" 5 | template-pack = "egui" 6 | -------------------------------------------------------------------------------- /demo/oboe-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katyo/oboe-rs/7ea2b9b3bc9cdfa9ed4cbfeafdcafb47b3fac4e7/demo/oboe-demo.png -------------------------------------------------------------------------------- /demo/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/katyo/oboe-rs/7ea2b9b3bc9cdfa9ed4cbfeafdcafb47b3fac4e7/demo/release.keystore -------------------------------------------------------------------------------- /demo/src/audio.rs: -------------------------------------------------------------------------------- 1 | use atomic_float::AtomicF32; 2 | use std::{ 3 | f32::consts::PI, 4 | marker::PhantomData, 5 | sync::{atomic::Ordering, Arc}, 6 | }; 7 | 8 | use oboe::{ 9 | AudioDeviceDirection, AudioDeviceInfo, AudioFeature, AudioOutputCallback, AudioOutputStream, 10 | AudioOutputStreamSafe, AudioStream, AudioStreamAsync, AudioStreamBase, AudioStreamBuilder, 11 | AudioStreamSafe, DataCallbackResult, DefaultStreamValues, Mono, Output, PerformanceMode, 12 | Result, SharingMode, Stereo, StreamState, 13 | }; 14 | 15 | pub enum GenState { 16 | Started, 17 | Paused, 18 | Stopped, 19 | } 20 | 21 | /// Sine-wave generator stream 22 | #[derive(Default)] 23 | pub struct SineGen { 24 | stream: Option>>, 25 | param: Arc, 26 | } 27 | 28 | impl core::ops::Deref for SineGen { 29 | type Target = SineParam; 30 | 31 | fn deref(&self) -> &Self::Target { 32 | &self.param 33 | } 34 | } 35 | 36 | impl SineGen { 37 | pub fn state(&self) -> GenState { 38 | if let Some(stream) = &self.stream { 39 | if matches!(stream.get_state(), StreamState::Paused) { 40 | GenState::Paused 41 | } else { 42 | GenState::Started 43 | } 44 | } else { 45 | GenState::Stopped 46 | } 47 | } 48 | 49 | /// Create and start audio stream 50 | pub fn start(&mut self) -> Result<()> { 51 | if let Some(stream) = &mut self.stream { 52 | stream.start()?; 53 | } else { 54 | let mut stream = AudioStreamBuilder::default() 55 | .set_performance_mode(PerformanceMode::LowLatency) 56 | .set_sharing_mode(SharingMode::Shared) 57 | .set_format::() 58 | .set_channel_count::() 59 | .set_callback(SineWave::::new(&self.param)) 60 | .open_stream()?; 61 | 62 | log::debug!("start stream: {:?}", stream); 63 | 64 | self.param.set_sample_rate(stream.get_sample_rate() as _); 65 | 66 | stream.start()?; 67 | 68 | self.stream = Some(stream); 69 | } 70 | 71 | Ok(()) 72 | } 73 | 74 | /// Pause audio stream 75 | pub fn pause(&mut self) -> Result<()> { 76 | if let Some(stream) = &mut self.stream { 77 | log::debug!("pause stream: {:?}", stream); 78 | stream.pause()?; 79 | } 80 | Ok(()) 81 | } 82 | 83 | /// Stop and remove audio stream 84 | pub fn stop(&mut self) -> Result<()> { 85 | if let Some(stream) = &mut self.stream { 86 | log::debug!("stop stream: {:?}", stream); 87 | stream.stop()?; 88 | self.stream = None; 89 | } 90 | Ok(()) 91 | } 92 | } 93 | 94 | pub struct SineParam { 95 | frequency: AtomicF32, 96 | gain: AtomicF32, 97 | sample_rate: AtomicF32, 98 | delta: AtomicF32, 99 | } 100 | 101 | impl Default for SineParam { 102 | fn default() -> Self { 103 | Self { 104 | frequency: AtomicF32::new(440.0), 105 | gain: AtomicF32::new(0.5), 106 | sample_rate: AtomicF32::new(0.0), 107 | delta: AtomicF32::new(0.0), 108 | } 109 | } 110 | } 111 | 112 | impl SineParam { 113 | pub fn sample_rate(&self) -> f32 { 114 | self.sample_rate.load(Ordering::SeqCst) 115 | } 116 | 117 | pub fn set_sample_rate(&self, sample_rate: f32) { 118 | let frequency = self.frequency.load(Ordering::Acquire); 119 | let delta = frequency * 2.0 * PI / sample_rate; 120 | 121 | self.delta.store(delta, Ordering::Release); 122 | self.sample_rate.store(sample_rate, Ordering::SeqCst); 123 | 124 | println!("Prepare sine wave generator: samplerate={sample_rate}, time delta={delta}"); 125 | } 126 | 127 | pub fn frequency(&self) -> f32 { 128 | self.frequency.load(Ordering::SeqCst) 129 | } 130 | 131 | pub fn set_frequency(&self, frequency: f32) { 132 | let sample_rate = self.sample_rate.load(Ordering::SeqCst); 133 | let delta = frequency * 2.0 * PI / sample_rate; 134 | 135 | self.delta.store(delta, Ordering::SeqCst); 136 | self.frequency.store(frequency, Ordering::SeqCst); 137 | } 138 | 139 | pub fn gain(&self) -> f32 { 140 | self.gain.load(Ordering::SeqCst) 141 | } 142 | 143 | pub fn set_gain(&self, gain: f32) { 144 | self.gain.store(gain, Ordering::SeqCst); 145 | } 146 | } 147 | 148 | pub struct SineWave { 149 | param: Arc, 150 | phase: f32, 151 | marker: PhantomData<(F, C)>, 152 | } 153 | 154 | impl Drop for SineWave { 155 | fn drop(&mut self) { 156 | println!("drop SineWave generator"); 157 | } 158 | } 159 | 160 | impl SineWave { 161 | pub fn new(param: &Arc) -> Self { 162 | println!("init SineWave generator"); 163 | Self { 164 | param: param.clone(), 165 | phase: 0.0, 166 | marker: PhantomData, 167 | } 168 | } 169 | } 170 | 171 | impl Iterator for SineWave { 172 | type Item = f32; 173 | 174 | fn next(&mut self) -> Option { 175 | let delta = self.param.delta.load(Ordering::Relaxed); 176 | let gain = self.param.gain.load(Ordering::Relaxed); 177 | 178 | let frame = gain * self.phase.sin(); 179 | 180 | self.phase += delta; 181 | while self.phase > 2.0 * PI { 182 | self.phase -= 2.0 * PI; 183 | } 184 | 185 | Some(frame) 186 | } 187 | } 188 | 189 | impl AudioOutputCallback for SineWave { 190 | type FrameType = (f32, Mono); 191 | 192 | fn on_audio_ready( 193 | &mut self, 194 | _stream: &mut dyn AudioOutputStreamSafe, 195 | frames: &mut [f32], 196 | ) -> DataCallbackResult { 197 | for frame in frames { 198 | *frame = self.next().unwrap(); 199 | } 200 | DataCallbackResult::Continue 201 | } 202 | } 203 | 204 | impl AudioOutputCallback for SineWave { 205 | type FrameType = (f32, Stereo); 206 | 207 | fn on_audio_ready( 208 | &mut self, 209 | _stream: &mut dyn AudioOutputStreamSafe, 210 | frames: &mut [(f32, f32)], 211 | ) -> DataCallbackResult { 212 | for frame in frames { 213 | frame.0 = self.next().unwrap(); 214 | frame.1 = frame.0; 215 | } 216 | DataCallbackResult::Continue 217 | } 218 | } 219 | 220 | /// Print device's audio info 221 | pub fn print_info() { 222 | if let Err(error) = DefaultStreamValues::init() { 223 | eprintln!("Unable to init default stream values due to: {error}"); 224 | } 225 | 226 | println!("Default stream values:"); 227 | println!(" Sample rate: {}", DefaultStreamValues::get_sample_rate()); 228 | println!( 229 | " Frames per burst: {}", 230 | DefaultStreamValues::get_frames_per_burst() 231 | ); 232 | println!( 233 | " Channel count: {}", 234 | DefaultStreamValues::get_channel_count() 235 | ); 236 | 237 | println!("Audio features:"); 238 | println!(" Low latency: {}", AudioFeature::LowLatency.has().unwrap()); 239 | println!(" Output: {}", AudioFeature::Output.has().unwrap()); 240 | println!(" Pro: {}", AudioFeature::Pro.has().unwrap()); 241 | println!(" Microphone: {}", AudioFeature::Microphone.has().unwrap()); 242 | println!(" Midi: {}", AudioFeature::Midi.has().unwrap()); 243 | 244 | let devices = AudioDeviceInfo::request(AudioDeviceDirection::InputOutput).unwrap(); 245 | 246 | println!("Audio Devices:"); 247 | 248 | for device in devices { 249 | println!("{{"); 250 | println!(" Id: {}", device.id); 251 | println!(" Type: {:?}", device.device_type); 252 | println!(" Direction: {:?}", device.direction); 253 | println!(" Address: {}", device.address); 254 | println!(" Product name: {}", device.product_name); 255 | println!(" Channel counts: {:?}", device.channel_counts); 256 | println!(" Sample rates: {:?}", device.sample_rates); 257 | println!(" Formats: {:?}", device.formats); 258 | println!("}}"); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /demo/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod audio; 2 | 3 | use eframe::{egui, NativeOptions}; 4 | 5 | use audio::{print_info, GenState, SineGen}; 6 | 7 | #[cfg(target_os = "android")] 8 | use egui_winit::winit; 9 | #[cfg(target_os = "android")] 10 | #[no_mangle] 11 | fn android_main(app: winit::platform::android::activity::AndroidApp) { 12 | use eframe::Renderer; 13 | use winit::platform::android::EventLoopBuilderExtAndroid; 14 | 15 | std::env::set_var("RUST_BACKTRACE", "full"); 16 | android_logger::init_once( 17 | android_logger::Config::default().with_max_level(log::LevelFilter::Info), 18 | ); 19 | 20 | let options = NativeOptions { 21 | event_loop_builder: Some(Box::new(|builder| { 22 | builder.with_android_app(app); 23 | })), 24 | renderer: Renderer::Wgpu, 25 | ..Default::default() 26 | }; 27 | DemoApp::run(options).unwrap(); 28 | } 29 | 30 | #[derive(Default)] 31 | pub struct DemoApp { 32 | pub gen: SineGen, 33 | pub error: Option, 34 | } 35 | 36 | impl DemoApp { 37 | pub fn run(options: NativeOptions) -> Result<(), eframe::Error> { 38 | print_info(); 39 | 40 | eframe::run_native( 41 | "egui-android-demo", 42 | options, 43 | Box::new(|_cc| Box::::default()), 44 | ) 45 | } 46 | 47 | pub fn result(&mut self, result: oboe::Result<()>) { 48 | self.error = result.err().map(|err| err.to_string()); 49 | } 50 | } 51 | 52 | impl eframe::App for DemoApp { 53 | fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 54 | ctx.set_pixels_per_point(2.0); 55 | 56 | egui::CentralPanel::default().show(ctx, |ui| { 57 | ui.label(egui::RichText::new("oboe-rs: Sine-wave generator").heading()); 58 | 59 | ui.separator(); 60 | 61 | ui.label("Start/stop generator:"); 62 | 63 | ui.horizontal(|ui| { 64 | let state = self.gen.state(); 65 | 66 | if ui.add_enabled(!matches!(state, GenState::Started), egui::Button::new("Start")).clicked() { 67 | let res = self.gen.start(); 68 | self.result(res); 69 | } 70 | 71 | if ui.add_enabled(!matches!(state, GenState::Stopped), egui::Button::new("Stop")).clicked() { 72 | let res = self.gen.stop(); 73 | self.result(res); 74 | } 75 | 76 | if ui.add_enabled(matches!(state, GenState::Started), egui::Button::new("Pause")).clicked() { 77 | let res = self.gen.pause(); 78 | self.result(res); 79 | } 80 | }); 81 | 82 | ui.separator(); 83 | 84 | ui.label("You can select frequency:"); 85 | 86 | ui.horizontal(|ui| { 87 | if ui.button("100 Hz").clicked() { 88 | self.gen.set_frequency(100.0); 89 | } 90 | if ui.button("440 Hz").clicked() { 91 | self.gen.set_frequency(440.0); 92 | } 93 | if ui.button("1 KHz").clicked() { 94 | self.gen.set_frequency(1000.0); 95 | } 96 | }); 97 | 98 | ui.label("Also you can change frequency:"); 99 | 100 | ui.add(egui::Slider::from_get_set(50.0..=5000.0, |set: Option| { 101 | if let Some(freq) = set { 102 | self.gen.set_frequency(freq as _); 103 | } 104 | self.gen.frequency() as _ 105 | }).text("Frequency")); 106 | 107 | ui.separator(); 108 | 109 | ui.label("You can adjust gain."); 110 | 111 | ui.add(egui::Slider::from_get_set(0.0..=1.0, |set: Option| { 112 | if let Some(gain) = set { 113 | self.gen.set_gain(gain as _); 114 | } 115 | self.gen.gain() as _ 116 | }).text("Gain")); 117 | 118 | ui.separator(); 119 | 120 | ui.label(if let Some(error) = &self.error { 121 | egui::RichText::new(error).color(egui::Color32::RED) 122 | } else { 123 | egui::RichText::new("OK").color(egui::Color32::GREEN) 124 | }); 125 | 126 | ui.separator(); 127 | 128 | ui.label("If you don't hear the sound or something looks broken, please report the problem here:"); 129 | ui.hyperlink("https://github.com/katyo/oboe-rs/issues"); 130 | 131 | ui.separator(); 132 | 133 | if ui.button("Quit").clicked() { 134 | std::process::exit(0); 135 | }; 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /demo/target: -------------------------------------------------------------------------------- 1 | ../target -------------------------------------------------------------------------------- /src/audio_stream.rs: -------------------------------------------------------------------------------- 1 | use num_traits::FromPrimitive; 2 | use oboe_sys as ffi; 3 | use std::{ 4 | ffi::c_void, 5 | fmt::{self, Display}, 6 | marker::PhantomData, 7 | mem::{transmute, MaybeUninit}, 8 | ops::{Deref, DerefMut}, 9 | }; 10 | 11 | use super::{ 12 | audio_stream_base_fmt, wrap_result, wrap_status, AudioApi, AudioStreamBase, FrameTimestamp, 13 | Input, IsFrameType, Output, RawAudioInputStream, RawAudioOutputStream, RawAudioStream, 14 | RawAudioStreamBase, Result, Status, StreamState, NANOS_PER_MILLISECOND, 15 | }; 16 | 17 | /** 18 | * The default number of nanoseconds to wait for when performing state change operations on the 19 | * stream, such as `start` and `stop`. 20 | * 21 | * See [AudioStream::start_with_timeout] 22 | */ 23 | pub const DEFAULT_TIMEOUT_NANOS: i64 = 2000 * NANOS_PER_MILLISECOND; 24 | 25 | /** 26 | * Safe base trait for Oboe audio stream. 27 | */ 28 | pub trait AudioStreamSafe: AudioStreamBase { 29 | /** 30 | * Query the current state, eg. `StreamState::Pausing` 31 | */ 32 | fn get_state(&self) -> StreamState; 33 | 34 | /** 35 | * This can be used to adjust the latency of the buffer by changing 36 | * the threshold where blocking will occur. 37 | * By combining this with [`AudioStreamSafe::get_xrun_count`], the latency can be tuned 38 | * at run-time for each device. 39 | * 40 | * This cannot be set higher than [`AudioStreamBase::get_buffer_capacity_in_frames`]. 41 | */ 42 | fn set_buffer_size_in_frames(&mut self, _requested_frames: i32) -> Result; 43 | 44 | /** 45 | * An XRun is an Underrun or an Overrun. 46 | * During playing, an underrun will occur if the stream is not written in time 47 | * and the system runs out of valid data. 48 | * During recording, an overrun will occur if the stream is not read in time 49 | * and there is no place to put the incoming data so it is discarded. 50 | * 51 | * An underrun or overrun can cause an audible "pop" or "glitch". 52 | */ 53 | fn get_xrun_count(&self) -> Result; 54 | 55 | /** 56 | * Returns true if XRun counts are supported on the stream 57 | */ 58 | fn is_xrun_count_supported(&self) -> bool; 59 | 60 | /** 61 | * Query the number of frames that are read or written by the endpoint at one time. 62 | */ 63 | fn get_frames_per_burst(&mut self) -> i32; 64 | 65 | /** 66 | * Get the number of bytes in each audio frame. This is calculated using the channel count 67 | * and the sample format. For example, a 2 channel floating point stream will have 68 | * 2 * 4 = 8 bytes per frame. 69 | */ 70 | fn get_bytes_per_frame(&mut self) -> i32 { 71 | self.get_channel_count() as i32 * self.get_bytes_per_sample() 72 | } 73 | 74 | /** 75 | * Get the number of bytes per sample. This is calculated using the sample format. For example, 76 | * a stream using 16-bit integer samples will have 2 bytes per sample. 77 | * 78 | * @return the number of bytes per sample. 79 | */ 80 | fn get_bytes_per_sample(&mut self) -> i32; 81 | 82 | /** 83 | * Calculate the latency of a stream based on getTimestamp(). 84 | * 85 | * Output latency is the time it takes for a given frame to travel from the 86 | * app to some type of digital-to-analog converter. If the DAC is external, for example 87 | * in a USB interface or a TV connected by HDMI, then there may be additional latency 88 | * that the Android device is unaware of. 89 | * 90 | * Input latency is the time it takes to a given frame to travel from an analog-to-digital 91 | * converter (ADC) to the app. 92 | * 93 | * Note that the latency of an OUTPUT stream will increase abruptly when you write data to it 94 | * and then decrease slowly over time as the data is consumed. 95 | * 96 | * The latency of an INPUT stream will decrease abruptly when you read data from it 97 | * and then increase slowly over time as more data arrives. 98 | * 99 | * The latency of an OUTPUT stream is generally higher than the INPUT latency 100 | * because an app generally tries to keep the OUTPUT buffer full and the INPUT buffer empty. 101 | */ 102 | fn calculate_latency_millis(&mut self) -> Result; 103 | 104 | /** 105 | * Get the estimated time that the frame at `frame_position` entered or left the audio processing 106 | * pipeline. 107 | * 108 | * This can be used to coordinate events and interactions with the external environment, and to 109 | * estimate the latency of an audio stream. An example of usage can be found in the hello-oboe 110 | * sample (search for "calculate_current_output_latency_millis"). 111 | * 112 | * The time is based on the implementation's best effort, using whatever knowledge is available 113 | * to the system, but cannot account for any delay unknown to the implementation. 114 | * 115 | * @param clockId the type of clock to use e.g. CLOCK_MONOTONIC 116 | * @return a FrameTimestamp containing the position and time at which a particular audio frame 117 | * entered or left the audio processing pipeline, or an error if the operation failed. 118 | */ 119 | fn get_timestamp(&mut self, clock_id: i32) -> Result; 120 | 121 | /** 122 | * Get the underlying audio API which the stream uses. 123 | */ 124 | fn get_audio_api(&self) -> AudioApi; 125 | 126 | /** 127 | * Returns true if the underlying audio API is AAudio. 128 | */ 129 | fn uses_aaudio(&self) -> bool { 130 | self.get_audio_api() == AudioApi::AAudio 131 | } 132 | 133 | /** 134 | * Returns the number of frames of data currently in the buffer 135 | */ 136 | fn get_available_frames(&mut self) -> Result; 137 | } 138 | 139 | /** 140 | * Base trait for Oboe audio stream. 141 | */ 142 | pub trait AudioStream: AudioStreamSafe { 143 | /** 144 | * Open a stream based on the current settings. 145 | * 146 | * Note that we do not recommend re-opening a stream that has been closed. 147 | * TODO Should we prevent re-opening? 148 | */ 149 | fn open(&mut self) -> Status { 150 | Ok(()) 151 | } 152 | 153 | /** 154 | * Close the stream and deallocate any resources from the open() call. 155 | */ 156 | fn close(&mut self) -> Status; 157 | 158 | /** 159 | * Start the stream. This will block until the stream has been started, an error occurs 160 | * or `timeout_nanoseconds` has been reached. 161 | */ 162 | fn start(&mut self) -> Status { 163 | self.start_with_timeout(DEFAULT_TIMEOUT_NANOS) 164 | } 165 | 166 | /** 167 | * Start the stream. This will block until the stream has been started, an error occurs 168 | * or `timeout_nanoseconds` has been reached. 169 | */ 170 | fn start_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status; 171 | 172 | /** 173 | * Stop the stream. This will block until the stream has been stopped, an error occurs 174 | * or `timeoutNanoseconds` has been reached. 175 | */ 176 | fn stop(&mut self) -> Status { 177 | self.stop_with_timeout(DEFAULT_TIMEOUT_NANOS) 178 | } 179 | 180 | /** 181 | * Stop the stream. This will block until the stream has been stopped, an error occurs 182 | * or `timeoutNanoseconds` has been reached. 183 | */ 184 | fn stop_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status; 185 | 186 | /** 187 | * Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling 188 | * `start(0)`. 189 | */ 190 | fn request_start(&mut self) -> Status; 191 | 192 | /** 193 | * Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling 194 | * `stop(0)`. 195 | */ 196 | fn request_stop(&mut self) -> Status; 197 | 198 | /** 199 | * Wait until the stream's current state no longer matches the input state. 200 | * The input state is passed to avoid race conditions caused by the state 201 | * changing between calls. 202 | * 203 | * Note that generally applications do not need to call this. It is considered 204 | * an advanced technique and is mostly used for testing. 205 | * 206 | * ```ignore 207 | * const TIMEOUT_NANOS: i64 = 500 * NANOS_PER_MILLISECOND; // arbitrary 1/2 second 208 | * let mut current_state = stream.get_state(); 209 | * loop { 210 | * if let Ok(next_state) = stream.wait_for_state_change(current_state, TIMEOUT_NANOS) { 211 | * if next_state != StreamState::Paused { 212 | * current_state = next_state; 213 | * continue; 214 | * } 215 | * } 216 | * break; 217 | * } 218 | * ``` 219 | * 220 | * If the state does not change within the timeout period then it will 221 | * return [`Error::Timeout`](crate::Error::Timeout). This is true even if timeout_nanoseconds is zero. 222 | */ 223 | fn wait_for_state_change( 224 | &mut self, 225 | input_state: StreamState, 226 | timeout_nanoseconds: i64, 227 | ) -> Result; 228 | 229 | /** 230 | * Wait until the stream has a minimum amount of data available in its buffer. 231 | * This can be used with an EXCLUSIVE MMAP input stream to avoid reading data too close to 232 | * the DSP write position, which may cause glitches. 233 | */ 234 | fn wait_for_available_frames( 235 | &mut self, 236 | num_frames: i32, 237 | timeout_nanoseconds: i64, 238 | ) -> Result; 239 | } 240 | 241 | /** 242 | * The stream which is used for async audio input 243 | */ 244 | pub trait AudioInputStreamSafe: AudioStreamSafe { 245 | /** 246 | * The number of audio frames read from the stream. 247 | * This monotonic counter will never get reset. 248 | */ 249 | fn get_frames_read(&mut self) -> i64; 250 | } 251 | 252 | /** 253 | * The stream which is used for audio input 254 | */ 255 | pub trait AudioInputStream: AudioStream + AudioInputStreamSafe {} 256 | 257 | /** 258 | * The stream which can be used for audio input in synchronous mode 259 | */ 260 | pub trait AudioInputStreamSync: AudioInputStream { 261 | type FrameType: IsFrameType; 262 | 263 | /** 264 | * Read data into the supplied buffer from the stream. This method will block until the read 265 | * is complete or it runs out of time. 266 | * 267 | * If `timeout_nanoseconds` is zero then this call will not wait. 268 | */ 269 | fn read( 270 | &mut self, 271 | _buffer: &mut [::Type], 272 | _timeout_nanoseconds: i64, 273 | ) -> Result; 274 | } 275 | 276 | /** 277 | * The stream which is used for async audio output 278 | */ 279 | pub trait AudioOutputStreamSafe: AudioStreamSafe { 280 | /** 281 | * The number of audio frames written into the stream. 282 | * This monotonic counter will never get reset. 283 | */ 284 | fn get_frames_written(&mut self) -> i64; 285 | } 286 | 287 | /** 288 | * The stream which has pause/flush capabilities 289 | */ 290 | pub trait AudioOutputStream: AudioStream + AudioOutputStreamSafe { 291 | /** 292 | * Pause the stream. This will block until the stream has been paused, an error occurs 293 | * or `timeoutNanoseconds` has been reached. 294 | */ 295 | fn pause(&mut self) -> Status { 296 | self.pause_with_timeout(DEFAULT_TIMEOUT_NANOS) 297 | } 298 | 299 | /** 300 | * Pause the stream. This will block until the stream has been paused, an error occurs 301 | * or `timeoutNanoseconds` has been reached. 302 | */ 303 | fn pause_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status; 304 | 305 | /** 306 | * Flush the stream. This will block until the stream has been flushed, an error occurs 307 | * or `timeoutNanoseconds` has been reached. 308 | */ 309 | fn flush(&mut self) -> Status { 310 | self.flush_with_timeout(DEFAULT_TIMEOUT_NANOS) 311 | } 312 | 313 | /** 314 | * Flush the stream. This will block until the stream has been flushed, an error occurs 315 | * or `timeoutNanoseconds` has been reached. 316 | */ 317 | fn flush_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status; 318 | 319 | /** 320 | * Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling 321 | * `pause(0)`. 322 | */ 323 | fn request_pause(&mut self) -> Status; 324 | 325 | /** 326 | * Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling 327 | * `flush(0)`. 328 | */ 329 | fn request_flush(&mut self) -> Status; 330 | } 331 | 332 | /** 333 | * The stream which can be used for audio output in synchronous mode 334 | */ 335 | pub trait AudioOutputStreamSync: AudioOutputStream { 336 | type FrameType: IsFrameType; 337 | 338 | /** 339 | * Write data from the supplied buffer into the stream. This method will block until the write 340 | * is complete or it runs out of time. 341 | * 342 | * If `timeout_nanoseconds` is zero then this call will not wait. 343 | */ 344 | fn write( 345 | &mut self, 346 | _buffer: &[::Type], 347 | _timeout_nanoseconds: i64, 348 | ) -> Result; 349 | } 350 | 351 | impl AudioStreamSafe for T { 352 | fn set_buffer_size_in_frames(&mut self, requested_frames: i32) -> Result { 353 | wrap_result(unsafe { 354 | ffi::oboe_AudioStream_setBufferSizeInFrames(self._raw_stream_mut(), requested_frames) 355 | }) 356 | } 357 | 358 | fn get_state(&self) -> StreamState { 359 | FromPrimitive::from_i32(unsafe { 360 | ffi::oboe_AudioStream_getState(self._raw_stream() as *const _ as *mut _) 361 | }) 362 | .unwrap() 363 | } 364 | 365 | fn get_xrun_count(&self) -> Result { 366 | wrap_result(unsafe { 367 | ffi::oboe_AudioStream_getXRunCount(self._raw_stream() as *const _ as *mut _) 368 | }) 369 | } 370 | 371 | fn is_xrun_count_supported(&self) -> bool { 372 | unsafe { ffi::oboe_AudioStream_isXRunCountSupported(self._raw_stream()) } 373 | } 374 | 375 | fn get_frames_per_burst(&mut self) -> i32 { 376 | unsafe { ffi::oboe_AudioStream_getFramesPerBurst(self._raw_stream_mut()) } 377 | } 378 | 379 | fn get_bytes_per_sample(&mut self) -> i32 { 380 | unsafe { ffi::oboe_AudioStream_getBytesPerSample(self._raw_stream_mut()) } 381 | } 382 | 383 | fn calculate_latency_millis(&mut self) -> Result { 384 | wrap_result(unsafe { ffi::oboe_AudioStream_calculateLatencyMillis(self._raw_stream_mut()) }) 385 | } 386 | 387 | fn get_timestamp(&mut self, clock_id: i32 /* clockid_t */) -> Result { 388 | wrap_result(unsafe { 389 | transmute(ffi::oboe_AudioStream_getTimestamp( 390 | self._raw_stream_mut() as *mut _ as *mut c_void, 391 | clock_id, 392 | )) 393 | }) 394 | } 395 | 396 | fn get_audio_api(&self) -> AudioApi { 397 | FromPrimitive::from_i32(unsafe { ffi::oboe_AudioStream_getAudioApi(self._raw_stream()) }) 398 | .unwrap() 399 | } 400 | 401 | fn get_available_frames(&mut self) -> Result { 402 | wrap_result(unsafe { ffi::oboe_AudioStream_getAvailableFrames(self._raw_stream_mut()) }) 403 | } 404 | } 405 | 406 | impl AudioStream for T { 407 | fn open(&mut self) -> Status { 408 | wrap_status(unsafe { ffi::oboe_AudioStream_open(self._raw_stream_mut()) }) 409 | } 410 | 411 | fn close(&mut self) -> Status { 412 | wrap_status(unsafe { ffi::oboe_AudioStream_close1(self._raw_stream_mut()) }) 413 | } 414 | 415 | fn start_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status { 416 | wrap_status(unsafe { 417 | ffi::oboe_AudioStream_start( 418 | self._raw_stream_mut() as *mut _ as *mut c_void, 419 | timeout_nanoseconds, 420 | ) 421 | }) 422 | } 423 | 424 | fn stop_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status { 425 | wrap_status(unsafe { 426 | ffi::oboe_AudioStream_stop( 427 | self._raw_stream_mut() as *mut _ as *mut c_void, 428 | timeout_nanoseconds, 429 | ) 430 | }) 431 | } 432 | 433 | fn request_start(&mut self) -> Status { 434 | wrap_status(unsafe { ffi::oboe_AudioStream_requestStart(self._raw_stream_mut()) }) 435 | } 436 | 437 | fn request_stop(&mut self) -> Status { 438 | wrap_status(unsafe { ffi::oboe_AudioStream_requestStop(self._raw_stream_mut()) }) 439 | } 440 | 441 | fn wait_for_state_change( 442 | &mut self, 443 | input_state: StreamState, 444 | timeout_nanoseconds: i64, 445 | ) -> Result { 446 | let mut next_state = MaybeUninit::::uninit(); 447 | wrap_status(unsafe { 448 | ffi::oboe_AudioStream_waitForStateChange( 449 | self._raw_stream_mut(), 450 | input_state as i32, 451 | next_state.as_mut_ptr() as *mut i32, 452 | timeout_nanoseconds, 453 | ) 454 | }) 455 | .map(|_| unsafe { next_state.assume_init() }) 456 | } 457 | 458 | fn wait_for_available_frames( 459 | &mut self, 460 | num_frames: i32, 461 | timeout_nanoseconds: i64, 462 | ) -> Result { 463 | wrap_result(unsafe { 464 | ffi::oboe_AudioStream_waitForAvailableFrames( 465 | self._raw_stream_mut(), 466 | num_frames, 467 | timeout_nanoseconds, 468 | ) 469 | }) 470 | } 471 | } 472 | 473 | impl AudioInputStreamSafe for T { 474 | fn get_frames_read(&mut self) -> i64 { 475 | unsafe { 476 | ffi::oboe_AudioStream_getFramesRead(self._raw_stream_mut() as *mut _ as *mut c_void) 477 | } 478 | } 479 | } 480 | 481 | impl AudioInputStream for T {} 482 | 483 | impl AudioOutputStreamSafe for T { 484 | fn get_frames_written(&mut self) -> i64 { 485 | unsafe { 486 | ffi::oboe_AudioStream_getFramesWritten(self._raw_stream_mut() as *mut _ as *mut c_void) 487 | } 488 | } 489 | } 490 | 491 | impl AudioOutputStream for T { 492 | fn pause_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status { 493 | wrap_status(unsafe { 494 | ffi::oboe_AudioStream_pause( 495 | self._raw_stream_mut() as *mut _ as *mut c_void, 496 | timeout_nanoseconds, 497 | ) 498 | }) 499 | } 500 | 501 | fn flush_with_timeout(&mut self, timeout_nanoseconds: i64) -> Status { 502 | wrap_status(unsafe { 503 | ffi::oboe_AudioStream_flush( 504 | self._raw_stream_mut() as *mut _ as *mut c_void, 505 | timeout_nanoseconds, 506 | ) 507 | }) 508 | } 509 | 510 | fn request_pause(&mut self) -> Status { 511 | wrap_status(unsafe { ffi::oboe_AudioStream_requestPause(self._raw_stream_mut()) }) 512 | } 513 | 514 | fn request_flush(&mut self) -> Status { 515 | wrap_status(unsafe { ffi::oboe_AudioStream_requestFlush(self._raw_stream_mut()) }) 516 | } 517 | } 518 | 519 | pub(crate) fn audio_stream_fmt( 520 | stream: &T, 521 | f: &mut fmt::Formatter<'_>, 522 | ) -> fmt::Result { 523 | audio_stream_base_fmt(stream, f)?; 524 | "Audio API: ".fmt(f)?; 525 | fmt::Debug::fmt(&stream.get_audio_api(), f)?; 526 | "\nCurrent state: ".fmt(f)?; 527 | fmt::Debug::fmt(&stream.get_state(), f)?; 528 | "\nXrun count: ".fmt(f)?; 529 | match stream.get_xrun_count() { 530 | Ok(count) => count.fmt(f)?, 531 | Err(error) => fmt::Debug::fmt(&error, f)?, 532 | } 533 | '\n'.fmt(f) 534 | } 535 | 536 | pub(crate) struct AudioStreamHandle(ffi::oboe_AudioStreamShared); 537 | 538 | impl Clone for AudioStreamHandle { 539 | fn clone(&self) -> Self { 540 | // We free to clone shared pointers 541 | let mut new = Self::default(); 542 | 543 | unsafe { ffi::oboe_AudioStreamShared_clone(&self.0, new.as_mut()) }; 544 | 545 | new 546 | } 547 | } 548 | 549 | impl Drop for AudioStreamHandle { 550 | /// SAFETY: `self.0` must be valid pointers. 551 | fn drop(&mut self) { 552 | // The error callback could be holding a shared_ptr, so don't delete AudioStream 553 | // directly, but only its shared_ptr. 554 | unsafe { ffi::oboe_AudioStreamShared_delete(&mut self.0 as *mut _) }; 555 | } 556 | } 557 | 558 | impl Default for AudioStreamHandle { 559 | fn default() -> Self { 560 | Self(unsafe { MaybeUninit::zeroed().assume_init() }) 561 | } 562 | } 563 | 564 | impl AsRef for AudioStreamHandle { 565 | fn as_ref(&self) -> &ffi::oboe_AudioStreamShared { 566 | &self.0 567 | } 568 | } 569 | 570 | impl AsMut for AudioStreamHandle { 571 | fn as_mut(&mut self) -> &mut ffi::oboe_AudioStreamShared { 572 | &mut self.0 573 | } 574 | } 575 | 576 | impl Deref for AudioStreamHandle { 577 | type Target = ffi::oboe_AudioStream; 578 | 579 | fn deref(&self) -> &Self::Target { 580 | unsafe { &*ffi::oboe_AudioStreamShared_deref(&self.0 as *const _ as *mut _) } 581 | } 582 | } 583 | 584 | impl DerefMut for AudioStreamHandle { 585 | fn deref_mut(&mut self) -> &mut Self::Target { 586 | unsafe { &mut *ffi::oboe_AudioStreamShared_deref(&mut self.0) } 587 | } 588 | } 589 | 590 | /** 591 | * Reference to the audio stream for passing to callbacks 592 | */ 593 | #[repr(transparent)] 594 | pub struct AudioStreamRef<'s, D> { 595 | raw: &'s mut ffi::oboe_AudioStream, 596 | _phantom: PhantomData, 597 | } 598 | 599 | impl<'s, D> fmt::Debug for AudioStreamRef<'s, D> { 600 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 601 | audio_stream_fmt(self, f) 602 | } 603 | } 604 | 605 | impl<'s, D> AudioStreamRef<'s, D> { 606 | pub(crate) fn wrap_raw<'a: 's>(raw: &'a mut ffi::oboe_AudioStream) -> Self { 607 | Self { 608 | raw, 609 | _phantom: PhantomData, 610 | } 611 | } 612 | } 613 | 614 | impl<'s, D> RawAudioStreamBase for AudioStreamRef<'s, D> { 615 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase { 616 | unsafe { &*ffi::oboe_AudioStream_getBase(self.raw as *const _ as *mut _) } 617 | } 618 | 619 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase { 620 | unsafe { &mut *ffi::oboe_AudioStream_getBase(self.raw) } 621 | } 622 | } 623 | 624 | impl<'s, D> RawAudioStream for AudioStreamRef<'s, D> { 625 | fn _raw_stream(&self) -> &ffi::oboe_AudioStream { 626 | self.raw 627 | } 628 | 629 | fn _raw_stream_mut(&mut self) -> &mut ffi::oboe_AudioStream { 630 | self.raw 631 | } 632 | } 633 | 634 | impl<'s> RawAudioInputStream for AudioStreamRef<'s, Input> {} 635 | 636 | impl<'s> RawAudioOutputStream for AudioStreamRef<'s, Output> {} 637 | 638 | /** 639 | * The audio stream for asynchronous (callback-driven) mode 640 | */ 641 | pub struct AudioStreamAsync { 642 | raw: AudioStreamHandle, 643 | _phantom: PhantomData<(D, F)>, 644 | } 645 | 646 | impl fmt::Debug for AudioStreamAsync { 647 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 648 | audio_stream_fmt(self, f) 649 | } 650 | } 651 | 652 | impl AudioStreamAsync { 653 | // SAFETY: `raw` must be valid. 654 | pub(crate) fn wrap_handle(raw: AudioStreamHandle) -> Self { 655 | Self { 656 | raw, 657 | _phantom: PhantomData, 658 | } 659 | } 660 | } 661 | 662 | impl Drop for AudioStreamAsync { 663 | fn drop(&mut self) { 664 | // SAFETY: As long as the conditions on Self::wrap_raw are guaranteed on the creation of 665 | // self, this is safe. 666 | let _ = self.close(); 667 | } 668 | } 669 | 670 | impl RawAudioStreamBase for AudioStreamAsync { 671 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase { 672 | unsafe { &*ffi::oboe_AudioStream_getBase(&*self.raw as *const _ as *mut _) } 673 | } 674 | 675 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase { 676 | unsafe { &mut *ffi::oboe_AudioStream_getBase(&mut *self.raw as *mut _) } 677 | } 678 | } 679 | 680 | impl RawAudioStream for AudioStreamAsync { 681 | fn _raw_stream(&self) -> &ffi::oboe_AudioStream { 682 | &self.raw 683 | } 684 | 685 | fn _raw_stream_mut(&mut self) -> &mut ffi::oboe_AudioStream { 686 | &mut self.raw 687 | } 688 | } 689 | 690 | impl RawAudioInputStream for AudioStreamAsync {} 691 | 692 | impl RawAudioOutputStream for AudioStreamAsync {} 693 | 694 | /** 695 | * The audio stream for synchronous (blocking) mode 696 | */ 697 | pub struct AudioStreamSync { 698 | raw: AudioStreamHandle, 699 | _phantom: PhantomData<(D, F)>, 700 | } 701 | 702 | impl fmt::Debug for AudioStreamSync { 703 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 704 | audio_stream_fmt(self, f) 705 | } 706 | } 707 | 708 | impl AudioStreamSync { 709 | // SAFETY: `raw` must be valid. 710 | pub(crate) fn wrap_handle(raw: AudioStreamHandle) -> Self { 711 | Self { 712 | raw, 713 | _phantom: PhantomData, 714 | } 715 | } 716 | } 717 | 718 | impl Drop for AudioStreamSync { 719 | fn drop(&mut self) { 720 | // SAFETY: As long as the conditions on Self::wrap_raw are guaranteed on the creation of 721 | // self, this is safe. 722 | let _ = self.close(); 723 | } 724 | } 725 | 726 | impl RawAudioStreamBase for AudioStreamSync { 727 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase { 728 | unsafe { &*ffi::oboe_AudioStream_getBase(&*self.raw as *const _ as *mut _) } 729 | } 730 | 731 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase { 732 | unsafe { &mut *ffi::oboe_AudioStream_getBase(&mut *self.raw as *mut _) } 733 | } 734 | } 735 | 736 | impl RawAudioStream for AudioStreamSync { 737 | fn _raw_stream(&self) -> &ffi::oboe_AudioStream { 738 | &self.raw 739 | } 740 | 741 | fn _raw_stream_mut(&mut self) -> &mut ffi::oboe_AudioStream { 742 | &mut self.raw 743 | } 744 | } 745 | 746 | impl RawAudioInputStream for AudioStreamSync {} 747 | 748 | impl RawAudioOutputStream for AudioStreamSync {} 749 | 750 | impl AudioInputStreamSync for AudioStreamSync { 751 | type FrameType = F; 752 | 753 | fn read( 754 | &mut self, 755 | buffer: &mut [::Type], 756 | timeout_nanoseconds: i64, 757 | ) -> Result { 758 | wrap_result(unsafe { 759 | ffi::oboe_AudioStream_read( 760 | &mut *self.raw, 761 | buffer.as_mut_ptr() as *mut c_void, 762 | buffer.len() as i32, 763 | timeout_nanoseconds, 764 | ) 765 | }) 766 | } 767 | } 768 | 769 | impl AudioOutputStreamSync for AudioStreamSync { 770 | type FrameType = F; 771 | 772 | fn write( 773 | &mut self, 774 | buffer: &[::Type], 775 | timeout_nanoseconds: i64, 776 | ) -> Result { 777 | wrap_result(unsafe { 778 | ffi::oboe_AudioStream_write( 779 | &mut *self.raw, 780 | buffer.as_ptr() as *const c_void, 781 | buffer.len() as i32, 782 | timeout_nanoseconds, 783 | ) 784 | }) 785 | } 786 | } 787 | -------------------------------------------------------------------------------- /src/audio_stream_base.rs: -------------------------------------------------------------------------------- 1 | //use oboe_sys as ffi; 2 | use num_traits::FromPrimitive; 3 | 4 | use std::fmt::{self, Display}; 5 | 6 | use super::{ 7 | AudioFormat, ChannelCount, ContentType, Direction, InputPreset, PerformanceMode, 8 | RawAudioStreamBase, SampleRateConversionQuality, SessionId, SharingMode, Usage, 9 | }; 10 | 11 | /** 12 | * Base trait containing parameters for audio streams and builders. 13 | */ 14 | pub trait AudioStreamBase { 15 | /** 16 | * Get actual number of channels 17 | */ 18 | fn get_channel_count(&self) -> ChannelCount; 19 | 20 | /** 21 | * Get actual stream direction 22 | * 23 | * `Direction::Input` or `Direction::Output`. 24 | */ 25 | fn get_direction(&self) -> Direction; 26 | 27 | /** 28 | * Get the actual sample rate for the stream 29 | */ 30 | fn get_sample_rate(&self) -> i32; 31 | 32 | /** 33 | * Get the number of frames in each callback 34 | */ 35 | fn get_frames_per_callback(&self) -> i32; 36 | 37 | /** 38 | * Get the audio sample format (e.g. F32 or I16) 39 | */ 40 | fn get_format(&self) -> AudioFormat; 41 | 42 | /** 43 | * Query the maximum number of frames that can be filled without blocking. 44 | * If the stream has been closed the last known value will be returned. 45 | */ 46 | fn get_buffer_size_in_frames(&self) -> i32; 47 | 48 | /** 49 | * Get the capacity in number of frames 50 | */ 51 | fn get_buffer_capacity_in_frames(&self) -> i32; 52 | 53 | /** 54 | * Get the sharing mode of the stream 55 | */ 56 | fn get_sharing_mode(&self) -> SharingMode; 57 | 58 | /** 59 | * Get the performance mode of the stream 60 | */ 61 | fn get_performance_mode(&self) -> PerformanceMode; 62 | 63 | /** 64 | * Get the device identifier of the stream 65 | */ 66 | fn get_device_id(&self) -> i32; 67 | 68 | /** 69 | * Get the usage for this stream 70 | */ 71 | fn get_usage(&self) -> Usage; 72 | 73 | /** 74 | * Get the stream's content type 75 | */ 76 | fn get_content_type(&self) -> ContentType; 77 | 78 | /** 79 | * Get the stream's input preset 80 | */ 81 | fn get_input_preset(&self) -> InputPreset; 82 | 83 | /** 84 | * Get the stream's session ID allocation strategy (None or Allocate) 85 | */ 86 | fn get_session_id(&self) -> SessionId; 87 | 88 | /** 89 | * Return true if can convert channel counts to achieve optimal results. 90 | */ 91 | fn is_channel_conversion_allowed(&self) -> bool; 92 | 93 | /** 94 | * Return true if Oboe can convert data formats to achieve optimal results. 95 | */ 96 | fn is_format_conversion_allowed(&self) -> bool; 97 | 98 | /** 99 | * Get whether and how Oboe can convert sample rates to achieve optimal results. 100 | */ 101 | fn get_sample_rate_conversion_quality(&self) -> SampleRateConversionQuality; 102 | } 103 | 104 | impl AudioStreamBase for T { 105 | fn get_channel_count(&self) -> ChannelCount { 106 | FromPrimitive::from_i32(self._raw_base().mChannelCount).unwrap() 107 | } 108 | 109 | fn get_direction(&self) -> Direction { 110 | FromPrimitive::from_i32(self._raw_base().mDirection).unwrap() 111 | } 112 | 113 | fn get_sample_rate(&self) -> i32 { 114 | self._raw_base().mSampleRate 115 | } 116 | 117 | fn get_frames_per_callback(&self) -> i32 { 118 | self._raw_base().mFramesPerCallback 119 | } 120 | 121 | fn get_format(&self) -> AudioFormat { 122 | FromPrimitive::from_i32(self._raw_base().mFormat).unwrap() 123 | } 124 | 125 | fn get_buffer_size_in_frames(&self) -> i32 { 126 | self._raw_base().mBufferSizeInFrames 127 | } 128 | 129 | fn get_buffer_capacity_in_frames(&self) -> i32 { 130 | self._raw_base().mBufferCapacityInFrames 131 | } 132 | 133 | fn get_sharing_mode(&self) -> SharingMode { 134 | FromPrimitive::from_i32(self._raw_base().mSharingMode).unwrap() 135 | } 136 | 137 | fn get_performance_mode(&self) -> PerformanceMode { 138 | FromPrimitive::from_i32(self._raw_base().mPerformanceMode).unwrap() 139 | } 140 | 141 | fn get_device_id(&self) -> i32 { 142 | self._raw_base().mDeviceId 143 | } 144 | 145 | fn get_usage(&self) -> Usage { 146 | FromPrimitive::from_i32(self._raw_base().mUsage).unwrap() 147 | } 148 | 149 | fn get_content_type(&self) -> ContentType { 150 | FromPrimitive::from_i32(self._raw_base().mContentType).unwrap() 151 | } 152 | 153 | fn get_input_preset(&self) -> InputPreset { 154 | FromPrimitive::from_i32(self._raw_base().mInputPreset).unwrap() 155 | } 156 | 157 | fn get_session_id(&self) -> SessionId { 158 | FromPrimitive::from_i32(self._raw_base().mSessionId).unwrap() 159 | } 160 | 161 | fn is_channel_conversion_allowed(&self) -> bool { 162 | self._raw_base().mChannelConversionAllowed 163 | } 164 | 165 | fn is_format_conversion_allowed(&self) -> bool { 166 | self._raw_base().mFormatConversionAllowed 167 | } 168 | 169 | fn get_sample_rate_conversion_quality(&self) -> SampleRateConversionQuality { 170 | FromPrimitive::from_i32(self._raw_base().mSampleRateConversionQuality).unwrap() 171 | } 172 | } 173 | 174 | pub(crate) fn audio_stream_base_fmt( 175 | base: &T, 176 | f: &mut fmt::Formatter<'_>, 177 | ) -> fmt::Result { 178 | "DeviceId: ".fmt(f)?; 179 | base.get_device_id().fmt(f)?; 180 | "\nSessionId: ".fmt(f)?; 181 | fmt::Debug::fmt(&base.get_session_id(), f)?; 182 | "\nDirection: ".fmt(f)?; 183 | fmt::Debug::fmt(&base.get_direction(), f)?; 184 | if base.get_direction() == Direction::Input { 185 | "\nInput preset: ".fmt(f)?; 186 | fmt::Debug::fmt(&base.get_input_preset(), f)?; 187 | } 188 | "\nBuffer capacity in frames: ".fmt(f)?; 189 | base.get_buffer_capacity_in_frames().fmt(f)?; 190 | "\nBuffer size in frames: ".fmt(f)?; 191 | base.get_buffer_size_in_frames().fmt(f)?; 192 | "\nFrames per callback: ".fmt(f)?; 193 | base.get_frames_per_callback().fmt(f)?; 194 | "\nSample rate: ".fmt(f)?; 195 | base.get_sample_rate().fmt(f)?; 196 | "\nSample rate conversion quality: ".fmt(f)?; 197 | fmt::Debug::fmt(&base.get_sample_rate_conversion_quality(), f)?; 198 | "\nChannel count: ".fmt(f)?; 199 | fmt::Debug::fmt(&base.get_channel_count(), f)?; 200 | if base.is_channel_conversion_allowed() { 201 | " (conversion allowed)".fmt(f)?; 202 | } 203 | "\nFormat: ".fmt(f)?; 204 | fmt::Debug::fmt(&base.get_format(), f)?; 205 | if base.is_format_conversion_allowed() { 206 | " (conversion allowed)".fmt(f)?; 207 | } 208 | "\nSharing mode: ".fmt(f)?; 209 | fmt::Debug::fmt(&base.get_sharing_mode(), f)?; 210 | "\nPerformance mode: ".fmt(f)?; 211 | fmt::Debug::fmt(&base.get_performance_mode(), f)?; 212 | "\nUsage: ".fmt(f)?; 213 | fmt::Debug::fmt(&base.get_usage(), f)?; 214 | "\nContent type: ".fmt(f)?; 215 | fmt::Debug::fmt(&base.get_content_type(), f)?; 216 | '\n'.fmt(f) 217 | } 218 | -------------------------------------------------------------------------------- /src/audio_stream_builder.rs: -------------------------------------------------------------------------------- 1 | use num_traits::FromPrimitive; 2 | use oboe_sys as ffi; 3 | use std::{ 4 | fmt, 5 | marker::PhantomData, 6 | mem::{ManuallyDrop, MaybeUninit}, 7 | ops::{Deref, DerefMut}, 8 | }; 9 | 10 | use crate::{set_input_callback, set_output_callback}; 11 | 12 | use super::{ 13 | audio_stream_base_fmt, wrap_status, AudioApi, AudioInputCallback, AudioOutputCallback, 14 | AudioStreamAsync, AudioStreamHandle, AudioStreamSync, ContentType, Input, InputPreset, 15 | IsChannelCount, IsDirection, IsFormat, IsFrameType, Mono, Output, PerformanceMode, 16 | RawAudioStreamBase, Result, SampleRateConversionQuality, SessionId, SharingMode, Stereo, 17 | Unspecified, Usage, 18 | }; 19 | 20 | #[repr(transparent)] 21 | pub(crate) struct AudioStreamBuilderHandle(ffi::oboe_AudioStreamBuilder); 22 | 23 | impl AudioStreamBuilderHandle { 24 | pub(crate) fn open_stream(&mut self) -> Result { 25 | let mut stream = AudioStreamHandle::default(); 26 | 27 | wrap_status(unsafe { 28 | ffi::oboe_AudioStreamBuilder_openStreamShared(&mut **self, stream.as_mut()) 29 | }) 30 | .map(|_| stream) 31 | } 32 | } 33 | 34 | impl Default for AudioStreamBuilderHandle { 35 | fn default() -> Self { 36 | let mut raw = MaybeUninit::zeroed(); 37 | 38 | Self(unsafe { 39 | ffi::oboe_AudioStreamBuilder_create(raw.as_mut_ptr()); 40 | raw.assume_init() 41 | }) 42 | } 43 | } 44 | 45 | impl Drop for AudioStreamBuilderHandle { 46 | fn drop(&mut self) { 47 | unsafe { ffi::oboe_AudioStreamBuilder_delete(&mut **self) } 48 | } 49 | } 50 | 51 | impl Deref for AudioStreamBuilderHandle { 52 | type Target = ffi::oboe_AudioStreamBuilder; 53 | 54 | fn deref(&self) -> &Self::Target { 55 | &self.0 56 | } 57 | } 58 | 59 | impl DerefMut for AudioStreamBuilderHandle { 60 | fn deref_mut(&mut self) -> &mut Self::Target { 61 | &mut self.0 62 | } 63 | } 64 | 65 | /** 66 | * Factory for an audio stream. 67 | */ 68 | #[repr(transparent)] 69 | pub struct AudioStreamBuilder { 70 | raw: ManuallyDrop, 71 | _phantom: PhantomData<(D, C, T)>, 72 | } 73 | 74 | impl Drop for AudioStreamBuilder { 75 | fn drop(&mut self) { 76 | // SAFETY: self.raw is only drop here, or taken in Self::destructs, which don't drop self. 77 | unsafe { 78 | ManuallyDrop::drop(&mut self.raw); 79 | } 80 | } 81 | } 82 | 83 | impl fmt::Debug for AudioStreamBuilder { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | audio_stream_base_fmt(self, f) 86 | } 87 | } 88 | 89 | impl RawAudioStreamBase for AudioStreamBuilder { 90 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase { 91 | unsafe { &*ffi::oboe_AudioStreamBuilder_getBase(&**self.raw as *const _ as *mut _) } 92 | } 93 | 94 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase { 95 | unsafe { &mut *ffi::oboe_AudioStreamBuilder_getBase(&mut **self.raw) } 96 | } 97 | } 98 | 99 | impl Default for AudioStreamBuilder { 100 | /** 101 | * Create new audio stream builder 102 | */ 103 | fn default() -> Self { 104 | Self { 105 | raw: Default::default(), 106 | _phantom: PhantomData, 107 | } 108 | } 109 | } 110 | 111 | impl AudioStreamBuilder { 112 | fn convert(self) -> AudioStreamBuilder { 113 | AudioStreamBuilder { 114 | raw: ManuallyDrop::new(self.destructs()), 115 | _phantom: PhantomData, 116 | } 117 | } 118 | 119 | /** 120 | * Request a specific number of channels 121 | * 122 | * Default is `Unspecified`. If the value is unspecified then 123 | * the application should query for the actual value after the stream is opened. 124 | */ 125 | pub fn set_channel_count(self) -> AudioStreamBuilder { 126 | let mut builder = self.convert(); 127 | builder._raw_base_mut().mChannelCount = X::CHANNEL_COUNT as i32; 128 | builder 129 | } 130 | 131 | /** 132 | * Request mono mode for a stream 133 | */ 134 | pub fn set_mono(self) -> AudioStreamBuilder { 135 | self.set_channel_count::() 136 | } 137 | 138 | /** 139 | * Request stereo mode for a stream 140 | */ 141 | pub fn set_stereo(self) -> AudioStreamBuilder { 142 | self.set_channel_count::() 143 | } 144 | 145 | /** 146 | * Request the direction for a stream 147 | * 148 | * The default is `Direction::Output` 149 | */ 150 | pub fn set_direction(self) -> AudioStreamBuilder { 151 | let mut builder = self.convert(); 152 | builder._raw_base_mut().mDirection = X::DIRECTION as i32; 153 | builder 154 | } 155 | 156 | /** 157 | * Request input direction for a stream 158 | */ 159 | pub fn set_input(self) -> AudioStreamBuilder { 160 | self.set_direction::() 161 | } 162 | 163 | /** 164 | * Request output direction for a stream 165 | * 166 | * It is optional because th stream builder already configured as output by default. 167 | */ 168 | pub fn set_output(self) -> AudioStreamBuilder { 169 | self.set_direction::() 170 | } 171 | 172 | /** 173 | * Request a specific sample rate in Hz. 174 | * 175 | * Default is kUnspecified. If the value is unspecified then 176 | * the application should query for the actual value after the stream is opened. 177 | * 178 | * Technically, this should be called the _frame rate_ or _frames per second_, 179 | * because it refers to the number of complete frames transferred per second. 180 | * But it is traditionally called _sample rate_. Se we use that term. 181 | */ 182 | pub fn set_sample_rate(mut self, sample_rate: i32) -> Self { 183 | self._raw_base_mut().mSampleRate = sample_rate; 184 | self 185 | } 186 | 187 | /** 188 | * Request a specific number of frames for the data callback. 189 | * 190 | * Default is kUnspecified. If the value is unspecified then 191 | * the actual number may vary from callback to callback. 192 | * 193 | * If an application can handle a varying number of frames then we recommend 194 | * leaving this unspecified. This allow the underlying API to optimize 195 | * the callbacks. But if your application is, for example, doing FFTs or other block 196 | * oriented operations, then call this function to get the sizes you need. 197 | */ 198 | pub fn set_frames_per_callback(mut self, frames_per_callback: i32) -> Self { 199 | self._raw_base_mut().mFramesPerCallback = frames_per_callback; 200 | self 201 | } 202 | 203 | /** 204 | * Request a sample data format, for example `f32`. 205 | * 206 | * Default is unspecified. If the value is unspecified then 207 | * the application should query for the actual value after the stream is opened. 208 | */ 209 | pub fn set_format(self) -> AudioStreamBuilder { 210 | let mut builder = self.convert(); 211 | builder._raw_base_mut().mFormat = X::FORMAT as i32; 212 | builder 213 | } 214 | 215 | pub fn set_i16(self) -> AudioStreamBuilder { 216 | self.set_format::() 217 | } 218 | 219 | pub fn set_f32(self) -> AudioStreamBuilder { 220 | self.set_format::() 221 | } 222 | 223 | /** 224 | * Set the requested buffer capacity in frames. 225 | * Buffer capacity in frames is the maximum possible buffer size in frames. 226 | * 227 | * The final stream capacity may differ. For __AAudio__ it should be at least this big. 228 | * For __OpenSL ES__, it could be smaller. 229 | * 230 | * Default is unspecified. 231 | */ 232 | pub fn set_buffer_capacity_in_frames(mut self, buffer_capacity_in_frames: i32) -> Self { 233 | self._raw_base_mut().mBufferCapacityInFrames = buffer_capacity_in_frames; 234 | self 235 | } 236 | 237 | /** 238 | * Get the audio API which will be requested when opening the stream. No guarantees that this is 239 | * the API which will actually be used. Query the stream itself to find out the API which is 240 | * being used. 241 | * 242 | * If you do not specify the API, then __AAudio__ will be used if isAAudioRecommended() 243 | * returns true. Otherwise __OpenSL ES__ will be used. 244 | */ 245 | pub fn get_audio_api(&self) -> AudioApi { 246 | FromPrimitive::from_i32(unsafe { ffi::oboe_AudioStreamBuilder_getAudioApi(&**self.raw) }) 247 | .unwrap() 248 | } 249 | 250 | /** 251 | * If you leave this unspecified then Oboe will choose the best API 252 | * for the device and SDK version at runtime. 253 | * 254 | * This should almost always be left unspecified, except for debugging purposes. 255 | * Specifying __AAudio__ will force Oboe to use AAudio on 8.0, which is extremely risky. 256 | * Specifying __OpenSL ES__ should mainly be used to test legacy performance/functionality. 257 | * 258 | * If the caller requests AAudio and it is supported then AAudio will be used. 259 | */ 260 | pub fn set_audio_api(mut self, audio_api: AudioApi) -> Self { 261 | unsafe { ffi::oboe_AudioStreamBuilder_setAudioApi(&mut **self.raw, audio_api as i32) } 262 | self 263 | } 264 | 265 | /** 266 | * Is the AAudio API supported on this device? 267 | * 268 | * AAudio was introduced in the Oreo 8.0 release. 269 | */ 270 | pub fn is_aaudio_supported() -> bool { 271 | unsafe { ffi::oboe_AudioStreamBuilder_isAAudioSupported() } 272 | } 273 | 274 | /** 275 | * Is the AAudio API recommended this device? 276 | * 277 | * AAudio may be supported but not recommended because of version specific issues. 278 | * AAudio is not recommended for Android 8.0 or earlier versions. 279 | */ 280 | pub fn is_aaudio_recommended() -> bool { 281 | unsafe { ffi::oboe_AudioStreamBuilder_isAAudioRecommended() } 282 | } 283 | 284 | /** 285 | * Request a mode for sharing the device. 286 | * The requested sharing mode may not be available. 287 | * So the application should query for the actual mode after the stream is opened. 288 | */ 289 | pub fn set_sharing_mode(mut self, sharing_mode: SharingMode) -> Self { 290 | self._raw_base_mut().mSharingMode = sharing_mode as i32; 291 | self 292 | } 293 | 294 | /** 295 | * Request a shared mode for the device 296 | */ 297 | pub fn set_shared(self) -> Self { 298 | self.set_sharing_mode(SharingMode::Shared) 299 | } 300 | 301 | /** 302 | * Request an exclusive mode for the device 303 | */ 304 | pub fn set_exclusive(self) -> Self { 305 | self.set_sharing_mode(SharingMode::Exclusive) 306 | } 307 | 308 | /** 309 | * Request a performance level for the stream. 310 | * This will determine the latency, the power consumption, and the level of 311 | * protection from glitches. 312 | */ 313 | pub fn set_performance_mode(mut self, performance_mode: PerformanceMode) -> Self { 314 | self._raw_base_mut().mPerformanceMode = performance_mode as i32; 315 | self 316 | } 317 | 318 | /** 319 | * Set the intended use case for the stream. 320 | * 321 | * The system will use this information to optimize the behavior of the stream. 322 | * This could, for example, affect how volume and focus is handled for the stream. 323 | * 324 | * The default, if you do not call this function, is Usage::Media. 325 | * 326 | * Added in API level 28. 327 | */ 328 | pub fn set_usage(mut self, usage: Usage) -> Self { 329 | self._raw_base_mut().mUsage = usage as i32; 330 | self 331 | } 332 | 333 | /** 334 | * Set the type of audio data that the stream will carry. 335 | * 336 | * The system will use this information to optimize the behavior of the stream. 337 | * This could, for example, affect whether a stream is paused when a notification occurs. 338 | * 339 | * The default, if you do not call this function, is `ContentType::Music`. 340 | * 341 | * Added in API level 28. 342 | */ 343 | pub fn set_content_type(mut self, content_type: ContentType) -> Self { 344 | self._raw_base_mut().mContentType = content_type as i32; 345 | self 346 | } 347 | 348 | /** 349 | * Set the input (capture) preset for the stream. 350 | * 351 | * The system will use this information to optimize the behavior of the stream. 352 | * This could, for example, affect which microphones are used and how the 353 | * recorded data is processed. 354 | * 355 | * The default, if you do not call this function, is InputPreset::VoiceRecognition. 356 | * That is because VoiceRecognition is the preset with the lowest latency 357 | * on many platforms. 358 | * 359 | * Added in API level 28. 360 | */ 361 | pub fn set_input_preset(mut self, input_preset: InputPreset) -> Self { 362 | self._raw_base_mut().mInputPreset = input_preset as i32; 363 | self 364 | } 365 | 366 | /** 367 | * Set the requested session ID. 368 | * 369 | * The session ID can be used to associate a stream with effects processors. 370 | * The effects are controlled using the Android AudioEffect Java API. 371 | * 372 | * The default, if you do not call this function, is `SessionId::None`. 373 | * 374 | * If set to `SessionId::Allocate` then a session ID will be allocated 375 | * when the stream is opened. 376 | * 377 | * The allocated session ID can be obtained by calling AudioStream::getSessionId() 378 | * and then used with this function when opening another stream. 379 | * This allows effects to be shared between streams. 380 | * 381 | * Session IDs from Oboe can be used the Android Java APIs and vice versa. 382 | * So a session ID from an Oboe stream can be passed to Java 383 | * and effects applied using the Java AudioEffect API. 384 | * 385 | * Allocated session IDs will always be positive and nonzero. 386 | * 387 | * Added in API level 28. 388 | */ 389 | pub fn set_session_id(mut self, session_id: SessionId) -> Self { 390 | self._raw_base_mut().mSessionId = session_id as i32; 391 | self 392 | } 393 | 394 | /** 395 | * Request a stream to a specific audio input/output device given an audio device ID. 396 | * 397 | * In most cases, the primary device will be the appropriate device to use, and the 398 | * device ID can be left unspecified. 399 | * 400 | * On Android, for example, the ID could be obtained from the Java AudioManager. 401 | * AudioManager.getDevices() returns an array of AudioDeviceInfo[], which contains 402 | * a getId() method (as well as other type information), that should be passed 403 | * to this method. 404 | * 405 | * When `java-interface` feature is used you can call [`AudioDeviceInfo::request`](crate::AudioDeviceInfo::request) for listing devices info. 406 | * 407 | * Note that when using OpenSL ES, this will be ignored and the created 408 | * stream will have device ID unspecified. 409 | */ 410 | pub fn set_device_id(mut self, device_id: i32) -> Self { 411 | self._raw_base_mut().mDeviceId = device_id; 412 | self 413 | } 414 | 415 | /** 416 | * If true then Oboe might convert channel counts to achieve optimal results. 417 | * On some versions of Android for example, stereo streams could not use a FAST track. 418 | * So a mono stream might be used instead and duplicated to two channels. 419 | * On some devices, mono streams might be broken, so a stereo stream might be opened 420 | * and converted to mono. 421 | * 422 | * Default is true. 423 | */ 424 | pub fn set_channel_conversion_allowed(mut self, allowed: bool) -> Self { 425 | self._raw_base_mut().mChannelConversionAllowed = allowed; 426 | self 427 | } 428 | 429 | /** 430 | * If true then Oboe might convert data formats to achieve optimal results. 431 | * On some versions of Android, for example, a float stream could not get a 432 | * low latency data path. So an I16 stream might be opened and converted to float. 433 | * 434 | * Default is true. 435 | */ 436 | pub fn set_format_conversion_allowed(mut self, allowed: bool) -> Self { 437 | self._raw_base_mut().mFormatConversionAllowed = allowed; 438 | self 439 | } 440 | 441 | /** 442 | * Specify the quality of the sample rate converter in Oboe. 443 | * 444 | * If set to None then Oboe will not do sample rate conversion. But the underlying APIs might 445 | * still do sample rate conversion if you specify a sample rate. 446 | * That can prevent you from getting a low latency stream. 447 | * 448 | * If you do the conversion in Oboe then you might still get a low latency stream. 449 | * 450 | * Default is `SampleRateConversionQuality::None` 451 | */ 452 | pub fn set_sample_rate_conversion_quality( 453 | mut self, 454 | quality: SampleRateConversionQuality, 455 | ) -> Self { 456 | self._raw_base_mut().mSampleRateConversionQuality = quality as i32; 457 | self 458 | } 459 | 460 | /** 461 | * Returns true if AAudio will be used based on the current settings. 462 | */ 463 | pub fn will_use_aaudio(&self) -> bool { 464 | let audio_api = self.get_audio_api(); 465 | (audio_api == AudioApi::AAudio && Self::is_aaudio_supported()) 466 | || (audio_api == AudioApi::Unspecified && Self::is_aaudio_recommended()) 467 | } 468 | 469 | /// Descontructs self into its handle, without calling drop. 470 | fn destructs(mut self) -> AudioStreamBuilderHandle { 471 | // Safety: the std::mem::forget prevents `raw` from being dropped by Self::drop. 472 | let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; 473 | 474 | std::mem::forget(self); 475 | 476 | raw 477 | } 478 | } 479 | 480 | impl AudioStreamBuilder { 481 | /** 482 | * Create and open a synchronous (blocking) stream based on the current settings. 483 | */ 484 | pub fn open_stream(self) -> Result> { 485 | let mut raw = self.destructs(); 486 | 487 | let stream = raw.open_stream().map(AudioStreamSync::wrap_handle); 488 | 489 | drop(raw); 490 | 491 | stream 492 | } 493 | } 494 | 495 | impl AudioStreamBuilder { 496 | /** 497 | * Specifies an object to handle data or error related callbacks from the underlying API. 498 | * 499 | * __Important: See AudioStreamCallback for restrictions on what may be called 500 | * from the callback methods.__ 501 | * 502 | * When an error callback occurs, the associated stream will be stopped and closed in a separate thread. 503 | * 504 | * A note on why the streamCallback parameter is a raw pointer rather than a smart pointer: 505 | * 506 | * The caller should retain ownership of the object streamCallback points to. At first glance weak_ptr may seem like 507 | * a good candidate for streamCallback as this implies temporary ownership. However, a weak_ptr can only be created 508 | * from a shared_ptr. A shared_ptr incurs some performance overhead. The callback object is likely to be accessed 509 | * every few milliseconds when the stream requires new data so this overhead is something we want to avoid. 510 | * 511 | * This leaves a raw pointer as the logical type choice. The only caveat being that the caller must not destroy 512 | * the callback before the stream has been closed. 513 | */ 514 | pub fn set_callback(self, stream_callback: F) -> AudioStreamBuilderAsync 515 | where 516 | F: AudioInputCallback, 517 | (T, C): IsFrameType, 518 | { 519 | let mut raw = self.destructs(); 520 | set_input_callback(&mut raw, stream_callback); 521 | AudioStreamBuilderAsync { 522 | raw: ManuallyDrop::new(raw), 523 | _phantom: PhantomData, 524 | } 525 | } 526 | } 527 | 528 | impl AudioStreamBuilder { 529 | /** 530 | * Specifies an object to handle data or error related callbacks from the underlying API. 531 | * 532 | * __Important: See AudioStreamCallback for restrictions on what may be called 533 | * from the callback methods.__ 534 | * 535 | * When an error callback occurs, the associated stream will be stopped and closed in a separate thread. 536 | * 537 | * A note on why the streamCallback parameter is a raw pointer rather than a smart pointer: 538 | * 539 | * The caller should retain ownership of the object streamCallback points to. At first glance weak_ptr may seem like 540 | * a good candidate for streamCallback as this implies temporary ownership. However, a weak_ptr can only be created 541 | * from a shared_ptr. A shared_ptr incurs some performance overhead. The callback object is likely to be accessed 542 | * every few milliseconds when the stream requires new data so this overhead is something we want to avoid. 543 | * 544 | * This leaves a raw pointer as the logical type choice. The only caveat being that the caller must not destroy 545 | * the callback before the stream has been closed. 546 | */ 547 | pub fn set_callback(self, stream_callback: F) -> AudioStreamBuilderAsync 548 | where 549 | F: AudioOutputCallback, 550 | (T, C): IsFrameType, 551 | { 552 | let mut raw = self.destructs(); 553 | set_output_callback(&mut raw, stream_callback); 554 | AudioStreamBuilderAsync { 555 | raw: ManuallyDrop::new(raw), 556 | _phantom: PhantomData, 557 | } 558 | } 559 | } 560 | 561 | /** 562 | * Factory for an audio stream. 563 | */ 564 | pub struct AudioStreamBuilderAsync { 565 | raw: ManuallyDrop, 566 | _phantom: PhantomData<(D, F)>, 567 | } 568 | 569 | impl Drop for AudioStreamBuilderAsync { 570 | fn drop(&mut self) { 571 | // SAFETY: self.raw is only droped here, or taken in Self::destructs, which don't drop self. 572 | unsafe { 573 | ManuallyDrop::drop(&mut self.raw); 574 | } 575 | } 576 | } 577 | 578 | impl fmt::Debug for AudioStreamBuilderAsync { 579 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 580 | audio_stream_base_fmt(self, f) 581 | } 582 | } 583 | 584 | impl RawAudioStreamBase for AudioStreamBuilderAsync { 585 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase { 586 | unsafe { &*ffi::oboe_AudioStreamBuilder_getBase(&**self.raw as *const _ as *mut _) } 587 | } 588 | 589 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase { 590 | unsafe { &mut *ffi::oboe_AudioStreamBuilder_getBase(&mut **self.raw) } 591 | } 592 | } 593 | 594 | impl AudioStreamBuilderAsync { 595 | /// Descontructs self into its handle without calling drop. 596 | fn destructs(mut self) -> AudioStreamBuilderHandle { 597 | // Safety: the std::mem::forget prevents `raw` from being dropped by Self::drop. 598 | let raw = unsafe { ManuallyDrop::take(&mut self.raw) }; 599 | 600 | std::mem::forget(self); 601 | 602 | raw 603 | } 604 | } 605 | 606 | impl AudioStreamBuilderAsync { 607 | /** 608 | * Create and open an asynchronous (callback-driven) input stream based on the current settings. 609 | */ 610 | pub fn open_stream(self) -> Result> { 611 | let mut raw = self.destructs(); 612 | 613 | let stream = raw.open_stream().map(AudioStreamAsync::wrap_handle); 614 | 615 | drop(raw); 616 | 617 | stream 618 | } 619 | } 620 | 621 | impl AudioStreamBuilderAsync { 622 | /** 623 | * Create and open an asynchronous (callback-driven) output stream based on the current settings. 624 | */ 625 | pub fn open_stream(self) -> Result> { 626 | let mut raw = self.destructs(); 627 | 628 | let stream = raw.open_stream().map(AudioStreamAsync::wrap_handle); 629 | 630 | drop(raw); 631 | 632 | stream 633 | } 634 | } 635 | -------------------------------------------------------------------------------- /src/audio_stream_callback.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::c_void, 3 | slice::{from_raw_parts, from_raw_parts_mut}, 4 | }; 5 | 6 | use oboe_sys as ffi; 7 | 8 | use num_traits::FromPrimitive; 9 | 10 | use super::{ 11 | AudioInputStreamSafe, AudioOutputStreamSafe, AudioStreamBuilderHandle, AudioStreamRef, 12 | DataCallbackResult, Error, IsFrameType, 13 | }; 14 | 15 | /** 16 | * This trait defines a callback interface for: 17 | * 18 | * 1) moving data to/from an audio stream using `on_audio_ready` 19 | * 2) being alerted when a stream has an error using `on_error_*` methods 20 | */ 21 | pub trait AudioInputCallback { 22 | /** 23 | * The sample type and number of channels for processing. 24 | * 25 | * Oboe supports only two sample types: 26 | * 27 | * - **i16** - signed 16-bit integer samples 28 | * - **f32** - 32-bit floating point samples 29 | * 30 | * Oboe supports only mono and stereo channel configurations. 31 | */ 32 | type FrameType: IsFrameType; 33 | 34 | /** 35 | * This will be called when an error occurs on a stream or when the stream is disconnected. 36 | * 37 | * Note that this will be called on a different thread than the onAudioReady() thread. 38 | * This thread will be created by Oboe. 39 | * 40 | * The underlying stream will already be stopped by Oboe but not yet closed. 41 | * So the stream can be queried. 42 | * 43 | * Do not close or delete the stream in this method because it will be 44 | * closed after this method returns. 45 | */ 46 | fn on_error_before_close( 47 | &mut self, 48 | _audio_stream: &mut dyn AudioInputStreamSafe, 49 | _error: Error, 50 | ) { 51 | } 52 | 53 | /** 54 | * This will be called when an error occurs on a stream or when the stream is disconnected. 55 | * The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe. 56 | * So the underlying stream cannot be referenced. 57 | * But you can still query most parameters. 58 | * 59 | * This callback could be used to reopen a new stream on another device. 60 | * You can safely delete the old AudioStream in this method. 61 | */ 62 | fn on_error_after_close( 63 | &mut self, 64 | _audio_stream: &mut dyn AudioInputStreamSafe, 65 | _error: Error, 66 | ) { 67 | } 68 | 69 | /** 70 | * A buffer is ready for processing. 71 | * 72 | * For an output stream, this function should render and write `num_frames` of data 73 | * in the stream's current data format to the audioData buffer. 74 | * 75 | * For an input stream, this function should read and process `num_frames` of data 76 | * from the audioData buffer. 77 | * 78 | * The audio data is passed through the buffer. So do NOT call read() or 79 | * write() on the stream that is making the callback. 80 | * 81 | * Note that numFrames can vary unless AudioStreamBuilder::setFramesPerCallback() 82 | * is called. 83 | * 84 | * Also note that this callback function should be considered a "real-time" function. 85 | * It must not do anything that could cause an unbounded delay because that can cause the 86 | * audio to glitch or pop. 87 | * 88 | * These are things the function should NOT do: 89 | * 90 | * - allocate memory 91 | * - any file operations such as opening, closing, reading or writing 92 | * - any network operations such as streaming 93 | * - use any mutexes or other blocking synchronization primitives 94 | * - sleep 95 | * - stop or close stream 96 | * - read or write on stream which invoked it 97 | * 98 | * The following are OK to call from the data callback: 99 | * 100 | * - stream.get_*() 101 | * 102 | * If you need to move data, eg. MIDI commands, in or out of the callback function then 103 | * we recommend the use of non-blocking techniques such as an atomic FIFO. 104 | */ 105 | fn on_audio_ready( 106 | &mut self, 107 | audio_stream: &mut dyn AudioInputStreamSafe, 108 | audio_data: &[::Type], 109 | ) -> DataCallbackResult; 110 | } 111 | 112 | /** 113 | * This trait defines a callback interface for: 114 | * 115 | * 1) moving data to/from an audio stream using `on_audio_ready` 116 | * 2) being alerted when a stream has an error using `on_error_*` methods 117 | */ 118 | pub trait AudioOutputCallback { 119 | /** 120 | * The sample type and number of channels for processing. 121 | * 122 | * Oboe supports only two sample types: 123 | * 124 | * - **i16** - signed 16-bit integer samples 125 | * - **f32** - 32-bit floating point samples 126 | * 127 | * Oboe supports only mono and stereo channel configurations. 128 | */ 129 | type FrameType: IsFrameType; 130 | 131 | /** 132 | * This will be called when an error occurs on a stream or when the stream is disconnected. 133 | * 134 | * Note that this will be called on a different thread than the onAudioReady() thread. 135 | * This thread will be created by Oboe. 136 | * 137 | * The underlying stream will already be stopped by Oboe but not yet closed. 138 | * So the stream can be queried. 139 | * 140 | * Do not close or delete the stream in this method because it will be 141 | * closed after this method returns. 142 | */ 143 | fn on_error_before_close( 144 | &mut self, 145 | _audio_stream: &mut dyn AudioOutputStreamSafe, 146 | _error: Error, 147 | ) { 148 | } 149 | 150 | /** 151 | * This will be called when an error occurs on a stream or when the stream is disconnected. 152 | * The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe. 153 | * So the underlying stream cannot be referenced. 154 | * But you can still query most parameters. 155 | * 156 | * This callback could be used to reopen a new stream on another device. 157 | * You can safely delete the old AudioStream in this method. 158 | */ 159 | fn on_error_after_close( 160 | &mut self, 161 | _audio_stream: &mut dyn AudioOutputStreamSafe, 162 | _error: Error, 163 | ) { 164 | } 165 | 166 | /** 167 | * A buffer is ready for processing. 168 | * 169 | * For an output stream, this function should render and write numFrames of data 170 | * in the stream's current data format to the audioData buffer. 171 | * 172 | * For an input stream, this function should read and process numFrames of data 173 | * from the audioData buffer. 174 | * 175 | * The audio data is passed through the buffer. So do NOT call read() or 176 | * write() on the stream that is making the callback. 177 | * 178 | * Note that numFrames can vary unless AudioStreamBuilder::set_frames_per_callback() 179 | * is called. 180 | * 181 | * Also note that this callback function should be considered a "real-time" function. 182 | * It must not do anything that could cause an unbounded delay because that can cause the 183 | * audio to glitch or pop. 184 | * 185 | * These are things the function should NOT do: 186 | * 187 | * - allocate memory 188 | * - any file operations such as opening, closing, reading or writing 189 | * - any network operations such as streaming 190 | * - use any mutexes or other blocking synchronization primitives 191 | * - sleep 192 | * - stop or close stream 193 | * - read or write on stream which invoked it 194 | * 195 | * The following are OK to call from the data callback: 196 | * 197 | * - stream.get_*() 198 | * 199 | * If you need to move data, eg. MIDI commands, in or out of the callback function then 200 | * we recommend the use of non-blocking techniques such as an atomic FIFO. 201 | */ 202 | fn on_audio_ready( 203 | &mut self, 204 | audio_stream: &mut dyn AudioOutputStreamSafe, 205 | audio_data: &mut [::Type], 206 | ) -> DataCallbackResult; 207 | } 208 | 209 | pub(crate) fn set_input_callback( 210 | builder: &mut AudioStreamBuilderHandle, 211 | callback: T, 212 | ) { 213 | let callback = Box::into_raw(Box::new(callback)); 214 | 215 | // SAFETY: `callback` has the same type as the first argument of each function, and each 216 | // function follows the C ABI. 217 | unsafe { 218 | ffi::oboe_AudioStreamBuilder_setCallback( 219 | &mut **builder as *mut ffi::oboe_AudioStreamBuilder, 220 | callback.cast(), 221 | Some(drop_context::), 222 | Some(on_audio_ready_input_wrapper::), 223 | Some(on_error_before_close_input_wrapper::), 224 | Some(on_error_after_close_input_wrapper::), 225 | ); 226 | } 227 | } 228 | 229 | pub(crate) fn set_output_callback( 230 | builder: &mut AudioStreamBuilderHandle, 231 | callback: T, 232 | ) { 233 | let callback = Box::new(callback); 234 | let callback = Box::into_raw(callback); 235 | 236 | // SAFETY: `callback` has the same type as the first argument of each function, and each 237 | // function follows the C ABI. 238 | unsafe { 239 | ffi::oboe_AudioStreamBuilder_setCallback( 240 | &mut **builder as *mut ffi::oboe_AudioStreamBuilder, 241 | callback.cast(), 242 | Some(drop_context::), 243 | Some(on_audio_ready_output_wrapper::), 244 | Some(on_error_before_close_output_wrapper::), 245 | Some(on_error_after_close_output_wrapper::), 246 | ); 247 | } 248 | } 249 | 250 | unsafe extern "C" fn drop_context(context: *mut c_void) { 251 | let context = Box::from_raw(context as *mut T); 252 | drop(context); 253 | } 254 | 255 | unsafe extern "C" fn on_error_before_close_input_wrapper( 256 | context: *mut c_void, 257 | audio_stream: *mut ffi::oboe_AudioStream, 258 | error: ffi::oboe_Result, 259 | ) { 260 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 261 | let callback = &mut *(context as *mut T); 262 | 263 | callback.on_error_before_close(&mut audio_stream, FromPrimitive::from_i32(error).unwrap()); 264 | } 265 | 266 | unsafe extern "C" fn on_error_after_close_input_wrapper( 267 | context: *mut c_void, 268 | audio_stream: *mut ffi::oboe_AudioStream, 269 | error: ffi::oboe_Result, 270 | ) { 271 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 272 | let callback = &mut *(context as *mut T); 273 | 274 | callback.on_error_after_close(&mut audio_stream, FromPrimitive::from_i32(error).unwrap()); 275 | } 276 | 277 | unsafe extern "C" fn on_audio_ready_input_wrapper( 278 | context: *mut c_void, 279 | audio_stream: *mut ffi::oboe_AudioStream, 280 | audio_data: *mut c_void, 281 | num_frames: i32, 282 | ) -> ffi::oboe_DataCallbackResult { 283 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 284 | 285 | let audio_data = from_raw_parts( 286 | audio_data as *const ::Type, 287 | num_frames as usize, 288 | ); 289 | 290 | let callback = &mut *(context as *mut T); 291 | 292 | callback.on_audio_ready(&mut audio_stream, audio_data) as i32 293 | } 294 | 295 | unsafe extern "C" fn on_error_before_close_output_wrapper( 296 | context: *mut c_void, 297 | audio_stream: *mut ffi::oboe_AudioStream, 298 | error: ffi::oboe_Result, 299 | ) { 300 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 301 | 302 | let callback = &mut *(context as *mut T); 303 | 304 | callback.on_error_before_close(&mut audio_stream, FromPrimitive::from_i32(error).unwrap()); 305 | } 306 | 307 | unsafe extern "C" fn on_error_after_close_output_wrapper( 308 | context: *mut c_void, 309 | audio_stream: *mut ffi::oboe_AudioStream, 310 | error: ffi::oboe_Result, 311 | ) { 312 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 313 | let callback = &mut *(context as *mut T); 314 | 315 | callback.on_error_after_close(&mut audio_stream, FromPrimitive::from_i32(error).unwrap()); 316 | } 317 | 318 | unsafe extern "C" fn on_audio_ready_output_wrapper( 319 | context: *mut c_void, 320 | audio_stream: *mut ffi::oboe_AudioStream, 321 | audio_data: *mut c_void, 322 | num_frames: i32, 323 | ) -> ffi::oboe_DataCallbackResult { 324 | let mut audio_stream = AudioStreamRef::wrap_raw(&mut *audio_stream); 325 | 326 | let audio_data = from_raw_parts_mut( 327 | audio_data as *mut ::Type, 328 | num_frames as usize, 329 | ); 330 | 331 | let callback = &mut *(context as *mut T); 332 | 333 | callback.on_audio_ready(&mut audio_stream, audio_data) as i32 334 | } 335 | -------------------------------------------------------------------------------- /src/definitions.rs: -------------------------------------------------------------------------------- 1 | use num_derive::{FromPrimitive, ToPrimitive}; 2 | use num_traits::FromPrimitive; 3 | use oboe_sys as ffi; 4 | use std::{error, fmt, result}; 5 | 6 | /** 7 | * The number of nanoseconds in a microsecond. 1,000. 8 | */ 9 | pub const NANOS_PER_MICROSECOND: i64 = 1000; 10 | 11 | /** 12 | * The number of nanoseconds in a millisecond. 1,000,000. 13 | */ 14 | pub const NANOS_PER_MILLISECOND: i64 = NANOS_PER_MICROSECOND * 1000; 15 | 16 | /** 17 | * The number of milliseconds in a second. 1,000. 18 | */ 19 | pub const MILLIS_PER_SECOND: i64 = 1000; 20 | 21 | /** 22 | * The number of nanoseconds in a second. 1,000,000,000. 23 | */ 24 | pub const NANOS_PER_SECOND: i64 = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND; 25 | 26 | /** 27 | * The state of the audio stream. 28 | */ 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 30 | #[repr(i32)] 31 | pub enum StreamState { 32 | Uninitialized = ffi::oboe_StreamState_Uninitialized, 33 | Unknown = ffi::oboe_StreamState_Unknown, 34 | Open = ffi::oboe_StreamState_Open, 35 | Starting = ffi::oboe_StreamState_Starting, 36 | Started = ffi::oboe_StreamState_Started, 37 | Pausing = ffi::oboe_StreamState_Pausing, 38 | Paused = ffi::oboe_StreamState_Paused, 39 | Flushing = ffi::oboe_StreamState_Flushing, 40 | Flushed = ffi::oboe_StreamState_Flushed, 41 | Stopping = ffi::oboe_StreamState_Stopping, 42 | Stopped = ffi::oboe_StreamState_Stopped, 43 | Closing = ffi::oboe_StreamState_Closing, 44 | Closed = ffi::oboe_StreamState_Closed, 45 | Disconnected = ffi::oboe_StreamState_Disconnected, 46 | } 47 | 48 | /** 49 | * The direction of the stream. 50 | */ 51 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 52 | #[repr(i32)] 53 | pub enum Direction { 54 | /** 55 | * Used for playback. 56 | */ 57 | Output = ffi::oboe_Direction_Output, 58 | 59 | /** 60 | * Used for recording. 61 | */ 62 | Input = ffi::oboe_Direction_Input, 63 | } 64 | 65 | /** 66 | * The format of audio samples. 67 | */ 68 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 69 | #[repr(i32)] 70 | pub enum AudioFormat { 71 | /** 72 | * Invalid format. 73 | */ 74 | Invalid = ffi::oboe_AudioFormat_Invalid, 75 | 76 | /** 77 | * Unspecified format. Format will be decided by Oboe. 78 | */ 79 | Unspecified = ffi::oboe_AudioFormat_Unspecified, 80 | 81 | /** 82 | * Signed 16-bit integers. 83 | */ 84 | I16 = ffi::oboe_AudioFormat_I16, 85 | 86 | /** 87 | * Signed 24-bit integers. 88 | */ 89 | I24 = ffi::oboe_AudioFormat_I24, 90 | 91 | /** 92 | * Signed 32-bit integers. 93 | */ 94 | I32 = ffi::oboe_AudioFormat_I32, 95 | 96 | /** 97 | * Single precision floating points. 98 | */ 99 | F32 = ffi::oboe_AudioFormat_Float, 100 | } 101 | 102 | /** 103 | * The result of an audio callback. 104 | */ 105 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 106 | #[repr(i32)] 107 | pub enum DataCallbackResult { 108 | /** 109 | * Indicates to the caller that the callbacks should continue. 110 | */ 111 | Continue = ffi::oboe_DataCallbackResult_Continue, 112 | 113 | /** 114 | * Indicates to the caller that the callbacks should stop immediately. 115 | */ 116 | Stop = ffi::oboe_DataCallbackResult_Stop, 117 | } 118 | 119 | /** 120 | * The result of an operation with value 121 | */ 122 | pub type Result = result::Result; 123 | 124 | /** 125 | * The result of operation without value 126 | */ 127 | pub type Status = Result<()>; 128 | 129 | pub(crate) fn wrap_status(result: i32) -> Status { 130 | if result == ffi::oboe_Result_OK { 131 | Ok(()) 132 | } else { 133 | Err(FromPrimitive::from_i32(result).unwrap()) 134 | } 135 | } 136 | 137 | pub(crate) fn wrap_result(result: ffi::oboe_ResultWithValue) -> Result { 138 | if result.mError == ffi::oboe_Result_OK { 139 | Ok(result.mValue) 140 | } else { 141 | Err(FromPrimitive::from_i32(result.mError).unwrap()) 142 | } 143 | } 144 | 145 | /** 146 | * The error of an operation. 147 | */ 148 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 149 | #[repr(i32)] 150 | pub enum Error { 151 | Disconnected = ffi::oboe_Result_ErrorDisconnected, 152 | IllegalArgument = ffi::oboe_Result_ErrorIllegalArgument, 153 | Internal = ffi::oboe_Result_ErrorInternal, 154 | InvalidState = ffi::oboe_Result_ErrorInvalidState, 155 | InvalidHandle = ffi::oboe_Result_ErrorInvalidHandle, 156 | Unimplemented = ffi::oboe_Result_ErrorUnimplemented, 157 | Unavailable = ffi::oboe_Result_ErrorUnavailable, 158 | NoFreeHandles = ffi::oboe_Result_ErrorNoFreeHandles, 159 | NoMemory = ffi::oboe_Result_ErrorNoMemory, 160 | Null = ffi::oboe_Result_ErrorNull, 161 | Timeout = ffi::oboe_Result_ErrorTimeout, 162 | WouldBlock = ffi::oboe_Result_ErrorWouldBlock, 163 | InvalidFormat = ffi::oboe_Result_ErrorInvalidFormat, 164 | OutOfRange = ffi::oboe_Result_ErrorOutOfRange, 165 | NoService = ffi::oboe_Result_ErrorNoService, 166 | InvalidRate = ffi::oboe_Result_ErrorInvalidRate, 167 | Closed = ffi::oboe_Result_ErrorClosed, 168 | } 169 | 170 | impl error::Error for Error {} 171 | impl fmt::Display for Error { 172 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 173 | fmt::Debug::fmt(self, f) 174 | } 175 | } 176 | 177 | /** 178 | * The sharing mode of the audio stream. 179 | */ 180 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 181 | #[repr(i32)] 182 | pub enum SharingMode { 183 | /** 184 | * This will be the only stream using a particular source or sink. 185 | * This mode will provide the lowest possible latency. 186 | * You should close EXCLUSIVE streams immediately when you are not using them. 187 | * 188 | * If you do not need the lowest possible latency then we recommend using Shared, 189 | * which is the default. 190 | */ 191 | Exclusive = ffi::oboe_SharingMode_Exclusive, 192 | 193 | /** 194 | * Multiple applications can share the same device. 195 | * The data from output streams will be mixed by the audio service. 196 | * The data for input streams will be distributed by the audio service. 197 | * 198 | * This will have higher latency than the EXCLUSIVE mode. 199 | */ 200 | Shared = ffi::oboe_SharingMode_Shared, 201 | } 202 | 203 | /** 204 | * The performance mode of the audio stream. 205 | */ 206 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 207 | #[repr(i32)] 208 | pub enum PerformanceMode { 209 | /** 210 | * No particular performance needs. Default. 211 | */ 212 | None = ffi::oboe_PerformanceMode_None, 213 | 214 | /** 215 | * Extending battery life is most important. 216 | */ 217 | PowerSaving = ffi::oboe_PerformanceMode_PowerSaving, 218 | 219 | /** 220 | * Reducing latency is most important. 221 | */ 222 | LowLatency = ffi::oboe_PerformanceMode_LowLatency, 223 | } 224 | 225 | /** 226 | * The underlying audio API used by the audio stream. 227 | */ 228 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 229 | #[repr(i32)] 230 | pub enum AudioApi { 231 | /** 232 | * Try to use AAudio. If not available then use OpenSL ES. 233 | */ 234 | Unspecified = ffi::oboe_AudioApi_Unspecified, 235 | 236 | /** 237 | * Use OpenSL ES. 238 | */ 239 | OpenSLES = ffi::oboe_AudioApi_OpenSLES, 240 | 241 | /** 242 | * Try to use AAudio. Fail if unavailable. 243 | */ 244 | AAudio = ffi::oboe_AudioApi_AAudio, 245 | } 246 | 247 | /** 248 | * Specifies the quality of the sample rate conversion performed by Oboe. 249 | * Higher quality will require more CPU load. 250 | * Higher quality conversion will probably be implemented using a sinc based resampler. 251 | */ 252 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 253 | #[repr(i32)] 254 | pub enum SampleRateConversionQuality { 255 | /** 256 | * No conversion by Oboe. Underlying APIs may still do conversion. 257 | */ 258 | None, 259 | 260 | /** 261 | * Fastest conversion but may not sound great. 262 | * This may be implemented using bilinear interpolation. 263 | */ 264 | Fastest, 265 | Low, 266 | Medium, 267 | High, 268 | 269 | /** 270 | * Highest quality conversion, which may be expensive in terms of CPU. 271 | */ 272 | Best, 273 | } 274 | 275 | /** 276 | * The Usage attribute expresses *why* you are playing a sound, what is this sound used for. 277 | * This information is used by certain platforms or routing policies 278 | * to make more refined volume or routing decisions. 279 | * 280 | * Note that these match the equivalent values in AudioAttributes in the Android Java API. 281 | * 282 | * This attribute only has an effect on Android API 28+. 283 | */ 284 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 285 | #[repr(i32)] 286 | pub enum Usage { 287 | /** 288 | * Use this for streaming media, music performance, video, podcasts, etcetera. 289 | */ 290 | Media = ffi::oboe_Usage_Media, 291 | 292 | /** 293 | * Use this for voice over IP, telephony, etcetera. 294 | */ 295 | VoiceCommunication = ffi::oboe_Usage_VoiceCommunication, 296 | 297 | /** 298 | * Use this for sounds associated with telephony such as busy tones, DTMF, etcetera. 299 | */ 300 | VoiceCommunicationSignalling = ffi::oboe_Usage_VoiceCommunicationSignalling, 301 | 302 | /** 303 | * Use this to demand the users attention. 304 | */ 305 | Alarm = ffi::oboe_Usage_Alarm, 306 | 307 | /** 308 | * Use this for notifying the user when a message has arrived or some 309 | * other background event has occured. 310 | */ 311 | Notification = ffi::oboe_Usage_Notification, 312 | 313 | /** 314 | * Use this when the phone rings. 315 | */ 316 | NotificationRingtone = ffi::oboe_Usage_NotificationRingtone, 317 | 318 | /** 319 | * Use this to attract the users attention when, for example, the battery is low. 320 | */ 321 | NotificationEvent = ffi::oboe_Usage_NotificationEvent, 322 | 323 | /** 324 | * Use this for screen readers, etcetera. 325 | */ 326 | AssistanceAccessibility = ffi::oboe_Usage_AssistanceAccessibility, 327 | 328 | /** 329 | * Use this for driving or navigation directions. 330 | */ 331 | AssistanceNavigationGuidance = ffi::oboe_Usage_AssistanceNavigationGuidance, 332 | 333 | /** 334 | * Use this for user interface sounds, beeps, etcetera. 335 | */ 336 | AssistanceSonification = ffi::oboe_Usage_AssistanceSonification, 337 | 338 | /** 339 | * Use this for game audio and sound effects. 340 | */ 341 | Game = ffi::oboe_Usage_Game, 342 | 343 | /** 344 | * Use this for audio responses to user queries, audio instructions or help utterances. 345 | */ 346 | Assistant = ffi::oboe_Usage_Assistant, 347 | } 348 | 349 | /** 350 | * The ContentType attribute describes *what* you are playing. 351 | * It expresses the general category of the content. This information is optional. 352 | * But in case it is known (for instance {@link Movie} for a 353 | * movie streaming service or {@link Speech} for 354 | * an audio book application) this information might be used by the audio framework to 355 | * enforce audio focus. 356 | * 357 | * Note that these match the equivalent values in AudioAttributes in the Android Java API. 358 | * 359 | * This attribute only has an effect on Android API 28+. 360 | */ 361 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 362 | #[repr(i32)] 363 | pub enum ContentType { 364 | /** 365 | * Use this for spoken voice, audio books, etcetera. 366 | */ 367 | Speech = ffi::oboe_ContentType_Speech, 368 | 369 | /** 370 | * Use this for pre-recorded or live music. 371 | */ 372 | Music = ffi::oboe_ContentType_Music, 373 | 374 | /** 375 | * Use this for a movie or video soundtrack. 376 | */ 377 | Movie = ffi::oboe_ContentType_Movie, 378 | 379 | /** 380 | * Use this for sound is designed to accompany a user action, 381 | * such as a click or beep sound made when the user presses a button. 382 | */ 383 | Sonification = ffi::oboe_ContentType_Sonification, 384 | } 385 | 386 | /** 387 | * Defines the audio source. 388 | * An audio source defines both a default physical source of audio signal, and a recording 389 | * configuration. 390 | * 391 | * Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API. 392 | * 393 | * This attribute only has an effect on Android API 28+. 394 | */ 395 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 396 | #[repr(i32)] 397 | pub enum InputPreset { 398 | /** 399 | * Use this preset when other presets do not apply. 400 | */ 401 | Generic = ffi::oboe_InputPreset_Generic, 402 | 403 | /** 404 | * Use this preset when recording video. 405 | */ 406 | Camcorder = ffi::oboe_InputPreset_Camcorder, 407 | 408 | /** 409 | * Use this preset when doing speech recognition. 410 | */ 411 | VoiceRecognition = ffi::oboe_InputPreset_VoiceRecognition, 412 | 413 | /** 414 | * Use this preset when doing telephony or voice messaging. 415 | */ 416 | VoiceCommunication = ffi::oboe_InputPreset_VoiceCommunication, 417 | 418 | /** 419 | * Use this preset to obtain an input with no effects. 420 | * Note that this input will not have automatic gain control 421 | * so the recorded volume may be very low. 422 | */ 423 | Unprocessed = ffi::oboe_InputPreset_Unprocessed, 424 | 425 | /** 426 | * Use this preset for capturing audio meant to be processed in real time 427 | * and played back for live performance (e.g karaoke). 428 | * The capture path will minimize latency and coupling with playback path. 429 | */ 430 | VoicePerformance = ffi::oboe_InputPreset_VoicePerformance, 431 | } 432 | 433 | /** 434 | * This attribute can be used to allocate a session ID to the audio stream. 435 | * 436 | * This attribute only has an effect on Android API 28+. 437 | */ 438 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 439 | #[repr(i32)] 440 | pub enum SessionId { 441 | /** 442 | * Do not allocate a session ID. 443 | * Effects cannot be used with this stream. 444 | * Default. 445 | */ 446 | None = ffi::oboe_SessionId_None, 447 | 448 | /** 449 | * Allocate a session ID that can be used to attach and control 450 | * effects using the Java AudioEffects API. 451 | * Note that the use of this flag may result in higher latency. 452 | * 453 | * Note that this matches the value of `AudioManager.AUDIO_SESSION_ID_GENERATE`. 454 | */ 455 | Allocate = ffi::oboe_SessionId_Allocate, 456 | } 457 | 458 | /** 459 | * The channel count of the audio stream. 460 | * Use of this enum is convenient to avoid "magic" 461 | * numbers when specifying the channel count. 462 | * 463 | * For example, you can write 464 | * `builder.set_channel_count(ChannelCount::Stereo)` 465 | * rather than `builder.set_channel_count(2). 466 | */ 467 | #[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)] 468 | #[repr(i32)] 469 | pub enum ChannelCount { 470 | /** 471 | * Audio channel count definition, use Mono or Stereo 472 | */ 473 | Unspecified = ffi::oboe_ChannelCount_Unspecified, 474 | 475 | /** 476 | * Use this for mono audio. 477 | */ 478 | Mono = ffi::oboe_ChannelCount_Mono, 479 | 480 | /** 481 | * Use this for stereo audio. 482 | */ 483 | Stereo = ffi::oboe_ChannelCount_Stereo, 484 | } 485 | 486 | /** 487 | * The default (optimal) audio streaming values. 488 | * 489 | * On API 16 to 26 OpenSL ES will be used. 490 | * When using OpenSL ES the optimal values for `sample_rate` and 491 | * `frames_per_burst` are not known by the native code. 492 | * On API 17+ these values should be obtained from the AudioManager using this code: 493 | * 494 | * ```java 495 | * // Note that this technique only works for built-in speakers and headphones. 496 | * AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 497 | * String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); 498 | * int defaultSampleRate = Integer.parseInt(sampleRateStr); 499 | * String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 500 | * int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr); 501 | * ``` 502 | * 503 | * It can then be passed down to Oboe through JNI. 504 | * 505 | * AAudio will get the optimal `frames_per_burst` from the HAL and will ignore this value. 506 | */ 507 | pub struct DefaultStreamValues(()); 508 | 509 | impl DefaultStreamValues { 510 | /** 511 | * The default sample rate to use when opening new audio streams 512 | */ 513 | pub fn get_sample_rate() -> i32 { 514 | unsafe { ffi::oboe_DefaultStreamValues_SampleRate } 515 | } 516 | 517 | pub fn set_sample_rate(sample_rate: i32) { 518 | unsafe { 519 | ffi::oboe_DefaultStreamValues_SampleRate = sample_rate; 520 | } 521 | } 522 | 523 | /** 524 | * The default frames per burst to use when opening new audio streams 525 | */ 526 | pub fn get_frames_per_burst() -> i32 { 527 | unsafe { ffi::oboe_DefaultStreamValues_FramesPerBurst } 528 | } 529 | 530 | pub fn set_frames_per_burst(frames_per_burst: i32) { 531 | unsafe { 532 | ffi::oboe_DefaultStreamValues_FramesPerBurst = frames_per_burst; 533 | } 534 | } 535 | 536 | /** 537 | * The default channel count to use when opening new audio streams 538 | */ 539 | pub fn get_channel_count() -> i32 { 540 | unsafe { ffi::oboe_DefaultStreamValues_ChannelCount } 541 | } 542 | 543 | pub fn set_channel_count(channel_count: i32) { 544 | unsafe { 545 | ffi::oboe_DefaultStreamValues_ChannelCount = channel_count; 546 | } 547 | } 548 | } 549 | 550 | /** 551 | * The time at which the frame at `position` was presented 552 | */ 553 | #[repr(C)] 554 | #[derive(Debug, Clone, Copy)] 555 | pub struct FrameTimestamp { 556 | /** 557 | * The position in number of frames 558 | */ 559 | pub position: i64, 560 | 561 | /** 562 | * The timestamp in nanoseconds 563 | */ 564 | pub timestamp: i64, 565 | } 566 | -------------------------------------------------------------------------------- /src/java_interface.rs: -------------------------------------------------------------------------------- 1 | mod audio_features; 2 | mod definitions; 3 | mod devices_info; 4 | mod stream_defaults; 5 | mod utils; 6 | 7 | pub use self::audio_features::*; 8 | pub use self::definitions::*; 9 | -------------------------------------------------------------------------------- /src/java_interface/audio_features.rs: -------------------------------------------------------------------------------- 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 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 14 | pub enum AudioFeature { 15 | LowLatency, 16 | Output, 17 | Pro, 18 | Microphone, 19 | Midi, 20 | } 21 | 22 | impl From for &'static str { 23 | fn from(feature: AudioFeature) -> Self { 24 | use AudioFeature::*; 25 | match feature { 26 | LowLatency => PackageManager::FEATURE_AUDIO_LOW_LATENCY, 27 | Output => PackageManager::FEATURE_AUDIO_OUTPUT, 28 | Pro => PackageManager::FEATURE_AUDIO_PRO, 29 | Microphone => PackageManager::FEATURE_MICROPHONE, 30 | Midi => PackageManager::FEATURE_MIDI, 31 | } 32 | } 33 | } 34 | 35 | impl AudioFeature { 36 | /** 37 | * Check availability of an audio feature using Android Java API 38 | */ 39 | pub fn has(&self) -> Result { 40 | let context = get_context(); 41 | 42 | with_attached(context, |env, activity| { 43 | try_check_system_feature(env, &activity, (*self).into()) 44 | }) 45 | .map_err(|error| error.to_string()) 46 | } 47 | } 48 | 49 | fn try_check_system_feature<'j>( 50 | env: &mut JNIEnv<'j>, 51 | activity: &JObject<'j>, 52 | feature: &str, 53 | ) -> JResult { 54 | let package_manager = get_package_manager(env, activity)?; 55 | 56 | has_system_feature(env, &package_manager, feature) 57 | } 58 | -------------------------------------------------------------------------------- /src/java_interface/definitions.rs: -------------------------------------------------------------------------------- 1 | use num_derive::FromPrimitive; 2 | 3 | use crate::AudioFormat; 4 | 5 | pub(crate) struct Context; 6 | 7 | impl Context { 8 | pub const AUDIO_SERVICE: &'static str = "audio"; 9 | } 10 | 11 | pub(crate) struct PackageManager; 12 | 13 | impl PackageManager { 14 | pub const FEATURE_AUDIO_LOW_LATENCY: &'static str = "android.hardware.audio.low_latency"; 15 | pub const FEATURE_AUDIO_OUTPUT: &'static str = "android.hardware.audio.output"; 16 | pub const FEATURE_AUDIO_PRO: &'static str = "android.hardware.audio.pro"; 17 | pub const FEATURE_MICROPHONE: &'static str = "android.hardware.microphone"; 18 | pub const FEATURE_MIDI: &'static str = "android.software.midi"; 19 | } 20 | 21 | pub(crate) struct AudioManager; 22 | 23 | impl AudioManager { 24 | pub const PROPERTY_OUTPUT_SAMPLE_RATE: &'static str = 25 | "android.media.property.OUTPUT_SAMPLE_RATE"; 26 | pub const PROPERTY_OUTPUT_FRAMES_PER_BUFFER: &'static str = 27 | "android.media.property.OUTPUT_FRAMES_PER_BUFFER"; 28 | 29 | pub const GET_DEVICES_INPUTS: i32 = 1 << 0; 30 | pub const GET_DEVICES_OUTPUTS: i32 = 1 << 1; 31 | pub const GET_DEVICES_ALL: i32 = Self::GET_DEVICES_INPUTS | Self::GET_DEVICES_OUTPUTS; 32 | } 33 | 34 | /** 35 | * The Android audio device info 36 | */ 37 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 38 | #[derive(Debug, Clone)] 39 | pub struct AudioDeviceInfo { 40 | /** 41 | * Device identifier 42 | */ 43 | pub id: i32, 44 | 45 | /** 46 | * The type of device 47 | */ 48 | pub device_type: AudioDeviceType, 49 | 50 | /** 51 | * The device can be used for playback and/or capture 52 | */ 53 | pub direction: AudioDeviceDirection, 54 | 55 | /** 56 | * Device address 57 | */ 58 | pub address: String, 59 | 60 | /** 61 | * Device product name 62 | */ 63 | pub product_name: String, 64 | 65 | /** 66 | * Available channel configurations 67 | */ 68 | pub channel_counts: Vec, 69 | 70 | /** 71 | * Supported sample rates 72 | */ 73 | pub sample_rates: Vec, 74 | 75 | /** 76 | * Supported audio formats 77 | */ 78 | pub formats: Vec, 79 | } 80 | 81 | /** 82 | * The type of audio device 83 | */ 84 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 85 | #[derive(Debug, Clone, Copy, FromPrimitive)] 86 | #[non_exhaustive] 87 | #[repr(i32)] 88 | pub enum AudioDeviceType { 89 | Unknown = 0, 90 | AuxLine = 19, 91 | BleBroadcast = 30, 92 | BleHeadset = 26, 93 | BleSpeaker = 27, 94 | BluetoothA2DP = 8, 95 | BluetoothSCO = 7, 96 | BuiltinEarpiece = 1, 97 | BuiltinMic = 15, 98 | BuiltinSpeaker = 2, 99 | BuiltinSpeakerSafe = 24, 100 | Bus = 21, 101 | Dock = 13, 102 | Fm = 14, 103 | FmTuner = 16, 104 | Hdmi = 9, 105 | HdmiArc = 10, 106 | HdmiEarc = 29, 107 | HearingAid = 23, 108 | Ip = 20, 109 | LineAnalog = 5, 110 | LineDigital = 6, 111 | RemoteSubmix = 25, 112 | Telephony = 18, 113 | TvTuner = 17, 114 | UsbAccessory = 12, 115 | UsbDevice = 11, 116 | UsbHeadset = 22, 117 | WiredHeadphones = 4, 118 | WiredHeadset = 3, 119 | Unsupported = -1, 120 | } 121 | 122 | /** 123 | * The direction of audio device 124 | */ 125 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 126 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 127 | #[repr(i32)] 128 | pub enum AudioDeviceDirection { 129 | Dumb = 0, 130 | Input = AudioManager::GET_DEVICES_INPUTS, 131 | Output = AudioManager::GET_DEVICES_OUTPUTS, 132 | InputOutput = AudioManager::GET_DEVICES_ALL, 133 | } 134 | 135 | impl AudioDeviceDirection { 136 | pub fn new(is_input: bool, is_output: bool) -> Self { 137 | use self::AudioDeviceDirection::*; 138 | match (is_input, is_output) { 139 | (true, true) => InputOutput, 140 | (false, true) => Output, 141 | (true, false) => Input, 142 | _ => Dumb, 143 | } 144 | } 145 | 146 | pub fn is_input(&self) -> bool { 147 | 0 < *self as i32 & AudioDeviceDirection::Input as i32 148 | } 149 | 150 | pub fn is_output(&self) -> bool { 151 | 0 < *self as i32 & AudioDeviceDirection::Output as i32 152 | } 153 | } 154 | 155 | impl AudioFormat { 156 | pub(crate) const ENCODING_PCM_16BIT: i32 = 2; 157 | //pub(crate) const ENCODING_PCM_8BIT: i32 = 3; 158 | pub(crate) const ENCODING_PCM_FLOAT: i32 = 4; 159 | 160 | pub(crate) fn from_encoding(encoding: i32) -> Option { 161 | match encoding { 162 | AudioFormat::ENCODING_PCM_16BIT => Some(AudioFormat::I16), 163 | AudioFormat::ENCODING_PCM_FLOAT => Some(AudioFormat::F32), 164 | _ => None, 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/java_interface/devices_info.rs: -------------------------------------------------------------------------------- 1 | use num_traits::FromPrimitive; 2 | 3 | use crate::AudioFormat; 4 | 5 | use super::{ 6 | utils::{ 7 | call_method_no_args_ret_bool, call_method_no_args_ret_char_sequence, 8 | call_method_no_args_ret_int, call_method_no_args_ret_int_array, 9 | call_method_no_args_ret_string, get_context, get_devices, get_system_service, 10 | with_attached, JNIEnv, JObject, JResult, 11 | }, 12 | AudioDeviceDirection, AudioDeviceInfo, AudioDeviceType, Context, 13 | }; 14 | 15 | impl AudioDeviceInfo { 16 | /** 17 | * Request audio devices using Android Java API 18 | */ 19 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 20 | pub fn request(direction: AudioDeviceDirection) -> Result, String> { 21 | let context = get_context(); 22 | 23 | with_attached(context, |env, context| { 24 | let sdk_version = env 25 | .get_static_field("android/os/Build$VERSION", "SDK_INT", "I")? 26 | .i()?; 27 | 28 | if sdk_version >= 23 { 29 | try_request_devices_info(env, &context, direction) 30 | } else { 31 | Err(jni::errors::Error::MethodNotFound { 32 | name: "".into(), 33 | sig: "".into(), 34 | }) 35 | } 36 | }) 37 | .map_err(|error| error.to_string()) 38 | } 39 | } 40 | 41 | fn try_request_devices_info<'j>( 42 | env: &mut JNIEnv<'j>, 43 | context: &JObject<'j>, 44 | direction: AudioDeviceDirection, 45 | ) -> JResult> { 46 | let audio_manager = get_system_service(env, context, Context::AUDIO_SERVICE)?; 47 | 48 | let devices = get_devices(env, &audio_manager, direction as i32)?; 49 | 50 | let length = env.get_array_length(&devices)?; 51 | 52 | (0..length) 53 | .map(|index| { 54 | let device = env.get_object_array_element(&devices, index)?; 55 | let id = call_method_no_args_ret_int(env, &device, "getId")?; 56 | let address = call_method_no_args_ret_string(env, &device, "getAddress")?; 57 | let address = String::from(env.get_string(&address)?); 58 | let product_name = 59 | call_method_no_args_ret_char_sequence(env, &device, "getProductName")?; 60 | let product_name = String::from(env.get_string(&product_name)?); 61 | let device_type = 62 | FromPrimitive::from_i32(call_method_no_args_ret_int(env, &device, "getType")?) 63 | .unwrap_or(AudioDeviceType::Unsupported); 64 | let direction = AudioDeviceDirection::new( 65 | call_method_no_args_ret_bool(env, &device, "isSource")?, 66 | call_method_no_args_ret_bool(env, &device, "isSink")?, 67 | ); 68 | let channel_counts = 69 | call_method_no_args_ret_int_array(env, &device, "getChannelCounts")?; 70 | let sample_rates = call_method_no_args_ret_int_array(env, &device, "getSampleRates")?; 71 | let formats = call_method_no_args_ret_int_array(env, &device, "getEncodings")? 72 | .into_iter() 73 | .filter_map(AudioFormat::from_encoding) 74 | .collect::>(); 75 | 76 | Ok(AudioDeviceInfo { 77 | id, 78 | address, 79 | product_name, 80 | device_type, 81 | direction, 82 | channel_counts, 83 | sample_rates, 84 | formats, 85 | }) 86 | }) 87 | .collect::, _>>() 88 | } 89 | -------------------------------------------------------------------------------- /src/java_interface/stream_defaults.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | utils::{ 3 | get_context, get_property, get_system_service, with_attached, JNIEnv, JObject, JResult, 4 | }, 5 | AudioManager, Context, 6 | }; 7 | 8 | use crate::DefaultStreamValues; 9 | 10 | impl DefaultStreamValues { 11 | /** 12 | * Try request defaults from AudioManager properties. 13 | */ 14 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "java-interface")))] 15 | pub fn init() -> Result<(), String> { 16 | let activity = get_context(); 17 | 18 | let values = with_attached(activity, |env, context| { 19 | let sdk_version = env 20 | .get_static_field("android/os/Build$VERSION", "SDK_INT", "I")? 21 | .i()?; 22 | 23 | if sdk_version < 17 { 24 | Err(jni::errors::Error::MethodNotFound { 25 | name: "".into(), 26 | sig: "".into(), 27 | }) 28 | } else if sdk_version < 26 { 29 | try_request_default_stream_values(env, &context).map(Some) 30 | } else { 31 | // not necessary 32 | Ok(None) 33 | } 34 | }); 35 | 36 | match values { 37 | Ok(Some((sample_rate, frames_per_burst))) => { 38 | if let Some(value) = sample_rate { 39 | Self::set_sample_rate(value); 40 | } 41 | if let Some(value) = frames_per_burst { 42 | Self::set_frames_per_burst(value); 43 | } 44 | Ok(()) 45 | } 46 | Ok(None) => Ok(()), 47 | Err(error) => Err(error.to_string()), 48 | } 49 | } 50 | } 51 | 52 | fn try_request_default_stream_values<'j>( 53 | env: &mut JNIEnv<'j>, 54 | context: &JObject<'j>, 55 | ) -> JResult<(Option, Option)> { 56 | let audio_manager = get_system_service(env, context, Context::AUDIO_SERVICE)?; 57 | 58 | let sample_rate = get_property( 59 | env, 60 | &audio_manager, 61 | AudioManager::PROPERTY_OUTPUT_SAMPLE_RATE, 62 | )?; 63 | let sample_rate = env.get_string(&sample_rate)?; 64 | 65 | let frames_per_burst = get_property( 66 | env, 67 | &audio_manager, 68 | AudioManager::PROPERTY_OUTPUT_FRAMES_PER_BUFFER, 69 | )?; 70 | let frames_per_burst = env.get_string(&frames_per_burst)?; 71 | 72 | Ok(( 73 | (*sample_rate).to_str().ok().and_then(|s| s.parse().ok()), 74 | (*frames_per_burst) 75 | .to_str() 76 | .ok() 77 | .and_then(|s| s.parse().ok()), 78 | )) 79 | } 80 | -------------------------------------------------------------------------------- /src/java_interface/utils.rs: -------------------------------------------------------------------------------- 1 | use jni::sys::jobject; 2 | use ndk_context::AndroidContext; 3 | use std::sync::Arc; 4 | 5 | pub use jni::Executor; 6 | 7 | pub use jni::{ 8 | errors::Result as JResult, 9 | objects::{JIntArray, JObject, JObjectArray, JString}, 10 | JNIEnv, JavaVM, 11 | }; 12 | 13 | pub fn get_context() -> AndroidContext { 14 | ndk_context::android_context() 15 | } 16 | 17 | pub fn with_attached(context: AndroidContext, closure: F) -> JResult 18 | where 19 | for<'j> F: FnOnce(&mut JNIEnv<'j>, JObject<'j>) -> JResult, 20 | { 21 | let vm = Arc::new(unsafe { JavaVM::from_raw(context.vm().cast())? }); 22 | let context = context.context(); 23 | let context = unsafe { JObject::from_raw(context as jobject) }; 24 | Executor::new(vm).with_attached(|env| closure(env, context)) 25 | } 26 | 27 | pub fn call_method_no_args_ret_int_array<'j>( 28 | env: &mut JNIEnv<'j>, 29 | subject: &JObject<'j>, 30 | method: &str, 31 | ) -> JResult> { 32 | let array: JIntArray = env.call_method(subject, method, "()[I", &[])?.l()?.into(); 33 | 34 | let length = env.get_array_length(&array)?; 35 | let mut values = Vec::with_capacity(length as usize); 36 | 37 | env.get_int_array_region(array, 0, values.as_mut())?; 38 | 39 | Ok(values) 40 | } 41 | 42 | pub fn call_method_no_args_ret_int<'j>( 43 | env: &mut JNIEnv<'j>, 44 | subject: &JObject<'j>, 45 | method: &str, 46 | ) -> JResult { 47 | env.call_method(subject, method, "()I", &[])?.i() 48 | } 49 | 50 | pub fn call_method_no_args_ret_bool<'j>( 51 | env: &mut JNIEnv<'j>, 52 | subject: &JObject<'j>, 53 | method: &str, 54 | ) -> JResult { 55 | env.call_method(subject, method, "()Z", &[])?.z() 56 | } 57 | 58 | pub fn call_method_no_args_ret_string<'j>( 59 | env: &mut JNIEnv<'j>, 60 | subject: &JObject<'j>, 61 | method: &str, 62 | ) -> JResult> { 63 | Ok(env 64 | .call_method(subject, method, "()Ljava/lang/String;", &[])? 65 | .l()? 66 | .into()) 67 | } 68 | 69 | pub fn call_method_no_args_ret_char_sequence<'j>( 70 | env: &mut JNIEnv<'j>, 71 | subject: &JObject<'j>, 72 | method: &str, 73 | ) -> JResult> { 74 | let cseq = env 75 | .call_method(subject, method, "()Ljava/lang/CharSequence;", &[])? 76 | .l()?; 77 | 78 | Ok(env 79 | .call_method(&cseq, "toString", "()Ljava/lang/String;", &[])? 80 | .l()? 81 | .into()) 82 | } 83 | 84 | pub fn call_method_string_arg_ret_bool<'j>( 85 | env: &mut JNIEnv<'j>, 86 | subject: &JObject<'j>, 87 | name: &str, 88 | arg: impl AsRef, 89 | ) -> JResult { 90 | env.call_method( 91 | subject, 92 | name, 93 | "(Ljava/lang/String;)Z", 94 | &[(&env.new_string(arg)?).into()], 95 | )? 96 | .z() 97 | } 98 | 99 | pub fn call_method_string_arg_ret_string<'j>( 100 | env: &mut JNIEnv<'j>, 101 | subject: &JObject<'j>, 102 | name: &str, 103 | arg: impl AsRef, 104 | ) -> JResult> { 105 | Ok(env 106 | .call_method( 107 | subject, 108 | name, 109 | "(Ljava/lang/String;)Ljava/lang/String;", 110 | &[(&env.new_string(arg)?).into()], 111 | )? 112 | .l()? 113 | .into()) 114 | } 115 | 116 | pub fn call_method_string_arg_ret_object<'j>( 117 | env: &mut JNIEnv<'j>, 118 | subject: &JObject<'j>, 119 | method: &str, 120 | arg: &str, 121 | ) -> JResult> { 122 | env.call_method( 123 | subject, 124 | method, 125 | "(Ljava/lang/String;)Ljava/lang/Object;", 126 | &[(&env.new_string(arg)?).into()], 127 | )? 128 | .l() 129 | } 130 | 131 | pub fn get_package_manager<'j>( 132 | env: &mut JNIEnv<'j>, 133 | subject: &JObject<'j>, 134 | ) -> JResult> { 135 | env.call_method( 136 | subject, 137 | "getPackageManager", 138 | "()Landroid/content/pm/PackageManager;", 139 | &[], 140 | )? 141 | .l() 142 | } 143 | 144 | pub fn has_system_feature<'j>( 145 | env: &mut JNIEnv<'j>, 146 | subject: &JObject<'j>, 147 | name: &str, 148 | ) -> JResult { 149 | call_method_string_arg_ret_bool(env, subject, "hasSystemFeature", name) 150 | } 151 | 152 | pub fn get_system_service<'j>( 153 | env: &mut JNIEnv<'j>, 154 | subject: &JObject<'j>, 155 | name: &str, 156 | ) -> JResult> { 157 | call_method_string_arg_ret_object(env, subject, "getSystemService", name) 158 | } 159 | 160 | pub fn get_property<'j>( 161 | env: &mut JNIEnv<'j>, 162 | subject: &JObject<'j>, 163 | name: &str, 164 | ) -> JResult> { 165 | call_method_string_arg_ret_string(env, subject, "getProperty", name) 166 | } 167 | 168 | pub fn get_devices<'j>( 169 | env: &mut JNIEnv<'j>, 170 | subject: &JObject<'j>, 171 | flags: i32, 172 | ) -> JResult> { 173 | env.call_method( 174 | subject, 175 | "getDevices", 176 | "(I)[Landroid/media/AudioDeviceInfo;", 177 | &[flags.into()], 178 | )? 179 | .l() 180 | .map(From::from) 181 | } 182 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] 3 | 4 | mod audio_stream; 5 | mod audio_stream_base; 6 | mod audio_stream_builder; 7 | mod audio_stream_callback; 8 | mod definitions; 9 | mod private; 10 | mod type_guide; 11 | mod version; 12 | 13 | #[cfg(feature = "java-interface")] 14 | mod java_interface; 15 | 16 | pub use self::audio_stream::*; 17 | pub use self::audio_stream_base::*; 18 | pub use self::audio_stream_builder::*; 19 | pub use self::audio_stream_callback::*; 20 | pub use self::definitions::*; 21 | pub(crate) use self::private::*; 22 | pub use self::type_guide::*; 23 | pub use self::version::*; 24 | 25 | #[cfg(feature = "java-interface")] 26 | pub use self::java_interface::*; 27 | -------------------------------------------------------------------------------- /src/private.rs: -------------------------------------------------------------------------------- 1 | use oboe_sys as ffi; 2 | 3 | pub trait RawAudioStreamBase { 4 | fn _raw_base(&self) -> &ffi::oboe_AudioStreamBase; 5 | fn _raw_base_mut(&mut self) -> &mut ffi::oboe_AudioStreamBase; 6 | } 7 | 8 | pub trait RawAudioStream { 9 | fn _raw_stream(&self) -> &ffi::oboe_AudioStream; 10 | fn _raw_stream_mut(&mut self) -> &mut ffi::oboe_AudioStream; 11 | } 12 | 13 | /// The raw marker for input stream 14 | pub trait RawAudioInputStream {} 15 | 16 | /// The raw marker for output stream 17 | pub trait RawAudioOutputStream {} 18 | -------------------------------------------------------------------------------- /src/type_guide.rs: -------------------------------------------------------------------------------- 1 | use super::{AudioFormat, ChannelCount, Direction}; 2 | 3 | /** 4 | * Unspecified marker type for use everywhere 5 | */ 6 | pub struct Unspecified; 7 | 8 | /** 9 | * The trait for direction marker types 10 | */ 11 | pub trait IsDirection { 12 | const DIRECTION: Direction; 13 | } 14 | 15 | /** 16 | * The input direction marker 17 | */ 18 | pub struct Input; 19 | 20 | impl IsDirection for Input { 21 | const DIRECTION: Direction = Direction::Input; 22 | } 23 | 24 | /** 25 | * The output direction marker 26 | */ 27 | pub struct Output; 28 | 29 | impl IsDirection for Output { 30 | const DIRECTION: Direction = Direction::Output; 31 | } 32 | 33 | /** 34 | * The traint for format marker types 35 | */ 36 | pub trait IsFormat { 37 | const FORMAT: AudioFormat; 38 | } 39 | 40 | impl IsFormat for Unspecified { 41 | const FORMAT: AudioFormat = AudioFormat::Unspecified; 42 | } 43 | 44 | impl IsFormat for i16 { 45 | const FORMAT: AudioFormat = AudioFormat::I16; 46 | } 47 | 48 | impl IsFormat for i32 { 49 | const FORMAT: AudioFormat = AudioFormat::I32; 50 | } 51 | 52 | impl IsFormat for f32 { 53 | const FORMAT: AudioFormat = AudioFormat::F32; 54 | } 55 | 56 | /** 57 | * The trait for channel count marker types 58 | */ 59 | pub trait IsChannelCount { 60 | const CHANNEL_COUNT: ChannelCount; 61 | } 62 | 63 | impl IsChannelCount for Unspecified { 64 | const CHANNEL_COUNT: ChannelCount = ChannelCount::Unspecified; 65 | } 66 | 67 | /** 68 | * The single mono channel configuration marker 69 | */ 70 | pub struct Mono; 71 | 72 | impl IsChannelCount for Mono { 73 | const CHANNEL_COUNT: ChannelCount = ChannelCount::Mono; 74 | } 75 | 76 | /** 77 | * The dual stereo channels configuration marker 78 | */ 79 | pub struct Stereo; 80 | 81 | impl IsChannelCount for Stereo { 82 | const CHANNEL_COUNT: ChannelCount = ChannelCount::Stereo; 83 | } 84 | 85 | pub enum AltFrame { 86 | Mono(T), 87 | Stereo(T, T), 88 | } 89 | 90 | /** 91 | * The trait for frame type marker types 92 | */ 93 | pub trait IsFrameType { 94 | type Type; 95 | type Format: IsFormat; 96 | type ChannelCount: IsChannelCount; 97 | } 98 | 99 | impl IsFrameType for (T, Unspecified) { 100 | type Type = AltFrame; 101 | type Format = T; 102 | type ChannelCount = Unspecified; 103 | } 104 | 105 | impl IsFrameType for (T, Mono) { 106 | type Type = T; 107 | type Format = T; 108 | type ChannelCount = Mono; 109 | } 110 | 111 | impl IsFrameType for (T, Stereo) { 112 | type Type = (T, T); 113 | type Format = T; 114 | type ChannelCount = Stereo; 115 | } 116 | -------------------------------------------------------------------------------- /src/version.rs: -------------------------------------------------------------------------------- 1 | use oboe_sys as ffi; 2 | use std::str::from_utf8_unchecked; 3 | 4 | /** 5 | * The version info 6 | */ 7 | pub struct Version; 8 | 9 | impl Version { 10 | /** 11 | * The major version number 12 | */ 13 | pub const MAJOR: u8 = ffi::oboe_Version_Major; 14 | 15 | /** 16 | * The minor version number 17 | */ 18 | pub const MINOR: u8 = ffi::oboe_Version_Minor; 19 | 20 | /** 21 | * The patch version number 22 | */ 23 | pub const PATCH: u16 = ffi::oboe_Version_Patch; 24 | 25 | /** 26 | * The version as 32-bit number 27 | */ 28 | pub const NUMBER: u32 = ffi::oboe_Version_Number; 29 | 30 | /** 31 | * The version as text 32 | */ 33 | pub fn text() -> &'static str { 34 | unsafe { from_utf8_unchecked(ffi::oboe_Version_Text.as_ref()) } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oboe-sys" 3 | version.workspace = true 4 | description = "Unsafe bindings for oboe an android library for low latency audio IO" 5 | categories = ["external-ffi-bindings", "multimedia::audio"] 6 | keywords.workspace = true 7 | authors.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | repository.workspace = true 11 | homepage.workspace = true 12 | edition.workspace = true 13 | include = ["/build.rs", "/src/*.rs", "/oboe/include", "/oboe/src", "/oboe-ext/include", "/oboe-ext/src", "/README.md"] 14 | 15 | [build-dependencies.fetch_unroll] 16 | workspace = true 17 | optional = true 18 | 19 | [build-dependencies.cc] 20 | workspace = true 21 | features = ["parallel"] 22 | 23 | [build-dependencies.bindgen] 24 | workspace = true 25 | optional = true 26 | 27 | [features] 28 | shared-link = [] 29 | generate-bindings = ["bindgen"] 30 | fetch-prebuilt = ["fetch_unroll"] 31 | shared-stdcxx = [] 32 | test = [] 33 | 34 | [package.metadata.docs.rs] 35 | targets = [ 36 | "aarch64-linux-android", 37 | "armv7-linux-androideabi", 38 | "i686-linux-android", 39 | "x86_64-linux-android", 40 | ] 41 | -------------------------------------------------------------------------------- /sys/Makefile: -------------------------------------------------------------------------------- 1 | TARGETS := armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android 2 | 3 | lib ?= oboe 4 | crate ?= oboe-sys 5 | conf ?= release 6 | link ?= shared 7 | ver ?= 0.1.0 8 | 9 | arch = $(firstword $(subst -, ,$(1))) 10 | outpath = ../target/$(1)/$(conf)/build/$(crate)-*/out/$(2) 11 | out = $(if $(wildcard $(call outpath,$(1),$(2))),$(firstword $(shell ls -t $(call outpath,$(1),$(2))))) 12 | libext = $(if $(filter static,$(link)),a,so) 13 | 14 | define copy-target-rs 15 | copy-out: copy-out-$(1) 16 | copy-out-$(1): 17 | ifneq ($(call out,$(1)),) 18 | $(if $(call out,$(1),bindings.rs),@cp $(call out,$(1),bindings.rs) src/bindings_$(call arch,$(1)).rs) 19 | @mkdir -p lib/$(conf)/$(call arch,$(1)) 20 | $(if $(call out,$(1),build/lib$(lib)-ext.$(libext)),@cp $(call out,$(1),build/lib$(lib)-ext.$(libext)) lib/$(conf)/$(call arch,$(1))/lib$(lib)-ext.$(libext)) 21 | else 22 | @echo No prebuild outputs for target:$(1) and config:$(conf) 23 | endif 24 | show-out: show-out-$(1) 25 | show-out-$(1): 26 | ifneq ($(call out,$(1)),) 27 | $(if $(call out,$(1),bindings.rs),@ls -al $(call out,$(1),bindings.rs)) 28 | $(if $(call out,$(1),build/lib$(lib)-ext.$(libext)),@ls -al $(call out,$(1),build/lib$(lib)-ext.$(libext))) 29 | else 30 | @echo No prebuild outputs for target:$(1) and config:$(conf) 31 | endif 32 | pack-out: copy-out-$(1) 33 | endef 34 | 35 | pack-out: 36 | @mkdir -p ../target/prebuilt 37 | @tar -czf ../target/prebuilt/$(crate)_$(ver)_$(conf).tar.gz -C lib/$(conf) . 38 | 39 | $(foreach target,$(TARGETS),$(eval $(call copy-target-rs,$(target)))) 40 | 41 | doc: 42 | @cargo doc --features rustdoc 43 | -------------------------------------------------------------------------------- /sys/README.md: -------------------------------------------------------------------------------- 1 | # Raw (unsafe) bindings for Oboe library 2 | 3 | [![github](https://img.shields.io/badge/github-katyo/oboe--rs-8da0cb.svg?style=for-the-badge&logo=github)](https://github.com/katyo/oboe-rs) 4 | [![Crates.io Package](https://img.shields.io/crates/v/oboe-sys.svg?style=for-the-badge&color=fc8d62&logo=rust)](https://crates.io/crates/oboe-sys) 5 | [![Docs.rs API Docs](https://img.shields.io/badge/docs.rs-oboe--sys-66c2a5?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K)](https://docs.rs/oboe-sys) 6 | [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-brightgreen.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) 7 | [![CI Status](https://img.shields.io/github/actions/workflow/status/katyo/oboe-rs/rust.yml?branch=master&style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/katyo/oboe-rs/actions?query=workflow%3ARust) 8 | 9 | [Oboe](https://github.com/google/oboe) is a C++ library which makes it easy to build high-performance audio apps on Android. It was created primarily to allow developers to target a simplified API that works across multiple API levels back to API level 16 (Jelly Bean). 10 | 11 | Usually you shouldn't use this crate directly, instead use [oboe](https://crates.io/crates/oboe) crate which provides safe interface. 12 | -------------------------------------------------------------------------------- /sys/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | 4 | use std::{ 5 | env, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | fn main() { 10 | // Skip build on docs.rs and CI 11 | if matches!(env::var("DOCS_RS"), Ok(s) if s == "1") { 12 | return; 13 | } 14 | 15 | let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by cargo.")); 16 | let src_dir = Path::new("oboe"); 17 | let ext_dir = Path::new("oboe-ext"); 18 | 19 | let target = env::var("TARGET").expect("TARGET is set by cargo."); 20 | let profile = env::var("PROFILE").expect("PROFILE is set by cargo."); 21 | 22 | let builder = Builder::new( 23 | "oboe", 24 | env!("CARGO_PKG_VERSION"), 25 | target, 26 | profile, 27 | "https://github.com/katyo/{package}-rs/releases/download/{version}/lib{package}-ext_{target}_{profile}.tar.gz", 28 | out_dir, 29 | src_dir, 30 | ext_dir, 31 | ); 32 | 33 | builder.bindings(); 34 | builder.library(); 35 | 36 | add_libdir(builder.lib_dir); 37 | 38 | /*if cfg!(feature = "shared-stdcxx") { 39 | add_lib("c++_shared", false); 40 | } else { 41 | add_lib("c++_static", false); 42 | }*/ 43 | 44 | add_lib("oboe-ext", !cfg!(feature = "shared-link")); 45 | 46 | add_lib("log", false); 47 | add_lib("OpenSLES", false); 48 | } 49 | 50 | struct Builder { 51 | pub src_dir: PathBuf, 52 | 53 | pub lib_url: String, 54 | pub lib_dir: PathBuf, 55 | 56 | pub ext_dir: PathBuf, 57 | pub bind_file: PathBuf, 58 | 59 | pub target: String, 60 | pub profile: String, 61 | } 62 | 63 | impl Builder { 64 | #[allow(clippy::too_many_arguments)] 65 | pub fn new( 66 | package: impl AsRef, 67 | version: impl AsRef, 68 | 69 | target: impl AsRef, 70 | profile: impl AsRef, 71 | 72 | lib_url: impl AsRef, 73 | 74 | out_dir: impl AsRef, 75 | src_dir: impl AsRef, 76 | ext_dir: impl AsRef, 77 | ) -> Self { 78 | let package = package.as_ref(); 79 | let version = version.as_ref(); 80 | let profile = profile.as_ref(); 81 | let target = target.as_ref(); 82 | 83 | let lib_url = lib_url 84 | .as_ref() 85 | .replace("{package}", package) 86 | .replace("{version}", version) 87 | .replace("{target}", target) 88 | .replace("{profile}", profile); 89 | 90 | let out_dir = out_dir.as_ref(); 91 | let lib_dir = out_dir.join("library"); 92 | 93 | let src_dir = src_dir.as_ref().into(); 94 | let ext_dir = ext_dir.as_ref().into(); 95 | 96 | let bind_file = out_dir.join("bindings.rs"); 97 | 98 | let target = target.into(); 99 | let profile = profile.into(); 100 | 101 | Self { 102 | src_dir, 103 | 104 | lib_url, 105 | lib_dir, 106 | 107 | ext_dir, 108 | bind_file, 109 | 110 | target, 111 | profile, 112 | } 113 | } 114 | 115 | #[cfg(not(feature = "generate-bindings"))] 116 | pub fn bindings(&self) {} 117 | 118 | #[cfg(feature = "generate-bindings")] 119 | pub fn bindings(&self) { 120 | let target_os = 121 | env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS is set by cargo."); 122 | 123 | let target_arch = 124 | env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH is set by cargo."); 125 | 126 | let mut clang_args = Vec::new(); 127 | 128 | if target_os == "android" { 129 | let ndk_target = android_target(&target_arch); 130 | 131 | clang_args.push(format!("--target={}", ndk_target)); 132 | } 133 | 134 | let src_include = self.src_dir.join("include"); 135 | let ext_include = self.ext_dir.join("include"); 136 | 137 | let bindings = bindgen::Builder::default() 138 | .detect_include_paths(true) 139 | .clang_args(&clang_args) 140 | .clang_args(&["-xc++", "-std=c++14"]) 141 | .clang_args(&[ 142 | format!("-I{}", ext_include.display()), 143 | format!("-I{}", src_include.display()), 144 | ]) 145 | .header( 146 | ext_include 147 | .join("oboe") 148 | .join("OboeExt.h") 149 | .display() 150 | .to_string(), 151 | ) 152 | .allowlist_type("oboe::ChannelCount") 153 | .allowlist_type("oboe::AudioStreamBase") 154 | .allowlist_type("oboe::AudioStream") 155 | .allowlist_type("oboe::AudioStreamBuilder") 156 | .allowlist_type("oboe::LatencyTuner") 157 | .allowlist_type("oboe::AudioStreamCallbackWrapper") 158 | .allowlist_type("oboe::StabilizedCallback") 159 | .allowlist_type("oboe::DefaultStreamValues") 160 | .allowlist_type("oboe::Version") 161 | .allowlist_function("oboe::AudioStreamBuilder_.+") 162 | .allowlist_function("oboe::AudioStream_.+") 163 | .allowlist_function("oboe::AudioStreamShared_.*") 164 | .allowlist_function("oboe::AudioStreamCallbackWrapper_.+") 165 | .allowlist_function("oboe::getSdkVersion") 166 | .blocklist_type("std::.*_ptr.*") 167 | .blocklist_type("oboe::ManagedStream") 168 | .blocklist_function("oboe::AudioStreamBuilder_openStream") 169 | .blocklist_function("oboe::AudioStreamBuilder_openStream1") 170 | .blocklist_function("oboe::AudioStreamBuilder_openManagedStream") 171 | .blocklist_function("oboe::AudioStreamBuilder_setPackageName") 172 | .blocklist_function("oboe::AudioStreamBuilder_setAttributionTag") 173 | .opaque_type("std::.*") 174 | .opaque_type("oboe::AudioStream") 175 | .opaque_type("oboe::AudioStreamBuilder") 176 | .opaque_type("oboe::LatencyTuner") 177 | .generate() 178 | .expect("Unable to generate bindings"); 179 | 180 | bindings 181 | .write_to_file(&self.bind_file) 182 | .expect("Couldn't write bindings!"); 183 | } 184 | 185 | #[cfg(feature = "test")] 186 | pub fn library(&self) {} 187 | 188 | #[cfg(all(not(feature = "test"), feature = "fetch-prebuilt"))] 189 | pub fn library(&self) { 190 | if self.lib_dir.is_dir() { 191 | eprintln!( 192 | "Prebuilt library {} already fetched to {}", 193 | self.lib_url, 194 | self.lib_dir.display() 195 | ); 196 | } else { 197 | eprintln!( 198 | "Fetching prebuilt library {} to {}", 199 | self.lib_url, 200 | self.lib_dir.display() 201 | ); 202 | 203 | fetch_unroll::Fetch::from(&self.lib_url) 204 | .unroll() 205 | .to(&self.lib_dir) 206 | .expect("Prebuilt library should be fetched."); 207 | } 208 | } 209 | 210 | #[cfg(all(not(feature = "test"), not(feature = "fetch-prebuilt")))] 211 | pub fn library(&self) { 212 | let src_files = &[ 213 | "aaudio/AAudioLoader.cpp", 214 | "aaudio/AudioStreamAAudio.cpp", 215 | "common/AdpfWrapper.cpp", 216 | "common/AudioSourceCaller.cpp", 217 | "common/AudioStream.cpp", 218 | "common/AudioStreamBuilder.cpp", 219 | "common/DataConversionFlowGraph.cpp", 220 | "common/FilterAudioStream.cpp", 221 | "common/FixedBlockAdapter.cpp", 222 | "common/FixedBlockReader.cpp", 223 | "common/FixedBlockWriter.cpp", 224 | "common/LatencyTuner.cpp", 225 | "common/SourceFloatCaller.cpp", 226 | "common/SourceI16Caller.cpp", 227 | "common/SourceI24Caller.cpp", 228 | "common/SourceI32Caller.cpp", 229 | "common/Utilities.cpp", 230 | "common/QuirksManager.cpp", 231 | "fifo/FifoBuffer.cpp", 232 | "fifo/FifoController.cpp", 233 | "fifo/FifoControllerBase.cpp", 234 | "fifo/FifoControllerIndirect.cpp", 235 | "flowgraph/FlowGraphNode.cpp", 236 | "flowgraph/ChannelCountConverter.cpp", 237 | "flowgraph/ClipToRange.cpp", 238 | "flowgraph/ManyToMultiConverter.cpp", 239 | "flowgraph/MonoToMultiConverter.cpp", 240 | "flowgraph/MultiToMonoConverter.cpp", 241 | "flowgraph/RampLinear.cpp", 242 | "flowgraph/SampleRateConverter.cpp", 243 | "flowgraph/SinkFloat.cpp", 244 | "flowgraph/SinkI16.cpp", 245 | "flowgraph/SinkI24.cpp", 246 | "flowgraph/SinkI32.cpp", 247 | "flowgraph/SourceFloat.cpp", 248 | "flowgraph/SourceI16.cpp", 249 | "flowgraph/SourceI24.cpp", 250 | "flowgraph/SourceI32.cpp", 251 | "flowgraph/resampler/IntegerRatio.cpp", 252 | "flowgraph/resampler/LinearResampler.cpp", 253 | "flowgraph/resampler/MultiChannelResampler.cpp", 254 | "flowgraph/resampler/PolyphaseResampler.cpp", 255 | "flowgraph/resampler/PolyphaseResamplerMono.cpp", 256 | "flowgraph/resampler/PolyphaseResamplerStereo.cpp", 257 | "flowgraph/resampler/SincResampler.cpp", 258 | "flowgraph/resampler/SincResamplerStereo.cpp", 259 | "opensles/AudioInputStreamOpenSLES.cpp", 260 | "opensles/AudioOutputStreamOpenSLES.cpp", 261 | "opensles/AudioStreamBuffered.cpp", 262 | "opensles/AudioStreamOpenSLES.cpp", 263 | "opensles/EngineOpenSLES.cpp", 264 | "opensles/OpenSLESUtilities.cpp", 265 | "opensles/OutputMixerOpenSLES.cpp", 266 | "common/StabilizedCallback.cpp", 267 | "common/Trace.cpp", 268 | "common/Version.cpp", 269 | ]; 270 | 271 | let ext_files = &[ 272 | "AudioStreamWrapper.cpp", 273 | "AudioStreamBuilderWrapper.cpp", 274 | "AudioStreamCallbackWrapper.cpp", 275 | ]; 276 | 277 | if env::var(format!("CXX_{}", self.target)).is_err() { 278 | if let Ok(cc) = env::var(format!("CC_{}", self.target)) { 279 | env::set_var( 280 | format!("CXX_{}", self.target), 281 | cc.replace("clang", "clang++"), 282 | ); 283 | } 284 | } 285 | 286 | let mut library = cc::Build::new(); 287 | 288 | library.cpp(true); 289 | library.cpp_link_stdlib(if cfg!(feature = "shared-stdcxx") { 290 | "c++_shared" 291 | } else { 292 | "c++_static" 293 | }); 294 | for flag in &[ 295 | "-std=c++17", 296 | "-Wall", 297 | "-Wextra-semi", 298 | "-Wshadow", 299 | "-Wshadow-field", 300 | "-fno-rtti", 301 | "-fno-exceptions", 302 | //"-Ofast", 303 | ] { 304 | library.flag(flag); 305 | } 306 | if self.profile == "debug" { 307 | //library.flag("-Werror"); 308 | library.define("OBOE_ENABLE_LOGGING", "1"); 309 | } 310 | 311 | library.static_flag(!cfg!(feature = "shared-link")); 312 | library.shared_flag(cfg!(feature = "shared-link")); 313 | 314 | library.include(self.src_dir.join("include")); 315 | library.include(self.src_dir.join("src")); 316 | library.include(self.ext_dir.join("include")); 317 | library.include(self.ext_dir.join("src")); 318 | 319 | for file in src_files { 320 | library.file(self.src_dir.join("src").join(file)); 321 | } 322 | for file in ext_files { 323 | library.file(self.ext_dir.join("src").join(file)); 324 | } 325 | 326 | library.out_dir(&self.lib_dir); 327 | library.compile("oboe-ext"); 328 | } 329 | } 330 | 331 | fn android_target(target_arch: impl AsRef) -> &'static str { 332 | match target_arch.as_ref() { 333 | "arm" => "armv7a-linux-androideabi", 334 | "aarch64" => "aarch64-linux-android", 335 | "x86" => "i686-linux-android", 336 | "x86_64" => "x86_64-linux-android", 337 | arch => panic!("Unsupported architecture {}", arch), 338 | } 339 | } 340 | 341 | fn rustc_target(target_arch: impl AsRef) -> &'static str { 342 | match target_arch.as_ref() { 343 | "arm" => "armv7", 344 | "aarch64" => "aarch64", 345 | "x86" => "i686", 346 | "x86_64" => "x86_64", 347 | arch => panic!("Unsupported architecture {}", arch), 348 | } 349 | } 350 | 351 | fn add_lib(_name: impl AsRef, _static: bool) { 352 | #[cfg(not(feature = "test"))] 353 | println!( 354 | "cargo:rustc-link-lib={}{}", 355 | if _static { "static=" } else { "" }, 356 | _name.as_ref() 357 | ); 358 | } 359 | 360 | fn add_libdir(_path: impl AsRef) { 361 | #[cfg(not(feature = "test"))] 362 | println!( 363 | "cargo:rustc-link-search=native={}", 364 | _path.as_ref().display() 365 | ); 366 | } 367 | -------------------------------------------------------------------------------- /sys/oboe-ext/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(oboe-ext) 4 | 5 | set(oboe-ext_sources 6 | src/AudioStreamWrapper.cpp 7 | src/AudioStreamBuilderWrapper.cpp 8 | src/AudioStreamCallbackWrapper.cpp 9 | ) 10 | 11 | set(oboe_sources 12 | src/aaudio/AAudioLoader.cpp 13 | src/aaudio/AudioStreamAAudio.cpp 14 | src/common/AudioSourceCaller.cpp 15 | src/common/AudioStream.cpp 16 | src/common/AudioStreamBuilder.cpp 17 | src/common/DataConversionFlowGraph.cpp 18 | src/common/FilterAudioStream.cpp 19 | src/common/FixedBlockAdapter.cpp 20 | src/common/FixedBlockReader.cpp 21 | src/common/FixedBlockWriter.cpp 22 | src/common/LatencyTuner.cpp 23 | src/common/SourceFloatCaller.cpp 24 | src/common/SourceI16Caller.cpp 25 | src/common/Utilities.cpp 26 | src/common/QuirksManager.cpp 27 | src/fifo/FifoBuffer.cpp 28 | src/fifo/FifoController.cpp 29 | src/fifo/FifoControllerBase.cpp 30 | src/fifo/FifoControllerIndirect.cpp 31 | src/flowgraph/FlowGraphNode.cpp 32 | src/flowgraph/ChannelCountConverter.cpp 33 | src/flowgraph/ClipToRange.cpp 34 | src/flowgraph/ManyToMultiConverter.cpp 35 | src/flowgraph/MonoToMultiConverter.cpp 36 | src/flowgraph/MultiToMonoConverter.cpp 37 | src/flowgraph/RampLinear.cpp 38 | src/flowgraph/SampleRateConverter.cpp 39 | src/flowgraph/SinkFloat.cpp 40 | src/flowgraph/SinkI16.cpp 41 | src/flowgraph/SinkI24.cpp 42 | src/flowgraph/SourceFloat.cpp 43 | src/flowgraph/SourceI16.cpp 44 | src/flowgraph/SourceI24.cpp 45 | src/flowgraph/resampler/IntegerRatio.cpp 46 | src/flowgraph/resampler/LinearResampler.cpp 47 | src/flowgraph/resampler/MultiChannelResampler.cpp 48 | src/flowgraph/resampler/PolyphaseResampler.cpp 49 | src/flowgraph/resampler/PolyphaseResamplerMono.cpp 50 | src/flowgraph/resampler/PolyphaseResamplerStereo.cpp 51 | src/flowgraph/resampler/SincResampler.cpp 52 | src/flowgraph/resampler/SincResamplerStereo.cpp 53 | src/opensles/AudioInputStreamOpenSLES.cpp 54 | src/opensles/AudioOutputStreamOpenSLES.cpp 55 | src/opensles/AudioStreamBuffered.cpp 56 | src/opensles/AudioStreamOpenSLES.cpp 57 | src/opensles/EngineOpenSLES.cpp 58 | src/opensles/OpenSLESUtilities.cpp 59 | src/opensles/OutputMixerOpenSLES.cpp 60 | src/common/StabilizedCallback.cpp 61 | src/common/Trace.cpp 62 | src/common/Version.cpp 63 | ) 64 | 65 | list(TRANSFORM oboe_sources PREPEND ${OBOE_DIR}/) 66 | 67 | add_library(oboe-ext ${oboe-ext_sources} ${oboe_sources}) 68 | 69 | target_include_directories(oboe-ext 70 | PRIVATE src ${OBOE_DIR}/src 71 | PUBLIC include ${OBOE_DIR}/include) 72 | 73 | target_compile_options(oboe-ext PRIVATE 74 | -std=c++14 75 | -Wall 76 | -Wextra-semi 77 | -Wshadow 78 | -Wshadow-field 79 | -fno-rtti 80 | -fno-exceptions 81 | -Ofast 82 | "$<$:-Werror>") 83 | 84 | target_compile_definitions(oboe-ext PUBLIC 85 | $<$:OBOE_ENABLE_LOGGING=1>) 86 | -------------------------------------------------------------------------------- /sys/oboe-ext/include/oboe/OboeExt.h: -------------------------------------------------------------------------------- 1 | #ifndef OBOE_EXT_H 2 | #define OBOE_EXT_H 3 | 4 | #include "oboe/Oboe.h" 5 | 6 | namespace oboe { 7 | typedef std::shared_ptr AudioStreamShared; 8 | 9 | typedef void (*DropContextHandler)(void *context); 10 | 11 | typedef DataCallbackResult (*AudioReadyHandler)(void *context, 12 | AudioStream *oboeStream, 13 | void *audioData, 14 | int32_t numFrames); 15 | 16 | typedef void (*ErrorCloseHandler)(void *context, 17 | AudioStream *oboeStream, 18 | Result error); 19 | 20 | class AudioStreamCallbackWrapper 21 | : public AudioStreamDataCallback, public AudioStreamErrorCallback { 22 | public: 23 | AudioStreamCallbackWrapper(void *context, 24 | const DropContextHandler drop_context, 25 | const AudioReadyHandler audio_ready, 26 | const ErrorCloseHandler before_close, 27 | const ErrorCloseHandler after_close); 28 | 29 | ~AudioStreamCallbackWrapper(); 30 | 31 | DataCallbackResult onAudioReady(AudioStream *oboeStream, 32 | void *audioData, 33 | int32_t numFrames); 34 | 35 | void onErrorBeforeClose(AudioStream *oboeStream, 36 | Result error); 37 | 38 | void onErrorAfterClose(AudioStream *oboeStream, 39 | Result error); 40 | 41 | private: 42 | void *_context; 43 | const DropContextHandler _drop_context; 44 | const AudioReadyHandler _audio_ready; 45 | const ErrorCloseHandler _before_close; 46 | const ErrorCloseHandler _after_close; 47 | }; 48 | 49 | void AudioStreamBuilder_create(AudioStreamBuilder *builder); 50 | void AudioStreamBuilder_delete(AudioStreamBuilder *builder); 51 | void AudioStreamBuilder_setCallback(AudioStreamBuilder *builder, 52 | void *context, 53 | const DropContextHandler drop_context, 54 | const AudioReadyHandler audio_ready, 55 | const ErrorCloseHandler before_close, 56 | const ErrorCloseHandler after_close); 57 | 58 | AudioApi AudioStreamBuilder_getAudioApi(const AudioStreamBuilder *builder); 59 | void AudioStreamBuilder_setAudioApi(AudioStreamBuilder *builder, AudioApi api); 60 | AudioStreamBase* AudioStreamBuilder_getBase(AudioStreamBuilder *builder); 61 | 62 | Result AudioStreamBuilder_openStreamShared(AudioStreamBuilder *builder, 63 | AudioStreamShared *sharedStream); 64 | 65 | void AudioStreamShared_clone(const AudioStreamShared *sharedStream, 66 | AudioStreamShared *newSharedStream); 67 | void AudioStreamShared_delete(AudioStreamShared *sharedStream); 68 | AudioStream *AudioStreamShared_deref(AudioStreamShared *sharedStream); 69 | Result AudioStream_open(AudioStream *oboeStream); 70 | Result AudioStream_close(AudioStream *oboeStream); 71 | Result AudioStream_requestStart(AudioStream *oboeStream); 72 | Result AudioStream_requestPause(AudioStream *oboeStream); 73 | Result AudioStream_requestFlush(AudioStream *oboeStream); 74 | Result AudioStream_requestStop(AudioStream *oboeStream); 75 | StreamState AudioStream_getState(AudioStream *oboeStream); 76 | Result AudioStream_waitForStateChange(AudioStream *oboeStream, 77 | StreamState inputState, 78 | StreamState *nextState, 79 | int64_t timeoutNanoseconds); 80 | ResultWithValue 81 | AudioStream_setBufferSizeInFrames(AudioStream *oboeStream, 82 | int32_t requestedFrames); 83 | ResultWithValue 84 | AudioStream_getXRunCount(AudioStream *oboeStream); 85 | bool AudioStream_isXRunCountSupported(const AudioStream *oboeStream); 86 | int32_t AudioStream_getFramesPerBurst(AudioStream *oboeStream); 87 | ResultWithValue 88 | AudioStream_calculateLatencyMillis(AudioStream *oboeStream); 89 | AudioApi AudioStream_getAudioApi(const AudioStream *oboeStream); 90 | ResultWithValue AudioStream_read(AudioStream *oboeStream, 91 | void* buffer, 92 | int32_t numFrames, 93 | int64_t timeoutNanoseconds); 94 | ResultWithValue AudioStream_write(AudioStream *oboeStream, 95 | const void* buffer, 96 | int32_t numFrames, 97 | int64_t timeoutNanoseconds); 98 | 99 | AudioStreamBase* AudioStream_getBase(AudioStream *oboeStream); 100 | } 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /sys/oboe-ext/src/AudioStreamBuilderWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "oboe/OboeExt.h" 2 | 3 | namespace oboe { 4 | /*void AudioStreamBuilder_init(AudioStreamBuilder *builder) { 5 | new (builder) AudioStreamBuilder(); 6 | } 7 | 8 | void AudioStreamBuilder_drop(AudioStreamBuilder *builder) { 9 | builder->~AudioStreamBuilder(); 10 | }*/ 11 | 12 | void AudioStreamBuilder_create(AudioStreamBuilder *builder) { 13 | new(builder) AudioStreamBuilder(); // call constructor on preallocated data buffer 14 | } 15 | 16 | void AudioStreamBuilder_delete(AudioStreamBuilder *builder) { 17 | builder->~AudioStreamBuilder(); // call destructor directly to avoid free data 18 | } 19 | 20 | AudioApi AudioStreamBuilder_getAudioApi(const AudioStreamBuilder *builder) { 21 | return builder->getAudioApi(); 22 | } 23 | 24 | void AudioStreamBuilder_setAudioApi(AudioStreamBuilder *builder, AudioApi api) { 25 | builder->setAudioApi(api); 26 | } 27 | 28 | /// Takes ownership of context (drop_context will be called to free it). 29 | void AudioStreamBuilder_setCallback(AudioStreamBuilder *builder, 30 | void *context, 31 | const DropContextHandler drop_context, 32 | const AudioReadyHandler audio_ready, 33 | const ErrorCloseHandler before_close, 34 | const ErrorCloseHandler after_close) { 35 | auto s = std::make_shared( 36 | context, 37 | drop_context, 38 | audio_ready, 39 | before_close, 40 | after_close); 41 | 42 | builder->setDataCallback(s); 43 | builder->setErrorCallback(s); 44 | } 45 | 46 | AudioStreamBase* AudioStreamBuilder_getBase(AudioStreamBuilder *builder) { 47 | return static_cast(builder); 48 | } 49 | 50 | Result AudioStreamBuilder_openStreamShared(AudioStreamBuilder *builder, 51 | AudioStreamShared *sharedStream) { 52 | new(sharedStream) std::shared_ptr(); // call constructor on preallocated data buffer 53 | return builder->openStream(*sharedStream); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sys/oboe-ext/src/AudioStreamCallbackWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "oboe/OboeExt.h" 2 | 3 | #include 4 | 5 | namespace oboe { 6 | AudioStreamCallbackWrapper:: 7 | AudioStreamCallbackWrapper(void *context, 8 | const DropContextHandler drop_context, 9 | const AudioReadyHandler audio_ready, 10 | const ErrorCloseHandler before_close, 11 | const ErrorCloseHandler after_close): 12 | _context(context), 13 | _drop_context(drop_context), 14 | _audio_ready(audio_ready), 15 | _before_close(before_close), 16 | _after_close(after_close) {} 17 | 18 | AudioStreamCallbackWrapper 19 | ::~AudioStreamCallbackWrapper() { 20 | _drop_context(_context); 21 | } 22 | 23 | DataCallbackResult AudioStreamCallbackWrapper:: 24 | onAudioReady(AudioStream *oboeStream, 25 | void *audioData, 26 | int32_t numFrames) { 27 | return _audio_ready(_context, oboeStream, audioData, numFrames); 28 | } 29 | 30 | void AudioStreamCallbackWrapper:: 31 | onErrorBeforeClose(AudioStream *oboeStream, 32 | Result error) { 33 | _before_close(_context, oboeStream, error); 34 | } 35 | 36 | void AudioStreamCallbackWrapper:: 37 | onErrorAfterClose(AudioStream *oboeStream, 38 | Result error) { 39 | _after_close(_context, oboeStream, error); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sys/oboe-ext/src/AudioStreamWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "oboe/OboeExt.h" 2 | 3 | namespace oboe { 4 | void AudioStreamShared_clone(const AudioStreamShared *sharedStream, 5 | AudioStreamShared *newSharedStream) { 6 | *newSharedStream = *sharedStream; // initialize with copy constructor 7 | } 8 | 9 | void AudioStreamShared_delete(AudioStreamShared *sharedStream) { 10 | sharedStream->~shared_ptr(); // call destructor directly 11 | } 12 | 13 | AudioStream *AudioStreamShared_deref(AudioStreamShared *sharedStream) { 14 | return sharedStream->get(); 15 | } 16 | 17 | Result AudioStream_open(AudioStream *oboeStream) { 18 | return oboeStream->open(); 19 | } 20 | 21 | Result AudioStream_close(AudioStream *oboeStream) { 22 | return oboeStream->close(); 23 | } 24 | 25 | Result AudioStream_requestStart(AudioStream *oboeStream) { 26 | return oboeStream->requestStart(); 27 | } 28 | 29 | Result AudioStream_requestPause(AudioStream *oboeStream) { 30 | return oboeStream->requestPause(); 31 | } 32 | 33 | Result AudioStream_requestFlush(AudioStream *oboeStream) { 34 | return oboeStream->requestFlush(); 35 | } 36 | 37 | Result AudioStream_requestStop(AudioStream *oboeStream) { 38 | return oboeStream->requestStop(); 39 | } 40 | 41 | StreamState AudioStream_getState(AudioStream *oboeStream) { 42 | return oboeStream->getState(); 43 | } 44 | 45 | Result AudioStream_waitForStateChange(AudioStream *oboeStream, 46 | StreamState inputState, 47 | StreamState *nextState, 48 | int64_t timeoutNanoseconds) { 49 | return oboeStream->waitForStateChange(inputState, 50 | nextState, 51 | timeoutNanoseconds); 52 | } 53 | 54 | ResultWithValue 55 | AudioStream_setBufferSizeInFrames(AudioStream *oboeStream, 56 | int32_t requestedFrames) { 57 | return oboeStream->setBufferSizeInFrames(requestedFrames); 58 | } 59 | 60 | ResultWithValue 61 | AudioStream_getXRunCount(AudioStream *oboeStream) { 62 | return oboeStream->getXRunCount(); 63 | } 64 | 65 | bool AudioStream_isXRunCountSupported(const AudioStream *oboeStream) { 66 | return oboeStream->isXRunCountSupported(); 67 | } 68 | 69 | int32_t AudioStream_getFramesPerBurst(AudioStream *oboeStream) { 70 | return oboeStream->getFramesPerBurst(); 71 | } 72 | 73 | ResultWithValue 74 | AudioStream_calculateLatencyMillis(AudioStream *oboeStream) { 75 | return oboeStream->calculateLatencyMillis(); 76 | } 77 | 78 | AudioApi AudioStream_getAudioApi(const AudioStream *oboeStream) { 79 | return oboeStream->getAudioApi(); 80 | } 81 | 82 | ResultWithValue AudioStream_read(AudioStream *oboeStream, 83 | void* buffer, 84 | int32_t numFrames, 85 | int64_t timeoutNanoseconds) { 86 | return oboeStream->read(buffer, numFrames, timeoutNanoseconds); 87 | } 88 | 89 | ResultWithValue AudioStream_write(AudioStream *oboeStream, 90 | const void* buffer, 91 | int32_t numFrames, 92 | int64_t timeoutNanoseconds) { 93 | return oboeStream->write(buffer, numFrames, timeoutNanoseconds); 94 | } 95 | 96 | AudioStreamBase* AudioStream_getBase(AudioStream *oboeStream) { 97 | return static_cast(oboeStream); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow( 3 | non_upper_case_globals, 4 | non_camel_case_types, 5 | non_snake_case, 6 | deref_nullptr, // TODO: Remove after closing https://github.com/rust-lang/rust-bindgen/issues/1651 7 | clippy::redundant_static_lifetimes, // TODO: Remove after that this issue will be fixed in bindgen 8 | clippy::missing_safety_doc 9 | )] 10 | 11 | #[cfg(all(not(target_os = "android"), not(feature = "test")))] 12 | compile_error!("Currently oboe-sys only supports Android platform"); 13 | 14 | #[cfg(feature = "generate-bindings")] 15 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 16 | 17 | #[cfg(all( 18 | not(feature = "generate-bindings"), 19 | any(target_os = "android", test), 20 | target_arch = "arm" 21 | ))] 22 | include!("bindings_armv7.rs"); 23 | 24 | #[cfg(all( 25 | not(feature = "generate-bindings"), 26 | any(target_os = "android", test), 27 | target_arch = "aarch64" 28 | ))] 29 | include!("bindings_aarch64.rs"); 30 | 31 | #[cfg(all( 32 | not(feature = "generate-bindings"), 33 | any(target_os = "android", test), 34 | target_arch = "x86" 35 | ))] 36 | include!("bindings_i686.rs"); 37 | 38 | #[cfg(all( 39 | not(feature = "generate-bindings"), 40 | any(target_os = "android", test), 41 | target_arch = "x86_64" 42 | ))] 43 | include!("bindings_x86_64.rs"); 44 | --------------------------------------------------------------------------------