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
@@ -164,19 +164,32 @@ namespace RTC
164
164
  {
165
165
  MS_TRACE();
166
166
 
167
- // Just interested if this is the first Sender Report for a RTP stream.
168
- if (!first)
167
+ if (first)
168
+ {
169
+ MS_DEBUG_TAG(simulcast, "first SenderReport [ssrc:%" PRIu32 "]", rtpStream->GetSsrc());
170
+ }
171
+
172
+ // If the capture instant of our RTP timestamp reference stream cannot be told
173
+ // yet, do nothing since we know we won't be able to switch.
174
+ auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();
175
+
176
+ if (!producerTsReferenceRtpStream || !producerTsReferenceRtpStream->GetCaptureMapping().has_value())
169
177
  {
170
178
  return;
171
179
  }
172
180
 
173
- MS_DEBUG_TAG(simulcast, "first SenderReport [ssrc:%" PRIu32 "]", rtpStream->GetSsrc());
181
+ // Other than the first Sender Report of each stream, the only one worth checking
182
+ // layers upon is the one that makes the capture instant of the RTP timestamp
183
+ // reference stream known, since other spatial layers may become switchable then.
184
+ // NOTE: Doing it on every single Sender Report would ask the Transport to
185
+ // redistribute its outgoing bitrate at the pace of the RTCP of the sender and we
186
+ // don't want that.
187
+ const bool tsReferenceCaptureMappingIsNew =
188
+ this->tsReferenceSpatialLayerWithCaptureMapping != this->tsReferenceSpatialLayer;
174
189
 
175
- // If our RTP timestamp reference stream does not yet have SR, do nothing
176
- // since we know we won't be able to switch.
177
- auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();
190
+ this->tsReferenceSpatialLayerWithCaptureMapping = this->tsReferenceSpatialLayer;
178
191
 
179
- if (!producerTsReferenceRtpStream || !producerTsReferenceRtpStream->GetSenderReportNtpMs())
192
+ if (!first && !tsReferenceCaptureMappingIsNew)
180
193
  {
181
194
  return;
182
195
  }
@@ -225,6 +238,11 @@ namespace RTC
225
238
  uint32_t requiredBitrate{ 0u };
226
239
  int16_t spatialLayer{ 0 };
227
240
  int16_t temporalLayer{ 0 };
241
+ // Whether a usable spatial layer has been found, in which case we must not
242
+ // go above the preferred spatial layer. Same criteria as in
243
+ // RecalculateTargetLayers(), so both take the same decision no matter the
244
+ // bitrate of the layer.
245
+ bool usableSpatialLayerFound{ this->provisionalTargetLayers.spatial != -1 };
228
246
 
229
247
  for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx)
230
248
  {
@@ -249,8 +267,12 @@ namespace RTC
249
267
  {
250
268
  continue;
251
269
  }
252
- // If this is higher than preferred spatial layer, abort.
253
- else if (spatialLayer > this->preferredLayers.spatial)
270
+ // If this is higher than preferred spatial layer, abort unless no lower
271
+ // spatial layer is usable or this is the provisional one (so we may still
272
+ // increase its temporal layer).
273
+ else if (
274
+ spatialLayer > this->preferredLayers.spatial && usableSpatialLayerFound &&
275
+ spatialLayer != this->provisionalTargetLayers.spatial)
254
276
  {
255
277
  MS_DEBUG_DEV(
256
278
  "avoid upgrading to spatial layer %" PRIi16
@@ -300,10 +322,19 @@ namespace RTC
300
322
  continue;
301
323
  }
302
324
 
325
+ // This spatial layer is usable even if it has no bitrate at all.
326
+ usableSpatialLayerFound = true;
327
+
303
328
  temporalLayer = 0;
304
329
 
330
+ // Don't consider temporal layers above the preferred one, nor above the
331
+ // ones this stream has.
332
+ const auto maxTemporalLayer = std::min(
333
+ static_cast<int16_t>(producerRtpStream->GetTemporalLayers() - 1),
334
+ this->preferredLayers.temporal);
335
+
305
336
  // Check bitrate of every temporal layer.
306
- for (; std::cmp_less(temporalLayer, producerRtpStream->GetTemporalLayers()); ++temporalLayer)
337
+ for (; temporalLayer <= maxTemporalLayer; ++temporalLayer)
307
338
  {
308
339
  // Ignore temporal layers lower than the one we already have (taking
309
340
  // into account the spatial layer too).
@@ -589,43 +620,71 @@ namespace RTC
589
620
  {
590
621
  tsOffset = 0u;
591
622
  }
592
- // If this is not the RTP stream we use as TS reference, do NTP based RTP
593
- // TS synchronization.
623
+ // If this is not the RTP stream we use as TS reference, synchronize its RTP
624
+ // timestamps based on the capture instant of the media.
594
625
  else
595
626
  {
596
- auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();
597
- auto* producerTargetRtpStream = GetProducerTargetRtpStream();
598
-
599
- // NOTE: If we are here is because we have Sender Reports for both the
600
- // TS reference stream and the target one.
601
- MS_ASSERT(
602
- producerTsReferenceRtpStream->GetSenderReportNtpMs(),
603
- "no Sender Report for TS reference RTP stream");
604
- MS_ASSERT(
605
- producerTargetRtpStream->GetSenderReportNtpMs(), "no Sender Report for current RTP stream");
606
-
607
- // Calculate NTP and TS stuff.
608
- auto ntpMs1 = producerTsReferenceRtpStream->GetSenderReportNtpMs();
609
- auto ts1 = producerTsReferenceRtpStream->GetSenderReportTs();
610
- auto ntpMs2 = producerTargetRtpStream->GetSenderReportNtpMs();
611
- auto ts2 = producerTargetRtpStream->GetSenderReportTs();
612
- int64_t diffMs;
613
-
614
- if (ntpMs2 >= ntpMs1)
627
+ const auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();
628
+ // NOTE: The stream of the spatial layer of this packet, which is not always
629
+ // the one of the target spatial layer. A resync may be pending for the
630
+ // current spatial layer while the target one is a different one.
631
+ const auto* producerRtpStream = this->producerRtpStreams.at(spatialLayer);
632
+
633
+ const auto tsReferenceCaptureMapping = producerTsReferenceRtpStream->GetCaptureMapping();
634
+ const auto captureMapping = producerRtpStream->GetCaptureMapping();
635
+
636
+ // Without the capture instant of the TS reference stream there is nothing to
637
+ // align this one to, so take this spatial layer as the new TS reference and
638
+ // let its RTP timestamps go untouched, which is what would have been done had
639
+ // it been chosen as TS reference in the first place.
640
+ if (!tsReferenceCaptureMapping.has_value())
615
641
  {
616
- diffMs = ntpMs2 - ntpMs1;
642
+ MS_DEBUG_TAG(
643
+ simulcast,
644
+ "cannot tell the capture instant of the TS reference stream, using spatial layer "
645
+ "%" PRIi16 " as RTP timestamp reference",
646
+ spatialLayer);
647
+
648
+ this->tsReferenceSpatialLayer = spatialLayer;
617
649
  }
618
- else
650
+ // The capture instant of this stream cannot be told yet, so there is no way to
651
+ // align it and no reason to give up a TS reference stream that is good. Discard
652
+ // the packet and wait, since the Sender Report that is missing is on its way.
653
+ else if (!captureMapping.has_value())
619
654
  {
620
- diffMs = -1 * (ntpMs1 - ntpMs2);
621
- }
655
+ MS_DEBUG_TAG(
656
+ simulcast,
657
+ "cannot tell yet the capture instant of spatial layer %" PRIi16
658
+ ", discarding packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]",
659
+ spatialLayer,
660
+ packet->GetSsrc(),
661
+ packet->GetSequenceNumber());
662
+
663
+ // Ask for another key frame since this one is being discarded.
664
+ if (packet->IsKeyFrame())
665
+ {
666
+ RequestKeyFrame();
667
+ }
622
668
 
623
- const int64_t diffTs = diffMs * clockRate / 1000;
624
- const uint32_t newTs2 = ts2 - diffTs;
669
+ result.type = RtpPacketProcessResult::Type::SILENT_DROP;
625
670
 
626
- // Apply offset. This is the difference that later must be removed from
627
- // the sending RTP packet.
628
- tsOffset = newTs2 - ts1;
671
+ return result;
672
+ }
673
+ else
674
+ {
675
+ // Calculate capture instant and TS stuff.
676
+ const auto captureMs1 = tsReferenceCaptureMapping.value().captureMs;
677
+ const auto ts1 = tsReferenceCaptureMapping.value().ts;
678
+ const auto captureMs2 = captureMapping.value().captureMs;
679
+ const auto ts2 = captureMapping.value().ts;
680
+ const int64_t diffMs = static_cast<int64_t>(captureMs2) - static_cast<int64_t>(captureMs1);
681
+ const int64_t diffTs = diffMs * clockRate / 1000;
682
+ const uint32_t newTs2 = ts2 - diffTs;
683
+
684
+ // Apply offset. This is the difference that later must be removed from
685
+ // the sending RTP packet.
686
+ tsOffset = newTs2 - ts1;
687
+ }
629
688
  }
630
689
 
631
690
  // When switching to a new stream it may happen that the timestamp of this
@@ -857,8 +916,9 @@ namespace RTC
857
916
 
858
917
  // If we don't have yet a RTP timestamp reference, set it now.
859
918
  if (
860
- newTargetSpatialLayer != -1 && (this->tsReferenceSpatialLayer == -1 ||
861
- !GetProducerTsReferenceRtpStream()->GetSenderReportNtpMs()))
919
+ newTargetSpatialLayer != -1 &&
920
+ (this->tsReferenceSpatialLayer == -1 ||
921
+ !GetProducerTsReferenceRtpStream()->GetCaptureMapping().has_value()))
862
922
  {
863
923
  MS_DEBUG_TAG(
864
924
  simulcast, "using spatial layer %" PRIi16 " as RTP timestamp reference", newTargetSpatialLayer);
@@ -962,6 +1022,13 @@ namespace RTC
962
1022
  continue;
963
1023
  }
964
1024
 
1025
+ // Don't go above the preferred spatial layer if we already found a usable
1026
+ // lower one.
1027
+ if (spatialLayer > this->preferredLayers.spatial && newTargetLayers.spatial != -1)
1028
+ {
1029
+ break;
1030
+ }
1031
+
965
1032
  newTargetLayers.spatial = spatialLayer;
966
1033
 
967
1034
  // If this is the preferred or higher spatial layer take it and exit.
@@ -973,19 +1040,11 @@ namespace RTC
973
1040
 
974
1041
  if (newTargetLayers.spatial != -1)
975
1042
  {
976
- if (newTargetLayers.spatial == this->preferredLayers.spatial)
977
- {
978
- newTargetLayers.temporal = this->preferredLayers.temporal;
979
- }
980
- else if (newTargetLayers.spatial < this->preferredLayers.spatial)
981
- {
982
- newTargetLayers.temporal =
983
- static_cast<int16_t>(this->encodingContext->GetTemporalLayers() - 1);
984
- }
985
- else
986
- {
987
- newTargetLayers.temporal = 0;
988
- }
1043
+ // Don't consider temporal layers above the preferred one, nor above the
1044
+ // ones this stream has.
1045
+ newTargetLayers.temporal = std::min(
1046
+ this->preferredLayers.temporal,
1047
+ static_cast<int16_t>(this->encodingContext->GetTemporalLayers() - 1));
989
1048
  }
990
1049
 
991
1050
  // Return true if any target layer changed.
@@ -1050,7 +1109,7 @@ namespace RTC
1050
1109
 
1051
1110
  return (
1052
1111
  this->tsReferenceSpatialLayer == -1 || spatialLayer == this->tsReferenceSpatialLayer ||
1053
- this->producerRtpStreams.at(spatialLayer)->GetSenderReportNtpMs());
1112
+ this->producerRtpStreams.at(spatialLayer)->GetCaptureMapping().has_value());
1054
1113
  }
