什么是TS封装?
TS(Transport Stream) 是一种用于传输音视频数据的封装格式,全称 MPEG-2 Transport Stream 。它由 MPEG‑2 Systems 标准定义,主要用于 广播电视、卫星传输、IPTV 以及流媒体传输 。TS 的设计目标是保证在不稳定网络或广播环境中仍能可靠传输音视频数据。
TS 封装最大的特点是 容错性强、可连续传输、支持多节目复用 。例如在数字电视和流媒体系统中,经常将 H.264/H.265 视频与 AAC、MP2、AC3 音频封装为 TS 进行传输。
TS封装结构
TS 传输流由 固定长度的数据包组成 ,每个 TS 包大小为:188 Bytes。
TS 包结构如下:
| 字段 | 大小 | 说明 |
|---|---|---|
| Sync Byte | 1B | 同步字节,固定为 0x47 |
| Header | 3B | 包头信息 |
| Adaptation Field | 可选 | 适配字段 |
| Payload | 剩余 | 音视频数据 |
1. 同步字节(Sync Byte)
TS 每个包的第一个字节为:
0x47
作用:
- 标识 TS 包开始
- 接收端可通过扫描 0x47 来恢复同步
在网络传输中,如果发生丢包或错位,可以通过该字节重新同步数据流。
2. TS Header(包头)
TS Header 长度为 4字节,结构如下:
| 字段 | 位数 | 说明 |
|---|---|---|
| sync_byte | 8 | 同步字节 |
| transport_error_indicator | 1 | 传输错误标志 |
| payload_unit_start_indicator | 1 | payload开始标志 |
| transport_priority | 1 | 优先级 |
| PID | 13 | Packet Identifier |
| scrambling_control | 2 | 加密控制 |
| adaptation_field_control | 2 | 是否包含适配字段 |
| continuity_counter | 4 | 连续计数器 |
其中 PID(Packet Identifier) 是 TS 中最重要的字段,用于区分不同的数据流,例如:
| PID | 含义 |
|---|---|
| 0x0000 | PAT |
| 0x0001 | CAT |
| 0x0002 | TSDT |
| 0x0003 | IPMP |
| 0x0010 | NIT |
| 0x1FFF | Null Packet |
音视频流通常分配独立 PID,例如:
Video PID : 256
Audio PID : 257
PSI表(节目特定信息)
TS 支持 多节目复用(MPTS) ,为了管理这些节目,TS 中定义了 PSI(Program Specific Information)表。
主要包括:
1. PAT(Program Association Table)
PAT 表用于描述 节目与 PMT 的对应关系。
示例:
Program 1 -> PMT PID 100
Program 2 -> PMT PID 200
所有 TS 流必须包含 PAT 表,且 PID 固定为:
PID = 0x0000
2. PMT(Program Map Table)
PMT 表描述 某个节目包含哪些音视频流。
例如:
Program 1
Video PID : 256 (H264)
Audio PID : 257 (AAC)
PMT 包含的信息:
- stream_type
- elementary_PID
- ES_info_length
常见 stream_type:
| 类型 | 值 |
|---|---|
| MPEG2 Video | 0x02 |
| H264 | 0x1B |
| H265 | 0x24 |
| AAC | 0x0F |
| AC3 | 0x81 |
3. PCR(Program Clock Reference)
PCR 用于 音视频同步,属于 TS 中的时间基准。
其作用是:
同步解码器系统时钟
PCR 通常放在:
- adaptation field
- 视频 PID
PCR 基于 27MHz 时钟,结构:
PCR = base (33bit) + extension (9bit)
接收端通过 PCR 恢复系统时间,从而保证 音视频同步播放。
PES封装
TS 中真正承载音视频数据的是 PES(Packetized Elementary Stream)。
PES 是对 ES(Elementary Stream) 的封装。
例如:
H264 NALU -> PES -> TS
AAC Frame -> PES -> TS
PES 结构:
| 字段 | 大小 |
|---|---|
| Packet Start Code | 3B |
| Stream ID | 1B |
| PES Packet Length | 2B |
| Flags | 2B |
| Header Length | 1B |
| PTS/DTS | 可选 |
| ES Data | 数据 |
其中关键时间戳:
PTS(Presentation Time Stamp)表示 显示时间
DTS(Decoding Time Stamp)表示 解码时间
例如:
PTS = 90000
DTS = 60000
TS 使用 90kHz 时钟。
TS封装与解封装
TS封装
TS封装的目标是将 音视频ES流封装为188字节的TS包。
完整流程主要包括:
ES → PES → TS Packet → TS Stream
流程:
bash
ES流
(H264/AAC)
│
▼
PES封装
│
▼
TS Packet封装
(188字节)
│
▼
PAT / PMT生成
│
▼
TS复用输出
1. 原始ES流(Elementary Stream)
编码器产生的原始码流称为 ES流。
例如:
视频:
H264 NALU
H265 NALU
音频:
AAC Frame
MP2 Frame
AC3 Frame
这些数据还不能直接放入 TS,需要先进行 PES封装。
2. PES封装
PES(Packetized Elementary Stream)是 ES的打包格式,用于在TS中传输。
PES结构:
PES Header
+ ES Data
PES头主要包含:
| 字段 | 说明 |
|---|---|
| start_code | 起始码 |
| stream_id | 流ID |
| packet_length | 长度 |
| flags | 标志 |
| PTS/DTS | 时间戳 |
重要时间戳:
- PTS(Presentation Time Stamp)
表示:
显示时间
- DTS(Decoding Time Stamp)
表示:
解码时间
例如:
PTS = 90000
DTS = 60000
TS 使用 90kHz时钟。
封装后结构:
PES Header
+ H264 NALU
3. TS Packet封装
PES数据会被切分为 188字节TS包。
TS Packet结构:
TS Header (4 bytes)
Adaptation Field (optional)
Payload
完整结构:
| Sync | Header | Adaptation | Payload |
1. 同步字节
0x47
用于同步 TS 包。
2. TS Header
TS包头结构:
| 字段 | 说明 |
|---|---|
| sync_byte | 同步字节 |
| payload_unit_start_indicator | payload开始标志 |
| PID | 数据流标识 |
| adaptation_field_control | 是否包含适配字段 |
| continuity_counter | 连续计数 |
其中 PID(Packet Identifier) 非常重要。
例如:
Video PID : 256
Audio PID : 257
3. Adaptation Field
适配字段是可选的,常用于放置:
PCR
填充数据
随机访问标志
4. PSI表生成
TS流必须包含 PSI(Program Specific Information)表。
主要包括:
1. PAT
PAT(Program Association Table)用于描述:
节目 -> PMT PID
示例:
Program 1 -> PMT PID 100
PAT PID固定为:
0x0000
2. PMT
PMT(Program Map Table)描述:
节目包含哪些流
例如:
Program 1
Video PID : 256
Audio PID : 257
3. PCR
PCR(Program Clock Reference)用于 音视频同步。
通常写入:
Video PID
PCR基于:
27MHz系统时钟
接收端通过PCR恢复系统时间。
5. TS复用(Mux)
封装后的TS包需要进行 复用输出。
TS流结构:
TS Stream
├── PAT
├── PMT
├── Video TS Packet
├── Audio TS Packet
输出策略通常为:
V V A V V A
这样可以保证音视频 均匀输出。
最终形成完整TS流:
TS Packet
TS Packet
TS Packet
...
TS解封装
TS解封装的目标是:
TS Stream → PES → ES
也称为 Demux(解复用)。
流程:
bash
TS Stream
│
▼
TS同步
│
▼
解析TS Header
│
▼
PAT解析
│
▼
PMT解析
│
▼
Payload拼接
│
▼
PES解析
│
▼
恢复ES流
(H264/AAC)
1. TS同步
解复用首先要进行 同步检测。
步骤:
-
扫描字节流
-
查找
0x47 -
每隔188字节验证同步
0x47 .... 188 bytes
0x47 .... 188 bytes
若同步错误需要重新同步。
2. 解析TS Header
解析每个TS包的Header:
PID
payload_start
continuity_counter
根据PID分类数据:
PAT
PMT
Video
Audio
3. PAT解析
PAT用于确定:
PMT PID
例如:
Program 1 -> PMT PID 100
解析后可以找到 PMT。
4. PMT解析
PMT用于获取:
Video PID
Audio PID
例如:
Video PID = 256
Audio PID = 257
之后即可解析对应数据流。
5. TS Payload拼接
TS包中的payload属于 PES数据片段。
需要根据:
PID
payload_start_indicator
进行拼接。
示例:
TS1 -> PES1 part1
TS2 -> PES1 part2
TS3 -> PES1 part3
拼接后恢复:
完整PES
6. PES解析
解析PES头:
PTS
DTS
ES Data
提取:
H264 NALU
AAC Frame
例如:
PES Header
+ H264 NALU
7. 恢复ES流
最终恢复原始数据:
视频:
H264 NALU
音频:
AAC Frame
随后即可:
解码播放
示例(c++)
公共定义
c++
// include/ts_def.h
#ifndef TS_DEF_H
#define TS_DEF_H
#include <stdint.h>
#define TS_PACKET_SIZE 188
#define PID_PAT 0x0000
#define PID_PMT 0x0100
#define PID_VIDEO 0x0101
#define PID_AUDIO 0x0102
#define STREAM_TYPE_H264 0x1B
#define STREAM_TYPE_H265 0x24
#define STREAM_TYPE_AAC 0x0F
#endif
TS封装(Mux)
c++
#ifndef TS_MUXER_H
#define TS_MUXER_H
#include <vector>
#include <fstream>
#include "ts_def.h"
class TsMuxer
{
public:
TsMuxer();
void open(const std::string& file);
void close();
void write_pat();
void write_pmt();
void write_video(uint8_t* data,int size,uint64_t pts);
private:
std::ofstream out;
uint8_t continuity_pat;
uint8_t continuity_pmt;
uint8_t continuity_video;
void write_ts_packet(uint16_t pid,
bool payload_start,
uint8_t* payload,
int payload_size,
uint8_t& continuity);
std::vector<uint8_t> build_pes(uint8_t* data,int size,uint64_t pts);
};
#endif
////////////////////////////////////////////////////////////////////////////////////////////////
#include "ts_muxer.h"
#include <string.h>
#include <iostream>
TsMuxer::TsMuxer()
{
continuity_pat=0;
continuity_pmt=0;
continuity_video=0;
}
void TsMuxer::open(const std::string& file)
{
out.open(file,std::ios::binary);
}
void TsMuxer::close()
{
out.close();
}
void TsMuxer::write_ts_packet(uint16_t pid,
bool payload_start,
uint8_t* payload,
int payload_size,
uint8_t& continuity)
{
uint8_t packet[TS_PACKET_SIZE];
memset(packet,0xff,TS_PACKET_SIZE);
packet[0]=0x47;
packet[1]=((payload_start?0x40:0x00)|((pid>>8)&0x1f));
packet[2]=pid&0xff;
packet[3]=0x10|(continuity&0x0f);
continuity++;
memcpy(packet+4,payload,payload_size);
out.write((char*)packet,TS_PACKET_SIZE);
}
void TsMuxer::write_pat()
{
uint8_t payload[184];
memset(payload,0xff,184);
payload[0]=0;
uint8_t pat[]={
0x00,0xb0,0x0d,
0x00,0x01,
0xc1,
0x00,
0x00,
0x00,0x01,
0xe1,0x00,
0,0,0,0
};
memcpy(payload+1,pat,sizeof(pat));
write_ts_packet(PID_PAT,true,payload,184,continuity_pat);
}
void TsMuxer::write_pmt()
{
uint8_t payload[184];
memset(payload,0xff,184);
payload[0]=0;
uint8_t pmt[]={
0x02,0xb0,0x17,
0x00,0x01,
0xc1,
0x00,
0x00,
0xe1,0x01,
0xf0,0x00,
STREAM_TYPE_H264,
0xe1,0x01,
0xf0,0x00,
0,0,0,0
};
memcpy(payload+1,pmt,sizeof(pmt));
write_ts_packet(PID_PMT,true,payload,184,continuity_pmt);
}
std::vector<uint8_t> TsMuxer::build_pes(uint8_t* data,int size,uint64_t pts)
{
std::vector<uint8_t> pes;
pes.push_back(0x00);
pes.push_back(0x00);
pes.push_back(0x01);
pes.push_back(0xe0);
pes.push_back(0);
pes.push_back(0);
pes.push_back(0x80);
pes.push_back(0x80);
pes.push_back(5);
uint64_t v=pts;
pes.push_back((0x21)|((v>>29)&0x0e));
pes.push_back((v>>22)&0xff);
pes.push_back(((v>>14)&0xff)|1);
pes.push_back((v>>7)&0xff);
pes.push_back((v<<1)|1);
pes.insert(pes.end(),data,data+size);
return pes;
}
void TsMuxer::write_video(uint8_t* data,int size,uint64_t pts)
{
auto pes=build_pes(data,size,pts);
int pos=0;
while(pos<pes.size())
{
uint8_t payload[184];
int copy=std::min(184,(int)pes.size()-pos);
memcpy(payload,pes.data()+pos,copy);
write_ts_packet(PID_VIDEO,
pos==0,
payload,
copy,
continuity_video);
pos+=copy;
}
}
TS解封装(Demux)
c++
#ifndef TS_DEMUXER_H
#define TS_DEMUXER_H
#include <string>
#include <vector>
class TsDemuxer
{
public:
void parse(const std::string& file);
private:
std::vector<uint8_t> pes_video;
void parse_video(uint8_t* payload,bool start);
};
#endif
///////////////////////////////////////////////////////////////////////
#include "ts_demuxer.h"
#include <fstream>
#include <iostream>
#include <stdint.h>
void TsDemuxer::parse(const std::string& file)
{
std::ifstream in(file,std::ios::binary);
uint8_t packet[188];
while(in.read((char*)packet,188))
{
if(packet[0]!=0x47)
{
std::cout<<"sync error\n";
continue;
}
uint16_t pid=((packet[1]&0x1f)<<8)|packet[2];
bool start=packet[1]&0x40;
uint8_t* payload=packet+4;
if(pid==0x101)
{
parse_video(payload,start);
}
}
}
void TsDemuxer::parse_video(uint8_t* payload,bool start)
{
if(start)
{
if(!pes_video.empty())
{
std::cout<<"video pes size "<<pes_video.size()<<std::endl;
pes_video.clear();
}
}
pes_video.insert(pes_video.end(),payload,payload+184);
}
总结
TS(Transport Stream)是一种为 实时传输和广播环境设计的音视频封装格式 ,通过 188字节固定包结构、PID机制、PSI表和PCR时间同步机制 实现稳定的音视频传输。其核心思想是将 原始音视频码流封装为 PES,再切分为 TS 包进行复用传输。