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: 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
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: 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
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: i64 = hwptr.wrapping_add(buffersize).wrapping_sub(applptr);
286 let r: i64 = 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: i64 = 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: 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
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(func:"Wrong direction"));
352 }
353 let boundary: i64 = 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: 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: i64 = 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: 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
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: 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
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: 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}
629