音视频学习(九十二):ts封装

什么是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同步

解复用首先要进行 同步检测

步骤:

  1. 扫描字节流

  2. 查找 0x47

  3. 每隔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 包进行复用传输

相关推荐
m0_706653235 小时前
用PS CC2017实现视频面部优化的新突破
音视频
视频技术分享6 小时前
音视频SDK的多平台兼容性挑战与适配方案
音视频
八角Z18 小时前
AI短视频创作实战心得:从玩具到生产力工具亲测
人工智能·机器学习·服务发现·音视频
王家视频教程图书馆1 天前
横屏下的全屏视频 右边视频控制器被虚拟按键遮挡了,解决方式隐藏状态栏,竖屏后在恢复
音视频
EasyDSS1 天前
EasyDSS如何基于LiveKit/AI大模型/AI会议助手/语音转写STT技术破解音视频应用核心痛点
人工智能·音视频·webrtc·语音识别·点播技术·流媒体直播
阿甘编程点滴1 天前
书单号视频搬运软件推荐8款(2026实测版)
人工智能·音视频
liulilittle1 天前
Windows 11 上搭建 YouTube 视频下载工具:yt-dlp + FFmpeg
windows·ffmpeg·音视频
yzx9910131 天前
手把手教你安装视频下载神器 Lux(多平台教程)
音视频
YWamy1 天前
音视频SDK开发的三大核心挑战及高效应对策略
音视频