1use std::ffi::{c_char, c_void, CString};
30use std::path::Path;
31use std::sync::mpsc::{Receiver, SyncSender};
32use std::sync::{Condvar, Mutex};
33use std::time::Duration;
34
35pub use rockbox_metadata::Metadata;
36
37extern "C" {
38 fn rbcodec_set_sink(
39 cb: Option<unsafe extern "C" fn(*mut c_void, *const i16, usize, std::ffi::c_ulong)>,
40 user: *mut c_void,
41 );
42 fn rbcodec_open(path: *const c_char) -> i32;
43 fn rbcodec_run() -> i32;
44 fn rbcodec_close();
45 fn rbcodec_request_seek(time_ms: std::ffi::c_long);
46 fn rbcodec_request_halt();
47 fn rbcodec_elapsed_ms() -> std::ffi::c_ulong;
48}
49
50#[derive(Debug, Clone)]
52pub struct Chunk {
53 pub pcm: Vec<i16>,
55 pub sample_rate: u32,
57}
58
59#[derive(Debug)]
61pub enum Error {
62 Open(std::path::PathBuf),
64 Parse(std::path::PathBuf),
66 CodecNotAvailable(String),
69 CodecInit(std::path::PathBuf),
71 InvalidPath,
73 OutOfMemory,
75}
76
77impl std::fmt::Display for Error {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Error::Open(p) => write!(f, "cannot open {}", p.display()),
81 Error::Parse(p) => write!(f, "unrecognized or corrupt audio file {}", p.display()),
82 Error::CodecNotAvailable(c) => write!(
83 f,
84 "no decoder for {c} compiled in (enable the matching cargo feature)"
85 ),
86 Error::CodecInit(p) => write!(f, "codec failed to initialize for {}", p.display()),
87 Error::InvalidPath => write!(f, "path contains a NUL byte"),
88 Error::OutOfMemory => write!(f, "out of memory"),
89 }
90 }
91}
92
93impl std::error::Error for Error {}
94
95struct Gate {
97 busy: Mutex<bool>,
98 cv: Condvar,
99}
100
101static GATE: Gate = Gate {
102 busy: Mutex::new(false),
103 cv: Condvar::new(),
104};
105
106impl Gate {
107 fn acquire(&self) {
108 let mut busy = self.busy.lock().unwrap_or_else(|e| e.into_inner());
109 while *busy {
110 busy = self.cv.wait(busy).unwrap_or_else(|e| e.into_inner());
111 }
112 *busy = true;
113 }
114
115 fn release(&self) {
116 *self.busy.lock().unwrap_or_else(|e| e.into_inner()) = false;
117 self.cv.notify_one();
118 }
119}
120
121enum Msg {
122 Pcm(Chunk),
123 Done(i32),
124}
125
126struct SinkCtx {
127 tx: SyncSender<Msg>,
128}
129
130unsafe extern "C" fn sink_trampoline(
135 user: *mut c_void,
136 pcm: *const i16,
137 frames: usize,
138 frequency: std::ffi::c_ulong,
139) {
140 let ctx = &*(user as *const SinkCtx);
141 let samples = std::slice::from_raw_parts(pcm, frames * 2);
142 let chunk = Chunk {
143 pcm: samples.to_vec(),
144 sample_rate: frequency as u32,
145 };
146 if ctx.tx.send(Msg::Pcm(chunk)).is_err() {
147 rbcodec_request_halt();
148 }
149}
150
151pub struct Decoder {
153 rx: Receiver<Msg>,
154 thread: Option<std::thread::JoinHandle<()>>,
155 _sink: Box<SinkCtx>,
157 meta: Metadata,
158 status: Option<i32>,
159}
160
161impl Decoder {
162 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
165 let path = path.as_ref();
166 let c_path =
167 CString::new(path.to_string_lossy().as_bytes()).map_err(|_| Error::InvalidPath)?;
168
169 let meta = rockbox_metadata::read(path).map_err(|e| match e {
171 rockbox_metadata::Error::Open(p) => Error::Open(p),
172 rockbox_metadata::Error::OutOfMemory => Error::OutOfMemory,
173 _ => Error::Parse(path.to_path_buf()),
174 })?;
175
176 GATE.acquire();
177
178 let (tx, rx) = std::sync::mpsc::sync_channel::<Msg>(8);
179 let sink = Box::new(SinkCtx { tx: tx.clone() });
180
181 let rc = unsafe {
182 rbcodec_set_sink(
183 Some(sink_trampoline),
184 &*sink as *const SinkCtx as *mut c_void,
185 );
186 rbcodec_open(c_path.as_ptr())
187 };
188 if rc != 0 {
189 GATE.release();
190 return Err(match rc {
191 -1 => Error::Open(path.to_path_buf()),
192 -2 => Error::Parse(path.to_path_buf()),
193 -3 => Error::CodecNotAvailable(meta.codec.clone()),
194 -6 => Error::OutOfMemory,
195 _ => Error::CodecInit(path.to_path_buf()),
196 });
197 }
198
199 let thread = std::thread::Builder::new()
200 .name("rbcodec".into())
201 .spawn(move || {
202 let status = unsafe { rbcodec_run() };
203 let _ = tx.send(Msg::Done(status));
204 })
205 .expect("spawn codec thread");
206
207 Ok(Decoder {
208 rx,
209 thread: Some(thread),
210 _sink: sink,
211 meta,
212 status: None,
213 })
214 }
215
216 pub fn metadata(&self) -> &Metadata {
218 &self.meta
219 }
220
221 pub fn next_chunk(&mut self) -> Option<Chunk> {
224 if self.status.is_some() {
225 return None;
226 }
227 match self.rx.recv() {
228 Ok(Msg::Pcm(chunk)) => Some(chunk),
229 Ok(Msg::Done(status)) => {
230 self.status = Some(status);
231 None
232 }
233 Err(_) => {
234 self.status = Some(-1);
235 None
236 }
237 }
238 }
239
240 pub fn seek(&mut self, pos: Duration) {
243 unsafe { rbcodec_request_seek(pos.as_millis() as std::ffi::c_long) };
244 }
245
246 pub fn elapsed(&self) -> Duration {
248 Duration::from_millis(unsafe { rbcodec_elapsed_ms() } as u64)
249 }
250
251 pub fn status(&self) -> Option<i32> {
254 self.status
255 }
256}
257
258impl Drop for Decoder {
259 fn drop(&mut self) {
260 if self.status.is_none() {
266 unsafe { rbcodec_request_halt() };
267 while let Ok(msg) = self.rx.recv_timeout(Duration::from_secs(5)) {
269 if matches!(msg, Msg::Done(_)) {
270 break;
271 }
272 }
273 }
274 if let Some(t) = self.thread.take() {
275 let _ = t.join();
276 }
277 unsafe {
278 rbcodec_close();
279 rbcodec_set_sink(None, std::ptr::null_mut());
280 }
281 GATE.release();
282 }
283}