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