Skip to content

Use rtpbuffer for the jitter buffer - #428

Open
mojl wants to merge 15 commits into
pion:mainfrom
mojl:jitterbuffer-use-rtpbuffer
Open

mojl wants to merge 15 commits into
pion:mainfrom
mojl:jitterbuffer-use-rtpbuffer

Conversation

@mojl

@mojl mojl commented Jul 20, 2026

Copy link
Copy Markdown

Description

Use rtpbuffer for the jitter buffer (as suggested by Sean in #278). I am using a ring buffer (as rtpbuffer packets can be overwritten any time) for the playback buffer and it accepts retainable packets too so it uses the buffer pool too.

I tried to keep the behavior of the API as 1:1 as possible, but the differences are:

  • Emitting starts when we have X amount of sequential packets, not just any packets (minStartCount).
  • All forms of Pop do move the playoutHead to that sequance number.
  • The default capacity is now 1024, as keyframes can be big sometimes.

Working on: receiver interceptor, unit tests for the ring buffer, and a NoCopy option.

@mojl

mojl commented Jul 21, 2026

Copy link
Copy Markdown
Author

The interceptor now supports this new jitter buffer, supports multiple tracks/streams, and accepts options for building the jitter buffer.

I've added the #255 fix since it was stopping me from testing.

@thatsnotright

Copy link
Copy Markdown
Contributor

Let me do some performance regression tests but this looks good

@thatsnotright

Copy link
Copy Markdown
Contributor

I have a branch that changes the data structure underneath for jb from a priority queue to a tree, based on feedback from @at-wat with my original port of SampleBuilder to jitterbuffer. This PR would allocate a lot more (~24% more in the perf tests) and is slower by ~68%. I'll do some profiling to see where the issues may be and hopefully we can reduce the memory allocations.

@mojl

mojl commented Sep 18, 2026

Copy link
Copy Markdown
Author

Thank you for checking this PR, Rob.

The data structure I've used (ring buffer) is dictated by the data structure the internal rtpbuffer already uses (circular ring?), I've used the simplest data structure possible that can work alongside it. It does outperform a priority queue a bit, but not by much and a priority queue is simpler and more predictable and I only used this to reuse code.

The performance degradation is due to using RetainablePacket, since that's what rtpbuffer accepts. With some memory reuse ReceiverInterceptor can outperform the current implementation by removing 1 allocation, this is possible now since we are using PacketFactory which copies on push per default.

But as I understand ReceiverInterceptor is pretty much unusable here since the user/dev cannot skip gaps using it, so they should be told to reuse memory when using JitterBuffer directly in documentation.

There is trade off, and this is the result of using rtpbuffer, the conclusion can be either that we want that trade off: faster with copy + memory reuse, or that we prefer to not use rtpbuffer here.

I am happy to keep working on this PR if you think it is worth it after the optimization, I will push a commit later.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants