@@ -13,6 +13,7 @@ import (
13
13
"errors"
14
14
"fmt"
15
15
"io"
16
+ "regexp"
16
17
"strings"
17
18
"sync"
18
19
"sync/atomic"
@@ -26,6 +27,7 @@ import (
26
27
"github.com/pion/sdp/v3"
27
28
"github.com/pion/transport/v3/test"
28
29
"github.com/pion/transport/v3/vnet"
30
+ "github.com/pion/webrtc/v4/internal/util"
29
31
"github.com/pion/webrtc/v4/pkg/media"
30
32
"github.com/stretchr/testify/assert"
31
33
"github.com/stretchr/testify/require"
@@ -1388,6 +1390,222 @@ func TestPeerConnection_Simulcast(t *testing.T) {
1388
1390
})
1389
1391
}
1390
1392
1393
+ type simulcastTestTrackLocal struct {
1394
+ * TrackLocalStaticRTP
1395
+ }
1396
+
1397
+ // don't use ssrc&payload in bindings to let the test write different stream packets.
1398
+ func (s * simulcastTestTrackLocal ) WriteRTP (pkt * rtp.Packet ) error {
1399
+ packet := getPacketAllocationFromPool ()
1400
+
1401
+ defer resetPacketPoolAllocation (packet )
1402
+
1403
+ * packet = * pkt
1404
+
1405
+ s .mu .RLock ()
1406
+ defer s .mu .RUnlock ()
1407
+
1408
+ writeErrs := []error {}
1409
+
1410
+ for _ , b := range s .bindings {
1411
+ if _ , err := b .writeStream .WriteRTP (& packet .Header , packet .Payload ); err != nil {
1412
+ writeErrs = append (writeErrs , err )
1413
+ }
1414
+ }
1415
+
1416
+ return util .FlattenErrs (writeErrs )
1417
+ }
1418
+
1419
+ func TestPeerConnection_Simulcast_RTX (t * testing.T ) {
1420
+ lim := test .TimeOut (time .Second * 30 )
1421
+ defer lim .Stop ()
1422
+
1423
+ report := test .CheckRoutines (t )
1424
+ defer report ()
1425
+
1426
+ rids := []string {"a" , "b" }
1427
+ pcOffer , pcAnswer , err := newPair ()
1428
+ assert .NoError (t , err )
1429
+
1430
+ vp8WriterAStatic , err := NewTrackLocalStaticRTP (RTPCodecCapability {MimeType : MimeTypeVP8 }, "video" , "pion2" , WithRTPStreamID (rids [0 ]))
1431
+ assert .NoError (t , err )
1432
+
1433
+ vp8WriterBStatic , err := NewTrackLocalStaticRTP (RTPCodecCapability {MimeType : MimeTypeVP8 }, "video" , "pion2" , WithRTPStreamID (rids [1 ]))
1434
+ assert .NoError (t , err )
1435
+
1436
+ vp8WriterA , vp8WriterB := & simulcastTestTrackLocal {vp8WriterAStatic }, & simulcastTestTrackLocal {vp8WriterBStatic }
1437
+
1438
+ sender , err := pcOffer .AddTrack (vp8WriterA )
1439
+ assert .NoError (t , err )
1440
+ assert .NotNil (t , sender )
1441
+
1442
+ assert .NoError (t , sender .AddEncoding (vp8WriterB ))
1443
+
1444
+ var ridMapLock sync.RWMutex
1445
+ ridMap := map [string ]int {}
1446
+
1447
+ assertRidCorrect := func (t * testing.T ) {
1448
+ ridMapLock .Lock ()
1449
+ defer ridMapLock .Unlock ()
1450
+
1451
+ for _ , rid := range rids {
1452
+ assert .Equal (t , ridMap [rid ], 1 )
1453
+ }
1454
+ assert .Equal (t , len (ridMap ), 2 )
1455
+ }
1456
+
1457
+ ridsFullfilled := func () bool {
1458
+ ridMapLock .Lock ()
1459
+ defer ridMapLock .Unlock ()
1460
+
1461
+ ridCount := len (ridMap )
1462
+ return ridCount == 2
1463
+ }
1464
+
1465
+ var rtxPacketRead atomic.Int32
1466
+ var wg sync.WaitGroup
1467
+ wg .Add (2 )
1468
+
1469
+ pcAnswer .OnTrack (func (trackRemote * TrackRemote , _ * RTPReceiver ) {
1470
+ ridMapLock .Lock ()
1471
+ ridMap [trackRemote .RID ()] = ridMap [trackRemote .RID ()] + 1
1472
+ ridMapLock .Unlock ()
1473
+
1474
+ defer wg .Done ()
1475
+
1476
+ for {
1477
+ _ , attr , rerr := trackRemote .ReadRTP ()
1478
+ if rerr != nil {
1479
+ break
1480
+ }
1481
+ if pt , ok := attr .Get (AttributeRtxPayloadType ).(byte ); ok {
1482
+ if pt == 97 {
1483
+ rtxPacketRead .Add (1 )
1484
+ }
1485
+ }
1486
+ }
1487
+ })
1488
+
1489
+ parameters := sender .GetParameters ()
1490
+ assert .Equal (t , "a" , parameters .Encodings [0 ].RID )
1491
+ assert .Equal (t , "b" , parameters .Encodings [1 ].RID )
1492
+
1493
+ var midID , ridID , rsid uint8
1494
+ for _ , extension := range parameters .HeaderExtensions {
1495
+ switch extension .URI {
1496
+ case sdp .SDESMidURI :
1497
+ midID = uint8 (extension .ID )
1498
+ case sdp .SDESRTPStreamIDURI :
1499
+ ridID = uint8 (extension .ID )
1500
+ case sdesRepairRTPStreamIDURI :
1501
+ rsid = uint8 (extension .ID )
1502
+ }
1503
+ }
1504
+ assert .NotZero (t , midID )
1505
+ assert .NotZero (t , ridID )
1506
+ assert .NotZero (t , rsid )
1507
+
1508
+ err = signalPairWithModification (pcOffer , pcAnswer , func (sdp string ) string {
1509
+ // Original chrome sdp contains no ssrc info https://pastebin.com/raw/JTjX6zg6
1510
+ re := regexp .MustCompile ("(?m)[\r \n ]+^.*a=ssrc.*$" )
1511
+ res := re .ReplaceAllString (sdp , "" )
1512
+ return res
1513
+ })
1514
+ assert .NoError (t , err )
1515
+
1516
+ // padding only packets should not affect simulcast probe
1517
+ var sequenceNumber uint16
1518
+ for sequenceNumber = 0 ; sequenceNumber < simulcastProbeCount + 10 ; sequenceNumber ++ {
1519
+ time .Sleep (20 * time .Millisecond )
1520
+
1521
+ for i , track := range []* simulcastTestTrackLocal {vp8WriterA , vp8WriterB } {
1522
+ pkt := & rtp.Packet {
1523
+ Header : rtp.Header {
1524
+ Version : 2 ,
1525
+ SequenceNumber : sequenceNumber ,
1526
+ PayloadType : 96 ,
1527
+ Padding : true ,
1528
+ SSRC : uint32 (i ),
1529
+ },
1530
+ Payload : []byte {0x00 , 0x02 },
1531
+ }
1532
+
1533
+ assert .NoError (t , track .WriteRTP (pkt ))
1534
+ }
1535
+ }
1536
+ assert .False (t , ridsFullfilled (), "Simulcast probe should not be fulfilled by padding only packets" )
1537
+
1538
+ for ; ! ridsFullfilled (); sequenceNumber ++ {
1539
+ time .Sleep (20 * time .Millisecond )
1540
+
1541
+ for i , track := range []* simulcastTestTrackLocal {vp8WriterA , vp8WriterB } {
1542
+ pkt := & rtp.Packet {
1543
+ Header : rtp.Header {
1544
+ Version : 2 ,
1545
+ SequenceNumber : sequenceNumber ,
1546
+ PayloadType : 96 ,
1547
+ SSRC : uint32 (i ),
1548
+ },
1549
+ Payload : []byte {0x00 },
1550
+ }
1551
+ assert .NoError (t , pkt .Header .SetExtension (midID , []byte ("0" )))
1552
+ assert .NoError (t , pkt .Header .SetExtension (ridID , []byte (track .RID ())))
1553
+
1554
+ assert .NoError (t , track .WriteRTP (pkt ))
1555
+ }
1556
+ }
1557
+
1558
+ assertRidCorrect (t )
1559
+
1560
+ for i := 0 ; i < simulcastProbeCount + 10 ; i ++ {
1561
+ sequenceNumber ++
1562
+ time .Sleep (10 * time .Millisecond )
1563
+
1564
+ for j , track := range []* simulcastTestTrackLocal {vp8WriterA , vp8WriterB } {
1565
+ pkt := & rtp.Packet {
1566
+ Header : rtp.Header {
1567
+ Version : 2 ,
1568
+ SequenceNumber : sequenceNumber ,
1569
+ PayloadType : 97 ,
1570
+ SSRC : uint32 (100 + j ),
1571
+ },
1572
+ Payload : []byte {0x00 , 0x00 , 0x00 , 0x00 , 0x00 },
1573
+ }
1574
+ assert .NoError (t , pkt .Header .SetExtension (midID , []byte ("0" )))
1575
+ assert .NoError (t , pkt .Header .SetExtension (ridID , []byte (track .RID ())))
1576
+ assert .NoError (t , pkt .Header .SetExtension (rsid , []byte (track .RID ())))
1577
+
1578
+ assert .NoError (t , track .WriteRTP (pkt ))
1579
+ }
1580
+ }
1581
+
1582
+ for ; rtxPacketRead .Load () == 0 ; sequenceNumber ++ {
1583
+ time .Sleep (20 * time .Millisecond )
1584
+
1585
+ for i , track := range []* simulcastTestTrackLocal {vp8WriterA , vp8WriterB } {
1586
+ pkt := & rtp.Packet {
1587
+ Header : rtp.Header {
1588
+ Version : 2 ,
1589
+ SequenceNumber : sequenceNumber ,
1590
+ PayloadType : 96 ,
1591
+ SSRC : uint32 (i ),
1592
+ },
1593
+ Payload : []byte {0x00 },
1594
+ }
1595
+ assert .NoError (t , pkt .Header .SetExtension (midID , []byte ("0" )))
1596
+ assert .NoError (t , pkt .Header .SetExtension (ridID , []byte (track .RID ())))
1597
+
1598
+ assert .NoError (t , track .WriteRTP (pkt ))
1599
+ }
1600
+ }
1601
+
1602
+ closePairNow (t , pcOffer , pcAnswer )
1603
+
1604
+ wg .Wait ()
1605
+
1606
+ assert .Greater (t , rtxPacketRead .Load (), int32 (0 ), "no rtx packet read" )
1607
+ }
1608
+
1391
1609
// Everytime we receieve a new SSRC we probe it and try to determine the proper way to handle it.
1392
1610
// In most cases a Track explicitly declares a SSRC and a OnTrack is fired. In two cases we don't
1393
1611
// know the SSRC ahead of time
0 commit comments