1055
1114
 
1056
1115
  RTC::RTP::RtpStreamRecv* SimulcastProducerStreamManager::GetProducerTsReferenceRtpStream() const
@@ -190,9 +190,14 @@ namespace RTC
190
190
 
191
191
  temporalLayer = 0;
192
192
 
193
+ // Don't consider temporal layers above the preferred one, nor above the
194
+ // ones this stream has.
195
+ const auto maxTemporalLayer = std::min(
196
+ static_cast<int16_t>(this->producerRtpStream->GetTemporalLayers() - 1),
197
+ this->preferredLayers.temporal);
198
+
193
199
  // Check bitrate of every temporal layer.
194
- for (; std::cmp_less(temporalLayer, this->producerRtpStream->GetTemporalLayers());
195
- ++temporalLayer)
200
+ for (; temporalLayer <= maxTemporalLayer; ++temporalLayer)
196
201
  {
197
202
  // Ignore temporal layers lower than the one we already have (taking
198
203
  // into account the spatial layer too).
@@ -247,8 +252,9 @@ namespace RTC
247
252
  }
248
253
  }
249
254
 
250
- // If this is the preferred or higher spatial layer, take it and exit.
251
- if (spatialLayer >= this->preferredLayers.spatial)
255
+ // If this is the preferred or higher spatial layer, take it and exit,
256
+ // unless we have not found any usable spatial layer yet.
257
+ if (spatialLayer >= this->preferredLayers.spatial && this->provisionalTargetLayers.spatial != -1)
252
258
  {
253
259
  break;
254
260
  }
