1. 程式人生 > >海康ps流轉換h264流

海康ps流轉換h264流

from:http://blog.csdn.net/wwyyxx26/article/details/15224879

海康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是海康自己的一個自減計數值 

  1. #pragma pack(1)  
  2. union littel_endian_size  
  3. {  
  4.     unsigned short int  length;  
  5.     unsigned char       byte[2];  
  6. };  
  7. struct pack_start_code  
  8. {  
  9.     unsigned char start_code[3];  
  10.     unsigned char stream_id[1];  
  11. };  
  12. struct program_stream_pack_header  
  13. {  
  14.     pack_start_code PackStart;// 4  
  15.     unsigned char Buf[9];  
  16.     unsigned char stuffinglen;  
  17. };  
  18. struct program_stream_map  
  19. {  
  20.     pack_start_code PackStart;  
  21.     littel_endian_size PackLength;//we mast do exchange  
  22.     //program_stream_info_length  
  23.     //info  
  24.     //elementary_stream_map_length  
  25.     //elem  
  26. };  
  27. struct program_stream_e  
  28. {  
  29.     pack_start_code     PackStart;  
  30.     littel_endian_size  PackLength;//we mast do exchange  
  31.     char                PackInfo1[2];  
  32.     unsigned char       stuffing_length;  
  33. };  
  34. #pragma pack()  
  35. int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)  
  36. {  
  37.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  38.     //通過 00 00 01 ba頭的第14個位元組的最後3位來確定頭部填充了多少位元組  
  39.     program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;  
  40.     unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';  
  41.     *leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//減去頭和填充的位元組  
  42.     *NextPack = Pack+sizeof(program_stream_pack_header) + pack_stuffing_length;  
  43.     if(*leftlength<4) return 0;  
  44.     //printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);  
  45.     return *leftlength;  
  46. }  
  47. inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)  
  48. {  
  49.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  50.     program_stream_map* PSMPack = (program_stream_map*)Pack;  
  51.     //no payload  
  52.     *PayloadData = 0;  
  53.     *PayloadDataLen = 0;  
  54.     if(length < sizeof(program_stream_map)) return 0;  
  55.     littel_endian_size psm_length;  
  56.     psm_length.byte[0] = PSMPack->PackLength.byte[1];  
  57.     psm_length.byte[1] = PSMPack->PackLength.byte[0];  
  58.     *leftlength = length - psm_length.length - sizeof(program_stream_map);  
  59.     //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);  
  60.     if(*leftlength<=0) return 0;  
  61.     *NextPack = Pack + psm_length.length + sizeof(program_stream_map);  
  62.     return *leftlength;  
  63. }  
  64. inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)  
  65. {  
  66.     //printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);  
  67.     program_stream_e* PSEPack = (program_stream_e*)Pack;  
  68.     *PayloadData = 0;  
  69.     *PayloadDataLen = 0;  
  70.     if(length < sizeof(program_stream_e)) return 0;  
  71.     littel_endian_size pse_length;  
  72.     pse_length.byte[0] = PSEPack->PackLength.byte[1];  
  73.     pse_length.byte[1] = PSEPack->PackLength.byte[0];  
  74.     *PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;  
  75.     if(*PayloadDataLen>0)   
  76.         *PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;  
  77.     *leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);  
  78.     //printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);  
  79.     if(*leftlength<=0) return 0;  
  80.     *NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;  
  81.     return *leftlength;  
  82. }  
  83. int inline GetH246FromPs(char* buffer,int length,CallbackHead& head, char **h264Buffer, int *h264length)  
  84. {  
  85.     int leftlength = 0;  
  86.     char *NextPack = 0;  
  87.     *h264Buffer = buffer;  
  88.     *h264length = 0;  
  89.     if(ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength)==0)  
  90.         return 0;  
  91.     char *PayloadData=NULL;   
  92.     int PayloadDataLen=0;  
  93.     while(leftlength >= sizeof(pack_start_code))  
  94.     {  
  95.         PayloadData=NULL;  
  96.         PayloadDataLen=0;  
  97.         if(NextPack   
  98.         && NextPack[0]=='\x00'   
  99.         && NextPack[1]=='\x00'   
  100.         && NextPack[2]=='\x01'   
  101.         && NextPack[3]=='\xE0')  
  102.         {  
  103.             //接著就是流包,說明是非i幀  
  104.             if(Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))  
  105.             {  
  106.                 if(PayloadDataLen)  
  107.                 {  
  108.                     memcpy(buffer, PayloadData, PayloadDataLen);  
  109.                     buffer += PayloadDataLen;  
  110.                     *h264length += PayloadDataLen;  
  111.                 }  
  112.             }  
  113.             else   
  114.             {  
  115.                 if(PayloadDataLen)  
  116.                 {  
  117.                     memcpy(buffer, PayloadData, PayloadDataLen);  
  118.                     buffer += PayloadDataLen;  
  119.                     *h264length += PayloadDataLen;  
  120.                 }  
  121.                 break;  
  122.             }  
  123.         }  
  124.         else if(NextPack   
  125.             && NextPack[0]=='\x00'   
  126.             && NextPack[1]=='\x00'  
  127.             && NextPack[2]=='\x01'  
  128.             && NextPack[3]=='\xBC')  
  129.         {  
  130.             if(ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen)==0)  
  131.                 break;  
  132.         }  
  133.         else  
  134.         {  
  135.             //printf("[%s]no konw %x %x %x %x\n", __FUNCTION__, NextPack[0], NextPack[1], NextPack[2], NextPack[3]);  
  136.             break;  
  137.         }  
  138.     }  
  139.     return *h264length;  
  140. }  

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),如果直接送入解碼器,有可能會出現問題,這裡的處理方式要麼丟棄這兩個部分,要麼和之後的資料合起來,再送入解碼器裡,如有遇到的朋友可以交流一下:)