Priority queue error
dryarullin opened this issue · comments
Your environment.
- Version: v0.1.28
What did you do?
jitterBuffer := jitterbuffer.New(jitterbuffer.WithMinimumPacketCount(16))
jbEvent := make(chan struct{})
eventFunc := func(event jitterbuffer.Event, jb *jitterbuffer.JitterBuffer) {
jbEvent <- struct{}{}
}
jitterBuffer.Listen(jitterbuffer.BeginPlayback, eventFunc)
go func() {
for {
select {
case <-jbEvent:
{
packet, _ := jitterBuffer.Pop()
...
}
...
}
}
}()
go func() {
for {
packet, _, err := remoteTrack.ReadRTP()
...
jitterBuffer.Push(packet)
}
}()
What did you expect?
It seemed to me that I would receive jitterbuffer.BeginPlayback
event whenever the JitterBuffer packet count became 16.
What happened?
func (q *PriorityQueue) PopAt(sqNum uint16)
and func (q *PriorityQueue) PopAtTimestamp(timestamp uint32)
don't reduce the PriorityQueue length.
It would also be great to have an example of using JitterBuffer in practice.
Another question has appeared. Shouldn't the function func (jb *JitterBuffer) updateState()
be implemented like this?
func (jb *JitterBuffer) updateState() {
// For now, we only look at the number of packets captured in the play buffer
if jb.packets.Length() >= jb.minStartCount && jb.state == Buffering {
jb.state = Emitting
jb.playoutReady = true
jb.emit(BeginPlayback)
} else if jb.packets.Length() < jb.minStartCount && jb.state == Emitting {
jb.state = Buffering
jb.playoutReady = false
jb.emit(StartBuffering)
}
}
Thanks for reporting this, I'll add more tests and fix up the issues you've uncovered!
@dryarullin There are a couple things to unpack here, but from your original ticket did you never receive a BeginPlayback event, or did you receive the event the first time the buffer filled and not receive it after the buffer had dropped below your minimum?
Another question has appeared. Shouldn't the function
func (jb *JitterBuffer) updateState()
be implemented like this?
The original intent of this was to provide a very basic start to a jitter buffer. My understanding and idea was that it would be a fixed length delay that could be tuned based on initial network probing but once playback began it shouldn't stop but may underrun if no packets are available (in which case you should receive a buffer underrun event).
If we paused playback every time the buffer dipped below the minimum start count then this would behave basically like having no jitter buffer at all. I can see a use where if the buffer length hits zero (or perhaps a configurable percent of the minimum start length) we should enter a rebuffer state where we again wait for the minimum start count before re-starting playback but I'm unsure why you would expect it to pause and re-buffer every time the count dropped below 16 in your case.
Can you talk a bit more about your use case and need for the buffer to re-enter a buffering state like this?
I received the BeginPlayback event the first time the buffer filled and didn't receive it after the second time the buffer filled.
For example:
- the buffer was filled to the minimum for the first time
- the BeginPlayback event was received
- N elements were extracted from the buffer
- N elements were added to the buffer
- the BeginPlayback event wasn't received
Even if you assumed different logic, it seems to me that you need to add q.length--
code to the func (q *PriorityQueue) PopAt(sqNum uint16)
and func (q *PriorityQueue) PopAtTimestamp(timestamp uint32)
methods:
For example:
func (q *PriorityQueue) PopAt(sqNum uint16) (*rtp.Packet, error) {
if q.next == nil {
return nil, ErrInvalidOperation
}
if q.next.priority == sqNum {
val := q.next.val
q.next = q.next.next
q.length-- <------- here
return val, nil
}
pos := q.next
prev := q.next.prev
for pos != nil {
if pos.priority == sqNum {
val := pos.val
prev.next = pos.next
if prev.next != nil {
prev.next.prev = prev
}
q.length-- <------- here
return val, nil
}
prev = pos
pos = pos.next
}
return nil, ErrNotFound
}
Yes, I agree that bug exists. I was trying to understand the use case for multiple BeginPlayback events. I will get a PR open soon to resolve these.
Here's my current usage. And it performs really well on networks with packet loss. I emulate packet loss using a special utility. As you can see, I generally do not allow the buffer size to be less than the minimum. 16 in my case.
func (pi *PionRtcService) remoteTrackRead(ctx context.Context, done <-chan struct{},
remoteTrack *webrtc.TrackRemote) <-chan convert.AudioTrackData {
audioStream := make(chan convert.AudioTrackData)
jitterBuffer := jitterbuffer.New(jitterbuffer.WithMinimumPacketCount(16))
go func() {
defer close(audioStream)
for {
packet, _, err := remoteTrack.ReadRTP()
if err != nil {
if err == io.EOF {
audioStream <- convert.AudioTrackData{Final: true}
return
}
audioStream <- convert.AudioTrackData{Error: err}
return
}
if packet.Padding {
continue
}
jitterBuffer.Push(packet)
L:
for {
jbPacket, jbErr := jitterBuffer.Pop()
if jbErr != nil {
if errors.Is(jbErr, jitterbuffer.ErrNotFound) {
jitterBuffer.SetPlayoutHead(jitterBuffer.PlayoutHead() + 1)
continue
} else {
// ErrPopWhileBuffering error
break
}
}
select {
case audioStream <- convert.AudioTrackData{Data: jbPacket.Payload}:
{
break L
}
case <-done:
return
}
}
}
}()
return audioStream
}