#SimulcastConsumer::SendRtpPacket
void SimulcastConsumer::SendRtpPacket(RTC::RtpPacket* packet)
{
MS_TRACE();
//1.consumer处于不活动状态则直接返回
if (!IsActive())
return;
//2.目标时间域名层无效则直接返回
if (this->targetTemporalLayer == -1)
return;
auto payloadType = packet->GetPayloadType();
//3.不支持的负载类型则直接返回 NOTE: This may happen if this Consumer supports just some codecs of those in the corresponding Producer.
if (this->supportedCodecPayloadTypes.find(payloadType) == this->supportedCodecPayloadTypes.end())
{
MS_DEBUG_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType);
return;
}
auto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc());
bool shouldSwitchCurrentSpatialLayer{ false };
//4.检查:当前空间层不是目标空间层且packet所属的空间层是目标空间层:则判断是否是关键帧,不是关键帧忽略直接返回,是关键帧则设置需要切换当前空间层标志,同时设置需要同步标志
// Check whether this is the packet we are waiting for in order to update the current spatial layer.
if (this->currentSpatialLayer != this->targetSpatialLayer && spatialLayer == this->targetSpatialLayer)
{
// Ignore if not a key frame.
if (!packet->IsKeyFrame())
return;
shouldSwitchCurrentSpatialLayer = true;
// Need to resync the stream.
this->syncRequired = true;
}
//4.检查:如果packet所属空间层不是当前空间层则直接返回
// If the packet belongs to different spatial layer than the one being sent, drop it.
else if (spatialLayer != this->currentSpatialLayer)
{
return;
}
//5.如果需要同步但不是关键帧则直接返回
// If we need to sync and this is not a key frame, ignore the packet.
if (this->syncRequired && !packet->IsKeyFrame())
return;
// Whether this is the first packet after re-sync.
bool isSyncPacket = this->syncRequired;
//6.需要同步后的第一个packet,同步序列号和时间戳
// Sync sequence number and timestamp if required.
if (isSyncPacket)
{
if (packet->IsKeyFrame())
MS_DEBUG_TAG(rtp, "sync key frame received");
uint32_t tsOffset{ 0u };
//6.1 同步RtpStream的rtp时间戳 // Sync our RTP stream's RTP timestamp.
if (spatialLayer == this->tsReferenceSpatialLayer) //如果packet所属空间层为时间参考的空间层,则认为tsOffset时间偏移为0
{
tsOffset = 0u;
}
//6.2 如果packet所属空间层不为时间参考的空间层,则需要基于NTP时间进行Rtp时间戳同步
// If this is not the RTP stream we use as TS reference, do NTP based RTP TS synchronization.
else
{
auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream();
auto* producerTargetRtpStream = GetProducerTargetRtpStream();
// NOTE: If we are here is because we have Sender Reports for both the TS reference stream and the target one.
MS_ASSERT(producerTsReferenceRtpStream->GetSenderReportNtpMs(), "no Sender Report for TS reference RTP stream");
MS_ASSERT(producerTargetRtpStream->GetSenderReportNtpMs(), "no Sender Report for current RTP stream");
// Calculate NTP and TS stuff.
auto ntpMs1 = producerTsReferenceRtpStream->GetSenderReportNtpMs();
auto ts1 = producerTsReferenceRtpStream->GetSenderReportTs();
auto ntpMs2 = producerTargetRtpStream->GetSenderReportNtpMs();
auto ts2 = producerTargetRtpStream->GetSenderReportTs();
//6.3 计算RtpStream rtp时间戳差值,换到到rtp时间单位
int64_t diffMs;
if (ntpMs2 >= ntpMs1)
diffMs = ntpMs2 - ntpMs1;
else
diffMs = -1 * (ntpMs1 - ntpMs2);
int64_t diffTs = diffMs * this->rtpStream->GetClockRate() / 1000;
uint32_t newTs2 = ts2 - diffTs;
//6.4 此tsOffset是两个rtpstream rtp时间戳差值
// Apply offset. This is the difference that later must be removed from the sending RTP packet.
tsOffset = newTs2 - ts1;
}
//6.5 当切换到新rtpstream时,可能一个问题是关键帧的时间戳小于已经发送到对端的最大时间戳,为此选定的Producer流的整个实时过程中应用额外的偏移量以“修复”它
// When switching to a new stream it may happen that the timestamp of this
// key frame is lower than the highest timestamp sent to the remote endpoint.
// If so, apply an extra offset to "fix" it for the whole live of this selected
// Producer stream.
// clang-format off
if (
shouldSwitchCurrentSpatialLayer &&
(packet->GetTimestamp() - tsOffset <= this->rtpStream->GetMaxPacketTs()) //关键帧时间戳小于最大发送数据包时间戳,需要额外补偿
)
// clang-format on
{
// Max delay in ms we allow for the stream when switching.
// https://en.wikipedia.org/wiki/Audio-to-video_synchronization#Recommendations
static const uint32_t MaxExtraOffsetMs{ 75u };
int64_t maxTsExtraOffset = MaxExtraOffsetMs * this->rtpStream->GetClockRate() / 1000; //允许流切换允许的最大延迟
uint32_t tsExtraOffset = this->rtpStream->GetMaxPacketTs() - packet->GetTimestamp() + tsOffset; //关键帧与发送最大数据包时间差值
// NOTE: Don't ask for a key frame if already done.
if (this->keyFrameForTsOffsetRequested) //如果已经发送关键帧请求不再请求
{
// Give up and use the theoretical offset.
if (tsExtraOffset > maxTsExtraOffset) //如果发送完关键帧请求后,关键帧的时间戳差值仍大于允许的最大延迟,则放弃额外offset补偿,使用理论差值offset同步
{
tsExtraOffset = 1u; //默认比理论offset大1
}
}
else if (tsExtraOffset > maxTsExtraOffset) //如果没有请求过关键帧,则进行关键帧请求,本次同步结束返回等待下一个关键帧进行同步
{
RequestKeyFrameForTargetSpatialLayer();
this->keyFrameForTsOffsetRequested = true;
return;
}
// It's common that, when switching spatial layer, the resulting TS for the outgoing packet matches the highest seen in the previous stream. Fix it.
else if (tsExtraOffset == 0u) //如果关键帧时间戳刚好是发送最大数据包时间戳,则以30fps时间单位偏移
{
// Apply an expected offset for a new frame in a 30fps stream.
static const uint8_t MsOffset{ 33u }; // (1 / 30 * 1000).
tsExtraOffset = MsOffset * this->rtpStream->GetClockRate() / 1000;
}
if (tsExtraOffset > 0u)
{
MS_DEBUG_TAG(
simulcast,
"RTP timestamp extra offset generated for stream switching: %" PRIu32,
tsExtraOffset);
// Increase the timestamp offset for the whole life of this Producer stream (until switched to a different one).
tsOffset -= tsExtraOffset; //计算出的额外offset偏移
}
}
this->tsOffset = tsOffset; //保存当前偏移
// Sync our RTP stream's sequence number.
this->rtpSeqManager.Sync(packet->GetSequenceNumber() - 1); //设置同步起始序列号
this->encodingContext->SyncRequired();
this->syncRequired = false; //同步完成不再需求同步
this->keyFrameForTsOffsetRequested = false; //关键帧时间戳同步完成不再需求同步
}
if (shouldSwitchCurrentSpatialLayer) //应该切换当前空间层
{
// Update current spatial layer.
this->currentSpatialLayer = this->targetSpatialLayer;
// Update target and current temporal layer.
this->encodingContext->SetTargetTemporalLayer(this->targetTemporalLayer);
this->encodingContext->SetCurrentTemporalLayer(packet->GetTemporalLayer());
// Reset the score of our RtpStream to 10.
this->rtpStream->ResetScore(10u, /*notify*/ false);
// Emit the layersChange event.
EmitLayersChange();
// Emit the score event.
EmitScore();
// Rewrite payload if needed.
packet->ProcessPayload(this->encodingContext.get());
}
else //不需要切换
{
auto previousTemporalLayer = this->encodingContext->GetCurrentTemporalLayer();
// Rewrite payload if needed. Drop packet if necessary.
if (!packet->ProcessPayload(this->encodingContext.get()))
{
this->rtpSeqManager.Drop(packet->GetSequenceNumber());
return;
}
if (previousTemporalLayer != this->encodingContext->GetCurrentTemporalLayer())
EmitLayersChange();
}
// Update RTP seq number and timestamp based on NTP offset.
uint16_t seq;
uint32_t timestamp = packet->GetTimestamp() - this->tsOffset;
this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); //更新数据包序列号
// Save original packet fields.
auto origSsrc = packet->GetSsrc();
auto origSeq = packet->GetSequenceNumber();
auto origTimestamp = packet->GetTimestamp();
// Rewrite packet.
packet->SetSsrc(this->rtpParameters.encodings[0].ssrc);
packet->SetSequenceNumber(seq);
packet->SetTimestamp(timestamp);
// Process the packet.
if (this->rtpStream->ReceivePacket(packet))
{
// Send the packet.
this->listener->OnConsumerSendRtpPacket(this, packet);
}
// Restore packet fields.
packet->SetSsrc(origSsrc);
packet->SetSequenceNumber(origSeq);
packet->SetTimestamp(origTimestamp);
// Restore the original payload if needed.
packet->RestorePayload();
}
- 更详细的代码参考mediasoup,详见mediasoup-demo.