PS封装格式:GB28181协议RTP传输

news2024/11/20 13:30:08

      在安防行业,有个协议是无论如何都要适配的,因为公安监控网络用的就是它,它就是:GB28181。而这份协议主要由海康制定,所以除了海康其他厂商想要适配都会少许有点儿麻烦。

1. GB28181要求的RTP流格式
    首先,我们来看看I帧的PS流格式,这里需要注意的是SPS、PPS之前要加上PES头部。如下图所示,其中绿色部分就是我们拿到的H.264裸流数据,须将它拆分成三段并在前面加上PES头部。这一点在GB28181标准中没有细说,需要通过分析海康IPC流才能看出。

 一般情况下IDR帧很大,超过了RTP的负载长度限制(1400字节),所以上面这一个I帧要拆分成若干包RTP分多次发送。第一包的结构如上图所示,第二包以后RTP的结构就简单多了,它是这样的:

 上面提到的是I帧的情况,相比它,P/B帧的帧格式真是太简单了,因为它既没有SYS、PSM,也没有SPS、PPS:

 

P/B帧大小一般不超过1400字节,如果超过1400字节,也需分成多包RTP数据进行传输,超出1400部分的第二包RTP结构:

 

2. 头部信息

    首先是RTP包头信息,它一般长度为12个字节

#define RTP_HDR_LEN 12
static int gb28181_make_rtp_header(char *pData, int marker_flag, unsigned short cseq, long long curpts, unsigned int ssrc)
{
    bits_buffer_s      bitsBuffer;
    if (pData == NULL)
        return -1;
    bitsBuffer.i_size = RTP_HDR_LEN;
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data =    (unsigned char *)(pData);
    
    memset(bitsBuffer.p_data, 0, RTP_HDR_SIZE);
    bits_write(&bitsBuffer, 2, RTP_VERSION);    /* rtp version   版本号,固定为2  */
    bits_write(&bitsBuffer, 1, 0);                           /* rtp padding     */
    bits_write(&bitsBuffer, 1, 0);                           /* rtp extension     */
    bits_write(&bitsBuffer, 4, 0);                            /* rtp CSRC count */
    bits_write(&bitsBuffer, 1, (marker_flag));         /* rtp marker  结束标志位,一帧图像的最后一包RTP置1*/
    bits_write(&bitsBuffer, 7, 96);                  /* rtp payload type,96代表PS*/
    bits_write(&bitsBuffer, 16, (cseq));            /* rtp sequence      */
    bits_write(&bitsBuffer, 32, (curpts));         /* rtp timestamp      */
    bits_write(&bitsBuffer, 32, (ssrc));         /* rtp SSRC          */
    return 0;
}

 然后是PSH头部,它占了14个字节:

#define PS_HDR_LEN  14
static int gb28181_make_ps_header(char *pData, unsigned long long s64Scr)
{
    unsigned long long lScrExt = (s64Scr) % 100;    
    s64Scr = s64Scr / 100;
    bits_buffer_s      bitsBuffer;
    bitsBuffer.i_size = PS_HDR_LEN;    
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data =    (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PS_HDR_LEN);
    bits_write(&bitsBuffer, 32, 0x000001BA);            /*start codes 起始码*/
    bits_write(&bitsBuffer, 2,     1);                        /*marker bits '01b'*/
    bits_write(&bitsBuffer, 3,     (s64Scr>>30)&0x07);     /*System clock [32..30]*/
    bits_write(&bitsBuffer, 1,     1);                        			/*marker bit*/
    bits_write(&bitsBuffer, 15, (s64Scr>>15)&0x7FFF);   /*System clock [29..15]*/
    bits_write(&bitsBuffer, 1,     1);                        		/*marker bit*/
    bits_write(&bitsBuffer, 15, s64Scr & 0x7fff);        /*System clock [14..0]*/
    bits_write(&bitsBuffer, 1,     1);                        /*marker bit*/
    bits_write(&bitsBuffer, 9,     0);                     /*SCR extension*/
    bits_write(&bitsBuffer, 1,     1);                        /*marker bit*/
    bits_write(&bitsBuffer, 22, (255)&0x3fffff);        /*bit rate(n units of 50 bytes per second.)*/
    bits_write(&bitsBuffer, 2,     3);                        /*marker bits '11'*/
    bits_write(&bitsBuffer, 5,     0x1f);                    /*reserved(reserved for future use)*/
    bits_write(&bitsBuffer, 3,     0);                        /*stuffing length*/
    return 0;
}

后面是SYS,它包含了流类型信息,比如音频还是视频、视频编码格式

