当前位置: 首页 > news >正文

【RTSP】客户端(五)H264 265处理逻辑

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;
}

相关文章:

  • AI绘画笔记--基础知识
  • LeetCode 每日一题 2025/3/10-2025/3/16
  • 招聘信息|基于SprinBoot+vue的招聘信息管理系统(源码+数据库+文档)
  • 【Linux网络】HTTPS
  • 社交网络分析实战(NetworkX分析Twitter关系图)
  • 第十次CCF-CSP认证(含C++源码)
  • SpringBoot MCP 入门使用
  • Axios 请求取消:从原理到实践
  • XSS漏洞学习(1)
  • 无需 Docker 也能下载镜像!轻松获取 Docker 镜像文件!
  • uniapp scroll组件下拉刷新异步更新数据列表
  • spring-aop笔记
  • 【生日蛋糕——DFS剪枝优化】
  • Vue Date 今天的开始时间与结束时间
  • 【小沐学Web3D】three.js 加载三维模型(vue3)
  • HCIA-AI人工智能笔记1:大模型技术演进与发展历程
  • Jetson Nano NX 重装系统
  • 2024年12月CCF-GESP编程能力等级认证C++编程一级真题解析
  • Mysql查看执行计划、explain关键字详解(超详细)
  • 《Electron 学习之旅:从入门到实践》
  • 孟夏韵评《无序的学科》丨误读与重构的文化漂流
  • 当智慧农场遇见绿色工厂:百事如何用科技留住春天的味道?
  • 东部沿海大省浙江,为何盯上内河航运?
  • 全国人大常委会今年将初次审议检察公益诉讼法
  • 人民日报民生观:转人工客服,怎么这么难?
  • 盛和资源海外找稀土矿提速:拟超7亿元收购匹克,加快推动坦桑尼亚项目