mediasoup 3.24.2 → 3.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/node/lib/Consumer.js +1 -1
  3. package/node/lib/Transport.d.ts.map +1 -1
  4. package/node/lib/Transport.js +7 -0
  5. package/node/lib/ortc.d.ts.map +1 -1
  6. package/node/lib/ortc.js +10 -0
  7. package/node/lib/test/test-PipeTransport.js +35 -30
  8. package/package.json +6 -6
  9. package/worker/include/DepLibUV.hpp +33 -4
  10. package/worker/include/RTC/Consumer.hpp +4 -1
  11. package/worker/include/RTC/Producer.hpp +34 -5
  12. package/worker/include/RTC/RTCP/Packet.hpp +7 -1
  13. package/worker/include/RTC/RTP/Packet.hpp +18 -0
  14. package/worker/include/RTC/RTP/RtpStream.hpp +76 -14
  15. package/worker/include/RTC/RTP/RtpStreamRecv.hpp +191 -7
  16. package/worker/include/RTC/RTP/RtpStreamSend.hpp +56 -10
  17. package/worker/include/RTC/RTP/RtxStream.hpp +34 -2
  18. package/worker/include/RTC/RemoteCaptureTimeEstimator.hpp +92 -0
  19. package/worker/include/RTC/RemoteClockOffsetEstimator.hpp +87 -0
  20. package/worker/include/RTC/Router.hpp +2 -5
  21. package/worker/include/RTC/SimulcastProducerStreamManager.hpp +3 -0
  22. package/worker/include/RTC/Transport.hpp +15 -7
  23. package/worker/include/Shared.hpp +5 -0
  24. package/worker/include/SharedInterface.hpp +6 -0
  25. package/worker/include/Utils.hpp +44 -3
  26. package/worker/meson.build +4 -0
  27. package/worker/mocks/include/MockShared.hpp +7 -0
  28. package/worker/src/DepLibUV.cpp +17 -0
  29. package/worker/src/RTC/Consumer.cpp +8 -5
  30. package/worker/src/RTC/Producer.cpp +85 -16
  31. package/worker/src/RTC/RTP/Packet.cpp +9 -1
  32. package/worker/src/RTC/RTP/RtpStream.cpp +14 -10
  33. package/worker/src/RTC/RTP/RtpStreamRecv.cpp +150 -23
  34. package/worker/src/RTC/RTP/RtpStreamSend.cpp +34 -19
  35. package/worker/src/RTC/RTP/RtxStream.cpp +12 -6
  36. package/worker/src/RTC/RemoteCaptureTimeEstimator.cpp +77 -0
  37. package/worker/src/RTC/RemoteClockOffsetEstimator.cpp +106 -0
  38. package/worker/src/RTC/Router.cpp +10 -8
  39. package/worker/src/RTC/RtpDictionaries/RtcpParameters.cpp +13 -3
  40. package/worker/src/RTC/SCTP/packet/chunks/SackChunk.cpp +6 -2
  41. package/worker/src/RTC/SimulcastProducerStreamManager.cpp +115 -56
  42. package/worker/src/RTC/SvcProducerStreamManager.cpp +22 -17
  43. package/worker/src/RTC/Transport.cpp +96 -4
  44. package/worker/test/src/RTC/RTP/TestPacket.cpp +6 -0
  45. package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +2 -2
  46. package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +119 -0
  47. package/worker/test/src/RTC/TestConsumer.cpp +4 -3
  48. package/worker/test/src/RTC/TestPortManager.cpp +1 -1
  49. package/worker/test/src/RTC/TestRemoteCaptureTimeEstimator.cpp +164 -0
  50. package/worker/test/src/RTC/TestRemoteClockOffsetEstimator.cpp +241 -0
  51. package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +2 -2
  52. package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +340 -15
  53. package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +2 -2
  54. package/worker/test/src/RTC/TestTransportTuple.cpp +1 -1
  55. package/worker/test/src/Utils/TestTime.cpp +49 -0
@@ -70,9 +70,9 @@ namespace
70
70
  {
71
71
  }
72
72
 
