devices/virtio/snd/vios_backend/
shm_streams.rs1use std::fs::File;
11use std::os::unix::io::FromRawFd;
12use std::path::Path;
13use std::sync::Arc;
14use std::time::Duration;
15use std::time::Instant;
16
17use audio_streams::shm_streams::BufferSet;
18use audio_streams::shm_streams::ServerRequest;
19use audio_streams::shm_streams::SharedMemory as AudioSharedMemory;
20use audio_streams::shm_streams::ShmStream;
21use audio_streams::shm_streams::ShmStreamSource;
22use audio_streams::BoxError;
23use audio_streams::SampleFormat;
24use audio_streams::StreamDirection;
25use audio_streams::StreamEffect;
26use base::error;
27use base::linux::SharedMemoryLinux;
28use base::Error as SysError;
29use base::MemoryMapping;
30use base::MemoryMappingBuilder;
31use base::RawDescriptor;
32use base::SharedMemory;
33use base::VolatileMemory;
34use sync::Mutex;
35
36use super::shm_vios::Error;
37use super::shm_vios::Result;
38use super::shm_vios::VioSClient;
39use super::shm_vios::VioSStreamParams;
40use crate::virtio::snd::common::*;
41use crate::virtio::snd::constants::*;
42
43type GenericResult<T> = std::result::Result<T, BoxError>;
46
47enum StreamState {
48 Available,
49 Acquired,
50 Active,
51}
52
53struct StreamDesc {
54 state: Arc<Mutex<StreamState>>,
55 direction: StreamDirection,
56}
57
58pub struct VioSShmStreamSource {
60 vios_client: Arc<Mutex<VioSClient>>,
61 stream_descs: Vec<StreamDesc>,
62}
63
64impl VioSShmStreamSource {
65 pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
67 let vios_client = Arc::new(Mutex::new(VioSClient::try_new(server)?));
68 let mut stream_descs: Vec<StreamDesc> = Vec::new();
69 let mut idx = 0u32;
70 while let Some(info) = vios_client.lock().stream_info(idx) {
71 stream_descs.push(StreamDesc {
72 state: Arc::new(Mutex::new(StreamState::Active)),
73 direction: if info.direction == VIRTIO_SND_D_OUTPUT {
74 StreamDirection::Playback
75 } else {
76 StreamDirection::Capture
77 },
78 });
79 idx += 1;
80 }
81 Ok(Self {
82 vios_client,
83 stream_descs,
84 })
85 }
86}
87
88impl VioSShmStreamSource {
89 fn new_stream_inner(
90 &mut self,
91 stream_id: u32,
92 direction: StreamDirection,
93 num_channels: usize,
94 format: SampleFormat,
95 frame_rate: u32,
96 buffer_size: usize,
97 _effects: &[StreamEffect],
98 client_shm: &dyn AudioSharedMemory<Error = base::Error>,
99 _buffer_offsets: [u64; 2],
100 ) -> GenericResult<Box<dyn ShmStream>> {
101 let frame_size = num_channels * format.sample_bytes();
102 let period_bytes = (frame_size * buffer_size) as u32;
103 self.vios_client.lock().prepare_stream(stream_id)?;
104 let params = VioSStreamParams {
105 buffer_bytes: 2 * period_bytes,
106 period_bytes,
107 features: 0u32,
108 channels: num_channels as u8,
109 format: from_sample_format(format),
110 rate: virtio_frame_rate(frame_rate)?,
111 };
112 self.vios_client
113 .lock()
114 .set_stream_parameters(stream_id, params)?;
115 self.vios_client.lock().start_stream(stream_id)?;
116 VioSndShmStream::new(
117 buffer_size,
118 num_channels,
119 format,
120 frame_rate,
121 stream_id,
122 direction,
123 self.vios_client.clone(),
124 client_shm,
125 self.stream_descs[stream_id as usize].state.clone(),
126 )
127 }
128
129 fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {
130 self.stream_descs
131 .iter()
132 .position(|s| match &*s.state.lock() {
133 StreamState::Available => s.direction == direction,
134 _ => false,
135 })
136 .map(|idx| idx as u32)
137 }
138}
139
140impl ShmStreamSource<base::Error> for VioSShmStreamSource {
141 #[allow(clippy::too_many_arguments)]
143 fn new_stream(
144 &mut self,
145 direction: StreamDirection,
146 num_channels: usize,
147 format: SampleFormat,
148 frame_rate: u32,
149 buffer_size: usize,
150 effects: &[StreamEffect],
151 client_shm: &dyn AudioSharedMemory<Error = base::Error>,
152 buffer_offsets: [u64; 2],
153 ) -> GenericResult<Box<dyn ShmStream>> {
154 self.vios_client.lock().start_bg_thread()?;
155 let stream_id = self
156 .get_unused_stream_id(direction)
157 .ok_or(Box::new(Error::NoStreamsAvailable))?;
158 let stream = self
159 .new_stream_inner(
160 stream_id,
161 direction,
162 num_channels,
163 format,
164 frame_rate,
165 buffer_size,
166 effects,
167 client_shm,
168 buffer_offsets,
169 )
170 .inspect_err(|_e| {
171 let _ = self.vios_client.lock().release_stream(stream_id);
174 })?;
175 *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
176 Ok(stream)
177 }
178
179 fn keep_fds(&self) -> Vec<RawDescriptor> {
185 self.vios_client.lock().keep_rds()
186 }
187}
188
189pub struct VioSndShmStream {
191 num_channels: usize,
192 frame_rate: u32,
193 buffer_size: usize,
194 frame_size: usize,
195 interval: Duration,
196 next_frame: Duration,
197 start_time: Instant,
198 stream_id: u32,
199 direction: StreamDirection,
200 vios_client: Arc<Mutex<VioSClient>>,
201 client_shm: SharedMemory,
202 state: Arc<Mutex<StreamState>>,
203}
204
205impl VioSndShmStream {
206 fn new(
208 buffer_size: usize,
209 num_channels: usize,
210 format: SampleFormat,
211 frame_rate: u32,
212 stream_id: u32,
213 direction: StreamDirection,
214 vios_client: Arc<Mutex<VioSClient>>,
215 client_shm: &dyn AudioSharedMemory<Error = base::Error>,
216 state: Arc<Mutex<StreamState>>,
217 ) -> GenericResult<Box<dyn ShmStream>> {
218 let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
219
220 let dup_fd = unsafe { libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
224 if dup_fd < 0 {
225 return Err(Box::new(Error::DupError(SysError::last())));
226 }
227 let file = unsafe { File::from_raw_fd(dup_fd) };
230 let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;
231
232 Ok(Box::new(Self {
233 num_channels,
234 frame_rate,
235 buffer_size,
236 frame_size: format.sample_bytes() * num_channels,
237 interval,
238 next_frame: interval,
239 start_time: Instant::now(),
240 stream_id,
241 direction,
242 vios_client,
243 client_shm: client_shm_clone,
244 state,
245 }))
246 }
247}
248
249impl ShmStream for VioSndShmStream {
250 fn frame_size(&self) -> usize {
251 self.frame_size
252 }
253
254 fn num_channels(&self) -> usize {
255 self.num_channels
256 }
257
258 fn frame_rate(&self) -> u32 {
259 self.frame_rate
260 }
261
262 fn wait_for_next_action_with_timeout(
266 &mut self,
267 timeout: Duration,
268 ) -> GenericResult<Option<ServerRequest>> {
269 let elapsed = self.start_time.elapsed();
270 if elapsed < self.next_frame {
271 if timeout < self.next_frame - elapsed {
272 std::thread::sleep(timeout);
273 return Ok(None);
274 } else {
275 std::thread::sleep(self.next_frame - elapsed);
276 }
277 }
278 self.next_frame += self.interval;
279 Ok(Some(ServerRequest::new(self.buffer_size, self)))
280 }
281}
282
283impl BufferSet for VioSndShmStream {
284 fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
285 match self.direction {
286 StreamDirection::Playback => {
287 let requested_size = frames * self.frame_size;
288 let shm_ref = &mut self.client_shm;
289 let (_, res) = self.vios_client.lock().inject_audio_data::<Result<()>, _>(
290 self.stream_id,
291 requested_size,
292 |slice| {
293 if requested_size != slice.size() {
294 error!(
295 "Buffer size is different than the requested size: {} vs {}",
296 requested_size,
297 slice.size()
298 );
299 }
300 let size = std::cmp::min(requested_size, slice.size());
301 let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
302 let src_slice = src_mmap
303 .get_slice(mmap_offset, size)
304 .map_err(Error::VolatileMemoryError)?;
305 src_slice.copy_to_volatile_slice(slice);
306 Ok(())
307 },
308 )?;
309 res?;
310 }
311 StreamDirection::Capture => {
312 let requested_size = frames * self.frame_size;
313 let shm_ref = &mut self.client_shm;
314 let (_, res) = self
315 .vios_client
316 .lock()
317 .request_audio_data::<Result<()>, _>(
318 self.stream_id,
319 requested_size,
320 |slice| {
321 if requested_size != slice.size() {
322 error!(
323 "Buffer size is different than the requested size: {} vs {}",
324 requested_size,
325 slice.size()
326 );
327 }
328 let size = std::cmp::min(requested_size, slice.size());
329 let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
330 let dst_slice = dst_mmap
331 .get_slice(mmap_offset, size)
332 .map_err(Error::VolatileMemoryError)?;
333 slice.copy_to_volatile_slice(dst_slice);
334 Ok(())
335 },
336 )?;
337 res?;
338 }
339 }
340 Ok(())
341 }
342
343 fn ignore(&mut self) -> GenericResult<()> {
344 Ok(())
345 }
346}
347
348impl Drop for VioSndShmStream {
349 fn drop(&mut self) {
350 let stream_id = self.stream_id;
351 {
352 let vios_client = self.vios_client.lock();
353 if let Err(e) = vios_client
354 .stop_stream(stream_id)
355 .and_then(|_| vios_client.release_stream(stream_id))
356 {
357 error!("Failed to stop and release stream {}: {}", stream_id, e);
358 }
359 }
360 *self.state.lock() = StreamState::Available;
361 }
362}
363
364fn mmap_buffer(
368 src: &mut SharedMemory,
369 offset: usize,
370 size: usize,
371) -> Result<(MemoryMapping, usize)> {
372 let aligned_offset = offset & !(base::pagesize() - 1);
374 let offset_from_mapping_start = offset - aligned_offset;
375 let extended_size = size + offset_from_mapping_start;
376
377 let mmap = MemoryMappingBuilder::new(extended_size)
378 .offset(aligned_offset as u64)
379 .from_shared_memory(src)
380 .build()
381 .map_err(Error::GuestMmapError)?;
382
383 Ok((mmap, offset_from_mapping_start))
384}