mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 11:32:19 +08:00
Add an end-to-end test for the NACK sender
Test that NACKs are negotiated correctly, and that we receive the expected NACK if we negotiated it.
This commit is contained in:
@@ -9,12 +9,14 @@ package webrtc
|
|||||||
//
|
//
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pion/interceptor"
|
"github.com/pion/interceptor"
|
||||||
mock_interceptor "github.com/pion/interceptor/pkg/mock"
|
mock_interceptor "github.com/pion/interceptor/pkg/mock"
|
||||||
|
"github.com/pion/rtcp"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
"github.com/pion/transport/v3/test"
|
"github.com/pion/transport/v3/test"
|
||||||
"github.com/pion/webrtc/v4/pkg/media"
|
"github.com/pion/webrtc/v4/pkg/media"
|
||||||
@@ -285,3 +287,152 @@ func Test_Interceptor_ZeroSSRC(t *testing.T) {
|
|||||||
<-probeReceiverCreated
|
<-probeReceiverCreated
|
||||||
closePairNow(t, offerer, answerer)
|
closePairNow(t, offerer, answerer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestInterceptorNack is an end-to-end test for the NACK sender.
|
||||||
|
// It test that:
|
||||||
|
// - we get a NACK if we negotiated generic NACks;
|
||||||
|
// - we don't get a NACK if we did not negotiate generick NACKs;
|
||||||
|
// - the NACK corresponds to the missing packet.
|
||||||
|
func TestInterceptorNack(t *testing.T) {
|
||||||
|
to := test.TimeOut(time.Second * 20)
|
||||||
|
defer to.Stop()
|
||||||
|
|
||||||
|
t.Run("Nack", func(t *testing.T) { testInterceptorNack(t, true) })
|
||||||
|
t.Run("NoNack", func(t *testing.T) { testInterceptorNack(t, false) })
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInterceptorNack(t *testing.T, requestNack bool) {
|
||||||
|
const numPackets = 20
|
||||||
|
|
||||||
|
ir := interceptor.Registry{}
|
||||||
|
m := MediaEngine{}
|
||||||
|
var capability []RTCPFeedback
|
||||||
|
if requestNack {
|
||||||
|
capability = append(capability, RTCPFeedback{"nack", ""})
|
||||||
|
}
|
||||||
|
err := m.RegisterCodec(
|
||||||
|
RTPCodecParameters{
|
||||||
|
RTPCodecCapability: RTPCodecCapability{
|
||||||
|
"video/VP8", 90000, 0,
|
||||||
|
"",
|
||||||
|
capability,
|
||||||
|
},
|
||||||
|
PayloadType: 96,
|
||||||
|
},
|
||||||
|
RTPCodecTypeVideo,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
api := NewAPI(
|
||||||
|
WithMediaEngine(&m),
|
||||||
|
WithInterceptorRegistry(&ir),
|
||||||
|
)
|
||||||
|
|
||||||
|
pc1, err := api.NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
track1, err := NewTrackLocalStaticRTP(
|
||||||
|
RTPCodecCapability{MimeType: MimeTypeVP8},
|
||||||
|
"video", "pion",
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
sender, err := pc1.AddTrack(track1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
pc2, err := NewPeerConnection(Configuration{})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
offer, err := pc1.CreateOffer(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = pc1.SetLocalDescription(offer)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
<-GatheringCompletePromise(pc1)
|
||||||
|
|
||||||
|
err = pc2.SetRemoteDescription(*pc1.LocalDescription())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
answer, err := pc2.CreateAnswer(nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = pc2.SetLocalDescription(answer)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
<-GatheringCompletePromise(pc2)
|
||||||
|
|
||||||
|
err = pc1.SetRemoteDescription(*pc2.LocalDescription())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
var gotNack bool
|
||||||
|
rtcpDone := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(rtcpDone)
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
for {
|
||||||
|
n, _, err2 := sender.Read(buf)
|
||||||
|
// nolint
|
||||||
|
if err2 == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
ps, err2 := rtcp.Unmarshal(buf[:n])
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
for _, p := range ps {
|
||||||
|
if pn, ok := p.(*rtcp.TransportLayerNack); ok {
|
||||||
|
assert.Equal(t, len(pn.Nacks), 1)
|
||||||
|
assert.Equal(t,
|
||||||
|
pn.Nacks[0].PacketID, uint16(1),
|
||||||
|
)
|
||||||
|
assert.Equal(t,
|
||||||
|
pn.Nacks[0].LostPackets,
|
||||||
|
rtcp.PacketBitmap(0),
|
||||||
|
)
|
||||||
|
gotNack = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
pc2.OnTrack(func(track2 *TrackRemote, _ *RTPReceiver) {
|
||||||
|
for i := 0; i < numPackets; i++ {
|
||||||
|
if i == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p, _, err2 := track2.ReadRTP()
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
assert.Equal(t, p.SequenceNumber, uint16(i))
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < numPackets; i++ {
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
if i == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var p rtp.Packet
|
||||||
|
p.Version = 2
|
||||||
|
p.Marker = true
|
||||||
|
p.PayloadType = 96
|
||||||
|
p.SequenceNumber = uint16(i)
|
||||||
|
p.Timestamp = uint32(i * 90000 / 50)
|
||||||
|
p.Payload = []byte{42}
|
||||||
|
err2 := track1.WriteRTP(&p)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-done
|
||||||
|
err = pc1.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = pc2.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
<-rtcpDone
|
||||||
|
|
||||||
|
if requestNack {
|
||||||
|
if !gotNack {
|
||||||
|
t.Errorf("Expected to get a NACK, got none")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if gotNack {
|
||||||
|
t.Errorf("Expected to get no NACK, got one")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user