73
- void OnRtpStreamNeedWorstRemoteFractionLost(
74
- RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
73
+ uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
75
74
  {
75
+ return 0;
76
76
  }
77
77
  };
78
78
 
@@ -598,7 +598,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
598
598
 
599
599
  manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
600
600
 
601
- // Give score > 0 so RecalculateTargetLayers keeps target at layer 0.
601
+ // Give score > 0 so RecalculateTargetLayers() keeps target at layer 0.
602
602
  manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
603
603
 
604
604
  // Set target and sync on layer 0.
@@ -639,7 +639,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
639
639
  manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
640
640
  manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
641
641
 
642
- // Both streams have score > 0 so RecalculateTargetLayers can pick layers.
642
+ // Both streams have score > 0 so RecalculateTargetLayers() can pick layers.
643
643
  manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
644
644
  manager->ProducerRtpStreamScore(rtpStream1.get(), /*score*/ 7, /*previousScore*/ 0);
645
645
 
@@ -661,7 +661,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
661
661
 
662
662
  auto keyFrameCountBefore = listener.keyFrameRequestCount;
663
663
 
664
- // Reconnect: RecalculateTargetLayers picks preferred layer 1,
664
+ // Reconnect: RecalculateTargetLayers() picks preferred layer 1,
665
665
  // which differs from current (-1), so a keyframe is requested.
666
666
  manager->OnTransportConnected();
667
667
 
@@ -681,7 +681,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
681
681
  REQUIRE(listener.keyFrameRequestCount == 0);
682
682
  }
683
683
 
684
- SECTION("RecalculateTargetLayers() skips spatial layer without Sender Report")
684
+ SECTION("RecalculateTargetLayers() skips spatial layer without capture instant")
685
685
  {
686
686
  MockListener listener;
687
687
  auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
@@ -708,9 +708,9 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
708
708
  REQUIRE(manager->GetCurrentSpatialLayer() == 0);
709
709
  REQUIRE(manager->GetTargetLayers().temporal == 0);
710
710
 
711
- // Give both streams score > 0. RecalculateTargetLayers will be called
712
- // but CanSwitchToSpatialLayer(1) returns false because layer 1 has no
713
- // Sender Report and tsReferenceSpatialLayer is 0 (not -1).
711
+ // Give both streams score > 0. RecalculateTargetLayers() will be called
712
+ // but CanSwitchToSpatialLayer(1) returns false because the capture instant of
713
+ // layer 1 cannot be told and tsReferenceSpatialLayer is 0 (not -1).
714
714
  manager->ProducerRtpStreamScore(rtpStream0.get(), /*score*/ 7, /*previousScore*/ 0);
715
715
 
716
716
  REQUIRE(manager->GetTargetLayers().spatial == 0);
@@ -733,7 +733,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
733
733
  REQUIRE(result.tsOffset == 0u);
734
734
  }
735
735
 
