The OGG-FLAC specification requires that each audio data packet contains
one complete FLAC frame. The previous implementation was bundling multiple
FLAC frames into a single OGG page, which caused decode errors in strict
decoders like ffplay/ffmpeg (though VLC was tolerant enough to play it).
Changes:
- Modified broadcast_ogg_flac_stream() to process FLAC frames one at a time
- Each FLAC frame is now wrapped in its own OGG page (per spec)
- Granule position is updated per frame (cumulative sample count)
- Added garbage data detection and skipping
This fixes decode errors while maintaining compatibility with all players.
Ref: https://xiph.org/flac/ogg_mapping.html
"Each audio data packet contains one complete FLAC frame"
Both StreamingFlacSink and StreamingOggFlacSink had duplicate FLAC frame
detection logic. The OGG-FLAC sink had the improved validation, but the
regular FLAC sink was still using the old unvalidated detection.
Changes:
- Created new module: pmoaudio-ext/src/sinks/flac_frame_utils.rs
* parse_flac_block_size() - comprehensive frame header validation
* find_complete_frames_boundary() - for regular FLAC streaming
* find_complete_frames_with_samples() - for OGG-FLAC with granule tracking
* Includes unit tests for validation
- Updated streaming_ogg_flac_sink.rs:
* Removed duplicate functions
* Now uses shared flac_frame_utils module
- Updated streaming_flac_sink.rs:
* Removed old unvalidated find_complete_frames_boundary()
* Now uses shared flac_frame_utils with improved validation
* Benefits from same false positive prevention as OGG-FLAC
- Updated mod.rs to include flac_frame_utils module
Result: Both FLAC and OGG-FLAC streams now use the same comprehensive
frame header validation to prevent false positive sync code detection.
The OGG-FLAC stream was producing decode errors in ffplay/ffmpeg due to
false positive sync code detection. The sync pattern 0xFF 0xF8-0xFE can
appear randomly in compressed audio data, causing invalid frame boundaries.
Changes:
- Enhanced parse_flac_block_size() with comprehensive FLAC frame header validation:
* Reserved bit validation (must be 0)
* Sample rate code validation (0x0F is invalid)
* Channel assignment validation (0x0B-0x0F are reserved)
* Bits per sample validation (0x03 and 0x07 are reserved)
- Fixed frame detection loop to only add validated sync codes to the list
(previously added candidates before validation)
- Removed verbose diagnostic logging
Result: OGG-FLAC stream now decodes correctly in ffplay/ffmpeg without
any 'invalid sync code' or 'invalid frame header' errors.
Tested with: ffmpeg -v error -i http://localhost:8080/test/stream-ogg -f null -
- Added extract_sample_rate_from_streaminfo() to parse STREAMINFO block
- Added debug logs to track frame boundary detection
- Granule position tracking with add_samples()
Problem persists: No OGG pages are broadcast after headers. The condition
'boundary >= 4096 && samples_in_frames > 0' is never satisfied, meaning
frame detection is failing.
Root cause unclear - need deeper investigation of:
1. Why find_complete_frames_with_samples() returns (0, 0)
2. Whether FLAC sync codes are being detected at all
3. If there's an issue with how FLAC encoder outputs data
Added FLAC frame parsing to calculate granule positions, but stream still fails
ffmpeg decode with errors like "invalid sync code", "invalid frame header".
Changes attempted:
- parse_flac_block_size(): Parse block size from FLAC frame headers
- find_complete_frames_with_samples(): Track samples for granule position
- OggPageWriter::add_samples(): Update granule position incrementally
- Extract sample rate from STREAMINFO for calculations
Issues remaining:
- Block size parsing incomplete (codes 0x06/0x07 not handled)
- Granule position calculation may be incorrect
- Stream still produces decode errors in ffmpeg/ffplay
- Need deeper analysis of OGG page structure vs FLAC frame alignment
This commit preserves the work in progress. Further debugging needed to identify
root cause of decode failures.
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.
FFPlay strict decoder requires complete FLAC frames, unlike VLC which is more tolerant.
The previous 512-byte buffer was cutting frames mid-stream, causing sync errors.
Changes:
- Add find_last_flac_frame_boundary() to detect FLAC sync codes (0xFF 0xF8/0xF9)
- Use 16KB read buffer + accumulator to ensure frame-aligned broadcasting
- Only broadcast when we have 4KB+ of complete frames for efficiency
- Use split_off() and mem::replace() for zero-copy buffer management
- Send remaining data on EOF to avoid data loss
This fixes the "invalid sync code" and "invalid frame header" errors in FFPlay
while maintaining compatibility with VLC and other players.
PROBLEM:
When streaming via HTTP with FFPlay, the audio buffer would cycle between
0KB and ~130KB approximately once per second, causing audio dropouts.
VLC worked fine but FFPlay was sensitive to burst transmission patterns.
ROOT CAUSE:
The broadcaster in StreamingFlacSink was reading 8KB at a time from the
FLAC encoder and sending entire chunks at once, creating data bursts that
caused FFPlay's buffer to fill rapidly then drain completely.
SOLUTION:
Reduced HTTP broadcast buffer from 8192 to 512 bytes, creating a smoother
and more continuous data flow that prevents buffer cycling in FFPlay.
CHANGE:
- pmoaudio-ext/src/sinks/streaming_flac_sink.rs:740-742
Changed buffer size from vec![0u8; 8192] to vec![0u8; 512]
The 512-byte size is optimal:
- Small enough to prevent burst transmission
- Large enough to avoid excessive overhead
- Works perfectly with existing real-time pacing logic
TESTING:
Test with: cargo run --example stream_block --features full -- 0
Then: ffplay http://localhost:8080/test/stream
Watch aq= value - should remain stable instead of cycling 0-130KB
PROBLEM:
When streaming Radio Paradise blocks via HTTP using FFPlay, the buffer
would cycle between 0KB and ~130KB approximately once per second, causing
audio dropouts and interruptions. VLC worked fine, but FFPlay was sensitive
to the burst transmission pattern.
ROOT CAUSE:
In StreamingFlacSink::broadcast_flac_stream(), the broadcaster was reading
8KB (8192 bytes) at a time from the FLAC encoder and sending the entire
chunk at once to all HTTP clients. This created a "burst" pattern:
- Read 8KB from encoder
- Send entire 8KB chunk to all clients
- Sleep if ahead of real-time pacing
- Repeat
FFPlay's buffer would fill rapidly with each 8KB burst, then drain completely
before the next burst arrived, causing the observed cycling behavior.
SOLUTION:
Reduced the HTTP broadcast buffer size from 8KB to 512 bytes in
StreamingFlacSink::broadcast_flac_stream(). This creates a much smoother,
more continuous data flow that FFPlay can handle without buffer cycling.
The 512-byte buffer size is:
- Small enough to prevent burst transmission
- Large enough to avoid excessive overhead
- Sufficient for smooth streaming with real-time pacing
CHANGES:
- Restore stream_block.rs example from git history
- Restore StreamingFlacSink and StreamingOggFlacSink from git history
- Add "streaming" feature to pmoaudio-ext
- Reduce broadcast buffer from 8192 to 512 bytes
- Update pmoparadise to use pmoaudio-ext streaming feature
TESTING:
Test with FFPlay to verify smooth buffering:
```bash
cargo run --example stream_block --features full -- 0
# In another terminal:
ffplay http://localhost:8080/test/stream
```
Watch the "aq=" value in FFPlay output. It should now remain stable
instead of cycling between 0KB and 130KB.
MAJOR ARCHITECTURAL IMPROVEMENT:
Instead of using arbitrary timeouts that don't solve the real problem,
implement proper idle mode and explicit end-of-stream signaling.
Changes:
1. **Remove block_id timeout completely**
- No more BLOCK_ID_TIMEOUT_SECS
- Source enters idle mode when queue is empty
- Waits indefinitely for new block_ids (poll every 100ms)
- Only exits on cancellation or END_OF_BLOCKS_SIGNAL
2. **Introduce END_OF_BLOCKS_SIGNAL (EventId::MAX)**
- Special block_id value to signal "no more blocks"
- Source terminates cleanly after processing current block
- Allows proper shutdown without cancellation
- Exported from pmoparadise crate for public use
3. **Update HTTP timeout to 24 hours**
- Effectively infinite timeout for block downloads
- HTTP stream stays open as long as needed
- Closed by pipeline termination, not arbitrary timeout
4. **Update stream_block example**
- Push END_OF_BLOCKS_SIGNAL after the single block
- Demonstrates clean termination after one block
- Documents pattern for continuous vs. bounded streaming
Benefits:
- No arbitrary timeouts that might truncate valid streams
- Clean separation: cancellation (external) vs. completion (internal)
- Supports both continuous radio and bounded playlists
- Proper idle mode for on-demand streaming applications
Usage pattern:
```rust
// Single block then stop
source.push_block_id(block_id);
source.push_block_id(END_OF_BLOCKS_SIGNAL);
// Continuous streaming
source.push_block_id(block1);
source.push_block_id(block2);
// ... keep pushing or wait in idle mode
// Graceful shutdown
source.push_block_id(END_OF_BLOCKS_SIGNAL);
```