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.
- package/CHANGELOG.md +16 -0
- package/node/lib/Consumer.js +1 -1
- package/node/lib/Transport.d.ts.map +1 -1
- package/node/lib/Transport.js +7 -0
- package/node/lib/ortc.d.ts.map +1 -1
- package/node/lib/ortc.js +10 -0
- package/node/lib/test/test-PipeTransport.js +35 -30
- package/package.json +6 -6
- package/worker/include/DepLibUV.hpp +33 -4
- package/worker/include/RTC/Consumer.hpp +4 -1
- package/worker/include/RTC/Producer.hpp +34 -5
- package/worker/include/RTC/RTCP/Packet.hpp +7 -1
- package/worker/include/RTC/RTP/Packet.hpp +18 -0
- package/worker/include/RTC/RTP/RtpStream.hpp +76 -14
- package/worker/include/RTC/RTP/RtpStreamRecv.hpp +191 -7
- package/worker/include/RTC/RTP/RtpStreamSend.hpp +56 -10
- package/worker/include/RTC/RTP/RtxStream.hpp +34 -2
- package/worker/include/RTC/RemoteCaptureTimeEstimator.hpp +92 -0
- package/worker/include/RTC/RemoteClockOffsetEstimator.hpp +87 -0
- package/worker/include/RTC/Router.hpp +2 -5
- package/worker/include/RTC/SimulcastProducerStreamManager.hpp +3 -0
- package/worker/include/RTC/Transport.hpp +15 -7
- package/worker/include/Shared.hpp +5 -0
- package/worker/include/SharedInterface.hpp +6 -0
- package/worker/include/Utils.hpp +44 -3
- package/worker/meson.build +4 -0
- package/worker/mocks/include/MockShared.hpp +7 -0
- package/worker/src/DepLibUV.cpp +17 -0
- package/worker/src/RTC/Consumer.cpp +8 -5
- package/worker/src/RTC/Producer.cpp +85 -16
- package/worker/src/RTC/RTP/Packet.cpp +9 -1
- package/worker/src/RTC/RTP/RtpStream.cpp +14 -10
- package/worker/src/RTC/RTP/RtpStreamRecv.cpp +150 -23
- package/worker/src/RTC/RTP/RtpStreamSend.cpp +34 -19
- package/worker/src/RTC/RTP/RtxStream.cpp +12 -6
- package/worker/src/RTC/RemoteCaptureTimeEstimator.cpp +77 -0
- package/worker/src/RTC/RemoteClockOffsetEstimator.cpp +106 -0
- package/worker/src/RTC/Router.cpp +10 -8
- package/worker/src/RTC/RtpDictionaries/RtcpParameters.cpp +13 -3
- package/worker/src/RTC/SCTP/packet/chunks/SackChunk.cpp +6 -2
- package/worker/src/RTC/SimulcastProducerStreamManager.cpp +115 -56
- package/worker/src/RTC/SvcProducerStreamManager.cpp +22 -17
- package/worker/src/RTC/Transport.cpp +96 -4
- package/worker/test/src/RTC/RTP/TestPacket.cpp +6 -0
- package/worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp +2 -2
- package/worker/test/src/RTC/RTP/TestRtpStreamSend.cpp +119 -0
- package/worker/test/src/RTC/TestConsumer.cpp +4 -3
- package/worker/test/src/RTC/TestPortManager.cpp +1 -1
- package/worker/test/src/RTC/TestRemoteCaptureTimeEstimator.cpp +164 -0
- package/worker/test/src/RTC/TestRemoteClockOffsetEstimator.cpp +241 -0
- package/worker/test/src/RTC/TestSimpleProducerStreamManager.cpp +2 -2
- package/worker/test/src/RTC/TestSimulcastProducerStreamManager.cpp +340 -15
- package/worker/test/src/RTC/TestSvcProducerStreamManager.cpp +2 -2
- package/worker/test/src/RTC/TestTransportTuple.cpp +1 -1
- package/worker/test/src/Utils/TestTime.cpp +49 -0
|
@@ -70,9 +70,9 @@ namespace
|
|
|
70
70
|
{
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
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
|
|
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
|
|
713
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
}
|