Skip to content

PacedBuffer: add paced jitter buffer wrapper and tests - #24

Closed
milos-lk wants to merge 3 commits into
mainfrom
add-pacer-buffer
Closed

PacedBuffer: add paced jitter buffer wrapper and tests#24
milos-lk wants to merge 3 commits into
mainfrom
add-pacer-buffer

Conversation

@milos-lk

Copy link
Copy Markdown
Contributor

Introduces a paced wrapper around the jitter buffer that drains samples at media-clock cadence—absorbing bursts without blowing max timestamp drift. It can be configured to allow a lead up tiime so more sampels are pushed initially to prewarm subsequent processing stages.

Introduces a paced wrapper around the jitter buffer that drains samples at media-clock cadence—absorbing bursts without blowing max timestamp drift. It can be configured to allow a lead up tiime so more sampels are pushed initially to prewarm subsequent processing stages.

@biglittlebigben biglittlebigben left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just a few remarks about switching to core.Fuse when possible/sensible as sticking with one mechanism with well understood semantics may help with maintenance long term.

Comment thread jitter/paced_buffer.go
allowLead time.Duration // maximum lead allowed when pre-warming
maxLag time.Duration // maximum lag permitted before clamping

stop chan struct{} // closed to signal shutdown

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We tend to use core.Fuse for this purpose as they tend to be safer (no panic on double close for instance)

Comment thread jitter/paced_buffer.go

stop chan struct{} // closed to signal shutdown
start chan struct{} // closed when pacing should begin
done sync.WaitGroup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we never add more than 1 entry to the WaitGroup, would a core.Fuse work here as well?

@boks1971 boks1971 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Good stuff @milos-lk

Comment thread jitter/paced_buffer.go
close(pb.samples)
}

func (pb *PacedBuffer) handleSample(sample []*rtp.Packet) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guessing this needs to change to take jitter.ExtPacket as jitter buffer emits those in the latest media-sdk.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes - the branch was created before these changes - I am going to integrate that - thanks for pointing out.

Comment thread jitter/paced_buffer.go
return pb
}

func (pb *PacedBuffer) Samples() <-chan []*rtp.Packet {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe @dennwc changed the jitter buffer interface to use a callback rather than a channel. Should this follow the same pattern?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense - will update it.

Comment thread jitter/paced_buffer.go
Comment on lines +119 to +120
case <-p.timer.C:
default:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the purpose of this?
this will not wait

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s there to drain the timer’s channel when Stop reports that the timer has already fired. time.Timer.Stop() returns false if the timer either has fired or was never active; in the “fired” case the value may still be sitting in p.timer.C. That non‑blocking select snags the pending tick so the channel is empty before we call Reset again. Because of the default branch it never blocks—if there’s nothing to drain we just fall through. This pattern avoids stale wakeups or panics from resetting a timer whose channel still holds an unread tick.

Comment thread jitter/paced_buffer.go
opts...,
)

pb.done.Add(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where do we call .Done() on this wait group?
Maybe it's better to use errgroup?

@milos-lk milos-lk Sep 30, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we call it here:
https://github.com/livekit/media-sdk/blob/add-pacer-buffer/jitter/paced_buffer.go#L260

Since we’re only tracking this single goroutine, the plain sync.WaitGroup already does the job; IMO switching to an errgroup wouldn’t add value unless we need aggregated error handling / multiple goroutines. But it will do the job as well.

@milos-lk milos-lk closed this Dec 1, 2025
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.

4 participants