fuchsia_audio/
vmo_buffer.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::Format;
6use fuchsia_runtime::vmar_root_self;
7
8use thiserror::Error;
9
10// Given coefficients for an affine function that transforms 'a' into 'b', apply it to 'a_value'.
11// The affine function is:
12//   b = ((a_value - a_offset) * numerator / denominator) + b_offset
13//
14// Generally we use these functions to translate between a running frame and a time. 'frame_number'
15// is usually positive (and monotonic time should always be) but there are corner cases such as
16// `presentation_frame()` where the result can be negative, so params and retval are signed. To
17// retain full precision during the multiply-then-divide, we use i128. When available, we will use
18// div_floor so we always round in the same direction (toward -INF) regardless of pos or neg values.
19pub fn apply_affine_function(
20    a_value: i64,
21    numerator: u32,
22    denominator: u32,
23    a_offset: i64,
24    b_offset: i64,
25) -> i64 {
26    let numerator_i128 = numerator as i128;
27    let denominator_i128 = denominator as i128;
28
29    // TODO: When `int_roundings` is released, do this instead:
30    // let intermediate = (a_value - a_offset) as i128 * numerator_i128;
31    // /Fractions should round NEGATIVELY (toward -INF, not toward ZERO as happens in int division).
32    // intermediate.div_floor(denominator_i128) as i64 + b_offset
33
34    // Fractions should round NEGATIVELY (toward -INF, not toward ZERO as happens in int division).
35    let mut intermediate = (a_value - a_offset) as i128 * numerator_i128;
36    // Check for negative; if so, subtract "almost-one" before the DIV so it rounds toward -INF.
37    if intermediate < 0 {
38        intermediate = intermediate - denominator_i128 + 1;
39    }
40    (intermediate / denominator_i128) as i64 + b_offset
41}
42
43/// A VMO-backed ring buffer that contains frames of audio.
44pub struct VmoBuffer {
45    /// VMO that contains `num_frames` of audio in `format`.
46    vmo: zx::Vmo,
47
48    /// Size of the VMO, in bytes.
49    vmo_size_bytes: u64,
50
51    /// Number of frames in the VMO.
52    num_frames: u64,
53
54    /// Format of each frame.
55    format: Format,
56
57    /// Base address of the memory-mapped `vmo`.
58    base_address: usize,
59}
60
61impl VmoBuffer {
62    pub fn new(vmo: zx::Vmo, num_frames: u64, format: Format) -> Result<Self, VmoBufferError> {
63        // Ensure that the VMO is big enough to hold `num_frames` of audio in the given `format`.
64        let data_size_bytes = num_frames * format.bytes_per_frame() as u64;
65        let vmo_size_bytes = vmo.get_size().map_err(VmoBufferError::VmoGetSize)?;
66
67        if data_size_bytes > vmo_size_bytes {
68            return Err(VmoBufferError::VmoTooSmall { data_size_bytes, vmo_size_bytes });
69        }
70
71        let base_address = vmar_root_self()
72            .map(
73                0,
74                &vmo,
75                0,
76                vmo_size_bytes as usize,
77                // TODO(https://fxbug.dev/356700720): Don't map read-only VMOs with `PERM_WRITE`.
78                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
79            )
80            .map_err(VmoBufferError::VmoMap)?;
81
82        log::debug!(
83            "format {:?} num frames {} data_size_bytes {}",
84            format,
85            num_frames,
86            data_size_bytes
87        );
88
89        Ok(Self { vmo, vmo_size_bytes, num_frames, format, base_address })
90    }
91
92    /// Returns the size of the buffer in bytes.
93    ///
94    /// This may be less than the size of the backing VMO.
95    pub fn data_size_bytes(&self) -> u64 {
96        self.num_frames * self.format.bytes_per_frame() as u64
97    }
98
99    /// Writes all frames from `buf` to the ring buffer at position `running_frame`.
100    pub fn write_to_frame(&self, running_frame: u64, buf: &[u8]) -> Result<(), VmoBufferError> {
101        if !buf.len().is_multiple_of(self.format.bytes_per_frame() as usize) {
102            return Err(VmoBufferError::BufferIncompleteFrames);
103        }
104        let frame_offset = running_frame % self.num_frames;
105        let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
106        let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
107
108        // Check whether the buffer can be written to contiguously or if the write needs to be
109        // split into two: one until the end of the buffer and one starting from the beginning.
110        if (frame_offset + num_frames_in_buf) <= self.num_frames {
111            self.vmo.write(buf, byte_offset as u64).map_err(VmoBufferError::VmoWrite)?;
112            // Flush cache so that hardware reads most recent write.
113            self.flush_cache(byte_offset, buf.len()).map_err(VmoBufferError::VmoFlushCache)?;
114            log::debug!(
115                "At {} wrote {} frames at ring_buf pos {} (running frame {}-{})",
116                zx::MonotonicInstant::get().into_nanos(),
117                num_frames_in_buf,
118                frame_offset,
119                running_frame,
120                running_frame + num_frames_in_buf
121            );
122            fuchsia_trace::instant!(
123                c"audio-streaming",
124                c"AudioLib::VmoBuffer::write_to_frame",
125                fuchsia_trace::Scope::Process,
126                "source bytes to write" => buf.len(),
127                "source frames to write" => num_frames_in_buf,
128                "frame size" => self.format.bytes_per_frame(),
129                "dest RB size (frames)" => self.num_frames,
130                "dest RB running frame" => running_frame,
131                "dest RB frame position" => frame_offset,
132                "vmo write start" => byte_offset,
133                "vmo write len (bytes)" => buf.len()
134            );
135        } else {
136            let frames_to_write_until_end = self.num_frames - frame_offset;
137            let bytes_until_buffer_end =
138                frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
139
140            self.vmo
141                .write(&buf[..bytes_until_buffer_end], byte_offset as u64)
142                .map_err(VmoBufferError::VmoWrite)?;
143            // Flush cache so that hardware reads most recent write.
144            self.flush_cache(byte_offset, bytes_until_buffer_end)
145                .map_err(VmoBufferError::VmoFlushCache)?;
146            log::debug!(
147                "At {} first {} frames at ring_buf pos {} (running frame {}-{})",
148                zx::MonotonicInstant::get().into_nanos(),
149                frames_to_write_until_end,
150                frame_offset,
151                running_frame,
152                running_frame + frames_to_write_until_end
153            );
154
155            if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
156                log::error!(
157                    "Remainder of write buffer (size {}) is too big for the vmo (size {})",
158                    buf[bytes_until_buffer_end..].len(),
159                    self.vmo_size_bytes
160                );
161            }
162
163            // Write what remains to the beginning of the buffer.
164            self.vmo.write(&buf[bytes_until_buffer_end..], 0).map_err(VmoBufferError::VmoWrite)?;
165            self.flush_cache(0, buf.len() - bytes_until_buffer_end)
166                .map_err(VmoBufferError::VmoFlushCache)?;
167            fuchsia_trace::instant!(
168                c"audio-streaming",
169                c"AudioLib::VmoBuffer::write_to_frame",
170                fuchsia_trace::Scope::Process,
171                "source bytes to write" => buf.len(),
172                "source frames to write" => num_frames_in_buf,
173                "frame size" => self.format.bytes_per_frame(),
174                "dest RB size (frames)" => self.num_frames,
175                "dest RB running frame" => running_frame,
176                "dest RB frame position" => frame_offset,
177                "first vmo write start" => byte_offset,
178                "first vmo write len (bytes)" => bytes_until_buffer_end,
179                "second vmo write start" => 0,
180                "second vmo write len (bytes)" => buf.len() - bytes_until_buffer_end
181            );
182            log::debug!(
183                "At {}, then {} frames at ring_buf frame 0 (running frame {}-{})",
184                zx::MonotonicInstant::get().into_nanos(),
185                num_frames_in_buf - frames_to_write_until_end,
186                running_frame + frames_to_write_until_end,
187                running_frame + num_frames_in_buf
188            );
189        }
190
191        Ok(())
192    }
193
194    /// Reads frames from the ring buffer into `buf` starting at position `running_frame`.
195    pub fn read_from_frame(
196        &self,
197        running_frame: u64,
198        buf: &mut [u8],
199    ) -> Result<(), VmoBufferError> {
200        if !buf.len().is_multiple_of(self.format.bytes_per_frame() as usize) {
201            return Err(VmoBufferError::BufferIncompleteFrames);
202        }
203        let frame_offset = running_frame % self.num_frames;
204        let byte_offset = frame_offset as usize * self.format.bytes_per_frame() as usize;
205        let num_frames_in_buf = buf.len() as u64 / self.format.bytes_per_frame() as u64;
206
207        // Check whether the buffer can be read from contiguously or if the read needs to be
208        // split into two: one until the end of the buffer and one starting from the beginning.
209        if (frame_offset + num_frames_in_buf) <= self.num_frames {
210            // Flush and invalidate cache so we read the hardware's most recent write.
211            log::debug!("frame {} reading starting from position {}", running_frame, byte_offset);
212            self.flush_invalidate_cache(byte_offset, buf.len())
213                .map_err(VmoBufferError::VmoFlushCache)?;
214            self.vmo.read(buf, byte_offset as u64).map_err(VmoBufferError::VmoRead)?;
215        } else {
216            let frames_to_write_until_end = self.num_frames - frame_offset;
217            let bytes_until_buffer_end =
218                frames_to_write_until_end as usize * self.format.bytes_per_frame() as usize;
219
220            log::debug!(
221                "frame {} reading starting from position {}  (with looparound)",
222                running_frame,
223                byte_offset
224            );
225            // Flush and invalidate cache so we read the hardware's most recent write.
226            self.flush_invalidate_cache(byte_offset, bytes_until_buffer_end)
227                .map_err(VmoBufferError::VmoFlushCache)?;
228            self.vmo
229                .read(&mut buf[..bytes_until_buffer_end], byte_offset as u64)
230                .map_err(VmoBufferError::VmoRead)?;
231
232            if buf[bytes_until_buffer_end..].len() > self.vmo_size_bytes as usize {
233                log::error!("Remainder of read buffer is too big for the vmo.");
234            }
235
236            self.flush_invalidate_cache(0, buf.len() - bytes_until_buffer_end)
237                .map_err(VmoBufferError::VmoFlushCache)?;
238            self.vmo
239                .read(&mut buf[bytes_until_buffer_end..], 0)
240                .map_err(VmoBufferError::VmoRead)?;
241        }
242        Ok(())
243    }
244
245    /// Flush the cache for a portion of the memory-mapped VMO.
246    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
247    fn flush_cache(&self, offset_bytes: usize, size_bytes: usize) -> Result<(), zx::Status> {
248        assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
249        let status = unsafe {
250            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
251            zx::sys::zx_cache_flush(
252                (self.base_address + offset_bytes) as *mut u8,
253                size_bytes,
254                zx::sys::ZX_CACHE_FLUSH_DATA,
255            )
256        };
257        zx::Status::ok(status)
258    }
259
260    /// Flush and invalidate cache for a portion of the memory-mapped VMO.
261    // TODO(https://fxbug.dev/328478694): Remove these methods once VMOs are created without caching
262    fn flush_invalidate_cache(
263        &self,
264        offset_bytes: usize,
265        size_bytes: usize,
266    ) -> Result<(), zx::Status> {
267        assert!(offset_bytes + size_bytes <= self.vmo_size_bytes as usize);
268        let status = unsafe {
269            // SAFETY: The range was asserted above to be within the mapped region of the VMO.
270            zx::sys::zx_cache_flush(
271                (self.base_address + offset_bytes) as *mut u8,
272                size_bytes,
273                zx::sys::ZX_CACHE_FLUSH_DATA | zx::sys::ZX_CACHE_FLUSH_INVALIDATE,
274            )
275        };
276        zx::Status::ok(status)
277    }
278}
279
280impl Drop for VmoBuffer {
281    fn drop(&mut self) {
282        // SAFETY: `base_address` and `vmo_size_bytes` are private to self,
283        // so no other code can observe that this mapping has been removed.
284        unsafe {
285            vmar_root_self().unmap(self.base_address, self.vmo_size_bytes as usize).unwrap();
286        }
287    }
288}
289
290#[derive(Error, Debug)]
291pub enum VmoBufferError {
292    #[error(
293        "VMO is too small ({vmo_size_bytes} bytes) to hold ring buffer data ({data_size_bytes} bytes)"
294    )]
295    VmoTooSmall { data_size_bytes: u64, vmo_size_bytes: u64 },
296
297    #[error("Buffer size is invalid; contains incomplete frames")]
298    BufferIncompleteFrames,
299
300    #[error("Failed to memory map VMO: {}", .0)]
301    VmoMap(#[source] zx::Status),
302
303    #[error("Failed to get VMO size: {}", .0)]
304    VmoGetSize(#[source] zx::Status),
305
306    #[error("Failed to flush VMO memory cache: {}", .0)]
307    VmoFlushCache(#[source] zx::Status),
308
309    #[error("Failed to read from VMO: {}", .0)]
310    VmoRead(#[source] zx::Status),
311
312    #[error("Failed to write to VMO: {}", .0)]
313    VmoWrite(#[source] zx::Status),
314}
315
316#[cfg(test)]
317mod test {
318    use super::*;
319    use crate::format::SampleType;
320    use assert_matches::assert_matches;
321
322    // For a given start time, validate that one second translates to a second of frames.
323    #[test]
324    fn affine_function_time_to_frames() {
325        let start_time = zx::MonotonicInstant::from_nanos(1_234_567_890);
326        let current_time = start_time + zx::MonotonicDuration::from_seconds(1);
327
328        let frames_per_second: u32 = 48000;
329        let nanos_per_second: u32 = 1_000_000_000;
330
331        let start_frame: i64 = 1_234_567;
332        let expected_frame = start_frame + (frames_per_second as i64);
333
334        let current_frame = apply_affine_function(
335            current_time.into_nanos(),
336            frames_per_second,
337            nanos_per_second,
338            start_time.into_nanos(),
339            start_frame,
340        );
341
342        assert_eq!(current_frame, expected_frame);
343    }
344
345    // For a given start frame, validate that a second of frames translates to one second.
346    #[test]
347    fn affine_function_frames_to_time() {
348        let nanos_per_second: u32 = 1_000_000_000;
349        let frames_per_second: u32 = 48000;
350
351        let start_frame: i64 = 1_234_567;
352        let current_frame: i64 = start_frame + (frames_per_second as i64);
353
354        let start_time = zx::MonotonicInstant::from_nanos(1_234_567_890);
355        let expected_time = start_time + zx::MonotonicDuration::from_seconds(1);
356
357        let time_for_frame = apply_affine_function(
358            current_frame,
359            nanos_per_second,
360            frames_per_second,
361            start_frame,
362            start_time.into_nanos(),
363        );
364
365        assert_eq!(time_for_frame, expected_time.into_nanos());
366    }
367
368    // Validate that 0.999_999_999 second leads to a frame value that is NOT rounded up -- this
369    // should result in 47999 frames, not 48000.
370    #[test]
371    fn affine_function_rounds_down() {
372        let start_time = zx::MonotonicInstant::from_nanos(1_234_567_890);
373        let current_time = start_time + zx::MonotonicDuration::from_seconds(1)
374            - zx::MonotonicDuration::from_nanos(1);
375
376        let frames_per_second: u32 = 48000;
377        let nanos_per_second: u32 = 1_000_000_000;
378
379        let start_frame: i64 = 1_234_567;
380        let expected_frame = start_frame + (frames_per_second as i64) - 1;
381
382        let current_frame = apply_affine_function(
383            current_time.into_nanos(),
384            frames_per_second,
385            nanos_per_second,
386            start_time.into_nanos(),
387            start_frame,
388        );
389
390        assert_eq!(current_frame, expected_frame);
391    }
392
393    // Affine functions should always round the same direction.
394    // Thus they must round negatively (toward -INF), rather than rounding toward zero.
395    // Validate that -1.000_000_001 second leads to a frame value that is NOT rounded up -- this
396    // should result in -48001 frames, not -48000.
397    #[test]
398    fn affine_function_rounds_negatively() {
399        let start_time = zx::MonotonicInstant::from_nanos(1_234_567_890);
400        let current_time = start_time
401            - zx::MonotonicDuration::from_seconds(1)
402            - zx::MonotonicDuration::from_nanos(1);
403
404        let frames_per_second: u32 = 48000;
405        let nanos_per_second: u32 = 1_000_000_000;
406
407        let start_frame: i64 = 1_234_567;
408        let expected_frame = start_frame - (frames_per_second as i64) - 1;
409
410        let current_frame = apply_affine_function(
411            current_time.into_nanos(),
412            frames_per_second,
413            nanos_per_second,
414            start_time.into_nanos(),
415            start_frame,
416        );
417
418        assert_eq!(current_frame, expected_frame);
419    }
420
421    #[test]
422    fn vmobuffer_vmo_too_small() {
423        let format =
424            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 1 };
425
426        // VMO size is rounded up to the system page size.
427        let page_size = zx::system_get_page_size() as u64;
428        let num_frames = page_size + 1;
429        let vmo = zx::Vmo::create(page_size).unwrap();
430
431        assert_matches!(
432            VmoBuffer::new(vmo, num_frames, format).err(),
433            Some(VmoBufferError::VmoTooSmall { .. })
434        )
435    }
436
437    #[test]
438    fn vmobuffer_read_write() {
439        let format =
440            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
441        const NUM_FRAMES_VMO: u64 = 10;
442        const NUM_FRAMES_BUF: u64 = 5;
443        const SAMPLE: u8 = 42;
444
445        let vmo_size = format.bytes_per_frame() as u64 * NUM_FRAMES_VMO;
446        let buf_size = format.bytes_per_frame() as u64 * NUM_FRAMES_BUF;
447
448        let vmo = zx::Vmo::create(vmo_size).unwrap();
449
450        // Buffer used to read from the VmoBuffer.
451        let mut in_buf = vec![0; buf_size as usize];
452        // Buffer used to write to from the VmoBuffer.
453        let out_buf = vec![SAMPLE; buf_size as usize];
454
455        let vmo_buffer = VmoBuffer::new(vmo, NUM_FRAMES_VMO, format).unwrap();
456
457        // Write the buffer to the VmoBuffer, starting on the second frame (zero based).
458        vmo_buffer.write_to_frame(1, &out_buf).unwrap();
459
460        // Read back from the VmoBuffer.
461        vmo_buffer.read_from_frame(1, &mut in_buf).unwrap();
462
463        assert_eq!(in_buf, out_buf);
464    }
465
466    #[test]
467    fn vmobuffer_read_write_wrapping() {
468        let format =
469            Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
470        let page_size = zx::system_get_page_size() as u64;
471        let num_frames_vmo: u64 = page_size;
472        let num_frames_buf: u64 = page_size / 2;
473        const SAMPLE: u8 = 42;
474
475        let vmo_size = format.bytes_per_frame() as u64 * num_frames_vmo;
476        let buf_size = format.bytes_per_frame() as u64 * num_frames_buf;
477
478        let vmo = zx::Vmo::create(vmo_size).unwrap();
479
480        // Buffer used to read from the VmoBuffer.
481        let mut in_buf = vec![0; buf_size as usize];
482        // Buffer used to write to from the VmoBuffer.
483        let out_buf = vec![SAMPLE; buf_size as usize];
484
485        let vmo_buffer = VmoBuffer::new(vmo, num_frames_vmo, format).unwrap();
486
487        // Write and read at the last frame to ensure the operations wrap to the beginning.
488        let frame = num_frames_vmo - 1;
489        assert!(frame + num_frames_buf > num_frames_vmo);
490
491        // Write the buffer to the VmoBuffer, starting on the last frame.
492        vmo_buffer.write_to_frame(frame, &out_buf).unwrap();
493
494        // Read back from the VmoBuffer.
495        vmo_buffer.read_from_frame(frame, &mut in_buf).unwrap();
496
497        assert_eq!(in_buf, out_buf);
498    }
499}