Merge pull request #61 from coissac/claude/fix-stream-block-ffplay-buffer-011CV5WqrBMi5Wvj93ozWCvC

Improve FLAC frame boundary detection with multi-sync-code algorithm
This commit is contained in:
coissac
2025-11-13 10:10:00 +01:00
committed by GitHub

View File

@@ -727,30 +727,45 @@ impl NodeLogic for StreamingFlacSinkLogic {
}
}
/// Find the last FLAC frame boundary in the buffer.
/// Returns the position after the last complete frame, or 0 if no complete frame is found.
/// Find the position where we should split the buffer to send complete FLAC frames.
/// Returns the byte position just before the last FLAC frame starts.
///
/// FLAC frames start with a sync code: 14 bits set to 1, followed by a 0 bit.
/// This corresponds to byte patterns: 0xFF 0xF8 or 0xFF 0xF9.
fn find_last_flac_frame_boundary(data: &[u8]) -> usize {
if data.len() < 2 {
/// This corresponds to byte patterns: 0xFF 0xF8 through 0xFF 0xFF.
///
/// The sync code marks the START of a frame. To send complete frames, we find the
/// last sync code and send everything BEFORE it (which contains complete frames),
/// keeping the data from the last sync code onward for the next iteration.
fn find_complete_frames_boundary(data: &[u8]) -> usize {
if data.len() < 4 {
return 0;
}
let mut last_boundary = 0;
let mut sync_positions = Vec::new();
// Search for FLAC sync codes (0xFF 0xF8 or 0xFF 0xF9)
// Search for FLAC sync codes
// FLAC sync is 14 bits of 1: first byte is always 0xFF
// Second byte: 0xF8-0xFF (most common: 0xF8 for fixed blocksize, 0xF9 for variable)
// We search more conservatively for 0xF8-0xFE to avoid false positives
for i in 0..data.len() - 1 {
if data[i] == 0xFF && (data[i + 1] == 0xF8 || data[i + 1] == 0xF9) {
// Found a potential sync code
// We consider everything before this position as a complete frame
if i > 0 {
last_boundary = i;
}
let byte1 = data[i];
let byte2 = data[i + 1];
// Check for FLAC sync pattern: 0xFF followed by 0xF8-0xFE
// We exclude 0xFF 0xFF as it's less common and more likely to be a false positive
if byte1 == 0xFF && byte2 >= 0xF8 && byte2 <= 0xFE {
sync_positions.push(i);
}
}
last_boundary
// We need at least 2 sync codes to identify one complete frame
// The last sync code marks the start of a potentially incomplete frame
// Return the position of the last sync code - everything before it is complete
if sync_positions.len() >= 2 {
*sync_positions.last().unwrap()
} else {
0
}
}
/// Broadcaster task: reads FLAC bytes from encoder and broadcasts to all clients.
@@ -792,11 +807,19 @@ async fn broadcast_flac_stream(
// Append to accumulator
accumulator.extend_from_slice(&read_buffer[..n]);
// Find the last complete FLAC frame boundary
let boundary = find_last_flac_frame_boundary(&accumulator);
// Find where to split: position of last sync code (start of last incomplete frame)
// Everything before this position contains only complete frames
let boundary = find_complete_frames_boundary(&accumulator);
// Only broadcast if we have at least one complete frame (4KB minimum for efficiency)
if boundary > 4096 {
trace!(
"Buffer state: accumulator={} bytes, boundary={} bytes, will_send={}",
accumulator.len(),
boundary,
boundary >= 1024
);
// Only broadcast if we have at least one complete frame (1KB minimum to avoid excessive small sends)
if boundary >= 1024 {
// Precise pacing based on audio timestamp
let audio_timestamp = *current_timestamp.read().await;
let elapsed = start_time.elapsed().as_secs_f64();