Darwin 流媒体服务器可以根据客户端BufferWindow的大小,动态控制发包的速度;客户端需要设定BufferWindow,并通过RTCP包定期将当前可用BufferWindow大小告诉服务器。
服务器端实现流程如下:
1.服务器端有个RTCPTask的任务会在TaskThread的线程里一直执行,接收客户端发送过来的RTCP包,并调用ProcessIncomingRTCPPacket将RTCP包交给相应的RTPStream类处理
RTPStream::ProcessIncomingRTCPPacket会对接收的RTCP进行解析,当解析的是RTCPAckPacket::kAckPacketName类型的包时,会调用ProcessCompressedQTSSPacket,具体代码如下:
RTPStream::ProcessCompressedQTSSPacket会解析该RTCP包并调用SetWindowSize,代码如下:
/*
QTSS APP: QTSS Application-defined RTCP packet
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| subtype | PT=APP=204 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC/CSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| name (ASCII) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ <---- app data start
| SSRC/CSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| version | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| field name='ob' other | version=0 | length=4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Over-buffer window size in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
fieldnames = rr, lt, ls, dl, :), :|, :(, ey, pr, pd, pl, bl, fr, xr, d#, ob
*/
Bool16 RTPStream::ProcessCompressedQTSSPacket(RTCPPacket &rtcpPacket, SInt64 &curTime, StrPtrLen ¤tPtr)
{
RTCPCompressedQTSSPacket compressedQTSSPacket;
UInt8* packetBuffer = rtcpPacket.GetPacketBuffer();
UInt32 packetLen = (rtcpPacket.GetPacketLength() * 4) + RTCPPacket::kRTCPHeaderSizeInBytes;
this->PrintPacketPrefEnabled((char*)packetBuffer, packetLen, RTPStream::rtcpAPP);
if (!compressedQTSSPacket.ParseAPPData((UInt8*)currentPtr.Ptr, currentPtr.Len))
return false;//abort if we discover a malformed app packet
fReceiverBitRate = compressedQTSSPacket.GetReceiverBitRate();
fAvgLateMsec = compressedQTSSPacket.GetAverageLateMilliseconds();
fPercentPacketsLost = compressedQTSSPacket.GetPercentPacketsLost();
fAvgBufDelayMsec = compressedQTSSPacket.GetAverageBufferDelayMilliseconds();
fIsGettingBetter = (UInt16)compressedQTSSPacket.GetIsGettingBetter();
fIsGettingWorse = (UInt16)compressedQTSSPacket.GetIsGettingWorse();
fNumEyes = compressedQTSSPacket.GetNumEyes();
fNumEyesActive = compressedQTSSPacket.GetNumEyesActive();
fNumEyesPaused = compressedQTSSPacket.GetNumEyesPaused();
fTotalPacketsRecv = compressedQTSSPacket.GetTotalPacketReceived();
fTotalPacketsDropped = compressedQTSSPacket.GetTotalPacketsDropped();
fTotalPacketsLost = compressedQTSSPacket.GetTotalPacketsLost();
fClientBufferFill = compressedQTSSPacket.GetClientBufferFill();
fFrameRate = compressedQTSSPacket.GetFrameRate();
fExpectedFrameRate = compressedQTSSPacket.GetExpectedFrameRate();
fAudioDryCount = compressedQTSSPacket.GetAudioDryCount();
// Update our overbuffer window size to match what the client is telling us
if (fTransportType != qtssRTPTransportTypeUDP)
{
// qtss_printf("Setting over buffer to %d\n", compressedQTSSPacket.GetOverbufferWindowSize());
fSession->GetOverbufferWindow()->SetWindowSize(compressedQTSSPacket.GetOverbufferWindowSize());
}
#ifdef DEBUG_RTCP_PACKETS
compressedQTSSPacket.Dump();
#endif
return true;
}
RTPOverbufferWindow::SetWindowSize设置客户端当前可用的BufferWindow的大小,并将计算上个发送周期内发送的字节数设为0,用来统计当前周期发送的字节数。
void RTPOverbufferWindow::SetWindowSize(UInt32 inWindowSizeInBytes)
{
fWindowSize = inWindowSizeInBytes;
fBytesSentSinceLastReport = 0;
}
// if the client is running low on memory, wait a while for it to be freed up
// there's nothing magic bout these numbers, we're just trying to be conservative
if ((fWindowSize != -1) && (inPacketSize * 5 > fWindowSize - fBytesSentSinceLastReport))
{
return inCurrentTime + (fSendInterval * 5); // client reports don't come that often
}
fWindowSize - fBytesSentSinceLastReport为当前周期内客户端BufferWindow还有多少可用的空间,当剩余的空间小于当前包大小的5倍时,返回五个发送间隔的延迟发送时间,这样就避免了由于服务端发包过快,客户端来不及处理导致的丢包。