Skip to main content

alsa/direct/
pcm.rs

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