pion / interceptor

Pluggable RTP/RTCP processors for building real time communication

Home Page:https://pion.ly/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

  1. the buffer was filled to the minimum for the first time
  2. the BeginPlayback event was received
  3. N elements were extracted from the buffer
  4. N elements were added to the buffer
  5. 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
}