据传输媒体的质量不同,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的区别

S流与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)
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[];
unsigned char system_clock_reference_base21 : ;
unsigned char marker_bit : ;
unsigned char system_clock_reference_base1 : ;
unsigned char fix_bit : ;
unsigned char system_clock_reference_base22;
unsigned char system_clock_reference_base31 : ;
unsigned char marker_bit1 : ;
unsigned char system_clock_reference_base23 : ;
unsigned char system_clock_reference_base32;
unsigned char system_clock_reference_extension1 : ;
unsigned char marker_bit2 : ;
unsigned char system_clock_reference_base33 : ;
unsigned char marker_bit3 : ;
unsigned char system_clock_reference_extension2 : ;
unsigned char program_mux_rate1;
unsigned char program_mux_rate2;
unsigned char marker_bit5 : ;
unsigned char marker_bit4 : ;
unsigned char program_mux_rate3 : ;
unsigned char pack_stuffing_length : ;
unsigned char reserved : ; pack_header()
{
pack_start_code[] = 0x00;
pack_start_code[] = 0x00;
pack_start_code[] = 0x01;
pack_start_code[] = 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 = ;
system_clock_reference_extension2 = ;
} void getSystem_clock_reference_base(UINT64 &_ui64SCR)
{
_ui64SCR = (system_clock_reference_base1 << ) | (system_clock_reference_base21 << )
| (system_clock_reference_base22 << ) | (system_clock_reference_base23 << )
| (system_clock_reference_base31 << ) | (system_clock_reference_base32 << )
| (system_clock_reference_base33);
} void setSystem_clock_reference_base(UINT64 _ui64SCR)
{
system_clock_reference_base1 = (_ui64SCR >> ) & 0x07;
system_clock_reference_base21 = (_ui64SCR >> ) & 0x03;
system_clock_reference_base22 = (_ui64SCR >> ) & 0xFF;
system_clock_reference_base23 = (_ui64SCR >> ) & 0x1F;
system_clock_reference_base31 = (_ui64SCR >> ) & 0x03;
system_clock_reference_base32 = (_ui64SCR >> ) & 0xFF;
system_clock_reference_base33 = _ui64SCR & 0x1F;
} void getProgram_mux_rate(unsigned int &_uiMux_rate)
{
_uiMux_rate = (program_mux_rate1 << ) | (program_mux_rate2 << ) | program_mux_rate3;
} void setProgram_mux_rate(unsigned int _uiMux_rate)
{
program_mux_rate1 = (_uiMux_rate >> ) & 0xFF;
program_mux_rate2 = (_uiMux_rate >> ) & 0xFF;
program_mux_rate3 = _uiMux_rate & 0x3F;
}
};

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

pack_header header;
header.setProgram_mux_rate();
header.setSystem_clock_reference_base();
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 = //pack_start_code
+ // '01'
+ //system_clock_reference_base [32..30]
+ //marker_bit
+ //system_clock_reference_base [29..15]
+ //marker_bit
+ //system_clock_reference_base [14..0]
+ //marker_bit
+ //system_clock_reference_extension
+ //marker_bit
+ // program_mux_rate
+ //marker_bit
+ //marker_bit
+ //reserved
+ ; //pack_stuffing_length if (stuffingLength > )
{
for (int i = ; i < stuffingLength; i++)
{
calcBinaryBitLen += ;
}
} if ((calcBinaryBitLen / ) > binaryLen)
{
if (binary)
delete[] binary; binary = new BYTE[calcBinaryBitLen / ];
} binaryLen = calcBinaryBitLen / ; BYTE* p = binary;
bits_buffer_t bw; bits_initwrite(&bw, binaryLen, p);
bits_write(&bw, , PACK_HEADER_START_CODE); //pack_start_code
bits_write(&bw, , 0x1); // '01'
bits_write(&bw, , (SCRBase >> ) & 0x07); //system_clock_reference_base [32..30]
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , (SCRBase >> ) & 0x7FFF); //system_clock_reference_base [29..15]
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , SCRBase & 0x7FFF); //system_clock_reference_base [14..0]
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , SCRExt); //system_clock_reference_extension
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , programMuxRate & 0x3FFFFF); // program_mux_rate
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , ); //marker_bit
bits_write(&bw, , 0x1F); //reserved
bits_write(&bw, , stuffingLength & 0x07); //pack_stuffing_length if (stuffingLength > )
{
for (int i = ; i < stuffingLength; i++)
{
bits_write(&bw, , 0xFF); //stuffing
}
}
return ;
}