@@ -589,6 +595,13 @@ namespace RTC
589
595
  continue;
590
596
  }
591
597
 
598
+ // Don't go above the preferred spatial layer if we already found a usable
599
+ // lower one.
600
+ if (spatialLayer > this->preferredLayers.spatial && newTargetLayers.spatial != -1)
601
+ {
602
+ break;
603
+ }
604
+
592
605
  newTargetLayers.spatial = spatialLayer;
593
606
 
594
607
  // If this is the preferred or higher spatial layer and has bitrate,
@@ -601,19 +614,11 @@ namespace RTC
601
614
 
602
615
  if (newTargetLayers.spatial != -1)
603
616
  {
604
- if (newTargetLayers.spatial == this->preferredLayers.spatial)
605
- {
606
- newTargetLayers.temporal = this->preferredLayers.temporal;
607
- }
608
- else if (newTargetLayers.spatial < this->preferredLayers.spatial)
609
- {
610
- newTargetLayers.temporal =
611
- static_cast<int16_t>(this->encodingContext->GetTemporalLayers() - 1);
612
- }
613
- else
614
- {
615
- newTargetLayers.temporal = 0;
616
- }
617
+ // Don't consider temporal layers above the preferred one, nor above the
618
+ // ones this stream has.
619
+ newTargetLayers.temporal = std::min(
620
+ this->preferredLayers.temporal,
621
+ static_cast<int16_t>(this->encodingContext->GetTemporalLayers() - 1));
617
622
  }
618
623
 
619
624
  done:
@@ -640,6 +640,15 @@ namespace RTC
640
640
  // Insert into the map.
641
641
  this->mapProducers[producerId] = producer;
642
642
 
