| 1 | /*! |
| 2 | This module bypasses alsa-lib and directly read and write into memory mapped kernel memory. |
| 3 | In case of the sample memory, this is in many cases the DMA buffers that is transferred to the sound card. |
| 4 | |
| 5 | The 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 | |
| 15 | Note: Not all sound card drivers support this direct method of communication; although almost all |
| 16 | modern/common ones do. It only works with hardware devices though (such as "hw:xxx" device strings), |
| 17 | don't expect it to work with, e g, the PulseAudio plugin or so. |
| 18 | |
| 19 | For an example of how to use this mode, look in the "synth-example" directory. |
| 20 | */ |
| 21 | |
| 22 | use libc; |
| 23 | use std::{mem, ptr, fmt, cmp}; |
| 24 | use crate::error::{Error, Result}; |
| 25 | use std::os::unix::io::RawFd; |
| 26 | use crate::{pcm, PollDescriptors, Direction}; |
| 27 | use crate::pcm::Frames; |
| 28 | use std::marker::PhantomData; |
| 29 | |
| 30 | use 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. |
| 35 | pub struct SyncPtrStatus(snd_pcm_mmap_status); |
| 36 | |
| 37 | impl 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)] |
| 90 | pub struct Status(DriverMemory<snd_pcm_mmap_status>); |
| 91 | |
| 92 | fn pcm_to_fd(p: &pcm::PCM) -> Result<RawFd> { |
| 93 | let mut fds: [libc::pollfd; 1] = unsafe { mem::zeroed() }; |
| 94 | let c: usize = PollDescriptors::fill(self:p, &mut fds)?; |
| 95 | if c != 1 { |
| 96 | return Err(Error::unsupported(func:"snd_pcm_poll_descriptors returned wrong number of fds" )) |
| 97 | } |
| 98 | Ok(fds[0].fd) |
| 99 | } |
| 100 | |
| 101 | impl 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)] |
| 158 | pub struct Control(DriverMemory<snd_pcm_mmap_control>); |
| 159 | |
| 160 | impl 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 | |
| 202 | struct DriverMemory<S> { |
| 203 | ptr: *mut S, |
| 204 | size: libc::size_t, |
| 205 | } |
| 206 | |
| 207 | impl<S> fmt::Debug for DriverMemory<S> { |
| 208 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "DriverMemory( {:?})" , self.ptr) } |
| 209 | } |
| 210 | |
| 211 | impl<S> DriverMemory<S> { |
| 212 | fn new(fd: RawFd, count: usize, offs: libc::off_t, writable: bool) -> Result<Self> { |
| 213 | let mut total: usize = count * mem::size_of::<S>(); |
| 214 | let ps: usize = pagesize(); |
| 215 | assert!(total > 0); |
| 216 | if total % ps != 0 { total += ps - total % ps }; |
| 217 | let flags: i32 = if writable { libc::PROT_WRITE | libc::PROT_READ } else { libc::PROT_READ }; |
| 218 | let p: *mut c_void = unsafe { libc::mmap(addr:ptr::null_mut(), len:total, prot:flags, flags:libc::MAP_FILE | libc::MAP_SHARED, fd, offset:offs) }; |
| 219 | if p.is_null() || p == libc::MAP_FAILED { |
| 220 | Err(Error::last(func:"mmap (of driver memory)" )) |
| 221 | } else { |
| 222 | Ok(DriverMemory { ptr: p as *mut S, size: total }) |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | unsafe impl<S> Send for DriverMemory<S> {} |
| 228 | unsafe impl<S> Sync for DriverMemory<S> {} |
| 229 | |
| 230 | impl<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)] |
| 237 | struct SampleData<S> { |
| 238 | mem: DriverMemory<S>, |
| 239 | frames: pcm::Frames, |
| 240 | channels: u32, |
| 241 | } |
| 242 | |
| 243 | impl<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 |
| 272 | pub 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)] |
| 279 | pub struct Playback; |
| 280 | |
| 281 | impl 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: usize = hwptr.wrapping_add(buffersize).wrapping_sub(applptr); |
| 286 | let r: usize = 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)] |
| 293 | pub struct Capture; |
| 294 | |
| 295 | impl 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 | |
| 304 | pub type MmapPlayback<S> = MmapIO<S, Playback>; |
| 305 | |
| 306 | pub type MmapCapture<S> = MmapIO<S, Capture>; |
| 307 | |
| 308 | #[derive (Debug)] |
| 309 | /// Struct containing direct I/O functions shared between playback and capture. |
| 310 | pub 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. |
| 320 | pub struct RawSamples<S> { |
| 321 | pub ptr: *mut S, |
| 322 | pub frames: Frames, |
| 323 | pub channels: u32, |
| 324 | } |
| 325 | |
| 326 | impl<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: isize = 0; |
| 337 | let max_samples: isize = self.samples(); |
| 338 | while z < max_samples { |
| 339 | let b: S = if let Some(b: S) = i.next() { b } else { return (true, z) }; |
| 340 | ptr::write_volatile(self.ptr.offset(z), src:b); |
| 341 | z += 1; |
| 342 | }; |
| 343 | (false, z) |
| 344 | } |
| 345 | |
| 346 | } |
| 347 | |
| 348 | impl<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(func:"Wrong direction" )); |
| 352 | } |
| 353 | let boundary: as Try>::Output = 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 | |
| 364 | pub (crate) fn new_mmap<S, D: MmapDir>(p: &pcm::PCM) -> Result<MmapIO<S, D>> { MmapIO::new(p) } |
| 365 | |
| 366 | impl<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 | |
| 438 | impl<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: RawSamples, more_data: Option>) = self.data_ptr(); |
| 442 | let (iter_end: bool, samples: isize) = unsafe { data.write_samples(i) }; |
| 443 | let mut z: isize = samples / data.channels as isize; |
| 444 | if !iter_end { |
| 445 | if let Some(data2: RawSamples) = more_data { |
| 446 | let (_, samples2: isize) = unsafe { data2.write_samples(i) }; |
| 447 | z += samples2 / data2.channels as isize; |
| 448 | } |
| 449 | } |
| 450 | let z: isize = z as Frames; |
| 451 | self.commit(z); |
| 452 | z |
| 453 | } |
| 454 | } |
| 455 | |
| 456 | impl<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: RawSamples, more_data: Option>) = 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 |
| 474 | pub 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 | |
| 482 | impl<'a, S: 'static + Copy> CaptureIter<'a, S> { |
| 483 | fn handle_max(&mut self) { |
| 484 | self.p_offs = 0; |
| 485 | if let Some(p2: RawSamples) = 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 | |
| 495 | impl<'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: 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 | |
| 511 | impl<'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. |
| 520 | fn 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. |
| 556 | fn 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 ] |
| 600 | fn 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 | } |
| 629 | |