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