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