Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
74c03f5
Update cpal requirement from 0.15 to 0.16
dependabot[bot] Jul 7, 2025
3808b5c
Bumps rodio to 0.21
mnmaita Jul 28, 2025
1d57e31
Removes coreaudio-sys patch requirement
mnmaita Jul 28, 2025
eb5d399
Updates Android docs
mnmaita Jul 28, 2025
e8eb10d
Updates doc comment pointing to older version
mnmaita Jul 28, 2025
df34ab2
Bumps referenced bevy version in examples doc template
mnmaita Jul 28, 2025
3ee25dc
Reworks audio feature flags
mnmaita Jul 28, 2025
a3b770e
Migrates code for rodio 0.21 compatibility
mnmaita Jul 28, 2025
3241026
Reworks features to make symphonia the default
mnmaita Jul 29, 2025
ddb4654
Fixes internal feature flags
mnmaita Jul 29, 2025
4591533
Fixes feature gated extensions
mnmaita Jul 29, 2025
b2cae1e
Updates AudioLoader docs
mnmaita Jul 29, 2025
7f556de
Temporarily makes lewton the default ogg backend
mnmaita Oct 22, 2025
4c00de2
Fixes disable-audio.patch
mnmaita Oct 22, 2025
298d57b
Fixes licence exceptions for symphonia
mnmaita Oct 27, 2025
52f543c
Removes minimp3 usage and fixes fallback features
mnmaita Oct 29, 2025
2071696
Updates cargo_features.md
mnmaita Nov 23, 2025
ac8758b
Removes obsolete android_shared_stdcxx feature
mnmaita Nov 23, 2025
d8f259e
TEMPORARY: cpal patch to fix Send + Sync issue
mnmaita Nov 23, 2025
9e56f59
Reexports rodio's ChannelCount and SampleRate aliases
mnmaita Dec 11, 2025
b87a126
TEMPORARY: rodio patch for latest cpal compatibility
mnmaita Dec 11, 2025
e4f3850
Fixes decodable example
mnmaita Dec 11, 2025
eb30f93
Enables lewton when using audio-all feature
mnmaita Dec 11, 2025
057e21b
Adds migration guide
mnmaita Dec 11, 2025
bd03421
Fix markdown lints
mnmaita Dec 11, 2025
2da6feb
Updates cpal to 0.17
mnmaita Dec 20, 2025
0180221
Reverts symphonia as default feature
mnmaita Dec 20, 2025
e39cc3f
TEMPORARY: Updates rodio patch revision
mnmaita Feb 10, 2026
d795275
Turns audio-all into a collection + fix feature docs
mnmaita Feb 10, 2026
290a606
Updates renamed rodio types
mnmaita Feb 10, 2026
1239596
Updates disable-audio.patch
mnmaita Feb 10, 2026
20bff64
Updates rodio to 0.22
mnmaita Feb 21, 2026
cfc1577
Apply suggestions from code review
mnmaita Mar 23, 2026
0b1d5ad
Sets minimum Android API version to 26 in validation-jobs.yml
mnmaita Mar 24, 2026
0edc1b1
Updates EXAMPLE_README template
mnmaita Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/validation-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
run: cargo install --force cargo-ndk

- name: Build .so file
run: cargo ndk -t arm64-v8a -o android_example/app/src/main/jniLibs build --package bevy_mobile_example
run: cargo ndk -t arm64-v8a -P 26 -o android_example/app/src/main/jniLibs build --package bevy_mobile_example

