2025年3月28日 星期五 甲辰(龙)年 月廿七 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 文件格式与编码

MPEG-PS封装格式

时间:09-27来源:作者:点击数:21
城东书院 www.cdsy.xyz

据传输媒体的质量不同,MPEG-2中定义了两种复合信息流:传送流(TS:TransportStream)和节目流(PS:ProgramStream)

PS文件分为3层:ps层(Program Stream)、pes层(Packet Elemental Stream)、es层(Elementary Stream)。es层就是音视频数据,pes层是在音视频数据上加了时间戳等对数据帧的说明信息,ps层是在pes层上加入了数据流识别和传输的必要信息。

1.Ps和Ts的区别

TS流与PS流的区别在于TS流的包结构是固定长度的,而PS流的包结构是可变长度的。

 PS包与TS包在结构上的这种差异,导致了它们对传输误码具有不同的抵抗能力,因而应用的环境也有所不同。TS码流由于采用了固定长度的包结构,当传输误码破坏了某一TS包的同步信息时,接收机可在固定的位置检测它后面包中的同步信息,从而恢复同步,避免了信息丢失。而PS包由于长度是变化的,一旦某一PS包的同步信息丢失,接收机无法确定下一包的同步位置,就会造成失步,导致严重的信息丢失。因此,在信道环境较为恶劣,传输误码较高时,一般采用TS码流;而在信道环境较好,传输误码较低时,一般采用PS码流。由于TS码流具有较强的抵抗传输误码的能力,因此目前在传输媒体中进行传输的MPEG-2码流基本上都采用了TS码流的包格式。

  MPEG2-PS主要应用于存储的具有固定时长的节目,如DVD电影,而MPEG-TS则主要应用于实时传送的节目,比如实时广播的电视节目。这两种格式的主要区别是什么呢?你将DVD上的VOB文件的前面一截剪掉(或者干脆就是数据损坏),那么就会导致整个文件无法解码,而电视节目是你任何时候打开电视机都能解码(收看)的,所以,MPEG2-TS格式的特点就是要求从视频流的任一片段开始都是可以独立解码的。PS主要用于播放或编辑系统, TS主要用于数据传输。

2.Ps文件结构

一个完整的MPEG-2文件就是一个PS流文件。使用Elecard StreamAnalyzer打开一个MPEG-2文件,得到下面信息。

可以看出来,正如我们上面所说的,整个文件分为3层。首先整个文件被分为了一个个的ProgramPack,然后ProgramPack里面包含了ProgramPack header和Pes包,Pes包里又包含了Pes header和音频编码数据(MPEG-2 Audio)或视频编码数据(MPEG-2 Video)。

下面就分别来分析PS文件的 Ps和Pes包。

2.1.Ps层

Ps层主要由pack header和数据组成,pack header中各个bit的意义如下

我们可以通过分析一个示例文件来了解它

其中system_clock_reference的意义如下

SCR and SCR_ext together are the System Clock Reference, a counter driven at 27MHz, used as a reference to synchronize streams. The clock is divided by 300 (to match the 90KHz clocks such as PTS/DTS), the quotient is SCR (33 bits), the remainder is SCR_ext (9 bits)

参考自:http://dvd.sourceforge.net/dvdinfo/packhdr.html

system_clock_reference_base的计算方法为:

  • scr += packet_size * 90000LL / (mux_rate * 50LL);

参考自:ffmpeg-3.3.1 Mpegenc.c

基本信息了解完了,下面就开始定义这个结构了,一开始是采用了位域来定义的

  • struct pack_header
  • {
  • unsigned char pack_start_code[4];
  • unsigned char system_clock_reference_base21 : 2;
  • unsigned char marker_bit : 1;
  • unsigned char system_clock_reference_base1 : 3;
  • unsigned char fix_bit : 2;
  • unsigned char system_clock_reference_base22;
  • unsigned char system_clock_reference_base31 : 2;
  • unsigned char marker_bit1 : 1;
  • unsigned char system_clock_reference_base23 : 5;
  • unsigned char system_clock_reference_base32;
  • unsigned char system_clock_reference_extension1 : 2;
  • unsigned char marker_bit2 : 1;
  • unsigned char system_clock_reference_base33 : 5;
  • unsigned char marker_bit3 : 1;
  • unsigned char system_clock_reference_extension2 : 7;
  • unsigned char program_mux_rate1;
  • unsigned char program_mux_rate2;
  • unsigned char marker_bit5 : 1;
  • unsigned char marker_bit4 : 1;
  • unsigned char program_mux_rate3 : 6;
  • unsigned char pack_stuffing_length : 3;
  • unsigned char reserved : 5;
  • pack_header()
  • {
  • pack_start_code[0] = 0x00;
  • pack_start_code[1] = 0x00;
  • pack_start_code[2] = 0x01;
  • pack_start_code[3] = 0xBA;
  • fix_bit = 0x01;
  • marker_bit = 0x01;
  • marker_bit1 = 0x01;
  • marker_bit2 = 0x01;
  • marker_bit3 = 0x01;
  • marker_bit4 = 0x01;
  • marker_bit5 = 0x01;
  • reserved = 0x1F;
  • pack_stuffing_length = 0x00;
  • system_clock_reference_extension1 = 0;
  • system_clock_reference_extension2 = 0;
  • }
  • void getSystem_clock_reference_base(UINT64 &_ui64SCR)
  • {
  • _ui64SCR = (system_clock_reference_base1 << 30) | (system_clock_reference_base21 << 28)
  • | (system_clock_reference_base22 << 20) | (system_clock_reference_base23 << 15)
  • | (system_clock_reference_base31 << 13) | (system_clock_reference_base32 << 5)
  • | (system_clock_reference_base33);
  • }
  • void setSystem_clock_reference_base(UINT64 _ui64SCR)
  • {
  • system_clock_reference_base1 = (_ui64SCR >> 30) & 0x07;
  • system_clock_reference_base21 = (_ui64SCR >> 28) & 0x03;
  • system_clock_reference_base22 = (_ui64SCR >> 20) & 0xFF;
  • system_clock_reference_base23 = (_ui64SCR >> 15) & 0x1F;
  • system_clock_reference_base31 = (_ui64SCR >> 13) & 0x03;
  • system_clock_reference_base32 = (_ui64SCR >> 5) & 0xFF;
  • system_clock_reference_base33 = _ui64SCR & 0x1F;
  • }
  • void getProgram_mux_rate(unsigned int &_uiMux_rate)
  • {
  • _uiMux_rate = (program_mux_rate1 << 14) | (program_mux_rate2 << 6) | program_mux_rate3;
  • }
  • void setProgram_mux_rate(unsigned int _uiMux_rate)
  • {
  • program_mux_rate1 = (_uiMux_rate >> 14) & 0xFF;
  • program_mux_rate2 = (_uiMux_rate >> 6) & 0xFF;
  • program_mux_rate3 = _uiMux_rate & 0x3F;
  • }
  • };

