Improve FLAC frame boundary detection with multi-sync-code algorithm

The previous algorithm was incorrectly finding frame boundaries.
Now properly collects all sync code positions and returns the last one,
ensuring everything before it contains only complete frames.

Changes:
- Collect all sync code positions (0xFF 0xF8-0xFE) in buffer
- Return position of last sync code (start of incomplete frame)
- Everything before this is complete frames ready to send
- Require at least 2 sync codes to identify complete frames
- Lower threshold from 4KB to 1KB for better responsiveness
- Add detailed trace logging for debugging

This should fix "sample/frame number mismatch in adjacent frames" errors.
This commit is contained in:
Claude
2025-11-13 08:12:45 +00:00
parent f783244477
commit 4e42ccbb03

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();