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 std::{mem, ptr, fmt, cmp};
24use crate::error::{Error, Result};
25use std::os::unix::io::RawFd;
26use crate::{pcm, PollDescriptors, Direction};
27use crate::pcm::Frames;
28use std::marker::PhantomData;
29
30use 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.
35pub struct SyncPtrStatus(snd_pcm_mmap_status);
36
37impl 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)]
90pub struct Status(DriverMemory<snd_pcm_mmap_status>);
91
92fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> {
93    let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() };
94    let c = PollDescriptors::fill(p, &mut fds)?;
95    if c != 1 {
96        return Err(Error::unsupported("snd_pcm_poll_descriptors returned wrong number of fds"))
97    }
98    Ok(fds[0].fd)
99}
100
101impl 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)]
158pub struct Control(DriverMemory<snd_pcm_mmap_control>);
159
160impl 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
202struct DriverMemory<S> {
203   ptr: *mut S,
204   size: libc::size_t,
205}
206
207impl<S> fmt::Debug for DriverMemory<S> {
208   fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory({:?})", self.ptr) }
209}
210
211impl<S> DriverMemory<S> {
212    fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> {
213        let mut total = count * mem::size_of::<S>();
214        let ps = pagesize();
215        assert!(total > 0);
216        if total % ps != 0 { total += ps - total % ps };
217        let flags = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ };
218        let p = unsafe { libc::mmap(ptr::null_mut(), total, flags, libc::MAP_FILE | libc::MAP_SHARED, fd, offs) };
219        if p.is_null() || p == libc::MAP_FAILED {
220            Err(Error::last("mmap (of driver memory)"))
221        } else {
222            Ok(DriverMemory { ptr: p as *mut S, size: total })
223        }
224    }
225}
226
227unsafe impl<S> Send for DriverMemory<S> {}
228unsafe impl<S> Sync for DriverMemory<S> {}
229
230impl<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)]
237struct SampleData<S> {
238    mem: DriverMemory<S>,
239    frames: pcm::Frames,
240    channels: u32,
241}
242
243impl<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
272pub 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)]
279pub struct Playback;
280
281impl 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 = hwptr.wrapping_add(buffersize).wrapping_sub(applptr);
286	let r = 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)]
293pub struct Capture;
294
295impl 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 = hwptr.wrapping_sub(applptr);
300	if r < 0 { r.wrapping_add(boundary) } else { r }
301    }
302}
303
304pub type MmapPlayback<S> = MmapIO<S, Playback>;
305
306pub type MmapCapture<S> = MmapIO<S, Capture>;
307
308#[derive(Debug)]
309/// Struct containing direct I/O functions shared between playback and capture.
310pub 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.
320pub struct RawSamples<S> {
321    pub ptr: *mut S,
322    pub frames: Frames,
323    pub channels: u32,
324}
325
326impl<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 = 0;
337        let max_samples = self.samples();
338        while z < max_samples {
339            let b = if let Some(b) = i.next() { b } else { return (true, z) };
340            ptr::write_volatile(self.ptr.offset(z), b);
341            z += 1;
342        };
343        (false, z)
344    }
345
346}
347
348impl<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("Wrong direction"));
352        }
353        let boundary = 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
364pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) }
365
366impl<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
438impl<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, more_data) = self.data_ptr();
442        let (iter_end, samples) = unsafe { data.write_samples(i) };
443        let mut z = samples / data.channels as isize;
444        if !iter_end {
445            if let Some(data2) = more_data {
446                let (_, samples2) = unsafe {  data2.write_samples(i) };
447                z += samples2 / data2.channels as isize;
448            }
449        }
450        let z = z as Frames;
451        self.commit(z);
452        z
453    }
454}
455
456impl<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, more_data) = 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
474pub 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
482impl<'a, S: 'static + Copy> CaptureIter<'a, S> {
483    fn handle_max(&mut self) {
484        self.p_offs = 0;
485        if let Some(p2) = 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
495impl<'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 = 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
511impl<'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.
520fn 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.
556fn 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]
600fn 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}