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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user