Skip to main content

koan_core/audio/
bit_perfect.rs

1//! Bit-perfect output on Android: which of a USB DAC's mixer formats a
2//! stream can pass through untouched, and the app that asks Android for it.
3//!
4//! Android mixes every app's audio at one rate and applies volume, unless an
5//! app sets the USB device's preferred mixer attributes with
6//! `MIXER_BEHAVIOR_BIT_PERFECT` (Android 14). The mixer then hands that app's
7//! stream to the device unchanged, provided the stream matches the attributes
8//! exactly: rate, channels and sample encoding. Those are Java APIs, so the
9//! app answers for them through [`OutputHost`]; what to ask for is decided
10//! here, where it can be tested.
11
12use std::sync::Arc;
13
14use parking_lot::RwLock;
15
16/// A sample encoding a mixer can run in, and an AAudio stream can be opened
17/// with.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Encoding {
20    I16,
21    I24Packed,
22    I32,
23    Float,
24}
25
26impl Encoding {
27    /// The integer bits a sample keeps on its way out in this encoding. The
28    /// engine's samples are floats, which hold a 24-bit integer exactly and
29    /// no more, so a 32-bit integer carries 24 of them.
30    pub fn bits(self) -> u16 {
31        match self {
32            Self::I16 => 16,
33            Self::I24Packed | Self::I32 | Self::Float => 24,
34        }
35    }
36
37    /// Bytes per sample in an AAudio buffer.
38    pub fn bytes(self) -> usize {
39        match self {
40            Self::I16 => 2,
41            Self::I24Packed => 3,
42            Self::I32 | Self::Float => 4,
43        }
44    }
45
46    /// `AAUDIO_FORMAT_*`.
47    pub fn aaudio(self) -> i32 {
48        match self {
49            Self::I16 => 1,
50            Self::Float => 2,
51            Self::I24Packed => 3,
52            Self::I32 => 4,
53        }
54    }
55}
56
57/// One bit-perfect configuration a USB output offers.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct MixerFormat {
60    /// `AudioDeviceInfo.getId()`, which the stream is opened on.
61    pub device_id: i32,
62    pub rate: u32,
63    pub channels: u16,
64    pub encoding: Encoding,
65}
66
67/// The app's side of bit-perfect output: Android's `AudioManager`.
68///
69/// Called on the player thread, before a stream opens.
70pub trait OutputHost: Send + Sync {
71    /// The rate the system mixer runs at: what a stream not passed through is
72    /// resampled to.
73    fn mixer_rate(&self) -> u32;
74    /// The bit-perfect formats of the USB output in use. Empty when the route
75    /// is not USB, the system is older than Android 14, or the person has
76    /// turned bit-perfect output off.
77    fn offers(&self) -> Vec<MixerFormat>;
78    /// Ask Android to pass streams of `format` through untouched; `false` if
79    /// it refused.
80    fn hold(&self, format: MixerFormat) -> bool;
81    /// Give the mixer back to the system.
82    fn release(&self);
83}
84
85static HOST: RwLock<Option<Arc<dyn OutputHost>>> = RwLock::new(None);
86
87/// Install the app's host. Until one is installed, output is mixed.
88pub fn set_host(host: Arc<dyn OutputHost>) {
89    *HOST.write() = Some(host);
90}
91
92pub(crate) fn host() -> Option<Arc<dyn OutputHost>> {
93    HOST.read().clone()
94}
95
96/// The format to hold for a stream at `rate` with `channels`, whose samples
97/// carry `bit_depth` integer bits (`None` for a lossy source, or one a gain or
98/// DSP has processed, which needs 24).
99///
100/// Only an exact rate and channel count will do: anything else is the mixer's
101/// work again. Of the encodings that keep every bit, the widest integer one is
102/// preferred: a 16-bit sample zero-padded to 24 or 32 bits reaches the DAC
103/// unchanged, and the next track of a gapless run, perhaps of more bits, still
104/// fits. A float comes last, leaving the final conversion to the driver.
105/// Nothing above 24 bits survives the engine's floats, so a 32-bit source is
106/// never bit-perfect.
107pub fn choose(
108    offers: &[MixerFormat],
109    rate: u32,
110    channels: u16,
111    bit_depth: Option<u16>,
112) -> Option<MixerFormat> {
113    let needed = bit_depth.unwrap_or(24);
114    if needed > 24 {
115        return None;
116    }
117    let rank = |e: Encoding| match e {
118        Encoding::I32 => 0,
119        Encoding::I24Packed => 1,
120        Encoding::I16 => 2,
121        Encoding::Float => 3,
122    };
123    offers
124        .iter()
125        .filter(|f| f.rate == rate && f.channels == channels && f.encoding.bits() >= needed)
126        .min_by_key(|f| rank(f.encoding))
127        .copied()
128}
129
130/// Write `samples` into an AAudio buffer of `encoding`. Exact for any sample
131/// that came from an integer of at most the encoding's bits: the decoder
132/// scales an n-bit integer by 2^(n-1), which this undoes.
133pub fn encode(samples: &[f32], encoding: Encoding, out: &mut [u8]) {
134    match encoding {
135        Encoding::Float => {
136            for (s, o) in samples.iter().zip(out.as_chunks_mut::<4>().0) {
137                o.copy_from_slice(&s.to_ne_bytes());
138            }
139        }
140        Encoding::I16 => {
141            for (s, o) in samples.iter().zip(out.as_chunks_mut::<2>().0) {
142                o.copy_from_slice(&(scale(*s, 15) as i16).to_ne_bytes());
143            }
144        }
145        Encoding::I24Packed => {
146            for (s, o) in samples.iter().zip(out.as_chunks_mut::<3>().0) {
147                let v = scale(*s, 23) as i32;
148                o.copy_from_slice(&v.to_le_bytes()[..3]);
149            }
150        }
151        Encoding::I32 => {
152            for (s, o) in samples.iter().zip(out.as_chunks_mut::<4>().0) {
153                o.copy_from_slice(&(scale(*s, 31) as i32).to_ne_bytes());
154            }
155        }
156    }
157}
158
159/// `s` as a signed integer of `bits + 1` bits, clamped to its range.
160fn scale(s: f32, bits: u32) -> i64 {
161    let full = (1i64 << bits) as f64;
162    ((s as f64) * full).round().clamp(-full, full - 1.0) as i64
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    fn offer(rate: u32, channels: u16, encoding: Encoding) -> MixerFormat {
170        MixerFormat {
171            device_id: 7,
172            rate,
173            channels,
174            encoding,
175        }
176    }
177
178    #[test]
179    fn nothing_offered_is_the_mixer() {
180        assert_eq!(choose(&[], 44100, 2, Some(16)), None);
181    }
182
183    #[test]
184    fn only_the_source_rate_and_channels_will_do() {
185        let offers = [
186            offer(48000, 2, Encoding::I24Packed),
187            offer(44100, 1, Encoding::I24Packed),
188        ];
189        assert_eq!(choose(&offers, 44100, 2, Some(16)), None);
190    }
191
192    #[test]
193    fn the_widest_integer_encoding_so_a_deeper_track_still_fits() {
194        let all = [
195            offer(44100, 2, Encoding::I16),
196            offer(44100, 2, Encoding::Float),
197            offer(44100, 2, Encoding::I24Packed),
198            offer(44100, 2, Encoding::I32),
199        ];
200        let chosen = |offers: &[MixerFormat]| choose(offers, 44100, 2, Some(16)).unwrap().encoding;
201        assert_eq!(chosen(&all), Encoding::I32);
202        assert_eq!(chosen(&all[..3]), Encoding::I24Packed);
203        assert_eq!(chosen(&all[..1]), Encoding::I16);
204    }
205
206    #[test]
207    fn a_32_bit_source_is_never_bit_perfect() {
208        let offers = [offer(44100, 2, Encoding::I32)];
209        assert_eq!(choose(&offers, 44100, 2, Some(32)), None);
210    }
211
212    #[test]
213    fn sixteen_bits_cannot_carry_twenty_four() {
214        let offers = [offer(96000, 2, Encoding::I16)];
215        assert_eq!(choose(&offers, 96000, 2, Some(24)), None);
216    }
217
218    #[test]
219    fn a_float_mixer_serves_when_no_integer_one_will() {
220        let offers = [
221            offer(96000, 2, Encoding::I16),
222            offer(96000, 2, Encoding::Float),
223        ];
224        assert_eq!(
225            choose(&offers, 96000, 2, Some(24)).unwrap().encoding,
226            Encoding::Float
227        );
228    }
229
230    #[test]
231    fn a_source_without_a_bit_depth_needs_twenty_four() {
232        let offers = [
233            offer(48000, 2, Encoding::I16),
234            offer(48000, 2, Encoding::I24Packed),
235        ];
236        assert_eq!(
237            choose(&offers, 48000, 2, None).unwrap().encoding,
238            Encoding::I24Packed
239        );
240        assert_eq!(choose(&offers[..1], 48000, 2, None), None);
241    }
242
243    #[test]
244    fn encoding_gives_back_the_integers_the_decoder_scaled() {
245        for &n in &[-8_388_608i32, -1, 0, 1, 4_660_803, 8_388_607] {
246            let s = n as f32 / 8_388_608.0;
247            let mut out = [0u8; 3];
248            encode(&[s], Encoding::I24Packed, &mut out);
249            let back = i32::from_le_bytes([
250                out[0],
251                out[1],
252                out[2],
253                if out[2] & 0x80 != 0 { 0xff } else { 0 },
254            ]);
255            assert_eq!(back, n);
256
257            let mut out = [0u8; 4];
258            encode(&[s], Encoding::I32, &mut out);
259            assert_eq!(i32::from_ne_bytes(out), n << 8);
260        }
261        for &n in &[-32768i16, -1, 0, 1, 12345, 32767] {
262            let mut out = [0u8; 2];
263            encode(&[n as f32 / 32768.0], Encoding::I16, &mut out);
264            assert_eq!(i16::from_ne_bytes(out), n);
265        }
266    }
267
268    #[test]
269    fn full_scale_clamps_rather_than_wraps() {
270        let mut out = [0u8; 2];
271        encode(&[1.0], Encoding::I16, &mut out);
272        assert_eq!(i16::from_ne_bytes(out), i16::MAX);
273    }
274}