sim_lib_stream_coreaudio/
backend.rs1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_stream_core::{BufferPolicy, StreamMedia};
3use sim_lib_stream_host::{
4 HostBackend, HostBackendCapability, HostBackendInfo, HostClockInfo, HostDeviceInventory,
5 HostDeviceSpec, HostDirection, HostLatencyInfo, HostOpenStream, HostPortSpec, HostStreamConfig,
6 HostStreamConfigRequest,
7};
8
9use crate::{CoreAudioDevice, CoreAudioTiming};
10
11pub fn coreaudio_audio_backend_candidate() -> &'static str {
13 "coreaudio"
14}
15
16pub fn coreaudio_backend_symbol() -> Symbol {
21 Symbol::qualified("stream/host", coreaudio_audio_backend_candidate())
22}
23
24pub fn coreaudio_transport_symbol() -> Symbol {
26 Symbol::qualified("stream/transport", coreaudio_audio_backend_candidate())
27}
28
29pub fn coreaudio_clock_symbol() -> Symbol {
31 Symbol::qualified("clock", "coreaudio")
32}
33
34#[derive(Clone, Debug)]
36pub struct CoreAudioBackend {
37 info: HostBackendInfo,
38 devices: Vec<CoreAudioDevice>,
39}
40
41impl Default for CoreAudioBackend {
42 fn default() -> Self {
43 Self::new(Vec::new())
44 }
45}
46
47impl CoreAudioBackend {
48 pub fn new(devices: Vec<CoreAudioDevice>) -> Self {
53 Self {
54 info: HostBackendInfo::new(
55 coreaudio_backend_symbol(),
56 coreaudio_transport_symbol(),
57 StreamMedia::Pcm,
58 true,
59 )
60 .with_capabilities(capabilities_for(&devices, false)),
61 devices,
62 }
63 }
64
65 pub fn fake() -> Self {
71 let timing = CoreAudioTiming::default_low_latency();
72 let devices = vec![
73 CoreAudioDevice::output(
74 "coreaudio/default-output",
75 "Fake CoreAudio Default Output",
76 2,
77 timing,
78 )
79 .expect("valid fake output")
80 .with_default_output(),
81 CoreAudioDevice::input(
82 "coreaudio/default-input",
83 "Fake CoreAudio Default Input",
84 2,
85 timing,
86 )
87 .expect("valid fake input")
88 .with_default_input(),
89 ];
90 Self {
91 info: HostBackendInfo::new(
92 coreaudio_backend_symbol(),
93 coreaudio_transport_symbol(),
94 StreamMedia::Pcm,
95 false,
96 )
97 .with_capabilities(capabilities_for(&devices, true)),
98 devices,
99 }
100 }
101
102 pub fn list_devices(&self) -> &[CoreAudioDevice] {
104 &self.devices
105 }
106
107 pub fn default_output(&self) -> Option<&CoreAudioDevice> {
109 self.devices.iter().find(|device| device.default_output())
110 }
111
112 pub fn default_input(&self) -> Option<&CoreAudioDevice> {
114 self.devices.iter().find(|device| device.default_input())
115 }
116
117 pub fn open_default_output(&self, capacity: usize) -> Result<HostOpenStream> {
122 let device = self
123 .default_output()
124 .ok_or_else(|| Error::Eval("CoreAudio default output was not found".to_owned()))?;
125 self.open(request(device, HostDirection::Output, capacity)?)
126 }
127
128 pub fn open_default_input(&self, capacity: usize) -> Result<HostOpenStream> {
133 let device = self
134 .default_input()
135 .ok_or_else(|| Error::Eval("CoreAudio default input was not found".to_owned()))?;
136 self.open(request(device, HostDirection::Input, capacity)?)
137 }
138
139 fn require_device(
140 &self,
141 device_id: &Symbol,
142 direction: HostDirection,
143 ) -> Result<&CoreAudioDevice> {
144 let Some(device) = self
145 .devices
146 .iter()
147 .find(|candidate| candidate.id() == device_id)
148 else {
149 return Err(Error::Eval(format!(
150 "CoreAudio device {device_id} was not found"
151 )));
152 };
153 if !device.is_compatible_with(direction) {
154 return Err(Error::TypeMismatch {
155 expected: "CoreAudio device with requested direction",
156 found: "CoreAudio device with another direction",
157 });
158 }
159 Ok(device)
160 }
161}
162
163impl HostBackend for CoreAudioBackend {
164 fn info(&self) -> &HostBackendInfo {
165 &self.info
166 }
167
168 fn enumerate(&self) -> Result<HostDeviceInventory> {
169 let devices = self
170 .devices
171 .iter()
172 .map(|device| {
173 Ok(HostDeviceSpec::new(
174 device.id().clone(),
175 coreaudio_backend_symbol(),
176 StreamMedia::Pcm,
177 device.direction(),
178 coreaudio_clock_symbol(),
179 BufferPolicy::bounded(device.timing().buffer_frames())?,
180 ))
181 })
182 .collect::<Result<Vec<_>>>()?;
183 let ports = self
184 .devices
185 .iter()
186 .map(|device| {
187 HostPortSpec::new(
188 device.port_symbol(),
189 device.id().clone(),
190 coreaudio_backend_symbol(),
191 StreamMedia::Pcm,
192 device.direction(),
193 )
194 })
195 .collect();
196 Ok(HostDeviceInventory::new(coreaudio_backend_symbol())
197 .with_devices(devices)
198 .with_ports(ports))
199 }
200
201 fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
202 if request.backend() != self.info.id() {
203 return Err(Error::Eval(format!(
204 "CoreAudio backend cannot open {} requests",
205 request.backend()
206 )));
207 }
208 if request.media() != StreamMedia::Pcm {
209 return Err(Error::TypeMismatch {
210 expected: "PCM stream request",
211 found: "non-PCM stream request",
212 });
213 }
214 let direction = request.direction();
215 let device = self.require_device(request.device(), direction)?;
216 let config = HostStreamConfig::from_request(
217 request,
218 latency_for(direction, device.timing()),
219 HostClockInfo::new(
220 coreaudio_clock_symbol(),
221 Some(device.timing().sample_rate_hz()),
222 true,
223 ),
224 );
225 Ok(HostOpenStream::new(config))
226 }
227}
228
229fn request(
230 device: &CoreAudioDevice,
231 direction: HostDirection,
232 capacity: usize,
233) -> Result<HostStreamConfigRequest> {
234 Ok(HostStreamConfigRequest::new(
235 coreaudio_backend_symbol(),
236 device.id().clone(),
237 StreamMedia::Pcm,
238 direction,
239 BufferPolicy::bounded(capacity)?,
240 ))
241}
242
243fn capabilities_for(devices: &[CoreAudioDevice], fake: bool) -> Vec<HostBackendCapability> {
244 let mut capabilities = Vec::new();
245 if devices
246 .iter()
247 .any(|device| device.is_compatible_with(HostDirection::Input))
248 {
249 capabilities.push(HostBackendCapability::AudioInput);
250 }
251 if devices
252 .iter()
253 .any(|device| device.is_compatible_with(HostDirection::Output))
254 {
255 capabilities.push(HostBackendCapability::AudioOutput);
256 }
257 if devices
258 .iter()
259 .any(|device| device.direction() == HostDirection::Duplex)
260 {
261 capabilities.push(HostBackendCapability::Duplex);
262 }
263 capabilities.push(HostBackendCapability::Hotplug);
264 capabilities.push(HostBackendCapability::Reconnect);
265 if fake {
266 capabilities.push(HostBackendCapability::Offline);
267 capabilities.push(HostBackendCapability::Fake);
268 }
269 capabilities
270}
271
272fn latency_for(direction: HostDirection, timing: CoreAudioTiming) -> HostLatencyInfo {
273 match direction {
274 HostDirection::Input => HostLatencyInfo::new(timing.input_latency_frames(), 0),
275 HostDirection::Output => HostLatencyInfo::new(0, timing.output_latency_frames()),
276 HostDirection::Duplex => HostLatencyInfo::new(
277 timing.input_latency_frames(),
278 timing.output_latency_frames(),
279 ),
280 }
281}