1use 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::AlsaPcmDevice;
10
11pub fn alsa_backend_symbol() -> Symbol {
13 Symbol::qualified("stream/host", "alsa")
14}
15
16pub fn alsa_transport_symbol() -> Symbol {
18 Symbol::qualified("stream/transport", "alsa")
19}
20
21#[derive(Clone, Debug)]
23pub struct AlsaBackend {
24 info: HostBackendInfo,
25 devices: Vec<AlsaPcmDevice>,
26}
27
28impl Default for AlsaBackend {
29 fn default() -> Self {
30 Self::new(Vec::new())
31 }
32}
33
34impl AlsaBackend {
35 pub fn new(devices: Vec<AlsaPcmDevice>) -> Self {
40 Self {
41 info: HostBackendInfo::new(
42 alsa_backend_symbol(),
43 alsa_transport_symbol(),
44 StreamMedia::Pcm,
45 true,
46 )
47 .with_capabilities(capabilities_for(&devices, true)),
48 devices,
49 }
50 }
51
52 pub fn fake() -> Self {
71 let devices = vec![
72 AlsaPcmDevice::default_playback(2, 48_000)
73 .expect("valid default playback")
74 .with_buffer_frames(128)
75 .expect("valid default playback buffer"),
76 AlsaPcmDevice::default_capture(2, 48_000)
77 .expect("valid default capture")
78 .with_buffer_frames(128)
79 .expect("valid default capture buffer"),
80 AlsaPcmDevice::playback("hw:0,0", "Fake ALSA hw Playback", 2, 48_000)
81 .expect("valid hw playback"),
82 AlsaPcmDevice::capture("plughw:1,0", "Fake ALSA plughw Capture", 1, 48_000)
83 .expect("valid plughw capture"),
84 ];
85 Self {
86 info: HostBackendInfo::new(
87 alsa_backend_symbol(),
88 alsa_transport_symbol(),
89 StreamMedia::Pcm,
90 false,
91 )
92 .with_capabilities(capabilities_for(&devices, false)),
93 devices,
94 }
95 }
96
97 pub fn list_devices(&self) -> &[AlsaPcmDevice] {
99 &self.devices
100 }
101
102 pub fn default_playback(&self) -> Option<&AlsaPcmDevice> {
104 self.devices
105 .iter()
106 .find(|device| device.is_default() && device.is_compatible_with(HostDirection::Output))
107 }
108
109 pub fn default_capture(&self) -> Option<&AlsaPcmDevice> {
111 self.devices
112 .iter()
113 .find(|device| device.is_default() && device.is_compatible_with(HostDirection::Input))
114 }
115
116 pub fn open_default_playback(&self, capacity: usize) -> Result<HostOpenStream> {
121 let device = self
122 .default_playback()
123 .ok_or_else(|| Error::Eval("ALSA default playback PCM was not found".to_owned()))?;
124 self.open(request(device, HostDirection::Output, capacity)?)
125 }
126
127 pub fn open_default_capture(&self, capacity: usize) -> Result<HostOpenStream> {
132 let device = self
133 .default_capture()
134 .ok_or_else(|| Error::Eval("ALSA default capture PCM was not found".to_owned()))?;
135 self.open(request(device, HostDirection::Input, capacity)?)
136 }
137
138 fn require_device(
139 &self,
140 device_id: &Symbol,
141 direction: HostDirection,
142 ) -> Result<&AlsaPcmDevice> {
143 let Some(device) = self
144 .devices
145 .iter()
146 .find(|candidate| candidate.id() == device_id)
147 else {
148 return Err(Error::Eval(format!(
149 "ALSA PCM device {device_id} was not found"
150 )));
151 };
152 if !device.is_compatible_with(direction) {
153 return Err(Error::TypeMismatch {
154 expected: "ALSA PCM device with requested direction",
155 found: "ALSA PCM device with another direction",
156 });
157 }
158 Ok(device)
159 }
160}
161
162impl HostBackend for AlsaBackend {
163 fn info(&self) -> &HostBackendInfo {
164 &self.info
165 }
166
167 fn enumerate(&self) -> Result<HostDeviceInventory> {
168 let devices = self
169 .devices
170 .iter()
171 .map(|device| {
172 Ok(HostDeviceSpec::new(
173 device.id().clone(),
174 alsa_backend_symbol(),
175 StreamMedia::Pcm,
176 device.direction(),
177 Symbol::qualified("clock", "alsa"),
178 BufferPolicy::bounded(device.buffer_frames())?,
179 ))
180 })
181 .collect::<Result<Vec<_>>>()?;
182 let ports = self
183 .devices
184 .iter()
185 .map(|device| {
186 HostPortSpec::new(
187 device.port_symbol(),
188 device.id().clone(),
189 alsa_backend_symbol(),
190 StreamMedia::Pcm,
191 device.direction(),
192 )
193 })
194 .collect();
195 Ok(HostDeviceInventory::new(alsa_backend_symbol())
196 .with_devices(devices)
197 .with_ports(ports))
198 }
199
200 fn open(&self, request: HostStreamConfigRequest) -> Result<HostOpenStream> {
201 if request.backend() != self.info.id() {
202 return Err(Error::Eval(format!(
203 "ALSA backend cannot open {} requests",
204 request.backend()
205 )));
206 }
207 if request.media() != StreamMedia::Pcm {
208 return Err(Error::TypeMismatch {
209 expected: "PCM stream request",
210 found: "non-PCM stream request",
211 });
212 }
213 let direction = request.direction();
214 let device = self.require_device(request.device(), direction)?;
215 let config = HostStreamConfig::from_request(
216 request,
217 latency_for(direction, device.buffer_frames()),
218 HostClockInfo::new(
219 Symbol::qualified("clock", "alsa"),
220 Some(device.sample_rate_hz()),
221 !self.info.hardware_required(),
222 ),
223 );
224 Ok(HostOpenStream::new(config))
225 }
226}
227
228fn request(
229 device: &AlsaPcmDevice,
230 direction: HostDirection,
231 capacity: usize,
232) -> Result<HostStreamConfigRequest> {
233 Ok(HostStreamConfigRequest::new(
234 alsa_backend_symbol(),
235 device.id().clone(),
236 StreamMedia::Pcm,
237 direction,
238 BufferPolicy::bounded(capacity)?,
239 ))
240}
241
242fn capabilities_for(devices: &[AlsaPcmDevice], fake: bool) -> Vec<HostBackendCapability> {
243 let mut capabilities = Vec::new();
244 if devices
245 .iter()
246 .any(|device| device.is_compatible_with(HostDirection::Output))
247 {
248 capabilities.push(HostBackendCapability::AudioOutput);
249 }
250 if devices
251 .iter()
252 .any(|device| device.is_compatible_with(HostDirection::Input))
253 {
254 capabilities.push(HostBackendCapability::AudioInput);
255 }
256 if devices
257 .iter()
258 .any(|device| device.direction() == HostDirection::Duplex)
259 {
260 capabilities.push(HostBackendCapability::Duplex);
261 }
262 capabilities.push(HostBackendCapability::Reconnect);
263 if fake {
264 capabilities.push(HostBackendCapability::Offline);
265 capabilities.push(HostBackendCapability::Fake);
266 }
267 capabilities
268}
269
270fn latency_for(direction: HostDirection, frames: usize) -> HostLatencyInfo {
271 let frames = frames as u32;
272 match direction {
273 HostDirection::Input => HostLatencyInfo::new(frames, 0),
274 HostDirection::Output => HostLatencyInfo::new(0, frames),
275 HostDirection::Duplex => HostLatencyInfo::new(frames, frames),
276 }
277}