2025-10-27 16:10:57 +01:00
|
|
|
use std::{
|
2025-10-27 17:05:36 +01:00
|
|
|
future::Future,
|
2025-10-27 16:10:57 +01:00
|
|
|
io,
|
|
|
|
|
path::PathBuf,
|
|
|
|
|
pin::Pin,
|
2025-10-27 17:05:36 +01:00
|
|
|
sync::{
|
|
|
|
|
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
|
|
|
Arc,
|
|
|
|
|
},
|
2025-10-27 16:10:57 +01:00
|
|
|
task::{Context, Poll},
|
2025-10-27 17:05:36 +01:00
|
|
|
time::Duration,
|
2025-10-27 16:10:57 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf};
|
|
|
|
|
|
|
|
|
|
use pmoflac::{decode_flac_stream, encode_flac_stream, EncoderOptions, FlacError, PcmFormat};
|
|
|
|
|
|
|
|
|
|
fn fixture(name: &str) -> PathBuf {
|
|
|
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
|
|
|
.join("test_data")
|
|
|
|
|
.join(name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct VecAsyncReader {
|
|
|
|
|
data: Vec<u8>,
|
|
|
|
|
pos: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl VecAsyncReader {
|
|
|
|
|
fn new(data: Vec<u8>) -> Self {
|
|
|
|
|
Self { data, pos: 0 }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AsyncRead for VecAsyncReader {
|
|
|
|
|
fn poll_read(
|
|
|
|
|
mut self: Pin<&mut Self>,
|
|
|
|
|
_cx: &mut Context<'_>,
|
|
|
|
|
buf: &mut ReadBuf<'_>,
|
|
|
|
|
) -> Poll<io::Result<()>> {
|
|
|
|
|
if self.pos >= self.data.len() {
|
|
|
|
|
return Poll::Ready(Ok(()));
|
|
|
|
|
}
|
|
|
|
|
let remaining = &self.data[self.pos..];
|
|
|
|
|
let to_copy = remaining.len().min(buf.remaining());
|
|
|
|
|
if to_copy == 0 {
|
|
|
|
|
return Poll::Ready(Ok(()));
|
|
|
|
|
}
|
|
|
|
|
buf.put_slice(&remaining[..to_copy]);
|
|
|
|
|
self.pos += to_copy;
|
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn decode_stream_info_16_44() -> Result<(), FlacError> {
|
|
|
|
|
let bytes = std::fs::read(fixture(
|
|
|
|
|
"1abaa2c7fb4302e20ac570e79857b700.32bits-44.1Khz.flac",
|
|
|
|
|
))?;
|
|
|
|
|
let mut stream = decode_flac_stream(VecAsyncReader::new(bytes)).await?;
|
|
|
|
|
let info = stream.info().clone();
|
|
|
|
|
|
|
|
|
|
assert_eq!(info.sample_rate, 44_100);
|
|
|
|
|
assert_eq!(info.channels, 2);
|
|
|
|
|
assert_eq!(info.bits_per_sample, 16);
|
|
|
|
|
|
|
|
|
|
let mut pcm = Vec::new();
|
|
|
|
|
stream.read_to_end(&mut pcm).await?;
|
|
|
|
|
assert!(!pcm.is_empty());
|
|
|
|
|
stream.wait().await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
2025-10-27 17:05:36 +01:00
|
|
|
#[ignore] // Slow test with large 24-bit/192kHz file. Run with: cargo test -- --ignored
|
2025-10-27 16:10:57 +01:00
|
|
|
async fn roundtrip_encode_decode_24_192() -> Result<(), FlacError> {
|
|
|
|
|
let bytes = std::fs::read(fixture("Yuri-Korzunov_Movement_24bit-192kHz.flac"))?;
|
|
|
|
|
let mut decoder = decode_flac_stream(VecAsyncReader::new(bytes)).await?;
|
|
|
|
|
let info = decoder.info().clone();
|
|
|
|
|
|
|
|
|
|
let mut pcm_bytes = Vec::new();
|
|
|
|
|
decoder.read_to_end(&mut pcm_bytes).await?;
|
|
|
|
|
decoder.wait().await?;
|
|
|
|
|
|
|
|
|
|
assert!(!pcm_bytes.is_empty());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
pcm_bytes.len() % (info.bytes_per_sample() * info.channels as usize),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let format = PcmFormat {
|
|
|
|
|
sample_rate: info.sample_rate,
|
|
|
|
|
channels: info.channels,
|
|
|
|
|
bits_per_sample: info.bits_per_sample,
|
|
|
|
|
};
|
|
|
|
|
let options = EncoderOptions {
|
|
|
|
|
total_samples: info.total_samples,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let pcm_reader = VecAsyncReader::new(pcm_bytes.clone());
|
|
|
|
|
let mut encoder_stream = encode_flac_stream(pcm_reader, format, options).await?;
|
|
|
|
|
let mut encoded_bytes = Vec::new();
|
|
|
|
|
encoder_stream.read_to_end(&mut encoded_bytes).await?;
|
|
|
|
|
encoder_stream.wait().await?;
|
|
|
|
|
|
|
|
|
|
assert!(!encoded_bytes.is_empty());
|
|
|
|
|
|
|
|
|
|
let flac_reader = VecAsyncReader::new(encoded_bytes);
|
|
|
|
|
let mut decoder_roundtrip = decode_flac_stream(flac_reader).await?;
|
|
|
|
|
let mut pcm_roundtrip = Vec::new();
|
|
|
|
|
decoder_roundtrip.read_to_end(&mut pcm_roundtrip).await?;
|
|
|
|
|
decoder_roundtrip.wait().await?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(pcm_roundtrip, pcm_bytes);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
/// SlowReader simulates a slow stream by introducing delays between reads.
|
|
|
|
|
/// This helps verify that the encoder/decoder truly streams data rather than
|
|
|
|
|
/// buffering everything before producing output.
|
|
|
|
|
struct SlowReader {
|
|
|
|
|
data: Vec<u8>,
|
|
|
|
|
pos: usize,
|
|
|
|
|
chunk_size: usize,
|
|
|
|
|
delay: Duration,
|
|
|
|
|
chunks_read: Arc<AtomicUsize>,
|
|
|
|
|
sleep: Option<Pin<Box<tokio::time::Sleep>>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SlowReader {
|
|
|
|
|
fn new(data: Vec<u8>, chunk_size: usize, delay: Duration) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data,
|
|
|
|
|
pos: 0,
|
|
|
|
|
chunk_size,
|
|
|
|
|
delay,
|
|
|
|
|
chunks_read: Arc::new(AtomicUsize::new(0)),
|
|
|
|
|
sleep: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn chunks_read(&self) -> Arc<AtomicUsize> {
|
|
|
|
|
self.chunks_read.clone()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AsyncRead for SlowReader {
|
|
|
|
|
fn poll_read(
|
|
|
|
|
mut self: Pin<&mut Self>,
|
|
|
|
|
cx: &mut Context<'_>,
|
|
|
|
|
buf: &mut ReadBuf<'_>,
|
|
|
|
|
) -> Poll<io::Result<()>> {
|
|
|
|
|
// If we have a sleep in progress, poll it first
|
|
|
|
|
if let Some(mut sleep) = self.sleep.take() {
|
|
|
|
|
match sleep.as_mut().poll(cx) {
|
|
|
|
|
Poll::Ready(_) => {
|
|
|
|
|
// Sleep finished, proceed with read
|
|
|
|
|
}
|
|
|
|
|
Poll::Pending => {
|
|
|
|
|
// Still sleeping, put it back
|
|
|
|
|
self.sleep = Some(sleep);
|
|
|
|
|
return Poll::Pending;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if self.pos >= self.data.len() {
|
|
|
|
|
return Poll::Ready(Ok(()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let remaining = &self.data[self.pos..];
|
|
|
|
|
let to_copy = remaining.len().min(buf.remaining()).min(self.chunk_size);
|
|
|
|
|
if to_copy == 0 {
|
|
|
|
|
return Poll::Ready(Ok(()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf.put_slice(&remaining[..to_copy]);
|
|
|
|
|
self.pos += to_copy;
|
|
|
|
|
self.chunks_read.fetch_add(1, Ordering::SeqCst);
|
|
|
|
|
|
|
|
|
|
// Start a new sleep for the next read
|
|
|
|
|
self.sleep = Some(Box::pin(tokio::time::sleep(self.delay)));
|
|
|
|
|
|
|
|
|
|
Poll::Ready(Ok(()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn encoder_streams_without_buffering_all_input() -> Result<(), FlacError> {
|
|
|
|
|
// Generate PCM data: 1 second of 16-bit stereo at 44.1kHz
|
|
|
|
|
let sample_rate = 44_100;
|
|
|
|
|
let channels = 2u8;
|
|
|
|
|
let bits_per_sample = 16u8;
|
|
|
|
|
let duration_secs = 1;
|
|
|
|
|
let total_samples = sample_rate * channels as u32 * duration_secs;
|
|
|
|
|
let bytes_per_sample = 2;
|
|
|
|
|
let total_bytes = total_samples as usize * bytes_per_sample;
|
|
|
|
|
|
|
|
|
|
// Generate sine wave PCM data
|
|
|
|
|
let mut pcm_data = Vec::with_capacity(total_bytes);
|
|
|
|
|
for i in 0..total_samples / channels as u32 {
|
|
|
|
|
let t = i as f32 / sample_rate as f32;
|
|
|
|
|
let sample = (t * 440.0 * 2.0 * std::f32::consts::PI).sin();
|
|
|
|
|
let sample_i16 = (sample * 16384.0) as i16;
|
|
|
|
|
let bytes = sample_i16.to_le_bytes();
|
|
|
|
|
// Stereo: same for both channels
|
|
|
|
|
pcm_data.extend_from_slice(&bytes);
|
|
|
|
|
pcm_data.extend_from_slice(&bytes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let format = PcmFormat {
|
|
|
|
|
sample_rate,
|
|
|
|
|
channels,
|
|
|
|
|
bits_per_sample,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create a slow reader that delivers 8KB chunks with 10ms delay
|
|
|
|
|
let chunk_size = 8 * 1024;
|
|
|
|
|
let delay = Duration::from_millis(10);
|
|
|
|
|
let slow_reader = SlowReader::new(pcm_data.clone(), chunk_size, delay);
|
|
|
|
|
let chunks_read_counter = slow_reader.chunks_read();
|
|
|
|
|
|
|
|
|
|
// Start encoding
|
2025-10-27 20:42:16 +01:00
|
|
|
let mut encoder_stream =
|
|
|
|
|
encode_flac_stream(slow_reader, format, EncoderOptions::default()).await?;
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
// Try to read some FLAC data before all PCM data has been consumed
|
|
|
|
|
let mut first_chunk = vec![0u8; 4096];
|
|
|
|
|
let output_started = Arc::new(AtomicBool::new(false));
|
|
|
|
|
let output_started_clone = output_started.clone();
|
|
|
|
|
|
|
|
|
|
// Spawn a task to check when we get first output
|
|
|
|
|
let read_handle = tokio::spawn(async move {
|
|
|
|
|
match encoder_stream.read(&mut first_chunk).await {
|
|
|
|
|
Ok(n) if n > 0 => {
|
|
|
|
|
output_started_clone.store(true, Ordering::SeqCst);
|
|
|
|
|
Ok((n, encoder_stream))
|
|
|
|
|
}
|
|
|
|
|
Ok(_) => Err(FlacError::Encode("No data read".into())),
|
|
|
|
|
Err(e) => Err(FlacError::Io(e)),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Wait a bit to let the encoder start processing
|
|
|
|
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
|
|
|
|
|
|
|
|
|
// Check that we've started getting output
|
2025-10-27 20:42:16 +01:00
|
|
|
let (first_read, mut encoder_stream) =
|
|
|
|
|
read_handle.await.map_err(|e| FlacError::TaskJoin {
|
2025-10-27 17:05:36 +01:00
|
|
|
role: "read-test",
|
|
|
|
|
details: e.to_string(),
|
2025-10-27 20:42:16 +01:00
|
|
|
})??;
|
2025-10-27 17:05:36 +01:00
|
|
|
|
|
|
|
|
assert!(first_read > 0, "Should have received some FLAC data");
|
|
|
|
|
assert!(
|
|
|
|
|
output_started.load(Ordering::SeqCst),
|
|
|
|
|
"Output should have started"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let chunks_read_so_far = chunks_read_counter.load(Ordering::SeqCst);
|
|
|
|
|
let total_chunks = (pcm_data.len() + chunk_size - 1) / chunk_size;
|
|
|
|
|
|
|
|
|
|
// Verify streaming behavior: we should get output before reading everything
|
|
|
|
|
// With delays of 50ms per chunk, if we're truly streaming, we should see output
|
|
|
|
|
// before the slowreader has been fully consumed.
|
|
|
|
|
// Note: this is a heuristic test. In practice, the encoder needs enough data
|
|
|
|
|
// to fill at least one block before it can output anything.
|
|
|
|
|
println!(
|
|
|
|
|
"Streaming check: read {}/{} chunks when first output arrived",
|
|
|
|
|
chunks_read_so_far, total_chunks
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// More lenient check: just verify we got SOME output
|
|
|
|
|
assert!(
|
|
|
|
|
first_read > 0,
|
|
|
|
|
"Should have received FLAC output (got {} bytes)",
|
|
|
|
|
first_read
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Read the rest to completion
|
|
|
|
|
let mut rest = Vec::new();
|
|
|
|
|
encoder_stream.read_to_end(&mut rest).await?;
|
|
|
|
|
encoder_stream.wait().await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
|
async fn decoder_streams_without_buffering_all_input() -> Result<(), FlacError> {
|
|
|
|
|
// Load a FLAC file
|
|
|
|
|
let bytes = std::fs::read(fixture(
|
|
|
|
|
"1abaa2c7fb4302e20ac570e79857b700.32bits-44.1Khz.flac",
|
|
|
|
|
))?;
|
|
|
|
|
|
|
|
|
|
// Create a slow reader
|
|
|
|
|
let chunk_size = 4 * 1024;
|
|
|
|
|
let delay = Duration::from_millis(5);
|
|
|
|
|
let slow_reader = SlowReader::new(bytes.clone(), chunk_size, delay);
|
|
|
|
|
let chunks_read_counter = slow_reader.chunks_read();
|
|
|
|
|
|
|
|
|
|
// Start decoding
|
|
|
|
|
let mut decoder_stream = decode_flac_stream(slow_reader).await?;
|
|
|
|
|
|
|
|
|
|
// Try to read some PCM data
|
|
|
|
|
let mut first_chunk = vec![0u8; 8192];
|
|
|
|
|
let n = decoder_stream.read(&mut first_chunk).await?;
|
|
|
|
|
|
|
|
|
|
assert!(n > 0, "Should have received some PCM data");
|
|
|
|
|
|
|
|
|
|
let chunks_read_so_far = chunks_read_counter.load(Ordering::SeqCst);
|
|
|
|
|
let total_chunks = (bytes.len() + chunk_size - 1) / chunk_size;
|
|
|
|
|
|
|
|
|
|
// Verify streaming behavior
|
|
|
|
|
assert!(
|
|
|
|
|
chunks_read_so_far < (total_chunks * 8 / 10),
|
|
|
|
|
"Decoder should produce output before consuming all input. Read {}/{} chunks",
|
|
|
|
|
chunks_read_so_far,
|
|
|
|
|
total_chunks
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Read the rest
|
|
|
|
|
let mut rest = Vec::new();
|
|
|
|
|
decoder_stream.read_to_end(&mut rest).await?;
|
|
|
|
|
decoder_stream.wait().await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|