Skip to main content

tympan_aspl/
objects.rs

1//! The AudioServerPlugin object tree.
2//!
3//! Core Audio addresses everything a plug-in exposes by
4//! [`AudioObjectId`]. The HAL never sees the driver's Rust types
5//! directly — it walks an object *tree* through the property
6//! protocol: it asks the plug-in object for its device list, asks
7//! each device for its streams, and so on.
8//!
9//! [`ObjectMap`] is that tree, materialised from a driver's
10//! [`DeviceSpec`]. It assigns a stable [`AudioObjectId`] to the
11//! plug-in, the device, and each stream, and answers the two
12//! questions the property dispatcher needs:
13//!
14//! - *what* is this id? — [`ObjectMap::resolve`]
15//! - *what does this id own?* — [`ObjectMap::owned_objects`]
16//!
17//! The map is cross-platform plain data: building it and walking it
18//! needs no FFI, so the object-tree logic is unit-testable on any
19//! host.
20//!
21//! ## Id assignment
22//!
23//! A driver exposes a single device (see [`Driver::device`]), so the
24//! tree is small and its ids are fixed:
25//!
26//! | Id | Object |
27//! |---:|---|
28//! | `1` | the plug-in ([`AudioObjectId::PLUGIN`]) |
29//! | `2` | the device |
30//! | `3`… | the streams, in input-then-output order, present ones only |
31//!
32//! [`Driver`]: crate::Driver
33//! [`Driver::device`]: crate::Driver::device
34
35extern crate alloc;
36
37use alloc::vec::Vec;
38
39use crate::device::DeviceSpec;
40use crate::object::{AudioObjectId, ObjectKind};
41use crate::property::PropertyScope;
42use crate::stream::{StreamDirection, StreamSpec};
43
44/// What an [`AudioObjectId`] resolves to within an [`ObjectMap`].
45#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
46pub enum Object {
47    /// The plug-in object itself ([`AudioObjectId::PLUGIN`]).
48    PlugIn,
49    /// The driver's device.
50    Device,
51    /// One of the device's streams.
52    Stream(StreamDirection),
53}
54
55impl Object {
56    /// The [`ObjectKind`] — and therefore the `kAudio*ClassID` — of
57    /// this object.
58    #[inline]
59    #[must_use]
60    pub const fn kind(self) -> ObjectKind {
61        match self {
62            Self::PlugIn => ObjectKind::PlugIn,
63            Self::Device => ObjectKind::Device,
64            Self::Stream(_) => ObjectKind::Stream,
65        }
66    }
67}
68
69/// The object tree for one driver, materialised from its
70/// [`DeviceSpec`].
71///
72/// Holds the spec plus the [`AudioObjectId`]s assigned to the
73/// device and its streams. Construct it with [`ObjectMap::new`]; the
74/// framework builds one when the HAL first asks the plug-in for its
75/// object tree, and the property dispatcher consults it on every
76/// property call.
77#[derive(Clone, PartialEq, Debug)]
78pub struct ObjectMap {
79    spec: DeviceSpec,
80    device_id: AudioObjectId,
81    input_stream_id: Option<AudioObjectId>,
82    output_stream_id: Option<AudioObjectId>,
83}
84
85impl ObjectMap {
86    /// Build the object tree for `spec`, assigning ids from
87    /// [`AudioObjectId::FIRST_DYNAMIC`] onwards.
88    ///
89    /// The device takes the first id; the streams follow in
90    /// input-then-output order, skipping a direction the device does
91    /// not have.
92    #[must_use]
93    pub fn new(spec: DeviceSpec) -> Self {
94        let mut next = AudioObjectId::FIRST_DYNAMIC.as_u32();
95        let device_id = AudioObjectId::from_u32(next);
96        next += 1;
97
98        let input_stream_id = spec.input().map(|_| {
99            let id = AudioObjectId::from_u32(next);
100            next += 1;
101            id
102        });
103        let output_stream_id = spec.output().map(|_| AudioObjectId::from_u32(next));
104
105        Self {
106            spec,
107            device_id,
108            input_stream_id,
109            output_stream_id,
110        }
111    }
112
113    /// The driver's [`DeviceSpec`].
114    #[inline]
115    #[must_use]
116    pub fn spec(&self) -> &DeviceSpec {
117        &self.spec
118    }
119
120    /// The plug-in object's id — always [`AudioObjectId::PLUGIN`].
121    #[inline]
122    #[must_use]
123    pub const fn plugin_id(&self) -> AudioObjectId {
124        AudioObjectId::PLUGIN
125    }
126
127    /// The device object's id.
128    #[inline]
129    #[must_use]
130    pub const fn device_id(&self) -> AudioObjectId {
131        self.device_id
132    }
133
134    /// The id of the stream carrying `direction`, or `None` if the
135    /// device has no such stream.
136    #[inline]
137    #[must_use]
138    pub const fn stream_id(&self, direction: StreamDirection) -> Option<AudioObjectId> {
139        match direction {
140            StreamDirection::Input => self.input_stream_id,
141            StreamDirection::Output => self.output_stream_id,
142        }
143    }
144
145    /// The [`StreamSpec`] for `direction`, or `None` if the device
146    /// has no such stream.
147    #[inline]
148    #[must_use]
149    pub fn stream_spec(&self, direction: StreamDirection) -> Option<StreamSpec> {
150        self.spec.stream(direction)
151    }
152
153    /// Resolve an [`AudioObjectId`] to the [`Object`] it names, or
154    /// `None` if the id is not part of this tree.
155    #[must_use]
156    pub fn resolve(&self, id: AudioObjectId) -> Option<Object> {
157        if id == AudioObjectId::PLUGIN {
158            return Some(Object::PlugIn);
159        }
160        if id == self.device_id {
161            return Some(Object::Device);
162        }
163        if Some(id) == self.input_stream_id {
164            return Some(Object::Stream(StreamDirection::Input));
165        }
166        if Some(id) == self.output_stream_id {
167            return Some(Object::Stream(StreamDirection::Output));
168        }
169        None
170    }
171
172    /// `true` iff `id` is part of this tree.
173    #[inline]
174    #[must_use]
175    pub fn contains(&self, id: AudioObjectId) -> bool {
176        self.resolve(id).is_some()
177    }
178
179    /// Every [`AudioObjectId`] in the tree, in tree order: plug-in,
180    /// device, then streams.
181    #[must_use]
182    pub fn all_ids(&self) -> Vec<AudioObjectId> {
183        let mut ids = Vec::with_capacity(4);
184        ids.push(self.plugin_id());
185        ids.push(self.device_id);
186        ids.extend(self.input_stream_id);
187        ids.extend(self.output_stream_id);
188        ids
189    }
190
191    /// The objects `id` owns (its children in the tree), filtered to
192    /// `scope`.
193    ///
194    /// - The plug-in owns the device. (The plug-in's scope is always
195    ///   [`PropertyScope::GLOBAL`].)
196    /// - The device owns its streams; [`PropertyScope::INPUT`] /
197    ///   [`PropertyScope::OUTPUT`] narrow the list to that
198    ///   direction, [`PropertyScope::GLOBAL`] returns both.
199    /// - A stream owns nothing.
200    ///
201    /// An id not in the tree, or a scope that does not apply, yields
202    /// an empty list.
203    #[must_use]
204    pub fn owned_objects(&self, id: AudioObjectId, scope: PropertyScope) -> Vec<AudioObjectId> {
205        match self.resolve(id) {
206            Some(Object::PlugIn) => alloc::vec![self.device_id],
207            Some(Object::Device) => self.device_streams(scope),
208            Some(Object::Stream(_)) | None => Vec::new(),
209        }
210    }
211
212    /// The device's stream ids, filtered to `scope`.
213    ///
214    /// [`PropertyScope::GLOBAL`] returns every stream (input first,
215    /// then output); [`PropertyScope::INPUT`] / [`PropertyScope::OUTPUT`]
216    /// return just that direction's stream. Any other scope yields an
217    /// empty list.
218    #[must_use]
219    pub fn device_streams(&self, scope: PropertyScope) -> Vec<AudioObjectId> {
220        let mut ids = Vec::with_capacity(2);
221        let want_input = scope == PropertyScope::GLOBAL || scope == PropertyScope::INPUT;
222        let want_output = scope == PropertyScope::GLOBAL || scope == PropertyScope::OUTPUT;
223        if want_input {
224            ids.extend(self.input_stream_id);
225        }
226        if want_output {
227            ids.extend(self.output_stream_id);
228        }
229        ids
230    }
231
232    /// The id of the object that owns `id` (its parent in the tree):
233    /// the device for a stream, the plug-in for the device,
234    /// [`AudioObjectId::UNKNOWN`] for the plug-in (it has no owner),
235    /// and `None` for an id not in the tree.
236    #[must_use]
237    pub fn owner_of(&self, id: AudioObjectId) -> Option<AudioObjectId> {
238        match self.resolve(id)? {
239            Object::PlugIn => Some(AudioObjectId::UNKNOWN),
240            Object::Device => Some(self.plugin_id()),
241            Object::Stream(_) => Some(self.device_id),
242        }
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::format::StreamFormat;
250
251    fn loopback() -> DeviceSpec {
252        let format = StreamFormat::float32(48_000.0, 2);
253        DeviceSpec::new("com.example.loopback", "Loopback", "tympan-aspl")
254            .with_input(StreamSpec::input(format))
255            .with_output(StreamSpec::output(format))
256    }
257
258    fn output_only() -> DeviceSpec {
259        DeviceSpec::new("com.example.speaker", "Speaker", "tympan-aspl")
260            .with_output(StreamSpec::output(StreamFormat::float32(48_000.0, 2)))
261    }
262
263    #[test]
264    fn loopback_assigns_ids_in_tree_order() {
265        let map = ObjectMap::new(loopback());
266        assert_eq!(map.plugin_id(), AudioObjectId::PLUGIN);
267        assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
268        assert_eq!(
269            map.stream_id(StreamDirection::Input),
270            Some(AudioObjectId::from_u32(3))
271        );
272        assert_eq!(
273            map.stream_id(StreamDirection::Output),
274            Some(AudioObjectId::from_u32(4))
275        );
276    }
277
278    #[test]
279    fn output_only_device_skips_the_input_id() {
280        let map = ObjectMap::new(output_only());
281        assert_eq!(map.device_id(), AudioObjectId::from_u32(2));
282        assert_eq!(map.stream_id(StreamDirection::Input), None);
283        // The output stream takes id 3 — the input id is not
284        // reserved-and-skipped, it is simply not allocated.
285        assert_eq!(
286            map.stream_id(StreamDirection::Output),
287            Some(AudioObjectId::from_u32(3))
288        );
289    }
290
291    #[test]
292    fn streamless_device_has_only_plugin_and_device() {
293        let map = ObjectMap::new(DeviceSpec::new("uid", "name", "maker"));
294        assert_eq!(map.stream_id(StreamDirection::Input), None);
295        assert_eq!(map.stream_id(StreamDirection::Output), None);
296        assert_eq!(
297            map.all_ids(),
298            alloc::vec![AudioObjectId::PLUGIN, AudioObjectId::from_u32(2)]
299        );
300    }
301
302    #[test]
303    fn resolve_maps_ids_back_to_objects() {
304        let map = ObjectMap::new(loopback());
305        assert_eq!(map.resolve(AudioObjectId::PLUGIN), Some(Object::PlugIn));
306        assert_eq!(
307            map.resolve(AudioObjectId::from_u32(2)),
308            Some(Object::Device)
309        );
310        assert_eq!(
311            map.resolve(AudioObjectId::from_u32(3)),
312            Some(Object::Stream(StreamDirection::Input))
313        );
314        assert_eq!(
315            map.resolve(AudioObjectId::from_u32(4)),
316            Some(Object::Stream(StreamDirection::Output))
317        );
318        assert_eq!(map.resolve(AudioObjectId::from_u32(99)), None);
319        assert_eq!(map.resolve(AudioObjectId::UNKNOWN), None);
320    }
321
322    #[test]
323    fn contains_matches_resolve() {
324        let map = ObjectMap::new(loopback());
325        for id in map.all_ids() {
326            assert!(map.contains(id));
327        }
328        assert!(!map.contains(AudioObjectId::from_u32(100)));
329    }
330
331    #[test]
332    fn object_kinds_follow_the_tree() {
333        assert_eq!(Object::PlugIn.kind(), ObjectKind::PlugIn);
334        assert_eq!(Object::Device.kind(), ObjectKind::Device);
335        assert_eq!(
336            Object::Stream(StreamDirection::Input).kind(),
337            ObjectKind::Stream
338        );
339    }
340
341    #[test]
342    fn all_ids_is_tree_ordered() {
343        let map = ObjectMap::new(loopback());
344        assert_eq!(
345            map.all_ids(),
346            alloc::vec![
347                AudioObjectId::PLUGIN,
348                AudioObjectId::from_u32(2),
349                AudioObjectId::from_u32(3),
350                AudioObjectId::from_u32(4),
351            ]
352        );
353    }
354
355    #[test]
356    fn plugin_owns_the_device() {
357        let map = ObjectMap::new(loopback());
358        assert_eq!(
359            map.owned_objects(AudioObjectId::PLUGIN, PropertyScope::GLOBAL),
360            alloc::vec![map.device_id()]
361        );
362    }
363
364    #[test]
365    fn device_owns_streams_filtered_by_scope() {
366        let map = ObjectMap::new(loopback());
367        let dev = map.device_id();
368        assert_eq!(
369            map.owned_objects(dev, PropertyScope::GLOBAL),
370            alloc::vec![AudioObjectId::from_u32(3), AudioObjectId::from_u32(4)]
371        );
372        assert_eq!(
373            map.owned_objects(dev, PropertyScope::INPUT),
374            alloc::vec![AudioObjectId::from_u32(3)]
375        );
376        assert_eq!(
377            map.owned_objects(dev, PropertyScope::OUTPUT),
378            alloc::vec![AudioObjectId::from_u32(4)]
379        );
380    }
381
382    #[test]
383    fn streams_own_nothing() {
384        let map = ObjectMap::new(loopback());
385        assert!(map
386            .owned_objects(AudioObjectId::from_u32(3), PropertyScope::GLOBAL)
387            .is_empty());
388    }
389
390    #[test]
391    fn device_streams_respects_a_one_sided_device() {
392        let map = ObjectMap::new(output_only());
393        assert_eq!(
394            map.device_streams(PropertyScope::GLOBAL),
395            alloc::vec![AudioObjectId::from_u32(3)]
396        );
397        assert!(map.device_streams(PropertyScope::INPUT).is_empty());
398        assert_eq!(
399            map.device_streams(PropertyScope::OUTPUT),
400            alloc::vec![AudioObjectId::from_u32(3)]
401        );
402    }
403
404    #[test]
405    fn owner_walks_up_the_tree() {
406        let map = ObjectMap::new(loopback());
407        assert_eq!(
408            map.owner_of(AudioObjectId::PLUGIN),
409            Some(AudioObjectId::UNKNOWN)
410        );
411        assert_eq!(map.owner_of(map.device_id()), Some(AudioObjectId::PLUGIN));
412        assert_eq!(
413            map.owner_of(AudioObjectId::from_u32(3)),
414            Some(map.device_id())
415        );
416        assert_eq!(map.owner_of(AudioObjectId::from_u32(99)), None);
417    }
418
419    #[test]
420    fn stream_spec_is_forwarded_from_the_device_spec() {
421        let map = ObjectMap::new(loopback());
422        assert_eq!(
423            map.stream_spec(StreamDirection::Input).unwrap().direction(),
424            StreamDirection::Input
425        );
426        assert_eq!(
427            ObjectMap::new(output_only()).stream_spec(StreamDirection::Input),
428            None
429        );
430    }
431}