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