github.com/pion/webrtc/v4@v4.0.1/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 "math/rand" 17 "regexp" 18 "strings" 19 "sync" 20 "sync/atomic" 21 "testing" 22 "time" 23 24 "github.com/pion/logging" 25 "github.com/pion/rtcp" 26 "github.com/pion/rtp" 27 "github.com/pion/sdp/v3" 28 "github.com/pion/transport/v3/test" 29 "github.com/pion/transport/v3/vnet" 30 "github.com/pion/webrtc/v4/internal/util" 31 "github.com/pion/webrtc/v4/pkg/media" 32 "github.com/stretchr/testify/assert" 33 "github.com/stretchr/testify/require" 34 ) 35 36 var ( 37 errIncomingTrackIDInvalid = errors.New("incoming Track ID is invalid") 38 errIncomingTrackLabelInvalid = errors.New("incoming Track Label is invalid") 39 errNoTransceiverwithMid = errors.New("no transceiver with mid") 40 ) 41 42 /* 43 Integration test for bi-directional peers 44 45 This asserts we can send RTP and RTCP both ways, and blocks until 46 each side gets something (and asserts payload contents) 47 */ 48 // nolint: gocyclo 49 func TestPeerConnection_Media_Sample(t *testing.T) { 50 const ( 51 expectedTrackID = "video" 52 expectedStreamID = "pion" 53 ) 54 55 lim := test.TimeOut(time.Second * 30) 56 defer lim.Stop() 57 58 report := test.CheckRoutines(t) 59 defer report() 60 61 pcOffer, pcAnswer, err := newPair() 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 awaitRTPRecv := make(chan bool) 67 awaitRTPRecvClosed := make(chan bool) 68 awaitRTPSend := make(chan bool) 69 70 awaitRTCPSenderRecv := make(chan bool) 71 awaitRTCPSenderSend := make(chan error) 72 73 awaitRTCPReceiverRecv := make(chan error) 74 awaitRTCPReceiverSend := make(chan error) 75 76 trackMetadataValid := make(chan error) 77 78 pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) { 79 if track.ID() != expectedTrackID { 80 trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackIDInvalid, expectedTrackID, track.ID()) 81 return 82 } 83 84 if track.StreamID() != expectedStreamID { 85 trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedStreamID, track.StreamID()) 86 return 87 } 88 close(trackMetadataValid) 89 90 go func() { 91 for { 92 time.Sleep(time.Millisecond * 100) 93 if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: uint32(track.SSRC()), MediaSSRC: uint32(track.SSRC())}}); routineErr != nil { 94 awaitRTCPReceiverSend <- routineErr 95 return 96 } 97 98 select { 99 case <-awaitRTCPSenderRecv: 100 close(awaitRTCPReceiverSend) 101 return 102 default: 103 } 104 } 105 }() 106 107 go func() { 108 _, _, routineErr := receiver.Read(make([]byte, 1400)) 109 if routineErr != nil { 110 awaitRTCPReceiverRecv <- routineErr 111 } else { 112 close(awaitRTCPReceiverRecv) 113 } 114 }() 115 116 haveClosedAwaitRTPRecv := false 117 for { 118 p, _, routineErr := track.ReadRTP() 119 if routineErr != nil { 120 close(awaitRTPRecvClosed) 121 return 122 } else if bytes.Equal(p.Payload, []byte{0x10, 0x00}) && !haveClosedAwaitRTPRecv { 123 haveClosedAwaitRTPRecv = true 124 close(awaitRTPRecv) 125 } 126 } 127 }) 128 129 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, expectedTrackID, expectedStreamID) 130 if err != nil { 131 t.Fatal(err) 132 } 133 sender, err := pcOffer.AddTrack(vp8Track) 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 go func() { 139 for { 140 time.Sleep(time.Millisecond * 100) 141 if pcOffer.ICEConnectionState() != ICEConnectionStateConnected { 142 continue 143 } 144 if routineErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil { 145 fmt.Println(routineErr) 146 } 147 148 select { 149 case <-awaitRTPRecv: 150 close(awaitRTPSend) 151 return 152 default: 153 } 154 } 155 }() 156 157 go func() { 158 parameters := sender.GetParameters() 159 160 for { 161 time.Sleep(time.Millisecond * 100) 162 if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(parameters.Encodings[0].SSRC), MediaSSRC: uint32(parameters.Encodings[0].SSRC)}}); routineErr != nil { 163 awaitRTCPSenderSend <- routineErr 164 } 165 166 select { 167 case <-awaitRTCPReceiverRecv: 168 close(awaitRTCPSenderSend) 169 return 170 default: 171 } 172 } 173 }() 174 175 go func() { 176 if _, _, routineErr := sender.Read(make([]byte, 1400)); routineErr == nil { 177 close(awaitRTCPSenderRecv) 178 } 179 }() 180 181 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 182 183 err, ok := <-trackMetadataValid 184 if ok { 185 t.Fatal(err) 186 } 187 188 <-awaitRTPRecv 189 <-awaitRTPSend 190 191 <-awaitRTCPSenderRecv 192 if err, ok = <-awaitRTCPSenderSend; ok { 193 t.Fatal(err) 194 } 195 196 <-awaitRTCPReceiverRecv 197 if err, ok = <-awaitRTCPReceiverSend; ok { 198 t.Fatal(err) 199 } 200 201 closePairNow(t, pcOffer, pcAnswer) 202 <-awaitRTPRecvClosed 203 } 204 205 /* 206 PeerConnection should be able to be torn down at anytime 207 This test adds an input track and asserts 208 209 * OnTrack doesn't fire since no video packets will arrive 210 * No goroutine leaks 211 * No deadlocks on shutdown 212 */ 213 func TestPeerConnection_Media_Shutdown(t *testing.T) { 214 iceCompleteAnswer := make(chan struct{}) 215 iceCompleteOffer := make(chan struct{}) 216 217 lim := test.TimeOut(time.Second * 30) 218 defer lim.Stop() 219 220 report := test.CheckRoutines(t) 221 defer report() 222 223 pcOffer, pcAnswer, err := newPair() 224 if err != nil { 225 t.Fatal(err) 226 } 227 228 _, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 229 if err != nil { 230 t.Fatal(err) 231 } 232 233 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeAudio, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 234 if err != nil { 235 t.Fatal(err) 236 } 237 238 opusTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeOpus}, "audio", "pion1") 239 if err != nil { 240 t.Fatal(err) 241 } 242 243 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 if _, err = pcOffer.AddTrack(opusTrack); err != nil { 249 t.Fatal(err) 250 } else if _, err = pcAnswer.AddTrack(vp8Track); err != nil { 251 t.Fatal(err) 252 } 253 254 var onTrackFiredLock sync.Mutex 255 onTrackFired := false 256 257 pcAnswer.OnTrack(func(*TrackRemote, *RTPReceiver) { 258 onTrackFiredLock.Lock() 259 defer onTrackFiredLock.Unlock() 260 onTrackFired = true 261 }) 262 263 pcAnswer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 264 if iceState == ICEConnectionStateConnected { 265 close(iceCompleteAnswer) 266 } 267 }) 268 pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 269 if iceState == ICEConnectionStateConnected { 270 close(iceCompleteOffer) 271 } 272 }) 273 274 err = signalPair(pcOffer, pcAnswer) 275 if err != nil { 276 t.Fatal(err) 277 } 278 <-iceCompleteAnswer 279 <-iceCompleteOffer 280 281 // Each PeerConnection should have one sender, one receiver and one transceiver 282 for _, pc := range []*PeerConnection{pcOffer, pcAnswer} { 283 senders := pc.GetSenders() 284 if len(senders) != 1 { 285 t.Errorf("Each PeerConnection should have one RTPSender, we have %d", len(senders)) 286 } 287 288 receivers := pc.GetReceivers() 289 if len(receivers) != 2 { 290 t.Errorf("Each PeerConnection should have two RTPReceivers, we have %d", len(receivers)) 291 } 292 293 transceivers := pc.GetTransceivers() 294 if len(transceivers) != 2 { 295 t.Errorf("Each PeerConnection should have two RTPTransceivers, we have %d", len(transceivers)) 296 } 297 } 298 299 closePairNow(t, pcOffer, pcAnswer) 300 301 onTrackFiredLock.Lock() 302 if onTrackFired { 303 t.Fatalf("PeerConnection OnTrack fired even though we got no packets") 304 } 305 onTrackFiredLock.Unlock() 306 } 307 308 /* 309 Integration test for behavior around media and disconnected peers 310 311 * Sending RTP and RTCP to a disconnected Peer shouldn't return an error 312 */ 313 func TestPeerConnection_Media_Disconnected(t *testing.T) { 314 lim := test.TimeOut(time.Second * 30) 315 defer lim.Stop() 316 317 report := test.CheckRoutines(t) 318 defer report() 319 320 s := SettingEngine{} 321 s.SetICETimeouts(time.Second/2, time.Second/2, time.Second/8) 322 323 m := &MediaEngine{} 324 assert.NoError(t, m.RegisterDefaultCodecs()) 325 326 pcOffer, pcAnswer, wan := createVNetPair(t, nil) 327 328 keepPackets := &atomicBool{} 329 keepPackets.set(true) 330 331 // Add a filter that monitors the traffic on the router 332 wan.AddChunkFilter(func(vnet.Chunk) bool { 333 return keepPackets.get() 334 }) 335 336 vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 vp8Sender, err := pcOffer.AddTrack(vp8Track) 342 if err != nil { 343 t.Fatal(err) 344 } 345 346 haveDisconnected := make(chan error) 347 pcOffer.OnICEConnectionStateChange(func(iceState ICEConnectionState) { 348 if iceState == ICEConnectionStateDisconnected { 349 close(haveDisconnected) 350 } else if iceState == ICEConnectionStateConnected { 351 // Assert that DTLS is done by pull remote certificate, don't tear down the PC early 352 for { 353 if len(vp8Sender.Transport().GetRemoteCertificate()) != 0 { 354 if pcAnswer.sctpTransport.association() != nil { 355 break 356 } 357 } 358 359 time.Sleep(time.Second) 360 } 361 362 keepPackets.set(false) 363 } 364 }) 365 366 if err = signalPair(pcOffer, pcAnswer); err != nil { 367 t.Fatal(err) 368 } 369 370 err, ok := <-haveDisconnected 371 if ok { 372 t.Fatal(err) 373 } 374 for i := 0; i <= 5; i++ { 375 if rtpErr := vp8Track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); rtpErr != nil { 376 t.Fatal(rtpErr) 377 } else if rtcpErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 0}}); rtcpErr != nil { 378 t.Fatal(rtcpErr) 379 } 380 } 381 382 assert.NoError(t, wan.Stop()) 383 closePairNow(t, pcOffer, pcAnswer) 384 } 385 386 type undeclaredSsrcLogger struct{ unhandledSimulcastError chan struct{} } 387 388 func (u *undeclaredSsrcLogger) Trace(string) {} 389 func (u *undeclaredSsrcLogger) Tracef(string, ...interface{}) {} 390 func (u *undeclaredSsrcLogger) Debug(string) {} 391 func (u *undeclaredSsrcLogger) Debugf(string, ...interface{}) {} 392 func (u *undeclaredSsrcLogger) Info(string) {} 393 func (u *undeclaredSsrcLogger) Infof(string, ...interface{}) {} 394 func (u *undeclaredSsrcLogger) Warn(string) {} 395 func (u *undeclaredSsrcLogger) Warnf(string, ...interface{}) {} 396 func (u *undeclaredSsrcLogger) Error(string) {} 397 func (u *undeclaredSsrcLogger) Errorf(format string, _ ...interface{}) { 398 if format == incomingUnhandledRTPSsrc { 399 close(u.unhandledSimulcastError) 400 } 401 } 402 403 type undeclaredSsrcLoggerFactory struct{ unhandledSimulcastError chan struct{} } 404 405 func (u *undeclaredSsrcLoggerFactory) NewLogger(string) logging.LeveledLogger { 406 return &undeclaredSsrcLogger{u.unhandledSimulcastError} 407 } 408 409 // Filter SSRC lines 410 func filterSsrc(offer string) (filteredSDP string) { 411 scanner := bufio.NewScanner(strings.NewReader(offer)) 412 for scanner.Scan() { 413 l := scanner.Text() 414 if strings.HasPrefix(l, "a=ssrc") { 415 continue 416 } 417 418 filteredSDP += l + "\n" 419 } 420 return 421 } 422 423 // If a SessionDescription has a single media section and no SSRC 424 // assume that it is meant to handle all RTP packets 425 func TestUndeclaredSSRC(t *testing.T) { 426 lim := test.TimeOut(time.Second * 30) 427 defer lim.Stop() 428 429 report := test.CheckRoutines(t) 430 defer report() 431 432 t.Run("No SSRC", func(t *testing.T) { 433 pcOffer, pcAnswer, err := newPair() 434 assert.NoError(t, err) 435 436 vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 437 assert.NoError(t, err) 438 439 _, err = pcOffer.AddTrack(vp8Writer) 440 assert.NoError(t, err) 441 442 onTrackFired := make(chan struct{}) 443 pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { 444 assert.Equal(t, trackRemote.StreamID(), vp8Writer.StreamID()) 445 assert.Equal(t, trackRemote.ID(), vp8Writer.ID()) 446 close(onTrackFired) 447 }) 448 449 offer, err := pcOffer.CreateOffer(nil) 450 assert.NoError(t, err) 451 452 offerGatheringComplete := GatheringCompletePromise(pcOffer) 453 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 454 <-offerGatheringComplete 455 456 offer.SDP = filterSsrc(pcOffer.LocalDescription().SDP) 457 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 458 459 answer, err := pcAnswer.CreateAnswer(nil) 460 assert.NoError(t, err) 461 462 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 463 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 464 <-answerGatheringComplete 465 466 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 467 468 sendVideoUntilDone(onTrackFired, t, []*TrackLocalStaticSample{vp8Writer}) 469 closePairNow(t, pcOffer, pcAnswer) 470 }) 471 472 t.Run("Has RID", func(t *testing.T) { 473 unhandledSimulcastError := make(chan struct{}) 474 475 m := &MediaEngine{} 476 assert.NoError(t, m.RegisterDefaultCodecs()) 477 478 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{ 479 LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError}, 480 }), WithMediaEngine(m)).newPair(Configuration{}) 481 assert.NoError(t, err) 482 483 vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 484 assert.NoError(t, err) 485 486 _, err = pcOffer.AddTrack(vp8Writer) 487 assert.NoError(t, err) 488 489 offer, err := pcOffer.CreateOffer(nil) 490 assert.NoError(t, err) 491 492 offerGatheringComplete := GatheringCompletePromise(pcOffer) 493 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 494 <-offerGatheringComplete 495 496 // Append RID to end of SessionDescription. Will not be considered unhandled anymore 497 offer.SDP = filterSsrc(pcOffer.LocalDescription().SDP) + "a=" + sdpAttributeRid + "\r\n" 498 assert.NoError(t, pcAnswer.SetRemoteDescription(offer)) 499 500 answer, err := pcAnswer.CreateAnswer(nil) 501 assert.NoError(t, err) 502 503 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 504 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 505 <-answerGatheringComplete 506 507 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 508 509 sendVideoUntilDone(unhandledSimulcastError, t, []*TrackLocalStaticSample{vp8Writer}) 510 closePairNow(t, pcOffer, pcAnswer) 511 }) 512 } 513 514 func TestAddTransceiverFromTrackSendOnly(t *testing.T) { 515 lim := test.TimeOut(time.Second * 30) 516 defer lim.Stop() 517 518 report := test.CheckRoutines(t) 519 defer report() 520 521 pc, err := NewPeerConnection(Configuration{}) 522 if err != nil { 523 t.Error(err.Error()) 524 } 525 526 track, err := NewTrackLocalStaticSample( 527 RTPCodecCapability{MimeType: "audio/Opus"}, 528 "track-id", 529 "stream-id", 530 ) 531 if err != nil { 532 t.Error(err.Error()) 533 } 534 535 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 536 Direction: RTPTransceiverDirectionSendonly, 537 }) 538 if err != nil { 539 t.Error(err.Error()) 540 } 541 542 if transceiver.Receiver() != nil { 543 t.Errorf("Transceiver shouldn't have a receiver") 544 } 545 546 if transceiver.Sender() == nil { 547 t.Errorf("Transceiver should have a sender") 548 } 549 550 if len(pc.GetTransceivers()) != 1 { 551 t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers())) 552 } 553 554 if len(pc.GetSenders()) != 1 { 555 t.Errorf("PeerConnection should have one sender but has %d", len(pc.GetSenders())) 556 } 557 558 offer, err := pc.CreateOffer(nil) 559 if err != nil { 560 t.Error(err.Error()) 561 } 562 563 if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly) { 564 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendonly) 565 } 566 567 assert.NoError(t, pc.Close()) 568 } 569 570 func TestAddTransceiverFromTrackSendRecv(t *testing.T) { 571 lim := test.TimeOut(time.Second * 30) 572 defer lim.Stop() 573 574 report := test.CheckRoutines(t) 575 defer report() 576 577 pc, err := NewPeerConnection(Configuration{}) 578 if err != nil { 579 t.Error(err.Error()) 580 } 581 582 track, err := NewTrackLocalStaticSample( 583 RTPCodecCapability{MimeType: "audio/Opus"}, 584 "track-id", 585 "stream-id", 586 ) 587 if err != nil { 588 t.Error(err.Error()) 589 } 590 591 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 592 Direction: RTPTransceiverDirectionSendrecv, 593 }) 594 if err != nil { 595 t.Error(err.Error()) 596 } 597 598 if transceiver.Receiver() == nil { 599 t.Errorf("Transceiver should have a receiver") 600 } 601 602 if transceiver.Sender() == nil { 603 t.Errorf("Transceiver should have a sender") 604 } 605 606 if len(pc.GetTransceivers()) != 1 { 607 t.Errorf("PeerConnection should have one transceiver but has %d", len(pc.GetTransceivers())) 608 } 609 610 offer, err := pc.CreateOffer(nil) 611 if err != nil { 612 t.Error(err.Error()) 613 } 614 615 if !offerMediaHasDirection(offer, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv) { 616 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionSendrecv) 617 } 618 assert.NoError(t, pc.Close()) 619 } 620 621 func TestAddTransceiverAddTrack_Reuse(t *testing.T) { 622 pc, err := NewPeerConnection(Configuration{}) 623 assert.NoError(t, err) 624 625 tr, err := pc.AddTransceiverFromKind( 626 RTPCodecTypeVideo, 627 RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}, 628 ) 629 assert.NoError(t, err) 630 631 assert.Equal(t, []*RTPTransceiver{tr}, pc.GetTransceivers()) 632 633 addTrack := func() (TrackLocal, *RTPSender) { 634 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 635 assert.NoError(t, err) 636 637 sender, err := pc.AddTrack(track) 638 assert.NoError(t, err) 639 640 return track, sender 641 } 642 643 track1, sender1 := addTrack() 644 assert.Equal(t, 1, len(pc.GetTransceivers())) 645 assert.Equal(t, sender1, tr.Sender()) 646 assert.Equal(t, track1, tr.Sender().Track()) 647 require.NoError(t, pc.RemoveTrack(sender1)) 648 649 track2, _ := addTrack() 650 assert.Equal(t, 1, len(pc.GetTransceivers())) 651 assert.Equal(t, track2, tr.Sender().Track()) 652 653 addTrack() 654 assert.Equal(t, 2, len(pc.GetTransceivers())) 655 656 assert.NoError(t, pc.Close()) 657 } 658 659 func TestAddTransceiverAddTrack_NewRTPSender_Error(t *testing.T) { 660 pc, err := NewPeerConnection(Configuration{}) 661 assert.NoError(t, err) 662 663 _, err = pc.AddTransceiverFromKind( 664 RTPCodecTypeVideo, 665 RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}, 666 ) 667 assert.NoError(t, err) 668 669 dtlsTransport := pc.dtlsTransport 670 pc.dtlsTransport = nil 671 672 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 673 assert.NoError(t, err) 674 675 _, err = pc.AddTrack(track) 676 assert.Error(t, err, "DTLSTransport must not be nil") 677 678 assert.Equal(t, 1, len(pc.GetTransceivers())) 679 680 pc.dtlsTransport = dtlsTransport 681 assert.NoError(t, pc.Close()) 682 } 683 684 func TestRtpSenderReceiver_ReadClose_Error(t *testing.T) { 685 pc, err := NewPeerConnection(Configuration{}) 686 assert.NoError(t, err) 687 688 tr, err := pc.AddTransceiverFromKind( 689 RTPCodecTypeVideo, 690 RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv}, 691 ) 692 assert.NoError(t, err) 693 694 sender, receiver := tr.Sender(), tr.Receiver() 695 assert.NoError(t, sender.Stop()) 696 _, _, err = sender.Read(make([]byte, 0, 1400)) 697 assert.ErrorIs(t, err, io.ErrClosedPipe) 698 699 assert.NoError(t, receiver.Stop()) 700 _, _, err = receiver.Read(make([]byte, 0, 1400)) 701 assert.ErrorIs(t, err, io.ErrClosedPipe) 702 703 assert.NoError(t, pc.Close()) 704 } 705 706 // nolint: dupl 707 func TestAddTransceiverFromKind(t *testing.T) { 708 lim := test.TimeOut(time.Second * 30) 709 defer lim.Stop() 710 711 report := test.CheckRoutines(t) 712 defer report() 713 714 pc, err := NewPeerConnection(Configuration{}) 715 if err != nil { 716 t.Error(err.Error()) 717 } 718 719 transceiver, err := pc.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{ 720 Direction: RTPTransceiverDirectionRecvonly, 721 }) 722 if err != nil { 723 t.Error(err.Error()) 724 } 725 726 if transceiver.Receiver() == nil { 727 t.Errorf("Transceiver should have a receiver") 728 } 729 730 if transceiver.Sender() != nil { 731 t.Errorf("Transceiver shouldn't have a sender") 732 } 733 734 offer, err := pc.CreateOffer(nil) 735 if err != nil { 736 t.Error(err.Error()) 737 } 738 739 if !offerMediaHasDirection(offer, RTPCodecTypeVideo, RTPTransceiverDirectionRecvonly) { 740 t.Errorf("Direction on SDP is not %s", RTPTransceiverDirectionRecvonly) 741 } 742 assert.NoError(t, pc.Close()) 743 } 744 745 func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) { 746 lim := test.TimeOut(time.Second * 30) 747 defer lim.Stop() 748 749 report := test.CheckRoutines(t) 750 defer report() 751 752 pc, err := NewPeerConnection(Configuration{}) 753 if err != nil { 754 t.Error(err.Error()) 755 } 756 757 track, err := NewTrackLocalStaticSample( 758 RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"}, 759 "track-id", 760 "track-label", 761 ) 762 if err != nil { 763 t.Error(err.Error()) 764 } 765 766 transceiver, err := pc.AddTransceiverFromTrack(track, RTPTransceiverInit{ 767 Direction: RTPTransceiverDirectionRecvonly, 768 }) 769 770 if transceiver != nil { 771 t.Error("AddTransceiverFromTrack shouldn't succeed with Direction RTPTransceiverDirectionRecvonly") 772 } 773 774 assert.NotNil(t, err) 775 assert.NoError(t, pc.Close()) 776 } 777 778 func TestPlanBMediaExchange(t *testing.T) { 779 runTest := func(trackCount int, t *testing.T) { 780 addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample { 781 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, fmt.Sprintf("video-%d", util.RandUint32()), fmt.Sprintf("video-%d", util.RandUint32())) 782 assert.NoError(t, err) 783 784 _, err = p.AddTrack(track) 785 assert.NoError(t, err) 786 787 return track 788 } 789 790 pcOffer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB}) 791 assert.NoError(t, err) 792 793 pcAnswer, err := NewPeerConnection(Configuration{SDPSemantics: SDPSemanticsPlanB}) 794 assert.NoError(t, err) 795 796 var onTrackWaitGroup sync.WaitGroup 797 onTrackWaitGroup.Add(trackCount) 798 pcAnswer.OnTrack(func(*TrackRemote, *RTPReceiver) { 799 onTrackWaitGroup.Done() 800 }) 801 802 done := make(chan struct{}) 803 go func() { 804 onTrackWaitGroup.Wait() 805 close(done) 806 }() 807 808 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo) 809 assert.NoError(t, err) 810 811 outboundTracks := []*TrackLocalStaticSample{} 812 for i := 0; i < trackCount; i++ { 813 outboundTracks = append(outboundTracks, addSingleTrack(pcOffer)) 814 } 815 816 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 817 818 func() { 819 for { 820 select { 821 case <-time.After(20 * time.Millisecond): 822 for _, track := range outboundTracks { 823 assert.NoError(t, track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second})) 824 } 825 case <-done: 826 return 827 } 828 } 829 }() 830 831 closePairNow(t, pcOffer, pcAnswer) 832 } 833 834 lim := test.TimeOut(time.Second * 30) 835 defer lim.Stop() 836 837 report := test.CheckRoutines(t) 838 defer report() 839 840 t.Run("Single Track", func(t *testing.T) { 841 runTest(1, t) 842 }) 843 t.Run("Multi Track", func(t *testing.T) { 844 runTest(2, t) 845 }) 846 } 847 848 // TestPeerConnection_Start_Only_Negotiated_Senders tests that only 849 // the current negotiated transceivers senders provided in an 850 // offer/answer are started 851 func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) { 852 lim := test.TimeOut(time.Second * 30) 853 defer lim.Stop() 854 855 report := test.CheckRoutines(t) 856 defer report() 857 858 pcOffer, err := NewPeerConnection(Configuration{}) 859 assert.NoError(t, err) 860 defer func() { assert.NoError(t, pcOffer.Close()) }() 861 862 pcAnswer, err := NewPeerConnection(Configuration{}) 863 assert.NoError(t, err) 864 defer func() { assert.NoError(t, pcAnswer.Close()) }() 865 866 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 867 require.NoError(t, err) 868 869 sender1, err := pcOffer.AddTrack(track1) 870 require.NoError(t, err) 871 872 offer, err := pcOffer.CreateOffer(nil) 873 assert.NoError(t, err) 874 875 offerGatheringComplete := GatheringCompletePromise(pcOffer) 876 assert.NoError(t, pcOffer.SetLocalDescription(offer)) 877 <-offerGatheringComplete 878 assert.NoError(t, pcAnswer.SetRemoteDescription(*pcOffer.LocalDescription())) 879 answer, err := pcAnswer.CreateAnswer(nil) 880 assert.NoError(t, err) 881 answerGatheringComplete := GatheringCompletePromise(pcAnswer) 882 assert.NoError(t, pcAnswer.SetLocalDescription(answer)) 883 <-answerGatheringComplete 884 885 // Add a new track between providing the offer and applying the answer 886 887 track2, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2") 888 require.NoError(t, err) 889 890 sender2, err := pcOffer.AddTrack(track2) 891 require.NoError(t, err) 892 893 // apply answer so we'll test generateMatchedSDP 894 assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())) 895 896 // Wait for senders to be started by startTransports spawned goroutine 897 pcOffer.ops.Done() 898 899 // sender1 should be started but sender2 should not be started 900 assert.True(t, sender1.hasSent(), "sender1 is not started but should be started") 901 assert.False(t, sender2.hasSent(), "sender2 is started but should not be started") 902 } 903 904 // TestPeerConnection_Start_Right_Receiver tests that the right 905 // receiver (the receiver which transceiver has the same media section as the track) 906 // is started for the specified track 907 func TestPeerConnection_Start_Right_Receiver(t *testing.T) { 908 isTransceiverReceiverStarted := func(pc *PeerConnection, mid string) (bool, error) { 909 for _, transceiver := range pc.GetTransceivers() { 910 if transceiver.Mid() != mid { 911 continue 912 } 913 return transceiver.Receiver() != nil && transceiver.Receiver().haveReceived(), nil 914 } 915 return false, fmt.Errorf("%w: %q", errNoTransceiverwithMid, mid) 916 } 917 918 lim := test.TimeOut(time.Second * 30) 919 defer lim.Stop() 920 921 report := test.CheckRoutines(t) 922 defer report() 923 924 pcOffer, pcAnswer, err := newPair() 925 require.NoError(t, err) 926 927 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 928 assert.NoError(t, err) 929 930 track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1") 931 require.NoError(t, err) 932 933 sender1, err := pcOffer.AddTrack(track1) 934 require.NoError(t, err) 935 936 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 937 938 pcOffer.ops.Done() 939 pcAnswer.ops.Done() 940 941 // transceiver with mid 0 should be started 942 started, err := isTransceiverReceiverStarted(pcAnswer, "0") 943 assert.NoError(t, err) 944 assert.True(t, started, "transceiver with mid 0 should be started") 945 946 // Remove track 947 assert.NoError(t, pcOffer.RemoveTrack(sender1)) 948 949 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 950 951 pcOffer.ops.Done() 952 pcAnswer.ops.Done() 953 954 // transceiver with mid 0 should not be started 955 started, err = isTransceiverReceiverStarted(pcAnswer, "0") 956 assert.NoError(t, err) 957 assert.False(t, started, "transceiver with mid 0 should not be started") 958 959 // Add a new transceiver (we're not using AddTrack since it'll reuse the transceiver with mid 0) 960 _, err = pcOffer.AddTransceiverFromTrack(track1) 961 assert.NoError(t, err) 962 963 _, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly}) 964 assert.NoError(t, err) 965 966 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 967 968 pcOffer.ops.Done() 969 pcAnswer.ops.Done() 970 971 // transceiver with mid 0 should not be started 972 started, err = isTransceiverReceiverStarted(pcAnswer, "0") 973 assert.NoError(t, err) 974 assert.False(t, started, "transceiver with mid 0 should not be started") 975 // transceiver with mid 2 should be started 976 started, err = isTransceiverReceiverStarted(pcAnswer, "2") 977 assert.NoError(t, err) 978 assert.True(t, started, "transceiver with mid 2 should be started") 979 980 closePairNow(t, pcOffer, pcAnswer) 981 } 982 983 func TestPeerConnection_Simulcast_Probe(t *testing.T) { 984 lim := test.TimeOut(time.Second * 30) //nolint 985 defer lim.Stop() 986 987 report := test.CheckRoutines(t) 988 defer report() 989 990 // Assert that failed Simulcast probing doesn't cause 991 // the handleUndeclaredSSRC to be leaked 992 t.Run("Leak", func(t *testing.T) { 993 track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 994 assert.NoError(t, err) 995 996 offerer, answerer, err := newPair() 997 assert.NoError(t, err) 998 999 _, err = offerer.AddTrack(track) 1000 assert.NoError(t, err) 1001 1002 ticker := time.NewTicker(time.Millisecond * 20) 1003 defer ticker.Stop() 1004 testFinished := make(chan struct{}) 1005 seenFiveStreams, seenFiveStreamsCancel := context.WithCancel(context.Background()) 1006 1007 go func() { 1008 for { 1009 select { 1010 case <-testFinished: 1011 return 1012 case <-ticker.C: 1013 answerer.dtlsTransport.lock.Lock() 1014 if len(answerer.dtlsTransport.simulcastStreams) >= 5 { 1015 seenFiveStreamsCancel() 1016 } 1017 answerer.dtlsTransport.lock.Unlock() 1018 1019 track.mu.Lock() 1020 if len(track.bindings) == 1 { 1021 _, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{ 1022 Version: 2, 1023 SSRC: util.RandUint32(), 1024 }, []byte{0, 1, 2, 3, 4, 5}) 1025 assert.NoError(t, err) 1026 } 1027 track.mu.Unlock() 1028 } 1029 } 1030 }() 1031 1032 assert.NoError(t, signalPair(offerer, answerer)) 1033 1034 peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, offerer, answerer) 1035 peerConnectionConnected.Wait() 1036 1037 <-seenFiveStreams.Done() 1038 1039 closePairNow(t, offerer, answerer) 1040 close(testFinished) 1041 }) 1042 1043 // Assert that NonSimulcast Traffic isn't incorrectly broken by the probe 1044 t.Run("Break NonSimulcast", func(t *testing.T) { 1045 unhandledSimulcastError := make(chan struct{}) 1046 1047 m := &MediaEngine{} 1048 assert.NoError(t, m.RegisterDefaultCodecs()) 1049 assert.NoError(t, ConfigureSimulcastExtensionHeaders(m)) 1050 1051 pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{ 1052 LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError}, 1053 }), WithMediaEngine(m)).newPair(Configuration{}) 1054 assert.NoError(t, err) 1055 1056 firstTrack, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "firstTrack", "firstTrack") 1057 assert.NoError(t, err) 1058 1059 _, err = pcOffer.AddTrack(firstTrack) 1060 assert.NoError(t, err) 1061 1062 secondTrack, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "secondTrack", "secondTrack") 1063 assert.NoError(t, err) 1064 1065 _, err = pcOffer.AddTrack(secondTrack) 1066 assert.NoError(t, err) 1067 1068 assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) (filtered string) { 1069 shouldDiscard := false 1070 1071 scanner := bufio.NewScanner(strings.NewReader(sessionDescription)) 1072 for scanner.Scan() { 1073 if strings.HasPrefix(scanner.Text(), "m=video") { 1074 shouldDiscard = !shouldDiscard 1075 } 1076 1077 if !shouldDiscard { 1078 filtered += scanner.Text() + "\r\n" 1079 } 1080 } 1081 return 1082 })) 1083 1084 peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, pcOffer, pcAnswer) 1085 peerConnectionConnected.Wait() 1086 1087 sequenceNumber := uint16(0) 1088 sendRTPPacket := func() { 1089 sequenceNumber++ 1090 assert.NoError(t, firstTrack.WriteRTP(&rtp.Packet{ 1091 Header: rtp.Header{ 1092 Version: 2, 1093 SequenceNumber: sequenceNumber, 1094 }, 1095 Payload: []byte{0x00}, 1096 })) 1097 time.Sleep(20 * time.Millisecond) 1098 } 1099 1100 for ; sequenceNumber <= 5; sequenceNumber++ { 1101 sendRTPPacket() 1102 } 1103 1104 trackRemoteChan := make(chan *TrackRemote, 1) 1105 pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { 1106 trackRemoteChan <- trackRemote 1107 }) 1108 1109 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1110 1111 trackRemote := func() *TrackRemote { 1112 for { 1113 select { 1114 case t := <-trackRemoteChan: 1115 return t 1116 default: 1117 sendRTPPacket() 1118 } 1119 } 1120 }() 1121 1122 func() { 1123 for { 1124 select { 1125 case <-unhandledSimulcastError: 1126 return 1127 default: 1128 sendRTPPacket() 1129 } 1130 } 1131 }() 1132 1133 _, _, err = trackRemote.Read(make([]byte, 1500)) 1134 assert.NoError(t, err) 1135 1136 closePairNow(t, pcOffer, pcAnswer) 1137 }) 1138 } 1139 1140 // Assert that CreateOffer returns an error for a RTPSender with no codecs 1141 // pion/webrtc#1702 1142 func TestPeerConnection_CreateOffer_NoCodecs(t *testing.T) { 1143 lim := test.TimeOut(time.Second * 30) 1144 defer lim.Stop() 1145 1146 report := test.CheckRoutines(t) 1147 defer report() 1148 1149 m := &MediaEngine{} 1150 1151 pc, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{}) 1152 assert.NoError(t, err) 1153 1154 track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") 1155 assert.NoError(t, err) 1156 1157 _, err = pc.AddTrack(track) 1158 assert.NoError(t, err) 1159 1160 _, err = pc.CreateOffer(nil) 1161 assert.Equal(t, err, ErrSenderWithNoCodecs) 1162 1163 assert.NoError(t, pc.Close()) 1164 } 1165 1166 // Assert that AddTrack is thread-safe 1167 func TestPeerConnection_RaceReplaceTrack(t *testing.T) { 1168 pc, err := NewPeerConnection(Configuration{}) 1169 assert.NoError(t, err) 1170 1171 addTrack := func() *TrackLocalStaticSample { 1172 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar") 1173 assert.NoError(t, err) 1174 _, err = pc.AddTrack(track) 1175 assert.NoError(t, err) 1176 return track 1177 } 1178 1179 for i := 0; i < 10; i++ { 1180 addTrack() 1181 } 1182 for _, tr := range pc.GetTransceivers() { 1183 assert.NoError(t, pc.RemoveTrack(tr.Sender())) 1184 } 1185 1186 var wg sync.WaitGroup 1187 tracks := make([]*TrackLocalStaticSample, 10) 1188 wg.Add(10) 1189 for i := 0; i < 10; i++ { 1190 go func(j int) { 1191 tracks[j] = addTrack() 1192 wg.Done() 1193 }(i) 1194 } 1195 1196 wg.Wait() 1197 1198 for _, track := range tracks { 1199 have := false 1200 for _, t := range pc.GetTransceivers() { 1201 if t.Sender() != nil && t.Sender().Track() == track { 1202 have = true 1203 break 1204 } 1205 } 1206 if !have { 1207 t.Errorf("track was added but not found on senders") 1208 } 1209 } 1210 1211 assert.NoError(t, pc.Close()) 1212 } 1213 1214 func TestPeerConnection_Simulcast(t *testing.T) { 1215 lim := test.TimeOut(time.Second * 30) 1216 defer lim.Stop() 1217 1218 report := test.CheckRoutines(t) 1219 defer report() 1220 1221 rids := []string{"a", "b", "c"} 1222 1223 t.Run("E2E", func(t *testing.T) { 1224 pcOffer, pcAnswer, err := newPair() 1225 assert.NoError(t, err) 1226 1227 vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0])) 1228 assert.NoError(t, err) 1229 1230 vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1])) 1231 assert.NoError(t, err) 1232 1233 vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2])) 1234 assert.NoError(t, err) 1235 1236 sender, err := pcOffer.AddTrack(vp8WriterA) 1237 assert.NoError(t, err) 1238 assert.NotNil(t, sender) 1239 1240 assert.NoError(t, sender.AddEncoding(vp8WriterB)) 1241 assert.NoError(t, sender.AddEncoding(vp8WriterC)) 1242 1243 var ridMapLock sync.RWMutex 1244 ridMap := map[string]int{} 1245 1246 assertRidCorrect := func(t *testing.T) { 1247 ridMapLock.Lock() 1248 defer ridMapLock.Unlock() 1249 1250 for _, rid := range rids { 1251 assert.Equal(t, ridMap[rid], 1) 1252 } 1253 assert.Equal(t, len(ridMap), 3) 1254 } 1255 1256 ridsFullfilled := func() bool { 1257 ridMapLock.Lock() 1258 defer ridMapLock.Unlock() 1259 1260 ridCount := len(ridMap) 1261 return ridCount == 3 1262 } 1263 1264 pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { 1265 ridMapLock.Lock() 1266 defer ridMapLock.Unlock() 1267 ridMap[trackRemote.RID()] = ridMap[trackRemote.RID()] + 1 1268 }) 1269 1270 parameters := sender.GetParameters() 1271 assert.Equal(t, "a", parameters.Encodings[0].RID) 1272 assert.Equal(t, "b", parameters.Encodings[1].RID) 1273 assert.Equal(t, "c", parameters.Encodings[2].RID) 1274 1275 var midID, ridID uint8 1276 for _, extension := range parameters.HeaderExtensions { 1277 switch extension.URI { 1278 case sdp.SDESMidURI: 1279 midID = uint8(extension.ID) 1280 case sdp.SDESRTPStreamIDURI: 1281 ridID = uint8(extension.ID) 1282 } 1283 } 1284 assert.NotZero(t, midID) 1285 assert.NotZero(t, ridID) 1286 1287 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1288 1289 // padding only packets should not affect simulcast probe 1290 var sequenceNumber uint16 1291 for sequenceNumber = 0; sequenceNumber < simulcastProbeCount+10; sequenceNumber++ { 1292 time.Sleep(20 * time.Millisecond) 1293 1294 for _, track := range []*TrackLocalStaticRTP{vp8WriterA, vp8WriterB, vp8WriterC} { 1295 pkt := &rtp.Packet{ 1296 Header: rtp.Header{ 1297 Version: 2, 1298 SequenceNumber: sequenceNumber, 1299 PayloadType: 96, 1300 Padding: true, 1301 }, 1302 Payload: []byte{0x00, 0x02}, 1303 } 1304 1305 assert.NoError(t, track.WriteRTP(pkt)) 1306 } 1307 } 1308 assert.False(t, ridsFullfilled(), "Simulcast probe should not be fulfilled by padding only packets") 1309 1310 for ; !ridsFullfilled(); sequenceNumber++ { 1311 time.Sleep(20 * time.Millisecond) 1312 1313 for _, track := range []*TrackLocalStaticRTP{vp8WriterA, vp8WriterB, vp8WriterC} { 1314 pkt := &rtp.Packet{ 1315 Header: rtp.Header{ 1316 Version: 2, 1317 SequenceNumber: sequenceNumber, 1318 PayloadType: 96, 1319 }, 1320 Payload: []byte{0x00}, 1321 } 1322 assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) 1323 assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) 1324 1325 assert.NoError(t, track.WriteRTP(pkt)) 1326 } 1327 } 1328 1329 assertRidCorrect(t) 1330 closePairNow(t, pcOffer, pcAnswer) 1331 }) 1332 1333 t.Run("RTCP", func(t *testing.T) { 1334 pcOffer, pcAnswer, err := newPair() 1335 assert.NoError(t, err) 1336 1337 vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0])) 1338 assert.NoError(t, err) 1339 1340 vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1])) 1341 assert.NoError(t, err) 1342 1343 vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2])) 1344 assert.NoError(t, err) 1345 1346 sender, err := pcOffer.AddTrack(vp8WriterA) 1347 assert.NoError(t, err) 1348 assert.NotNil(t, sender) 1349 1350 assert.NoError(t, sender.AddEncoding(vp8WriterB)) 1351 assert.NoError(t, sender.AddEncoding(vp8WriterC)) 1352 1353 rtcpCounter := uint64(0) 1354 pcAnswer.OnTrack(func(trackRemote *TrackRemote, receiver *RTPReceiver) { 1355 _, _, simulcastReadErr := receiver.ReadSimulcastRTCP(trackRemote.RID()) 1356 assert.NoError(t, simulcastReadErr) 1357 atomic.AddUint64(&rtcpCounter, 1) 1358 }) 1359 1360 var midID, ridID uint8 1361 for _, extension := range sender.GetParameters().HeaderExtensions { 1362 switch extension.URI { 1363 case sdp.SDESMidURI: 1364 midID = uint8(extension.ID) 1365 case sdp.SDESRTPStreamIDURI: 1366 ridID = uint8(extension.ID) 1367 } 1368 } 1369 assert.NotZero(t, midID) 1370 assert.NotZero(t, ridID) 1371 1372 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1373 1374 for sequenceNumber := uint16(0); atomic.LoadUint64(&rtcpCounter) < 3; sequenceNumber++ { 1375 time.Sleep(20 * time.Millisecond) 1376 1377 for _, track := range []*TrackLocalStaticRTP{vp8WriterA, vp8WriterB, vp8WriterC} { 1378 pkt := &rtp.Packet{ 1379 Header: rtp.Header{ 1380 Version: 2, 1381 SequenceNumber: sequenceNumber, 1382 PayloadType: 96, 1383 }, 1384 Payload: []byte{0x00}, 1385 } 1386 assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) 1387 assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) 1388 1389 assert.NoError(t, track.WriteRTP(pkt)) 1390 } 1391 } 1392 1393 closePairNow(t, pcOffer, pcAnswer) 1394 }) 1395 } 1396 1397 type simulcastTestTrackLocal struct { 1398 *TrackLocalStaticRTP 1399 } 1400 1401 // don't use ssrc&payload in bindings to let the test write different stream packets. 1402 func (s *simulcastTestTrackLocal) WriteRTP(pkt *rtp.Packet) error { 1403 packet := getPacketAllocationFromPool() 1404 1405 defer resetPacketPoolAllocation(packet) 1406 1407 *packet = *pkt 1408 1409 s.mu.RLock() 1410 defer s.mu.RUnlock() 1411 1412 writeErrs := []error{} 1413 1414 for _, b := range s.bindings { 1415 if _, err := b.writeStream.WriteRTP(&packet.Header, packet.Payload); err != nil { 1416 writeErrs = append(writeErrs, err) 1417 } 1418 } 1419 1420 return util.FlattenErrs(writeErrs) 1421 } 1422 1423 func TestPeerConnection_Simulcast_RTX(t *testing.T) { 1424 lim := test.TimeOut(time.Second * 30) 1425 defer lim.Stop() 1426 1427 report := test.CheckRoutines(t) 1428 defer report() 1429 1430 rids := []string{"a", "b"} 1431 pcOffer, pcAnswer, err := newPair() 1432 assert.NoError(t, err) 1433 1434 vp8WriterAStatic, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0])) 1435 assert.NoError(t, err) 1436 1437 vp8WriterBStatic, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1])) 1438 assert.NoError(t, err) 1439 1440 vp8WriterA, vp8WriterB := &simulcastTestTrackLocal{vp8WriterAStatic}, &simulcastTestTrackLocal{vp8WriterBStatic} 1441 1442 sender, err := pcOffer.AddTrack(vp8WriterA) 1443 assert.NoError(t, err) 1444 assert.NotNil(t, sender) 1445 1446 assert.NoError(t, sender.AddEncoding(vp8WriterB)) 1447 1448 var ridMapLock sync.RWMutex 1449 ridMap := map[string]int{} 1450 1451 assertRidCorrect := func(t *testing.T) { 1452 ridMapLock.Lock() 1453 defer ridMapLock.Unlock() 1454 1455 for _, rid := range rids { 1456 assert.Equal(t, ridMap[rid], 1) 1457 } 1458 assert.Equal(t, len(ridMap), 2) 1459 } 1460 1461 ridsFullfilled := func() bool { 1462 ridMapLock.Lock() 1463 defer ridMapLock.Unlock() 1464 1465 ridCount := len(ridMap) 1466 return ridCount == 2 1467 } 1468 1469 var rtxPacketRead atomic.Int32 1470 var wg sync.WaitGroup 1471 wg.Add(2) 1472 1473 pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { 1474 ridMapLock.Lock() 1475 ridMap[trackRemote.RID()] = ridMap[trackRemote.RID()] + 1 1476 ridMapLock.Unlock() 1477 1478 defer wg.Done() 1479 1480 for { 1481 _, attr, rerr := trackRemote.ReadRTP() 1482 if rerr != nil { 1483 break 1484 } 1485 if pt, ok := attr.Get(AttributeRtxPayloadType).(byte); ok { 1486 if pt == 97 { 1487 rtxPacketRead.Add(1) 1488 } 1489 } 1490 } 1491 }) 1492 1493 parameters := sender.GetParameters() 1494 assert.Equal(t, "a", parameters.Encodings[0].RID) 1495 assert.Equal(t, "b", parameters.Encodings[1].RID) 1496 1497 var midID, ridID, rsid uint8 1498 for _, extension := range parameters.HeaderExtensions { 1499 switch extension.URI { 1500 case sdp.SDESMidURI: 1501 midID = uint8(extension.ID) 1502 case sdp.SDESRTPStreamIDURI: 1503 ridID = uint8(extension.ID) 1504 case sdesRepairRTPStreamIDURI: 1505 rsid = uint8(extension.ID) 1506 } 1507 } 1508 assert.NotZero(t, midID) 1509 assert.NotZero(t, ridID) 1510 assert.NotZero(t, rsid) 1511 1512 err = signalPairWithModification(pcOffer, pcAnswer, func(sdp string) string { 1513 // Original chrome sdp contains no ssrc info https://pastebin.com/raw/JTjX6zg6 1514 re := regexp.MustCompile("(?m)[\r\n]+^.*a=ssrc.*$") 1515 res := re.ReplaceAllString(sdp, "") 1516 return res 1517 }) 1518 assert.NoError(t, err) 1519 1520 // padding only packets should not affect simulcast probe 1521 var sequenceNumber uint16 1522 for sequenceNumber = 0; sequenceNumber < simulcastProbeCount+10; sequenceNumber++ { 1523 time.Sleep(20 * time.Millisecond) 1524 1525 for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { 1526 pkt := &rtp.Packet{ 1527 Header: rtp.Header{ 1528 Version: 2, 1529 SequenceNumber: sequenceNumber, 1530 PayloadType: 96, 1531 Padding: true, 1532 SSRC: uint32(i + 1), 1533 }, 1534 Payload: []byte{0x00, 0x02}, 1535 } 1536 1537 assert.NoError(t, track.WriteRTP(pkt)) 1538 } 1539 } 1540 assert.False(t, ridsFullfilled(), "Simulcast probe should not be fulfilled by padding only packets") 1541 1542 for ; !ridsFullfilled(); sequenceNumber++ { 1543 time.Sleep(20 * time.Millisecond) 1544 1545 for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { 1546 pkt := &rtp.Packet{ 1547 Header: rtp.Header{ 1548 Version: 2, 1549 SequenceNumber: sequenceNumber, 1550 PayloadType: 96, 1551 SSRC: uint32(i + 1), 1552 }, 1553 Payload: []byte{0x00}, 1554 } 1555 assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) 1556 assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) 1557 1558 assert.NoError(t, track.WriteRTP(pkt)) 1559 } 1560 } 1561 1562 assertRidCorrect(t) 1563 1564 for i := 0; i < simulcastProbeCount+10; i++ { 1565 sequenceNumber++ 1566 time.Sleep(10 * time.Millisecond) 1567 1568 for j, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { 1569 pkt := &rtp.Packet{ 1570 Header: rtp.Header{ 1571 Version: 2, 1572 SequenceNumber: sequenceNumber, 1573 PayloadType: 97, 1574 SSRC: uint32(100 + j), 1575 }, 1576 Payload: []byte{0x00, 0x00, 0x00, 0x00, 0x00}, 1577 } 1578 assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) 1579 assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) 1580 assert.NoError(t, pkt.Header.SetExtension(rsid, []byte(track.RID()))) 1581 1582 assert.NoError(t, track.WriteRTP(pkt)) 1583 } 1584 } 1585 1586 for ; rtxPacketRead.Load() == 0; sequenceNumber++ { 1587 time.Sleep(20 * time.Millisecond) 1588 1589 for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { 1590 pkt := &rtp.Packet{ 1591 Header: rtp.Header{ 1592 Version: 2, 1593 SequenceNumber: sequenceNumber, 1594 PayloadType: 96, 1595 SSRC: uint32(i + 1), 1596 }, 1597 Payload: []byte{0x00}, 1598 } 1599 assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) 1600 assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) 1601 1602 assert.NoError(t, track.WriteRTP(pkt)) 1603 } 1604 } 1605 1606 closePairNow(t, pcOffer, pcAnswer) 1607 1608 wg.Wait() 1609 1610 assert.Greater(t, rtxPacketRead.Load(), int32(0), "no rtx packet read") 1611 } 1612 1613 // Everytime we receive a new SSRC we probe it and try to determine the proper way to handle it. 1614 // In most cases a Track explicitly declares a SSRC and a OnTrack is fired. In two cases we don't 1615 // know the SSRC ahead of time 1616 // * Undeclared SSRC in a single media section (https://github.com/pion/webrtc/issues/880) 1617 // * Simulcast 1618 // 1619 // The Undeclared SSRC processing code would run before Simulcast. If a Simulcast Offer/Answer only 1620 // contained one Media Section we would never fire the OnTrack. We would assume it was a failed 1621 // Undeclared SSRC processing. This test asserts that we properly handled this. 1622 func TestPeerConnection_Simulcast_NoDataChannel(t *testing.T) { 1623 lim := test.TimeOut(time.Second * 30) 1624 defer lim.Stop() 1625 1626 report := test.CheckRoutines(t) 1627 defer report() 1628 1629 pcSender, pcReceiver, err := newPair() 1630 assert.NoError(t, err) 1631 1632 var wg sync.WaitGroup 1633 wg.Add(4) 1634 1635 var connectionWg sync.WaitGroup 1636 connectionWg.Add(2) 1637 1638 connectionStateChangeHandler := func(state PeerConnectionState) { 1639 if state == PeerConnectionStateConnected { 1640 connectionWg.Done() 1641 } 1642 } 1643 1644 pcSender.OnConnectionStateChange(connectionStateChangeHandler) 1645 pcReceiver.OnConnectionStateChange(connectionStateChangeHandler) 1646 1647 pcReceiver.OnTrack(func(*TrackRemote, *RTPReceiver) { 1648 defer wg.Done() 1649 }) 1650 1651 go func() { 1652 defer wg.Done() 1653 vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("a")) 1654 assert.NoError(t, err) 1655 1656 sender, err := pcSender.AddTrack(vp8WriterA) 1657 assert.NoError(t, err) 1658 assert.NotNil(t, sender) 1659 1660 vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("b")) 1661 assert.NoError(t, err) 1662 err = sender.AddEncoding(vp8WriterB) 1663 assert.NoError(t, err) 1664 1665 vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("c")) 1666 assert.NoError(t, err) 1667 err = sender.AddEncoding(vp8WriterC) 1668 assert.NoError(t, err) 1669 1670 parameters := sender.GetParameters() 1671 var midID, ridID, rsidID uint8 1672 for _, extension := range parameters.HeaderExtensions { 1673 switch extension.URI { 1674 case sdp.SDESMidURI: 1675 midID = uint8(extension.ID) 1676 case sdp.SDESRTPStreamIDURI: 1677 ridID = uint8(extension.ID) 1678 case sdesRepairRTPStreamIDURI: 1679 rsidID = uint8(extension.ID) 1680 } 1681 } 1682 assert.NotZero(t, midID) 1683 assert.NotZero(t, ridID) 1684 assert.NotZero(t, rsidID) 1685 1686 // signaling 1687 offerSDP, err := pcSender.CreateOffer(nil) 1688 assert.NoError(t, err) 1689 err = pcSender.SetLocalDescription(offerSDP) 1690 assert.NoError(t, err) 1691 1692 err = pcReceiver.SetRemoteDescription(offerSDP) 1693 assert.NoError(t, err) 1694 answerSDP, err := pcReceiver.CreateAnswer(nil) 1695 assert.NoError(t, err) 1696 1697 answerGatheringComplete := GatheringCompletePromise(pcReceiver) 1698 err = pcReceiver.SetLocalDescription(answerSDP) 1699 assert.NoError(t, err) 1700 <-answerGatheringComplete 1701 1702 assert.NoError(t, pcSender.SetRemoteDescription(*pcReceiver.LocalDescription())) 1703 1704 connectionWg.Wait() 1705 1706 var seqNo uint16 1707 for i := 0; i < 100; i++ { 1708 pkt := &rtp.Packet{ 1709 Header: rtp.Header{ 1710 Version: 2, 1711 SequenceNumber: seqNo, 1712 PayloadType: 96, 1713 }, 1714 Payload: []byte{0x00, 0x00}, 1715 } 1716 1717 assert.NoError(t, pkt.SetExtension(ridID, []byte("a"))) 1718 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1719 assert.NoError(t, vp8WriterA.WriteRTP(pkt)) 1720 1721 assert.NoError(t, pkt.SetExtension(ridID, []byte("b"))) 1722 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1723 assert.NoError(t, vp8WriterB.WriteRTP(pkt)) 1724 1725 assert.NoError(t, pkt.SetExtension(ridID, []byte("c"))) 1726 assert.NoError(t, pkt.SetExtension(midID, []byte(sender.rtpTransceiver.Mid()))) 1727 assert.NoError(t, vp8WriterC.WriteRTP(pkt)) 1728 1729 seqNo++ 1730 } 1731 }() 1732 1733 wg.Wait() 1734 1735 closePairNow(t, pcSender, pcReceiver) 1736 } 1737 1738 // Check that PayloadType of 0 is handled correctly. At one point 1739 // we incorrectly assumed 0 meant an invalid stream and wouldn't update things 1740 // properly 1741 func TestPeerConnection_Zero_PayloadType(t *testing.T) { 1742 lim := test.TimeOut(time.Second * 5) 1743 defer lim.Stop() 1744 1745 report := test.CheckRoutines(t) 1746 defer report() 1747 1748 pcOffer, pcAnswer, err := newPair() 1749 require.NoError(t, err) 1750 1751 audioTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypePCMU}, "audio", "audio") 1752 require.NoError(t, err) 1753 1754 _, err = pcOffer.AddTrack(audioTrack) 1755 require.NoError(t, err) 1756 1757 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1758 1759 trackFired := make(chan struct{}) 1760 1761 pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) { 1762 require.Equal(t, track.Codec().MimeType, MimeTypePCMU) 1763 close(trackFired) 1764 }) 1765 1766 func() { 1767 ticker := time.NewTicker(20 * time.Millisecond) 1768 defer ticker.Stop() 1769 1770 for { 1771 select { 1772 case <-trackFired: 1773 return 1774 case <-ticker.C: 1775 if routineErr := audioTrack.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil { 1776 fmt.Println(routineErr) 1777 } 1778 } 1779 } 1780 }() 1781 1782 closePairNow(t, pcOffer, pcAnswer) 1783 } 1784 1785 // Assert that NACKs work E2E with no extra configuration. If media is sent over a lossy connection 1786 // the user gets retransmitted RTP packets with no extra configuration 1787 func Test_PeerConnection_RTX_E2E(t *testing.T) { 1788 defer test.TimeOut(time.Second * 30).Stop() 1789 1790 pcOffer, pcAnswer, wan := createVNetPair(t, nil) 1791 1792 wan.AddChunkFilter(func(vnet.Chunk) bool { 1793 return rand.Intn(5) != 4 //nolint: gosec 1794 }) 1795 1796 track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "track-id", "stream-id") 1797 assert.NoError(t, err) 1798 1799 rtpSender, err := pcOffer.AddTrack(track) 1800 assert.NoError(t, err) 1801 1802 go func() { 1803 rtcpBuf := make([]byte, 1500) 1804 for { 1805 if _, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { 1806 return 1807 } 1808 } 1809 }() 1810 1811 rtxSsrc := rtpSender.GetParameters().Encodings[0].RTX.SSRC 1812 ssrc := rtpSender.GetParameters().Encodings[0].SSRC 1813 1814 rtxRead, rtxReadCancel := context.WithCancel(context.Background()) 1815 pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) { 1816 for { 1817 pkt, attributes, readRTPErr := track.ReadRTP() 1818 if errors.Is(readRTPErr, io.EOF) { 1819 return 1820 } else if pkt.PayloadType == 0 { 1821 continue 1822 } 1823 1824 assert.NotNil(t, pkt) 1825 assert.Equal(t, pkt.SSRC, uint32(ssrc)) 1826 assert.Equal(t, pkt.PayloadType, uint8(96)) 1827 1828 rtxPayloadType := attributes.Get(AttributeRtxPayloadType) 1829 rtxSequenceNumber := attributes.Get(AttributeRtxSequenceNumber) 1830 rtxSSRC := attributes.Get(AttributeRtxSsrc) 1831 if rtxPayloadType != nil && rtxSequenceNumber != nil && rtxSSRC != nil { 1832 assert.Equal(t, rtxPayloadType, uint8(97)) 1833 assert.Equal(t, rtxSSRC, uint32(rtxSsrc)) 1834 1835 rtxReadCancel() 1836 } 1837 } 1838 }) 1839 1840 assert.NoError(t, signalPair(pcOffer, pcAnswer)) 1841 1842 func() { 1843 for { 1844 select { 1845 case <-time.After(20 * time.Millisecond): 1846 writeErr := track.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}) 1847 assert.NoError(t, writeErr) 1848 case <-rtxRead.Done(): 1849 return 1850 } 1851 } 1852 }() 1853 1854 assert.NoError(t, wan.Stop()) 1855 closePairNow(t, pcOffer, pcAnswer) 1856 }