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::*;
pub struct SyncPtrStatus(snd_pcm_mmap_status);
impl SyncPtrStatus {
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) } }
pub fn htstamp(&self) -> libc::timespec { self.0.tstamp }
}
#[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)
}
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)
}
}
pub fn hw_ptr(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).hw_ptr) as pcm::Frames
}
}
pub fn htstamp(&self) -> libc::timespec {
unsafe {
ptr::read_volatile(&(*self.0.ptr).tstamp)
}
}
pub fn audio_htstamp(&self) -> libc::timespec {
unsafe {
ptr::read_volatile(&(*self.0.ptr).audio_tstamp)
}
}
}
#[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)
}
pub fn appl_ptr(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).appl_ptr) as pcm::Frames
}
}
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)
}
}
pub fn avail_min(&self) -> pcm::Frames {
unsafe {
ptr::read_volatile(&(*self.0.ptr).avail_min) as pcm::Frames
}
}
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
};
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,
})
}
}
pub trait MmapDir: fmt::Debug {
const DIR: Direction;
fn avail(hwptr: Frames, applptr: Frames, buffersize: Frames, boundary: Frames) -> Frames;
}
#[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 }
}
}
#[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)]
pub struct MmapIO<S, D> {
data: SampleData<S>,
c: Control,
ss: Status,
bound: Frames,
dir: PhantomData<*const D>,
}
#[derive(Debug, Clone, Copy)]
pub struct RawSamples<S> {
pub ptr: *mut S,
pub frames: Frames,
pub channels: u32,
}
impl<S> RawSamples<S> {
#[inline]
pub fn samples(&self) -> isize { self.frames as isize * (self.channels as isize) }
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> {
pub fn status(&self) -> &Status { &self.ss }
#[inline]
pub fn appl_ptr(&self) -> Frames { self.c.appl_ptr() }
#[inline]
pub fn hw_ptr(&self) -> Frames { self.ss.hw_ptr() }
#[inline]
pub fn boundary(&self) -> Frames { self.bound }
#[inline]
pub fn buffer_size(&self) -> Frames { self.data.frames }
#[inline]
pub fn channels(&self) -> u32 { self.data.channels }
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)
}
pub fn avail(&self) -> Frames { D::avail(self.hw_ptr(), self.appl_ptr(), self.buffer_size(), self.boundary()) }
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();
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> {
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> {
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,
}
}
}
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; }
}
}
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] 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] 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());
}