mirror of
https://github.com/pion/webrtc.git
synced 2025-09-27 11:32:19 +08:00
1007 lines
23 KiB
Go
1007 lines
23 KiB
Go
// +build !js
|
|
|
|
package webrtc
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pion/rtcp"
|
|
"github.com/pion/sdp/v2"
|
|
"github.com/pion/transport/test"
|
|
"github.com/pion/webrtc/v2/pkg/media"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func offerMediaHasDirection(offer SessionDescription, kind RTPCodecType, direction RTPTransceiverDirection) bool {
|
|
for _, media := range offer.parsed.MediaDescriptions {
|
|
if media.MediaName.Media == kind.String() {
|
|
_, exists := media.Attribute(direction.String())
|
|
return exists
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
/*
|
|
Integration test for bi-directional peers
|
|
|
|
This asserts we can send RTP and RTCP both ways, and blocks until
|
|
each side gets something (and asserts payload contents)
|
|
*/
|
|
// nolint: gocyclo
|
|
func TestPeerConnection_Media_Sample(t *testing.T) {
|
|
const (
|
|
expectedTrackID = "video"
|
|
expectedTrackLabel = "pion"
|
|
)
|
|
|
|
api := NewAPI()
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
awaitRTPRecv := make(chan bool)
|
|
awaitRTPRecvClosed := make(chan bool)
|
|
awaitRTPSend := make(chan bool)
|
|
|
|
awaitRTCPSenderRecv := make(chan bool)
|
|
awaitRTCPSenderSend := make(chan error)
|
|
|
|
awaitRTCPReceiverRecv := make(chan error)
|
|
awaitRTCPReceiverSend := make(chan error)
|
|
|
|
trackMetadataValid := make(chan error)
|
|
|
|
pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
|
|
if track.ID() != expectedTrackID {
|
|
trackMetadataValid <- fmt.Errorf("Incoming Track ID is invalid expected(%s) actual(%s)", expectedTrackID, track.ID())
|
|
return
|
|
}
|
|
|
|
if track.Label() != expectedTrackLabel {
|
|
trackMetadataValid <- fmt.Errorf("Incoming Track Label is invalid expected(%s) actual(%s)", expectedTrackLabel, track.Label())
|
|
return
|
|
}
|
|
close(trackMetadataValid)
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: track.SSRC(), MediaSSRC: track.SSRC()}}); routineErr != nil {
|
|
awaitRTCPReceiverSend <- routineErr
|
|
return
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTCPSenderRecv:
|
|
close(awaitRTCPReceiverSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
_, routineErr := receiver.Read(make([]byte, 1400))
|
|
if routineErr != nil {
|
|
awaitRTCPReceiverRecv <- routineErr
|
|
} else {
|
|
close(awaitRTCPReceiverRecv)
|
|
}
|
|
}()
|
|
|
|
haveClosedAwaitRTPRecv := false
|
|
for {
|
|
p, routineErr := track.ReadRTP()
|
|
if routineErr != nil {
|
|
close(awaitRTPRecvClosed)
|
|
return
|
|
} else if bytes.Equal(p.Payload, []byte{0x10, 0x00}) && !haveClosedAwaitRTPRecv {
|
|
haveClosedAwaitRTPRecv = true
|
|
close(awaitRTPRecv)
|
|
}
|
|
}
|
|
})
|
|
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), expectedTrackID, expectedTrackLabel)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rtpReceiver, err := pcOffer.AddTrack(vp8Track)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); routineErr != nil {
|
|
fmt.Println(routineErr)
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTPRecv:
|
|
close(awaitRTPSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Millisecond * 100)
|
|
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: vp8Track.SSRC(), MediaSSRC: vp8Track.SSRC()}}); routineErr != nil {
|
|
awaitRTCPSenderSend <- routineErr
|
|
}
|
|
|
|
select {
|
|
case <-awaitRTCPReceiverRecv:
|
|
close(awaitRTCPSenderSend)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
if _, routineErr := rtpReceiver.Read(make([]byte, 1400)); routineErr == nil {
|
|
close(awaitRTCPSenderRecv)
|
|
}
|
|
}()
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err, ok := <-trackMetadataValid
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
<-awaitRTPRecv
|
|
<-awaitRTPSend
|
|
|
|
<-awaitRTCPSenderRecv
|
|
err, ok = <-awaitRTCPSenderSend
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
<-awaitRTCPReceiverRecv
|
|
err, ok = <-awaitRTCPReceiverSend
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
<-awaitRTPRecvClosed
|
|
}
|
|
|
|
/*
|
|
PeerConnection should be able to be torn down at anytime
|
|
This test adds an input track and asserts
|
|
|
|
* OnTrack doesn't fire since no video packets will arrive
|
|
* No goroutine leaks
|
|
* No deadlocks on shutdown
|
|
*/
|
|
func TestPeerConnection_Media_Shutdown(t *testing.T) {
|
|
iceCompleteAnswer := make(chan struct{})
|
|
iceCompleteOffer := make(chan struct{})
|
|
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
opusTrack, err := pcOffer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err = pcOffer.AddTrack(opusTrack); err != nil {
|
|
t.Fatal(err)
|
|
} else if _, err = pcAnswer.AddTrack(vp8Track); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var onTrackFiredLock sync.Mutex
|
|
onTrackFired := false
|
|
|
|
pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
|
|
onTrackFiredLock.Lock()
|
|
defer onTrackFiredLock.Unlock()
|
|
onTrackFired = true
|
|
})
|
|
|
|
pcAnswer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateConnected {
|
|
close(iceCompleteAnswer)
|
|
}
|
|
})
|
|
pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateConnected {
|
|
close(iceCompleteOffer)
|
|
}
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
<-iceCompleteAnswer
|
|
<-iceCompleteOffer
|
|
|
|
// Each PeerConnection should have one sender, one receiver and two transceivers
|
|
for _, pc := range []*PeerConnection{pcOffer, pcAnswer} {
|
|
senders := pc.GetSenders()
|
|
if len(senders) != 1 {
|
|
t.Errorf("Each PeerConnection should have one RTPSender, we have %d", len(senders))
|
|
}
|
|
|
|
receivers := pc.GetReceivers()
|
|
if len(receivers) != 2 {
|
|
t.Errorf("Each PeerConnection should have two RTPReceivers, we have %d", len(receivers))
|
|
}
|
|
|
|
transceivers := pc.GetTransceivers()
|
|
if len(transceivers) != 2 {
|
|
t.Errorf("Each PeerConnection should have two RTPTransceivers, we have %d", len(transceivers))
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
|
|
onTrackFiredLock.Lock()
|
|
if onTrackFired {
|
|
t.Fatalf("PeerConnection OnTrack fired even though we got no packets")
|
|
}
|
|
onTrackFiredLock.Unlock()
|
|
}
|
|
|
|
/*
|
|
Integration test for behavior around media and disconnected peers
|
|
|
|
* Sending RTP and RTCP to a disconnected Peer shouldn't return an error
|
|
*/
|
|
func TestPeerConnection_Media_Disconnected(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
s := SettingEngine{}
|
|
s.SetConnectionTimeout(time.Duration(1)*time.Second, time.Duration(250)*time.Millisecond)
|
|
|
|
api := NewAPI(WithSettingEngine(s))
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Track, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Sender, err := pcOffer.AddTrack(vp8Track)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
haveDisconnected := make(chan error)
|
|
pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
|
|
if iceState == ICEConnectionStateDisconnected {
|
|
close(haveDisconnected)
|
|
} else if iceState == ICEConnectionStateConnected {
|
|
// Assert that DTLS is done by pull remote certificate, don't tear down the PC early
|
|
for {
|
|
if len(vp8Sender.Transport().GetRemoteCertificate()) != 0 {
|
|
pcAnswer.sctpTransport.lock.RLock()
|
|
haveAssociation := pcAnswer.sctpTransport.association != nil
|
|
pcAnswer.sctpTransport.lock.RUnlock()
|
|
|
|
if haveAssociation {
|
|
break
|
|
}
|
|
}
|
|
|
|
time.Sleep(time.Second)
|
|
}
|
|
|
|
if pcCloseErr := pcAnswer.Close(); pcCloseErr != nil {
|
|
haveDisconnected <- pcCloseErr
|
|
}
|
|
}
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err, ok := <-haveDisconnected
|
|
if ok {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i <= 5; i++ {
|
|
if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); rtpErr != nil {
|
|
t.Fatal(rtpErr)
|
|
} else if rtcpErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 0}}); rtcpErr != nil {
|
|
t.Fatal(rtcpErr)
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
}
|
|
|
|
/*
|
|
Integration test for behavior around media and closing
|
|
|
|
* Writing and Reading from tracks should return io.EOF when the PeerConnection is closed
|
|
*/
|
|
func TestPeerConnection_Media_Closed(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err = pcOffer.AddTrack(vp8Writer); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
answerChan := make(chan *Track)
|
|
pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
|
|
answerChan <- t
|
|
})
|
|
|
|
err = signalPair(pcOffer, pcAnswer)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
vp8Reader := func() *Track {
|
|
for {
|
|
if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
time.Sleep(time.Millisecond * 25)
|
|
|
|
select {
|
|
case t := <-answerChan:
|
|
return t
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}()
|
|
|
|
closeChan := make(chan error)
|
|
go func() {
|
|
time.Sleep(time.Second)
|
|
closeChan <- pcAnswer.Close()
|
|
}()
|
|
if _, err = vp8Reader.Read(make([]byte, 1)); err != io.EOF {
|
|
t.Fatal("Reading from closed Track did not return io.EOF")
|
|
} else if err = <-closeChan; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
|
|
if err = vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}); err != io.ErrClosedPipe {
|
|
t.Fatal("Write to Track with no RTPSenders did not return io.ErrClosedPipe")
|
|
} else if err = pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: 0, MediaSSRC: 0}}); err != io.ErrClosedPipe {
|
|
t.Fatal("WriteRTCP to closed PeerConnection did not return io.ErrClosedPipe")
|
|
}
|
|
}
|
|
|
|
// If a SessionDescription has a single media section and no SSRC
|
|
// assume that it is meant to handle all RTP packets
|
|
func TestUndeclaredSSRC(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pcOffer, pcAnswer, err := api.newPair()
|
|
assert.NoError(t, err)
|
|
|
|
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
|
|
assert.NoError(t, err)
|
|
|
|
vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
assert.NoError(t, err)
|
|
|
|
_, err = pcOffer.AddTrack(vp8Writer)
|
|
assert.NoError(t, err)
|
|
|
|
onTrackFired := make(chan *Track)
|
|
pcAnswer.OnTrack(func(t *Track, r *RTPReceiver) {
|
|
close(onTrackFired)
|
|
})
|
|
|
|
offer, err := pcOffer.CreateOffer(nil)
|
|
assert.NoError(t, err)
|
|
|
|
assert.NoError(t, pcOffer.SetLocalDescription(offer))
|
|
|
|
// Filter SSRC lines, and remove SCTP
|
|
filteredSDP := ""
|
|
scanner := bufio.NewScanner(strings.NewReader(offer.SDP))
|
|
inApplicationMedia := false
|
|
for scanner.Scan() {
|
|
l := scanner.Text()
|
|
if strings.HasPrefix(l, "m=") {
|
|
inApplicationMedia = !inApplicationMedia
|
|
} else if strings.HasPrefix(l, "a=ssrc") {
|
|
continue
|
|
}
|
|
|
|
if inApplicationMedia {
|
|
continue
|
|
}
|
|
|
|
filteredSDP += l + "\n"
|
|
}
|
|
|
|
offer.SDP = filteredSDP
|
|
assert.NoError(t, pcAnswer.SetRemoteDescription(offer))
|
|
|
|
answer, err := pcAnswer.CreateAnswer(nil)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, pcAnswer.SetLocalDescription(answer))
|
|
assert.NoError(t, pcOffer.SetRemoteDescription(answer))
|
|
|
|
go func() {
|
|
for {
|
|
assert.NoError(t, vp8Writer.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
|
|
time.Sleep(time.Millisecond * 25)
|
|
|
|
select {
|
|
case <-onTrackFired:
|
|
return
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
}()
|
|
|
|
<-onTrackFired
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
}
|
|
|
|
func TestOfferRejectionMissingCodec(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
api := NewAPI()
|
|
api.mediaEngine.RegisterDefaultCodecs()
|
|
pc, err := api.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
noCodecAPI := NewAPI()
|
|
noCodecPC, err := noCodecAPI.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
track, err := pc.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pc.AddTrack(track); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := signalPair(pc, noCodecPC); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var sdes sdp.SessionDescription
|
|
if err := sdes.Unmarshal([]byte(pc.RemoteDescription().SDP)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var videoDesc sdp.MediaDescription
|
|
for _, m := range sdes.MediaDescriptions {
|
|
if m.MediaName.Media == "video" {
|
|
videoDesc = *m
|
|
}
|
|
}
|
|
|
|
if got, want := videoDesc.MediaName.Formats, []string{"0"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("rejecting unknown codec: sdp m=%s, want trailing 0", *videoDesc.MediaName.String())
|
|
}
|
|
|
|
assert.NoError(t, noCodecPC.Close())
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackSendOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeOpus,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendonly,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver() != nil {
|
|
t.Errorf("Transceiver shouldn't have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender() == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
if len(pc.GetTransceivers()) != 1 {
|
|
t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers()))
|
|
}
|
|
|
|
if len(pc.GetSenders()) != 1 {
|
|
t.Errorf("PeerConnection should have one sender but has %d", len(pc.GetSenders()))
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendonly)
|
|
}
|
|
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackSendRecv(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeOpus,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendrecv,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver() == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender() == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
if len(pc.GetTransceivers()) != 1 {
|
|
t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers()))
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
// nolint: dupl
|
|
func TestAddTransceiver(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendrecv,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver() == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender() == nil {
|
|
t.Errorf("Transceiver should have a sender")
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionSendrecv) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
// nolint: dupl
|
|
func TestAddTransceiverFromKind(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionRecvonly,
|
|
})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if transceiver.Receiver() == nil {
|
|
t.Errorf("Transceiver should have a receiver")
|
|
}
|
|
|
|
if transceiver.Sender() != nil {
|
|
t.Errorf("Transceiver shouldn't have a sender")
|
|
}
|
|
|
|
offer, err := pc.CreateOffer(nil)
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly) {
|
|
t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionRecvonly)
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromKindFailsSendOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionSendonly,
|
|
})
|
|
|
|
if transceiver != nil {
|
|
t.Error("AddTransceiverFromKind shouldn't succeed with Direction RTPTransceiverDirectionSendonly")
|
|
}
|
|
|
|
assert.NotNil(t, err)
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pc, err := NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
track, err := pc.NewTrack(
|
|
DefaultPayloadTypeH264,
|
|
0xDEADBEEF,
|
|
"track-id",
|
|
"track-label",
|
|
)
|
|
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
transceiver, err := pc.AddTransceiverFromTrack(track, RtpTransceiverInit{
|
|
Direction: RTPTransceiverDirectionRecvonly,
|
|
})
|
|
|
|
if transceiver != nil {
|
|
t.Error("AddTransceiverFromTrack shouldn't succeed with Direction RTPTransceiverDirectionRecvonly")
|
|
}
|
|
|
|
assert.NotNil(t, err)
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestOmitMediaFromBundleIfUnsupported(t *testing.T) {
|
|
const sdpOfferWithAudioAndVideo = `v=0
|
|
o=- 6476616870435111971 2 IN IP4 127.0.0.1
|
|
s=-
|
|
t=0 0
|
|
a=group:BUNDLE 0 1
|
|
m=audio 9 UDP/TLS/RTP/SAVPF 111
|
|
c=IN IP4 0.0.0.0
|
|
a=rtcp:9 IN IP4 0.0.0.0
|
|
a=ice-ufrag:sRIG
|
|
a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
|
|
a=ice-options:trickle
|
|
a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
|
|
a=setup:actpass
|
|
a=mid:0
|
|
a=sendrecv
|
|
a=rtpmap:111 opus/48000/2
|
|
a=fmtp:111 minptime=10;useinbandfec=1
|
|
m=video 9 UDP/TLS/RTP/SAVPF 96
|
|
c=IN IP4 0.0.0.0
|
|
a=rtcp:9 IN IP4 0.0.0.0
|
|
a=ice-ufrag:sRIG
|
|
a=ice-pwd:yZb5ZMsBlPoK577sGhjvEUtT
|
|
a=ice-options:trickle
|
|
a=fingerprint:sha-256 27:EF:25:BF:57:45:BC:1C:0D:36:42:FF:5E:93:71:D2:41:58:EA:46:FD:A8:2A:F3:13:94:6E:E6:43:23:CB:D7
|
|
a=setup:actpass
|
|
a=mid:1
|
|
a=sendrecv
|
|
a=rtpmap:96 H264/90000
|
|
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
|
|
`
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
mediaEngine := MediaEngine{}
|
|
mediaEngine.RegisterCodec(
|
|
NewRTPH264Codec(DefaultPayloadTypeH264, 90000),
|
|
)
|
|
|
|
api := NewAPI(
|
|
WithMediaEngine(mediaEngine),
|
|
)
|
|
|
|
pc, err := api.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
if err = pc.SetRemoteDescription(SessionDescription{
|
|
Type: SDPTypeOffer,
|
|
SDP: sdpOfferWithAudioAndVideo,
|
|
}); nil != err {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
answer, err := pc.CreateAnswer(nil)
|
|
if nil != err {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
success := false
|
|
for _, attr := range answer.parsed.Attributes {
|
|
if attr.Key == "group" {
|
|
if attr.Value == "BUNDLE 1" {
|
|
success = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !success {
|
|
t.Fail()
|
|
}
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestGetRegisteredRTPCodecs(t *testing.T) {
|
|
mediaEngine := MediaEngine{}
|
|
expectedCodec := NewRTPH264Codec(DefaultPayloadTypeH264, 90000)
|
|
mediaEngine.RegisterCodec(expectedCodec)
|
|
|
|
api := NewAPI(WithMediaEngine(mediaEngine))
|
|
pc, err := api.NewPeerConnection(Configuration{})
|
|
if err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
|
|
codecs := pc.GetRegisteredRTPCodecs(RTPCodecTypeVideo)
|
|
if len(codecs) != 1 {
|
|
t.Errorf("expected to get only 1 codec but got %d codecs", len(codecs))
|
|
}
|
|
|
|
actualCodec := codecs[0]
|
|
if actualCodec != expectedCodec {
|
|
t.Errorf("expected to get %v but got %v", expectedCodec, actualCodec)
|
|
}
|
|
|
|
assert.NoError(t, pc.Close())
|
|
}
|
|
|
|
func TestPlanBMultiTrack(t *testing.T) {
|
|
addSingleTrack := func(p *PeerConnection) *Track {
|
|
track, err := p.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), fmt.Sprintf("video-%d", rand.Uint32()), fmt.Sprintf("video-%d", rand.Uint32()))
|
|
assert.NoError(t, err)
|
|
|
|
_, err = p.AddTrack(track)
|
|
assert.NoError(t, err)
|
|
|
|
return track
|
|
}
|
|
|
|
lim := test.TimeOut(time.Second * 30)
|
|
defer lim.Stop()
|
|
|
|
report := test.CheckRoutines(t)
|
|
defer report()
|
|
|
|
pcOffer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB})
|
|
assert.NoError(t, err)
|
|
|
|
pcAnswer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB})
|
|
assert.NoError(t, err)
|
|
|
|
var onTrackWaitGroup sync.WaitGroup
|
|
onTrackWaitGroup.Add(2)
|
|
pcAnswer.OnTrack(func(track *Track, r *RTPReceiver) {
|
|
onTrackWaitGroup.Done()
|
|
})
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
onTrackWaitGroup.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo)
|
|
assert.NoError(t, err)
|
|
|
|
track1 := addSingleTrack(pcOffer)
|
|
track2 := addSingleTrack(pcOffer)
|
|
|
|
assert.NoError(t, signalPair(pcOffer, pcAnswer))
|
|
|
|
func() {
|
|
for {
|
|
select {
|
|
case <-time.After(20 * time.Millisecond):
|
|
assert.NoError(t, track1.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
|
|
assert.NoError(t, track2.WriteSample(media.Sample{Data: []byte{0x00}, Samples: 1}))
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
assert.NoError(t, pcOffer.Close())
|
|
assert.NoError(t, pcAnswer.Close())
|
|
}
|