注:本文已收錄專(zhuān)利公般,版權(quán)所有。另外择份,部分段落縮減了部分內(nèi)容扣孟。
1. 背景
當(dāng)下移動(dòng)互聯(lián)網(wǎng)視頻直播正處于如火如荼的井噴式發(fā)展當(dāng)中,不同的行業(yè)(比如教育缓淹、醫(yī)療哈打、旅游等)都涉足參與進(jìn)來(lái),企圖在這個(gè)市場(chǎng)上占有一席之地讯壶,爭(zhēng)當(dāng)獨(dú)角獸式的公司料仗。傳統(tǒng)的直播,大多數(shù)是單向型伏蚊,比如電視臺(tái)或者運(yùn)營(yíng)商直播立轧,用戶只需要打開(kāi)終端收看即可,對(duì)于實(shí)時(shí)性并沒(méi)有太大的要求躏吊。而移動(dòng)直播往往在功能上需要主播端和播放端有交互氛改,這種交互不限于文字的互動(dòng),視頻與語(yǔ)音的互動(dòng)也日趨成為基本的交互場(chǎng)景需求比伏,傳統(tǒng)的做法是主播端采集視頻數(shù)據(jù)并編碼成x264胜卤、采集音頻數(shù)據(jù)并編碼成aac,再合并打包后通過(guò)一定的Qos算法將音視頻流數(shù)據(jù)通過(guò)rtmp協(xié)議(基于TCP協(xié)議之上)推流到CDN服務(wù)器進(jìn)行分發(fā)赁项,用戶端從CDN服務(wù)器拉流解碼來(lái)播放葛躏,這種方式的延時(shí)表現(xiàn)在rtmp推流到CDN和播放器從CDN通過(guò)rtmp拉流緩存播放,整個(gè)網(wǎng)絡(luò)鏈路的延遲通常在1-3秒或者更差悠菜。在網(wǎng)絡(luò)不穩(wěn)定的情況下舰攒,通常提高用戶觀看體驗(yàn)的方式是通過(guò)在主播端和播放端設(shè)置Gop緩存,讓碼率均勻悔醋;另外可以在主播端通過(guò)Qos算法檢測(cè)變化的網(wǎng)絡(luò)來(lái)動(dòng)態(tài)改變碼率和幀率摩窃;還可以接入多個(gè)視頻云服務(wù)CDN提供商,這樣可以做推拉流線路互備芬骄,對(duì)推流后視頻服務(wù)集群再優(yōu)化并根據(jù)端點(diǎn)網(wǎng)絡(luò)狀況做實(shí)時(shí)線路切換猾愿。另外一種方式是通過(guò)http HLS的方式進(jìn)行傳輸鹦聪,這種切片式的直播方式延遲更大,不適合高互動(dòng)式的場(chǎng)景直播蒂秘。
所以本文提出了一種基于UDP方式的視頻傳輸方案(webrtc層外)椎麦,旨在服務(wù)于低延遲高實(shí)時(shí)互動(dòng)式的直播場(chǎng)景,這種方式能將延遲控制在800ms人眼可接收范圍內(nèi)材彪,在網(wǎng)絡(luò)帶寬比較差或者強(qiáng)丟包观挎、亂序的情況下,通過(guò)緩存機(jī)制段化、帶寬自適應(yīng)檢測(cè)機(jī)制實(shí)時(shí)匯報(bào)給編碼器降低碼率嘁捷、分辨率、幀率显熏,能可靠有效的將實(shí)時(shí)畫(huà)面完整的播放出來(lái)雄嚣,提高了用戶體驗(yàn)。
2 視頻傳輸流程
2.1 視頻傳輸時(shí)序圖
本方案采用基于udp協(xié)議的報(bào)文傳輸喘蟆,設(shè)計(jì)了一套私有的報(bào)文格式缓升,以使得整個(gè)報(bào)文在網(wǎng)絡(luò)鏈路中傳輸簡(jiǎn)單可靠并可控,相對(duì)于TCP傳輸?shù)姆绞皆坦欤蟠蟮慕档土藞?bào)文的大小與復(fù)雜度港谊,保證了整個(gè)傳輸過(guò)程的實(shí)時(shí)性。如下圖1為視頻傳輸?shù)臅r(shí)序圖橙弱。
2.2 關(guān)鍵技術(shù)
本方案視頻編碼采用h264的形式(也可以是h265歧寺,負(fù)載數(shù)據(jù)是不受傳輸模塊限制的,可以在兩端進(jìn)行適配和協(xié)調(diào))棘脐,因?yàn)锽幀是雙向預(yù)測(cè)幀斜筐,它需要根據(jù)后向視頻幀來(lái)預(yù)測(cè)編碼,一定程度上會(huì)增大編解碼延遲蛀缝,所以為了保證傳輸和播放的實(shí)時(shí)性顷链,本方案視頻壓縮丟棄B幀編碼。
2.2.1 發(fā)送端處理
2.2.1.1 視頻切片算法
當(dāng)編碼器編碼出一幀完整的h264視頻幀數(shù)據(jù)時(shí)送入發(fā)送端屈梁,對(duì)于高分辨率的視頻編碼幀嗤练,幀大小往往高于UDP網(wǎng)絡(luò)MTU,所以此時(shí)需要發(fā)送端對(duì)其進(jìn)行分片處理再發(fā)送俘闯,每次按照分片單位來(lái)發(fā)送幀塊數(shù)據(jù)潭苞。
視頻編碼幀的最大分片數(shù)SMAX為500忽冻,規(guī)定單個(gè)分片字節(jié)大小SEGS為800bytes (根據(jù)探測(cè)MTU可動(dòng)態(tài)計(jì)算調(diào)整)真朗。當(dāng)幀字節(jié)大小FS小于SEGS+50時(shí)只分為一個(gè)分片;否則僧诚,整數(shù)倍的分片數(shù)S為FS/SEGS遮婶,超出的最后字節(jié)數(shù)FS1為FS%SEGS蝗碎,如果FS1大于50,則再單獨(dú)分一個(gè)分片旗扑,此時(shí)總分片數(shù)為S+1蹦骑,否則如果FS1小于50并且大于0時(shí),將FS1字節(jié)累加放入最后一個(gè)分片中臀防,此時(shí)總分片數(shù)為S眠菇。如下圖2為視頻分片結(jié)構(gòu)與流程。
2.2.1.2 發(fā)送端滑動(dòng)窗口
發(fā)送緩存區(qū)保存著所有正在發(fā)送且沒(méi)有收到接收方連續(xù)seq確認(rèn)acked的報(bào)文袱衷。當(dāng)收到peer端發(fā)來(lái)的分片的ack信息(攜帶一系列丟包的分片seq和已經(jīng)ack并連續(xù)處理到的seq)時(shí)捎废,發(fā)送端從發(fā)送緩存中獲取對(duì)應(yīng)的那些丟包seq并重發(fā)分片(如下圖的seq 10/12分片),同時(shí)從緩存中刪除區(qū)間[s1+1, s2]中的分片致燥,同時(shí)移動(dòng)滑動(dòng)窗口登疗。如下圖3為發(fā)送端滑動(dòng)窗口滑動(dòng)過(guò)程圖。
2.2.1.3 發(fā)送端帶寬自適應(yīng)調(diào)整算法
對(duì)于網(wǎng)絡(luò)的不可預(yù)測(cè)性嫌蚤,可能出現(xiàn)抖動(dòng)辐益、擁塞或者很多的丟包,如果按照固定的碼率和參數(shù)來(lái)發(fā)送視頻幀脱吱,這會(huì)導(dǎo)致發(fā)送端與接收端的線路間更擁塞智政,從而使觀看端出現(xiàn)更多的播放延遲或者馬賽克。所以在發(fā)送端需要做帶寬的實(shí)時(shí)估算來(lái)探測(cè)網(wǎng)絡(luò)情況箱蝠,從而便于實(shí)時(shí)根據(jù)網(wǎng)絡(luò)帶寬來(lái)調(diào)整上層視頻編碼器的幀率或者碼率女仰。
在發(fā)送端設(shè)置了一個(gè)定時(shí)器,每10秒鐘(可配置)做一次帶寬統(tǒng)計(jì)抡锈。rtt修正值(網(wǎng)絡(luò)抖動(dòng)的時(shí)間差值)為rtt_var疾忍,期望目標(biāo)帶寬為dst_bw,當(dāng)前發(fā)送的幀分片時(shí)間為cur_ts,最后acked的幀分片時(shí)間為acked_ts床三,則
delay_ts_delta = cur_ts–acked_ts
當(dāng)前單位時(shí)間內(nèi)的acked帶寬為bw一罩,則帶寬抖動(dòng)修正值acked_bw為:
acked_bw = (acked_bw * 3 + bw) / 4
如果當(dāng)前有包在重發(fā),且 delay _ts_delta 大于 8 * MAX(rtt + rtt_var, 100)撇簿,則dst_bw = acked_bw聂渊,向下降低調(diào)整視頻編碼器的分辨率、幀率或碼率來(lái)保證播放的實(shí)時(shí)性和流暢性四瘫;否則如果acked_bw 大于0,則dst_bw = acked_bw * 9 / 8汉嗽,向上提高調(diào)整視頻編碼器的分辨率、幀率或碼率來(lái)恢復(fù)清晰度和提高播放體驗(yàn)找蜜。
2.2.1.4 過(guò)期幀丟棄策略
在網(wǎng)絡(luò)擁塞時(shí)可能發(fā)送窗口緩沖區(qū)中有很多正在發(fā)送中的分片報(bào)文饼暑,為了緩解擁塞和減少延遲會(huì)對(duì)整個(gè)緩沖區(qū)做檢查,如果有超過(guò)一定閾值時(shí)間的GOP 幀存在,則會(huì)將這個(gè) GOP 內(nèi)的所有幀的分片從窗口緩沖區(qū)移除弓叛,并將它的下一個(gè) GOP 的 I 幀 fid和分片seq 通過(guò) syn 協(xié)議同步到各個(gè)接收端上彰居,接收端接收到此協(xié)議,會(huì)將最新連續(xù) seq 設(shè)置成同步過(guò)來(lái)的 seq撰筷。如果頻繁出現(xiàn)過(guò)期幀丟棄處理則會(huì)造成一定程度上的播放卡頓陈惰,此時(shí)說(shuō)明當(dāng)前網(wǎng)絡(luò)不適合傳輸高分辨率或高幀率的視頻,可以通知上層視頻編碼器設(shè)置為更小的分辨率或幀率毕籽。
2.2.2 接收端處理
2.2.2.1 接收端收包處理
接收端收到服務(wù)器中轉(zhuǎn)過(guò)來(lái)的syn消息(攜帶用戶uid抬闯、開(kāi)始分片序列號(hào)start_seq、幀率)后关筒,根據(jù)uid查找(或分配)對(duì)應(yīng)的發(fā)送者并激活画髓,同時(shí)根據(jù)start_seq更新已經(jīng)連續(xù)接收到的分片序列號(hào)base_seq和當(dāng)前接收到的分片最大序列號(hào)max_seq。
接收端第一幀必須是關(guān)鍵幀(即一個(gè)完整GOP的開(kāi)始)平委,如果是其他幀則丟棄奈虾,直到出現(xiàn)關(guān)鍵幀為止,因?yàn)槿绻谝粠荘幀將出現(xiàn)花屏現(xiàn)象廉赔。
每收到一個(gè)分片肉微,如果此分片的seq小于base_seq或者幀fid小于已經(jīng)接收到的最小幀min_fid(已經(jīng)接收過(guò)了)或者分片的seq大于max_seq+2000(太大的跳變導(dǎo)致丟包緩存太大),則丟棄蜡塌;否則碉纳,當(dāng)收到第一個(gè)關(guān)鍵幀的第一個(gè)片段時(shí),記下此時(shí)max_seq和base_seq馏艾,將此分片放入對(duì)應(yīng)幀fid的分片緩存區(qū)劳曹,同時(shí)計(jì)算單位時(shí)間內(nèi)的幀間隔時(shí)長(zhǎng),并更新丟包緩存表琅摩,如果此分片的seq和前一個(gè)已接收分片的seq連續(xù)铁孵,則更新base_seq為此分片的seq,再更新max_seq為MAX(max_seq, seq)房资,最后發(fā)送ack給peer端蜕劝。
如下圖5為接收端收到幀分片并存儲(chǔ)到緩存區(qū)的一個(gè)實(shí)例,對(duì)于一個(gè)完整的幀fid1的分片序列為區(qū)間seq [1, 8]轰异,幀fid2的分片序列為區(qū)間seq [9, 20]岖沛。幀的分片seq總是單調(diào)遞增的。
2.2.2.2 更新丟包緩存區(qū)策略
如下圖所示搭独,接收端已經(jīng)連續(xù)收到了seq [1, 5] 的分片包婴削,此時(shí)base_seq 和 max_seq 都是5,當(dāng)接收到下一個(gè)分片包seq 10時(shí)牙肝,將seq 10從丟包緩存中刪除唉俗,此時(shí)認(rèn)為 seq [6, 9] 是暫時(shí)丟失的(可能亂序不一定真丟失嗤朴,需要后續(xù)的包來(lái)確認(rèn)),如果丟包緩存中沒(méi)有此序號(hào)的丟包互躬,則將它們放入丟包緩存中,同時(shí)更新它們的丟包時(shí)間戳(當(dāng)前時(shí)間減去rtt值)颂郎,等待下一次的接收確認(rèn)吼渡。
2.2.2.3 接收端發(fā)送回應(yīng)ack策略
當(dāng)接收端每次收到peer端發(fā)來(lái)的分片,需要判斷是否發(fā)送回應(yīng)ack給peer端乓序,發(fā)送周期是10ms(毫秒)寺酪,小于10ms則不發(fā)送ack,發(fā)送太頻繁會(huì)導(dǎo)致網(wǎng)絡(luò)擁塞替劈;否則寄雀,獲取接收緩存中最老的一幀中最小的分片包序號(hào)min_seq,檢查接收端丟包緩存區(qū)陨献,并刪除區(qū)間[base_seq+1, min_seq]中的丟包盒犹,表明這些丟包已經(jīng)處理過(guò)了,同時(shí)設(shè)置滑動(dòng)窗口的base_seq為min_seq眨业,然后循環(huán)掃描丟包緩存區(qū)檢查當(dāng)前時(shí)間是否超過(guò)一個(gè)發(fā)送分片的rtt時(shí)間急膀,如果超過(guò)則累加此分片的丟包計(jì)數(shù)器,并更新丟包分片的時(shí)間為當(dāng)前時(shí)間龄捡,最后將所有超過(guò)rtt時(shí)長(zhǎng)的丟包發(fā)送ack回peer端卓嫂,最后計(jì)算播放緩存延遲時(shí)長(zhǎng)。
在幀緩存中會(huì)選擇性刪除一些播放過(guò)的幀分片聘殖,播放過(guò)的幀分片是不需要進(jìn)行重發(fā)的晨雳。
在接收端設(shè)置了一個(gè)定時(shí)器,每隔5ms(毫秒)也會(huì)檢測(cè)一次是否發(fā)送回應(yīng)ack給peer端奸腺,并掃描檢查和更新丟包緩存區(qū)餐禁。
2.2.3 獲取播放視頻幀處理
2.2.3.1 播放緩存區(qū)策略
在當(dāng)前播放端設(shè)置了一個(gè)幀緩存區(qū),如果緩存區(qū)過(guò)大時(shí)播放延遲就大突照,過(guò)小時(shí)又會(huì)出現(xiàn)播放卡頓情況坠宴。所以設(shè)置播放緩存區(qū)策略就至關(guān)重要,緩沖時(shí)間大小wait_ts應(yīng)該大于rtt + 2 * rtt_val绷旗,根據(jù)重發(fā)報(bào)文的次數(shù)來(lái)決定喜鼓,在接收端的計(jì)時(shí)器中定期檢查丟包數(shù)和rtt時(shí)長(zhǎng)來(lái)動(dòng)態(tài)的確定wait_ts的大小。
每次上層從播放緩存區(qū)獲取一幀時(shí)衔肢,內(nèi)部都會(huì)檢查當(dāng)前播放緩存區(qū)是處于可播放(max_fid>min_fid并且緩存區(qū)中最新幀(max_fid)的時(shí)間戳max_ts>wait_ts * 5 / 4庄岖,則更新可播放的絕對(duì)時(shí)間戳play_ts為當(dāng)前系統(tǒng)時(shí)間且cached_ts = max_ts - wait_ts * 5 / 4)中還是緩沖(max_fid=min_fid)中,如果在緩沖中則返回空數(shù)據(jù)角骤;否則同步更新播放緩沖幀時(shí)間戳隅忿,如果緩存中最久的一幀(min_fid)的所有分片已經(jīng)接收完整并且此幀時(shí)間F_Oldest_ts在播放緩存時(shí)間cached_ts內(nèi)心剥,則合并當(dāng)前幀分片返回給上層解碼播放,同時(shí)從緩存中刪除此幀背桐,并且更新min_fid為此幀的fid和當(dāng)前已經(jīng)緩存到的幀的時(shí)間戳cached_ts优烧。
此cached_ts的計(jì)算方式為:當(dāng) F_Oldest_ts + wait_ts * 5 / 4 >= max_ts(說(shuō)明緩存區(qū)幀很少)或者 min_fid + 1 = max_fid (說(shuō)明只剩一幀)時(shí),cached_ts = F_Oldest_ts链峭;否則cached_ts = max_ts - wait_ts * 5 / 4,說(shuō)明緩存區(qū)的幀足夠畦娄。