github.com/pion/webrtc/v3@v3.2.24/peerconnection_media_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build !js 5 // +build !js 6 7 package webrtc 8 9 import ( 10 "bufio" 11 "bytes" 12 "context" 13 "errors" 14 "fmt" 15 "io" 16 "strings" 17 "sync" 18 "testing" 19 "time" 20 21 "github.com/pion/logging" 22 "github.com/pion/randutil" 23 "github.com/pion/rtcp" 24 "github.com/pion/rtp" 25 "github.com/pion/sdp/v3" 26 "github.com/pion/transport/v2/test" 27 "github.com/pion/webrtc/v3/pkg/media" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 ) 31 32 var ( 33 errIncomingTrackIDInvalid = errors.New("incoming Track ID is invalid") 34 errIncomingTrackLabelInvalid = errors.New("incoming Track Label is invalid") 35 errNoTransceiverwithMid = errors.New("no transceiver with mid") 36 ) 37 38 func registerSimulcastHeaderExtensions(m *MediaEngine, codecType RTPCodecType) { 39 for _, extension := range []string{ 40 sdp.SDESMidURI, 41 sdp.SDESRTPStreamIDURI, 42 sdesRepairRTPStreamIDURI, 43 } { 44 if err := m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: extension}, codecType); err != nil { 45 panic(err) 46 } 47 } 48 } 49 50 /* 51 Integration test for bi-directional peers 52 53 This asserts we can send RTP and RTCP both ways, and blocks until 54 each side gets something (and asserts payload contents) 55 */ 56 // nolint: gocyclo 57 func TestPeerConnection_Media_Sample(t *testing.T) { 58 const ( 59 expectedTrackID = "video" 60 expectedStreamID = "pion" 61 ) 62 63 lim := test.TimeOut(time.Second * 30) 64 defer lim.Stop() 65 66 report := test.CheckRoutines(t) 67 defer report() 68 69 pcOffer, pcAnswer, err := newPair() 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 awaitRTPRecv := make(chan bool) 75 awaitRTPRecvClosed := make(chan bool) 76 awaitRTPSend := make(chan bool) 77 78 awaitRTCPSenderRecv := make(chan bool) 79 awaitRTCPSenderSend := make(chan error) 80 81 awaitRTCPReceiverRecv := make(chan error) 82 awaitRTCPReceiverSend := make(chan error) 83 84 trackMetadataValid := make(chan error) 85 86 pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) { 87 if track.ID() != expectedTrackID { 88 trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackIDInvalid, expectedTrackID, track.ID()) 89 return 90 } 91 92 if track.StreamID() != expectedStreamID { 93 trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedStreamID, track.StreamID()) 94 return 95 } 96 close(trackMetadataValid) 97 98 go func() { 99 for { 100 time.Sleep(time.Millisecond * 100) 101 if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: uint32(track.SSRC()), MediaSSRC: uint32(track.SSRC())}}); routineErr != nil { 102 awaitRTCPReceiverSend <- routineErr 103 return 104 } 105 106 select { 107 case <-awaitRTCPSenderRecv: 108 close(awaitRTCPReceiverSend) 109 return 110 default: 111 } 112 } 113 }() 114 115 go func() { 116 _, _, routineErr := receiver.Read(make([]byte, 1400)) 117 if routineErr != nil { 118 awaitRTCPReceiverRecv <- routineErr 119 } else { 120 close(awaitRTCPReceiverRecv) 121 } 122 }() 123 124 haveClosedAwaitRTPRecv := false 125 for { 126 p, _, routineErr := track.ReadRTP() 127 if routineErr != nil { 128 close(awaitRTPRecvClosed) 129 return 130 } else if bytes.Equal(p.Payload, []byte{0x10, 0x00}) && !haveClosedAwaitRTPRecv { 131 haveClosedAwaitRTPRecv = true 132 close(awaitRTPRecv) 133 } 134 } 135 }) 136 137 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, expectedTrackID, expectedStreamID) 138 if err != nil { 139 t.Fatal(err) 140 } 141 sender, err := pcOffer.AddTrack(vp8Track) 142 if err != nil { 143 t.Fatal(err) 144 } 145 146 go func() { 147 for { 148 time.Sleep(time.Millisecond * 100) 149 if pcOffer.ICEConnectionState() != ICEConnectionStateConnected { 150 continue 151 } 152 if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil { 153 fmt.Println(routineErr) 154 } 155 156 select { 157 case <-awaitRTPRecv: 158 close(awaitRTPSend) 159 return 160 default: 161 } 162 } 163 }() 164 165 go func() { 166 for { 167 time.Sleep(time.Millisecond * 100) 168 if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(sender.trackEncodings[0].ssrc), MediaSSRC: uint32(sender.trackEncodings[0].ssrc)}}); routineErr != nil { 169 awaitRTCPSenderSend <- routineErr 170 } 171 172 select { 173 case <-awaitRTCPReceiverRecv: 174 close(awaitRTCPSenderSend) 175 return 176 default: 177 } 178 } 179 }() 180 181 go func() { 182 if _, _, routineErr := sender.Read(make([]byte, 1400)); routineErr == nil { 183 close(awaitRTCPSenderRecv) 184 } 185 }() 186 187 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 188 189 err, ok := <-trackMetadataValid 190 if ok { 191 t.Fatal(err) 192 } 193 194 <-awaitRTPRecv 195 <-awaitRTPSend 196 197 <-awaitRTCPSenderRecv 198 if err, ok = <-awaitRTCPSenderSend; ok { 199 t.Fatal(err) 200 } 201 202 <-awaitRTCPReceiverRecv 203 if err, ok = <-awaitRTCPReceiverSend; ok { 204 t.Fatal(err) 205 } 206 207 closePairNow(t, pcOffer, pcAnswer) 208 <-awaitRTPRecvClosed 209 } 210 211 /* 212 PeerConnection should be able to be torn down at anytime 213 This test adds an input track and asserts 214 215 * OnTrack doesn't fire since no video packets will arrive 216 * No goroutine leaks 217 * No deadlocks on shutdown 218 */ 219 func TestPeerConnection_Media_Shutdown(t *testing.T) { 220 iceCompleteAnswer := make(chan struct{}) 221 iceCompleteOffer := make(chan struct{}) 222 223 lim := test.TimeOut(time.Second * 30) 224 defer lim.Stop() 225 226 report := test.CheckRoutines(t) 227 defer report() 228 229 pcOffer, pcAnswer, err := newPair() 230 if err != nil { 231 t.Fatal(err) 232 } 233 234 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeAudio, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 240 if err != nil { 241 t.Fatal(err) 242 } 243 244 opusTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeOpus}, "audio", "pion1") 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 if _, err = pcOffer.AddTrack(opusTrack); err != nil { 255 t.Fatal(err) 256 } else if _, err = pcAnswer.AddTrack(vp8Track); err != nil { 257 t.Fatal(err) 258 } 259 260 var onTrackFiredLock sync.Mutex 261 onTrackFired := false 262 263 pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) { 264 onTrackFiredLock.Lock() 265 defer onTrackFiredLock.Unlock() 266 onTrackFired = true 267 }) 268 269 pcAnswer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 270 if iceState == ICEConnectionStateConnected { 271 close(iceCompleteAnswer) 272 } 273 }) 274 pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 275 if iceState == ICEConnectionStateConnected { 276 close(iceCompleteOffer) 277 } 278 }) 279 280 err = signalPair(pcOffer, pcAnswer) 281 if err != nil { 282 t.Fatal(err) 283 } 284 <-iceCompleteAnswer 285 <-iceCompleteOffer 286 287 // Each PeerConnection should have one sender, one receiver and one transceiver 288 for _, pc := range []*PeerConnection{pcOffer, pcAnswer} { 289 senders := pc.GetSenders() 290 if len(senders) != 1 { 291 t.Errorf("Each PeerConnection should have one RTPSender, we have %d", len(senders)) 292 } 293 294 receivers := pc.GetReceivers() 295 if len(receivers) != 2 { 296 t.Errorf("Each PeerConnection should have two RTPReceivers, we have %d", len(receivers)) 297 } 298 299 transceivers := pc.GetTransceivers() 300 if len(transceivers) != 2 { 301 t.Errorf("Each PeerConnection should have two RTPTransceivers, we have %d", len(transceivers)) 302 } 303 } 304 305 closePairNow(t, pcOffer, pcAnswer) 306 307 onTrackFiredLock.Lock() 308 if onTrackFired { 309 t.Fatalf("PeerConnection OnTrack fired even though we got no packets") 310 } 311 onTrackFiredLock.Unlock() 312 } 313 314 /* 315 Integration test for behavior around media and disconnected peers 316 317 * Sending RTP and RTCP to a disconnected Peer shouldn't return an error 318 */ 319 func TestPeerConnection_Media_Disconnected(t *testing.T) { 320 lim := test.TimeOut(time.Second * 30) 321 defer lim.Stop() 322 323 report := test.CheckRoutines(t) 324 defer report() 325 326 s := SettingEngine{} 327 s.SetICETimeouts(time.Second/2, time.Second/2, time.Second/8) 328 329 m := &MediaEngine{} 330 assert.NoError(t, m.RegisterDefaultCodecs()) 331 332 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(s), WithMediaEngine(m)).newPair(Configuration{}) 333 if err != nil { 334 t.Fatal(err) 335 } 336 337 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 338 if err != nil { 339 t.Fatal(err) 340 } 341 342 vp8Sender, err := pcOffer.AddTrack(vp8Track) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 haveDisconnected := make(chan error) 348 pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 349 if iceState == ICEConnectionStateDisconnected { 350 close(haveDisconnected) 351 } else if iceState == ICEConnectionStateConnected { 352 // Assert that DTLS is done by pull remote certificate, don't tear down the PC early 353 for { 354 if len(vp8Sender.Transport().GetRemoteCertificate()) != 0 { 355 if pcAnswer.sctpTransport.association() != nil { 356 break 357 } 358 } 359 360 time.Sleep(time.Second) 361 } 362 363 if pcCloseErr := pcAnswer.Close(); pcCloseErr != nil { 364 haveDisconnected <- pcCloseErr 365 } 366 } 367 }) 368 369 err = signalPair(pcOffer, pcAnswer) 370 if err != nil { 371 t.Fatal(err) 372 } 373 374 err, ok := <-haveDisconnected 375 if ok { 376 t.Fatal(err) 377 } 378 for i := 0; i <= 5; i++ { 379 if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); rtpErr != nil { 380 t.Fatal(rtpErr) 381 } else if rtcpErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 0}}); rtcpErr != nil { 382 t.Fatal(rtcpErr) 383 } 384 } 385 386 assert.NoError(t, pcOffer.Close()) 387 } 388 389 type undeclaredSsrcLogger struct{ unhandledSimulcastError chan struct{} } 390 391 func (u *undeclaredSsrcLogger) Trace(string) {} 392 func (u *undeclaredSsrcLogger) Tracef(string, ...interface{}) {} 393 func (u *undeclaredSsrcLogger) Debug(string) {} 394 func (u *undeclaredSsrcLogger) Debugf(string, ...interface{}) {} 395 func (u *undeclaredSsrcLogger) Info(string) {} 396 func (u *undeclaredSsrcLogger) Infof(string, ...interface{}) {} 397 func (u *undeclaredSsrcLogger) Warn(string) {} 398 func (u *undeclaredSsrcLogger) Warnf(string, ...interface{}) {} 399 func (u *undeclaredSsrcLogger) Error(string) {} 400 func (u *undeclaredSsrcLogger) Errorf(format string, _ ...interface{}) { 401 if format == incomingUnhandledRTPSsrc { 402 close(u.unhandledSimulcastError) 403 } 404 } 405 406 type undeclaredSsrcLoggerFactory struct{ unhandledSimulcastError chan struct{} } 407 408 func (u *undeclaredSsrcLoggerFactory) NewLogger(string) logging.LeveledLogger { 409 return &undeclaredSsrcLogger{u.unhandledSimulcastError} 410 } 411 412 // Filter SSRC lines 413 func filterSsrc(offer string) (filteredSDP string) { 414 scanner := bufio.NewScanner(strings.NewReader(offer)) 415 for scanner.Scan() { 416 l := scanner.Text() 417 if strings.HasPrefix(l, "a=ssrc") { 418 continue 419 } 420 421 filteredSDP += l + "\n" 422 } 423 return 424 } 425 426 // If a SessionDescription has a single media section and no SSRC 427 // assume that it is meant to handle all RTP packets 428 func TestUndeclaredSSRC(t *testing.T) { 429 lim := test.TimeOut(time.Second * 30) 430 defer lim.Stop() 431 432 report := test.CheckRoutines(t) 433 defer report() 434 435 t.Run("No SSRC", func(t *testing.T) { 436 pcOffer, pcAnswer, err := newPair() 437 assert.NoError(t, err) 438 439 vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 440 assert.NoError(t, err) 441 442 _, err = pcOffer.AddTrack(vp8Writer) 443 assert.NoError(t, err) 444 445 onTrackFired := make(chan struct{}) 446 pcAnswer.OnTrack(func(trackRemote *TrackRemote, r *RTPReceiver) { 447 assert.Equal(t, trackRemote.StreamID(), vp8Writer.StreamID()) 448 assert.Equal(t, trackRemote.ID(), vp8Writer.ID()) 449 close(onTrackFired) 450 }) 451 452 offer, err := pcOffer.CreateOffer(nil) 453 assert.NoError(t, err) 454 455 offerGatheringComplete := GatheringCompletePromise(pcOffer) 456 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 457 <-offerGatheringComplete 458 459 offer.SDP = filterSsrc(pcOffer.LocalDescription().SDP) 460 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 461 462 answer, err := pcAnswer.CreateAnswer(nil) 463 assert.NoError(t, err) 464 465 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 466 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 467 <-answerGatheringComplete 468 469 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 470 471 sendVideoUntilDone(onTrackFired, t, []*TrackLocalStaticSample{vp8Writer}) 472 closePairNow(t, pcOffer, pcAnswer) 473 }) 474 475 t.Run("Has RID", func(t *testing.T) { 476 unhandledSimulcastError := make(chan struct{}) 477 478 m := &MediaEngine{} 479 assert.NoError(t, m.RegisterDefaultCodecs()) 480 481 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{ 482 LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError}, 483 }), WithMediaEngine(m)).newPair(Configuration{}) 484 assert.NoError(t, err) 485 486 vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 487 assert.NoError(t, err) 488 489 _, err = pcOffer.AddTrack(vp8Writer) 490 assert.NoError(t, err) 491 492 offer, err := pcOffer.CreateOffer(nil) 493 assert.NoError(t, err) 494 495 offerGatheringComplete := GatheringCompletePromise(pcOffer) 496 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 497 <-offerGatheringComplete 498 499 // Append RID to end of SessionDescription. Will not be considered unhandled anymore 500 offer.SDP = filterSsrc(pcOffer.LocalDescription().SDP) + "a=" + sdpAttributeRid + "\r\n" 501 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 502 503 answer, err := pcAnswer.CreateAnswer(nil) 504 assert.NoError(t, err) 505 506 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 507 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 508 <-answerGatheringComplete 509 510 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 511 512 sendVideoUntilDone(unhandledSimulcastError, t, []*TrackLocalStaticSample{vp8Writer}) 513 closePairNow(t, pcOffer, pcAnswer) 514 }) 515 } 516 517 func TestAddTransceiverFromTrackSendOnly(t *testing.T) { 518 lim := test.TimeOut(time.Second * 30) 519 defer lim.Stop() 520 521 report := test.CheckRoutines(t) 522 defer report() 523 524 pc, err := NewPeerConnection(Configuration{}) 525 if err != nil { 526 t.Error(err.Error()) 527 } 528 529 track, err := NewTrackLocalStaticSample( 530 RTPCodecCapability{MimeType: "audio/Opus"}, 531 "track-id", 532 "stream-id", 533 ) 534 if err != nil { 535 t.Error(err.Error()) 536 } 537 538 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 539 Direction: RTPTransceiverDirectionSendonly, 540 }) 541 if err != nil { 542 t.Error(err.Error()) 543 } 544 545 if transceiver.Receiver() != nil { 546 t.Errorf("Transceiver shouldn't have a receiver") 547 } 548 549 if transceiver.Sender() == nil { 550 t.Errorf("Transceiver should have a sender") 551 } 552 553 if len(pc.GetTransceivers()) != 1 { 554 t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers())) 555 } 556 557 if len(pc.GetSenders()) != 1 { 558 t.Errorf("PeerConnection should have one sender but has %d", len(pc.GetSenders())) 559 } 560 561 offer, err := pc.CreateOffer(nil) 562 if err != nil { 563 t.Error(err.Error()) 564 } 565 566 if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly) { 567 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendonly) 568 } 569 570 assert.NoError(t, pc.Close()) 571 } 572 573 func TestAddTransceiverFromTrackSendRecv(t *testing.T) { 574 lim := test.TimeOut(time.Second * 30) 575 defer lim.Stop() 576 577 report := test.CheckRoutines(t) 578 defer report() 579 580 pc, err := NewPeerConnection(Configuration{}) 581 if err != nil { 582 t.Error(err.Error()) 583 } 584 585 track, err := NewTrackLocalStaticSample( 586 RTPCodecCapability{MimeType: "audio/Opus"}, 587 "track-id", 588 "stream-id", 589 ) 590 if err != nil { 591 t.Error(err.Error()) 592 } 593 594 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 595 Direction: RTPTransceiverDirectionSendrecv, 596 }) 597 if err != nil { 598 t.Error(err.Error()) 599 } 600 601 if transceiver.Receiver() == nil { 602 t.Errorf("Transceiver should have a receiver") 603 } 604 605 if transceiver.Sender() == nil { 606 t.Errorf("Transceiver should have a sender") 607 } 608 609 if len(pc.GetTransceivers()) != 1 { 610 t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers())) 611 } 612 613 offer, err := pc.CreateOffer(nil) 614 if err != nil { 615 t.Error(err.Error()) 616 } 617 618 if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv) { 619 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv) 620 } 621 assert.NoError(t, pc.Close()) 622 } 623 624 func TestAddTransceiverAddTrack_Reuse(t *testing.T) { 625 pc, err := NewPeerConnection(Configuration{}) 626 assert.NoError(t, err) 627 628 tr, err := pc.AddTransceiverFromKind( 629 RTPCodecTypeVideo, 630 RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}, 631 ) 632 assert.NoError(t, err) 633 634 assert.Equal(t, []*RTPTransceiver{tr}, pc.GetTransceivers()) 635 636 addTrack := func() (TrackLocal, *RTPSender) { 637 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 638 assert.NoError(t, err) 639 640 sender, err := pc.AddTrack(track) 641 assert.NoError(t, err) 642 643 return track, sender 644 } 645 646 track1, sender1 := addTrack() 647 assert.Equal(t, 1, len(pc.GetTransceivers())) 648 assert.Equal(t, sender1, tr.Sender()) 649 assert.Equal(t, track1, tr.Sender().Track()) 650 require.NoError(t, pc.RemoveTrack(sender1)) 651 652 track2, _ := addTrack() 653 assert.Equal(t, 1, len(pc.GetTransceivers())) 654 assert.Equal(t, track2, tr.Sender().Track()) 655 656 addTrack() 657 assert.Equal(t, 2, len(pc.GetTransceivers())) 658 659 assert.NoError(t, pc.Close()) 660 } 661 662 func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) { 663 pc, err := NewPeerConnection(Configuration{}) 664 assert.NoError(t, err) 665 666 _, err = pc.AddTransceiverFromKind( 667 RTPCodecTypeVideo, 668 RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}, 669 ) 670 assert.NoError(t, err) 671 672 dtlsTransport := pc.dtlsTransport 673 pc.dtlsTransport = nil 674 675 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 676 assert.NoError(t, err) 677 678 _, err = pc.AddTrack(track) 679 assert.Error(t, err, "DTLSTransport must not be nil") 680 681 assert.Equal(t, 1, len(pc.GetTransceivers())) 682 683 pc.dtlsTransport = dtlsTransport 684 assert.NoError(t, pc.Close()) 685 } 686 687 func TestRtpSenderReceiver_ReadClose_Error(t *testing.T) { 688 pc, err := NewPeerConnection(Configuration{}) 689 assert.NoError(t, err) 690 691 tr, err := pc.AddTransceiverFromKind( 692 RTPCodecTypeVideo, 693 RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv}, 694 ) 695 assert.NoError(t, err) 696 697 sender, receiver := tr.Sender(), tr.Receiver() 698 assert.NoError(t, sender.Stop()) 699 _, _, err = sender.Read(make([]byte, 0, 1400)) 700 assert.ErrorIs(t, err, io.ErrClosedPipe) 701 702 assert.NoError(t, receiver.Stop()) 703 _, _, err = receiver.Read(make([]byte, 0, 1400)) 704 assert.ErrorIs(t, err, io.ErrClosedPipe) 705 706 assert.NoError(t, pc.Close()) 707 } 708 709 // nolint: dupl 710 func TestAddTransceiverFromKind(t *testing.T) { 711 lim := test.TimeOut(time.Second * 30) 712 defer lim.Stop() 713 714 report := test.CheckRoutines(t) 715 defer report() 716 717 pc, err := NewPeerConnection(Configuration{}) 718 if err != nil { 719 t.Error(err.Error()) 720 } 721 722 transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{ 723 Direction: RTPTransceiverDirectionRecvonly, 724 }) 725 if err != nil { 726 t.Error(err.Error()) 727 } 728 729 if transceiver.Receiver() == nil { 730 t.Errorf("Transceiver should have a receiver") 731 } 732 733 if transceiver.Sender() != nil { 734 t.Errorf("Transceiver shouldn't have a sender") 735 } 736 737 offer, err := pc.CreateOffer(nil) 738 if err != nil { 739 t.Error(err.Error()) 740 } 741 742 if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly) { 743 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionRecvonly) 744 } 745 assert.NoError(t, pc.Close()) 746 } 747 748 func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) { 749 lim := test.TimeOut(time.Second * 30) 750 defer lim.Stop() 751 752 report := test.CheckRoutines(t) 753 defer report() 754 755 pc, err := NewPeerConnection(Configuration{}) 756 if err != nil { 757 t.Error(err.Error()) 758 } 759 760 track, err := NewTrackLocalStaticSample( 761 RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, 762 "track-id", 763 "track-label", 764 ) 765 if err != nil { 766 t.Error(err.Error()) 767 } 768 769 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 770 Direction: RTPTransceiverDirectionRecvonly, 771 }) 772 773 if transceiver != nil { 774 t.Error("AddTransceiverFromTrack shouldn't succeed with Direction RTPTransceiverDirectionRecvonly") 775 } 776 777 assert.NotNil(t, err) 778 assert.NoError(t, pc.Close()) 779 } 780 781 func TestPlanBMediaExchange(t *testing.T) { 782 runTest := func(trackCount int, t *testing.T) { 783 addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample { 784 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()), fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32())) 785 assert.NoError(t, err) 786 787 _, err = p.AddTrack(track) 788 assert.NoError(t, err) 789 790 return track 791 } 792 793 pcOffer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB}) 794 assert.NoError(t, err) 795 796 pcAnswer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB}) 797 assert.NoError(t, err) 798 799 var onTrackWaitGroup sync.WaitGroup 800 onTrackWaitGroup.Add(trackCount) 801 pcAnswer.OnTrack(func(track *TrackRemote, r *RTPReceiver) { 802 onTrackWaitGroup.Done() 803 }) 804 805 done := make(chan struct{}) 806 go func() { 807 onTrackWaitGroup.Wait() 808 close(done) 809 }() 810 811 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo) 812 assert.NoError(t, err) 813 814 outboundTracks := []*TrackLocalStaticSample{} 815 for i := 0; i < trackCount; i++ { 816 outboundTracks = append(outboundTracks, addSingleTrack(pcOffer)) 817 } 818 819 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 820 821 func() { 822 for { 823 select { 824 case <-time.After(20 * time.Millisecond): 825 for _, track := range outboundTracks { 826 assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})) 827 } 828 case <-done: 829 return 830 } 831 } 832 }() 833 834 closePairNow(t, pcOffer, pcAnswer) 835 } 836 837 lim := test.TimeOut(time.Second * 30) 838 defer lim.Stop() 839 840 report := test.CheckRoutines(t) 841 defer report() 842 843 t.Run("Single Track", func(t *testing.T) { 844 runTest(1, t) 845 }) 846 t.Run("Multi Track", func(t *testing.T) { 847 runTest(2, t) 848 }) 849 } 850 851 // TestPeerConnection_Start_Only_Negotiated_Senders tests that only 852 // the current negotiated transceivers senders provided in an 853 // offer/answer are started 854 func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) { 855 lim := test.TimeOut(time.Second * 30) 856 defer lim.Stop() 857 858 report := test.CheckRoutines(t) 859 defer report() 860 861 pcOffer, err := NewPeerConnection(Configuration{}) 862 assert.NoError(t, err) 863 defer func() { assert.NoError(t, pcOffer.Close()) }() 864 865 pcAnswer, err := NewPeerConnection(Configuration{}) 866 assert.NoError(t, err) 867 defer func() { assert.NoError(t, pcAnswer.Close()) }() 868 869 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 870 require.NoError(t, err) 871 872 sender1, err := pcOffer.AddTrack(track1) 873 require.NoError(t, err) 874 875 offer, err := pcOffer.CreateOffer(nil) 876 assert.NoError(t, err) 877 878 offerGatheringComplete := GatheringCompletePromise(pcOffer) 879 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 880 <-offerGatheringComplete 881 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 882 answer, err := pcAnswer.CreateAnswer(nil) 883 assert.NoError(t, err) 884 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 885 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 886 <-answerGatheringComplete 887 888 // Add a new track between providing the offer and applying the answer 889 890 track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 891 require.NoError(t, err) 892 893 sender2, err := pcOffer.AddTrack(track2) 894 require.NoError(t, err) 895 896 // apply answer so we'll test generateMatchedSDP 897 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 898 899 // Wait for senders to be started by startTransports spawned goroutine 900 pcOffer.ops.Done() 901 902 // sender1 should be started but sender2 should not be started 903 assert.True(t, sender1.hasSent(), "sender1 is not started but should be started") 904 assert.False(t, sender2.hasSent(), "sender2 is started but should not be started") 905 } 906 907 // TestPeerConnection_Start_Right_Receiver tests that the right 908 // receiver (the receiver which transceiver has the same media section as the track) 909 // is started for the specified track 910 func TestPeerConnection_Start_Right_Receiver(t *testing.T) { 911 isTransceiverReceiverStarted := func(pc *PeerConnection, mid string) (bool, error) { 912 for _, transceiver := range pc.GetTransceivers() { 913 if transceiver.Mid() != mid { 914 continue 915 } 916 return transceiver.Receiver() != nil && transceiver.Receiver().haveReceived(), nil 917 } 918 return false, fmt.Errorf("%w: %q", errNoTransceiverwithMid, mid) 919 } 920 921 lim := test.TimeOut(time.Second * 30) 922 defer lim.Stop() 923 924 report := test.CheckRoutines(t) 925 defer report() 926 927 pcOffer, pcAnswer, err := newPair() 928 require.NoError(t, err) 929 930 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 931 assert.NoError(t, err) 932 933 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 934 require.NoError(t, err) 935 936 sender1, err := pcOffer.AddTrack(track1) 937 require.NoError(t, err) 938 939 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 940 941 pcOffer.ops.Done() 942 pcAnswer.ops.Done() 943 944 // transceiver with mid 0 should be started 945 started, err := isTransceiverReceiverStarted(pcAnswer, "0") 946 assert.NoError(t, err) 947 assert.True(t, started, "transceiver with mid 0 should be started") 948 949 // Remove track 950 assert.NoError(t, pcOffer.RemoveTrack(sender1)) 951 952 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 953 954 pcOffer.ops.Done() 955 pcAnswer.ops.Done() 956 957 // transceiver with mid 0 should not be started 958 started, err = isTransceiverReceiverStarted(pcAnswer, "0") 959 assert.NoError(t, err) 960 assert.False(t, started, "transceiver with mid 0 should not be started") 961 962 // Add a new transceiver (we're not using AddTrack since it'll reuse the transceiver with mid 0) 963 _, err = pcOffer.AddTransceiverFromTrack(track1) 964 assert.NoError(t, err) 965 966 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 967 assert.NoError(t, err) 968 969 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 970 971 pcOffer.ops.Done() 972 pcAnswer.ops.Done() 973 974 // transceiver with mid 0 should not be started 975 started, err = isTransceiverReceiverStarted(pcAnswer, "0") 976 assert.NoError(t, err) 977 assert.False(t, started, "transceiver with mid 0 should not be started") 978 // transceiver with mid 2 should be started 979 started, err = isTransceiverReceiverStarted(pcAnswer, "2") 980 assert.NoError(t, err) 981 assert.True(t, started, "transceiver with mid 2 should be started") 982 983 closePairNow(t, pcOffer, pcAnswer) 984 } 985 986 func TestPeerConnection_Simulcast_Probe(t *testing.T) { 987 lim := test.TimeOut(time.Second * 30) //nolint 988 defer lim.Stop() 989 990 report := test.CheckRoutines(t) 991 defer report() 992 993 // Assert that failed Simulcast probing doesn't cause 994 // the handleUndeclaredSSRC to be leaked 995 t.Run("Leak", func(t *testing.T) { 996 track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 997 assert.NoError(t, err) 998 999 offerer, answerer, err := newPair() 1000 assert.NoError(t, err) 1001 1002 _, err = offerer.AddTrack(track) 1003 assert.NoError(t, err) 1004 1005 ticker := time.NewTicker(time.Millisecond * 20) 1006 testFinished := make(chan struct{}) 1007 seenFiveStreams, seenFiveStreamsCancel := context.WithCancel(context.Background()) 1008 1009 go func() { 1010 for { 1011 select { 1012 case <-testFinished: 1013 return 1014 case <-ticker.C: 1015 answerer.dtlsTransport.lock.Lock() 1016 if len(answerer.dtlsTransport.simulcastStreams) >= 5 { 1017 seenFiveStreamsCancel() 1018 } 1019 answerer.dtlsTransport.lock.Unlock() 1020 1021 track.mu.Lock() 1022 if len(track.bindings) == 1 { 1023 _, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{ 1024 Version: 2, 1025 SSRC: randutil.NewMathRandomGenerator().Uint32(), 1026 }, []byte{0, 1, 2, 3, 4, 5}) 1027 assert.NoError(t, err) 1028 } 1029 track.mu.Unlock() 1030 } 1031 } 1032 }() 1033 1034 assert.NoError(t, signalPair(offerer, answerer)) 1035 1036 peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, offerer, answerer) 1037 peerConnectionConnected.Wait() 1038 1039 <-seenFiveStreams.Done() 1040 1041 closePairNow(t, offerer, answerer) 1042 close(testFinished) 1043 }) 1044 1045 // Assert that NonSimulcast Traffic isn't incorrectly broken by the probe 1046 t.Run("Break NonSimulcast", func(t *testing.T) { 1047 unhandledSimulcastError := make(chan struct{}) 1048 1049 m := &MediaEngine{} 1050 if err := m.RegisterDefaultCodecs(); err != nil { 1051 panic(err) 1052 } 1053 registerSimulcastHeaderExtensions(m, RTPCodecTypeVideo) 1054 1055 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{ 1056 LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError}, 1057 }), WithMediaEngine(m)).newPair(Configuration{}) 1058 assert.NoError(t, err) 1059 1060 firstTrack, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "firstTrack", "firstTrack") 1061 assert.NoError(t, err) 1062 1063 _, err = pcOffer.AddTrack(firstTrack) 1064 assert.NoError(t, err) 1065 1066 secondTrack, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "secondTrack", "secondTrack") 1067 assert.NoError(t, err) 1068 1069 _, err = pcOffer.AddTrack(secondTrack) 1070 assert.NoError(t, err) 1071 1072 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) (filtered string) { 1073 shouldDiscard := false 1074 1075 scanner := bufio.NewScanner(strings.NewReader(sessionDescription)) 1076 for scanner.Scan() { 1077 if strings.HasPrefix(scanner.Text(), "m=video") { 1078 shouldDiscard = !shouldDiscard 1079 } 1080 1081 if !shouldDiscard { 1082 filtered += scanner.Text() + "\r\n" 1083 } 1084 } 1085 1086 return 1087 })) 1088 1089 sequenceNumber := uint16(0) 1090 sendRTPPacket := func() { 1091 sequenceNumber++ 1092 assert.NoError(t, firstTrack.WriteRTP(&rtp.Packet{ 1093 Header: rtp.Header{ 1094 Version: 2, 1095 SequenceNumber: sequenceNumber, 1096 }, 1097 Payload: []byte{0x00}, 1098 })) 1099 time.Sleep(20 * time.Millisecond) 1100 } 1101 1102 for ; sequenceNumber <= 5; sequenceNumber++ { 1103 sendRTPPacket() 1104 } 1105 1106 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1107 1108 trackRemoteChan := make(chan *TrackRemote, 1) 1109 pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { 1110 trackRemoteChan <- trackRemote 1111 }) 1112 1113 trackRemote := func() *TrackRemote { 1114 for { 1115 select { 1116 case t := <-trackRemoteChan: 1117 return t 1118 default: 1119 sendRTPPacket() 1120 } 1121 } 1122 }() 1123 1124 func() { 1125 for { 1126 select { 1127 case <-unhandledSimulcastError: 1128 return 1129 default: 1130 sendRTPPacket() 1131 } 1132 } 1133 }() 1134 1135 _, _, err = trackRemote.Read(make([]byte, 1500)) 1136 assert.NoError(t, err) 1137 1138 closePairNow(t, pcOffer, pcAnswer) 1139 }) 1140 } 1141 1142 // Assert that CreateOffer returns an error for a RTPSender with no codecs 1143 // pion/webrtc#1702 1144 func TestPeerConnection_CreateOffer_NoCodecs(t *testing.T) { 1145 lim := test.TimeOut(time.Second * 30) 1146 defer lim.Stop() 1147 1148 report := test.CheckRoutines(t) 1149 defer report() 1150 1151 m := &MediaEngine{} 1152 1153 pc, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{}) 1154 assert.NoError(t, err) 1155 1156 track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 1157 assert.NoError(t, err) 1158 1159 _, err = pc.AddTrack(track) 1160 assert.NoError(t, err) 1161 1162 _, err = pc.CreateOffer(nil) 1163 assert.Equal(t, err, ErrSenderWithNoCodecs) 1164 1165 assert.NoError(t, pc.Close()) 1166 } 1167 1168 // Assert that AddTrack is thread-safe 1169 func TestPeerConnection_RaceReplaceTrack(t *testing.T) { 1170 pc, err := NewPeerConnection(Configuration{}) 1171 assert.NoError(t, err) 1172 1173 addTrack := func() *TrackLocalStaticSample { 1174 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 1175 assert.NoError(t, err) 1176 _, err = pc.AddTrack(track) 1177 assert.NoError(t, err) 1178 return track 1179 } 1180 1181 for i := 0; i < 10; i++ { 1182 addTrack() 1183 } 1184 for _, tr := range pc.GetTransceivers() { 1185 assert.NoError(t, pc.RemoveTrack(tr.Sender())) 1186 } 1187 1188 var wg sync.WaitGroup 1189 tracks := make([]*TrackLocalStaticSample, 10) 1190 wg.Add(10) 1191 for i := 0; i < 10; i++ { 1192 go func(j int) { 1193 tracks[j] = addTrack() 1194 wg.Done() 1195 }(i) 1196 } 1197 1198 wg.Wait() 1199 1200 for _, track := range tracks { 1201 have := false 1202 for _, t := range pc.GetTransceivers() { 1203 if t.Sender() != nil && t.Sender().Track() == track { 1204 have = true 1205 break 1206 } 1207 } 1208 if !have { 1209 t.Errorf("track was added but not found on senders") 1210 } 1211 } 1212 1213 assert.NoError(t, pc.Close()) 1214 } 1215 1216 func TestPeerConnection_Simulcast(t *testing.T) { 1217 lim := test.TimeOut(time.Second * 30) 1218 defer lim.Stop() 1219 1220 report := test.CheckRoutines(t) 1221 defer report() 1222 1223 rids := []string{"a", "b", "c"} 1224 var ridMapLock sync.RWMutex 1225 ridMap := map[string]int{} 1226 1227 // Enable Extension Headers needed for Simulcast 1228 m := &MediaEngine{} 1229 if err := m.RegisterDefaultCodecs(); err != nil { 1230 panic(err) 1231 } 1232 registerSimulcastHeaderExtensions(m, RTPCodecTypeVideo) 1233 1234 assertRidCorrect := func(t *testing.T) { 1235 ridMapLock.Lock() 1236 defer ridMapLock.Unlock() 1237 1238 for _, rid := range rids { 1239 assert.Equal(t, ridMap[rid], 1) 1240 } 1241 assert.Equal(t, len(ridMap), 3) 1242 } 1243 1244 ridsFullfilled := func() bool { 1245 ridMapLock.Lock() 1246 defer ridMapLock.Unlock() 1247 1248 ridCount := len(ridMap) 1249 return ridCount == 3 1250 } 1251 1252 onTrackHandler := func(trackRemote *TrackRemote, _ *RTPReceiver) { 1253 ridMapLock.Lock() 1254 defer ridMapLock.Unlock() 1255 ridMap[trackRemote.RID()] = ridMap[trackRemote.RID()] + 1 1256 } 1257 1258 t.Run("RTP Extension Based", func(t *testing.T) { 1259 pcOffer, pcAnswer, err := NewAPI(WithMediaEngine(m)).newPair(Configuration{}) 1260 assert.NoError(t, err) 1261 1262 vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("a")) 1263 assert.NoError(t, err) 1264 1265 sender, err := pcOffer.AddTrack(vp8WriterA) 1266 assert.NoError(t, err) 1267 assert.NotNil(t, sender) 1268 1269 vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("b")) 1270 assert.NoError(t, err) 1271 err = sender.AddEncoding(vp8WriterB) 1272 assert.NoError(t, err) 1273 1274 vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("c")) 1275 assert.NoError(t, err) 1276 err = sender.AddEncoding(vp8WriterC) 1277 assert.NoError(t, err) 1278 1279 ridMap = map[string]int{} 1280 pcAnswer.OnTrack(onTrackHandler) 1281 1282 parameters := sender.GetParameters() 1283 assert.Equal(t, "a", parameters.Encodings[0].RID) 1284 assert.Equal(t, "b", parameters.Encodings[1].RID) 1285 assert.Equal(t, "c", parameters.Encodings[2].RID) 1286 1287 var midID, ridID, rsidID uint8 1288 for _, extension := range parameters.HeaderExtensions { 1289 switch extension.URI { 1290 case sdp.SDESMidURI: 1291 midID = uint8(extension.ID) 1292 case sdp.SDESRTPStreamIDURI: 1293 ridID = uint8(extension.ID) 1294 case sdesRepairRTPStreamIDURI: 1295 rsidID = uint8(extension.ID) 1296 } 1297 } 1298 assert.NotZero(t, midID) 1299 assert.NotZero(t, ridID) 1300 assert.NotZero(t, rsidID) 1301 1302 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1303 1304 for sequenceNumber := uint16(0); !ridsFullfilled(); sequenceNumber++ { 1305 time.Sleep(20 * time.Millisecond) 1306 1307 for ssrc, rid := range rids { 1308 header := &rtp.Header{ 1309 Version: 2, 1310 SSRC: uint32(ssrc), 1311 SequenceNumber: sequenceNumber, 1312 PayloadType: 96, 1313 } 1314 assert.NoError(t, header.SetExtension(midID, []byte("0"))) 1315 1316 // Send RSID for first 10 packets 1317 if sequenceNumber >= 10 { 1318 assert.NoError(t, header.SetExtension(ridID, []byte(rid))) 1319 } else { 1320 assert.NoError(t, header.SetExtension(rsidID, []byte(rid))) 1321 header.SSRC += 10 1322 } 1323 1324 var writer *TrackLocalStaticRTP 1325 switch rid { 1326 case "a": 1327 writer = vp8WriterA 1328 case "b": 1329 writer = vp8WriterB 1330 case "c": 1331 writer = vp8WriterC 1332 } 1333 _, err = writer.bindings[0].writeStream.WriteRTP(header, []byte{0x00}) 1334 assert.NoError(t, err) 1335 } 1336 } 1337 1338 assertRidCorrect(t) 1339 closePairNow(t, pcOffer, pcAnswer) 1340 }) 1341 } 1342 1343 // Everytime we receieve a new SSRC we probe it and try to determine the proper way to handle it. 1344 // In most cases a Track explicitly declares a SSRC and a OnTrack is fired. In two cases we don't 1345 // know the SSRC ahead of time 1346 // * Undeclared SSRC in a single media section (https://github.com/pion/webrtc/issues/880) 1347 // * Simulcast 1348 // 1349 // The Undeclared SSRC processing code would run before Simulcast. If a Simulcast Offer/Answer only 1350 // contained one Media Section we would never fire the OnTrack. We would assume it was a failed 1351 // Undeclared SSRC processing. This test asserts that we properly handled this. 1352 func TestPeerConnection_Simulcast_NoDataChannel(t *testing.T) { 1353 lim := test.TimeOut(time.Second * 30) 1354 defer lim.Stop() 1355 1356 report := test.CheckRoutines(t) 1357 defer report() 1358 1359 // Enable Extension Headers needed for Simulcast 1360 m := &MediaEngine{} 1361 if err := m.RegisterDefaultCodecs(); err != nil { 1362 panic(err) 1363 } 1364 registerSimulcastHeaderExtensions(m, RTPCodecTypeVideo) 1365 1366 pcSender, pcReceiver, err := NewAPI(WithMediaEngine(m)).newPair(Configuration{}) 1367 assert.NoError(t, err) 1368 1369 var wg sync.WaitGroup 1370 wg.Add(4) 1371 1372 var connectionWg sync.WaitGroup 1373 connectionWg.Add(2) 1374 1375 connectionStateChangeHandler := func(state PeerConnectionState) { 1376 if state == PeerConnectionStateConnected { 1377 connectionWg.Done() 1378 } 1379 } 1380 1381 pcSender.OnConnectionStateChange(connectionStateChangeHandler) 1382 pcReceiver.OnConnectionStateChange(connectionStateChangeHandler) 1383 1384 pcReceiver.OnTrack(func(track *TrackRemote, _ *RTPReceiver) { 1385 defer wg.Done() 1386 }) 1387 1388 go func() { 1389 defer wg.Done() 1390 vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("a")) 1391 assert.NoError(t, err) 1392 1393 sender, err := pcSender.AddTrack(vp8WriterA) 1394 assert.NoError(t, err) 1395 assert.NotNil(t, sender) 1396 1397 vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("b")) 1398 assert.NoError(t, err) 1399 err = sender.AddEncoding(vp8WriterB) 1400 assert.NoError(t, err) 1401 1402 vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("c")) 1403 assert.NoError(t, err) 1404 err = sender.AddEncoding(vp8WriterC) 1405 assert.NoError(t, err) 1406 1407 parameters := sender.GetParameters() 1408 var midID, ridID, rsidID uint8 1409 for _, extension := range parameters.HeaderExtensions { 1410 switch extension.URI { 1411 case sdp.SDESMidURI: 1412 midID = uint8(extension.ID) 1413 case sdp.SDESRTPStreamIDURI: 1414 ridID = uint8(extension.ID) 1415 case sdesRepairRTPStreamIDURI: 1416 rsidID = uint8(extension.ID) 1417 } 1418 } 1419 assert.NotZero(t, midID) 1420 assert.NotZero(t, ridID) 1421 assert.NotZero(t, rsidID) 1422 1423 // signaling 1424 offerSDP, err := pcSender.CreateOffer(nil) 1425 assert.NoError(t, err) 1426 err = pcSender.SetLocalDescription(offerSDP) 1427 assert.NoError(t, err) 1428 1429 err = pcReceiver.SetRemoteDescription(offerSDP) 1430 assert.NoError(t, err) 1431 answerSDP, err := pcReceiver.CreateAnswer(nil) 1432 assert.NoError(t, err) 1433 1434 answerGatheringComplete := GatheringCompletePromise(pcReceiver) 1435 err = pcReceiver.SetLocalDescription(answerSDP) 1436 assert.NoError(t, err) 1437 <-answerGatheringComplete 1438 1439 assert.NoError(t, pcSender.SetRemoteDescription(*pcReceiver.LocalDescription())) 1440 1441 connectionWg.Wait() 1442 1443 var seqNo uint16 1444 for i := 0; i < 100; i++ { 1445 pkt := &rtp.Packet{ 1446 Header: rtp.Header{ 1447 Version: 2, 1448 SequenceNumber: seqNo, 1449 PayloadType: 96, 1450 }, 1451 Payload: []byte{0x00, 0x00}, 1452 } 1453 1454 assert.NoError(t, pkt.SetExtension(ridID, []byte("a"))) 1455 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1456 assert.NoError(t, vp8WriterA.WriteRTP(pkt)) 1457 1458 assert.NoError(t, pkt.SetExtension(ridID, []byte("b"))) 1459 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1460 assert.NoError(t, vp8WriterB.WriteRTP(pkt)) 1461 1462 assert.NoError(t, pkt.SetExtension(ridID, []byte("c"))) 1463 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1464 assert.NoError(t, vp8WriterC.WriteRTP(pkt)) 1465 1466 seqNo++ 1467 } 1468 }() 1469 1470 wg.Wait() 1471 1472 closePairNow(t, pcSender, pcReceiver) 1473 }