TS 文件为传输流文件,可以将其理解为一种单一码流、混合码流:
单一码流:TS流的基本组成单位,是长度为188字节的TS包;
混合码流:TS流有多种数据组成,一个TS包中的数据可以是视频、音频、填充数据、PSI/SI表格数据等;
TS(Transport Stream)流,也叫传输流,是一个又一个Packet形成,每一个Packet即为一个TS包,由4字节的Header和184字节的Data组成。
TS包大小固定为 188 字节,必须以0x47
开头,内部有3个部分:TS Header
、Adaptation Field
、Payload
,其中TS固定头(packet header)4个字节,Adaptation Field 可能存在也可能不存在,长度不固定。
其中,TS包中 Payload 所传输的信息包括两种类型:
- 视频、音频、辅助数据的
PES
包; - 节目专用信息
PSI
;
TS包也可以是空包,空包(0xFF)用来填充TS流,可能在重新进行多路复用时被插入或删除。视频、音频的ES流需进行打包形成视频、音频的 PES流。辅助数据(如图文电视信息)不需要打成PES包。
TS Header
固定占用 4 个字节。其中最重要的是:同步头(8bit)、PID(13bit)、counter计数器(4bit)。解析TS流要先找到PAT表,只要找到PAT就可以找到PMT,然后就可以找到音视频流了。
PAT表和PMT表需要定期插入TS流,因为用户随时可能加入TS流,这个间隔很小,通常每隔几个视频帧就要加入PAT和PMT。其中,PAT和PMT是必须的,还可以加入其他表如SDT(业务描述表)等,不过,HLS流只要PAT和PMT就可以了。
Structure
PID
PID信息非常关键,它直接表示了本次TS包的用途,目前根据PID,主要用以分辨对应的TS包类型,包括:PAT、CAT、NIT、PMT、以及媒体与数据等类型。
其中,比较重要的是PAT
和PMT
,如果PID为0x0000
,那么此TS的Payload为PAT;如果为表中未列出的值,那么为分配给此TS包的一个唯一标识,用来区分TS包属于哪个节目,比如某视频流,或者某音频流,一个节目包含多个TS包。
很明显,PID就是用来做索引的,通过该值判断包类型,以及媒体包属于哪一个流。
Adaptation Field Control
这里说的很清楚,当该值为00时无视,为01时表示只有载荷数据(中间通过一个0x00分割),为10时只有Adaptation Field,当为11时Adaptation Field后紧跟着Payload(中间没有0x00分割)
Visual Diagram
Demuxer
#define MTS_PACKET_SIZE 188
#define MTS_TS_HEADER_SIZE 4
#define MTS_SYNC_BYTE 0x47
struct mts_packet_header {
// --> fixed: 32bit
uint32_t sync_byte : 8; // 同步字节固定为0x47
uint32_t transport_error_indicator : 1; // 传输错误标志位,一般传输错误的话就不会处理这个包了
uint32_t payload_unit_start_indicator : 1; // 有效负载的开始标志,一个完整的数据包开始时标记为1,包括PES和PSI
uint32_t transport_priority : 1; // 传输优先级位,1表示高优先级
uint32_t PID : 13; // 有效负载数据的类型
uint32_t transport_scrambling_control : 2; // 加密标志位,0x00表示未加密
uint32_t adaptation_field_control : 2; // 调整字段控制,0x00不处理,0x01,0x02,0x03处理
uint32_t continuity_counter : 4; // 计数器,范围0-15
// --> unfixed: ~
struct mts_adaptation_field adaptation; // 扩展区
};
static inline uint32_t mts_dec_packet_header(const uint8_t* data,
size_t bytes,
struct mts_packet_header* pkhd) {
if (MTS_PACKET_SIZE != bytes) // 包必须188字节
return 0;
if (pkhd == nullptr)
return 0;
if (MTS_SYNC_BYTE != data[0]) // 包第一字节必须是0x47
return 0;
/**
* Fixed Packet Header
* ISO_IEC_13818 Table 2-2
*/
uint32_t len = 0;
memset(pkhd, 0, sizeof(mts_packet_header));
pkhd->sync_byte = MTS_SYNC_BYTE; // 8b
pkhd->transport_error_indicator = (data[1] >> 7) & 0x01; // 1b
pkhd->payload_unit_start_indicator = (data[1] >> 6) & 0x01; // 1b
pkhd->transport_priority = (data[1] >> 5) & 0x01; // 1b
pkhd->PID = ((data[1] << 8) | data[2]) & 0x1FFF; // 13b
pkhd->transport_scrambling_control = (data[3] >> 6) & 0x03; // 2b
pkhd->adaptation_field_control = (data[3] >> 4) & 0x03; // 2b
pkhd->continuity_counter = data[3] & 0x0F; // 4b
len += MTS_TS_HEADER_SIZE;
// Adaptation Field Control
if (0x02 == pkhd->adaptation_field_control ||
0x03 == pkhd->adaptation_field_control) {
mts_dec_adaptation_filed(data + 4, bytes - 4, &pkhd->adaptation);
len += 1 + pkhd->adaptation.transport_private_data_length;
}
// --> PID
return len;
}
Adaptation Field
紧跟着4个字节的固定头之后,是一个可能存在又可能不存在的扩展头,这个扩展头是否存在由固定头里的adaptation_field_control
决定,如下图所示:
这个扩展头的长度由该区域第一个字节(8bit)决定,如果adaptation_field_length==0
,那么整个扩展头的长度也就是1个字节,否则,整个扩展头的长度等于(8+length)bit。
打包 TS 流时 PAT
和 PMT
表是没有adaptation field的,只有当Payload是PES
时,才会有Adaption Field,不够的长度直接补 0xff 即可。视频流和音频流都需要加 adaptation field
,通常加在一个帧的第一个 TS 包和最后一个 TS 包里,中间的 TS 包不加。
Structure
PCR
其中PCR很重要,PCR是节目时钟参考,PCR、DTS、PTS 都是对同一个系统时钟的采样值,PCR 是递增的,因此可以将其设置为 DTS 值,音频数据不需要 PCR。如果没有PCR字段,iPad 是可以播放的,但 VLC 无法播放。
PCR分两部分编码:一个以系统时钟频率的 1/300 为单位,称为PCR_base
共33bit;另一个以系统时钟频率为单位,称为PCR_ext,共9bit,整个PCR共42bit。
PCR基数的时钟和PCR扩展的时钟是不同的,需要进行调整计算;
可以以此控制传输速率:
Demuxer
struct mts_adaptation_field {
// --> fixed: 8bit
uint32_t adaptation_field_length : 8;
// --> unfixed: adaptation_field_length
uint32_t discontinuity_indicator : 1;
uint32_t random_access_indicator : 1;
uint32_t elementary_stream_priority_indicator : 1;
uint32_t PCR_flag : 1;
uint32_t OPCR_flag : 1;
uint32_t splicing_point_flag : 1;
uint32_t transport_private_data_flag : 1;
uint32_t adaptation_field_extension_flag : 1;
uint64_t program_clock_reference_base : 33; // if (PCR_flag==1)
uint32_t program_clock_reference_extension : 9;
uint64_t original_program_clock_reference_base : 33; // if (OPCR_flag==1)
uint32_t original_program_clock_reference_extension : 9;
uint32_t splice_countdown : 8; // if (splicing_point_flag==1)
uint32_t transport_private_data_length : 8; // if (transport_private_data_flag==1)
int8_t transport_private_data[64]; // 自规约不超过64字节
uint32_t adaptation_field_extension_length : 8; // if (adaptation_field_extension_flag==1)
uint32_t ltw_flag : 1;
uint32_t piecewise_rate_flag : 1;
uint32_t seamless_splice_flag : 1;
uint32_t ltw_valid_flag : 1; // if (ltw_flag==1)
uint32_t ltw_offset : 15;
uint32_t piecewise_rate : 22; // if (piecewise_rate_flag==1)
uint32_t splice_type : 4; // if (seamless_splice_flag==1)
int64_t DTS_next_AU : 36;
};
static inline uint32_t mts_dec_adaptation_filed(const uint8_t* data, // 于adaptation_field起始
size_t bytes, // 剥离packet_header后的长度
struct mts_adaptation_field* adp) {
if (bytes > MTS_PACKET_SIZE)
return -1;
if (adp == nullptr)
return -1;
uint32_t i = 0, j = 0;
/**
* Adaptation Field Control
* ISO_IEC_13818 Table 2-6
*/
adp->adaptation_field_length = data[i++]; // 8b
// field
if (adp->adaptation_field_length > 0) {
adp->discontinuity_indicator = (data[i] >> 7) & 0x01; // 1b
adp->random_access_indicator = (data[i] >> 6) & 0x01; // 1b
adp->elementary_stream_priority_indicator = (data[i] >> 5) & 0x01; // 1b
adp->PCR_flag = (data[i] >> 4) & 0x01; // 1b
adp->OPCR_flag = (data[i] >> 3) & 0x01; // 1b
adp->splicing_point_flag = (data[i] >> 2) & 0x01; // 1b
adp->transport_private_data_flag = (data[i] >> 1) & 0x01; // 1b
adp->adaptation_field_extension_flag = (data[i] >> 0) & 0x01; // 1b
i++;
// PCR
if (adp->PCR_flag) {
adp->program_clock_reference_base =
((uint64_t)data[i] << 25) | ((uint64_t)data[i + 1] << 17) | ((uint64_t)data[i + 2] << 9) |
((uint64_t)data[i + 3] << 1) | ((data[i + 4] >> 7) & 0x01); // 33b
// --> reserved: 6b
adp->program_clock_reference_extension = ((data[i + 4] & 0x01) << 8) | data[i + 5]; // 9b
i += 6;
}
// OPCR
if (adp->OPCR_flag) {
adp->original_program_clock_reference_base =
(((uint64_t)data[i]) << 25) | ((uint64_t)data[i + 1] << 17) | ((uint64_t)data[i + 2] << 9) |
((uint64_t)data[i + 3] << 1) | ((data[i + 4] >> 7) & 0x01); // 33b
// --> reserved: 6b
adp->original_program_clock_reference_extension = ((data[i + 4] & 0x01) << 1) | data[i + 5]; // 9b
i += 6;
}
if (adp->splicing_point_flag) {
adp->splice_countdown = data[i++]; // 8b
}
if (adp->transport_private_data_flag) {
adp->transport_private_data_length = data[i++]; // 8b
if (adp->transport_private_data_length > 0) {
memcpy(&adp->transport_private_data, &data[i], adp->transport_private_data_length);
i += adp->transport_private_data_length;
}
} else {
adp->transport_private_data_length = 0;
}
if (adp->adaptation_field_extension_flag) {
uint8_t reserved;
adp->adaptation_field_extension_length = data[i++]; // 8b
adp->ltw_flag = (data[i] >> 7) & 0x01; // 1b
adp->piecewise_rate_flag = (data[i] >> 6) & 0x01; // 1b
adp->seamless_splice_flag = (data[i] >> 5) & 0x01; // 1b
reserved = data[i] & 0x1F; // 5b
i++;
if (adp->ltw_flag) {
adp->ltw_valid_flag = (data[i] >> 7) & 0x01; // 1b
adp->ltw_offset = ((data[i] & 0x7F) << 8) | data[i + 1]; // 15b
i += 2;
}
if (adp->piecewise_rate_flag) {
// --> reserved: 2b
adp->piecewise_rate = ((data[i] & 0x3F) << 16) | (data[i + 1] << 8) | data[i + 2]; // 22b
i += 3;
}
if (adp->seamless_splice_flag) {
adp->splice_type = (data[i] >> 4) & 0x0F; // 4b
adp->DTS_next_AU = (((data[i] >> 1) & 0x07) << 30) | (data[i + 1] << 22) |
(((data[i + 2] >> 1) & 0x7F) << 15) | (data[i + 3] << 7) |
((data[i + 4] >> 1) & 0x7F); // 36b
i += 5;
}
// reserved byte
}
// stuffing byte
}
return 0;
}
PAT
节目关联表(Program Association Table, PAT),得出所有节目的PID。N loop为一个数组,是节目列表及其对应的PID,每个节目就是指一段视频,具有唯一ID。
节目关联表每个 TS 流对应一张,用来描述该 TS 流中有多少个节目。TS流中,PAT包会重复实现,大约 0.5 秒出现一个,保证实时解码性。
PAT组成包括:ts_packet_header(4) + payload_unit_start_indicator(1) + PAT
例如:
Structure
Visual Diagram
Demuxer
struct mts_pmt_index {
// --> fixed: 32bit
uint32_t program_number : 16; // 节目号
uint32_t reserved : 3; // 保留
uint32_t program_map_PID : 13; // 节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
// --> unfixed: ~
struct mts_pmt PMT; // 扩展连接PMT表信息,如果table_id!=0x02则无效
};
struct mts_pat {
// --> fixed: 64bit
uint32_t table_id : 8; // 固定, 0x00, 标志是该表是PAT表
uint32_t section_syntax_indicator : 1; // 段语法标志位,固定为1
uint32_t zero : 1; // '0'
uint32_t reserved : 2; // 保留
uint32_t section_length : 12; // 表示从下一个字段开始到CRC32(含)之间有用的字节数
uint32_t transport_stream_id : 16; // 该传输流的ID,区别于一个网络中其它多路复用的流
uint32_t reserved2 : 2; // 保留
uint32_t version_number : 5; // 范围0-31,表示PAT的版本号
uint32_t current_next_indicator : 1; // 发送的PAT是当前有效还是下一个PAT有效
uint32_t sector_number : 8; // 分段的号码,PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
uint32_t last_sector_number : 8; // 最后一个分段的号码
// --> unfixed: ~
std::vector<mts_pmt_index> pmts; // PMT列表
uint32_t network_PID; // if (program_number == '0')
// --> fixed: 32bit
int64_t CRC : 32; // CRC32校验码
};
static inline uint32_t mts_dec_pat(struct mts_pat* pat,
const uint8_t* data,
size_t bytes) {
if (pat == nullptr)
return -1;
uint32_t i = 0;
/**
* Program association Table
* Table 2-25 Program association section
*/
memset(pat, 0, sizeof(mts_pat));
pat->table_id = data[0];
pat->section_syntax_indicator = (data[1] >> 7) & 0x01;
pat->zero = (data[1] >> 6) & 0x01;
pat->reserved = (data[1] >> 4) & 0x03;
pat->section_length = ((data[1] & 0x0F) << 8) | data[2]; // 从下一个字段开始到CRC32(含)之间有用的字节数
pat->transport_stream_id = (data[3] << 8) | data[4];
pat->reserved2 = (data[5] >> 6) & 0x03;
pat->version_number = (data[5] >> 1) & 0x1F;
pat->current_next_indicator = data[5] & 0x01;
pat->sector_number = data[6];
pat->last_sector_number = data[7];
// --> 64bit
// printf("PAT: %0x %0x %0x %0x %0x %0x %0x %0x\n",
// (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3],
// (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6], (unsigned int)data[7]);
if (PAT_TID_PAS != pat->table_id) // Table 2-26
return -1;
if (1 != pat->section_syntax_indicator) // 固定是1
return -1;
if (bytes < pat->section_length + 3) // 基础长度
return -1;
/**
* for (i = 0; i < N; i++) {}
*/
for (i = 8 /*loop start*/; i < pat->section_length - 4 /*CRC32*/; i += 4 /*loop size*/) {
unsigned int program_number = (data[i] << 8) | data[i + 1];
unsigned int reserved_3 = data[i + 2] >> 5;
if (program_number == 0x00) {
pat->network_PID = (data[i + 2] & 0x1F) << 8 | data[i + 3];
} else {
struct mts_pmt_index pmt {};
memset(&pmt, 0, sizeof(mts_pmt_index));
pmt.program_map_PID = (data[i + 2] & 0x1F) << 8 | data[i + 3];
pmt.reserved = 0;
pmt.program_number = program_number;
pat->pmts.push_back(pmt);
}
}
// CRC
uint32_t offset = pat->section_length + 3;
pat->CRC = (data[offset - 4] & 0x000000FF) << 24 |
(data[offset - 3] & 0x000000FF) << 16 |
(data[offset - 2] & 0x000000FF) << 8 |
(data[offset - 1] & 0x000000FF);
return 0;
}
PMT
节目映射表(PMT)的意义在于,它给出了节目号与组成这个节目元素之间的映射;也就是说,PMT是连接节目号与节目元素的桥梁。
我们知道,一个电视节目至少包含了视频和音频数据,而每一个节目的视音频数据都是以包的形式在TS流中传输的,所以说,一个TS流包含了多个节目的视频和音频数据包。
节目映射表的 PID 是由 PAT 表提供给出的。表征一路节目所有流信息,包含:VideoPID、AudioPID、DataPID。
PMT组成包括:ts_packet_header(4) + payload_unit_start_indicator(1) + PMT
例如:
Structure
Visual Diagram
Demuxer
struct mts_pmt {
// --> fixed: 96bit
uint32_t table_id : 8; // 固定, 0x02, 表示PMT表
uint32_t section_syntax_indicator : 1; // 固定, 0x01
uint32_t zero : 1; // 0x01
uint32_t reserved : 2; // 保留, 0x03
uint32_t section_length : 12; // 首先两位bit置为00,它指示段的byte数,由段长度域开始,包含CRC
uint32_t program_number : 16; // 指出该节目对应于可应用的 Program map PID
uint32_t reserved2 : 2; // 保留, 0x03
uint32_t version_number : 5; // 指出TS流中 Program map section 的版本号
uint32_t current_next_indicator : 1; // 当该位置1时,当前传送的 Program map section 可用
uint32_t sector_number : 8; // 固定, 0x00
uint32_t last_sector_number : 8; // 固定, 0x00
uint32_t reserved3 : 3; // 保留, 0x07
uint32_t PCR_PID : 13; // 指明TS包的PID值,该TS包含有PCR域
uint32_t reserved4 : 4; // 保留, 0x0F
uint32_t program_info_length : 12; // 前两位bit为00。该域指出跟随其后对节目信息的描述的byte数
// --> unfixed: ~
uint32_t program_info; // 节目信息
std::vector<mts_es_stream> stream; // ES列表, 一个节目有很多流
// --> fixed: 32bit
int64_t CRC : 32; // CRC32校验码
};
struct mts_es_stream {
// --> fixed: 40bit
uint32_t stream_type : 8; // 指示特定PID的节目元素包的类型。该处PID由elementary PID指定
uint32_t reserved : 3; // 保留, 0x07
uint32_t elementary_PID : 13; // 该域指示TS包的PID值。这些TS包含有相关的节目元素
uint32_t reserved2 : 4; // 保留, 0x0F
uint32_t ES_info_length : 12; // 前两位bit为00。该域指示跟随其后的描述相关节目元素的byte数
// --> unfixed: ~
uint32_t ES_info;
// --> unfixed: ~
struct mts_pes PES;
};
static inline uint32_t mts_dec_pmt(struct mts_pmt* pmt,
const uint8_t* data,
size_t bytes) {
uint32_t i = 0;
/**
* Program map Table
* Table 2-28 Transport Stream program map section
*/
memset(pmt, 0, sizeof(mts_pmt));
pmt->table_id = data[0];
pmt->section_syntax_indicator = (data[1] >> 7) & 0x01;
pmt->zero = (data[1] >> 6) & 0x01;
pmt->reserved = (data[1] >> 4) & 0x03;
pmt->section_length = ((data[1] & 0x0F) << 8) | data[2];
pmt->program_number = (data[3] << 8) | data[4];
pmt->reserved2 = (data[5] >> 6) & 0x03;
pmt->version_number = (data[5] >> 1) & 0x1F;
pmt->current_next_indicator = data[5] & 0x01;
pmt->sector_number = data[6];
pmt->last_sector_number = data[7];
pmt->reserved3 = (data[8] >> 5) & 0x07;
pmt->PCR_PID = ((data[8] & 0x1F) << 8) | data[9];
pmt->reserved4 = (data[10] >> 4) & 0x0F;
pmt->program_info_length = ((data[10] & 0x0F) << 8) | data[11];
// printf("PMT: %0x %0x %0x %0x %0x %0x %0x %0x, %0x, %0x, %0x, %0x\n", (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3], (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6],(unsigned int)data[7],(unsigned int)data[8],(unsigned int)data[9],(unsigned int)data[10],(unsigned int)data[11]);
if (PAT_TID_PMS != pmt->table_id) // Table 2-26
return -1;
if (1 != pmt->section_syntax_indicator) // 固定是1
return -1;
if (0 != pmt->sector_number) // 固定0x00
return -1;
if (0 != pmt->last_sector_number) // 固定0x00
return -1;
if (bytes < pmt->section_length + 3) // 基础长度
return -1;
/**
* for (i = 0; i < N1; i++) {}
*/
uint16_t last_info_length = 0; // 每一次循环体长度都是变量
for (i = 12 + pmt->program_info_length /*loop start*/; i < pmt->section_length - 4 /*CRC32*/; i += 5 + last_info_length /*loop size*/) {
uint16_t stream_type = data[i];
uint16_t elementary_PID = ((data[i + 1] & 0x1F) << 8) | data[i + 2];
uint16_t ES_info_length = ((data[i + 3] & 0x0F) << 8) | data[i + 4];
// printf("PMT: pn: %0x, pid: %0x, codec: %0x, es_len: %d\n",
// (unsigned int)pmt->program_number, (unsigned int)pid, (unsigned int)data[i], (unsigned int)len);
struct mts_es_stream stream {};
memset(&stream, 0, sizeof(mts_es_stream));
stream.stream_type = stream_type;
stream.reserved = 0;
stream.elementary_PID = elementary_PID;
stream.reserved2 = 0;
stream.ES_info_length = ES_info_length;
pmt->stream.push_back(stream);
}
// CRC
uint32_t offset = pmt->section_length + 3;
pmt->CRC = (data[offset - 4] & 0x000000FF) << 24 |
(data[offset - 3] & 0x000000FF) << 16 |
(data[offset - 2] & 0x000000FF) << 8 |
(data[offset - 1] & 0x000000FF);
return 0;
}
PES
PES层(ts层中payload)是在每一个视频/音频帧上加入了时间戳等信息,pes包内容很多,我们只留下最常用的。
pts是显示时间戳、dts是解码时间戳,视频数据两种时间戳都需要,音频数据只需要pts,所以pts和dts相同。
有pts和dts两种时间戳是B帧引起的,I帧和P帧的pts等于dts。如果一个视频没有B帧,则pts永远和dts相同。从文件中顺序读取视频帧,取出的帧顺序和dts顺序相同。dts算法比较简单,初始值 + 增量即可,pts计算比较复杂,需要在dts的基础上加偏移量。
音频的pes中只有pts(同dts),视频的I、P帧两种时间戳都要有,视频B帧只要pts(同dts)。打包pts和dts就需要知道视频帧类型,但是通过容器格式我们是无法判断帧类型的,必须解析h.264内容才可以获取帧类型。
PMT组成包括:ts_packet_header(4) + adaptation_field_length(1) + adaptation_field(adaptation_field_length) + PMT
例如:
Structure
Visual Diagram
Demuxer
struct mts_pes {
// --> fixed: 48bit
uint32_t packet_start_code_prefix : 24; // 起始码:0000 0000 0000 0000 0000 0001
uint32_t stream_id : 8; // 指示基本流的类型和编号
uint32_t PES_packet_length : 16; // 指示PES包中跟随该字段最后字节的字节数
// --> fixed: 24bit
uint32_t reserved : 2; // '10'
uint32_t PES_scrambling_control : 2; // 有效载荷的加扰方式
uint32_t PES_priority : 1; // 有效载荷的优先级
uint32_t data_alignment_indicator : 1; // 关键帧
uint32_t copyright : 1; // 有效载荷的素材依靠版权所保护
uint32_t original_or_copy : 1; // 1:有效载荷的内容是原始的, 0:有效载荷的内容是复制的
uint32_t PTS_DTS_flags : 2; // 10:PTS存在, 11:PTS和DTS均存在, 00:无PTS也无DTS, 01:禁用
uint32_t ESCR_flag : 1; // 1:ESCR_base,ESCR_extend均存在, 0:不存在
uint32_t ES_rate_flag : 1; // 1:ES_rate存在, 0:不存在
uint32_t DSM_trick_mode_flag : 1; // 1:DSM_trick_mode_存在, 0:不存在
uint32_t additional_copy_info_flag : 1; // 1:additional_copy_info存在, 0:不存在
uint32_t PES_CRC_flag : 1; // 1:PES_CRC存在, 0:不存在
uint32_t PES_extension_flag : 1; // 1:PES_extension存在, 0:不存在
uint32_t PES_header_data_length : 8; // 1:PES_header_data存在, 0:不存在
// --> unfixed: ~
uint64_t DTS : 33; // if (PTS_DTS_flags == '11')
uint64_t PTS : 33; // if (PTS_DTS_flags == '10' || PTS_DTS_flags == '11')
uint64_t ESCR_base : 33; // if (ESCR_flag == '1')
uint32_t ESCR_extension : 9;
uint32_t ES_rate : 22; // if (ES_rate_flag == '1')
};
static inline uint32_t mts_dec_pes(struct mts_pes* pes,
const uint8_t* data,
size_t bytes) {
uint32_t i = 0;
if (0x00 != data[0] || 0x00 != data[1] || 0x01 != data[2]) return 0;
if (0x02 != ((data[6] >> 6) & 0x03)) return 0;
/**
* PES Table
*Table 2-17 PES packet
*/
memset(pes, 0, sizeof(mts_pes));
pes->packet_start_code_prefix = 0x000001;
pes->stream_id = data[3];
pes->PES_packet_length = (data[4] << 8) | data[5];
pes->reserved = (data[6] >> 6) & 0x03;
pes->PES_scrambling_control = (data[6] >> 4) & 0x03;
pes->PES_priority = (data[6] >> 3) & 0x01;
pes->data_alignment_indicator = (data[6] >> 2) & 0x01;
pes->copyright = (data[6] >> 1) & 0x01;
pes->original_or_copy = data[6] & 0x01;
pes->PTS_DTS_flags = (data[7] >> 6) & 0x03;
pes->ESCR_flag = (data[7] >> 5) & 0x01;
pes->ES_rate_flag = (data[7] >> 4) & 0x01;
pes->DSM_trick_mode_flag = (data[7] >> 3) & 0x1;
pes->additional_copy_info_flag = (data[7] >> 2) & 0x1;
pes->PES_CRC_flag = (data[7] >> 1) & 0x1;
pes->PES_extension_flag = data[7] & 0x1;
pes->PES_header_data_length = data[8];
i += 9;
// PTS_DTS
if (0x02 & pes->PTS_DTS_flags) {
pes->PTS = ((((uint64_t)data[i] >> 1) & 0x07) << 30) |
((uint64_t)data[i + 1] << 22) |
((((uint64_t)data[i + 2] >> 1) & 0x7F) << 15) |
((uint64_t)data[i + 3] << 7) |
((data[i + 4] >> 1) & 0x7F);
i += 5;
}
if (0x01 & pes->PTS_DTS_flags) {
pes->DTS = ((((uint64_t)data[i] >> 1) & 0x07) << 30) |
((uint64_t)data[i + 1] << 22) |
((((uint64_t)data[i + 2] >> 1) & 0x7F) << 15) |
((uint64_t)data[i + 3] << 7) |
((data[i + 4] >> 1) & 0x7F);
i += 5;
} else if (0x02 & pes->PTS_DTS_flags) {
pes->DTS = pes->PTS;
}
// ESCR
if (pes->ESCR_flag) {
pes->ESCR_base = ((((uint64_t)data[i] >> 3) & 0x07) << 30) |
(((uint64_t)data[i] & 0x03) << 28) |
((uint64_t)data[i + 1] << 20) |
((((uint64_t)data[i + 2] >> 3) & 0x1F) << 15) |
(((uint64_t)data[i + 2] & 0x3) << 13) |
((uint64_t)data[i + 3] << 5) |
((data[i + 4] >> 3) & 0x1F);
pes->ESCR_extension = ((data[i + 4] & 0x03) << 7) |
((data[i + 5] >> 1) & 0x7F);
i += 6;
}
// ES_rate
if (pes->ES_rate_flag) {
pes->ES_rate = ((data[i] & 0x7F) << 15) |
(data[i + 1] << 7) |
((data[i + 2] >> 1) & 0x7F);
i += 3;
}
// DSM_trick_mode
if (pes->DSM_trick_mode_flag) {
i += 1;
return 0;
// 事实上不止于1, 如果真遇到该标志再重新实现
}
// additional_copy_info
if (pes->additional_copy_info_flag) {
i += 1;
}
// PES_CRC
if (pes->PES_CRC_flag) {
i += 2;
}
// PES_extension
if (pes->PES_extension_flag) {
i += 1;
return 0;
// 事实上不止于1, 如果真遇到该标志再重新实现
}
if (pes->PES_packet_length > 0) {
if (pes->PES_packet_length < pes->PES_header_data_length + 3)
return 0;
pes->PES_packet_length -= pes->PES_header_data_length + 3;
}
if (pes->PES_packet_length < 0)
return 0;
return pes->PES_header_data_length + 9;
}
点播视频dts算法
dts = 初始值 + 90000 / video_frame_rate,初始值可以随便指定,但是最好不要取0,video_frame_rate就是帧率,比如23、30。
pts和dts是以timescale为单位的,1s = 90000 time scale , 一帧就应该是90000/video_frame_rate 个timescale。
用一帧的timescale除以采样频率就可以转换为一帧的播放时长。
点播音频dts算法
dts = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate,audio_samples_per_frame这个值与编解码相关,aac取值1024,mp3取值1158,audio_sample_rate是采样率,比如24000、41000。AAC一帧解码出来是每声道1024个sample,也就是说一帧的时长为1024/sample_rate秒。所以每一帧时间戳依次0,1024/sample_rate,...,1024*n/sample_rate秒。
直播视频的dts和pts应该直接用直播数据流中的时间,不应该按公式计算。
ES
ES层指的就是音视频数据。一般的,视频为H.264视频,音频为AAC音频。
H.264视频的ES层
打包h.264数据我们必须给视频数据加上一个nalu(Network Abstraction Layer unit),nalu包括start code和nalu header,start code固定为0x00000001(帧开始)或0x000001(帧中)。h.264的数据是由slice组成的,slice的内容包括:视频、sps、pps等。nalu type决定了后面的h.264数据内容。
F | 1B | forbidden_zero_bit,h.264规定必须取0 |
NRI | 2B | nal_ref_idc,取值0~3,指示这个nalu的重要性,I帧、sps、pps通常取3,P帧通常取2,B帧通常取0 |
Type | 5B | NALU类型 |
AAC音频的ES层
打包aac音频必须加上一个adts(Audio Data Transport Stream)头,共7Byte,adts包括fixed_header和variable_header两部分,各28bit。
syncword | 12b | 固定为0xfff |
id | 1b | 0表示MPEG-4,1表示MPEG-2 |
layer | 2b | 固定为00 |
protection_absent | 1b | 固定为1 |
profile | 2b | 取值0~3,1表示aac |
sampling_frequency_index | 4b | 表示采样率,0: 96000 Hz,1: 88200 Hz,2: 64000 Hz,3:48000 Hz,4: 44100 Hz,5: 32000 Hz,6: 24000 Hz,7: 22050 Hz,8: 16000 Hz,9: 12000 Hz,10: 11025 Hz,11: 8000 Hz,12: 7350 Hz |
private_bit | 1b | 固定为0 |
channel_configuration | 3b | 取值0~7,1: 1 channel: front-center,2: 2 channels: front-left, front-right,3: 3 channels: front-center, front-left, front-right,4: 4 channels: front-center, front-left, front-right, back-center |
original_copy | 1b | 固定为0 |
home | 1b | 固定为0 |
附录
#define MTS_PACKET_SIZE 188 // TS包长
#define MTS_TS_HEADER_SIZE 4
#define MTS_PES_HEADER_LEN 6
#define H264_NAL_IDR 5
#define H264_NAL_AUD 9
#define MTS_SYNC_BYTE 0x47 // TS起始
enum E_TS_MEDIA_TYPE {
TS_UNKNOWN = 0x00,
TS_AUDIO = 0x01,
TS_VIDEO = 0x02,
};
enum E_TS_PID {
TS_PID_PAT = 0x00,
TS_PID_CAT = 0x01,
TS_PID_SDT = 0x11,
};
enum E_PAT_TID {
PAT_TID_PAS = 0x00,
PAT_TID_CAS = 0x01,
PAT_TID_PMS = 0x02,
PAT_TID_SDS = 0x03,
PAT_TID_SDT = 0x42,
};
enum E_PSI_STREAM_TYPE {
STREAM_VIDEO_MPEG4 = 0x10,
STREAM_VIDEO_H264 = 0x1b,
STREAM_VIDEO_H265 = 0x24,
STREAM_VIDEO_SVAC = 0x80,
STREAM_AUDIO_MP3 = 0x04,
STREAM_AUDIO_AAC = 0x0f,
STREAM_AUDIO_G711A = 0x90,
STREAM_AUDIO_G711U = 0x91,
STREAM_AUDIO_G729 = 0x99,
STREAM_AUDIO_OPUS = 0x9C,
};