SRT如何做到實時傳輸
SRT 工作模式
SRT 工作模式有實時(LIVE)模式和文件(FILE)模式兩種僧凰。使用FILE模式還存在BUFFER API和MESSAGE API兩種發(fā)送接口禁添。
實時模式
實時模式用于傳送實時多媒體流捕发。
實時模式下,數(shù)據(jù)分片(默認是1316 = 7 * 188,188是單個MPEG TS大惺堋)在一定的速率控制下發(fā)出讶泰,并且在接收端按照發(fā)送端發(fā)送的時間間隔重新組織好。
默認情況下拂到,接收端重組會有一定的時延痪署,默認為120ms。
文件模式
實時模式具有一定的速率控制兄旬,而文件模式則是盡力而為的傳送方式狼犯。
Buffer API
Buffer API和我們平常使用的TCP socket接口類似,只要有足夠的緩存能夠存下這些數(shù)據(jù)领铐,接口就會將這些數(shù)據(jù)交付到SRT協(xié)議棧悯森。接收端也會盡力而為的接收數(shù)據(jù)。
Message API
Message API的特點是數(shù)據(jù)是存在邊界的绪撵。也就是說這不是一個“流式”的接口瓢姻,而是類似于UDP的存在報文邊界的接口。當沒有足夠的緩存存下整個消息時音诈,消息數(shù)據(jù)不會被發(fā)送到SRT協(xié)議棧幻碱。當整個消息沒有接收完畢時绎狭,接收接口也不會將消息交付上去。
編程接口
通過設(shè)置socket option選項來設(shè)置工作模式和編程接口模式褥傍。
工作模式設(shè)置
使用SRTO_TRANSTYP
選項來設(shè)置工作模式:
SRTT_LIVE
: Live模式儡嘶。此模式為默認的模式,用于實時流傳輸恍风。SRTT_FILE:
File模式蹦狂。File模式是“最快速”的數(shù)據(jù)傳輸方式,它在交付的時候沒有速率控制和整型朋贬。
消息接口設(shè)置
使用SRTO_MESSAGEAPI
來設(shè)置消息接口格式:
true
: 使用Message模式凯楔。消息模式意味著數(shù)據(jù)是有邊界的。在Live模式下默認使用該模式兄世。false
:使用Buffer模式啼辣。Buffer模式意味著只要有數(shù)據(jù)能交付則會盡力交付啊研。在File模式下使用該模式御滩。
生存時間
SRT允許丟棄那些已經(jīng)明確無法按照目標時間要求送達的數(shù)據(jù)報文。
編程接口
數(shù)據(jù)結(jié)構(gòu)
SRT_MSGCTRL
參數(shù):
? SRT_MSGCTRL
結(jié)構(gòu)體可以設(shè)置發(fā)送/接受數(shù)據(jù)的屬性党远,其中就包含數(shù)據(jù)的生存時間msgttl
-
msgttl
: 輸入型參數(shù)削解。消息最大生存時間,超時仍然未正確送達則將被丟棄沟娱。-1表示永不超時氛驮。只在發(fā)送端有意義。 -
inorder
: 輸入型參數(shù)济似。設(shè)置為true表示需要將亂序報文嚴格排序后再提交給應(yīng)用矫废。只在發(fā)送端有意義。 -
srctime
: 在發(fā)送端為輸入型參數(shù)砰蠢,在接收端為輸出型參數(shù)蓖扑。在發(fā)送報文中打上時間戳,0表示當前時間台舱。 -
pktseq
: 報文序列號律杠,只在接收端有意義。 -
msgno
: 輸出參數(shù)竞惋。SRT協(xié)議棧給此消息打上的消息列號柜去。
srt_sendmsg2
接口與srt_recvmsg2
接口支持SRT_MSGCTRL參數(shù)用于控制數(shù)據(jù)包的屬性。
使用方式
發(fā)送接口:
int srt_send(SRTSOCKET s, const char* buf, int len);
int srt_sendmsg(SRTSOCKET s, const char* buf, int len, int msgttl, bool inorder, uint64_t srctime);
int srt_sendmsg2(SRTSOCKET s, const char* buf, int len, SRT_MSGCTRL* msgctrl);
接收接口:
int srt_recv(SRTSOCKET s, char* buf, int len);
int srt_recvmsg(SRTSOCKET s, char* buf, int len);
int srt_recvmsg2(SRTSOCKET s, char* buf, int len, SRT_MSGCTRL* msgctrl);
發(fā)送示例:
nb = srt_sendmsg(u, buf, nb, -1, true);
nb = srt_send(u, buf, nb);
SRT_MSGCTL mc = srt_msgctl_default;
nb = srt_sendmsg2(u, buf, nb, &mc);
接收示例:
nb = srt_recvmsg(u, buf, nb);
nb = srt_recv(u, buf, nb);
SRT_MSGCTL mc = srt_msgctl_default;
nb = srt_recvmsg2(u, buf, nb, &mc);
實際上srt_send
拆宛、srt_sendmsg
最終都是通過調(diào)用srt_sendmsg2
接口實現(xiàn)發(fā)送嗓奢。
/// Request UDT to send out a data block "data" with size of "len".
/// @param data [in] The address of the application data to be sent.
/// @param len [in] The size of the data block.
/// @return Actual size of data sent.
SRT_ATR_NODISCARD int send(const char* data, int len)
{
return sendmsg(data, len, -1, false, 0);
}
/// send a message of a memory block "data" with size of "len".
/// @param data [out] data received.
/// @param len [in] The desired size of data to be received.
/// @param ttl [in] the time-to-live of the message.
/// @param inorder [in] if the message should be delivered in order.
/// @param srctime [in] Time when the data were ready to send.
/// @return Actual size of data sent.
int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, uint64_t srctime)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
mctrl.msgttl = msttl;
mctrl.inorder = inorder;
mctrl.srctime = srctime;
return this->sendmsg2(data, len, Ref(mctrl));
}
/// Receive a message to buffer "data".
/// @param data [out] data received.
/// @param len [in] size of the buffer.
/// @return Actual size of data received.
SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, ref_t<SRT_MSGCTRL> m);
srt_sendmsg2
函數(shù)流程如下:
st=>start: sendmsg2
e=>end
exception=>end: Throw exception
checkArgs=>operation: Check Transmit Parameters for Live/File Mode
conArgs=>condition: Check Result
st->checkArgs->conArgs
checkBuff=>operation: Check Buffer Enough for Message API
conBuff=>condition: Check Result
conArgs(yes)->checkBuff
conArgs(no)->exception
checkNeedDrop=>operation: Check Need Drop
checkBuff->conBuff
conBuff(yes)->checkNeedDrop
conBuff(no)->exception
sendBlock=>operation: Block Send if no enough buffer and in block mode
checkNeedDrop->sendBlock
addToSendBuf=>operation: Add to UDT Send Buffer with TTL
sendBlock->addToSendBuf
updateSendSocket=>operation: Update current socket to send socket list
addToSendBuf->updateSendSocket->e
數(shù)據(jù)最終調(diào)用CSndBuffer::addbuffer接口添加到CsndBuff中,并設(shè)置了該數(shù)據(jù)block的TTL浑厚。
CsndBuffer類具有一個worker線程用于將已添加的數(shù)據(jù)發(fā)送出網(wǎng)絡(luò)蔓罚,將buffer數(shù)據(jù)讀取并發(fā)送的函數(shù)為CsndBuffer::readData
椿肩,它會判斷當前TTL是否已經(jīng)超時,決定是否將該數(shù)據(jù)打包發(fā)送至網(wǎng)絡(luò)豺谈。
其調(diào)用流程如下:
worker=>start: CsndQueue::worker
pop=>operation: CSndUList::pop
pack=>operation: CUDT::packData
packLost=>operation: CUDT::packLostData
readData=>subroutine: CSndBuffer::readData
worker->pop->pack->packLost->readData
readData函數(shù)代碼如下:
int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, uint64_t& srctime, int& msglen)
{
CGuard bufferguard(m_BufLock);
Block* p = m_pFirstBlock;
// XXX Suboptimal procedure to keep the blocks identifiable
// by sequence number. Consider using some circular buffer.
for (int i = 0; i < offset; ++ i)
p = p->m_pNext;
// Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale.
// If so, then inform the caller that it should first take care of the whole
// message (all blocks with that message id). Shift the m_pCurrBlock pointer
// to the position past the last of them. Then return -1 and set the
// msgno_bitset return reference to the message id that should be dropped as
// a whole.
// After taking care of that, the caller should immediately call this function again,
// this time possibly in order to find the real data to be sent.
// if found block is stale
// (This is for messages that have declared TTL - messages that fail to be sent
// before the TTL defined time comes, will be dropped).
if ((p->m_iTTL >= 0) && ((CTimer::getTime() - p->m_ullOriginTime_us) / 1000 > (uint64_t)p->m_iTTL))
{
int32_t msgno = p->getMsgSeq();
msglen = 1;
p = p->m_pNext;
bool move = false;
while (msgno == p->getMsgSeq())
{
if (p == m_pCurrBlock)
move = true;
p = p->m_pNext;
if (move)
m_pCurrBlock = p;
msglen ++;
}
HLOGC(dlog.Debug, log << "CSndBuffer::readData: due to TTL exceeded, " << msglen << " messages to drop, up to " << msgno);
// If readData returns -1, then msgno_bitset is understood as a Message ID to drop.
// This means that in this case it should be written by the message sequence value only
// (not the whole 4-byte bitset written at PH_MSGNO).
msgno_bitset = msgno;
return -1;
}
*data = p->m_pcData;
int readlen = p->m_iLength;
// XXX Here the value predicted to be applied to PH_MSGNO field is extracted.
// As this function is predicted to extract the data to send as a rexmited packet,
// the packet must be in the form ready to send - so, in case of encryption,
// encrypted, and with all ENC flags already set. So, the first call to send
// the packet originally (the other overload of this function) must set these
// flags.
msgno_bitset = p->m_iMsgNoBitset;
srctime =
p->m_ullSourceTime_us ? p->m_ullSourceTime_us :
p->m_ullOriginTime_us;
HLOGC(dlog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send [REXMIT]");
return readlen;
}
從上面的分析可知郑象,報文生存時間檢查存在兩個階段,一個是sendmsg2接口中通過checkNeedDrop
函數(shù)檢查已緩存數(shù)據(jù)的時間跨度(最新添加的數(shù)據(jù)與最舊添加的數(shù)據(jù)時間差)是否超過閾值茬末,一個是在發(fā)送線程中檢查數(shù)據(jù)的TTL厂榛。
Live模式的實時性
SRT默認的模式是Live模式,也可以使用setsockopt的方式設(shè)置為Live模式丽惭。
Live/File模式的實質(zhì)是一系列屬性配置击奶,設(shè)置為Live模式的屬性配置表為:
case SRTT_LIVE:
// Default live options:
// - tsbpd: on
// - latency: 120ms
// - linger: off
// - congctl: live
// - extraction method: message (reading call extracts one message)
m_bOPT_TsbPd = true;
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
m_iOPT_PeerTsbPdDelay = 0;
m_bOPT_TLPktDrop = true;
m_iOPT_SndDropDelay = 0;
m_bMessageAPI = true;
m_bRcvNakReport = true;
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
m_Linger.l_onoff = 0;
m_Linger.l_linger = 0;
m_CongCtl.select("live");
這里涉及到了幾個參數(shù):
TsbPd
- Timestamp-Based Packet Delivery Mode。攜帶時間戳责掏,在接收端會依據(jù)此時間戳整型上交應(yīng)用柜砾。TsbPdDelay
、PeerTsbPdDelay
: Timestamp based Delay换衬,一個用于發(fā)送端痰驱,一個用于接收端。含義是從發(fā)送端發(fā)送出去的時間戳開始計算瞳浦,到接收端遞交給應(yīng)用最大時延担映, 默認是120ms。接收端在會等待delay時間到后才將數(shù)據(jù)遞交給應(yīng)用叫潦。這是因為接收端做整型的時候需要考慮時間波動的問題蝇完,引入此延遲可以獲得一個緩沖。注意這個值需要考慮RTT抖動矗蕊,重傳等因素短蜕。TLPktDrop
: 是否允許發(fā)送側(cè)丟包。SndDropDelay
: 發(fā)送側(cè)丟包所做的時延判定傻咖。如果緩存在發(fā)送隊列中的數(shù)據(jù)時間跨度大于max(SndDropDelay+PeerTsbPdDelay(120ms)+20ms, 1000ms)值則考慮丟包朋魔。