summaryrefslogtreecommitdiffstats
path: root/third_party/rust/alsa/src/direct/pcm.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/alsa/src/direct/pcm.rs')
-rw-r--r--third_party/rust/alsa/src/direct/pcm.rs630
1 files changed, 630 insertions, 0 deletions
diff --git a/third_party/rust/alsa/src/direct/pcm.rs b/third_party/rust/alsa/src/direct/pcm.rs
new file mode 100644
index 0000000000..ccea666145
--- /dev/null
+++ b/third_party/rust/alsa/src/direct/pcm.rs
@@ -0,0 +1,630 @@
+/*!
+This module bypasses alsa-lib and directly read and write into memory mapped kernel memory.
+In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card.
+
+The reasons for doing this are:
+
+ * Minimum overhead where it matters most: let alsa-lib do the code heavy setup -
+ then steal its file descriptor and deal with sample streaming from Rust.
+ * RT-safety to the maximum extent possible. Creating/dropping any of these structs causes syscalls,
+ but function calls on these are just read and write from memory. No syscalls, no memory allocations,
+ not even loops (with the exception of `MmapPlayback::write` that loops over samples to write).
+ * Possibility to allow Send + Sync for structs
+ * It's a fun experiment and an interesting deep dive into how alsa-lib does things.
+
+Note: Not all sound card drivers support this direct method of communication; although almost all
+modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings),
+don't expect it to work with, e g, the PulseAudio plugin or so.
+
+For an example of how to use this mode, look in the "synth-example" directory.
+*/
+
+use libc;
+use std::{mem, ptr, fmt, cmp};
+use crate::error::{Error, Result};
+use std::os::unix::io::RawFd;
+use crate::{pcm, PollDescriptors, Direction};
+use crate::pcm::Frames;
+use std::marker::PhantomData;
+
+use super::ffi::*;
+
+/// Read PCM status via a simple kernel syscall, bypassing alsa-lib.
+///
+/// If Status is not available on your architecture, this is the second best option.
+pub struct SyncPtrStatus(snd_pcm_mmap_status);
+
+impl SyncPtrStatus {
+ /// Executes sync_ptr syscall.
+ ///
+ /// Unsafe because
+ /// - setting appl_ptr and avail_min might make alsa-lib confused
+ /// - no check that the fd is really a PCM
+ pub unsafe fn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option<pcm::Frames>, avail_min: Option<pcm::Frames>) -> Result<Self> {
+ let mut data = snd_pcm_sync_ptr {
+ flags: (if hwsync { SNDRV_PCM_SYNC_PTR_HWSYNC } else { 0 }) +
+ (if appl_ptr.is_some() { SNDRV_PCM_SYNC_PTR_APPL } else { 0 }) +
+ (if avail_min.is_some() { SNDRV_PCM_SYNC_PTR_AVAIL_MIN } else { 0 }),
+ c: snd_pcm_mmap_control_r {
+ control: snd_pcm_mmap_control {
+ appl_ptr: appl_ptr.unwrap_or(0) as snd_pcm_uframes_t,
+ avail_min: avail_min.unwrap_or(0) as snd_pcm_uframes_t,
+ }
+ },
+ s: mem::zeroed()
+ };
+
+ sndrv_pcm_ioctl_sync_ptr(fd, &mut data).map_err(|_|
+ Error::new("SNDRV_PCM_IOCTL_SYNC_PTR", nix::errno::Errno::last() as i32))?;
+
+ let i = data.s.status.state;
+ if (i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)) {
+ Ok(SyncPtrStatus(data.s.status))
+ } else {
+ Err(Error::unsupported("SNDRV_PCM_IOCTL_SYNC_PTR returned broken state"))
+ }
+ }
+
+ pub fn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames }
+ pub fn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ }
+ pub fn htstamp(&self) -> libc::timespec { self.0.tstamp }
+}
+
+
+
+/// Read PCM status directly from memory, bypassing alsa-lib.
+///
+/// This means that it's
+/// 1) less overhead for reading status (no syscall, no allocations, no virtual dispatch, just a read from memory)
+/// 2) Send + Sync, and
+/// 3) will only work for "hw" / "plughw" devices (not e g PulseAudio plugins), and not
+/// all of those are supported, although all common ones are (as of 2017, and a kernel from the same decade).
+/// Kernel supported archs are: x86, PowerPC, Alpha. Use "SyncPtrStatus" for other archs.
+///
+/// The values are updated every now and then by the kernel. Many functions will force an update to happen,
+/// e g `PCM::avail()` and `PCM::delay()`.
+///
+/// Note: Even if you close the original PCM device, ALSA will not actually close the device until all
+/// Status structs are dropped too.
+///
+#[derive(Debug)]
+pub struct Status(DriverMemory<snd_pcm_mmap_status>);
+
+fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> {
+ let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() };
+ let c = PollDescriptors::fill(p, &mut fds)?;
+ if c != 1 {
+ return Err(Error::unsupported("snd_pcm_poll_descriptors returned wrong number of fds"))
+ }
+ Ok(fds[0].fd)
+}
+
+impl Status {
+ pub fn new(p: &pcm::PCM) -> Result<Self> { Status::from_fd(pcm_to_fd(p)?) }
+
+ pub fn from_fd(fd: RawFd) -> Result<Self> {
+ DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_STATUS as libc::off_t, false).map(Status)
+ }
+
+ /// Current PCM state.
+ pub fn state(&self) -> pcm::State {
+ unsafe {
+ let i = ptr::read_volatile(&(*self.0.ptr).state);
+ assert!((i >= (pcm::State::Open as snd_pcm_state_t)) && (i <= (pcm::State::Disconnected as snd_pcm_state_t)));
+ mem::transmute(i as u8)
+ }
+ }
+
+ /// Number of frames hardware has read or written
+ ///
+ /// This number is updated every now and then by the kernel.
+ /// Calling most functions on the PCM will update it, so will usually a period interrupt.
+ /// No guarantees given.
+ ///
+ /// This value wraps at "boundary" (a large value you can read from SwParams).
+ pub fn hw_ptr(&self) -> pcm::Frames {
+ unsafe {
+ ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames
+ }
+ }
+
+ /// Timestamp - fast version of alsa-lib's Status::get_htstamp
+ ///
+ /// Note: This just reads the actual value in memory.
+ /// Unfortunately, the timespec is too big to be read atomically on most archs.
+ /// Therefore, this function can potentially give bogus result at times, at least in theory...?
+ pub fn htstamp(&self) -> libc::timespec {
+ unsafe {
+ ptr::read_volatile(&(*self.0.ptr).tstamp)
+ }
+ }
+
+ /// Audio timestamp - fast version of alsa-lib's Status::get_audio_htstamp
+ ///
+ /// Note: This just reads the actual value in memory.
+ /// Unfortunately, the timespec is too big to be read atomically on most archs.
+ /// Therefore, this function can potentially give bogus result at times, at least in theory...?
+ pub fn audio_htstamp(&self) -> libc::timespec {
+ unsafe {
+ ptr::read_volatile(&(*self.0.ptr).audio_tstamp)
+ }
+ }
+}
+
+/// Write PCM appl ptr directly, bypassing alsa-lib.
+///
+/// Provides direct access to appl ptr and avail min, without the overhead of
+/// alsa-lib or a syscall. Caveats that apply to Status applies to this struct too.
+#[derive(Debug)]
+pub struct Control(DriverMemory<snd_pcm_mmap_control>);
+
+impl Control {
+ pub fn new(p: &pcm::PCM) -> Result<Self> { Self::from_fd(pcm_to_fd(p)?) }
+
+ pub fn from_fd(fd: RawFd) -> Result<Self> {
+ DriverMemory::new(fd, 1, SNDRV_PCM_MMAP_OFFSET_CONTROL as libc::off_t, true).map(Control)
+ }
+
+ /// Read number of frames application has read or written
+ ///
+ /// This value wraps at "boundary" (a large value you can read from SwParams).
+ pub fn appl_ptr(&self) -> pcm::Frames {
+ unsafe {
+ ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames
+ }
+ }
+
+ /// Set number of frames application has read or written
+ ///
+ /// When the kernel wakes up due to a period interrupt, this value will
+ /// be checked by the kernel. An XRUN will happen in case the application
+ /// has not read or written enough data.
+ pub fn set_appl_ptr(&self, value: pcm::Frames) {
+ unsafe {
+ ptr::write_volatile(&mut (*self.0.ptr).appl_ptr, value as snd_pcm_uframes_t)
+ }
+ }
+
+ /// Read minimum number of frames in buffer in order to wakeup process
+ pub fn avail_min(&self) -> pcm::Frames {
+ unsafe {
+ ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames
+ }
+ }
+
+ /// Write minimum number of frames in buffer in order to wakeup process
+ pub fn set_avail_min(&self, value: pcm::Frames) {
+ unsafe {
+ ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t)
+ }
+ }
+}
+
+struct DriverMemory<S> {
+ ptr: *mut S,
+ size: libc::size_t,
+}
+
+impl<S> fmt::Debug for DriverMemory<S> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory({:?})", self.ptr) }
+}
+
+impl<S> DriverMemory<S> {
+ fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> {
+ let mut total = count * mem::size_of::<S>();
+ let ps = pagesize();
+ assert!(total > 0);
+ if total % ps != 0 { total += ps - total % ps };
+ let flags = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ };
+ let p = unsafe { libc::mmap(ptr::null_mut(), total, flags, libc::MAP_FILE | libc::MAP_SHARED, fd, offs) };
+ if p.is_null() || p == libc::MAP_FAILED {
+ Err(Error::new("mmap (of driver memory)", nix::errno::Errno::last() as i32))
+ } else {
+ Ok(DriverMemory { ptr: p as *mut S, size: total })
+ }
+ }
+}
+
+unsafe impl<S> Send for DriverMemory<S> {}
+unsafe impl<S> Sync for DriverMemory<S> {}
+
+impl<S> Drop for DriverMemory<S> {
+ fn drop(&mut self) {
+ unsafe {{ libc::munmap(self.ptr as *mut libc::c_void, self.size); } }
+ }
+}
+
+#[derive(Debug)]
+struct SampleData<S> {
+ mem: DriverMemory<S>,
+ frames: pcm::Frames,
+ channels: u32,
+}
+
+impl<S> SampleData<S> {
+ pub fn new(p: &pcm::PCM) -> Result<Self> {
+ let params = p.hw_params_current()?;
+ let bufsize = params.get_buffer_size()?;
+ let channels = params.get_channels()?;
+ if params.get_access()? != pcm::Access::MMapInterleaved {
+ return Err(Error::unsupported("Not MMAP interleaved data"))
+ }
+
+ let fd = pcm_to_fd(p)?;
+ let info = unsafe {
+ let mut info: snd_pcm_channel_info = mem::zeroed();
+ sndrv_pcm_ioctl_channel_info(fd, &mut info).map_err(|_|
+ Error::new("SNDRV_PCM_IOCTL_CHANNEL_INFO", nix::errno::Errno::last() as i32))?;
+ info
+ };
+ // println!("{:?}", info);
+ if (info.step != channels * mem::size_of::<S>() as u32 * 8) || (info.first != 0) {
+ return Err(Error::unsupported("MMAP data size mismatch"))
+ }
+ Ok(SampleData {
+ mem: DriverMemory::new(fd, (bufsize as usize) * (channels as usize), info.offset as libc::off_t, true)?,
+ frames: bufsize,
+ channels,
+ })
+ }
+}
+
+
+/// Dummy trait for better generics
+pub trait MmapDir: fmt::Debug {
+ const DIR: Direction;
+ fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames;
+}
+
+/// Dummy struct for better generics
+#[derive(Copy, Clone, Debug)]
+pub struct Playback;
+
+impl MmapDir for Playback {
+ const DIR: Direction = Direction::Playback;
+ #[inline]
+ fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames {
+ let r = hwptr.wrapping_add(buffersize).wrapping_sub(applptr);
+ let r = if r < 0 { r.wrapping_add(boundary) } else { r };
+ if r as usize >= boundary as usize { r.wrapping_sub(boundary) } else { r }
+ }
+}
+
+/// Dummy struct for better generics
+#[derive(Copy, Clone, Debug)]
+pub struct Capture;
+
+impl MmapDir for Capture {
+ const DIR: Direction = Direction::Capture;
+ #[inline]
+ fn avail(hwptr: Frames, applptr: Frames, _buffersize: Frames, boundary: Frames) -> Frames {
+ let r = hwptr.wrapping_sub(applptr);
+ if r < 0 { r.wrapping_add(boundary) } else { r }
+ }
+}
+
+pub type MmapPlayback<S> = MmapIO<S, Playback>;
+
+pub type MmapCapture<S> = MmapIO<S, Capture>;
+
+#[derive(Debug)]
+/// Struct containing direct I/O functions shared between playback and capture.
+pub struct MmapIO<S, D> {
+ data: SampleData<S>,
+ c: Control,
+ ss: Status,
+ bound: Frames,
+ dir: PhantomData<*const D>,
+}
+
+#[derive(Debug, Clone, Copy)]
+/// A raw pointer to samples, and the amount of samples readable or writable.
+pub struct RawSamples<S> {
+ pub ptr: *mut S,
+ pub frames: Frames,
+ pub channels: u32,
+}
+
+impl<S> RawSamples<S> {
+ #[inline]
+ /// Returns `frames` * `channels`, i e the amount of samples (of type `S`) that can be read/written.
+ pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) }
+
+ /// Writes samples from an iterator.
+ ///
+ /// Returns true if iterator was depleted, and the number of samples written.
+ /// This is just raw read/write of memory.
+ pub unsafe fn write_samples<I: Iterator<Item=S>>(&self, i: &mut I) -> (bool, isize) {
+ let mut z = 0;
+ let max_samples = self.samples();
+ while z < max_samples {
+ let b = if let Some(b) = i.next() { b } else { return (true, z) };
+ ptr::write_volatile(self.ptr.offset(z), b);
+ z += 1;
+ };
+ (false, z)
+ }
+
+}
+
+impl<S, D: MmapDir> MmapIO<S, D> {
+ fn new(p: &pcm::PCM) -> Result<Self> {
+ if p.info()?.get_stream() != D::DIR {
+ return Err(Error::unsupported("Wrong direction"));
+ }
+ let boundary = p.sw_params_current()?.get_boundary()?;
+ Ok(MmapIO {
+ data: SampleData::new(p)?,
+ c: Control::new(p)?,
+ ss: Status::new(p)?,
+ bound: boundary,
+ dir: PhantomData,
+ })
+ }
+}
+
+pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) }
+
+impl<S, D: MmapDir> MmapIO<S, D> {
+ /// Read current status
+ pub fn status(&self) -> &Status { &self.ss }
+
+ /// Read current number of frames committed by application
+ ///
+ /// This number wraps at 'boundary'.
+ #[inline]
+ pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() }
+
+ /// Read current number of frames read / written by hardware
+ ///
+ /// This number wraps at 'boundary'.
+ #[inline]
+ pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() }
+
+ /// The number at which hw_ptr and appl_ptr wraps.
+ #[inline]
+ pub fn boundary(&self) -> Frames { self.bound }
+
+ /// Total number of frames in hardware buffer
+ #[inline]
+ pub fn buffer_size(&self) -> Frames { self.data.frames }
+
+ /// Number of channels in stream
+ #[inline]
+ pub fn channels(&self) -> u32 { self.data.channels }
+
+ /// Notifies the kernel that frames have now been read / written by the application
+ ///
+ /// This will allow the kernel to write new data into this part of the buffer.
+ pub fn commit(&self, v: Frames) {
+ let mut z = self.appl_ptr() + v;
+ if z + v >= self.boundary() { z -= self.boundary() };
+ self.c.set_appl_ptr(z)
+ }
+
+ /// Number of frames available to read / write.
+ ///
+ /// In case of an underrun, this value might be bigger than the buffer size.
+ pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) }
+
+ /// Returns raw pointers to data to read / write.
+ ///
+ /// Use this if you want to read/write data yourself (instead of using iterators). If you do,
+ /// using `write_volatile` or `read_volatile` is recommended, since it's DMA memory and can
+ /// change at any time.
+ ///
+ /// Since this is a ring buffer, there might be more data to read/write in the beginning
+ /// of the buffer as well. If so this is returned as the second return value.
+ pub fn data_ptr(&self) -> (RawSamples<S>, Option<RawSamples<S>>) {
+ let (hwptr, applptr) = (self.hw_ptr(), self.appl_ptr());
+ let c = self.channels();
+ let bufsize = self.buffer_size();
+
+ // These formulas mostly mimic the behaviour of
+ // snd_pcm_mmap_begin (in alsa-lib/src/pcm/pcm.c).
+ let offs = applptr % bufsize;
+ let mut a = D::avail(hwptr, applptr, bufsize, self.boundary());
+ a = cmp::min(a, bufsize);
+ let b = bufsize - offs;
+ let more_data = if b < a {
+ let z = a - b;
+ a = b;
+ Some( RawSamples { ptr: self.data.mem.ptr, frames: z, channels: c })
+ } else { None };
+
+ let p = unsafe { self.data.mem.ptr.offset(offs as isize * self.data.channels as isize) };
+ (RawSamples { ptr: p, frames: a, channels: c }, more_data)
+ }
+}
+
+impl<S> MmapPlayback<S> {
+ /// Write samples to the kernel ringbuffer.
+ pub fn write<I: Iterator<Item=S>>(&mut self, i: &mut I) -> Frames {
+ let (data, more_data) = self.data_ptr();
+ let (iter_end, samples) = unsafe { data.write_samples(i) };
+ let mut z = samples / data.channels as isize;
+ if !iter_end {
+ if let Some(data2) = more_data {
+ let (_, samples2) = unsafe { data2.write_samples(i) };
+ z += samples2 / data2.channels as isize;
+ }
+ }
+ let z = z as Frames;
+ self.commit(z);
+ z
+ }
+}
+
+impl<S> MmapCapture<S> {
+ /// Read samples from the kernel ringbuffer.
+ ///
+ /// When the iterator is dropped or depleted, the read samples will be committed, i e,
+ /// the kernel can then write data to the location again. So do this ASAP.
+ pub fn iter(&mut self) -> CaptureIter<S> {
+ let (data, more_data) = self.data_ptr();
+ CaptureIter {
+ m: self,
+ samples: data,
+ p_offs: 0,
+ read_samples: 0,
+ next_p: more_data,
+ }
+ }
+}
+
+/// Iterator over captured samples
+pub struct CaptureIter<'a, S: 'static> {
+ m: &'a MmapCapture<S>,
+ samples: RawSamples<S>,
+ p_offs: isize,
+ read_samples: isize,
+ next_p: Option<RawSamples<S>>,
+}
+
+impl<'a, S: 'static + Copy> CaptureIter<'a, S> {
+ fn handle_max(&mut self) {
+ self.p_offs = 0;
+ if let Some(p2) = self.next_p.take() {
+ self.samples = p2;
+ } else {
+ self.m.commit((self.read_samples / self.samples.channels as isize) as Frames);
+ self.read_samples = 0;
+ self.samples.frames = 0; // Shortcut to "None" in case anyone calls us again
+ }
+ }
+}
+
+impl<'a, S: 'static + Copy> Iterator for CaptureIter<'a, S> {
+ type Item = S;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.p_offs >= self.samples.samples() {
+ self.handle_max();
+ if self.samples.frames <= 0 { return None; }
+ }
+ let s = unsafe { ptr::read_volatile(self.samples.ptr.offset(self.p_offs)) };
+ self.p_offs += 1;
+ self.read_samples += 1;
+ Some(s)
+ }
+}
+
+impl<'a, S: 'static> Drop for CaptureIter<'a, S> {
+ fn drop(&mut self) {
+ self.m.commit((self.read_samples / self.m.data.channels as isize) as Frames);
+ }
+}
+
+
+#[test]
+#[ignore] // Not everyone has a recording device on plughw:1. So let's ignore this test by default.
+fn record_from_plughw_rw() {
+ use crate::pcm::*;
+ use crate::{ValueOr, Direction};
+ use std::ffi::CString;
+ let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
+ let ss = self::Status::new(&pcm).unwrap();
+ let c = self::Control::new(&pcm).unwrap();
+ let hwp = HwParams::any(&pcm).unwrap();
+ hwp.set_channels(2).unwrap();
+ hwp.set_rate(44100, ValueOr::Nearest).unwrap();
+ hwp.set_format(Format::s16()).unwrap();
+ hwp.set_access(Access::RWInterleaved).unwrap();
+ pcm.hw_params(&hwp).unwrap();
+
+ {
+ let swp = pcm.sw_params_current().unwrap();
+ swp.set_tstamp_mode(true).unwrap();
+ pcm.sw_params(&swp).unwrap();
+ }
+ assert_eq!(ss.state(), State::Prepared);
+ pcm.start().unwrap();
+ assert_eq!(c.appl_ptr(), 0);
+ println!("{:?}, {:?}", ss, c);
+ let mut buf = [0i16; 512*2];
+ assert_eq!(pcm.io_i16().unwrap().readi(&mut buf).unwrap(), 512);
+ assert_eq!(c.appl_ptr(), 512);
+
+ assert_eq!(ss.state(), State::Running);
+ assert!(ss.hw_ptr() >= 512);
+ let t2 = ss.htstamp();
+ assert!(t2.tv_sec > 0 || t2.tv_nsec > 0);
+}
+
+
+#[test]
+#[ignore] // Not everyone has a record device on plughw:1. So let's ignore this test by default.
+fn record_from_plughw_mmap() {
+ use crate::pcm::*;
+ use crate::{ValueOr, Direction};
+ use std::ffi::CString;
+ use std::{thread, time};
+
+ let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Capture, false).unwrap();
+ let hwp = HwParams::any(&pcm).unwrap();
+ hwp.set_channels(2).unwrap();
+ hwp.set_rate(44100, ValueOr::Nearest).unwrap();
+ hwp.set_format(Format::s16()).unwrap();
+ hwp.set_access(Access::MMapInterleaved).unwrap();
+ pcm.hw_params(&hwp).unwrap();
+
+ let ss = unsafe { SyncPtrStatus::sync_ptr(pcm_to_fd(&pcm).unwrap(), false, None, None).unwrap() };
+ assert_eq!(ss.state(), State::Prepared);
+
+ let mut m = pcm.direct_mmap_capture::<i16>().unwrap();
+
+ assert_eq!(m.status().state(), State::Prepared);
+ assert_eq!(m.appl_ptr(), 0);
+ assert_eq!(m.hw_ptr(), 0);
+
+
+ println!("{:?}", m);
+
+ let now = time::Instant::now();
+ pcm.start().unwrap();
+ while m.avail() < 256 { thread::sleep(time::Duration::from_millis(1)) };
+ assert!(now.elapsed() >= time::Duration::from_millis(256 * 1000 / 44100));
+ let (ptr1, md) = m.data_ptr();
+ assert_eq!(ptr1.channels, 2);
+ assert!(ptr1.frames >= 256);
+ assert!(md.is_none());
+ println!("Has {:?} frames at {:?} in {:?}", m.avail(), ptr1.ptr, now.elapsed());
+ let samples: Vec<i16> = m.iter().collect();
+ assert!(samples.len() >= ptr1.frames as usize * 2);
+ println!("Collected {} samples", samples.len());
+ let (ptr2, _md) = m.data_ptr();
+ assert!(unsafe { ptr1.ptr.offset(256 * 2) } <= ptr2.ptr);
+}
+
+#[test]
+#[ignore]
+fn playback_to_plughw_mmap() {
+ use crate::pcm::*;
+ use crate::{ValueOr, Direction};
+ use std::ffi::CString;
+
+ let pcm = PCM::open(&*CString::new("plughw:1").unwrap(), Direction::Playback, false).unwrap();
+ let hwp = HwParams::any(&pcm).unwrap();
+ hwp.set_channels(2).unwrap();
+ hwp.set_rate(44100, ValueOr::Nearest).unwrap();
+ hwp.set_format(Format::s16()).unwrap();
+ hwp.set_access(Access::MMapInterleaved).unwrap();
+ pcm.hw_params(&hwp).unwrap();
+ let mut m = pcm.direct_mmap_playback::<i16>().unwrap();
+
+ assert_eq!(m.status().state(), State::Prepared);
+ assert_eq!(m.appl_ptr(), 0);
+ assert_eq!(m.hw_ptr(), 0);
+
+ println!("{:?}", m);
+ let mut i = (0..(m.buffer_size() * 2)).map(|i|
+ (((i / 2) as f32 * 2.0 * ::std::f32::consts::PI / 128.0).sin() * 8192.0) as i16);
+ m.write(&mut i);
+ assert_eq!(m.appl_ptr(), m.buffer_size());
+
+ pcm.start().unwrap();
+ pcm.drain().unwrap();
+ assert_eq!(m.appl_ptr(), m.buffer_size());
+ assert!(m.hw_ptr() >= m.buffer_size());
+}