#define SYS_HDR_LEN 18
static int gb28181_make_sys_header(char *pData)
{    
    bits_buffer_s      bitsBuffer;
    bitsBuffer.i_size = SYS_HDR_LEN;
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data =    (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, SYS_HDR_LEN);
    /*system header*/
    bits_write( &bitsBuffer, 32, 0x000001BB);    /*start code*/
    bits_write( &bitsBuffer, 16, SYS_HDR_LEN-6);  /* 减6,是因为start code加上length这两位,占了6个字节*/
    bits_write( &bitsBuffer, 1,     1);            /*marker_bit*/
    bits_write( &bitsBuffer, 22, 50000);        /*rate_bound*/
    bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
    bits_write( &bitsBuffer, 6,  1);            /*audio_bound*/
    bits_write( &bitsBuffer, 1,  0);            /*fixed_flag */
    bits_write( &bitsBuffer, 1,  1);            /*CSPS_flag */
    bits_write( &bitsBuffer, 1,  1);            /*system_audio_lock_flag*/
    bits_write( &bitsBuffer, 1,  1);            /*system_video_lock_flag*/
    bits_write( &bitsBuffer, 1,  1);            /*marker_bit*/
    bits_write( &bitsBuffer, 5,  1);            /*video_bound*/
    bits_write( &bitsBuffer, 1,  0);            /*dif from mpeg1*/
    bits_write( &bitsBuffer, 7,  0x7F);         /*reserver*/
    /*audio stream bound*/
    bits_write( &bitsBuffer, 8,  0xC0);         /*stream_id 音频的流id*/
    bits_write( &bitsBuffer, 2,  3);            /*marker_bit */
    bits_write( &bitsBuffer, 1,  0);            /*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 512);          /*PSTD_buffer_size_bound*/
    /*video stream bound*/
    bits_write( &bitsBuffer, 8,  0xE0);         /*stream_id 视频的流id*/
    bits_write( &bitsBuffer, 2,  3);            /*marker_bit */
    bits_write( &bitsBuffer, 1,  1);            /*PSTD_buffer_bound_scale*/
    bits_write( &bitsBuffer, 13, 2048);         /*PSTD_buffer_size_bound*/
    return 0;
}

 接下来是PSM,这里面记录了媒体信息,比如音视频的编码格式:

static int gb28181_make_psm_header(char *pData)
{
    
    bits_buffer_s      bitsBuffer;
    bitsBuffer.i_size = PSM_HDR_LEN; 
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data =    (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PSM_HDR_LEN);//24Bytes
    bits_write(&bitsBuffer, 24,0x000001);    /*start code*/
    bits_write(&bitsBuffer, 8, 0xBC);        /*map stream id*/
    bits_write(&bitsBuffer, 16,18);            /*program stream map length*/ 
    bits_write(&bitsBuffer, 1, 1);            /*current next indicator */
    bits_write(&bitsBuffer, 2, 3);            /*reserved*/
    bits_write(&bitsBuffer, 5, 0);             /*program stream map version*/
    bits_write(&bitsBuffer, 7, 0x7F);        /*reserved */
    bits_write(&bitsBuffer, 1, 1);            /*marker bit */
    bits_write(&bitsBuffer, 16,0);             /*programe stream info length*/
    bits_write(&bitsBuffer, 16, 8);         /*elementary stream map length    is*/
    /*video*/
    bits_write(&bitsBuffer, 8, 0x1B);       /*stream_type 视频编码格式H.264*/
    bits_write(&bitsBuffer, 8, 0xE0);        /*elementary_stream_id*/
    bits_write(&bitsBuffer, 16, 0);         /*elementary_stream_info_length */
    /*audio*/
    bits_write(&bitsBuffer, 8, 0x90);       /*stream_type 音频编码格式G711*/
    bits_write(&bitsBuffer, 8, 0xC0);        /*elementary_stream_id*/
    bits_write(&bitsBuffer, 16, 0);         /*elementary_stream_info_length is*/
    /*crc (2e b9 0f 3d)*/
    bits_write(&bitsBuffer, 8, 0x45);        /*crc (24~31) bits*/
    bits_write(&bitsBuffer, 8, 0xBD);        /*crc (16~23) bits*/
    bits_write(&bitsBuffer, 8, 0xDC);        /*crc (8~15) bits*/
    bits_write(&bitsBuffer, 8, 0xF4);        /*crc (0~7) bits*/
    return 0;
}

 最后是PES头部,它记录了帧的时间戳,DTS可以不填,如果填写要和PTS保持一致,且同一帧数据的PTS要也要一样(即SPS、PPS、IDR的PES要一致):