- name: Build app for Android
run: cd examples/mobile/android_example && chmod +x gradlew && ./gradlew build
Expand Down
46 changes: 21 additions & 25 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ dev = [
# COLLECTION: Features used to build audio Bevy apps.
audio = ["bevy_audio", "vorbis"]

# COLLECTION: Enables audio features and all supported formats.
audio-all-formats = ["bevy_audio", "aac", "flac", "mp3", "mp4", "vorbis", "wav"]

# COLLECTION: Features used to compose Bevy scenes.
scene = ["bevy_scene"]

Expand All @@ -196,7 +199,6 @@ default_app = [
default_platform = [
"std",
"android-game-activity",
"android_shared_stdcxx",
"bevy_gilrs",
"bevy_winit",
"default_font",
Expand Down Expand Up @@ -497,35 +499,32 @@ zstd_rust = ["bevy_internal/zstd_rust"]
# For KTX2 Zstandard decompression using [zstd](https://crates.io/crates/zstd). This is a faster backend, but uses unsafe C bindings. For the safe option, stick to the default backend with "zstd_rust".
zstd_c = ["bevy_internal/zstd_c"]

# FLAC audio format support
flac = ["bevy_internal/flac"]

# MP3 audio format support
mp3 = ["bevy_internal/mp3"]
# FLAC audio format support (through `symphonia`)
symphonia-flac = ["bevy_internal/symphonia-flac"]

# OGG/VORBIS audio format support
vorbis = ["bevy_internal/vorbis"]
# OGG/VORBIS audio format support (through `symphonia`)
symphonia-vorbis = ["bevy_internal/symphonia-vorbis"]

# WAV audio format support
wav = ["bevy_internal/wav"]
# WAV audio format support (through `symphonia`)
symphonia-wav = ["bevy_internal/symphonia-wav"]

# AAC audio format support (through symphonia)
symphonia-aac = ["bevy_internal/symphonia-aac"]
# AAC audio format support (through `symphonia`)
aac = ["bevy_internal/aac"]

# AAC, FLAC, MP3, MP4, OGG/VORBIS, and WAV audio formats support (through symphonia)
symphonia-all = ["bevy_internal/symphonia-all"]
# FLAC audio format support (through `claxon`)
flac = ["bevy_internal/flac"]

# FLAC audio format support (through symphonia)
symphonia-flac = ["bevy_internal/symphonia-flac"]
# MP4 audio format support (through `symphonia`). It also enables AAC support.
mp4 = ["bevy_internal/mp4"]

# MP4 audio format support (through symphonia)
symphonia-isomp4 = ["bevy_internal/symphonia-isomp4"]
# MP3 audio format support (through `symphonia`)
mp3 = ["bevy_internal/mp3"]

# OGG/VORBIS audio format support (through symphonia)
symphonia-vorbis = ["bevy_internal/symphonia-vorbis"]
# OGG/VORBIS audio format support (through `lewton`)
vorbis = ["bevy_internal/vorbis"]

# WAV audio format support (through symphonia)
symphonia-wav = ["bevy_internal/symphonia-wav"]
# WAV audio format support (through `hound`)
wav = ["bevy_internal/wav"]

# Enable serialization support through serde
serialize = ["bevy_internal/serialize"]
Expand Down Expand Up @@ -560,9 +559,6 @@ morph = ["bevy_internal/morph"]
# Enables bevy_mesh and bevy_animation morph weight support
morph_animation = ["bevy_internal/morph_animation"]

# Enable using a shared stdlib for cxx on Android
android_shared_stdcxx = ["bevy_internal/android_shared_stdcxx"]

# Enable detailed trace event logging. These trace events are expensive even when off, thus they require compile time opt-in
detailed_trace = ["bevy_internal/detailed_trace"]

Expand Down
44 changes: 44 additions & 0 deletions _release-content/migration-guides/rodio_0_22.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: "Rodio 0.22 Update"
pull_requests: [20323]
---

`rodio` was updated to `0.22` and `cpal` to `0.17`. The following sections will guide you through the necessary changes to ensure compatibility.

## Audio Feature Flags

Audio format related features were reworked with this update.

By default, Bevy will enable the `vorbis` feature, which supports OGG/VORBIS files through `lewton`.

If you are not using Bevy's default features, here's a list you can use for reference:

- `vorbis`: OGG/VORBIS audio format support (through `lewton`).
- `wav`: WAV audio format support (through `hound`).
- `mp3`: MP3 audio format support (through `symphonia`).
- `mp4`: MP4 audio format support (through `symphonia`). It also enables AAC support.
- `flac`: FLAC audio format support (through `claxon`).
- `aac`: AAC audio format support (through `symphonia`).

There are also specific `symphonia` backend flags you can use for certain formats instead of the default flags:

- `symphonia-flac`
- `symphonia-vorbis`
- `symphonia-wav`

Notice that OGG/VORBIS support through `symphonia` is currently subject to issues with buffering, reverb, looping and spatial audio. Check the following issues/PRs for additional context:

- <https://github.com/RustAudio/rodio/issues/775>
- <https://github.com/RustAudio/rodio/pull/786>

The `audio-all-formats` feature collection was added for convenience. It will enable `bevy_audio` and all the available audio formats through their default backends.

## Audio Traits

`type DecoderItem` was removed from the `Decodable` trait. Now `rodio::Sample` is an alias for `f32`.

## Android Related Features

The `android_shared_stdcxx` feature was removed, as `cpal`'s `oboe-shared-stdcxx` feature was also removed in favor of Android NDK audio APIs.

Keep in mind that if you are using `bevy_audio` the minimum supported Android API version is now 26 (Android 8/Oreo).
32 changes: 12 additions & 20 deletions crates/bevy_audio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,17 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.19.0-dev" }

# other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
rodio = { version = "0.20", default-features = false }
rodio = { version = "0.22", default-features = false, features = ["playback"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }

[target.'cfg(target_os = "android")'.dependencies]
cpal = { version = "0.15", optional = true }

[target.'cfg(target_vendor = "apple")'.dependencies]
# NOTE: Explicitly depend on this patch version to fix:
# https://github.com/bevyengine/bevy/issues/18893
coreaudio-sys = { version = "0.2.17", default-features = false }
cpal = { version = "0.17", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
rodio = { version = "0.20", default-features = false, features = [
rodio = { version = "0.22", default-features = false, features = [
"wasm-bindgen",
"playback",
] }
bevy_app = { path = "../bevy_app", version = "0.19.0-dev", default-features = false, features = [
"web",
Expand All @@ -43,18 +38,15 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.19.0-dev", default-featu
] }

[features]
aac = ["rodio/symphonia-aac"]
flac = ["rodio/claxon"]
mp3 = ["rodio/mp3"]
flac = ["rodio/flac"]
wav = ["rodio/wav"]
vorbis = ["rodio/vorbis"]
symphonia-aac = ["rodio/symphonia-aac"]
symphonia-all = ["rodio/symphonia-all"]
symphonia-flac = ["rodio/symphonia-flac"]
symphonia-isomp4 = ["rodio/symphonia-isomp4"]
symphonia-vorbis = ["rodio/symphonia-vorbis"]
symphonia-wav = ["rodio/symphonia-wav"]
# Enable using a shared stdlib for cxx on Android.
android_shared_stdcxx = ["cpal/oboe-shared-stdcxx"]
mp4 = ["rodio/mp4"]
vorbis = ["rodio/lewton"]
wav = ["rodio/hound"]
symphonia-flac = ["rodio/flac"]
symphonia-vorbis = ["rodio/vorbis"]
symphonia-wav = ["rodio/wav"]

[lints]
workspace = true
Expand Down
61 changes: 16 additions & 45 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,25 @@ use bevy_asset::{Asset, Assets};
use bevy_ecs::{prelude::*, system::SystemParam};
use bevy_math::Vec3;
use bevy_transform::prelude::GlobalTransform;
use rodio::{OutputStream, OutputStreamHandle, Sink, Source, SpatialSink};
use rodio::{DeviceSinkBuilder, MixerDeviceSink, Player, Source, SpatialPlayer};
use tracing::warn;

use crate::{AudioSink, AudioSinkPlayback};

/// Used internally to play audio on the current "audio device"
///
/// ## Note
///
/// Initializing this resource will leak [`OutputStream`]
/// using [`std::mem::forget`].
/// This is done to avoid storing this in the struct (and making this `!Send`)
/// while preventing it from dropping (to avoid halting of audio).
///
/// This is fine when initializing this once (as is default when adding this plugin),
/// since the memory cost will be the same.
/// However, repeatedly inserting this resource into the app will **leak more memory**.
#[derive(Resource)]
pub(crate) struct AudioOutput {
stream_handle: Option<OutputStreamHandle>,
stream: Option<MixerDeviceSink>,
}

impl Default for AudioOutput {
fn default() -> Self {
if let Ok((stream, stream_handle)) = OutputStream::try_default() {
// We leak `OutputStream` to prevent the audio from stopping.
core::mem::forget(stream);
Self {
stream_handle: Some(stream_handle),
}
} else {
warn!("No audio device found.");
Self {
stream_handle: None,
}
}
let stream = DeviceSinkBuilder::open_default_sink()
.inspect_err(|_err| {
warn!("No audio device found.");
})
.ok();
Self { stream }
}
}

Expand Down Expand Up @@ -111,12 +94,13 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
default_spatial_scale: Res<DefaultSpatialScale>,
mut commands: Commands,
) where
f32: rodio::cpal::FromSample<Source::DecoderItem>,
f32: rodio::cpal::FromSample<rodio::Sample>,
{
let Some(stream_handle) = audio_output.stream_handle.as_ref() else {
let Some(stream) = audio_output.stream.as_ref() else {
// audio output unavailable; cannot play sound
return;
};
let mixer = stream.mixer();

for (entity, source_handle, settings, maybe_emitter_transform) in &query_nonplaying {
let Some(audio_source) = audio_sources.get(&source_handle.0) else {
Expand Down Expand Up @@ -144,18 +128,12 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
Vec3::ZERO.into()
};

let sink = match SpatialSink::try_new(
stream_handle,
let sink = SpatialPlayer::connect_new(
mixer,
emitter_translation,
(left_ear * scale).into(),
(right_ear * scale).into(),
) {
Ok(sink) => sink,
Err(err) => {
warn!("Error creating spatial sink: {err:?}");
continue;
}
};
);

let decoder = audio_source.decoder();

Expand Down Expand Up @@ -226,14 +204,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
.insert((sink, PlaybackRemoveMarker)),
};
} else {
let sink = match Sink::try_new(stream_handle) {
Ok(sink) => sink,
Err(err) => {
warn!("Error creating sink: {err:?}");
continue;
}
};

let sink = Player::connect_new(mixer);
let decoder = audio_source.decoder();

match settings.mode {
Expand Down Expand Up @@ -359,7 +330,7 @@ pub(crate) fn cleanup_finished_audio<T: Decodable + Asset>(

/// Run Condition to only play audio if the audio output is available
pub(crate) fn audio_output_available(audio_output: Res<AudioOutput>) -> bool {
audio_output.stream_handle.is_some()
audio_output.stream.is_some()
}

/// Updates spatial audio sinks when emitter positions change.
Expand Down
29 changes: 12 additions & 17 deletions crates/bevy_audio/src/audio_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ impl AsRef<[u8]> for AudioSource {
///
/// This asset loader supports different audio formats based on the enable Bevy features.
/// The feature `bevy/vorbis` enables loading from `.ogg` files and is enabled by default.
/// Other file endings can be loaded from with additional features:
/// Other file extensions can be loaded from with additional features:
/// `.mp3` with `bevy/mp3`
/// `.flac` with `bevy/flac`
/// `.wav` with `bevy/wav`
/// `.flac` with `bevy/flac` or `bevy/symphonia-flac`
/// `.wav` with `bevy/wav` or `bevy/symphonia-wav`
/// The `bevy/audio-all-formats` feature collection will enable all supported audio formats.
#[derive(Default, TypePath)]
pub struct AudioLoader;

Expand All @@ -59,15 +60,15 @@ impl AssetLoader for AudioLoader {
&[
#[cfg(feature = "mp3")]
"mp3",
#[cfg(feature = "flac")]
#[cfg(any(feature = "flac", feature = "symphonia-flac"))]
"flac",
#[cfg(feature = "wav")]
#[cfg(any(feature = "wav", feature = "symphonia-wav"))]
"wav",
#[cfg(feature = "vorbis")]
#[cfg(any(feature = "vorbis", feature = "symphonia-vorbis"))]
"oga",
#[cfg(feature = "vorbis")]
#[cfg(any(feature = "vorbis", feature = "symphonia-vorbis"))]
"ogg",
#[cfg(feature = "vorbis")]
#[cfg(any(feature = "vorbis", feature = "symphonia-vorbis"))]
"spx",
]
}
Expand All @@ -80,22 +81,16 @@ impl AssetLoader for AudioLoader {
/// This trait is implemented for [`AudioSource`].
/// Check the example [`decodable`](https://github.com/bevyengine/bevy/blob/latest/examples/audio/decodable.rs) for how to implement this trait on a custom type.
pub trait Decodable: Send + Sync + 'static {
/// The type of the audio samples.
/// Usually a [`u16`], [`i16`] or [`f32`], as those implement [`rodio::Sample`].
/// Other types can implement the [`rodio::Sample`] trait as well.
type DecoderItem: rodio::Sample + Send + Sync;

/// The type of the iterator of the audio samples,
/// which iterates over samples of type [`Self::DecoderItem`].
/// which iterates over samples of type [`rodio::Sample`].
/// Must be a [`rodio::Source`] so that it can provide information on the audio it is iterating over.
type Decoder: rodio::Source + Send + Iterator<Item = Self::DecoderItem>;
type Decoder: rodio::Source + Send + Iterator<Item = rodio::Sample>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rodio::Source has Iterator<Item = rodio::Sample> as a bound as far as I can see, should we drop this redundant bound or should we keep it?


/// Build and return a [`Self::Decoder`] of the implementing type
fn decoder(&self) -> Self::Decoder;
}

impl Decodable for AudioSource {
type DecoderItem = <rodio::Decoder<Cursor<AudioSource>> as Iterator>::Item;
type Decoder = rodio::Decoder<Cursor<AudioSource>>;

fn decoder(&self) -> Self::Decoder {
Expand All @@ -115,5 +110,5 @@ pub trait AddAudioSource {
fn add_audio_source<T>(&mut self) -> &mut Self
where
T: Decodable + Asset,
f32: rodio::cpal::FromSample<T::DecoderItem>;
f32: rodio::cpal::FromSample<rodio::Sample>;
}
4 changes: 2 additions & 2 deletions crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub use audio_source::*;
pub use pitch::*;
pub use volume::*;

pub use rodio::{cpal::Sample as CpalSample, source::Source, Sample};
pub use rodio::{cpal::Sample as CpalSample, source::Source, ChannelCount, Sample, SampleRate};
pub use sinks::*;

use bevy_app::prelude::*;
Expand Down Expand Up @@ -108,7 +108,7 @@ impl AddAudioSource for App {
fn add_audio_source<T>(&mut self) -> &mut Self
where
T: Decodable + Asset,
f32: rodio::cpal::FromSample<T::DecoderItem>,
f32: rodio::cpal::FromSample<Sample>,
{
self.init_asset::<T>().add_systems(
PostUpdate,
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_audio/src/pitch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ impl Pitch {
}

impl Decodable for Pitch {
type DecoderItem = <SineWave as Iterator>::Item;
type Decoder = TakeDuration<SineWave>;

fn decoder(&self) -> Self::Decoder {
Expand Down
Loading