643
+ // Take this Producer into account for the capture time estimation of its
644
+ // sender.
645
+ {
646
+ const auto& cname = producer->GetRtpParameters().rtcp.cname;
647
+
648
+ this->mapCnameRemoteCaptureTimeEstimator[cname].UpdateSource(
649
+ producer->GetRtpHeaderExtensionIds().absCaptureTime != 0);
650
+ }
651
+
643
652
  MS_DEBUG_DEV("Producer created [producerId:%s]", producerId.c_str());
644
653
 
645
654
  // Take the transport related RTP header extensions of the Producer and
@@ -680,6 +689,14 @@ namespace RTC
680
689
  producerRtpHeaderExtensionIds.dependencyDescriptor;
681
690
  }
682
691
 
692
+ // NOTE: Not transport related, but needed here so that received packets carry
693
+ // its id and `RtpStreamRecv` can read the extension off them.
694
+ if (producerRtpHeaderExtensionIds.absCaptureTime != 0)
695
+ {
696
+ this->recvRtpHeaderExtensionIds.absCaptureTime =
697
+ producerRtpHeaderExtensionIds.absCaptureTime;
698
+ }
699
+
683
700
  // Create status response.
684
701
  auto responseOffset = FBS::Transport::CreateProduceResponse(
685
702
  request->GetBufferBuilder(), FBS::RtpParameters::Type(producer->GetType()));
@@ -1245,6 +1262,26 @@ namespace RTC
1245
1262
  // Remove it from the map.
1246
1263
  this->mapProducers.erase(producer->id);
1247
1264
 
1265
+ // Remove the capture time estimator of its sender once its last Producer
1266
+ // is gone.
1267
+ {
1268
+ const auto& cname = producer->GetRtpParameters().rtcp.cname;
1269
+
1270
+ const bool cnameStillInUse = std::ranges::any_of(
1271
+ this->mapProducers,
1272
+ [&cname](const auto& kv)
1273
+ {
1274
+ const auto* otherProducer = kv.second;
1275
+
1276
+ return otherProducer->GetRtpParameters().rtcp.cname == cname;
1277
+ });
1278
+
1279
+ if (!cnameStillInUse)
1280
+ {
1281
+ this->mapCnameRemoteCaptureTimeEstimator.erase(cname);
1282
+ }
1283
+ }
1284
+
1248
1285
  // Tell the child class to clear associated SSRCs.
1249
1286
  for (const auto& kv : producer->GetRtpStreams())
1250
1287
  {
@@ -2538,6 +2575,17 @@ namespace RTC
2538
2575
  {
2539
2576
  MS_TRACE();
2540
2577
 
2578
+ // Feed the capture time estimator of the sender of this Producer.
2579
+ const auto it =
2580
+ this->mapCnameRemoteCaptureTimeEstimator.find(producer->GetRtpParameters().rtcp.cname);
2581
+
2582
+ if (it != this->mapCnameRemoteCaptureTimeEstimator.end())
2583
+ {
2584
+ auto& remoteCaptureTimeEstimator = it->second;
2585
+
2586
+ remoteCaptureTimeEstimator.SenderReportReceived(rtpStream);
2587
+ }
2588
+
2541
2589
  this->listener->OnTransportProducerRtcpSenderReport(this, producer, rtpStream, first);
2542
2590
  }
2543
2591
 
@@ -2555,13 +2603,57 @@ namespace RTC
2555
2603
  SendRtcpPacket(packet);
2556
2604
  }
2557
2605
 
