数字信号实际传送的是数据流,一般数据流包括以下三种:
ES流(Elementary Stream): 也叫基本码流,包含视频、音频或数据的连续码流。
PES流(Packet Elementary Stream): 也叫打包的基本码流, 是将基本的码流ES流根据需要分成长度不等的数据包, 并加上包头就形成了打包的基本码流PES流。PES是打包过的ES,已经插入PTS和DTS,一般是一个PES包为一帧图像。
DVD节目中的MPEG2格式,是MPEG2-PS,全称是Program Stream,简称PS流。TS的全称则是Transport Stream。MPEG2-PS主要应用于存储的具有固定时长的节目,如DVD电影,而MPEG-TS则主要应用于实时传送的节目,比如实时广播的电视节目。这两种格式的主要区别是什么呢?你将DVD上的VOB文件的前面一截剪掉(或者干脆就是数据损坏),那么就会导致整个文件无法解码,而电视节目是你任何时候 打开电视机都能解码(收看)的,所以,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。
TS流(Transport Stream): 也叫传输流,ts流是由很多不同种类的包所组成的,这些数据包都是188个字节大小,这188个字节包含两部分,包头和负载,包头包括同步信息,包信息等等,而负载则是传输的数据,而这些负载则可以组成PES流或者私有流等等数据流.举 例说,一个TS流包括100个包,其中PSI信息包占20个,PES数据包80个,此TS流中只有一套节目流,不含有私有流,所以从这80个PES包中 的负载连接在一起,就是2个PES流(视频,音频),如果每个PES包的负载长度为100字节,则这两个PES流一共长度为8000个字节.假设其中视频 的PES流长度为6000字节.则视频的6000字节的PES流,是由PES包组成的.PES包没有固定的长度,而是由包头部的数据给出.而PS也是类似 TS流分解的方式,逆向的由PES包封装成包,其中要添加PACKET_HEAD,SYSTEM_HEAD等信息.所以上次所做的程序,并不是 TS->PS的转换,而是从一个复杂的TS流中,过滤去一套节目,构造出一个简单的TS流的过程。
封装 : 就是捆绑打包, 将画面视频文件和音轨文件打包在一起, 并按照一定规则建立排序和索引, 便于播放器或播放软件来索引播放. 包括AVI / PS(Program Stream)/ TS(Transport Stream)/ MKV(Matroska)等。
TS与PS的区别:
TS流与PS流的区别在于TS流的包结构是固定长度的,而PS流的包结构是可变长度。 PS包与TS包在结构上的这种差异,导致了它们对传输误码具有不同的抵抗能力,因而应用的环境也有所不同。TS码流由于采用了固定长度的包结构,当传输误码破坏了某一TS包的同步信息时,接收机可在固定的位置检测它后面包中的同步信息,从而恢复同步,避免了信息丢失。而PS包由于长度是变化的,一旦某一 PS包的同步信息丢失,接收机无法确定下一包的同步位置,就会造成失步,导致严重的信息丢失。因此,在信道环境较为恶劣,传输误码较高时,一般采用TS码流;而在信道环境较好,传输误码较低时,一般采用PS码流由于TS码流具有较强的抵抗传输误码的能力,因此目前在传输媒体中进行传输的MPEG-2码流基本上都采用了TS码流的包。
TS包的结构图:
TS包的解析:
大家都知道TS包是以0x47开始,是一个同步字节。而且每个包长都是188个字节。于是我们可以非常方便的同步TS流,比如说有丢包的情况。只要找连续的n个(比如说5个)188个长度的TS包,如果都是以0x47开始的话,那么基本上可以认为这些包是TS的合法包。
TS 流解码过程:
1. 获取TS中的PAT
2. 获取TS中的PMT
3. 根据PMT可以知道当前网络中传输的视频(音频)类型(H264),相应的PID,PCR的PID等信息。
4. 设置demux 模块的视频Filter 为相应视频的PID和stream type等。
5. 从视频Demux Filter 后得到的TS数据包中的payload 数据就是 one piece of PES,在TS header中有一些关于此 payload属于哪个 PES的 第多少个数据包。因此软件中应该将此payload中的数据copy到PES的buffer中,用于拼接一个PES包。
6. 拼接好的PES包的包头会有 PTS,DTS信息,去掉PES的header就是 ES。
7. 直接将 被被拔掉 PES包头的ES包送给decoder就可以进行解码。解码出来的数据就是一帧一帧的视频数据,这些数据至少应当与PES中的PTS关联一下,以便进行视音频同步。
8. I,B,B,P 信息是在ES中的。
名词定义
TS的包头
中文表格如下:
TS包头的定义:
//Transport Stream header
typedef struct TS_header
{
unsigned sync_byte :8; //同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47的
unsigned transport_error_indicator :1; //传输错误标志位,一般传输错误的话就不会处理这个包了
unsigned payload_unit_start_indicator :1; //有效负载的开始标志,根据后面有效负载的内容不同功能也不同
// payload_unit_start_indicator为1时,在前4个字节之后会有一个调整字节,它的数值决定了负载内容的具体开始位置。
unsigned transport_priority :1; //传输优先级位,1表示高优先级
unsigned PID :13; //有效负载数据的类型
unsigned transport_scrambling_control :2; //加密标志位,00表示未加密
unsigned adaption_field_control :2; //调整字段控制,。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。
unsigned continuity_counter :4; //一个4bit的计数器,范围0-15,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。
} TS_header;
//特殊参数说明:
//sync_byte:0x47
//payload_unit_start_indicator:0x01表示含有PSI或者PES头
//PID:0x0表示后面负载内容为PAT,不同的PID表示不同的负载
//adaption_field_control:
// 0x0: // reserved for future use by ISO/IEC
// 0x1: // 无调整字段,仅含有效负载
// 0x2: // 仅含调整字段,无有效负载
// 0x3: // 调整字段后含有效负载
TS包头的解析:
// Parse TS header
int Parse_TS_header(unsigned char *pTSBuf, TS_header *pheader)
{
pheader->sync_byte = pTSBuf[0];
if (pheader->sync_byte != 0x47)
return -1;
pheader->transport_error_indicator = pTSBuf[1] >> 7;
pheader->payload_unit_start_indicator = pTSBuf[1] >> 6 & 0x01;
pheader->transport_priority = pTSBuf[1] >> 5 & 0x01;
pheader->PID = (pTSBuf[1] & 0x1F) << 8 | pTSBuf[2];
pheader->transport_scrambling_control = pTSBuf[3] >> 6;
pheader->adaption_field_control = pTSBuf[3] >> 4 & 0x03;
pheader->continuity_counter = pTSBuf[3] & 0x0F;
return 0;
}
如下为一个TS包数据:
0x47 0x40 0x00 0x12 0x00 0x00 0xb0 0x0d 0x00 0x00 0xc1 0x00 0x00 0x00 0x01 0xe3 0xe8 0xf0 0x0b 0xd7 0x79 0xff 0xff 0xff 0xff 0xff 0xff 0xff......
分析知道前四位0x47 0x40 0x00 0x12TS头部即为TS包头数据,解析如下:
sync_byte :0x47
transport_error_indicator: 0x00
payload_unit_start_indicator: 0x01
transport_priority : 0x00
PID :0x0000
transport_scrambling_control :0x00
adaptation_field_control :0x01
continuity_counter :0x02
PID = 0x0000,表示此TS包的内容为PSI信息表格的PAT表格数据,在4字节的TS包头之后的第一个字节的Point_field = 0x00, 表示偏移量为0,即紧随其后的即为
PAT的数据信息。
PAT的定义
如果TS头中PID = 0x0000,表明包的内容为PAT。
PAT表格定义如下:
typedef struct TS_PAT_Program
{
unsigned program_number :16; //节目号
unsigned program_map_PID :13; //节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
}TS_PAT_Program;
//PAT表结构体
typedef struct TS_PAT
{
unsigned table_id : 8; //固定为0x00 ,标志是该表是PAT
unsigned section_syntax_indicator : 1; //段语法标志位,固定为1
unsigned zero : 1; //0
unsigned reserved_1 : 2; // 保留位
unsigned section_length : 12; //表示这个字节后面有用的字节数,包括CRC32
unsigned transport_stream_id : 16; //该传输流的ID,区别于一个网络中其它多路复用的流
unsigned reserved_2 : 2;// 保留位
unsigned version_number : 5; //范围0-31,表示PAT的版本号
unsigned current_next_indicator : 1; //发送的PAT是当前有效还是下一个PAT有效
unsigned section_number : 8; // 当前段号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
unsigned last_section_number : 8; //最后一个分段的号码,(section_number和last_section_number的功能是当PAT的内容大于184字节时,PAT表会分成多个段(sections),解复用程序必须在全部接收完完成在进行PAT的分析)
从for()开始,就是描述了当前流中的频道数目(N),每一个频道对应的PMT PID是什么。解复用程序需要和上图类似的循环来接收所有的频道号码和对应的PMT PID,并把这些信息在缓冲区中保存起来,在后来的处理中需要使用到PMT PID。
std::vector<TS_PAT_Program> program;
unsigned reserved_3 : 3; // 保留位
unsigned network_PID : 13; //网络信息表(NIT)的PID,节目号为0时对应的PID为network_PID
unsigned CRC_32 : 32; //CRC32校验码
} TS_PAT;
解析代码如下:
HRESULT CTS_Stream_Parse::adjust_PAT_table( TS_PAT * packet, unsigned char * buffer)
{
packet->table_id = buffer[0];
packet->section_syntax_indicator = buffer[1] >> 7;
packet->zero = buffer[1] >> 6 & 0x1;
packet->reserved_1 = buffer[1] >> 4 & 0x3;
packet->section_length = (buffer[1] & 0x0F) << 8 | buffer[2];
packet->transport_stream_id = buffer[3] << 8 | buffer[4];
packet->reserved_2 = buffer[5] >> 6;
packet->version_number = buffer[5] >> 1 & 0x1F;
packet->current_next_indicator = (buffer[5] << 7) >> 7;
packet->section_number = buffer[6];
packet->last_section_number = buffer[7];
int len = 0;
len = 3 + packet->section_length;
packet->CRC_32 = (buffer[len-4] & 0x000000FF) << 24
| (buffer[len-3] & 0x000000FF) << 16
| (buffer[len-2] & 0x000000FF) << 8
| (buffer[len-1] & 0x000000FF);
int n = 0;
for ( n = 0; n < packet->section_length - 12; n += 4 )
{
unsigned program_num = buffer[8 + n ] << 8 | buffer[9 + n ];
packet->reserved_3 = buffer[10 + n ] >> 5;
packet->network_PID = 0x00;
if ( program_num == 0x00)
{
packet->network_PID = (buffer[10 + n ] & 0x1F) << 8 | buffer[11 + n ];
TS_network_Pid = packet->network_PID; //记录该TS流的网络PID
TRACE(" packet->network_PID %0x /n/n", packet->network_PID );
}
else
{
TS_PAT_Program PAT_program;
PAT_program.program_map_PID = (buffer[10 + n] & 0x1F) << 8 | buffer[11 + n];
PAT_program.program_number = program_num;
packet->program.push_back( PAT_program );
TS_program.push_back( PAT_program );//向全局PAT节目数组中添加PAT节目信息
}
}
return 0;
}
因此,PAT数据解析结果如下:
PAT数据
table_id :0x00 //8
section_syntax_indicator :0x01 // 1
'0' :0x00 // 1
reserved 0x03 // 2
section_length :0x00d // 12
transport_stream_id :0x0000 // 16
reserved :0x03 // 2
version_number :0x00 // 5
current_next_indicator :0x01 // 1
section_number :0x00 // 8
last_section_number :0x00 // 8
program_number :0x0001 // 16
reserved :0x07 // 3
program_map_PID :0x03e8 // 13
CRC :0x f0 0b d7 79
//由解析结构可知,该PAT表格中没有网络信息包信息,只包含一个节目,其PID为0x03e8。
PMT定义如下
如果TS头中PID = 0x03e8,表明包的内容为PMT。
PMT结构定义:
typedef struct TS_PMT_Stream
{
unsigned stream_type : 8; //指示特定PID的节目元素包的类型。该处PID由elementary PID指定
unsigned elementary_PID : 13; //该域指示TS包的PID值。这些TS包含有相关的节目元素
unsigned ES_info_length : 12; //前两位bit为00。该域指示跟随其后的描述相关节目元素的byte数
unsigned descriptor;
}TS_PMT_Stream; //--------
//PMT 表结构体
typedef struct TS_PMT
{
unsigned table_id : 8; //固定为0x02, 表示PMT表
--------byte: 1-------------
unsigned section_syntax_indicator : 1; //固定为0x01
unsigned zero : 1; //0x01
unsigned reserved_1 : 2; //0x03
unsigned section_length : 12;//首先两位bit置为00,它指示段的byte数,由段长度域开始,包含CRC。
--------byte: 4-------------
unsigned program_number : 16;// 指出该节目对应于可应用的Program map PID
--------byte: 6-------------
unsigned reserved_2 : 2; //0x03
unsigned version_number : 5; //指出TS流中Program map section的版本号
unsigned current_next_indicator : 1; //当该位置1时,当前传送的Program map section可用;
--------byte: 7-------------
//当该位置0时,指示当前传送的Program map section不可用,下一个TS流的Program map section有效。
unsigned section_number : 8; //固定为0x00
unsigned last_section_number : 8; //固定为0x00
--------byte: 9-------------
unsigned reserved_3 : 3; //0x07
unsigned PCR_PID : 13; //指明TS包的PID值,该TS包含有PCR域,
//该PCR值对应于由节目号指定的对应节目。
//如果对于私有数据流的节目定义与PCR无关,这个域的值将为0x1FFF。
--------byte: 11-------------
unsigned reserved_4 : 4; //预留为0x0F
unsigned program_info_length : 12; //前两位bit为00。该域指出跟随其后对节目信息的描述的byte数。
--------byte: 13-------------
std::vector<TS_PMT_Stream> PMT_Stream; //每个元素包含8位, 指示特定PID的节目元素包的类型。该处PID由elementary PID指定
unsigned reserved_5 : 3; //0x07
unsigned reserved_6 : 4; //0x0F
------single: 5 Bytes---------
unsigned CRC_32 : 32;
} TS_PMT;
解析代码为:
HRESULT CTS_Stream_Parse::adjust_PMT_table ( TS_PMT * packet, unsigned char * buffer )
{
packet->table_id = buffer[0];
packet->section_syntax_indicator = buffer[1] >> 7;
packet->zero = buffer[1] >> 6 & 0x01;
packet->reserved_1 = buffer[1] >> 4 & 0x03;
packet->section_length = (buffer[1] & 0x0F) << 8 | buffer[2];
packet->program_number = buffer[3] << 8 | buffer[4];
packet->reserved_2 = buffer[5] >> 6;
packet->version_number = buffer[5] >> 1 & 0x1F;
packet->current_next_indicator = (buffer[5] << 7) >> 7;
packet->section_number = buffer[6];
packet->last_section_number = buffer[7];
packet->reserved_3 = buffer[8] >> 5;
packet->PCR_PID = ((buffer[8] << 8) | buffer[9]) & 0x1FFF;
PCRID = packet->PCR_PID;
packet->reserved_4 = buffer[10] >> 4;
packet->program_info_length = (buffer[10] & 0x0F) << 8 | buffer[11];
// Get CRC_32
int len = 0;
len = packet->section_length + 3;
packet->CRC_32 = (buffer[len-4] & 0x000000FF) << 24
| (buffer[len-3] & 0x000000FF) << 16
| (buffer[len-2] & 0x000000FF) << 8
| (buffer[len-1] & 0x000000FF);
int pos = 12;
// program info descriptor
if ( packet->program_info_length != 0 )
pos += packet->program_info_length;
// Get stream type and PID
for ( ; pos <= (packet->section_length + 2 ) - 4; )
{
TS_PMT_Stream pmt_stream;
pmt_stream.stream_type = buffer[pos];
packet->reserved_5 = buffer[pos+1] >> 5;
pmt_stream.elementary_PID = ((buffer[pos+1] << 8) | buffer[pos+2]) & 0x1FFF;
packet->reserved_6 = buffer[pos+3] >> 4;
pmt_stream.ES_info_length = (buffer[pos+3] & 0x0F) << 8 | buffer[pos+4];
pmt_stream.descriptor = 0x00;
if (pmt_stream.ES_info_length != 0)
{
pmt_stream.descriptor = buffer[pos + 5];
for( int len = 2; len <= pmt_stream.ES_info_length; len ++ )
{
pmt_stream.descriptor = pmt_stream.descriptor<< 8 | buffer[pos + 4 + len];
}
pos += pmt_stream.ES_info_length;
}
pos += 5;
packet->PMT_Stream.push_back( pmt_stream );
TS_Stream_type.push_back( pmt_stream );
}
return 0;
}
举例如下:
0x47 0x43 0xe8 0x120x00 0x02 0xb0 0x12 0x00 0x01 0xc1 0x00 0x00 0xe3 0xe9 0xf0 0x00 0x1b 0xe3 0xe9 0xf0 0x00 0xf0 0xaf 0xb4 0x4f 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
TS头部
sync_byte :0x47
transport_error_indicator: 0x00
payload_unit_start_indicator: 0x01
transport_priority : 0x00
PID :0x03e8
transport_scrambling_control :0x00
adaptation_field_control :0x01
continuity_counter :0x02
PMT数据
table_id :0x02 // 8
section_syntax_indicator :0x01 // 1
'0' :0x00 // 1
reserved :0x03 // 2
section_length : 0x012 // 12
program_number :0x00 01 // 16
reserved :0x03 // 2
version_number :0x00 // 5
current_next_indicator 0x01 // 1
section_number :0x00 // 8
last_section_number :0x00 // 8
reserved 0x07 // 3
PCR_PID :0x03 e9 // PCR(节目参考时钟)所在TS分组的PID // 13
reserved :0x0f //4
program_info_length :0x000 // 12
stream_type :0x1b // 8
reserved 0x07 // 3
elementary_PID :0x03 e9 // 13//该节目中包括的视频流,音频流等对应的TS分组的PID
reserved :0x0f // 4
ES_info_length :0x000 // 12
CRC : 0xf0 af b4 4f
SDT的定义
DVB系统提出了一个SDT表格,该表格标志一个节目的名称,并且能和 PMT中的PID联系起来,这样用户就可以通过直接选择节目名称来选择节目了. SDT, Service description section,服务描述段 SDT可以提供的信息包括: (1) 该节目是否在播放中 (2) 该节目是否被加密 (3) 该节目的名称
SDT定义如下: 各字段定义如下:
table_id:8bits的ID,可以是0x42,表示描述的是当前流的信息,也可以是0x46,表示是其他流的信息(EPG使用此参数)
section_syntax_indicator:段语法标志,一般是''1''
reserved_future_used:2bits保留未来使用
reserved:1bit保留位,防止控制字冲突,一般是''0'',也有可能是''1''
section_length:12bits的段长度,单位是Bytes,从transport_stream_id开始,到CRC_32结束(包含)
transport_stream_id:16bits当前描述的流ID
reserved:2bits保留位
version_number:5bits的版本号码,如果数据更新则此字段递增1
current_next_indicator:当前未来标志,一般是''0'',表示当前马上使用.
original_netword_id:16bits的原始网络ID号
reserved_future_use:8bits保留未来使用位
接下来是N个节目信息的循环:
service_id:16 bits的服务器ID,实际上就是PMT段中的program_number.
reserved_future_used:6bits保留未来使用位
EIT_schedule_flag:1bit的EIT信息,1表示当前流实现了该节目的EIT传送
EIT_present_following_flag:1bits的EIT信息,1表示当前流实现了该节目的EIT传送
running_status:3bits的运行状态信息:1-还未播放 2-几分钟后马上开始,3-被暂停播出,4-正在播放,其他---保留
free_CA_mode:1bits的加密信息,''1''表示该节目被加密. 紧 接着的是描述符,一般是Service descriptor,分析此描述符可以获取servive_id指定的节目的节目名称.具体格式请参考 EN300468中的Service descriptor部分.
PES的定义
packet_start_code_prefix(24) 开始码字为0X00 00 01
stream_id(8) 原始流的类型和数目,取值从1011 1100到1111 1111之间。各值含义具体见13818-1。
PES_packet_length(16) 表示从此字节之后PES包长(单位字节)。0表示PES包长不限制,且只能用于视频PES。
PES_scrambling_control(2) PES有效负载的加密模式。00表示不加密,其余表示用户自定义。
PES_priority(1) PES数据包的优先级。类似于TS的此字段。
data_alignment_indicator(1) 为1时,表明此分组头部之后紧跟着 数据流描述子中定义的访问单元类型。
copyright(1) 版权,1表示有版权,具体见版权描述子13818-1 1-2-6-24。0表示没有。
original_or_copy(1) 1表示原始数据,0表示备份
PTS_DTS_flag(2) 10表示含有PTS字段,11表示含有PTS和DTS字段,00表示不含有PTS和DTS,01无定义。
ESCR_flag(1) 1表示ESCR在PES首部出现,0表示不出现
ES_rate_flag(1) 1表示PES分组含有ES_rate字段。0表示不含有。
DSM_trick_mode_flag(1) 1表示有8位的trick_mode_flag字段,0表示不出现此字段。只对DSM有效。在广播中不用。
additional_copy_info_flag(1) 1表示有copy_info_flag字段,0表示不出现此字段。
PES_CRC_flag(1) 1表示PES分组中有CRC字段,0表示不出现此字段。
PES_extention_flag(1) 1表示扩展字段在PES包头存在,0表示扩展字段不存在
PES_header_data_length(8) 表示可选字段和填充字段所占的字节数。
填充字节(10)。
解析TS的例子详见http://download.csdn.net/detail/evsqiezi/6908733,简介如下:
在VC2008工程下,一个TS的解析例子,含有一个视频文件test.264,解析出来的文件为1.264。
解析步骤为:
1 取得PAT.
2 取得PMT.
3 取得PES.
4 取得ES.