SRT實時傳輸

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)用柜砾。
  • TsbPdDelayPeerTsbPdDelay: 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)值則考慮丟包朋魔。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市没龙,隨后出現(xiàn)的幾起案子铺厨,更是在濱河造成了極大的恐慌,老刑警劉巖硬纤,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件解滓,死亡現(xiàn)場離奇詭異,居然都是意外死亡筝家,警方通過查閱死者的電腦和手機洼裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溪王,“玉大人腮鞍,你說我怎么就攤上這事值骇。” “怎么了移国?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵吱瘩,是天一觀的道長。 經(jīng)常有香客問我迹缀,道長使碾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任祝懂,我火速辦了婚禮票摇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砚蓬。我一直安慰自己矢门,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布灰蛙。 她就那樣靜靜地躺著祟剔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缕允。 梳的紋絲不亂的頭發(fā)上峡扩,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天蹭越,我揣著相機與錄音障本,去河邊找鬼。 笑死响鹃,一個胖子當著我的面吹牛驾霜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播买置,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼粪糙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忿项?” 一聲冷哼從身側(cè)響起蓉冈,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轩触,沒想到半個月后寞酿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡脱柱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年伐弹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榨为。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡惨好,死狀恐怖煌茴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情日川,我是刑警寧澤蔓腐,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站龄句,受9級特大地震影響合住,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撒璧,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一透葛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卿樱,春花似錦僚害、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蹄胰,卻和暖如春岳遥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背裕寨。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工浩蓉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宾袜。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓捻艳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親庆猫。 傳聞我的和親對象是個殘疾皇子认轨,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容