2558
- void Transport::OnProducerNeedWorstRemoteFractionLost(
2559
- RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost)
2606
+ uint8_t Transport::OnProducerNeedWorstRemoteFractionLost(RTC::Producer* producer, uint32_t mappedSsrc)
2607
+ {
2608
+ MS_TRACE();
2609
+
2610
+ return this->listener->OnTransportNeedWorstRemoteFractionLost(this, producer, mappedSsrc);
2611
+ }
2612
+
2613
+ std::optional<uint64_t> Transport::OnProducerNeedLocalCaptureMs(
2614
+ RTC::Producer* producer, const RTC::RTP::RtpStreamRecv* rtpStream, uint32_t ts)
2615
+ {
2616
+ MS_TRACE();
2617
+
2618
+ const auto it =
2619
+ this->mapCnameRemoteCaptureTimeEstimator.find(producer->GetRtpParameters().rtcp.cname);
2620
+
2621
+ if (it == this->mapCnameRemoteCaptureTimeEstimator.end())
2622
+ {
2623
+ return std::nullopt;
2624
+ }
2625
+
2626
+ const auto& remoteCaptureTimeEstimator = it->second;
2627
+
2628
+ return remoteCaptureTimeEstimator.GetLocalCaptureMs(rtpStream, ts);
2629
+ }
2630
+
2631
+ std::optional<int64_t> Transport::OnProducerNeedRemoteClockOffsetMs(const RTC::Producer* producer)
2560
2632
  {
2561
2633
  MS_TRACE();
2562
2634
 
2563
- this->listener->OnTransportNeedWorstRemoteFractionLost(
2564
- this, producer, mappedSsrc, worstRemoteFractionLost);
2635
+ const auto it =
2636
+ this->mapCnameRemoteCaptureTimeEstimator.find(producer->GetRtpParameters().rtcp.cname);
2637
+
2638
+ if (it == this->mapCnameRemoteCaptureTimeEstimator.end())
2639
+ {
2640
+ return std::nullopt;
2641
+ }
2642
+
2643
+ const auto& remoteCaptureTimeEstimator = it->second;
2644
+ const auto clockOffsetMs = remoteCaptureTimeEstimator.GetClockOffsetMs();
2645
+
2646
+ if (!clockOffsetMs.has_value())
2647
+ {
2648
+ return std::nullopt;
2649
+ }
2650
+
2651
+ // NOTE: The estimator gives the offset against our own monotonic clock, while what
2652
+ // a receiver reconstructs out of the Sender Reports we send is the clock we
2653
+ // announce, so the distance to the NTP epoch has to be taken into account. Both
2654
+ // terms are huge and their sum is small, so they are added as milliseconds before
2655
+ // anything scales them up.
2656
+ return clockOffsetMs.value() + static_cast<int64_t>(this->shared->GetNtpOffsetMs());
2565
2657
  }
2566
2658
 
2567
2659
  void Transport::OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet)
@@ -1059,12 +1059,16 @@ SCENARIO("RTP Packet", "[serializable][rtp][packet]")
1059
1059
  /*paddingLength*/ 0);
1060
1060
 
1061
1061
  REQUIRE(packet->IsPaddedTo4Bytes() == true);
1062
+ REQUIRE(packet->GetCaptureMs() == std::nullopt);
1062
1063
 
1063
1064
  packet->SetPayloadType(100);
1064
1065
  packet->SetMarker(true);
1065
1066
  packet->SetSequenceNumber(12345);
1066
1067
  packet->SetTimestamp(987654321);
1067
1068
  packet->SetSsrc(1234567890);
1069
+ packet->SetCaptureMs(99998888);
1070
+
1071
+ REQUIRE(packet->GetCaptureMs() == 99998888);
1068
1072
 
1069
1073
  std::vector<RTC::RTP::Packet::Extension> extensions;
1070
1074
 
@@ -1204,6 +1208,7 @@ SCENARIO("RTP Packet", "[serializable][rtp][packet]")
1204
1208
  REQUIRE(extensionLen == 3);
1205
1209
 
1206
1210
  REQUIRE(packet->IsPaddedTo4Bytes() == true);
1211
+ REQUIRE(packet->GetCaptureMs() == 99998888);
1207
1212
 
1208
1213
  /* Clone it. */
1209
1214
 
@@ -1252,6 +1257,7 @@ SCENARIO("RTP Packet", "[serializable][rtp][packet]")
1252
1257
  REQUIRE(extensionLen == 3);