#define PES_HDR_LEN 19
static int gb28181_make_pes_header(char *pData, int stream_id, int payload_len, unsigned long long pts, unsigned long long dts)
{
    
    bits_buffer_s      bitsBuffer;
    bitsBuffer.i_size = PES_HDR_LEN;
    bitsBuffer.i_data = 0;
    bitsBuffer.i_mask = 0x80;
    bitsBuffer.p_data =    (unsigned char *)(pData);
    memset(bitsBuffer.p_data, 0, PES_HDR_LEN);
    /*system header*/
    bits_write( &bitsBuffer, 24,0x000001);    /*start code*/
    bits_write( &bitsBuffer, 8, (stream_id));    /*streamID*/
    bits_write( &bitsBuffer, 16,(payload_len)+13);    /*packet_len pes剩余头部以及后面的es长度之和,比如SPS长度+13*/
    bits_write( &bitsBuffer, 2, 2 );        /*'10'*/
    bits_write( &bitsBuffer, 2, 0 );        /*scrambling_control*/
    bits_write( &bitsBuffer, 1, 1 );        /*priority*/
    bits_write( &bitsBuffer, 1, 1 );        /*data_alignment_indicator*/
    bits_write( &bitsBuffer, 1, 0 );        /*copyright*/
    bits_write( &bitsBuffer, 1, 0 );        /*original_or_copy*/
    bits_write( &bitsBuffer, 1, 1 );        /*PTS_flag 是否有PTS*/
    bits_write( &bitsBuffer, 1, 1 );        /*DTS_flag 是否有DTS信息*/
    bits_write( &bitsBuffer, 1, 0 );        /*ESCR_flag*/
    bits_write( &bitsBuffer, 1, 0 );        /*ES_rate_flag*/
    bits_write( &bitsBuffer, 1, 0 );        /*DSM_trick_mode_flag*/
    bits_write( &bitsBuffer, 1, 0 );        /*additional_copy_info_flag*/
    bits_write( &bitsBuffer, 1, 0 );        /*PES_CRC_flag*/
    bits_write( &bitsBuffer, 1, 0 );        /*PES_extension_flag*/
    bits_write( &bitsBuffer, 8, 10);        /*header_data_length*/ 
    
    /*PTS,DTS*/    
    bits_write( &bitsBuffer, 4, 3 );                    /*'0011'*/
    bits_write( &bitsBuffer, 3, ((pts)>>30)&0x07 );     /*PTS[32..30]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((pts)>>15)&0x7FFF);    /*PTS[29..15]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(pts)&0x7FFF);          /*PTS[14..0]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 4, 1 );                    /*'0001'*/
    bits_write( &bitsBuffer, 3, ((dts)>>30)&0x07 );     /*DTS[32..30]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,((dts)>>15)&0x7FFF);    /*DTS[29..15]*/
    bits_write( &bitsBuffer, 1, 1 );
    bits_write( &bitsBuffer, 15,(dts)&0x7FFF);          /*DTS[14..0]*/
    bits_write( &bitsBuffer, 1, 1 );
    return 0;
}

关于时间戳的比特位结构,下图表示的比较清晰:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ansible 进阶

模块应用 firewalld模块 可以配置防火墙策略 [rootcontrol ~]# vim ~/ansible/firewall.yml --- - hosts: test #hosts定义需要远程的主机tasks: #tasks定义需要执行哪些任务- name: install firewalld. …

ChatGPT已过时?Auto-GPT迅速走红,无需人类插手自主解决复杂任务,GitHub标星5万

来源: AI前线 微信号:ai-front 作者 | Luke Larsen ChatGPT 之所以能风靡全球,很大程度上要归功于其简单的功能框架。作为一款 AI 聊天机器人,它唯一的作用就是生成令人信服的自然语言文本、顺畅回应用户的提问。 但 AI 聊天机器人的使用体…

大数据分析案例-基于XGBoost算法预测航空机票价格

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 喜欢大数据分析项目的小伙伴,希望可以多多支持该系列的其他文章 大数据分析案例合集…

实在智能获评十大数字经济风云企业,2022余杭数字经济“群英榜”发布

4月17日,经专家评审、公开投票,由中共杭州市余杭区委组织部(区委两新工委)、中共杭州市余杭区经济和信息化局委员会主办评选的2022年度余杭区数字经济“群英榜”正式公示。其中,实在智能成功获评十大数字经济风云企业之…

cocoscreator性能优化4-Sprite颜色数据去除

前言 Sprite是游戏内容的一个基本组成元素,包括ui、道具、立绘等各种地方都会用到。大部分情况下美术会帮我们调好图片颜色,我们只要把图片直接放到游戏里就行了。Sprite默认的渲染顶点数据中包含了颜色数据,由于我们并不需要去修改颜色&…

【Python】AttributeError: ‘list‘ object has no attribute ‘corr‘

