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+H264NaluHeader
find_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) { // end
if (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;
}