海康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是海康自己的一個自減計數值
- #pragma pack(1)
- union littel_endian_size
- {
- unsigned short int length;
- unsigned char byte[2];
- };
- struct pack_start_code
- {
- unsigned char start_code[3];
- unsigned char stream_id[1];
- };
- struct program_stream_pack_header
- {
- pack_start_code PackStart;// 4
- unsigned char Buf[9];
- unsigned char stuffinglen;
- };
- struct program_stream_map
- {
- pack_start_code PackStart;
- littel_endian_size PackLength;//we mast do exchange
- //program_stream_info_length
- //info
- //elementary_stream_map_length
- //elem
- };
- struct program_stream_e
- {
- pack_start_code PackStart;
- littel_endian_size PackLength;//we mast do exchange
- char PackInfo1[2];
- unsigned char stuffing_length;
- };
- #pragma pack()
- int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)
- {
- //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
- //通過 00 00 01 ba頭的第14個字節的最后3位來確定頭部填充了多少字節
- program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;
- unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';
- *leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//減去頭和填充的字節
- *NextPack = Pack+sizeof(program_stream_pack_header) + pack_stuffing_length;
- if(*leftlength<4) return 0;
- //printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);
- return *leftlength;
- }
- inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
- {
- //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
- program_stream_map* PSMPack = (program_stream_map*)Pack;
- //no payload
- *PayloadData = 0;
- *PayloadDataLen = 0;
- if(length < sizeof(program_stream_map)) return 0;
- littel_endian_size psm_length;
- psm_length.byte[0] = PSMPack->PackLength.byte[1];
- psm_length.byte[1] = PSMPack->PackLength.byte[0];
- *leftlength = length - psm_length.length - sizeof(program_stream_map);
- //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);
- if(*leftlength<=0) return 0;
- *NextPack = Pack + psm_length.length + sizeof(program_stream_map);
- return *leftlength;
- }
- inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
- {
- //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
- program_stream_e* PSEPack = (program_stream_e*)Pack;
- *PayloadData = 0;
- *PayloadDataLen = 0;
- if(length < sizeof(program_stream_e)) return 0;
- littel_endian_size pse_length;
- pse_length.byte[0] = PSEPack->PackLength.byte[1];
- pse_length.byte[1] = PSEPack->PackLength.byte[0];
- *PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;
- if(*PayloadDataLen>0)
- *PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;
- *leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);
- //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);
- if(*leftlength<=0) return 0;
- *NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;
- return *leftlength;
- }
- int inline GetH246FromPs(char* buffer,int length,CallbackHead& head, char **h264Buffer, int *h264length)
- {
- int leftlength = 0;
- char *NextPack = 0;
- *h264Buffer = buffer;
- *h264length = 0;
- if(ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength)==0)
- return 0;
- char *PayloadData=NULL;
- int PayloadDataLen=0;
- while(leftlength >= sizeof(pack_start_code))
- {
- PayloadData=NULL;
- PayloadDataLen=0;
- if(NextPack
- && NextPack[0]=='\x00'
- && NextPack[1]=='\x00'
- && NextPack[2]=='\x01'
- && NextPack[3]=='\xE0')
- {
- //接着就是流包,說明是非i幀
- if(Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))
- {
- if(PayloadDataLen)
- {
- memcpy(buffer, PayloadData, PayloadDataLen);
- buffer += PayloadDataLen;
- *h264length += PayloadDataLen;
- }
- }
- else
- {
- if(PayloadDataLen)
- {
- memcpy(buffer, PayloadData, PayloadDataLen);
- buffer += PayloadDataLen;
- *h264length += PayloadDataLen;
- }
- break;
- }
- }
- else if(NextPack
- && NextPack[0]=='\x00'
- && NextPack[1]=='\x00'
- && NextPack[2]=='\x01'
- && NextPack[3]=='\xBC')
- {
- if(ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen)==0)
- break;
- }
- else
- {
- //printf("[%s]no konw %x %x %x %x\n", __FUNCTION__, NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
- break;
- }
- }
- return *h264length;
- }
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),如果直接送入解碼器,有可能會出現問題,這里的處理方式要么丟棄這兩個部分,要么和之后的數據合起來,再送入解碼器里,如有遇到的朋友可以交流一下:)
轉自:http://blog.csdn.net/wwyyxx26/article/details/15224879