-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JitterBuffer: Add a JitterBuffer-based Interceptor
The JitterBufferInterceptor is designed to fit in a RemoteStream pipeline and buffer incoming packets for a short period (currently defaulting to 50 packets) before emitting packets to be consumed by the next step in the pipeline. The caller must ensure they are prepared to handle an ErrPopWhileBuffering in the case that insufficient packets have been received by the jitter buffer. The caller should retry the operation at some point later as the buffer may have been filled in the interim. The caller should also be aware that an ErrBufferUnderrun may be returned in the case that the initial buffering was sufficient and playback began but the caller is consuming packets (or they are not arriving) quickly enough.
- Loading branch information
1 parent
1449b4f
commit 2668bc2
Showing
9 changed files
with
281 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package jitterbuffer | ||
|
||
import ( | ||
"github.com/pion/logging" | ||
) | ||
|
||
// JitterBufferOption can be used to configure SenderInterceptor | ||
type JitterBufferOption func(d *JitterBufferInterceptor) error | ||
|
||
// Log sets a logger for the interceptor | ||
func Log(log logging.LeveledLogger) JitterBufferOption { | ||
return func(d *JitterBufferInterceptor) error { | ||
d.log = log | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package jitterbuffer | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/pion/interceptor" | ||
"github.com/pion/logging" | ||
"github.com/pion/rtp" | ||
) | ||
|
||
// JitterBufferInterceptorFactory is a interceptor.Factory for a GeneratorInterceptor | ||
type JitterBufferInterceptorFactory struct { | ||
Check warning on line 15 in pkg/jitterbuffer/receiver_interceptor.go GitHub Actions / lint / Go
|
||
opts []JitterBufferOption | ||
} | ||
|
||
// NewInterceptor constructs a new ReceiverInterceptor | ||
func (g *JitterBufferInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) { | ||
i := &JitterBufferInterceptor{ | ||
close: make(chan struct{}), | ||
log: logging.NewDefaultLoggerFactory().NewLogger("jitterbuffer"), | ||
buffer: New(), | ||
} | ||
|
||
for _, opt := range g.opts { | ||
if err := opt(i); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return i, nil | ||
} | ||
|
||
// JitterBufferInterceptor interceptor places a JitterBuffer in the chain to smooth packet arrival | ||
// and allow for network jitter | ||
// | ||
// The JitterBufferInterceptor is designed to fit in a RemoteStream | ||
// pipeline and buffer incoming packets for a short period (currently | ||
// defaulting to 50 packets) before emitting packets to be consumed by the | ||
// next step in the pipeline. | ||
// | ||
// The caller must ensure they are prepared to handle an | ||
// ErrPopWhileBuffering in the case that insufficient packets have been | ||
// received by the jitter buffer. The caller should retry the operation | ||
// at some point later as the buffer may have been filled in the interim. | ||
// | ||
// The caller should also be aware that an ErrBufferUnderrun may be | ||
// returned in the case that the initial buffering was sufficient and | ||
// playback began but the caller is consuming packets (or they are not | ||
// arriving) quickly enough. | ||
type JitterBufferInterceptor struct { | ||
interceptor.NoOp | ||
buffer *JitterBuffer | ||
m sync.Mutex | ||
wg sync.WaitGroup | ||
close chan struct{} | ||
log logging.LeveledLogger | ||
} | ||
|
||
// NewInterceptor returns a new JitterBufferInterceptorFactory | ||
func NewInterceptor(opts ...JitterBufferOption) (*JitterBufferInterceptorFactory, error) { | ||
return &JitterBufferInterceptorFactory{opts}, nil | ||
} | ||
|
||
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method | ||
// will be called once per rtp packet. | ||
func (i *JitterBufferInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader { | ||
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) { | ||
buf := make([]byte, len(b)) | ||
n, attr, err := reader.Read(buf, a) | ||
if err != nil { | ||
return n, attr, err | ||
} | ||
packet := &rtp.Packet{} | ||
if err := packet.Unmarshal(buf); err != nil { | ||
return 0, nil, err | ||
} | ||
i.buffer.Push(packet) | ||
if i.buffer.state == Emitting { | ||
newPkt, err := i.buffer.Pop() | ||
if err != nil { | ||
return 0, nil, err | ||
} | ||
nlen, err := newPkt.MarshalTo(b) | ||
return nlen, attr, err | ||
} | ||
return n, attr, ErrPopWhileBuffering | ||
}) | ||
} | ||
|
||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track. | ||
func (jb *JitterBufferInterceptor) UnbindRemoteStream(info *interceptor.StreamInfo) { | ||
defer jb.wg.Wait() | ||
jb.m.Lock() | ||
defer jb.m.Unlock() | ||
jb.buffer.Clear(true) | ||
} | ||
|
||
// Close closes the interceptor | ||
func (jb *JitterBufferInterceptor) Close() error { | ||
defer jb.wg.Wait() | ||
jb.m.Lock() | ||
defer jb.m.Unlock() | ||
jb.buffer.Clear(true) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package jitterbuffer | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
"time" | ||
|
||
"github.com/pion/interceptor" | ||
"github.com/pion/interceptor/internal/test" | ||
"github.com/pion/logging" | ||
"github.com/pion/rtcp" | ||
"github.com/pion/rtp" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestReceiverFilterEverythingOut(t *testing.T) { | ||
buf := bytes.Buffer{} | ||
|
||
factory, err := NewInterceptor( | ||
Log(logging.NewDefaultLoggerFactory().NewLogger("test")), | ||
) | ||
assert.NoError(t, err) | ||
|
||
i, err := factory.NewInterceptor("") | ||
assert.NoError(t, err) | ||
|
||
assert.Zero(t, buf.Len()) | ||
|
||
stream := test.NewMockStream(&interceptor.StreamInfo{ | ||
SSRC: 123456, | ||
ClockRate: 90000, | ||
}, i) | ||
defer func() { | ||
assert.NoError(t, stream.Close()) | ||
}() | ||
|
||
stream.ReceiveRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{ | ||
SenderSSRC: 123, | ||
MediaSSRC: 456, | ||
}}) | ||
stream.ReceiveRTP(&rtp.Packet{Header: rtp.Header{ | ||
SequenceNumber: uint16(0), | ||
}}) | ||
|
||
// Give time for packets to be handled and stream written to. | ||
time.Sleep(50 * time.Millisecond) | ||
|
||
err = i.Close() | ||
assert.NoError(t, err) | ||
|
||
// Every packet should have been filtered out – nothing should be written. | ||
assert.Zero(t, buf.Len()) | ||
} | ||
|
||
func TestReceiverFilterNothing(t *testing.T) { | ||
buf := bytes.Buffer{} | ||
|
||
factory, err := NewInterceptor( | ||
Log(logging.NewDefaultLoggerFactory().NewLogger("test")), | ||
) | ||
assert.NoError(t, err) | ||
|
||
i, err := factory.NewInterceptor("") | ||
assert.NoError(t, err) | ||
|
||
assert.EqualValues(t, 0, buf.Len()) | ||
|
||
stream := test.NewMockStream(&interceptor.StreamInfo{ | ||
SSRC: 123456, | ||
ClockRate: 90000, | ||
}, i) | ||
|
||
stream.ReceiveRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{ | ||
SenderSSRC: 123, | ||
MediaSSRC: 456, | ||
}}) | ||
for s := 0; s < 61; s++ { | ||
stream.ReceiveRTP(&rtp.Packet{Header: rtp.Header{ | ||
SequenceNumber: uint16(s), | ||
}}) | ||
} | ||
assert.NoError(t, stream.Close()) | ||
// Give time for packets to be handled and stream written to. | ||
time.Sleep(50 * time.Millisecond) | ||
for s := 0; s < 10; s++ { | ||
read := <-stream.ReadRTP() | ||
assert.EqualValues(t, uint16(s), read.Packet.Header.SequenceNumber) | ||
} | ||
err = i.Close() | ||
assert.NoError(t, err) | ||
} |