736
- SECTION("RecalculateTargetLayers() switches to preferred layer after Sender Report")
736
+ SECTION("RecalculateTargetLayers() switches to preferred layer once its capture instant is known")
737
737
  {
738
738
  MockListener listener;
739
739
  auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
@@ -757,9 +757,14 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
757
757
  manager->ProcessRtpPacket(
758
758
  packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
759
759
 
760
- // Target stays at 0 because layer 1 has no Sender Report.
760
+ // Target stays at 0 because the capture instant of layer 1 cannot be told.
761
761
  REQUIRE(manager->GetTargetLayers().spatial == 0);
762
762
 
763
+ // Both layers hold RTP timestamp 1000 and both were captured at the same instant,
764
+ // which is what makes them alignable to each other.
765
+ rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
766
+ rtpStream1->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
767
+
763
768
  // Feed packets to both streams so ReceiveRtcpSenderReport's UpdateScore
764
769
  // doesn't drop the score to 0.
765
770
  packet->SetSsrc(MappedSsrc0);
@@ -768,7 +773,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
768
773
  packet->SetSsrc(MappedSsrc1);
769
774
  feedRtpStreamRecv(rtpStream1.get(), packet.get(), 10);
770
775
 
771
- // Provide Sender Reports for both streams.
776
+ // Provide Sender Reports for both streams so that they get a score.
772
777
  RTC::RTCP::SenderReport sr0;
773
778
  sr0.SetSsrc(MappedSsrc0);
774
779
  sr0.SetNtpSec(1000);
@@ -784,7 +789,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
784
789
  rtpStream1->ReceiveRtcpSenderReport(&sr1);
785
790
 
786
791
  // Notify the manager about the first Sender Report for layer 1.
787
- // This triggers MayChangeLayers → RecalculateTargetLayers.
792
+ // This triggers MayChangeLayers() -> RecalculateTargetLayers().
788
793
  // Now CanSwitchToSpatialLayer(1) returns true, so preferred layer 1 is picked.
789
794
  manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ true);
790
795
 
@@ -804,9 +809,329 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
804
809
  REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
805
810
  REQUIRE(result.isSyncPacket == true);
806
811
  REQUIRE(result.spatialLayerSwitched == true);
807
- // Both SRs have the same NTP/RTP values, so tsOffset is 0.
812
+ // Both streams have the same capture instant and RTP timestamp, so tsOffset is 0.
813
+ REQUIRE(result.tsOffset == 0u);
814
+ REQUIRE(manager->GetCurrentSpatialLayer() == 1);
815
+ }
816
+
817
+ SECTION("ProcessRtpPacket() calculates tsOffset from the capture instant of both layers")
818
+ {
819
+ MockListener listener;
820
+ auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
821
+ auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
822
+ auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
823
+
824
+ manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
825
+ manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
826
+
827
+ // Set target layer to 0. This sets tsReferenceSpatialLayer = 0.
828
+ manager->UpdateTargetLayers(0, 0);
829
+
830
+ // Sync on layer 0 with a keyframe so current = 0.
831
+ packet->SetSsrc(MappedSsrc0);
832
+ packet->SetSequenceNumber(1);
833
+
834
+ auto* handler = new MockPayloadDescriptorHandler();
835
+ handler->isKeyFrame = true;
836
+ packet->SetPayloadDescriptorHandler(handler);
837
+
838
+ manager->ProcessRtpPacket(
839
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
840
+
841
+ // Both layers hold RTP timestamp 1000, but the one of layer 1 was captured 500 ms
842
+ // before the one of layer 0, so its RTP timeline runs 45000 ticks ahead.
843
+ rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
844
+ rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
845
+
846
+ packet->SetSsrc(MappedSsrc0);
847
+ feedRtpStreamRecv(rtpStream0.get(), packet.get(), 10);
848
+
849
+ packet->SetSsrc(MappedSsrc1);
850
+ feedRtpStreamRecv(rtpStream1.get(), packet.get(), 10);
851
+
852
+ // Provide Sender Reports for both streams so that they get a score.
853
+ RTC::RTCP::SenderReport sr0;
854
+ sr0.SetSsrc(MappedSsrc0);
855
+ sr0.SetNtpSec(1000);
856
+ sr0.SetNtpFrac(0);
857
+ sr0.SetRtpTs(90000);
858
+ rtpStream0->ReceiveRtcpSenderReport(&sr0);
859
+
860
+ RTC::RTCP::SenderReport sr1;
861
+ sr1.SetSsrc(MappedSsrc1);
862
+ sr1.SetNtpSec(1000);
863
+ sr1.SetNtpFrac(0);
864
+ sr1.SetRtpTs(90000);
865
+ rtpStream1->ReceiveRtcpSenderReport(&sr1);
866
+
867
+ manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ true);
868
+
869
+ REQUIRE(manager->GetTargetLayers().spatial == 1);
870
+
871
+ // Send a keyframe from layer 1 to complete the spatial switch.
872
+ packet->SetSsrc(MappedSsrc1);
873
+ packet->SetSequenceNumber(100);
874
+ packet->SetTimestamp(91000);
875
+
876
+ auto* handler1 = new MockPayloadDescriptorHandler();
877
+ handler1->isKeyFrame = true;
878
+ packet->SetPayloadDescriptorHandler(handler1);
879
+
880
+ auto result = manager->ProcessRtpPacket(
881
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
882
+
883
+ REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
884
+ REQUIRE(result.spatialLayerSwitched == true);
885
+ REQUIRE(result.tsOffset == 45000u);
886
+ REQUIRE(manager->GetCurrentSpatialLayer() == 1);
887
+ }
888
+
889
+ SECTION(
890
+ "ProcessRtpPacket() discards the packet while its spatial layer cannot tell its capture instant")
891
+ {
892
+ MockListener listener;
893
+ auto manager = createManager(
894
+ &listener,
895
+ /*ssrcs*/ TwoSsrcs,
896
+ /*preferredLayers*/ { 1, 0 },
897
+ /*keyFrameSupported*/ false);
898
+ auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
899
+ auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
900
+
901
+ manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
902
+ manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
903
+
904
+ // Only layer 0 can tell its capture instant, so it remains the TS reference one and
905
+ // layer 1 cannot be aligned to it yet.
906
+ rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
907
+
908
+ // Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0.
909
+ manager->UpdateTargetLayers(0, 0);
910
+
911
+ packet->SetSsrc(MappedSsrc0);
912
+ packet->SetSequenceNumber(1);
913
+ manager->ProcessRtpPacket(
914
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
915
+
916
+ REQUIRE(manager->GetCurrentSpatialLayer() == 0);
917
+
918
+ // Aim at layer 1, whose RTP timeline cannot be placed on the one of layer 0 yet.
919
+ manager->UpdateTargetLayers(1, 0);
920
+
921
+ const auto keyFrameRequestCountBefore = listener.keyFrameRequestCount;
922
+
923
+ packet->SetSsrc(MappedSsrc1);
924
+ packet->SetSequenceNumber(1);
925
+
926
+ auto result = manager->ProcessRtpPacket(
927
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
928
+
929
+ REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::SILENT_DROP);
930
+ REQUIRE(manager->GetCurrentSpatialLayer() == 0);
931
+ // The discarded packet is not a key frame, so no key frame is requested for it.
932
+ REQUIRE(listener.keyFrameRequestCount == keyFrameRequestCountBefore);
933
+
934
+ // Once layer 1 can tell its capture instant the switch completes with the proper
935
+ // offset.
936
+ rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
937
+
938
+ packet->SetSequenceNumber(2);
939
+
940
+ result = manager->ProcessRtpPacket(
941
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
942
+
943
+ REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
944
+ REQUIRE(result.spatialLayerSwitched == true);
945
+ REQUIRE(result.tsOffset == 45000u);
946
+ REQUIRE(manager->GetCurrentSpatialLayer() == 1);
947
+ }
948
+
949
+ SECTION(
950
+ "ProcessRtpPacket() takes the spatial layer as TS reference when the TS reference one "
951
+ "cannot tell its capture instant")
952
+ {
953
+ MockListener listener;
954
+ auto manager = createManager(
955
+ &listener,
956
+ /*ssrcs*/ TwoSsrcs,
957
+ /*preferredLayers*/ { 1, 0 },
958
+ /*keyFrameSupported*/ false);
959
+ auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
960
+ auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
961
+
962
+ manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
963
+ manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
964
+
965
+ // No stream can tell its capture instant in this SECTION.
966
+
967
+ // Feed packets and a Sender Report to both streams so that they get a score and
968
+ // RecalculateTargetLayers() takes them into account.
969
+ for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get() })
970
+ {
971
+ packet->SetSsrc(rtpStream->GetSsrc());
972
+ feedRtpStreamRecv(rtpStream, packet.get(), 10);
973
+
974
+ RTC::RTCP::SenderReport sr;
975
+ sr.SetSsrc(rtpStream->GetSsrc());
976
+ sr.SetNtpSec(1000);
977
+ sr.SetNtpFrac(0);
978
+ sr.SetRtpTs(90000);
979
+ rtpStream->ReceiveRtcpSenderReport(&sr);
980
+ }
981
+
982
+ // Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0.
983
+ manager->UpdateTargetLayers(0, 0);
984
+
985
+ packet->SetSsrc(MappedSsrc0);
986
+ packet->SetSequenceNumber(1);
987
+ manager->ProcessRtpPacket(
988
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
989
+
990
+ REQUIRE(manager->GetCurrentSpatialLayer() == 0);
991
+
992
+ // Aim at layer 1. Since layer 0 cannot tell its capture instant, layer 1 takes over
993
+ // as TS reference. A transport reconnection then asks for a resync of whatever
994
+ // layer arrives first.
995
+ manager->UpdateTargetLayers(1, 0);
996
+ manager->OnTransportConnected();
997
+
998
+ REQUIRE(manager->GetTargetLayers().spatial == 1);
999
+
1000
+ // A packet of the current layer 0 arrives. It is not the TS reference one and there
1001
+ // is no way to align it to layer 1, so layer 0 takes over as TS reference and its
1002
+ // RTP timestamps go untouched.
1003
+ packet->SetSsrc(MappedSsrc0);
1004
+ packet->SetSequenceNumber(2);
1005
+
1006
+ auto result = manager->ProcessRtpPacket(
1007
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
1008
+
1009
+ REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
1010
+ REQUIRE(result.isSyncPacket == true);
808
1011
  REQUIRE(result.tsOffset == 0u);
1012
+ }
1013
+
1014
+ SECTION(
1015
+ "ProducerRtcpSenderReport() checks layers upon a Sender Report that is not the first "
1016
+ "one of its stream")
1017
+ {
1018
+ MockListener listener;
1019
+ auto manager = createManager(&listener, /*ssrcs*/ TwoSsrcs, /*preferredLayers*/ { 1, 0 });
1020
+ auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
1021
+ auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
1022
+
1023
+ manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
1024
+ manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
1025
+
1026
+ rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
1027
+ rtpStream1->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
1028
+
1029
+ // Feed packets and a Sender Report to both streams so that they get a score and
1030
+ // RecalculateTargetLayers() takes them into account.
1031
+ for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get() })
1032
+ {
1033
+ packet->SetSsrc(rtpStream->GetSsrc());
1034
+ feedRtpStreamRecv(rtpStream, packet.get(), 10);
1035
+
1036
+ RTC::RTCP::SenderReport sr;
1037
+ sr.SetSsrc(rtpStream->GetSsrc());
1038
+ sr.SetNtpSec(1000);
1039
+ sr.SetNtpFrac(0);
1040
+ sr.SetRtpTs(90000);
1041
+ rtpStream->ReceiveRtcpSenderReport(&sr);
1042
+ }
1043
+
1044
+ // Set target layer to 0. This sets tsReferenceSpatialLayer = 0.
1045
+ manager->UpdateTargetLayers(0, 0);
1046
+
1047
+ REQUIRE(manager->GetTargetLayers().spatial == 0);
1048
+
1049
+ // A Sender Report that is not the first one of its stream may be the one that made
1050
+ // the capture instant of the TS reference stream known, so layers are checked and
1051
+ // the preferred layer 1 is picked.
1052
+ manager->ProducerRtcpSenderReport(rtpStream1.get(), /*first*/ false);
1053
+
1054
+ REQUIRE(manager->GetTargetLayers().spatial == 1);
1055
+ }
1056
+
1057
+ SECTION("ProcessRtpPacket() calculates tsOffset of the spatial layer of the packet")
1058
+ {
1059
+ MockListener listener;
1060
+ auto manager = createManager(
1061
+ &listener,
1062
+ /*ssrcs*/ ThreeSsrcs,
1063
+ /*preferredLayers*/ { 2, 0 },
1064
+ /*keyFrameSupported*/ false);
1065
+ auto rtpStream0 = createRtpStreamRecv(MappedSsrc0);
1066
+ auto rtpStream1 = createRtpStreamRecv(MappedSsrc1);
1067
+ auto rtpStream2 = createRtpStreamRecv(MappedSsrc2);
1068
+
1069
+ manager->ProducerRtpStream(rtpStream0.get(), MappedSsrc0);
1070
+ manager->ProducerRtpStream(rtpStream1.get(), MappedSsrc1);
1071
+ manager->ProducerRtpStream(rtpStream2.get(), MappedSsrc2);
1072
+
1073
+ // All layers hold RTP timestamp 1000. Layer 1 was captured 500 ms before layer 0
1074
+ // and layer 2 a whole second before it, so their offsets differ.
1075
+ rtpStream0->SetCaptureMapping(/*captureMs*/ 1000, /*ts*/ 1000);
1076
+ rtpStream1->SetCaptureMapping(/*captureMs*/ 500, /*ts*/ 1000);
1077
+ rtpStream2->SetCaptureMapping(/*captureMs*/ 0, /*ts*/ 1000);
1078
+
1079
+ // Feed packets and a Sender Report to every stream so that they all get a score
1080
+ // and RecalculateTargetLayers() takes them into account.
1081
+ for (auto* rtpStream : { rtpStream0.get(), rtpStream1.get(), rtpStream2.get() })
1082
+ {
1083
+ packet->SetSsrc(rtpStream->GetSsrc());
1084
+ feedRtpStreamRecv(rtpStream, packet.get(), 10);
1085
+
1086
+ RTC::RTCP::SenderReport sr;
1087
+ sr.SetSsrc(rtpStream->GetSsrc());
1088
+ sr.SetNtpSec(1000);
1089
+ sr.SetNtpFrac(0);
1090
+ sr.SetRtpTs(90000);
1091
+ rtpStream->ReceiveRtcpSenderReport(&sr);
1092
+ }
1093
+
1094
+ // Set target layer to 0 and sync. This sets tsReferenceSpatialLayer = 0, which
1095
+ // stays there for the rest of the SECTION since it has a capture mapping.
1096
+ manager->UpdateTargetLayers(0, 0);
1097
+
1098
+ packet->SetSsrc(MappedSsrc0);
1099
+ packet->SetSequenceNumber(1);
1100
+ manager->ProcessRtpPacket(
1101
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
1102
+
1103
+ REQUIRE(manager->GetCurrentSpatialLayer() == 0);
1104
+
1105
+ // Switch to layer 1.
1106
+ manager->UpdateTargetLayers(1, 0);
1107
+
1108
+ packet->SetSsrc(MappedSsrc1);
1109
+ packet->SetSequenceNumber(1);
1110
+
1111
+ auto result = manager->ProcessRtpPacket(
1112
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
1113
+
1114
+ REQUIRE(result.spatialLayerSwitched == true);
1115
+ REQUIRE(result.tsOffset == 45000u);
809
1116
  REQUIRE(manager->GetCurrentSpatialLayer() == 1);
1117
+
1118
+ // A transport reconnection asks for a resync of whatever layer arrives first, and
1119
+ // moves the target to the preferred layer 2.
1120
+ manager->OnTransportConnected();
1121
+
1122
+ REQUIRE(manager->GetTargetLayers().spatial == 2);
1123
+
1124
+ // A packet of the current layer 1 arrives while layer 2 is the target one, so the
1125
+ // offset must be the one of layer 1 and not the one of layer 2.
1126
+ packet->SetSsrc(MappedSsrc1);
1127
+ packet->SetSequenceNumber(2);
1128
+
1129
+ result = manager->ProcessRtpPacket(
1130
+ packet.get(), /*lastSentPacketHasMarker*/ false, /*clockRate*/ 90000, /*maxPacketTs*/ 0);
1131
+
1132
+ REQUIRE(result.type == RTC::ProducerStreamManager::RtpPacketProcessResult::Type::FORWARD);
1133
+ REQUIRE(result.isSyncPacket == true);
1134
+ REQUIRE(result.tsOffset == 45000u);
810
1135
  }
811
1136
 
812
1137
  SECTION("OnResumed() sets syncRequired")
@@ -840,7 +1165,7 @@ SCENARIO("SimulcastProducerStreamManager", "[rtp][producerstreammanager][simulca
840
1165
  // Call OnResumed — should set syncRequired.
841
1166
  manager->OnResumed();
842
1167
 
843
- // Reset target since OnResumed may have reset it via MayChangeLayers.
1168
+ // Reset target since OnResumed may have reset it via MayChangeLayers().
844
1169
  manager->UpdateTargetLayers(0, 0);
845
1170
 
846
1171
  // Prove syncRequired was set: next packet is a sync packet.
@@ -66,9 +66,9 @@ namespace
66
66
  {
67
67
  }
68
68
 
69
- void OnRtpStreamNeedWorstRemoteFractionLost(
70
- RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
69
+ uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
71
70
  {
71
+ return 0;
72
72
  }
73
73
  };
74
74
 
@@ -6,7 +6,7 @@
6
6
  #include <uv.h>
7
7
  #include <catch2/catch_test_macros.hpp>
8
8
 
9
- SCENARIO("TransportTuple", "[transport-tuple]")
9
+ SCENARIO("TransportTuple", "[transporttuple]")
10
10
  {
11
11
  class UdpSocketListener : public RTC::UdpSocket::Listener
12
12
  {
@@ -16,4 +16,53 @@ SCENARIO("Utils::Time", "[utils][time]")
16
16
  REQUIRE(ntp2.seconds == ntp.seconds);
17
17
  REQUIRE(ntp2.fractions == ntp.fractions);
18
18
  }
19
+
20
+ SECTION("TimeMs2Ntp()")
21
+ {
22
+ auto ntp = Utils::Time::TimeMs2Ntp(1500);
23
+
24
+ REQUIRE(ntp.seconds == 1);
25
+ // Half a second in NTP fractional units.
26
+ REQUIRE(ntp.fractions == 2147483648);
27
+
28
+ // A real NTP instant, seconds since Jan 1, 1900, which still fits in 32 bits.
29
+ ntp = Utils::Time::TimeMs2Ntp(3990000000750);
30
+
31
+ REQUIRE(ntp.seconds == 3990000000);
32
+ REQUIRE(Utils::Time::Ntp2TimeMs(ntp) == 3990000000750);
33
+ }
34
+
35
+ SECTION("TimeMs2Q32x32()")
36
+ {
37
+ // A whole second is the fractional unit itself.
38
+ // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
39
+ REQUIRE(Utils::Time::TimeMs2Q32x32(1000).value() == 4294967296);
40
+ // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
41
+ REQUIRE(Utils::Time::TimeMs2Q32x32(-1000).value() == -4294967296);
42
+ // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
43
+ REQUIRE(Utils::Time::TimeMs2Q32x32(0).value() == 0);
44
+ // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
45
+ REQUIRE(Utils::Time::TimeMs2Q32x32(1).value() == 4294967);
46
+
47
+ // Seconds are 32 bits wide in the format, so 2^31 seconds no longer fit.
48
+ constexpr int64_t OutOfRangeMs{ (1LL << 31) * 1000 };
49
+
50
+ REQUIRE(Utils::Time::TimeMs2Q32x32(OutOfRangeMs) == std::nullopt);
51
+ REQUIRE(Utils::Time::TimeMs2Q32x32(-OutOfRangeMs) == std::nullopt);
52
+ REQUIRE(Utils::Time::TimeMs2Q32x32(OutOfRangeMs - 1).has_value());
53
+ REQUIRE(Utils::Time::TimeMs2Q32x32(-OutOfRangeMs + 1).has_value());
54
+ }
55
+
56
+ SECTION("Q32x32ToTimeMs()")
57
+ {
58
+ REQUIRE(Utils::Time::Q32x32ToTimeMs(4294967296) == 1000);
59
+ REQUIRE(Utils::Time::Q32x32ToTimeMs(-4294967296) == -1000);
60
+ REQUIRE(Utils::Time::Q32x32ToTimeMs(0) == 0);
61
+
62
+ for (const int64_t ms : { 1, -1, 1000, -1000, 123456, -123456 })
63
+ {
64
+ // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
65
+ REQUIRE(Utils::Time::Q32x32ToTimeMs(Utils::Time::TimeMs2Q32x32(ms).value()) == ms);
66
+ }
67
+ }
19
68
  }