use libc; use std::{mem, ptr, fmt, cmp}; usecrate::error::{Error, Result}; use std::os::unix::io::RawFd; usecrate::{pcm, PollDescriptors, Direction}; usecrate::pcm::Frames; use std::marker::PhantomData;
usesuper::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. pubstruct 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 pubunsafefn sync_ptr(fd: RawFd, hwsync: bool, appl_ptr: Option<pcm::Frames>, avail_min: Option<pcm::Frames>) -> Result<Self> { letmut 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"))
}
}
pubfn hw_ptr(&self) -> pcm::Frames { self.0.hw_ptr as pcm::Frames } pubfn state(&self) -> pcm::State { unsafe { mem::transmute(self.0.state as u8) } /* valid range checked in sync_ptr */ } pubfn 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)] pubstruct Status(DriverMemory<snd_pcm_mmap_status>);
fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> { letmut 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)
}
/// Current PCM state. pubfn 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). pubfn 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...? pubfn 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...? pubfn 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)] pubstruct Control(DriverMemory<snd_pcm_mmap_control>);
/// Read number of frames application has read or written /// /// This value wraps at "boundary" (a large value you can read from SwParams). pubfn 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. pubfn 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 pubfn 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 pubfn set_avail_min(&self, value: pcm::Frames) { unsafe {
ptr::write_volatile(&mut (*self.0.ptr).avail_min, value as snd_pcm_uframes_t)
}
}
}
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 }
}
}
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 }
}
}
pubtype MmapPlayback<S> = MmapIO<S, Playback>;
pubtype MmapCapture<S> = MmapIO<S, Capture>;
#[derive(Debug)] /// Struct containing direct I/O functions shared between playback and capture. pubstruct 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. pubstruct 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. pubfn 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. pubunsafefn write_samples<I: Iterator<Item=S>>(&self, i: &e='color:red'>mut I) -> (bool, isize) { letmut z = 0; let max_samples = self.samples(); while z < max_samples { let b = iflet 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> { /// Read current status pubfn status(&self) -> &Status { &self.ss }
/// Read current number of frames committed by application /// /// This number wraps at 'boundary'. #[inline] pubfn appl_ptr(&self) -> Frames { self.c.appl_ptr() }
/// Read current number of frames read / written by hardware /// /// This number wraps at 'boundary'. #[inline] pubfn hw_ptr(&self) -> Frames { self.ss.hw_ptr() }
/// The number at which hw_ptr and appl_ptr wraps. #[inline] pubfn boundary(&self) -> Frames { self.bound }
/// Total number of frames in hardware buffer #[inline] pubfn buffer_size(&self) -> Frames { self.data.frames }
/// Number of channels in stream #[inline] pubfn 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. pubfn commit(&self, v: Frames) { letmut 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. pubfn 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. pubfn 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; letmut 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. pubfn write<I: Iterator<Item=S>>(&mutself, i: &mut I) -> Frames { let (data, more_data) = self.data_ptr(); let (iter_end, samples) = unsafe { data.write_samples(i) }; letmut z = samples / data.channels as isize; if !iter_end { iflet 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. pubfn iter(&mutself) -> CaptureIter<S> { let (data, more_data) = self.data_ptr();
CaptureIter {
m: self,
samples: data,
p_offs: 0,
read_samples: 0,
next_p: more_data,
}
}
}
impl<'a, S: 'static> Drop for CaptureIter<'a, S> { fn drop(&mutself) { 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() { usecrate::pcm::*; usecrate::{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();
#[test] #[ignore] // Not everyone has a record device on plughw:1. So let's ignore this test by default. fn record_from_plughw_mmap() { usecrate::pcm::*; usecrate::{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);
letmut m = pcm.direct_mmap_capture::<i16>().unwrap();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.