Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
02d518a
audio: write audio data to fifo
tsirysndr Dec 23, 2024
c37decf
audio: skip fifo if ROCKBOX_AUDIO_FIFO=0
tsirysndr Dec 23, 2024
23da5c8
pcm-sdl: update pcm-sdl.c
tsirysndr Dec 23, 2024
43bec05
crates/audio: fix fifo issue
tsirysndr Dec 23, 2024
ae39ad5
pcm: add new pcm thread
tsirysndr Dec 23, 2024
b06e3f3
pcm: fix includes
tsirysndr Dec 23, 2024
d6696e4
pcm: fix var conflicts
tsirysndr Dec 23, 2024
ffbf0db
pcm: fix incorrect process_buffer params
tsirysndr Dec 23, 2024
06ac36c
pcm: fix incorrect process_buffer params
tsirysndr Dec 23, 2024
d9f2b66
pcm: fix typo
tsirysndr Dec 23, 2024
5267f2b
pcm: fix incorrect debugfn params
tsirysndr Dec 23, 2024
0a92e77
pcm: skip if data_size == 0
tsirysndr Dec 23, 2024
f0a87da
pcm-sdl: remove unecessary macro
tsirysndr Dec 23, 2024
05c5e91
pcm: increase buffer size
tsirysndr Dec 23, 2024
1d48e38
pcm: update sleep time
tsirysndr Dec 23, 2024
da279c3
pcm: increase buffer size
tsirysndr Dec 23, 2024
a1f1189
pcm: print remaining size
tsirysndr Dec 23, 2024
115e016
pcm: increase buffer size
tsirysndr Dec 23, 2024
2780e2f
pcm: add buffer threshold
tsirysndr Dec 23, 2024
382e25e
pcm: break loop
tsirysndr Dec 23, 2024
23f1444
pcm: fix loop
tsirysndr Dec 23, 2024
5e4ea9c
pcm: fix loop
tsirysndr Dec 23, 2024
92d3f4c
pcm: break loop
tsirysndr Dec 23, 2024
d2f2107
pcm: remove threshold
tsirysndr Dec 23, 2024
298c0e9
pcm: use a circular buffer
tsirysndr Dec 23, 2024
8d46846
pcm: update circular buffer size
tsirysndr Dec 23, 2024
4e49b44
pcm: disable pcm_dma_play_init
tsirysndr Dec 24, 2024
3fe972c
pcm: disable pcm_dma_play_init
tsirysndr Dec 24, 2024
3cc7f11
pcm: disable pcm_dma_play_init
tsirysndr Dec 24, 2024
20c7692
pcm: disable pcm_dma_play_init
tsirysndr Dec 24, 2024
d827131
pcm: fill buffer
tsirysndr Dec 24, 2024
2990b7e
pcm: fill buffer
tsirysndr Dec 24, 2024
0200381
pcm: disable pcm_dma_play_init
tsirysndr Dec 24, 2024
12108c2
pcm: call pull_audio_data from rust
tsirysndr Dec 24, 2024
db107d5
pcm: fill buffer
tsirysndr Dec 24, 2024
a6c5bcb
pcm: fix loop
tsirysndr Dec 24, 2024
31b7ac4
pcm: disable pcm thread
tsirysndr Dec 24, 2024
403c4e3
pcm: include missing headers
tsirysndr Dec 24, 2024
583638c
pcm: remove duplicate symbol
tsirysndr Dec 24, 2024
dfa0d03
pcm: remove pcm thread
tsirysndr Dec 24, 2024
791e4be
pcm: open fifo
tsirysndr Dec 24, 2024
9be6be0
pcm: fix fifo write
tsirysndr Dec 24, 2024
9d59480
pcm: open fifo
tsirysndr Dec 24, 2024
4d42cf0
pcm: restore pcm-sdl
tsirysndr Dec 24, 2024
aac83a9
pcm: open fifo
tsirysndr Dec 24, 2024
6af8e70
pcm: open fifo
tsirysndr Dec 24, 2024
f839a24
pcm: unlink previous fifo
tsirysndr Dec 24, 2024
11b785c
pcm: unlink previous fifo
tsirysndr Dec 24, 2024
b4370f5
remove previous rockbox fifo
tsirysndr Dec 24, 2024
7e3dc5c
remove previous rockbox fifo
tsirysndr Dec 24, 2024
ef20fce
pcm: unlink previous fifo
tsirysndr Dec 24, 2024
ea4f099
remove previous rockbox fifo
tsirysndr Dec 24, 2024
02e0c70
remove previous rockbox fifo
tsirysndr Dec 24, 2024
80d344f
remove previous rockbox fifo
tsirysndr Dec 24, 2024
7064e65
pcm: unlink previous fifo
tsirysndr Dec 24, 2024
d57589e
pcm: unlink previous fifo
tsirysndr Dec 24, 2024
bdf54eb
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
4f28123
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
9c2ca16
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
ea85d66
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
5879521
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
bd314a8
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
0ed345a
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
c2b1cb5
pcm-sdl: increase fifo pipe size
tsirysndr Dec 24, 2024
c0c867e
pcm-sdl: increase samples
tsirysndr Dec 24, 2024
1eb5e96
pcm-sdl: increase fifo pipe size
tsirysndr Dec 24, 2024
6d90cff
pcm-sdl: increase fifo pipe size
tsirysndr Dec 24, 2024
13ed528
pcm-sdl: increase fifo pipe size
tsirysndr Dec 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 294 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

