PES,TS,PS,RTP等流的打包格式解析之TS流
上一篇描述了PES包頭的封裝格式,本篇描述一下TS包的封包格式
1.TS包頭格式
TS流,即傳輸流,是對PES包的進一步封裝,基本單位為TS包,固定每包大小為188位元組(或204位元組,在188位元組後加上16位元組的CRC校驗資料),由TS包頭和payload組成;其組成如下圖:
其中包頭由4個位元組的固定頭部和其後的adaptation field資料構成,位元組順序依次如下:
sync_byte:同步碼,其大小為固定8個bit,值為0x47;
transport_error_indicator:錯誤標誌位,佔位1bit,置為1表示此分組中至少有一個不可糾正的錯誤;
payload_unit_start_indicator
當TS包帶有PES包資料時,payload_unit_start_indicator具有以下的特點:置為1,標識TS包的有效淨荷以PES包的第一個位元組開始,即此TS包為PES包的起始包,且此TS分組中有且只有一個PES包的起始欄位;置為0,表示TS包不是PES包的起始包,是後面的資料包。
當 TS包帶有PSI資料時,payload_unit_start_indicator具有以下特點:置為1,表示TS
例如:若TS包載荷為PAT,則當接收到的TS包的payload_unit_start_indicator為1時,表明這個TS包包含了PAT頭資訊,從這個包裡面解析出
transport_priority:傳送優先順序,置1則表示此包比其他相同PID置0的包有高的優先順序,佔位1bit;
PID:指示有效負載中的資料型別,佔位13bit;0x0000代表PAT,0x0001代表CAT,0x0002-0x000F保留,0x1FFF表示空包;
transport_scrambling_control:有效負載加密模式標誌,佔位2bit,00表示未加密;
adaptation_field_control:調整欄位標誌,表示此TS首部是否跟隨調整欄位還是負載資料,佔位2bit,其中00位保留,01表示無調整欄位,只有有效負載資料,10表示只有調整欄位,無有效負載,11表示有調整欄位,且其後跟有有效負載;空分組此欄位應為01;
如果adaptation_field_control == 1x,表示後面跟有adaptation field欄位;
如果adaptation_field_control == x1,表示後面跟有data_bytes欄位;
continuity_counter:連續性計數,隨每一個相同PID的TS分組增加,達到最大值後又歸為0;佔位4bit,如果adaptation_field_control值為00或01,此值不應增加;若調整欄位的標誌位discontinuity_indicator值為1,則此值也不連續;
Adaptation field:調整欄位,只有當adaptation_field_control == 1x時,以下欄位才會存在,其中內容如下:
adaptation_field_length:調整欄位長度標示,標示此位元組後面調整欄位的長度,佔位8bit;值為0時,表示在TS分組中插入一個調整位元組,後面沒有調整欄位,緊跟著的是有效負載;adaptation_field_control == ‘11’時,此值在0~182之間,adaptation_field_control == ‘10’時,此值為183,若欄位沒這麼長則填充0xFF欄位;
後面的欄位都是在adaptation_field_length>0的時候才會出現,順序如下:
discontinuity_indicator:不連續狀態指示符,佔位1bit,置位1時表示此TS分組的不連續狀態為真;
random_access_indicator:隨機訪問指示符,佔位1bit;
elementary_stream_priority_indicator:原始流資料優先順序指示符,佔位1bit,置位1表示此原始流資料比相同PID的TS包中的其他原始流優先順序高;
PCR_flag:PCR標誌位,佔位1bit,置位1表示調整欄位中包含PCR欄位,置位0則沒有PCR欄位;
OPCR_flag:OPCR標誌位,佔位1bit,置位1表示調整欄位中包含OPCR欄位,置位0則沒有OPCR欄位;
splicing_point_flag:splice_countdown標誌位,佔位1bit,置位1表示調整欄位中包含splice_countdown欄位,置位0則沒有splice_countdown欄位;
transport_private_data_flag:transport_private_data標誌位,佔位1bit,置位1時表示調整欄位中含有1個或者多個私有資料位元組,置位0則無此位元組;
adaptation_field_extension_flag:調整欄位擴充套件標誌位,佔位1bit,置位1表示含有調整欄位擴充套件欄位,置位0則無擴充套件欄位;
以上8個bit是識別符號,後面是根據識別符號的值來確定的欄位,順序如下:
PCR欄位:當PCR_flag == 1時,此欄位才存在,佔位48bit,依次順序為:
program_clock_reference_base欄位:佔位33bit;
reserved欄位:佔位6bit;
program_clock_reference_extension欄位:佔位9bit;
OPCR欄位:當OPCR_flag == 1時,此欄位才存在,佔位48bit,依次順序為:
original_program_clock_reference_base欄位:佔位33bit;
reserved欄位:佔位6bit;
original_program_clock_reference_extension欄位:佔位9bit;
splice_countdown欄位:當splicing_point_flag == 1時此欄位存在,佔位8bit;
transport_private_data欄位:私有資料欄位,當transport_private_data_flag == 1時此欄位存在,佔位N*8bit,位元組順序為:
transport_private_data_length:表明私有資料的位元組長度,佔位8bit;
private_data_byte:私有資料,長度由前面的長度欄位確定;
adaptation_field_extension欄位:調整欄位擴充套件欄位,佔用長度不確定,當adaptation_field_extension_flag == 1時此欄位存在,欄位中也有3個標誌位,來確定一些欄位存不存在,其具體位元組順序如下:
adaptation_field_extension_length:調整欄位擴充套件欄位的長度,佔位8bit;
ltw_flag:ltw欄位標誌位,置位1時表示此欄位存在,佔位1bit;
piecewise_rate_flag:piecewise_rate欄位標誌位,置位1時此欄位存在,佔位1bit;
seamless_splice_flag:seamless_splice標誌位,置位1時此欄位存在,佔位1bit;
Reserved:保留欄位,佔位5bit;
Ltw欄位:當ltw_flag == 1時此欄位存在,佔位16bit,其由以下兩個欄位組成
ltw_valid_flag:佔位1bit,當ltw_valid_flag == 1時,ltw_offset才有效;
ltw_offset:佔位15bit;
piecewise_rate欄位:當piecewise_rate_flag == 1時此欄位存在,佔位24bit,其位元組順序如下:
reserved欄位:保留欄位,佔位2bit;
piecewise_rate欄位:佔位22bit;此欄位只有在當ltw_flag == 1和ltw_valid_flag == 1時才有定義,有定義時此欄位是一個正整數;
seamless_splice欄位:當seamless_splice_flag == 1時此欄位存在,佔位40bit;位元組順序依次為:
splice_type欄位:佔位4bit;標識delay和rate值;
DTS_next_AU[32..30]:佔位3bit;
marker_bit欄位:佔位1bit;
DTS_next_AU[29..15]欄位:佔位15bit;
marker_bit:佔位1bit;
DTS_next_AU[14..0]:佔位15bit;
marker_bit:佔位1bit;
stuffing_byte:填充欄位,固定為0xFF;
Payload_bytes:有效負載欄位,位元組來自PES包,PSI部分等;
總之,這些欄位就是一層套一層,一個欄位標誌位控制一個欄位,標誌位為1時,其標誌的欄位才會存在;
2.PSI程式特殊資訊表
TS包頭之後,就是負載payload的內容了,裡面可以是PES分組的資料,也可以是PSI資訊,PSI資訊主要由PAT,PMT,CAT等,在這裡主要介紹PAT和PMT兩種資訊表;由上所描述資訊可知,payload的型別是由PID來確定的,一般PID==0x0000則payload為PAT,PID== 0x0001,則payload為CAT,而PMT的PID則是在PAT中進行指定的;
PSI還有可能有一個特殊的欄位:
Point_field欄位:跟在包頭之後,佔位8bit,屬於有效負載,表示從此欄位開始到負載中PSI Section的第一個位元組之間的位元組數;當payload_unit_start_indicator == 1時,此欄位才存在;若point_field == 0x00,則表示此位元組後跟著的就是PSI Section的起始位元組;此欄位是在有效負載中的,計入有效負載的長度;
2.1.PAT:程式關聯表
PAT主要包含了節目編號和每一個節目對應的PMT的PID號碼,提供了節目編號和包含此節目定義資訊的TS分組(PMT分組)的PID之間的對應關係(一般我們的TS流中只有一個節目(頻道),所以PAT中一般只有一個PMT);
整個表由很多欄位組成;這個表可能會被分為多個分段section進行傳輸,即PAT可能會被分在多個TS包進行傳輸;PAT的資料分段section由以下欄位組成,依次順序為:
table_id欄位:標示PSI分段的內容,佔位8bit,當table_id == 0x00,表示此分段是PAT分段,當table_id == 0x01,表示此分段是CAT分段,當table_id == 0x02,表示此分段是PMT分段;在PAT中,id的值為0x00;
section_syntax_indicator欄位:佔位1bit,固定置位’1’;
‘0’欄位:佔位1bit;
Reserved欄位:保留欄位,佔位2bit;
section_length欄位:分段長度,佔位12bit,其中前2個bit固定為’00’,後10個bit表明了後面section欄位的長度,包括CRC的長度;
transport_stream_id欄位:TS流識別id,用於從網路中其他的多路複用中識別出此TS流,其值由使用者定義,佔位16bit;
Reserved:保留欄位,佔位2bit;
version_number欄位:整個PAT的版本號,佔位5bit;當PAT變化時,其值從0到31迴圈累加,當current_next_indicator == 1時,version_number為當前PAT的版本號,當current_next_indicator == 0時,其值為下一個PAT的版本號;
current_next_indicator欄位:指示符,佔位1bit,置位1時表示當前PAT有效,置位0時表示當前PAT無效,下一個PAT才有效;
section_number欄位:此分段Section的序號,佔位8bit,PAT的第一個分段Section的序號應該為0x00,將隨著PAT中的每一個分段累加1;
last_section_number欄位:PAT最後一個分段Section的序號,即最高序號值,佔位8bit;
Loop:
program_number欄位:佔位16bit,指明PMT可用的節目的編號;若program_number == 0x0000,則下一個參考PID是network PID,其他情況的值由使用者定義;在PAT的一個版本version_number中,這個值不能取某單個值多於一次(即一個PAT分段Section中只能有一個節目編號和其PMT的PID的對應關係);
Reserved:保留欄位,佔位3bit;
network_PID欄位:網路PID,佔位13bit,當program_number == 0x0000時,此欄位才存在;指明含有網路資訊表NIT的TS包的PID值;
program_map_PID欄位:佔位13bit,當program_number != 0x0000時此欄位存在,表示program_number所指明的節目可用的PMT分段的TS包的PID值;一個program_number不應有多個program_map_PID賦值,這個program_map_PID的值是由使用者定義的,不過不能取為其他目的而保留的值;
Loop end;
CRC_32欄位:CRC校驗值,佔位32bit;
2.2.PMT:節目對映表
PMT提供了節目編號和組成他們的節目原始流之間的對映關係,如果一個TS流中有多個節目,那個就會有多個PID不同的PMT表,在每個PMT中,都包含了節目原始流中不同的流型別TS包所對應的PID;即在PMT中,標識了當前節目中的視訊流,音訊流和與此節目相關的其他資料的TS包所對應的PID值;
同PAT一樣,PMT也可能被分為一個或多個Section分段進行傳輸,PMT由很多欄位組成,其欄位順序如下所示:
table_id欄位:標示PSI分段的內容,佔位8bit,在PMT中,固定為0x02;
section_syntax_indicator欄位:佔位1bit,固定置位’1’;
‘0’欄位:佔位1bit;
Reserved欄位:保留欄位,佔位2bit;
section_length欄位:分段長度,佔位12bit,其中前2個bit固定為’00’,後10個bit表明了後面section欄位的長度,包括CRC的長度;
program_number欄位:節目編號,佔位16bit,規定了此PMT所對應的節目編號,一個TS的PMT分段Section中,只能帶有一個節目定義;
Reserved欄位:保留欄位,佔位2bit;
version_number欄位:此PMT分段的版本號,佔位5bit,隨著此分段資訊的改變而累加1,直到31後再回到0迴圈;版本號對應於單個節目的定義,也就是對應於單個分段;當current_next_indicator == 1時,number值就是當前PMT分段的版本號,當current_next_indicator == 0時,number值位下一個可用PMT分段的版本號;
current_next_indicator欄位:指示符,佔位1bit,置位1時表示當前PMT分段有效,置位0時表示當前PMT分段無效,下一個PMT分段才有效;
section_number欄位:佔位8bit,固定為0x00;
last_section_number欄位:佔位8bit,固定為0x00;
Reserved欄位:保留欄位,佔位3bit;
PCR_PID欄位:PCR所在TS包的PID值,佔位13bit;表示由program_number所指明的節目中包含PCR欄位的TS包的PID值;如果一個私有流的節目定義無PCR與之相關,則這個欄位應置位0x1FFF;
Reserved欄位:保留欄位,佔位4bit;
program_info_length欄位:長度欄位,佔位12bit;前2bit固定為00,後10個bit指明瞭此欄位之後的descriptor的位元組數;
Descriptor欄位:節目描述資訊,長度由上一個欄位確定;
LOOP:
stream_type欄位:流型別欄位,佔位8bit;規定了由elementary_PID所指明的TS包的負載中的節目流的型別,即是視訊流還是音訊流或者其他資料;stream_type == 0x00是保留值;stream_type == 0x01和stream_type == 0x02是視訊;stream_type == 0x03和stream_type == 0x04是音訊;stream_type == 0x06是包含私有資料的PES包;
Reserved欄位:保留欄位,佔位3bit;
elementary_PID欄位:PID欄位,佔位13bit;指出攜帶相關原始流ES的TS包的PID值,即視訊包和音訊包等TS包的PID值;
Reserved欄位:保留欄位,佔位4bit;
ES_info_length欄位:長度欄位,佔位12bit;前2bit固定為00,後10個bit表示此欄位之後相關節目原始流ES的描述欄位長度;
Descriptor欄位:ES流描述資訊,長度由上一個欄位確定;
LOOP End;
CRC_32欄位:CRC校驗值,佔位32bit;
TS總結:TS解包流程就是現在TS包的包頭解出來PAT的PID,然後根據PID找到PAT,並從PAT中解出來每個節目所對應的PMT的PID,再根據PID找到所有節目的PMT,然後從每個節目的PMT中解出來當前節目所對應的不同流型別的TS包的PID,根據這些PID來找到對應的TS包,取出原始視訊流,音訊流和其他資料等;打包過程則是相反的;
TS頭裡面的PCR欄位是基準時間戳,在音視訊解碼顯示的時候,是根據PES頭裡面的PTS和DTS欄位與其對比,相同就說明該進行解碼和顯示了;PCR欄位是在TS的PMT中指定的PID,只有指定的PID的TS包裡面的PCR欄位才有用,我們打包的時候使用的是視訊的PID中的PCR,只有每幀的第一包TS頭裡面才會有PCR,而PES頭裡面的PTS和DTS就是視訊和音訊的相對時間戳;測試遇到了音視訊不同步的問題,原因就是TS打包時,PES頭裡面的音視訊PTS都用了視訊的時間戳,而我們在TS解析時是對音訊有相對延後的操作,其採用的視訊時間戳相對原來是有可能延後了多個視訊幀的,所以導致音訊有延後;
專案上TS流在IOS系統上面播放遇到坑:
1.今天除錯IOS系統播放TS流,發現了一個坑,IOS系統播放必須將視訊PES包頭中的Packet_Length欄位設定為0,音訊PES包的這個值必須不為0,否則IOS系統將無法播放TS流!!!
2.如果TS流裡面是隻有視訊沒有音訊的,那麼在封裝PMT的時候,一定不要放進去音訊的PID,否則IOS系統也播放不了- - (安卓各種順暢,IOS是各種坑啊,限制太多了ORZ...)