对于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要加上填充的字节数)。

MPEG-PS封装格式的更多相关文章

  1. FFmpeg封装格式处理

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10506636.html FFmpeg封装格式处理相关内容分为如下几篇文章: [1]. F ...

  2. 音视频处理之PS封装的介绍与使用20180928

    1.PS封装介绍MPEG2-PS是一种多路复用数字音频,视频等的封装容器.PS是Program Stream(程序流或节目流)的简称.程序流将一个或多个分组但有共同的时间基准的基本数据流(PES)合并 ...

  3. FLV封装格式及分析器工具

    http://blog.csdn.net/leixiaohua1020/article/details/17934487 FLV封装原理 FLV格式的封装原理,贴上来辅助学习之用.     FLV(F ...

  4. 最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)

    ===================================================== 最简单的基于FFmpeg的封装格式处理系列文章列表: 最简单的基于FFmpeg的封装格式处理 ...

  5. 最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)

    ===================================================== 最简单的基于FFmpeg的封装格式处理系列文章列表: 最简单的基于FFmpeg的封装格式处理 ...

  6. 最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)

    ===================================================== 最简单的基于FFmpeg的封装格式处理系列文章列表: 最简单的基于FFmpeg的封装格式处理 ...

  7. 视音频编解码学习工程:TS封装格式分析器

    =====================================================视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习工 ...

  8. 视音频编解码学习工程:FLV封装格式分析器

    ===================================================== 视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习 ...

  9. 视频流PS,PS封装H264

    出处: ISOIEC 13818-1 PS流: PS流由PSGOP组成,每个PSGOP是由I帧起始的多帧集合,每个GOP之间没有相互依赖信息,可以剪切拼接. | PSGOP0 | PSGOP1 | P ...

  10. 【多媒体封装格式详解】--- AAC ADTS格式分析

    ADTS全称是(Audio Data Transport Stream),是AAC的一种十分常见的传输格式. 记得第一次做demux的时候,把AAC音频的ES流从FLV封装格式中抽出来送给硬件解码器时 ...

随机推荐

  1. JAVA中代码前加@有什么作用

    严格说@符号并未在java 语言中作为操作符使用.而是在其他附属技术中使用比如1.javadoc 文档关键字/***  @author*  @version* @since* @see*/用于自动生成 ...

  2. (链接)xcode7,ios9 部分兼容设置

    xcode7,ios9 部分兼容设置 http://www.cnblogs.com/loganv/p/4647833.html

  3. LeetCode OJ:Subsets(子集)

    Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must ...

  4. L118

    The company needs to focus on its biggest clients.This article discussed the events that led to her ...

  5. BackBone 源码解读及思考

    说明 前段时间略忙,终于找到时间看看backbone代码. 正如知友们说的那样,backbone简单.随性. 代码简单的看一眼,就能知道作者的思路.因为简单,所以随性,可以很自由的和其他类库大搭配使用 ...

  6. uva1395 - Slim Span(最小生成树)

    先判断是不是连通图,不是就输出-1. 否则,把边排序,从最小的边开始枚举最小生成树里的最短边,对每个最短边用Kruskal算法找出最大边. 或者也可以不先判断连通图,而是在枚举之后如果ans还是INF ...

  7. 【javascript常见面试题】常见前端面试题及答案

    转自:http://www.cnblogs.com/syfwhu/p/4434132.html 前言 本文是在GitHub上看到一个大牛总结的前端常见面试题,很多问题问的都很好,很经典.很有代表性.上 ...

  8. 输入一个链表,输出该链表中倒数第k个结点

    package suanfa; import suanfa.doubleLinkedList.Node; public class solution { public Node find(Node h ...

  9. 在winform下实现左右布局多窗口界面的方法(一)

    在web页面上我们可以通过frameset,iframe嵌套框架很容易实现各种导航+内容的布局界面,而在winform.WPF中实现其实也很容易,通过本文给大家介绍在winform下实现左右布局多窗口 ...

  10. FastAdmin 中的 formatter flag 分析

    FastAdmin 中的 formatter flag 分析 效果 首先看看效果,这里的文字和颜色可以根据数据改变. 这是系统自带的分类管理. 代码 功能在 \public\assets\js\bac ...