Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/snd/vios_backend/shm_streams.rs
5394 views
1
// Copyright 2020 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
//! Provides an implementation of the audio_streams::shm_streams::ShmStream trait using the VioS
6
//! client.
7
//! Given that the VioS server doesn't emit an event when the next buffer is expected, this
8
//! implementation uses thread::sleep to drive the frame timings.
9
10
use std::fs::File;
11
use std::os::unix::io::FromRawFd;
12
use std::path::Path;
13
use std::sync::Arc;
14
use std::time::Duration;
15
use std::time::Instant;
16
17
use audio_streams::shm_streams::BufferSet;
18
use audio_streams::shm_streams::ServerRequest;
19
use audio_streams::shm_streams::SharedMemory as AudioSharedMemory;
20
use audio_streams::shm_streams::ShmStream;
21
use audio_streams::shm_streams::ShmStreamSource;
22
use audio_streams::BoxError;
23
use audio_streams::SampleFormat;
24
use audio_streams::StreamDirection;
25
use audio_streams::StreamEffect;
26
use base::error;
27
use base::linux::SharedMemoryLinux;
28
use base::Error as SysError;
29
use base::MemoryMapping;
30
use base::MemoryMappingBuilder;
31
use base::RawDescriptor;
32
use base::SharedMemory;
33
use base::VolatileMemory;
34
use sync::Mutex;
35
36
use super::shm_vios::Error;
37
use super::shm_vios::Result;
38
use super::shm_vios::VioSClient;
39
use super::shm_vios::VioSStreamParams;
40
use crate::virtio::snd::common::*;
41
use crate::virtio::snd::constants::*;
42
43
// This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
44
// public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
45
type GenericResult<T> = std::result::Result<T, BoxError>;
46
47
enum StreamState {
48
Available,
49
Acquired,
50
Active,
51
}
52
53
struct StreamDesc {
54
state: Arc<Mutex<StreamState>>,
55
direction: StreamDirection,
56
}
57
58
/// Adapter that provides the ShmStreamSource trait around the VioS backend.
59
pub struct VioSShmStreamSource {
60
vios_client: Arc<Mutex<VioSClient>>,
61
stream_descs: Vec<StreamDesc>,
62
}
63
64
impl VioSShmStreamSource {
65
/// Creates a new stream source given the path to the audio server's socket.
66
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
88
impl 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
140
impl ShmStreamSource<base::Error> for VioSShmStreamSource {
141
/// Creates a new stream
142
#[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
// Attempt to release the stream so that it can be used later. This is a best effort
172
// attempt, so we ignore any error it may return.
173
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
/// Get a list of file descriptors used by the implementation.
180
///
181
/// Returns any open file descriptors needed by the implementation.
182
/// This list helps users of the ShmStreamSource enter Linux jails without
183
/// closing needed file descriptors.
184
fn keep_fds(&self) -> Vec<RawDescriptor> {
185
self.vios_client.lock().keep_rds()
186
}
187
}
188
189
/// Adapter around a VioS stream that implements the ShmStream trait.
190
pub 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
205
impl VioSndShmStream {
206
/// Creates a new shm stream.
207
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
// SAFETY:
221
// Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
222
// file descriptor.
223
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
// SAFETY:
228
// safe because we checked the result of libc::fcntl()
229
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
249
impl 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
/// Waits until the next time a frame should be sent to the server. The server may release the
263
/// previous buffer much sooner than it needs the next one, so this function may sleep to wait
264
/// for the right time.
265
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
283
impl 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
348
impl 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
364
/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
365
/// offset aligned to page size, so the offset within the mapped region is returned along with the
366
/// MemoryMapping struct.
367
fn mmap_buffer(
368
src: &mut SharedMemory,
369
offset: usize,
370
size: usize,
371
) -> Result<(MemoryMapping, usize)> {
372
// If the buffer is not aligned to page size a bigger region needs to be mapped.
373
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
}
385
386