WebRTC Video JitterBuffer
目錄
一. 前言
二. Video JitterBuffer架構
三. PacketBuffer
四. ReferenceFinder
五. FrameBuffer
一. 前言
????????音視頻傳輸通常使用 UDP,由于網絡中存在丟包,抖動,亂序等現象,接收端收到的媒體包需要有個包緩沖區(qū)存放,對于視頻而言,一幀數據可能被打包到多個 RTP 包傳輸,因此接收端收到 RTP 包后會判斷是否可以組成視頻幀,如果可以組成視頻幀還要判斷其參考幀是否存在,如果存在則將該幀送入幀緩沖區(qū),等待解碼線程進行解碼。
二. Video JitterBuffer架構
????????如上所示,RtpVideoStreamReceiver 是收視頻包的處理類,其中的 Video JitterBuffer 邏輯主要由 PacketBuffer,RtpFrameReferenceFinder 和 FrameBuffer 互相協(xié)作實現。
? ? ? ? PacketBuffer 是 RTP 包的緩沖區(qū),收到 RTP 包后根據序列號存放到環(huán)形數組的特定位置,如果某一幀對應的 RTP 包收集完整則彈出該幀的所有 RTP 包數據。
? ? ? ? RtpFrameReferenceFinder 用于幀參考關系的查找確認,例如對于 I 幀不需要參考其他幀就可以進行解碼,P 幀則需要前向參考,而 B 幀則需要前后雙向參考,因此某一幀的數據收集完整后,還需要等待其參考幀就緒才能送入幀緩沖區(qū)等待解碼。
? ? ? ? FrameBuffer 為幀緩沖區(qū),解碼線程會讀取該緩沖區(qū)的視頻幀數據進行解碼。
三. PacketBuffer
????????一個視頻幀在發(fā)送時可能被打包成多個 RTP 包,接收端接收時需要有個包級別的緩沖區(qū),該緩沖區(qū)等待接收幀的完整 RTP 包,然后提交給下個流程進行組幀等處理。
????????PacketBuffer 的實現代碼在 modules/video_coding/packet_buffer.h 和 modules/video_coding/packet_buffer.cc 中。
? ? ? ? PacketBuffer 類涉及的成員變量和方法如上所示,其中最重要的成員是?std::vector<std::unique_ptr<Packet>> buffer_,它是一個用于存放 Packet 的動態(tài)環(huán)形數組(起始大小為 512,最大為 2048),即接收到 RTP 包后根據其序列號將包存放到該環(huán)形數組的對應位置(index = seq_num % buffer_.size()),每次插入 RTP 包都會判斷緩沖區(qū)靠前的 RTP 包是否已經是某一視頻幀的完整數據,如果是則將這些 RTP 包帶在 InsertPacket 函數的返回值?InsertResult 中。
? ? ? ? 對于 PacketBuffer 最重要的方法為 InsertPacket,它首先計算 RTP 包存放在環(huán)形數組的位置,如果該位置當前有存放數據,則通過包序號判斷是否為重復包,是重復包則不做處理,如果不是重復包說明此時緩沖區(qū)已經不夠用了,需要調用 ExpandBuffer 擴容后再繼續(xù)不斷嘗試將包塞入緩沖區(qū)。
????????每次 InsertPacket 將 Packet 存放到 buffer_[index] 后會再調用 FindFrames(seq_nunm),它查找當前是否收到了幀的完整數據,如果是則將幀對應的完整 RTP 包返回存放到 result.packets 中,FindFrames 邏輯如下。
????????FindFrames 在 buffer_ 大小不為 0 的情況下,判斷插入 seq_num 的包后是否可能拿到幀的完整 RTP 包,如果不可能則先跳過,下次?InsertPacket 還會再調用 FindFrames 查看是否能拿到幀的完整 RTP 包。
PotentialNewFrame(uint16_t seq_num) 判斷的邏輯如下。
1. 如果是幀的第一個包,它是有可能湊成幀的完整數據的,一方面是有些非關鍵幀只需要一個 RTP 包,即便是需要多個 RTP 包的關鍵幀,seq_num 之后的包可能早就收齊存在于緩沖區(qū)了
2. 如果不是幀的第一個包,并且緩沖區(qū) index 位置的前一個位置是空數據,說明這個幀的數據肯定不完整,是不可能湊出幀完整數據的
3. 如果 index 位置的前一個位置的包序號不是 seq_num - 1,或者時間戳跟 seq_num 包的時間戳不一樣,說明它們是沒有關系的包
4. 如果 index 前一個位置的包的 continuous 為 true,說明?seq_num 對應的幀在 seq_num 之前的包已經是收齊連續(xù)的了,再收到該包是可能湊齊幀完整數據的
? ? ? ? 如果 PotentialNewFrame 為?true 并且 buffer_[index] 是幀的最后一個包,說明此時可以拿到幀對應的完整 RTP 包數據了。
? ? ? ? start_index 的值是從幀的最后一個包不斷往前遞減,如果遇到對應的位置是 is_first_packet_in_frame 說明幀的包數據已經從尾部遍歷到首部,此時只要把 [start_seq_num, end_seq_num) 位置的包都存放到 found_frames 中即可,start_seq_num 是從 seq_num 從后一直遍歷往前到幀的第一個包的。
至此 PacketBuffer 的收包存放以及當幀對應的 RTP 包完整時如何獲取到的流程已經介紹完成。?
四. ReferenceFinder
????????ReferenceFinder 的作用是判斷當前幀的參考幀是否存在,如果存在其參考幀則將當前幀送到 FrameBuffer,等待解碼線程對其進行解碼,如果不存在則暫存等待其參考幀到達后再把它送到 FrameBuffer。(例如對于 I 幀不需要參考其他幀,對于 P 幀需要前向參考,對于 B 幀需要前后雙向參考)
????????如果調用 PacketBuffer InsertPacket 能組成完整幀數據,則其返回的?struct InsertResult 值中會攜帶該幀對應的完整 Packet 數據,然后調用 OnInsertedPacket。
struct InsertResult {std::vector<std::unique_ptr<Packet>> packets;// Indicates if the packet buffer was cleared, which means that a key// frame request should be sent.bool buffer_cleared = false; }; OnInsertedPacket(packet_buffer_.InsertPacket(std::move(packet)));? ? ? ? OnInsertedPacket 邏輯如下,如果 result.packets 不為空則遍歷 result.packets?將 Packet 的負載數據存放到 payloads,把 Packet 的信息存放到 packet_infos,當遍歷到幀的最后一個包時會調用 video depacketizer 的 AssembleFrame 將 payloads 存放到 bitstream 中(bitstream 實際上是一個 uint8_t 數組),然后再調用 OnAssembledFrame。
????????RtpVideoStreamReceiver::OnAssembledFrame 邏輯如下,如果之前還沒收到過任何幀并且當前的幀也不是關鍵幀則進行關鍵幀請求(RequestKeyFrame),之后再判斷?current_codec_ 與 frame codec?是否一致或設置 current_codec_,然后再調用?video_coding::RtpFrameReferenceFinder 的 ManageFrame 方法查找當前 frame 對應的參考幀。
????????RtpFrameReferenceFinder::ManageFrame 邏輯如下,主要是調用 ManageFrameInternal 判斷當前?frame 是否有對應的參考幀,如果還沒找到參考幀則將 frame 暫時保存到 stashed_frames_ 中,如果當前 frame 已經找到參考幀則調用 HandOffFrame 處理將其送進 FrameBuffer,然后再把暫存的 frame 重新彈出查看它們是否能找到參考幀,如果找到參考幀則也送到 FrameBuffer 中。
????????關于如何查找?guī)瑢膮⒖紟欠翊嬖?#xff0c;VP8,VP9,H264 的判斷邏輯不相同,具體可以查看 ManageFrameVp8,ManageFrameVp9,ManageFrameH264,本文不對此進行展開。
五. FrameBuffer
? ? ? ? ReferenceFinder 查找到幀對應的參考幀后會將該幀送入 FrameBuffer,解碼線程會對幀進行解碼,HandOffFrame 函數調用 frame_callback_->OnCompleteFrame 將幀送入 FrameBuffer。
如下 VideoReceiveStream::OnCompleteFrame 中最關鍵的邏輯為 frame_buffer_->InsertFrame。
FrameBuffer 中最重要的成員為 frames_,它是一個 FrameMap 類型的對象,存儲 frameId 與 frame 的映射關系,FrameBuffer::InsertFrame 就是將 frame 插入到?FrameMap 中。
using FrameMap = std::map<VideoLayerFrameId, FrameInfo>;????????解碼邏輯如下所示,調用 frame_buffer_->NextFrame 取出 buffer 中的幀,然后調用 HandleEncodedFrame 進行解碼。
總結
以上是生活随笔為你收集整理的WebRTC Video JitterBuffer的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 出租车司机的帐
- 下一篇: mysql 多个主码_数据库中几个基本概