1253
1258
 
1254
1259
  REQUIRE(packet->IsPaddedTo4Bytes() == true);
1260
+ REQUIRE(packet->GetCaptureMs() == 99998888);
1255
1261
 
1256
1262
  /* Set payload. */
1257
1263
 
@@ -106,9 +106,9 @@ SCENARIO("RtpStreamRecv", "[rtp][rtpstream][rtpstreamrecv]")
106
106
  }
107
107
  }
108
108
 
109
- void OnRtpStreamNeedWorstRemoteFractionLost(
110
- RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
109
+ uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
111
110
  {
111
+ return 0;
112
112
  }
113
113
 
114
114
  public:
@@ -1,5 +1,6 @@
1
1
  #include "common.hpp"
2
2
  #include "RTC/RTCP/FeedbackRtpNack.hpp"
3
+ #include "RTC/RTCP/SenderReport.hpp"
3
4
  #include "RTC/RTP/Codecs/AV1.hpp"
4
5
  #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp"
5
6
  #include "RTC/RTP/Codecs/VP8.hpp"
@@ -1125,6 +1126,124 @@ SCENARIO("RtpStreamSend", "[rtp][rtcp][nack][rtpstream][rtpstreamsend]")
1125
1126
  REQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED);
1126
1127
  }
1127
1128
 
1129
+ SECTION("Sender Report RTP timestamp is based on the capture instant when known")
1130
+ {
1131
+ // Instant reported by the mocked clock, and hence the one at which packets are seen.
1132
+ constexpr uint64_t PacketMs{ 1000 };
1133
+ constexpr uint32_t PacketTs{ 1533790901 };
1134
+
1135
+ TestRtpStreamListener testRtpStreamListener;
1136
+
1137
+ RTC::RTP::RtpStream::Params params;
1138
+
1139
+ params.ssrc = 1111;
1140
+ params.clockRate = 90000;
1141
+ params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;
1142
+
1143
+ std::string mid;
1144
+
1145
+ // Without capture instant, the instant at which the packet was seen is used.
1146
+ {
1147
+ RTC::RTP::RtpStreamSend stream(
1148
+ std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);
1149
+ auto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, PacketTs));
1150
+
1151
+ sendRtpPacket(
1152
+ {
1153
+ { std::addressof(stream), params.ssrc }
1154
+ },
1155
+ packet.get());
1156
+
1157
+ const std::unique_ptr<RTC::RTCP::SenderReport> report(
1158
+ stream.GetRtcpSenderReport(PacketMs + 1000));
1159
+
1160
+ REQUIRE(report);
1161
+ REQUIRE(report->GetRtpTs() == PacketTs + params.clockRate);
1162
+ }
1163
+
1164
+ // With capture instant, the extra half second since it is accounted for.
1165
+ {
1166
+ RTC::RTP::RtpStreamSend stream(
1167
+ std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);
1168
+ auto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, PacketTs));
1169
+
1170
+ packet->SetCaptureMs(PacketMs - 500);
1171
+
1172
+ sendRtpPacket(
1173
+ {
1174
+ { std::addressof(stream), params.ssrc }
1175
+ },
1176
+ packet.get());
1177
+
1178
+ const std::unique_ptr<RTC::RTCP::SenderReport> report(
1179
+ stream.GetRtcpSenderReport(PacketMs + 1000));
1180
+
1181
+ REQUIRE(report);
1182
+ REQUIRE(report->GetRtpTs() == PacketTs + ((1500 * params.clockRate) / 1000));
1183
+ }
1184
+
1185
+ // A capture instant ahead of now, which the estimation may yield, does not make the
1186
+ // difference wrap around.
1187
+ {
1188
+ RTC::RTP::RtpStreamSend stream(
1189
+ std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);
1190
+ auto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, PacketTs));
1191
+
1192
+ packet->SetCaptureMs(PacketMs + 3000);
1193
+
1194
+ sendRtpPacket(
1195
+ {
1196
+ { std::addressof(stream), params.ssrc }
1197
+ },
1198
+ packet.get());
1199
+
1200
+ const std::unique_ptr<RTC::RTCP::SenderReport> report(
1201
+ stream.GetRtcpSenderReport(PacketMs + 1000));
1202
+
1203
+ REQUIRE(report);
1204
+ REQUIRE(report->GetRtpTs() == PacketTs);
1205
+ }
1206
+ }
1207
+
1208
+ SECTION("no Sender Report is generated once the stream has stopped sending")
1209
+ {
1210
+ // Instant reported by the mocked clock, and hence the one at which packets are seen.
1211
+ constexpr uint64_t PacketMs{ 1000 };
1212
+ constexpr uint32_t PacketTs{ 1533790901 };
1213
+
1214
+ TestRtpStreamListener testRtpStreamListener;
1215
+
1216
+ RTC::RTP::RtpStream::Params params;
1217
+
1218
+ params.ssrc = 1111;
1219
+ params.clockRate = 90000;
1220
+ params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO;
1221
+
1222
+ std::string mid;
1223
+
1224
+ RTC::RTP::RtpStreamSend stream(
1225
+ std::addressof(testRtpStreamListener), std::addressof(shared), params, mid);
1226
+ auto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, PacketTs));
1227
+
1228
+ sendRtpPacket(
1229
+ {
1230
+ { std::addressof(stream), params.ssrc }
1231
+ },
1232
+ packet.get());
1233
+
1234
+ // Right at the limit the Sender Report is still generated.
1235
+ const std::unique_ptr<RTC::RTCP::SenderReport> report(
1236
+ stream.GetRtcpSenderReport(PacketMs + RTC::RTP::RtpStreamSend::MaxSenderReportReferenceAgeMs));
1237
+
1238
+ REQUIRE(report);
1239
+
1240
+ // Past the limit it is not.
1241
+ const std::unique_ptr<RTC::RTCP::SenderReport> staleReport(stream.GetRtcpSenderReport(
1242
+ PacketMs + RTC::RTP::RtpStreamSend::MaxSenderReportReferenceAgeMs + 1));
1243
+
1244
+ REQUIRE_FALSE(staleReport);
1245
+ }
1246
+
1128
1247
  #ifdef PERFORMANCE_TEST
