H264處理邏輯
整體邏輯分析
實現邏輯
- 解析 RTP 包頭:首先檢查 RTP 頭部的有效負載類型(
payloadType
)是否匹配 - 處理擴展頭:如果 RTP 包包含擴展頭,跳過擴展頭部分,獲取有效負載
- 處理分片數據:H264 分片數據通過
FU
指示符和FU
頭部來標識開始、中間和結束部分,分片數據會拼接并在結束時回調給外部 - 單一封包數據:如果是單一封包(非分片),直接將數據傳遞給回調函數
代碼實現
頭文件
// NALU頭部數據結構
struct H264NaluHeader {uint8_t type : 5;uint8_t nri : 2;uint8_t f : 1;
};
// 分片數據結構
struct H264FUIndicator {uint8_t type : 5;uint8_t nri : 2;uint8_t f : 1;};struct H264FUHeader {uint8_t type : 5;uint8_t r : 1;uint8_t e : 1; // 結束標志uint8_t s : 1; // 開始標志};class H264Demuxer : public RTPDemuxer {
public:void InputData(const uint8_t* data, size_t size);
private:uint8_t buffer_[4 * 1024 * 1024]; // 用于存儲拼接后的數據bool find_start_ = false; // 是否已經找到一個分片的起始部分size_t pos_buffer_ = 0; // 當前緩沖區的數據位置
};
源文件
// 總結:H264 數據進行解復用,處理分片(如果有)并將視頻數據傳遞給回調
void H264Demuxer::InputData(const uint8_t* data, size_t size) {//1. 解析RTP頭部struct RtpHeader *header = (struct RtpHeader *)data;int payload_type = header->payloadType;if (payload_type != payload_) {return;}//2. 提取有效的負載數據const uint8_t* payload = data + sizeof(struct RtpHeader);size_t payload_len = size - sizeof(struct RtpHeader);if (header->extension) {const uint8_t *extension_data = payload;size_t extension_length = 4 * (extension_data[2] << 8 | extension_data[3]);size_t payload_offset = 4 + extension_length;payload = payload + payload_offset;payload_len = payload_len - payload_offset;}//3. 處理分片FU數據struct H264NaluHeader *h264_header = (struct H264NaluHeader *)payload;if(h264_header->type == 28){//FU指示器struct H264FUIndicator *fu_indicator = (struct H264FUIndicator *)payload;//FU頭部struct H264FUHeader *fu_header = (struct H264FUHeader *)&payload[1];// 3.1 進一步處理分片數據,起始分片數據處理if (fu_header->s == 1) { // start//緩沖區存儲:0001+H264NaluHeaderfind_start_ = true;if (pos_buffer_ == 0) {struct H264NaluHeader header;header.f = fu_indicator->f;header.nri = fu_indicator->nri;header.type = fu_header->type;buffer_[0] = 0;buffer_[1] = 0;buffer_[2] = 0;buffer_[3] = 1;memcpy(buffer_ + 4, &header, sizeof(struct H264NaluHeader));pos_buffer_ += 4 + sizeof(struct H264NaluHeader);}memcpy(buffer_ + pos_buffer_, payload + 2, payload_len - 2);pos_buffer_ += payload_len - 2;}else if (fu_header->e == 1) { // endif (find_start_ == false) {return;}memcpy(buffer_ + pos_buffer_, payload + 2, payload_len - 2);pos_buffer_ += payload_len - 2;// 拼接結束后交給視頻處理器處理if (call_back_) {call_back_->OnVideoData(ntohl(header->timestamp), buffer_, pos_buffer_);}find_start_ = false;pos_buffer_ = 0;}else { // 中間分片if (!find_start_) {return;}memcpy(buffer_ + pos_buffer_, payload + 2, payload_len - 2);pos_buffer_ += payload_len - 2;} }
}
H264處理邏輯
整體邏輯分析
- 解析 RTP 包頭:首先檢查 RTP 包頭,判斷是否為我們關心的 H.265 視頻數據。
- 處理 RTP 擴展頭:如果 RTP 包中包含擴展頭,跳過擴展頭,獲取有效負載部分。
- 處理 H.265 分片數據:H.265 視頻數據可能被分成多個 RTP 包傳輸,使用 FU 頭部標識分片的開始、中間和結束部分。
H265Demuxer
將這些分片數據拼接成完整的視頻幀。 - 單一封包數據:如果數據不是分片,直接將完整的視頻幀數據通過回調傳遞給外部
代碼實現
頭文件
// H265 NALU頭部數據結構
struct H265NaluHeader {uint16_t layer_hi : 1;uint16_t type : 6; // NALU類型uint16_t f : 1; // 標志位uint16_t tid : 3; // 類型標識符uint16_t layer_low : 5;
};
// 分片數據結構
struct H265FUHeader {uint8_t type : 6; // NALU類型uint8_t e : 1; // 結束標志uint8_t s : 1; // 開始標志
};// H265解復用器
class H265Demuxer : public RTPDemuxer {
public:void InputData(const uint8_t* data, size_t size) override;
private:uint8_t buffer_[4 * 1024 * 1024]; // 用于存儲拼接后的數據bool find_start_ = false; // 是否已經找到一個分片的起始部分size_t pos_buffer_ = 0; // 當前緩沖區的數據位置
};
源文件
// 總結:H265 數據進行解復用,處理分片(如果有)并將視頻數據傳遞給回調
void H265Demuxer::InputData(const uint8_t* data, size_t size){//1. 解析RTP頭部struct RtpHeader *header = (struct RtpHeader *)data;int payload_type = header->payloadType;if (payload_type != payload_) {return;}//2. 提取有效的負載數據const uint8_t* payload = data + sizeof(struct RtpHeader);size_t payload_len = size - sizeof(struct RtpHeader);if (header->extension) {const uint8_t *extension_data = payload;size_t extension_length = 4 * (extension_data[2] << 8 | extension_data[3]);size_t payload_offset = 4 + extension_length;payload = payload + payload_offset;payload_len = payload_len - payload_offset;}//3. 處理分片FU數據struct H265NaluHeader *h265_header = (struct H265NaluHeader *)payload;if(h265_header->type == 49){//FU指示器struct H265FUHeader *fu_header = (struct H265FUHeader *)&payload[2];if (fu_header->s ==1){// 分片開始處理find_start_ = true;if(pos_buffer_ == 0){buffer_[0] = 0;buffer_[1] = 0;buffer_[2] = 0;buffer_[3] = 1;memcpy(buffer_ + 4, &header, sizeof(struct H265NaluHeader));pos_buffer_ += 4 + sizeof(struct H265NaluHeader);}memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3);pos_buffer_ += payload_len - 3;}else if(fu_header->e ==1){// 結束分片標志處理if(find_start_ == false){return;}memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3);pos_buffer_ += payload_len - 3;if (call_back_) {call_back_->OnVideoData(ntohl(header->timestamp), buffer_, pos_buffer_);}find_start_ = false;pos_buffer_ = 0;}else{// 中間分片處理if (!find_start_) {return;}memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3);pos_buffer_ += payload_len - 3;}}else{ // 單一封包buffer_[0] = 0;buffer_[1] = 0;buffer_[2] = 0;buffer_[3] = 1;memcpy(buffer_ + 4, payload, payload_len);if(call_back_){call_back_->OnVideoData(ntohl(header->timestamp), buffer_, payload_len + 4);}}return;
}