这样的好处是可以直接通过

  • pack_header header;
  • header.setProgram_mux_rate(25200);
  • header.setSystem_clock_reference_base(0);
  • os.write((char *)&header, sizeof(header));

来写入文件,但是不方便抽象成类,所以就参考ffmpeg使用了put_bits的方式

  • class PackHeader : public HeaderBase
  • {
  • public:
  • UINT64 SCRBase;
  • UINT8 SCRExt;
  • UINT32 programMuxRate;
  • UINT8 stuffingLength;
  • PackHeader();
  • virtual ~PackHeader();
  • int Serialize();
  • };

然后在类中加一个序列化函数,来将整个类序列化

  • int PackHeader::Serialize()
  • {
  • int calcBinaryBitLen = 32 //pack_start_code
  • + 2 // '01'
  • + 3 //system_clock_reference_base [32..30]
  • + 1 //marker_bit
  • + 15 //system_clock_reference_base [29..15]
  • + 1 //marker_bit
  • + 15 //system_clock_reference_base [14..0]
  • + 1 //marker_bit
  • + 9 //system_clock_reference_extension
  • + 1 //marker_bit
  • + 22 // program_mux_rate
  • + 1 //marker_bit
  • + 1 //marker_bit
  • + 5 //reserved
  • + 3; //pack_stuffing_length
  • if (stuffingLength > 0)
  • {
  • for (int i = 0; i < stuffingLength; i++)
  • {
  • calcBinaryBitLen += 8;
  • }
  • }
  • if ((calcBinaryBitLen / 8) > binaryLen)
  • {
  • if (binary)
  • delete[] binary;
  • binary = new BYTE[calcBinaryBitLen / 8];
  • }
  • binaryLen = calcBinaryBitLen / 8;
  • BYTE* p = binary;
  • bits_buffer_t bw;
  • bits_initwrite(&bw, binaryLen, p);
  • bits_write(&bw, 32, PACK_HEADER_START_CODE); //pack_start_code
  • bits_write(&bw, 2, 0x1); // '01'
  • bits_write(&bw, 3, (SCRBase >> 30) & 0x07); //system_clock_reference_base [32..30]
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 15, (SCRBase >> 15) & 0x7FFF); //system_clock_reference_base [29..15]
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 15, SCRBase & 0x7FFF); //system_clock_reference_base [14..0]
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 9, SCRExt); //system_clock_reference_extension
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 22, programMuxRate & 0x3FFFFF); // program_mux_rate
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 1, 1); //marker_bit
  • bits_write(&bw, 5, 0x1F); //reserved
  • bits_write(&bw, 3, stuffingLength & 0x07); //pack_stuffing_length
  • if (stuffingLength > 0)
  • {
  • for (int i = 0; i < stuffingLength; i++)
  • {
  • bits_write(&bw, 8, 0xFF); //stuffing
  • }
  • }
  • return 1;
  • }

对于DVD而言,一般开始的pack里面还有一个System header

我们也可以通过分析一个示例文件来了解它

2.2.Pes层

Pes层由编码的音频或视频数据(es)加上Pes头组成的,Pes头主要是通过PTS和DTS来提供音视频同步的信息,Pes头的各个bit的意义如下所示

Pes头之后紧跟着的就是编码的音频或视频数据(es)了,对于DVD而言,一个program pack的大小问0x800,所以一帧MPEG-2视频被分在多个Pes包里,不够一个包的就写在下一帧的第一个pack里,或在Pes Header后面填充FF(PES_header_data_length要加上填充的字节数)。

城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
上一篇:MXF文件结构浅析 下一篇:PGS字幕
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