从海康的ps流中提取h264数据_海康ps提取h264,h265-程序员宅基地

技术标签: 视频处理  h264  摄像头  海康  视频流  PS流  

海康7816使用ps流来封装h.264数据,这里使用的解码器无法识别ps流,因此需要将h264数据从ps流里提取出来

对于ps流的规定可以参考13818-1文档

 

这里从7816里获取到一些数据取样

00 00 01 BA 44 73 26 B8 34 01 00 00 03 FE FF FF 00 00 00 0100 00 01 BC00 5A E0 FF 00 24 40 0E 48 4B 00 01 0D AF C5 D3 E0 07 FF FF FF FF 41 12 48 4B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2C 1B E0 00 10 42 0E 00 00 A0 21 02 C0 02 40 12 1F FF 00 1C 21 91 C0 00 0C 43 0A 00 00 FE 00 7D 03 03 E8 03 FF BD BD 00 00 BF BF 00 00 00 00 00 0000 00 01 E0 00 1A 8C 80 0A 21 1C C9 AE 0D FF FF FF FF FC 00 00 00 01 67 42 00 1E 95 A8 2C 04 99 00 00 01 E0 00 0E 8C 0003 FF FF FC 00 00 00 01 68 CE 3C 80 00 00 01 E0 13 FA 8C 00 02 FF FD 。。。

如上是一个i帧的数据的开始部分,如下是一个非i帧的数据的开始部分

00 00 01 BA 44 73 27 99 34 01 00 00 03 FE FF FF 00 00 00 03 00 00 01 E0 07 12 8C 80 0A 21 1C C9 E6 4D FF FF FF FF F8。。。

可见都是以00 00 01 BA开头,这是ps的包头(Program Stream pack header),其中00 00 01是pack_start_code,是一个数据包的开始标识,接下来的1byte(BA)是流标识(stream_id),在文档13818-1的Table 2-33和2.5.3.4节有Program Stream pack header的描述。

