Merge pull request #66 from coissac/claude/debug-stream-block-example-011CV5dt2TcCP2fmuLdLUxE9
Fix OGG-FLAC streaming: Send one FLAC frame per OGG page per spec
This commit is contained in:
@@ -786,13 +786,47 @@ async fn broadcast_ogg_flac_stream(
|
|||||||
// Append to accumulator
|
// Append to accumulator
|
||||||
flac_accumulator.extend_from_slice(&read_buffer[..n]);
|
flac_accumulator.extend_from_slice(&read_buffer[..n]);
|
||||||
|
|
||||||
// Find where to split: position of last sync code (start of last incomplete frame)
|
// Process complete FLAC frames one at a time
|
||||||
// Also calculate total samples for granule position
|
// OGG-FLAC spec requires: "Each audio data packet contains one complete FLAC frame"
|
||||||
let (boundary, samples_in_frames) = flac_frame_utils::find_complete_frames_with_samples(&flac_accumulator);
|
loop {
|
||||||
|
// Find all complete frames in the accumulator
|
||||||
|
if flac_accumulator.len() < 4 {
|
||||||
|
break; // Need at least 4 bytes for sync code check
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all sync positions with their sample counts
|
||||||
|
let mut sync_data = Vec::new();
|
||||||
|
for i in 0..flac_accumulator.len() - 1 {
|
||||||
|
let byte1 = flac_accumulator[i];
|
||||||
|
let byte2 = flac_accumulator[i + 1];
|
||||||
|
|
||||||
|
if byte1 == 0xFF && byte2 >= 0xF8 && byte2 <= 0xFE {
|
||||||
|
if let Some(samples) = flac_frame_utils::parse_flac_block_size(&flac_accumulator, i) {
|
||||||
|
sync_data.push((i, samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need at least 2 sync codes to identify one complete frame
|
||||||
|
if sync_data.len() < 2 {
|
||||||
|
break; // No complete frames yet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the first complete frame (from first sync to second sync)
|
||||||
|
let first_frame_start = sync_data[0].0;
|
||||||
|
let first_frame_samples = sync_data[0].1;
|
||||||
|
let second_frame_start = sync_data[1].0;
|
||||||
|
|
||||||
|
// Verify first frame starts at position 0 (otherwise we have garbage data)
|
||||||
|
if first_frame_start != 0 {
|
||||||
|
warn!("OGG-FLAC: Skipping {} bytes of garbage data before first frame", first_frame_start);
|
||||||
|
flac_accumulator.drain(0..first_frame_start);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract just the first frame
|
||||||
|
let first_frame: Vec<u8> = flac_accumulator.drain(0..second_frame_start).collect();
|
||||||
|
|
||||||
// Only broadcast if we have at least one complete frame (4KB minimum for efficiency)
|
|
||||||
// OGG pages can be larger than pure FLAC broadcasts since they include page overhead
|
|
||||||
if boundary >= 4096 && samples_in_frames > 0 {
|
|
||||||
// Precise pacing based on audio timestamp
|
// Precise pacing based on audio timestamp
|
||||||
let audio_timestamp = *current_timestamp.read().await;
|
let audio_timestamp = *current_timestamp.read().await;
|
||||||
let elapsed = start_time.elapsed().as_secs_f64();
|
let elapsed = start_time.elapsed().as_secs_f64();
|
||||||
@@ -807,22 +841,18 @@ async fn broadcast_ogg_flac_stream(
|
|||||||
tokio::time::sleep(tokio::time::Duration::from_secs_f64(sleep_duration)).await;
|
tokio::time::sleep(tokio::time::Duration::from_secs_f64(sleep_duration)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split at boundary to avoid copying - extract prefix, keep suffix
|
|
||||||
let remaining = flac_accumulator.split_off(boundary);
|
|
||||||
let complete_frames = std::mem::replace(&mut flac_accumulator, remaining);
|
|
||||||
|
|
||||||
// Update granule position (cumulative sample count)
|
// Update granule position (cumulative sample count)
|
||||||
ogg_writer.add_samples(samples_in_frames);
|
ogg_writer.add_samples(first_frame_samples as u64);
|
||||||
|
|
||||||
// Wrap complete FLAC frames in OGG page
|
// Wrap this single FLAC frame in ONE OGG page (per OGG-FLAC spec)
|
||||||
let ogg_page = ogg_writer.create_page(&complete_frames, false, false, false);
|
let ogg_page = ogg_writer.create_page(&first_frame, false, false, false);
|
||||||
let ogg_bytes = Bytes::from(ogg_page);
|
let ogg_bytes = Bytes::from(ogg_page);
|
||||||
total_ogg_bytes += ogg_bytes.len() as u64;
|
total_ogg_bytes += ogg_bytes.len() as u64;
|
||||||
|
|
||||||
if let Err(e) = broadcast_tx.send(ogg_bytes.clone()) {
|
if let Err(e) = broadcast_tx.send(ogg_bytes.clone()) {
|
||||||
trace!("No active receivers for OGG-FLAC broadcast: {}", e);
|
trace!("No active receivers for OGG-FLAC broadcast: {}", e);
|
||||||
} else {
|
} else {
|
||||||
trace!("Broadcasted OGG page with {} bytes of FLAC data, {} samples ({} bytes total with OGG overhead)", complete_frames.len(), samples_in_frames, ogg_bytes.len());
|
trace!("Broadcasted OGG page with 1 FLAC frame ({} bytes), {} samples ({} bytes total with OGG overhead)", first_frame.len(), first_frame_samples, ogg_bytes.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user