859 changes: 416 additions & 443 deletions apps/main.c

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2927,6 +2927,7 @@ pub fn build(b: *std.Build) !void {
});
exe.linkSystemLibrary("rockbox_cli");
exe.linkSystemLibrary("rockbox_server");
exe.linkSystemLibrary("rockbox_audio");
exe.linkSystemLibrary("unwind");
exe.linkLibrary(libfirmware);
exe.linkLibrary(libspeex_voice);
Expand Down
14 changes: 14 additions & 0 deletions crates/audio/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "rockbox-audio"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true

[lib]
crate-type = ["staticlib", "lib"]

[dependencies]
cpal = "0.15.3"
libc.workspace = true
58 changes: 58 additions & 0 deletions crates/audio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::env;
use std::io::Write;
use std::{fs::OpenOptions, path::Path};

const FIFO_PATH: &str = "/tmp/rockbox_audio_fifo";

extern "C" {
fn pull_audio_data();
}

#[no_mangle]
pub extern "C" fn start_pcm_thread() {
std::thread::spawn(|| loop {
unsafe {
pull_audio_data();
}
});
}

#[no_mangle]
pub extern "C" fn process_pcm_buffer(data: *mut u8, size: usize) -> i32 {
if let Ok(var) = env::var("ROCKBOX_AUDIO_FIFO") {
if var.as_str() == "0" {
return 0;
}
}

if env::var("ROCKBOX_AUDIO_FIFO").is_err() {
return 0;
}

if !Path::new(FIFO_PATH).exists() {
let cstr_path = std::ffi::CString::new(FIFO_PATH).unwrap();
unsafe {
if libc::mkfifo(cstr_path.as_ptr(), 0o644) != 0 {
return -1;
}
}
}
let mut fifo = match OpenOptions::new().write(true).open(FIFO_PATH) {
Ok(f) => f,
Err(_) => return -2,
};

let pcm_data = unsafe {
if data.is_null() {
return -3;
}

std::slice::from_raw_parts(data, size).to_vec()
};

if fifo.write_all(&pcm_data).is_err() {
return -4;
}

return 0;
}
291 changes: 291 additions & 0 deletions crates/audio/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
use std::collections::VecDeque;
use std::env;
use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::Path;
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

const BUFFER_DURATION_SECS: f32 = 60.0; // Increased buffer size
const MIN_BUFFER_FILL_PERCENT: f32 = 0.5; // Start playback when buffer is 80% full

struct RingBuffer {
buffer: VecDeque<f32>,
capacity: usize,
ready: bool,
}

impl RingBuffer {
fn new(capacity: usize) -> Self {
Self {
buffer: VecDeque::with_capacity(capacity),
capacity,
ready: false,
}
}

fn write_chunk(&mut self, samples: &[f32]) -> usize {
let space = self.capacity - self.buffer.len();
let write_count = samples.len().min(space);
self.buffer.extend(&samples[..write_count]);
write_count
}

fn read_chunk(&mut self, out: &mut [f32]) -> usize {
let available = self.buffer.len().min(out.len());
for i in 0..available {
out[i] = self.buffer.pop_front().unwrap_or(0.0);
}
available
}

fn available(&self) -> usize {
self.buffer.len()
}

fn capacity(&self) -> usize {
self.capacity
}

fn is_ready(&self) -> bool {
self.ready
}

fn set_ready(&mut self, ready: bool) {
self.ready = ready;
}

fn current_size(&self) -> usize {
self.buffer.len()
}
}

#[derive(Debug)]
struct PcmFormat {
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
is_float: bool,
is_signed: bool,
is_le: bool,
}

impl Default for PcmFormat {
fn default() -> Self {
Self {
sample_rate: 44100,
channels: 2,
bits_per_sample: 16,
is_float: false,
is_signed: true,
is_le: true,
}
}
}

fn convert_sample(buffer: &[u8], format: &PcmFormat) -> f32 {
match (
format.bits_per_sample,
format.is_float,
format.is_signed,
format.is_le,
) {
(16, false, true, true) => {
let value = i16::from_le_bytes([buffer[0], buffer[1]]);
value as f32 / 32768.0
}
(16, false, true, false) => {
let value = i16::from_be_bytes([buffer[0], buffer[1]]);
value as f32 / 32768.0
}
(32, true, _, true) => f32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]),
(32, true, _, false) => f32::from_be_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]),
(8, false, false, _) => (buffer[0] as f32 - 128.0) / 128.0,
(8, false, true, _) => buffer[0] as i8 as f32 / 128.0,
_ => {
eprintln!(
"Unsupported format: {}bit, float={}, signed={}, le={}",
format.bits_per_sample, format.is_float, format.is_signed, format.is_le
);
0.0
}
}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage: {} <raw_pcm_file/fifo> [sample_rate] [channels] [bits] [float/int] [signed/unsigned] [le/be]", args[0]);
println!("Default format: 44100Hz, 2ch, 16-bit signed integer, little-endian");
return Ok(());
}

