1 | /*! |
2 | This module bypasses alsa-lib and directly read and write into memory mapped kernel memory. |
3 | In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card. |
4 | |
5 | The reasons for doing this are: |
6 | |
7 | * Minimum overhead where it matters most: let alsa-lib do the code heavy setup - |
8 | then steal its file descriptor and deal with sample streaming from Rust. |
9 | * RT-safety to the maximum extent possible. Creating/dropping any of these structs causes syscalls, |
10 | but function calls on these are just read and write from memory. No syscalls, no memory allocations, |
11 | not even loops (with the exception of `MmapPlayback::write` that loops over samples to write). |
12 | * Possibility to allow Send + Sync for structs |
13 | * It's a fun experiment and an interesting deep dive into how alsa-lib does things. |
14 | |
15 | Note: Not all sound card drivers support this direct method of communication; although almost all |
16 | modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings), |
17 | don't expect it to work with, e g, the PulseAudio plugin or so. |
18 | |
19 | For an example of how to use this mode, look in the "synth-example" directory. |
20 | */ |
21 | |
22 | use libc; |
23 | use std::{mem, ptr, fmt, cmp}; |
24 | use crate::error::{Error, Result}; |
25 | use std::os::unix::io::RawFd; |
26 | use crate::{pcm, PollDescriptors, Direction}; |
27 | use crate::pcm::Frames; |
28 | use std::marker::PhantomData; |
29 | |
30 | use super::ffi::*; |
31 | |
32 | /// Read PCM status via a simple kernel syscall, bypassing alsa-lib. |
33 | /// |
34 | /// If Status is not available on your architecture, this is the second best option. |
35 | pub struct SyncPtrStatus(snd_pcm_mmap_status); |
36 | |
37 | impl SyncPtrStatus { |
38 | /// Executes sync_ptr syscall. |
39 | /// |
40 | /// Unsafe because |
41 | /// - setting appl_ptr and avail_min might make alsa-lib confused |
42 | /// - no check that the fd is really a PCM |
43 | pub unsafe fn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option<pcm::Frames>, avail_min: Option<pcm::Frames>) -> Result<Self> { |
44 | let mut data = snd_pcm_sync_ptr { |
45 | flags: (if hwsync { SNDRV_PCM_SYNC_PTR_HWSYNC } else { 0 }) + |
46 | (if appl_ptr.is_some() { SNDRV_PCM_SYNC_PTR_APPL } else { 0 }) + |
47 | (if avail_min.is_some() { SNDRV_PCM_SYNC_PTR_AVAIL_MIN } else { 0 }), |
48 | c: snd_pcm_mmap_control_r { |
49 | control: snd_pcm_mmap_control { |
50 | appl_ptr: appl_ptr.unwrap_or(0) as snd_pcm_uframes_t, |
51 | avail_min: avail_min.unwrap_or(0) as snd_pcm_uframes_t, |
52 | } |
53 | }, |
54 | s: mem::zeroed() |
55 | }; |
56 | |
57 | sndrv_pcm_ioctl_sync_ptr(fd, &mut data)?; |
58 | |
59 | let i = data.s.status.state; |
60 | if (i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)) { |
61 | Ok(SyncPtrStatus(data.s.status)) |
62 | } else { |
63 | Err(Error::unsupported("SNDRV_PCM_IOCTL_SYNC_PTR returned broken state" )) |
64 | } |
65 | } |
66 | |
67 | pub fn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames } |
68 | pub fn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ } |
69 | pub fn htstamp(&self) -> libc::timespec { self.0.tstamp } |
70 | } |
71 | |
72 | |
73 | |
74 | /// Read PCM status directly from memory, bypassing alsa-lib. |
75 | /// |
76 | /// This means that it's |
77 | /// 1) less overhead for reading status (no syscall, no allocations, no virtual dispatch, just a read from memory) |
78 | /// 2) Send + Sync, and |
79 | /// 3) will only work for "hw" / "plughw" devices (not e g PulseAudio plugins), and not |
80 | /// all of those are supported, although all common ones are (as of 2017, and a kernel from the same decade). |
81 | /// Kernel supported archs are: x86, PowerPC, Alpha. Use "SyncPtrStatus" for other archs. |
82 | /// |
83 | /// The values are updated every now and then by the kernel. Many functions will force an update to happen, |
84 | /// e g `PCM::avail()` and `PCM::delay()`. |
85 | /// |
86 | /// Note: Even if you close the original PCM device, ALSA will not actually close the device until all |
87 | /// Status structs are dropped too. |
88 | /// |
89 | #[derive (Debug)] |
90 | pub struct Status(DriverMemory<snd_pcm_mmap_status>); |
91 | |
92 | fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> { |
93 | let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() }; |
94 | let c: usize = PollDescriptors::fill(self:p, &mut fds)?; |
95 | if c != 1 { |
96 | return Err(Error::unsupported(func:"snd_pcm_poll_descriptors returned wrong number of fds" )) |
97 | } |
98 | Ok(fds[0].fd) |
99 | } |
100 | |
101 | impl Status { |
102 | pub fn new(p: &pcm::PCM) -> Result<Self> { Status::from_fd(pcm_to_fd(p)?) } |
103 | |
104 | pub fn from_fd(fd: RawFd) -> Result<Self> { |
105 | DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_STATUS as libc::off_t, false).map(Status) |
106 | } |
107 | |
108 | /// Current PCM state. |
109 | pub fn state(&self) -> pcm::State { |
110 | unsafe { |
111 | let i = ptr::read_volatile(&(*self.0.ptr).state); |
112 | assert!((i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t))); |
113 | mem::transmute(i as u8) |
114 | } |
115 | } |
116 | |
117 | /// Number of frames hardware has read or written |
118 | /// |
119 | /// This number is updated every now and then by the kernel. |
120 | /// Calling most functions on the PCM will update it, so will usually a period interrupt. |
121 | /// No guarantees given. |
122 | /// |
123 | /// This value wraps at "boundary" (a large value you can read from SwParams). |
124 | pub fn hw_ptr(&self) -> pcm::Frames { |
125 | unsafe { |
126 | ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames |
127 | } |
128 | } |
129 | |
130 | /// Timestamp - fast version of alsa-lib's Status::get_htstamp |
131 | /// |
132 | /// Note: This just reads the actual value in memory. |
133 | /// Unfortunately, the timespec is too big to be read atomically on most archs. |
134 | /// Therefore, this function can potentially give bogus result at times, at least in theory...? |
135 | pub fn htstamp(&self) -> libc::timespec { |
136 | unsafe { |
137 | ptr::read_volatile(&(*self.0.ptr).tstamp) |
138 | } |
139 | } |
140 | |
141 | /// Audio timestamp - fast version of alsa-lib's Status::get_audio_htstamp |
142 | /// |
143 | /// Note: This just reads the actual value in memory. |
144 | /// Unfortunately, the timespec is too big to be read atomically on most archs. |
145 | /// Therefore, this function can potentially give bogus result at times, at least in theory...? |
146 | pub fn audio_htstamp(&self) -> libc::timespec { |
147 | unsafe { |
148 | ptr::read_volatile(&(*self.0.ptr).audio_tstamp) |
149 | } |
150 | } |
151 | } |
152 | |
153 | /// Write PCM appl ptr directly, bypassing alsa-lib. |
154 | /// |
155 | /// Provides direct access to appl ptr and avail min, without the overhead of |
156 | /// alsa-lib or a syscall. Caveats that apply to Status applies to this struct too. |
157 | #[derive (Debug)] |
158 | pub struct Control(DriverMemory<snd_pcm_mmap_control>); |
159 | |
160 | impl Control { |
161 | pub fn new(p: &pcm::PCM) -> Result<Self> { Self::from_fd(pcm_to_fd(p)?) } |
162 | |
163 | pub fn from_fd(fd: RawFd) -> Result<Self> { |
164 | DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_CONTROL as libc::off_t, true).map(Control) |
165 | } |
166 | |
167 | /// Read number of frames application has read or written |
168 | /// |
169 | /// This value wraps at "boundary" (a large value you can read from SwParams). |
170 | pub fn appl_ptr(&self) -> pcm::Frames { |
171 | unsafe { |
172 | ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames |
173 | } |
174 | } |
175 | |
176 | /// Set number of frames application has read or written |
177 | /// |
178 | /// When the kernel wakes up due to a period interrupt, this value will |
179 | /// be checked by the kernel. An XRUN will happen in case the application |
180 | /// has not read or written enough data. |
181 | pub fn set_appl_ptr(&self, value: pcm::Frames) { |
182 | unsafe { |
183 | ptr::write_volatile(&mut (*self.0.ptr).appl_ptr, value as snd_pcm_uframes_t) |
184 | } |
185 | } |
186 | |
187 | /// Read minimum number of frames in buffer in order to wakeup process |
188 | pub fn avail_min(&self) -> pcm::Frames { |
189 | unsafe { |
190 | ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames |
191 | } |
192 | } |
193 | |
194 | /// Write minimum number of frames in buffer in order to wakeup process |
195 | pub fn set_avail_min(&self, value: pcm::Frames) { |
196 | unsafe { |
197 | ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t) |
198 | } |
199 | } |
200 | } |
201 | |
202 | struct DriverMemory<S> { |
203 | ptr: *mut S, |
204 | size: libc::size_t, |
205 | } |
206 | |
207 | impl<S> fmt::Debug for DriverMemory<S> { |
208 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory( {:?})" , self.ptr) } |
209 | } |
210 | |
211 | impl<S> DriverMemory<S> { |
212 | fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> { |
213 | let mut total: usize = count * mem::size_of::<S>(); |
214 | let ps: usize = pagesize(); |
215 | assert!(total > 0); |
216 | if total % ps != 0 { total += ps - total % ps }; |
217 | let flags: i32 = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ }; |
218 | let p: *mut c_void = unsafe { libc::mmap(addr:ptr::null_mut(), len:total, prot:flags, flags:libc::MAP_FILE | libc::MAP_SHARED, fd, offset:offs) }; |
219 | if p.is_null() || p == libc::MAP_FAILED { |
220 | Err(Error::last(func:"mmap (of driver memory)" )) |
221 | } else { |
222 | Ok(DriverMemory { ptr: p as *mut S, size: total }) |
223 | } |
224 | } |
225 | } |
226 | |
227 | unsafe impl<S> Send for DriverMemory<S> {} |
228 | unsafe impl<S> Sync for DriverMemory<S> {} |
229 | |
230 | impl<S> Drop for DriverMemory<S> { |
231 | fn drop(&mut self) { |
232 | unsafe {{ libc::munmap(self.ptr as *mut libc::c_void, self.size); } } |
233 | } |
234 | } |
235 | |
236 | #[derive (Debug)] |
237 | struct SampleData<S> { |
238 | mem: DriverMemory<S>, |
239 | frames: pcm::Frames, |
240 | channels: u32, |
241 | } |
242 | |
243 | impl<S> SampleData<S> { |
244 | pub fn new(p: &pcm::PCM) -> Result<Self> { |
245 | let params = p.hw_params_current()?; |
246 | let bufsize = params.get_buffer_size()?; |
247 | let channels = params.get_channels()?; |
248 | if params.get_access()? != pcm::Access::MMapInterleaved { |
249 | return Err(Error::unsupported("Not MMAP interleaved data" )) |
250 | } |
251 | |
252 | let fd = pcm_to_fd(p)?; |
253 | let info = unsafe { |
254 | let mut info: snd_pcm_channel_info = mem::zeroed(); |
255 | sndrv_pcm_ioctl_channel_info(fd, &mut info)?; |
256 | info |
257 | }; |
258 | // println!("{:?}", info); |
259 | if (info.step != channels * mem::size_of::<S>() as u32 * 8) || (info.first != 0) { |
260 | return Err(Error::unsupported("MMAP data size mismatch" )) |
261 | } |
262 | Ok(SampleData { |
263 | mem: DriverMemory::new(fd, (bufsize as usize) * (channels as usize), info.offset as libc::off_t, true)?, |
264 | frames: bufsize, |
265 | channels, |
266 | }) |
267 | } |
268 | } |
269 | |
270 | |
271 | /// Dummy trait for better generics |
272 | pub trait MmapDir: fmt::Debug { |
273 | const DIR: Direction; |
274 | fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames; |
275 | } |
276 | |
277 | /// Dummy struct for better generics |
278 | #[derive (Copy, Clone, Debug)] |
279 | pub struct Playback; |
280 | |
281 | impl MmapDir for Playback { |
282 | const DIR: Direction = Direction::Playback; |
283 | #[inline ] |
284 | fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames { |
285 | let r: i64 = hwptr.wrapping_add(buffersize).wrapping_sub(applptr); |
286 | let r: i64 = if r < 0 { r.wrapping_add(boundary) } else { r }; |
287 | if r as usize >= boundary as usize { r.wrapping_sub(boundary) } else { r } |
288 | } |
289 | } |
290 | |
291 | /// Dummy struct for better generics |
292 | #[derive (Copy, Clone, Debug)] |
293 | pub struct Capture; |
294 | |
295 | impl MmapDir for Capture { |
296 | const DIR: Direction = Direction::Capture; |
297 | #[inline ] |
298 | fn avail(hwptr: Frames, applptr: Frames, _buffersize: Frames, boundary: Frames) -> Frames { |
299 | let r: i64 = hwptr.wrapping_sub(applptr); |
300 | if r < 0 { r.wrapping_add(boundary) } else { r } |
301 | } |
302 | } |
303 | |
304 | pub type MmapPlayback<S> = MmapIO<S, Playback>; |
305 | |
306 | pub type MmapCapture<S> = MmapIO<S, Capture>; |
307 | |
308 | #[derive (Debug)] |
309 | /// Struct containing direct I/O functions shared between playback and capture. |
310 | pub struct MmapIO<S, D> { |
311 | data: SampleData<S>, |
312 | c: Control, |
313 | ss: Status, |
314 | bound: Frames, |
315 | dir: PhantomData<*const D>, |
316 | } |
317 | |
318 | #[derive (Debug, Clone, Copy)] |
319 | /// A raw pointer to samples, and the amount of samples readable or writable. |
320 | pub struct RawSamples<S> { |
321 | pub ptr: *mut S, |
322 | pub frames: Frames, |
323 | pub channels: u32, |
324 | } |
325 | |
326 | impl<S> RawSamples<S> { |
327 | #[inline ] |
328 | /// Returns `frames` * `channels`, i e the amount of samples (of type `S`) that can be read/written. |
329 | pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) } |
330 | |
331 | /// Writes samples from an iterator. |
332 | /// |
333 | /// Returns true if iterator was depleted, and the number of samples written. |
334 | /// This is just raw read/write of memory. |
335 | pub unsafe fn write_samples<I: Iterator<Item=S>>(&self, i: &mut I) -> (bool, isize) { |
336 | let mut z: isize = 0; |
337 | let max_samples: isize = self.samples(); |
338 | while z < max_samples { |
339 | let b: S = if let Some(b: S) = i.next() { b } else { return (true, z) }; |
340 | ptr::write_volatile(self.ptr.offset(z), src:b); |
341 | z += 1; |
342 | }; |
343 | (false, z) |
344 | } |
345 | |
346 | } |
347 | |
348 | impl<S, D: MmapDir> MmapIO<S, D> { |
349 | fn new(p: &pcm::PCM) -> Result<Self> { |
350 | if p.info()?.get_stream() != D::DIR { |
351 | return Err(Error::unsupported(func:"Wrong direction" )); |
352 | } |
353 | let boundary: i64 = p.sw_params_current()?.get_boundary()?; |
354 | Ok(MmapIO { |
355 | data: SampleData::new(p)?, |
356 | c: Control::new(p)?, |
357 | ss: Status::new(p)?, |
358 | bound: boundary, |
359 | dir: PhantomData, |
360 | }) |
361 | } |
362 | } |
363 | |
364 | pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) } |
365 | |
366 | impl<S, D: MmapDir> MmapIO<S, D> { |
367 | /// Read current status |
368 | pub fn status(&self) -> &Status { &self.ss } |
369 | |
370 | /// Read current number of frames committed by application |
371 | /// |
372 | /// This number wraps at 'boundary'. |
373 | #[inline ] |
374 | pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() } |
375 | |
376 | /// Read current number of frames read / written by hardware |
377 | /// |
378 | /// This number wraps at 'boundary'. |
379 | #[inline ] |
380 | pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() } |
381 | |
382 | /// The number at which hw_ptr and appl_ptr wraps. |
383 | #[inline ] |
384 | pub fn boundary(&self) -> Frames { self.bound } |
385 | |
386 | /// Total number of frames in hardware buffer |
387 | #[inline ] |
388 | pub fn buffer_size(&self) -> Frames { self.data.frames } |
389 | |
390 | /// Number of channels in stream |
391 | #[inline ] |
392 | pub fn channels(&self) -> u32 { self.data.channels } |
393 | |
394 | /// Notifies the kernel that frames have now been read / written by the application |
395 | /// |
396 | /// This will allow the kernel to write new data into this part of the buffer. |
397 | pub fn commit(&self, v: Frames) { |
398 | let mut z = self.appl_ptr() + v; |
399 | if z + v >= self.boundary() { z -= self.boundary() }; |
400 | self.c.set_appl_ptr(z) |
401 | } |
402 | |
403 | /// Number of frames available to read / write. |
404 | /// |
405 | /// In case of an underrun, this value might be bigger than the buffer size. |
406 | pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) } |
407 | |
408 | /// Returns raw pointers to data to read / write. |
409 | /// |
410 | /// Use this if you want to read/write data yourself (instead of using iterators). If you do, |
411 | /// using `write_volatile` or `read_volatile` is recommended, since it's DMA memory and can |
412 | /// change at any time. |
413 | /// |
414 | /// Since this is a ring buffer, there might be more data to read/write in the beginning |
415 | /// of the buffer as well. If so this is returned as the second return value. |
416 | pub fn data_ptr(&self) -> (RawSamples<S>, Option<RawSamples<S>>) { |
417 | let (hwptr, applptr) = (self.hw_ptr(), self.appl_ptr()); |
418 | let c = self.channels(); |
419 | let bufsize = self.buffer_size(); |
420 | |
421 | // These formulas mostly mimic the behaviour of |
422 | // snd_pcm_mmap_begin (in alsa-lib/src/pcm/pcm.c). |
423 | let offs = applptr % bufsize; |
424 | let mut a = D::avail(hwptr, applptr, bufsize, self.boundary()); |
425 | a = cmp::min(a, bufsize); |
426 | let b = bufsize - offs; |
427 | let more_data = if b < a { |
428 | let z = a - b; |
429 | a = b; |
430 | Some( RawSamples { ptr: self.data.mem.ptr, frames: z, channels: c }) |
431 | } else { None }; |
432 | |
433 | let p = unsafe { self.data.mem.ptr.offset(offs as isize * self.data.channels as isize) }; |
434 | (RawSamples { ptr: p, frames: a, channels: c }, more_data) |
435 | } |
436 | } |
437 | |
438 | impl<S> MmapPlayback<S> { |
439 | /// Write samples to the kernel ringbuffer. |
440 | pub fn write<I: Iterator<Item=S>>(&mut self, i: &mut I) -> Frames { |
441 | let (data: RawSamples, more_data: Option>) = self.data_ptr(); |
442 | let (iter_end: bool, samples: isize) = unsafe { data.write_samples(i) }; |
443 | let mut z: isize = samples / data.channels as isize; |
444 | if !iter_end { |
445 | if let Some(data2: RawSamples) = more_data { |
446 | let (_, samples2: isize) = unsafe { data2.write_samples(i) }; |
447 | z += samples2 / data2.channels as isize; |
448 | } |
449 | } |
450 | let z: i64 = z as Frames; |
451 | self.commit(z); |
452 | z |
453 | } |
454 | } |
455 | |
456 | impl<S> MmapCapture<S> { |
457 | /// Read samples from the kernel ringbuffer. |
458 | /// |
459 | /// When the iterator is dropped or depleted, the read samples will be committed, i e, |
460 | /// the kernel can then write data to the location again. So do this ASAP. |
461 | pub fn iter(&mut self) -> CaptureIter<S> { |
462 | let (data: RawSamples, more_data: Option>) = self.data_ptr(); |
463 | CaptureIter { |
464 | m: self, |
465 | samples: data, |
466 | p_offs: 0, |
467 | read_samples: 0, |
468 | next_p: more_data, |
469 | } |
470 | } |
471 | } |
472 | |
473 | /// Iterator over captured samples |
474 | pub struct CaptureIter<'a, S: 'static> { |
475 | m: &'a MmapCapture<S>, |
476 | samples: RawSamples<S>, |
477 | p_offs: isize, |
478 | read_samples: isize, |
479 | next_p: Option<RawSamples<S>>, |
480 | } |
481 | |
482 | impl<'a, S: 'static + Copy> CaptureIter<'a, S> { |
483 | fn handle_max(&mut self) { |
484 | self.p_offs = 0; |
485 | if let Some(p2: RawSamples) = self.next_p.take() { |
486 | self.samples = p2; |
487 | } else { |
488 | self.m.commit((self.read_samples / self.samples.channels as isize) as Frames); |
489 | self.read_samples = 0; |
490 | self.samples.frames = 0; // Shortcut to "None" in case anyone calls us again |
491 | } |
492 | } |
493 | } |
494 | |
495 | impl<'a, S: 'static + Copy> Iterator for CaptureIter<'a, S> { |
496 | type Item = S; |
497 | |
498 | #[inline ] |
499 | fn next(&mut self) -> Option<Self::Item> { |
500 | if self.p_offs >= self.samples.samples() { |
501 | self.handle_max(); |
502 | if self.samples.frames <= 0 { return None; } |
503 | } |
504 | let s: S = unsafe { ptr::read_volatile(self.samples.ptr.offset(self.p_offs)) }; |
505 | self.p_offs += 1; |
506 | self.read_samples += 1; |
507 | Some(s) |
508 | } |
509 | } |
510 | |
511 | impl<'a, S: 'static> Drop for CaptureIter<'a, S> { |
512 | fn drop(&mut self) { |
513 | self.m.commit((self.read_samples / self.m.data.channels as isize) as Frames); |
514 | } |
515 | } |
516 | |
517 | |
518 | #[test ] |
519 | #[ignore ] // Not everyone has a recording device on plughw:1. So let's ignore this test by default. |
520 | fn record_from_plughw_rw() { |
521 | use crate::pcm::*; |
522 | use crate::{ValueOr, Direction}; |
523 | use std::ffi::CString; |
524 | let pcm = PCM::open(&*CString::new("plughw:1" ).unwrap(), Direction::Capture, false).unwrap(); |
525 | let ss = self::Status::new(&pcm).unwrap(); |
526 | let c = self::Control::new(&pcm).unwrap(); |
527 | let hwp = HwParams::any(&pcm).unwrap(); |
528 | hwp.set_channels(2).unwrap(); |
529 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); |
530 | hwp.set_format(Format::s16()).unwrap(); |
531 | hwp.set_access(Access::RWInterleaved).unwrap(); |
532 | pcm.hw_params(&hwp).unwrap(); |
533 | |
534 | { |
535 | let swp = pcm.sw_params_current().unwrap(); |
536 | swp.set_tstamp_mode(true).unwrap(); |
537 | pcm.sw_params(&swp).unwrap(); |
538 | } |
539 | assert_eq!(ss.state(), State::Prepared); |
540 | pcm.start().unwrap(); |
541 | assert_eq!(c.appl_ptr(), 0); |
542 | println!(" {:?}, {:?}" , ss, c); |
543 | let mut buf = [0i16; 512*2]; |
544 | assert_eq!(pcm.io_i16().unwrap().readi(&mut buf).unwrap(), 512); |
545 | assert_eq!(c.appl_ptr(), 512); |
546 | |
547 | assert_eq!(ss.state(), State::Running); |
548 | assert!(ss.hw_ptr() >= 512); |
549 | let t2 = ss.htstamp(); |
550 | assert!(t2.tv_sec > 0 || t2.tv_nsec > 0); |
551 | } |
552 | |
553 | |
554 | #[test ] |
555 | #[ignore ] // Not everyone has a record device on plughw:1. So let's ignore this test by default. |
556 | fn record_from_plughw_mmap() { |
557 | use crate::pcm::*; |
558 | use crate::{ValueOr, Direction}; |
559 | use std::ffi::CString; |
560 | use std::{thread, time}; |
561 | |
562 | let pcm = PCM::open(&*CString::new("plughw:1" ).unwrap(), Direction::Capture, false).unwrap(); |
563 | let hwp = HwParams::any(&pcm).unwrap(); |
564 | hwp.set_channels(2).unwrap(); |
565 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); |
566 | hwp.set_format(Format::s16()).unwrap(); |
567 | hwp.set_access(Access::MMapInterleaved).unwrap(); |
568 | pcm.hw_params(&hwp).unwrap(); |
569 | |
570 | let ss = unsafe { SyncPtrStatus::sync_ptr(pcm_to_fd(&pcm).unwrap(), false, None, None).unwrap() }; |
571 | assert_eq!(ss.state(), State::Prepared); |
572 | |
573 | let mut m = pcm.direct_mmap_capture::<i16>().unwrap(); |
574 | |
575 | assert_eq!(m.status().state(), State::Prepared); |
576 | assert_eq!(m.appl_ptr(), 0); |
577 | assert_eq!(m.hw_ptr(), 0); |
578 | |
579 | |
580 | println!(" {:?}" , m); |
581 | |
582 | let now = time::Instant::now(); |
583 | pcm.start().unwrap(); |
584 | while m.avail() < 256 { thread::sleep(time::Duration::from_millis(1)) }; |
585 | assert!(now.elapsed() >= time::Duration::from_millis(256 * 1000 / 44100)); |
586 | let (ptr1, md) = m.data_ptr(); |
587 | assert_eq!(ptr1.channels, 2); |
588 | assert!(ptr1.frames >= 256); |
589 | assert!(md.is_none()); |
590 | println!("Has {:?} frames at {:?} in {:?}" , m.avail(), ptr1.ptr, now.elapsed()); |
591 | let samples: Vec<i16> = m.iter().collect(); |
592 | assert!(samples.len() >= ptr1.frames as usize * 2); |
593 | println!("Collected {} samples" , samples.len()); |
594 | let (ptr2, _md) = m.data_ptr(); |
595 | assert!(unsafe { ptr1.ptr.offset(256 * 2) } <= ptr2.ptr); |
596 | } |
597 | |
598 | #[test ] |
599 | #[ignore ] |
600 | fn playback_to_plughw_mmap() { |
601 | use crate::pcm::*; |
602 | use crate::{ValueOr, Direction}; |
603 | use std::ffi::CString; |
604 | |
605 | let pcm = PCM::open(&*CString::new("plughw:1" ).unwrap(), Direction::Playback, false).unwrap(); |
606 | let hwp = HwParams::any(&pcm).unwrap(); |
607 | hwp.set_channels(2).unwrap(); |
608 | hwp.set_rate(44100, ValueOr::Nearest).unwrap(); |
609 | hwp.set_format(Format::s16()).unwrap(); |
610 | hwp.set_access(Access::MMapInterleaved).unwrap(); |
611 | pcm.hw_params(&hwp).unwrap(); |
612 | let mut m = pcm.direct_mmap_playback::<i16>().unwrap(); |
613 | |
614 | assert_eq!(m.status().state(), State::Prepared); |
615 | assert_eq!(m.appl_ptr(), 0); |
616 | assert_eq!(m.hw_ptr(), 0); |
617 | |
618 | println!(" {:?}" , m); |
619 | let mut i = (0..(m.buffer_size() * 2)).map(|i| |
620 | (((i / 2) as f32 * 2.0 * ::std::f32::consts::PI / 128.0).sin() * 8192.0) as i16); |
621 | m.write(&mut i); |
622 | assert_eq!(m.appl_ptr(), m.buffer_size()); |
623 | |
624 | pcm.start().unwrap(); |
625 | pcm.drain().unwrap(); |
626 | assert_eq!(m.appl_ptr(), m.buffer_size()); |
627 | assert!(m.hw_ptr() >= m.buffer_size()); |
628 | } |
629 | |