1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
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());
}
|