From 4e42ccbb0327b1cae9bffeed1204c8da80b136f4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 08:12:45 +0000 Subject: [PATCH] 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. --- pmoaudio-ext/src/sinks/streaming_flac_sink.rs | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/pmoaudio-ext/src/sinks/streaming_flac_sink.rs b/pmoaudio-ext/src/sinks/streaming_flac_sink.rs index ee5b2428..e73b7052 100644 --- a/pmoaudio-ext/src/sinks/streaming_flac_sink.rs +++ b/pmoaudio-ext/src/sinks/streaming_flac_sink.rs @@ -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();