Skip to content

VP9: switch packetizer to non-flexible mode (F=0)#868

Open
farit2000 wants to merge 2 commits intoalgesten:mainfrom
farit2000:feat/vp9-non-flexible-mode
Open

VP9: switch packetizer to non-flexible mode (F=0)#868
farit2000 wants to merge 2 commits intoalgesten:mainfrom
farit2000:feat/vp9-non-flexible-mode

Conversation

@farit2000
Copy link
Contributor

We've been running str0m as a WebRTC SFU in rtp_mode and hit an issue: Safari drops all VP9 inter-frames when the packetizer uses flexible mode (F=1). After debugging, we found that Safari's VP9 decoder expects non-flexible mode with tl0picidx for temporal reference tracking.

Switching to F=0 (non-flexible mode) fixes Safari and is also what other SFUs like LiveKit and mediasoup use in practice.

What changed

  • Packetizer now emits F=0 with layer indices (L=1) and tl0picidx on every packet
  • Keyframe first-packets include scalability structure (V=1) with a minimal GOF
  • Added detect_vp9_keyframe_bitstream() to correctly set the P bit from raw VP9 data
  • tl0picidx increments on every frame (all TID=0) and wraps at 255→0, per the spec
  • VP9HEADER_SIZE bumped to 8 to account for the larger header on keyframes

The depacketizer already handles both modes, so no changes needed there.

Testing

  • Updated existing packetizer tests for the new header format
  • Added tests for keyframe detection (all 4 VP9 profiles), fragmented keyframes with round-trip verification, and tl0picidx wrapping
  • Full test suite passes

Safari and older Chrome versions drop inter-frames when the VP9 RTP
packetizer uses flexible mode (F=1). This rewrites the VP9 packetizer
to use non-flexible mode (F=0) with proper layer indices and tl0picidx,
matching what browsers expect.

Changes:
- Switch from F=1 (flexible) to F=0 (non-flexible) packetization
- Add layer indices (L=1) with TID=0, SID=0, tl0picidx to every packet
- Add scalability structure (V=1) on keyframe first-packets (GOF)
- Detect keyframes from VP9 bitstream to set P flag correctly
- Increment tl0picidx on every frame (all TID=0 per spec Section 6.3)
- Add detect_vp9_keyframe_bitstream() for raw bitstream inspection
- VP9HEADER_SIZE increased to 8 (max header with SS on keyframes)

The depacketizer is unchanged — it already handles both modes correctly.
@algesten
Copy link
Owner

@farit2000 Great find! A little cargo fmt and we merge!

@algesten
Copy link
Owner

@farit2000 thinking about this. Safari limitations might guide our default to F=0, but should we really throw flexible mode out? Seems like a config option.

@davibe thoughts?

@farit2000
Copy link
Contributor Author

farit2000 commented Feb 15, 2026

@farit2000 thinking about this. Safari limitations might guide our default to F=0, but should we really throw flexible mode out? Seems like a config option.

@davibe thoughts?

Makes sense — we could keep both and add a config flag. In our SFU we always use F=0 because Chrome/Safari both handle it well, but having flexible mode as an option is reasonable. I can refactor this so Vp9Packetizer has a mode field (Flexible / NonFlexible) if you'd like.

@algesten
Copy link
Owner

@farit2000 perfect! Let's keep F=1 around, but switch the default to F=0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants