Skip to content
File

Blob: firmware/crates/esp32-radio/src/platform/music.rs

rust104 lines
1//! One bounded read cache. Native reads copy synchronously into Rust-owned bytes.
2use super::ffi;
3use crate::error::{Error, Result};
4use radio_core::music::{Error as ReadError, MAX_MUSIC_BYTES, ReadAt};
5 
6const CACHE_BYTES: usize = 32 * 1024;
7 
8pub(crate) struct MusicStorage {
9 length: usize,
10 cache: Vec<u8>,
11 start: usize,
12 used: usize,
13 validating: bool,
14 pub(crate) reads: u32,
15 pub(crate) max_read_us: u32,
16}
17 
18impl std::fmt::Debug for MusicStorage {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 f.debug_struct("MusicStorage")
21 .field("length", &self.length)
22 .field("cache_bytes", &self.cache.len())
23 .field("reads", &self.reads)
24 .field("max_read_us", &self.max_read_us)
25 .finish_non_exhaustive()
26 }
27}
28 
29impl MusicStorage {
30 pub(crate) fn open() -> Result<Self> {
31 // SAFETY: IDF owns the immutable partition descriptor for the process lifetime.
32 let length = unsafe { ffi::radio_music_size() };
33 if length == 0 || length > MAX_MUSIC_BYTES {
34 return Err(Error::new("music partition is missing or too large"));
35 }
36 Ok(Self {
37 length,
38 cache: vec![0; CACHE_BYTES],
39 start: 0,
40 used: 0,
41 validating: true,
42 reads: 0,
43 max_read_us: 0,
44 })
45 }
46 pub(crate) fn finish_validation(&mut self) {
47 self.validating = false;
48 self.reads = 0;
49 self.max_read_us = 0;
50 }
51 pub(crate) fn cache_bytes(&self) -> usize {
52 self.cache.len()
53 }
54}
55 
56impl ReadAt for MusicStorage {
57 fn size(&self) -> usize {
58 self.length
59 }
60 fn read_exact(&mut self, offset: usize, out: &mut [u8]) -> std::result::Result<(), ReadError> {
61 if offset
62 .checked_add(out.len())
63 .is_none_or(|end| end > self.length)
64 {
65 return Err(ReadError::Truncated);
66 }
67 let mut cursor = offset;
68 let mut copied = 0;
69 while copied < out.len() {
70 if cursor < self.start || cursor >= self.start + self.used {
71 let start = cursor / CACHE_BYTES * CACHE_BYTES;
72 let length = CACHE_BYTES.min(self.length - start);
73 let began = super::now_us();
74 // SAFETY: cache is initialized, exclusively borrowed and large enough.
75 // The native function validates partition bounds, copies synchronously,
76 // and retains no pointer. No flash writes occur while this reader lives.
77 let result =
78 unsafe { ffi::radio_music_read(start, self.cache.as_mut_ptr(), length) };
79 if result != 0 {
80 return Err(ReadError::Read);
81 }
82 self.reads = self.reads.saturating_add(1);
83 self.max_read_us = self
84 .max_read_us
85 .max(super::now_us().saturating_sub(began).min(u32::MAX as u64) as u32);
86 self.start = start;
87 self.used = length;
88 // Full-catalog CRC validation can keep core 0 busy for seconds.
89 // Let its idle task run between cache fills; playback reads do
90 // not take this extra sleep.
91 if self.validating {
92 std::thread::sleep(std::time::Duration::from_millis(1));
93 }
94 }
95 let within = cursor - self.start;
96 let count = (self.used - within).min(out.len() - copied);
97 out[copied..copied + count].copy_from_slice(&self.cache[within..within + count]);
98 cursor += count;
99 copied += count;
100 }
101 Ok(())
102 }
103}