let mut format = PcmFormat::default();
if args.len() > 2 {
format.sample_rate = args[2].parse().unwrap_or(44100);
}
if args.len() > 3 {
format.channels = args[3].parse().unwrap_or(2);
}
if args.len() > 4 {
format.bits_per_sample = args[4].parse().unwrap_or(16);
}
if args.len() > 5 {
format.is_float = args[5].to_lowercase() == "float";
}
if args.len() > 6 {
format.is_signed = args[6].to_lowercase() == "signed";
}
if args.len() > 7 {
format.is_le = args[7].to_lowercase() == "le";
}

println!("Playing PCM from {} with format: {:?}", args[1], format);

let host = cpal::default_host();
let device = host
.default_output_device()
.expect("Failed to get default output device");

// Calculate buffer sizes
let total_buffer_samples =
(format.sample_rate as f32 * BUFFER_DURATION_SECS * format.channels as f32) as usize;
let chunk_samples = format.sample_rate as usize / 10 * format.channels as usize; // 100ms chunks

let config = cpal::StreamConfig {
channels: format.channels,
sample_rate: cpal::SampleRate(format.sample_rate),
buffer_size: cpal::BufferSize::Fixed(chunk_samples as u32),
};

let ring_buffer = Arc::new(Mutex::new(RingBuffer::new(total_buffer_samples)));
let ring_buffer_player = ring_buffer.clone();

let (stop_tx, stop_rx) = mpsc::channel();
let (ready_tx, ready_rx) = mpsc::channel();

let ring_buffer_clone = ring_buffer.clone();
// Spawn audio playback thread

let play_thread = thread::spawn(move || {
let mut output_buffer = vec![0.0f32; chunk_samples];

let stream = device
.build_output_stream(
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let mut ring_buffer = ring_buffer_player.lock().unwrap();
if !ring_buffer.is_ready() {
return;
}
let read = ring_buffer.read_chunk(data);

// Fill remaining with silence if buffer underrun
if read < data.len() {
data[read..].fill(0.0);
}
},
|err| eprintln!("Error in audio playback: {}", err),
None,
)
.expect("Failed to build output stream");

// Wait for buffer to fill initially/
// ready_rx.recv().unwrap();

println!("Waiting for buffer fill");
std::thread::sleep(Duration::from_secs(20));

let mut mutex = ring_buffer_clone.lock().unwrap();
mutex.set_ready(true);
drop(mutex);

println!("Starting playback");
stream.play().expect("Failed to play stream");

// Wait for stop signal
let _ = stop_rx.recv();
});

let bytes_per_sample = (format.bits_per_sample as usize + 7) / 8;
let mut file = File::open(Path::new(&args[1]))?;
let mut samples_played = 0;
let mut is_playing = false;

// Pre-allocate buffers
let chunk_bytes = chunk_samples * bytes_per_sample;
let mut input_chunk = vec![0u8; chunk_bytes];
let mut conversion_buffer = Vec::with_capacity(chunk_samples);

loop {
match file.read(&mut input_chunk) {
Ok(0) => {
thread::sleep(Duration::from_millis(10));
continue;
}
Ok(bytes_read) => {
conversion_buffer.clear();
println!(">> bytes read {}", bytes_read);
println!(">> input chunk {}", chunk_bytes);

// Convert bytes to samples in chunks
for chunk in input_chunk[..bytes_read].chunks_exact(bytes_per_sample) {
let sample = convert_sample(chunk, &format);
let normalized_sample = if sample.abs() > 1.0 {
sample.signum()
} else {
sample
};
conversion_buffer.push(normalized_sample);
}

// Write to ring buffer
loop {
let mut ring_buffer = ring_buffer.lock().unwrap();
let written = ring_buffer.write_chunk(&conversion_buffer);
println!(
"Writing to ring buffer {} - {}",
ring_buffer.current_size(),
conversion_buffer.len()
);

if written == conversion_buffer.len() {
// Check if we should start playback
if !is_playing
&& (ring_buffer.available() as f32 / ring_buffer.capacity() as f32)
>= MIN_BUFFER_FILL_PERCENT
{
ready_tx.send(()).unwrap();
is_playing = true;
}
break;
}

drop(ring_buffer);
}

samples_played += conversion_buffer.len();
if samples_played % (format.sample_rate as usize * format.channels as usize) == 0 {
print!(
"\rPlayed {} seconds",
samples_played / format.sample_rate as usize / format.channels as usize
);
}
}
Err(e) if e.kind() == ErrorKind::Interrupted => continue,
Err(e) => {
eprintln!("\nError reading from file: {}", e);
break;
}
}
}

let _ = stop_tx.send(());
play_thread.join().unwrap();

println!("\nPlayback finished");
Ok(())
}
Loading
Loading