Files
webrtc/peerconnection_media_test.go
Sean DuBois 721b342e2e Properly generate DataChannel streamId
Before we computed DataChannel IDs before signaling, this
is incorrect because IDs must take into account if we are
running an DTLS Client or Server.

This updates the DataChannel ID generation code to take this
into account before generating a streamId.

Resolves #908
2019-11-14 10:51:05 -08:00

903 lines
21 KiB
Go

// +build !js
package webrtc
import (
"bytes"
"fmt"
"io"
"math/rand"
"reflect"
"regexp"
"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.AddTransceiver(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) {
iceComplete := make(chan bool)
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.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
if err != nil {
t.Fatal(err)
}
_, err = pcAnswer.AddTransceiver(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.RWMutex
onTrackFired := false
pcAnswer.OnTrack(func(track *Track, receiver *RTPReceiver) {
onTrackFiredLock.Lock()
defer onTrackFiredLock.Unlock()
onTrackFired = true
})
pcAnswer.OnICEConnectionStateChange(func(iceState ICEConnectionState) {
if iceState == ICEConnectionStateConnected {
go func() {
close(iceComplete)
}()
}
})
err = signalPair(pcOffer, pcAnswer)
if err != nil {
t.Fatal(err)
}
<-iceComplete
// 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.AddTransceiver(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.AddTransceiver(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
offer.SDP = regexp.MustCompile("a=ssrc.*").ReplaceAllString(offer.SDP, "")
offer.SDP = regexp.MustCompile("m=application(?s).*").ReplaceAllString(offer.SDP, "")
offer.SDP = regexp.MustCompile("\n\n").ReplaceAllString(offer.SDP, "")
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 "group" == attr.Key {
if "BUNDLE 1" == attr.Value {
success = true
}
}
}
if !success {
t.Fail()
}
assert.NoError(t, pc.Close())
}