1129
1248
  SECTION("Performance")
1130
1249
  {
@@ -33,9 +33,9 @@ namespace
33
33
  {
34
34
  }
35
35
 
36
- void OnRtpStreamNeedWorstRemoteFractionLost(
37
- RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override
36
+ uint8_t OnRtpStreamNeedWorstRemoteFractionLost(RTC::RTP::RtpStreamRecv* /*rtpStream*/) override
38
37
  {
38
+ return 0;
39
39
  }
40
40
  };
41
41
 
@@ -104,7 +104,8 @@ namespace
104
104
 
105
105
  encoding.ssrc = 1234567890;
106
106
 
107
- rtpParameters.mid = "mid";
107
+ rtpParameters.mid = "mid";
108
+ rtpParameters.rtcp.cname = "cname";
108
109
  rtpParameters.codecs.emplace_back(codec);
109
110
  rtpParameters.encodings.emplace_back(encoding);
110
111
  rtpParameters.headerExtensions = std::vector<RTC::RtpHeaderExtensionParameters>();
@@ -10,7 +10,7 @@
10
10
  // merged unrelated bindings. This scenario locks down the post-fix
11
11
  // behavior: distinct tuples produce distinct keys, equal tuples produce equal
12
12
  // keys.
13
- SCENARIO("PortManager", "[rtc][portmanager]")
13
+ SCENARIO("PortManager", "[portmanager]")
14
14
  {
15
15
  // Helper: build an IPv4 `sockaddr_storage` from a dotted-quad string + port=0.
16
16
  auto makeV4 = [](const char* dottedQuad)