參考博客:https://blog.csdn.net/rell336/article/details/38109621?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
一、TS協議
1.1 TS流和其他流的關系
1. ES(Elementary Stream,基本碼流)
- 定義:未經分段的原始媒體流,是連續的音頻、視頻或其他信息(如字幕)的原始數據序列。
- 特點:
- 不分段,以連續碼流形式存在。
- 是最底層的媒體數據,直接由編碼器生成(如H.264視頻流、AAC音頻流)。
- 不包含傳輸控制信息,無法直接用于傳輸。
2. PES(Packetized Elementary Stream,分組的基本碼流)
- 定義:將ES流分割為長度可變的數據包,并添加包頭后形成的流,用于承載ES流。
- 作用:為ES流添加傳輸所需的控制信息,實現ES流的分組化傳輸。
- 結構:
- PES包頭:包含時間戳(PTS/DTS)、數據長度、流類型等信息。
- PES有效載荷:分割后的ES流片段。
- 特點:
- 數據包長度可變,適合在可靠環境(如本地存儲)中使用。
3. TS(Transport Stream,傳輸流)
- 定義:由固定長度(188字節)的數據包組成,用于在不可靠環境(如廣播電視、網絡傳輸)中傳輸多個節目。
- 構成:
- 包含一個或多個節目,每個節目由音頻、視頻等ES流通過PES分組后封裝而成。
- 插入PSI/SI表(節目特定信息/服務信息),用于描述節目結構和服務信息。
- 關鍵特性:
- 固定包長:每個TS包為188字節(含4字節包頭),便于硬件快速處理。
- 獨立解碼:從流中任意位置均可開始解碼,容錯性強。
- PSI/SI重復傳輸:PSI/SI表按一定頻率重復發送,確保接收端能隨時獲取節目信息。
4. PS(Program Stream,節目流)
- 定義:用于傳輸單個節目,由可變長度數據包組成的流,適用于可靠存儲或傳輸環境(如DVD光盤)。
- 與TS流的核心區別:
- 包長度:PS包長度可變,TS包長度固定(188字節)。
- 應用場景:PS流適合本地存儲(如文件),TS流適合實時傳輸(如廣播電視)。
四者關系
- ES流是原始媒體數據(如視頻幀、音頻采樣)。
- PES流是ES流的分組形式,添加時間戳等控制信息。
- TS流/PS流是PES流的進一步封裝:
- TS流:固定包長,含多節目和PSI/SI,適合傳輸。
- PS流:可變包長,單節目,適合存儲。
簡單來說,ES→PES→TS/PS的過程,是媒體數據從原始形式到可傳輸/存儲形式的封裝鏈。
1.2 TS包
TS包的?度:188 B或204 B,204 B?度是在188B后?增加了16 B的CRC校驗數據。
-
sync_byte: 1B,固定值0x47,TS包的標識符,正常的TS包在0x47的包頭標識符往后188/204B之后仍然是0x47【下?個TS包的標識符】
-
transport_error_Indicator: 1bit,當其為1時,表示該TS包中?少有?個不可糾正的錯誤位,只有在錯誤糾正之后,該位才能重新置0【實際獲取TS包之后,該位為1的包丟棄】
-
payload_unit_start_indicator: 1bit,對于PSI數據包,該位為1時,表示該TS包是某個Section的第?個包,并且該包含有pointer_field,該變量的值意義在于,除了調整字段之外,往后pointer_field個字節開始,才是有效數據。對于空包來說,該值為0。
-
transport_priority: 1bit,表示傳輸優先級,對于相同PID的TS包,該字段置1的TS包擁有更?的優先級。PID:13bit,PID可以標識存儲于TS包中有效凈荷的數據的類型。PID?于TS包階段?于鑒別各種PSI/SI信息表、電視節?,區分?視頻的PES包等,是辨別碼流信息性質的關鍵。
-
transport_scrambling_control:2bit,?來指示傳送流包Payload的加擾?式。【傳送流包?部包括調整字段,則不應被加擾;空包也不加擾。】
Transport_scrambling_control | 描述 |
---|---|
00 | 未加擾 |
01 | 用戶定義 |
10 | 用戶定義 |
11 | 用戶定義 |
-
adaption_field_control:2bit,表示傳送流包?部是否跟隨調整字段/Payload【如果全部是調整字段則不含payload】
-
continuity_counter: 4bit,隨著具有相同PID的TS包增加?增加,當它達到最?(31)時,?恢復為0,如果adaption_field_control = 00/10,該連續計數器不增加,因為不含payload。
1.3解析TS包
1.3.1獲取包?
TS包的包?有兩種——188B或者204B,在解析TS包之前,必須要先判斷TS包包?,以便后續進?分析。
1. 標準固定長度
- 常規 TS 包:最常見的長度是 188 字節(包括 4 字節包頭 + 184 字節負載)。這是 DVB(歐洲數字電視標準)、ATSC(美國數字電視標準)等系統的默認長度。
- 擴展長度:在某些場景(如 DVB-T2、ISDB-Tb)中,TS 包可能被封裝到更大的幀結構中,此時 TS 包長度可能為 204 字節(188 字節 + 16 字節前向糾錯碼)。但這種情況下,核心 TS 包本身仍為 188 字節,只是外部添加了額外的保護機制。
2. 通過同步字節識別包邊界
TS 包的起始位置由 同步字節(固定值 0x47
)標識。接收端通過檢測連續的 0x47
來定位每個 TS 包的開始,并根據固定長度(如 188 字節)截取完整的包。例如:
0x47 [TS包頭4字節] [負載184字節] 0x47 [下一個TS包...]
如果遇到非 0x47
的字節,則表示傳輸錯誤或包丟失。
具體流程如下圖(以188B為例):
1.3.2 解析TS包頭
在獲取包?之后,就要對包頭信息進?解析并獲取有效數據,需要定義?個結構體存儲數據:
/*TS包包頭的結構體*/
typedef struct CSTSPacketHeader_S
{BYTE ucSyncByte; //TS包的標識符BYTE ucTransport_error_indicator; //傳輸錯誤指示器,當值為時,表示該包有誤BYTE ucPayload_unit_start_indicator; //有效凈荷開始標記// 注:如果想要節省存儲空間,可以使用位域的方式定義結構體。BYTE——unsigned char,Word——unsigned short intBYTE ucTransport_priority; //傳輸的優先級WORD wPID; //TS包的ID,用于區分不同的sectionBYTE ucTransport_scrambling_control; //指示ts傳送流包有效凈荷的加擾方式BYTE ucAdaptation_field_control; //指示是否有調整字段和有效凈荷BYTE ucContinuity_counter; //隨著相同PID TS包的增加而增加
} TSPacketHeader;
假定包?為188,我們每獲取?個TS包,就裝進?個?度為188的BYTE型數組?,前4個BYTE就是包頭的數據了,獲取數據可以邏輯與,左右移,邏輯或的?法進?,具體例?如下:
pstTSHeader->wPID = ((pucTSBuffer[1]& 0x1f) << 8) | pucTSBuffer[2];
1.3.3 判斷TS包的有效性
在?個碼流中,并不全部都是有效的TS包,需要將?些?效TS包剔除
?效TS包的情況分為五種:
- 該TS包往后188B不是0x47的包頭標識符(TS包都是連續發送,如果出現包不連續的地?,說
明該包數據傳送時出錯); - TS包存在錯誤,即transport_error_Indicator的值為1;
- TS包全是調整字段(空包),即adaption_field_control的值為10(?進制);
- TS包的調整字段屬于保留的情況,即adaption_field_control的值為00(?進制);
- TS包被加擾,即transport_scrambling_control不為00,(如果有做解擾可以去掉這種情況);
對于這五種情況的TS包我們?律丟棄,直接獲取下?個TS包。
1.3.4 確定payload的起始位置
1. 定位 TS 包起始
每個 TS 包以 同步字節(固定值 0x47
)開始。找到同步字節后,后續的 187 字節屬于當前 TS 包。
2. 解析 TS 包頭(4 字節)
TS 包頭的結構如下:
字節0: 同步字節 (0x47)
字節1: TEI(1) | PUSI(1) | TP(1) | PID(13)
字節2: PID(繼續)
字節3: TSC(2) | AFC(2) | CC(4)
其中關鍵字段:
- AFC (Adaptation Field Control):位于字節 3 的第 4-5 位(2 比特),指示自適應區和有效載荷的存在情況:
01
:無自適應區,僅有效載荷(Payload 從第 5 字節開始)。10
:僅有自適應區,無有效載荷(Payload 為空)。11
:自適應區后接有效載荷(需進一步計算 Payload 起始)。00
:保留值,不應使用。
3. 根據 AFC 計算 Payload 起始
情況 1:AFC = 01(僅有效載荷)
- Payload 起始位置:第 5 字節(即包頭后的第一個字節)。
情況 2:AFC = 10(僅自適應區)
- 無有效載荷,TS 包僅包含包頭和自適應區。
情況 3:AFC = 11(自適應區 + 有效載荷)
- 讀取自適應區長度:
- 包頭后的第 1 個字節(即第 5 字節)是 自適應區長度(
adaptation_field_length
),表示自適應區的總字節數(不包含自身)。
- 包頭后的第 1 個字節(即第 5 字節)是 自適應區長度(
- 計算 Payload 起始:
- Payload 起始位置 = 包頭(4 字節) + 自適應區長度字節(1 字節) + 自適應區內容(
adaptation_field_length
字節)。 - 公式:
Payload起始 = 5 + adaptation_field_length
。
- Payload 起始位置 = 包頭(4 字節) + 自適應區長度字節(1 字節) + 自適應區內容(
4. 示例計算
假設 TS 包數據如下(十六進制):
47 40 00 11 05 FF 00 01 02 03 61 62 63 ...
解析步驟:
- 同步字節:
47
→ 確認 TS 包起始。 - AFC 字段:字節 3 為
11
→ 二進制0001 0001
→ AFC =11
(自適應區 + 有效載荷)。 - 自適應區長度:第 5 字節為
05
→ 自適應區內容長度為 5 字節。 - Payload 起始:5(包頭 + 長度字節) + 5(自適應區內容) = 第 10 字節(
61
)。
二、 Section
2.1 Section的概念
-
?個TS數據包的最?凈荷為184個字節,當?個PSI/SI表的字節?度?于184字節時,就要對這個表進?分割,形成段(section)來傳送。
-
分段機制主要是將?個數據表分割成多個數據段。 在PSI/SI表到TS包的轉換過程中,段起到了中介的作?。由于?個數據包只有188字節,?段的?度是可變的,EIT表的段限?4096字節,其余PSI/SI表的段限?為1024字節。因此,?個段要分成?部分插?到TS包的payload中。
-
從TS碼流中可以獲取到TS包,TS包要組成Section,才能提取到想要的信息,所以?先要懂得怎么組section。
組Section之前要了解TS包在碼流中發送的?些情況:
- TS包發送的時候PID是?序的,連續的TS包的PID可能都是不?樣的;
- TS包發送的時候Section是相對有序的,也就是說,對于同?個PID的TS包,只有發完了?個Section,才會發送下?個Section,不然?法區分該TS包屬于哪?個Section,并且對于這個Section,TS包是有序發送的,否則數據會被打亂;
- 某個Section的第?個TS包有PSI/SI表的?些表頭信息(table_id,section_length等信息),我稱之為SectionHeader,后?的TS包就沒有,所以接收某個Section必須先拿到?包。
2.2 TS包組Section
-
TS包組section?先要找到該section的第?個TS包(下?簡稱為?包),?包含有該section的?度,可以?來判斷?個section是不是組完了。
-
通過判斷TS包包頭中的Payload Unit Start Indicator,該值為1的話,就說明這個TS包是?包,可以開始組?個section,?包含有Section的頭部,結構類似下表。
說明:uimsbf
表示無符號整數,最高位在前;bslbf
表示二進制符號位在前(布爾類或標志位常用) ,可用于解析 MPEG - 2 傳輸流中節目關聯段(PAT)的結構。
字段名 | 位寬 | 編碼類型 | |
---|---|---|---|
table_id | 8 | uimsbf | |
section_syntax_indicator | 1 | bslbf | |
‘0’ | 1 | bslbf | |
reserved | 2 | bslbf | |
section_length | 12 | uimsbf | |
transport_stream_id | 16 | uimsbf | |
reserved | 2 | bslbf | |
version_number | 5 | uimsbf | |
current_next_indicator | 1 | bslbf | |
section_number | 8 | uimsbf | |
last_section_number | 8 | uimsbf |
拿到?包之后,要獲取section的?度,有效數據的第?個字節的后四位和第三個字節組成?個12bit的字段,該值就是section_length后?數據的?度,如果算上前?三個字節,整個section的?度就是section_length += 3
。
將section_length和TS包有效?度進?對?,
- 如果section_length > TS包的有效數據,證明后?還有其他的TS包,將section_length減去TS包有效數據?度,獲得剩余?度;
- 如果是section_length <= TS包的有效數據,證明該section已經結束了。
如果?個section還沒組完,那么就要獲取后續的TS包,后續的TS包應該是和原來相同PID,并且TS包頭中continuity_counter要?原來的?1(31的話要變成0),拿到包后要與剩余?度進?對?,重復上?的步驟。
2.3 組多個Section和判全判重
對于?些PSI/SI表來說,由于數據較多,有時候不??個section,怎么針對這個表將所有的section組全?
子表 Section 組包、判全與判重機制說明
在處理 PSI/SI 表(以 PAT 等為例)的分段 Section 時,需通過以下邏輯實現組包、判全與判重:
1. 子表 Section 數量與組包邏輯
每個子表的 Section 頭部包含 last_section_number
字段,規則如下:
- 含義:標識當前子表最后一個 Section 的編號(
section_number
)。 - 數量計算:子表總 Section 數 =
last_section_number + 1
(因section_number
從 0 開始計數 )。 - 組包方式:以鏈表(或數組)存儲同一子表的 Section,按
section_number
順序拼接,還原完整子表數據。
2. 版本控制(判重核心邏輯)
每個 Section 頭部包含 version_number
字段,用于標識子表版本:
- 版本變更:若新 Section 的
version_number
與已存儲子表版本不一致,說明子表已更新,需:- 丟棄所有舊版本 Section。
- 重新初始化接收流程,采集新版本 Section。
- 版本未變:繼續校驗
section_number
,判斷是否重復或缺失。
3. 判全與判重邏輯
通過 標記數組 跟蹤 Section 接收狀態,流程如下:
步驟 | 操作邏輯 | 關鍵判斷 |
---|---|---|
1 | 初始化標記數組 | 以 last_section_number + 1 為長度,初始值全為 0 (未接收) |
2 | 接收新 Section | 解析其 section_number |
3 | 判重校驗 | 若標記數組中 section_number 對應下標值為 1 ,說明該 Section 已接收過,丟棄 |
4 | 標記接收狀態 | 若未重復,將標記數組中 section_number 對應下標值置為 1 |
5 | 判全校驗 | 遍歷標記數組,若下標 0 ~ last_section_number 對應值全為 1 ,說明子表所有 Section 已收全,可組裝完整子表 |
更多資料:https://github.com/0voice