一、问题描述 在绘制相关分析热力图的时候: import seaborn as sns to_corr [Age, Income, Kidhome, Teenhome, Recency, Complain, MntWines, MntFruits, MntMeatProducts, MntFishProducts, MntSweetProducts, MntGoldProds, NumDealsPurchases, AcceptedCmp1,…

如何将 Spire.Doc for C++ 集成到 C++ 程序中

Spire.Doc for C 是一个专业的 Word 库,供开发人员在任何类型的 C 应用程序中阅读、创建、编辑、比较和转换 Word 文档。 本文演示了如何以两种不同的方式将 Spire.Doc for C 集成到您的 C 应用程序中。 通过 NuGet 安装 Spire.Doc for C通过手动导入库安装 Spire.…

tomcat配置虚拟主机

文章目录 tomcat配置虚拟主机环境修改tomcat主配置文件server.xml配置配置虚拟主机目录以及文件重新启动tomcat修改windows的hosts文件浏览器测试 tomcat配置虚拟主机 环境 java version “1.8.0_261”Server version: Apache Tomcat/8.5.20 修改tomcat主配置文件server.xml…

缺省参数-函数重载

缺省参数 缺省参数是声明或定义函数时为函数的参数指定一个默认值 有什么用? 更加灵活的增加默认值,或者手动给一个初始值,解决了C语言#define给死一个值的缺陷,C语言无法做到缺省参数这么灵活 struct Stack {int* a;int top;in…

北邮22信通:(13)第三章 3.4 串的实现 KMP算法

北邮22信通一枚~ 跟随课程进度每周更新数据结构与算法的代码和文章 持续关注作者 解锁更多邮苑信通专属代码~ 上一篇文章: 下一篇文章: ***说明*** 1.本代码结合书上第二章线性表和4.3.3KMP算法结合书写。 2.加快匹配速度的根本原因&#xff1…

行业分析| 新的学习方式——在线自习室

互联网技术在快速革新中不断推动新应用、新场景、新模式和新业态的发展融通,如近年来不断催生的游戏直播、直播带货、户外直播等网络生态。随着互联网用户逐年增多,年轻化趋势明显,互联网直播内容逐渐向生活化、日常化拓展,加之“…

Linux 防火墙常用命令

目录 前文叙述 Linux 找不到 firewall 命令 firewalld 常用管理命令 firewall-cmd 常用命令参数说明 前文叙述 Linux 防火墙默认为开启状态,生产环境下 Linux 防火墙也肯定是开启状态。因此在生产环境下往往是开放特定端口让外部进行连接使用。 Linux 找不到 fir…

数字化时代,企业为什么越来越重视数据分析

自数据成为第五大生产要素后,其价值得到了越来越多企业的认可,也成为了各行各业企业的重要的资产。而信息化建设在企业中的发展更是给了数据增长的机会,随着业务信息系统在企业中成为基础建设,众多企业都通过业务系统沉淀了大量业…

JSP的基本使用总结

JSP的基本使用总结 🏠个人主页:shark-Gao 🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉 🎉目前状况:23届毕业生,目前在…

海睿思分享 | 风控监管,守住企业生命线

1 企业如何应对市场风险 随着市场形势日益严峻,企业风险系数也在同步增加,一旦风险管理出现重大问题,将是致命的、灾难性的,可能导致企业出现生存危机。 风控监管的主要目的在于通过对各类风险进行识别、分析、监控&#xff0c…

【java web篇】MyBatis之Mapper代理

📋 个人简介 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜📝 个人主页:馆主阿牛🔥🎉 支持我:点赞👍收藏⭐️留言&#x1f4d…

MacOS版本RedisDesktopManager源码编译

1. 克隆 : git clone --recursive https://github.com/RedisInsight/RedisDesktopManager.git 查看依赖模块: git submodule 编译注意事件,部分源码的子仓库依赖没有下载完整的,要手动下载 brotli子模块 编译brotli子模块 $ mkdir out && cd out $ ../configure-c…

助力春耕:数智驱动现代农业高质量发展

最近有部很有意思的综艺《种地吧!少年》,节目内容就是十个少年要用192天的时间在142亩土地上,完成抢收水稻,抢种小麦,并在6月份完成小麦的收割,这样一个内容。 这个节目没有流量明星,而被吸引去…

MyBatis(十六)MyBatis使用PageHelper

一、limit分页 mysql的limit后面两个数字: 第一个数字:startIndex(起始下标。下标从0开始。) 第二个数字:pageSize(每页显示的记录条数) 假设已知页码pageNum,还有每页显示的记录…

K8s集群搭建-Kubeadm方式搭建集群【1.23.0版本】

文章目录 一、初始化准备二、安装kubeadm三、初始化Master集群四、将新的Node节点加入集群五、部署CNI网络插件六、其他配置 Kubernetes1.24(包括1.24)之后不在兼容docker,如果有需要兼容docker的需求,则安装一个 cri-docker的插件,本文使用的是kuberne…