这里把上面i帧的的(Program Stream pack header列出来

00 00 01 BA 44 73 26 B8 34 01 00 00 03 FE FF FF 00 00 00 01

根据文档描述包头最少有14个字节,第14个字节的最后3bit说明了包头14字节后填充数据的长度,这里是pack_stuffing_length=FE&0x07=6,有6byte的填充数据,既是FF FF 00 00 00 01,海康7816使用这部分填充数据来说明每帧的序号,01说明是第1帧数据。

要注意的是包头可能还有系统标题头,id为bb,他也是包头的一部分,并且,他的长度并未算在pack_stufing_length里,比如:

00 00 01 BB 00 0C 80 CC F5 04 E1 7F E0 E0 E8 C0 C0 20 

这里起始码后的 00 0C 说明了其后数据的长度,这里是12个字节

接在Program Stream pack header后的是以00 00 01 BC开始的一个包,00 00 01是pack_start_code,BC是stream_id流标识,说明跟在Program Stream pack header后的是Program Stream map。文档13818-1的Table 2-35和2.5.4.2节有Program Stream pack header的描述。

跟在00 00 01 BC后的两位是说明了Program Stream map,他也是pes包的一种,包的长度program_stream_map_length,这里是00 5A,说明跟在其后的数据长度为90,跳过这其后的90byte数据是以00 00 01 E0开始的包,E表示是GB/TXXXX.2或GB/TAAAA.2视频流编号xxxx规格的pes包了,0表示流id为0,h264数据就在这个包里。

从Program Stream map里我们还能得知pes里的流是何种流(stream_type和elementary_stream_id表明),以及帧率()等

1110XXXX(0xex)表示视频数据,111XXXXX表示audio数据,其后的帧有关信息共5字节,2字节PES包长度是00 1A,表示此PES数据包的长度是0x001a 即26字节;2字节标准位信息是8C 80,5字节中的最后一字节表示附加数据长度是0A,跟在附加数据长度后的就是视频数据负载了。

pes包可以有多个,这里的i帧就把数据放到了多个pes包里,这里的非i帧就只有一个pes包

有了以上信息就已经可以从7816里剥离出h246数据了,更详细的说明请参考文档。

截取一段pes包头进行分析

00 00 01 E0 00 1A 8C 80 0A 21 1C C9 AE 0D FF FF FF FF FC 
00 1A: 2字节表示长度
8C(10 00 1 1 00): 首先是固定值10,。
接下来的两位为(PES加扰控制字段)PES_scrambling_control,这里是00,表示没有加扰(加密)。剩下的01,10,11由用户自定义。
接下来第4位为PES优先级字段(PES_priority),当为1时为高优先级,0为低优先级。这里为1。
接下来第3位为(数据对齐指示符字段)PESdata_alignment_indicator,
接下来第2位为版权位,
接下来第1位为版权位,
80(10 000000):
首先是PTS,DTS标志字段,这里是10,表示有PTS,没有DTS。
接下来第6位是ESCR标志字段,这里为0,表示没有该段
接下来第5位是ES速率标志字段,,这里为0,表示没有该段
接下来第4位是DSM特技方式标志字段,,这里为0,表示没有该段
接下来第3位是附加版权信息标志字段,,这里为0,表示没有该段
接下来第2位是PES CRC标志字段,,这里为0,表示没有该段
接下来第1位是PES扩展标志字段,,这里为0,表示没有该段
0A(10):8个字节,指出包含在PES分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前的字节指出了有无可选字段(这里只有PTS)。
因为这里PTS,DTS标志字段是10,那就有5个字节的PTS段,就是这里的21 1C C9 AE 0D
最后的五个字节的FF FF FF FF FC是海康自己的一个自减计数值 



[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #pragma pack(1)  
  2.   
  3. union littel_endian_size  
  4. {  
  5.     unsigned short int  length;  
  6.     unsigned char       byte[2];  
  7. };  
  8.   
  9. struct pack_start_code  
  10. {  
  11.     unsigned char start_code[3];  
  12.     unsigned char stream_id[1];  
  13. };  
  14.   
  15. struct program_stream_pack_header  
  16. {  
  17.     pack_start_code PackStart;// 4  
  18.     unsigned char Buf[9];  
  19.     unsigned char stuffinglen;  
  20. };  
  21.   
  22. struct program_stream_map  
  23. {  
  24.     pack_start_code PackStart;  
  25.     littel_endian_size PackLength;//we mast do exchange  
  26.     //program_stream_info_length  
  27.     //info  
  28.     //elementary_stream_map_length  
  29.     //elem  
  30. };  
  31.   
  32. struct program_stream_e  
  33. {  
  34.     pack_start_code     PackStart;  
  35.     littel_endian_size  PackLength;//we mast do exchange  
  36.     char                PackInfo1[2];  
  37.     unsigned char       stuffing_length;  
  38. };  
  39.   
  40. #pragma pack()  
  41.   
  42. int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)  
  43. {  
  44.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  45.     //通过 00 00 01 ba头的第14个字节的最后3位来确定头部填充了多少字节  
  46.     program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;  
  47.     unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';  
  48.   
  49.     *leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//减去头和填充的字节  
  50.     *NextPack = Pack+sizeof(program_stream_pack_header) + pack_stuffing_length;  
  51.   
  52.     if(*leftlength<4) return 0;  
  53.   
  54.     //printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);  
  55.   
  56.     return *leftlength;  
  57. }  
  58.   
  59. inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)  
  60. {  
  61.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  62.       
  63.     program_stream_map* PSMPack = (program_stream_map*)Pack;  
  64.   
  65.     //no payload  
  66.     *PayloadData = 0;  
  67.     *PayloadDataLen = 0;  
  68.       
  69.     if(length < sizeof(program_stream_map)) return 0;  
  70.   
  71.     littel_endian_size psm_length;  
  72.     psm_length.byte[0] = PSMPack->PackLength.byte[1];  
  73.     psm_length.byte[1] = PSMPack->PackLength.byte[0];  
  74.   
  75.     *leftlength = length - psm_length.length - sizeof(program_stream_map);  
  76.   
  77.     //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);  
  78.       
  79.     if(*leftlength<=0) return 0;  
  80.   
  81.     *NextPack = Pack + psm_length.length + sizeof(program_stream_map);  
  82.   
  83.     return *leftlength;  
  84. }  
  85.   
  86. inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)  
  87. {  
  88.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  89.     program_stream_e* PSEPack = (program_stream_e*)Pack;  
  90.   
  91.     *PayloadData = 0;  
  92.     *PayloadDataLen = 0;  
  93.   
  94.     if(length < sizeof(program_stream_e)) return 0;  
  95.       
  96.     littel_endian_size pse_length;  
  97.     pse_length.byte[0] = PSEPack->PackLength.byte[1];  
  98.     pse_length.byte[1] = PSEPack->PackLength.byte[0];  
  99.   
  100.     *PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;  
  101.     if(*PayloadDataLen>0)   
  102.         *PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;  
  103.           
  104.     *leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);  
  105.   
  106.     //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);  
  107.   
  108.     if(*leftlength<=0) return 0;  
  109.   
  110.     *NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;  
  111.   
  112.     return *leftlength;  
  113. }  
  114.   
  115. int inline GetH246FromPs(char* buffer,int length,CallbackHead& head, char **h264Buffer, int *h264length)  
  116. {  
  117.     int leftlength = 0;  
  118.     char *NextPack = 0;  
  119.   
  120.     *h264Buffer = buffer;  
  121.     *h264length = 0;  
  122.   
  123.     if(ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength)==0)  
  124.         return 0;  
  125.   
  126.     char *PayloadData=NULL;   
  127.     int PayloadDataLen=0;  
  128.   
  129.     while(leftlength >= sizeof(pack_start_code))  
  130.     {  
  131.         PayloadData=NULL;  
  132.         PayloadDataLen=0;  
  133.           
  134.         if(NextPack   
  135.         && NextPack[0]=='\x00'   
  136.         && NextPack[1]=='\x00'   
  137.         && NextPack[2]=='\x01'   
  138.         && NextPack[3]=='\xE0')  
  139.         {  
  140.             //接着就是流包,说明是非i帧  
  141.             if(Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))  
  142.             {  
  143.                 if(PayloadDataLen)  
  144.                 {  
  145.                     memcpy(buffer, PayloadData, PayloadDataLen);  
  146.                     buffer += PayloadDataLen;  
  147.                     *h264length += PayloadDataLen;  
  148.                 }  
  149.             }  
  150.             else   
  151.             {  
  152.                 if(PayloadDataLen)  
  153.                 {  
  154.                     memcpy(buffer, PayloadData, PayloadDataLen);  
  155.                     buffer += PayloadDataLen;  
  156.                     *h264length += PayloadDataLen;  
  157.                 }  
  158.   
  159.                 break;  
  160.             }  
  161.         }  
  162.         else if(NextPack   
  163.             && NextPack[0]=='\x00'   
  164.             && NextPack[1]=='\x00'  
  165.             && NextPack[2]=='\x01'  
  166.             && NextPack[3]=='\xBC')  
  167.         {  
  168.             if(ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen)==0)  
  169.                 break;  
  170.         }  
  171.         else  
  172.         {  
  173.             //printf("[%s]no konw %x %x %x %x\n", __FUNCTION__, NextPack[0], NextPack[1], NextPack[2], NextPack[3]);  
  174.             break;  
  175.         }  
  176.     }  
  177.   
  178.       
  179.     return *h264length;  
  180. }  


ps:
 这篇文章回复私信挺多的,有的同学读了成功的获取了原始的h.264数据,有的同学反映和他们遇到的情况不一样,比如subi2008同学说他读出的流有00 00 01 c0标识的pes数据,这个其实是音频数据,还有遇到00 00 01 bd的,这个是私有流的标识,总之,ps流就解析大家可以参看ps,ts流的文档,里面的内容都有,表2-18里说明了所有的流标识。


ps:

另外,有的hk摄像头回调然后解读出来的原始h.264码流,有的一包里只有分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,这里的处理方式要么丢弃这两个部分,要么和之后的数据合起来,再送入解码器里,如有遇到的朋友可以交流一下:)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jisuanji_wjfioj/article/details/40430455

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签