MPEG2 TS小结

来源:互联网 发布:mac的照片存在哪里 编辑:程序博客网 时间:2024/05/22 02:03

MPEG2 TS小结

应该说真正了解TS,还是看了朋友推荐的《数字电视业务信息及其编码》一书之后,MPEG2 TS和数字电视是紧密不可分割的,值得总结一下其中的一些关系。

ISO/IEC-13818-1:系统部分;ISO/IEC-13818-2:视频;ISO/IEC-13818-3:音频;ISO /IEC-13818-4:一致性测试;ISO/IEC-13818-5:软件部分;ISO/IEC-13818-6:数字存储媒体命令与控制;ISO /IEC-13818-7:高级音频编码;ISO/IEC-13818-8:系统解码实时接口;

MPEG2系统任务包括:1. 规定以包传输数据的协议;2. 规定收发两端数据流同步的协议;3. 提供多个数据流的复用和解复用协议;3. 提供数据流加密的协议。以包形式存储和传送数据流是MPEG2系统之要点。

ES是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。PES包由包头和payload组成,具体格式摘录如下:

可以看到PTS/DTS是打在PES包里面的,这两个parameters是解决视音频同步显示,防止解码器输入缓存上溢或下溢的关键。PTS表示显示单元出现在系统目标解码器(STD: system target decoder)的时间,DTS表示将存取单元全部字节从STD的ES解码缓存器移走的时刻。每个I、P、B帧的包头都有一个PTS和DTS,但PTS与DTS对B帧都是一样的,无须标出B帧的DTS。对I帧和P帧,显示前一定要存储于视频解 码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明PTS和DTS。

上节介绍过,ES首先需打包成PES流包,然后PES根据需要打包成PS或TS包进行存储或传输。其每路ES只包含一路信源的编码数据流,所以每路 PES也只包含相对应信源的数据流。

对PS流而言,每个PES包头含有PTS和DTS,流识别码,用于区别不同性质ES。然后通过PS复用器将PES包复用成PS包。实际上是将PES 包分解为更细小的PS包。在解码的时候,解复用器将PS分解成一个个PES包,拆包器然后将PES包拆成视频和音频的ES,最后输入至各自解码器进行解 码。一个问题是:各个ES在解码时,如何保证视音频的同步呢?除了PTS和DTS的配合工作外,还有一个重要的参数是SCR(system clock reference)。在编码的时候,PTS,DTS和SCR都是由STC(systemtime clock)生成的,在解码时,STC会再生,并通过锁相环路(PLL-phase lock loop),用本地SCR相位与输入的瞬时SCR相位锁相比较,以确定解码过程是否同步,若不同步,则用这个瞬时SCR调整27MHz的本地时钟频率。最 后,PTS,DTS和SCR一起配合,解决视音频同步播放的问题。PS格式摘录如下:

PS包的长度比较长且可变,主要用于无误码环境里,因为越长的话,同步越困难,且在丢包的情况下,重组也越困难。所以,PS适合于节目信息的编辑和 本地内容应用的application。

TS流也是由一个或多个PES组合而来的,他们可以具有相同的时间基准,也可以不 同。其基本的复用思想是,对具有相同时间基准的多个PES现进行节目复用,然后再对相互有独 立时间基准的各个PS进行传输复用,最终产生出TS。

TS包由包头和包数据2部分组成,其中包头还可以包括扩展的自适用区。包头长度占4bytes,自使用区和包数据共占184bytes,整个TS包长度相当于4个ATM包长。TS的包头由如下图摘录所示的同步字节、传输误码指示符、有效载荷单元起始指示符、传输优先、包识别(PID-PacketIdentification)、传输加扰控制、自适应区控制和连续计数器8部分组成。



其中,可用同步字节位串的自动相关特性,检测数据流中的包限制,建立包同步;传输误码指示符,是指有不能消除误码时,采用误码校正解码器可表示1bit的误码,但无法校正;有效载荷单元起始指示符,表示该数据包是否存在确定的起始信息;传输优先,是给TS包分配优先权;PID值是由用户确定的,解码器根据PIDTS上从不同ES来的TS包区别出来,以重建原来的ES;传输加扰控制,可指示数据包内容是否加扰,但包头和自适应区永远不加扰;自适应区控制,用2 bit表示有否自适应区,即(01)表示有有用信息无自适应区,(10)表示无有用信息有自适应区,(11)表示有有用信息有自适应区,(00)无定义;连续计数器可对PID包传送顺序计数,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。

TS包自适应区由自适应区长、各种标志指示符、与插入标志有关的信息和填充数据4部分组成。其中标志部分由间断指示符、随机存取指示符、ES优化指示符、PCR标志、接点标志、传输专用数据标志、原始PCR标志、自适应区扩展标志8个部分组成。重要的是标志部分的PCR字段,可给编解码器的27MHz时钟提供同步资料,进行同步。其过程是,通过PLL,用解码时本地用PCR相位与输入的瞬时PCR相位锁相比较,确定解码过程是否同步,若不同步,则用这个瞬时PCR调整时钟频率。因为,数字图像采用了复杂而不同的压缩编码算法,造成每幅图像的数据各不相同,使直接从压缩编码图像数据的开始部分获取时钟信息成为不可能。为此,选择了某些(而非全部)TS包的自适应区来传送定时信息。于是,被选中的TS包的自适应区,可用于测定包信息的控制bit和重要的控制信息。自适应区无须伴随每个包都发送,发送多少主要由选中的TS包的传输专用时标参数决定。标志中的随机存取指示符和接点标志,在节目变动时,为随机进入I帧压缩的数据流提供随机进入点,也为插入当地节目提供方便。自适应区中的填充数据是由于PES长不可能正好转为TS包的整数倍,最后的TS包保留一小部分有用容量,通过填充字节加以填补,这样可以防止缓存器下溢,保持总码率恒定不变。

 

前 面3节总结了MPEG2TS的基本格式,其中包括PES,PS和TS,以及相关字段的介绍。那么作为一种传输流,TS将内容进行打包/复用,让其媒体内容变成TS传输,并最终在 解码端解码。简单来看,TS是一个传输层的协议栈,它可以承载各种内容的传输,比如MPEG,WMV,H264,甚至是IP,那么其中的传输规范是如何定 义的呢?这个即是PSI(节目特定信息)要做的事情。

PSI 由四张表构成:PAT,PMT,CAT和NIT,这四张表分别描述了一个TS所 包括的所有ES流的传输结构。首先的一个概念是,TS是以包形式传播,在编解码端都需要以一定的包ID来标识TS流里承载的内容,比如,PAT表会存在于 一个或多个TS包里,所以要用一个特别的包ID来表示,另外,不同的ES流也需要不同的包ID来标识。我们有了PAT和PMT这两种表,解码器就可以根据PID,将TS上从不同ES来的TS包区分出来进行解码。

TS的解码分两步进行,其一,是从PID为0 的TS包里,解析出PAT表,然后从PAT表里找到各个节目源的PID,一般此类节目源都由若 干个ES流组成,并描述在PMT表里面,然后通过节目源的PID,就可以在PMT表里检索到各个ES的PID。其二,解码器根据PMT表里的ES流的PID,将TS流上的包进行区分,并按不同的ES流进行解码。所以,TS是经过节目复用和传输复用两层完成的,即在节目复用时,加入了PMT,在传输复用 时,加入了PAT。同样在节目解复用时,可以得到PMT,在传输解复用时,可以得到PAT。下图很好地概述了其思想。

TS是支持多路复用的,所以它可用来传输经复用后的多层节目。在复用过程中,要注意的是,解码过程中所需要面对的时间参考和同步问题,因为解复用是需要各种信息同步进行的,所以在复用过程中,就需要插入相关的时间信息:PTS,DTS,PCR。

在TS形成过程中,PTS和DTS是在ES 打包成PES时,根据STC的参考,将其时钟信息注入PES包中的,而之后在PES切成TS时,再将 PID和PCR信息注入到TS包中,当多路TS再进行复用的时候,各路TS的PCR将会被提取出来,再进行分析,然后再根据统一的STC参考,将新的 PCR生成并注入到TS中去,最后,因为原来PAT表信息不在适用,所以新的PAT表需要再生成,并附加到新的TS流中去。经过这多层的复用之后,新的 TS流即可以进入调制,传输阶段。过程可参见下图:

解 码过程要面对的问题是:解复用,视音频的同步,解码缓存器无上下溢。解复用即是将TS在同一信道里不同时序进行传输的节目分离出来;视音频同步由 DTS, PTS和PCR三者协调完成,并且PCR是重建系统时间基准的绝对时标,而DTS和PTS是解码和重现时刻的相对时标;对解码缓存器无上下溢的问题,必须借助于系统目标解码器(STD)模型来对其进行实现,基本思想如下:

  1. TS流进入解码器后,首先由换向器,按照一定的时序关系,将各种ES流分解出来(其中也包括PSI信息流)。
  2. 分解过后的ES流会进入各自的传输缓存器,通过之后,其PES流进入各自的主存储器,注意的是:PSI信息流会进入系统缓存器,最后也到达主存储 器。
  3. 最后,解码器根据DTS信息,从各个主存储器分别提取媒体或系统信息,进行解码,并根据PTS信息,将媒体内容进行显示处理。

其过程可参见下图:


最近完成了一个广电的IPTV项目,虽然只是阶段性的,但还是感觉到万事开头难,历经不少磨难啊。回到正题吧,因为前面5节对MPEG2 TS做过介绍,这次主要就结合实际项目的应用再总结下:

1. PSI依然是"根",只有定位到PSI表后,才能索引出PAT,和各个节目的PMT表。

2. PAT表记录了什么呢?表头信息和节目信息。

》表头信息包含若干字段,但最关键的是version_number,why?因为TS流里任何节目信息的变化,这个字段都会往上递加,从而使得TS解码 器可以据此让Decoder对不同节目做解码复位。

》节目信息包括TS流里每路节目的program_number,PMT_PID。由此再结合表头信息,TS解码器即可以定位到具体的有变化的节目流(新增或减少)。如下图:

3. 接下来和PAT表平行的就是PMT表了,因为PAT表已经给出明确的节目信息,所以,解码器可以定位到自己需要的节目。在PMT表里,我们可以看到表头信息,和音视频的stream_type,PID信息等。这样解码器即可以定位到具体的音视频包上,进行节目解码。如下图:


http://hi.baidu.com/bohryan/blog/item/90ad4af1724a605b342accec.html

16 11, 2010

FFMpeg对MPEG2TS流解码的流程分析[2]

作者 helloxchen 17:51 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础

5.渐入佳境
恩,前面的基础因该已近够了,有点像手剥洋葱头的感觉,我们来看看针对MPEG TS的相
应解析过程

我们后面的代码,主要集中在[libavformat/mpegts.c]里面,毛爷爷说:集中优势兵力打
围歼,恩,开始吧,蚂蚁啃骨头。

static int mpegts_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
MpegTSContext *ts = s->priv_data;
ByteIOContext *pb = s->pb;
uint8_t buf[1024];
int len;
int64_t pos;

......

/* read the first 1024 bytes to get packet size */
#####################################################################
【1】有了前面分析缓冲IO的经历,下面的代码就不是什么问题了:)
#####################################################################
pos = url_ftell(pb);
len = get_buffer(pb, buf, sizeof(buf));
if (len != sizeof(buf))
goto fail;
#####################################################################
【2】前面侦测文件格式时候其实已经知道TS包的大小了,这里又侦测一次,其实
有些多余,估计是因为解码框架的原因,已近侦测的包大小没能从前面被带过来,
可见框架虽好,却也会带来或多或少的一些不利影响
#####################################################################
ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
if (ts->raw_packet_size <= 0)
goto fail;
ts->stream = s;
ts->auto_guess = 0;

if (s->iformat == &mpegts_demuxer) {
/* normal demux */

/* first do a scaning to get all the services */
url_fseek(pb, pos, SEEK_SET);
##################################################################
【3】
##################################################################
mpegts_scan_sdt(ts);

##################################################################
【4】
##################################################################
mpegts_set_service(ts);

##################################################################
【5】
##################################################################
handle_packets(ts, s->probesize);
/* if could not find service, enable auto_guess */

ts->auto_guess = 1;

#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "tuning donen");
#endif
s->ctx_flags |= AVFMTCTX_NOHEADER;
} else {
......
}

url_fseek(pb, pos, SEEK_SET);
return 0;
fail:
return -1;
}

这里简单说一下MpegTSContext *ts,从上面可以看到,其实这是为了解码不同容器格式
所使用的私有数据,只有在相应的诸如mpegts.c 文件才可以使用的,这样,增加了这个库
的模块化,而模块化的最大好处,则在于把问题集中到了一个很小的有限区域里面,如果
你自己构造程序时候,不妨多参考其基本思想--这样的化,你之后的代码,还有你之后的
生活,都将轻松许多。

【3】【4】其实调用的是同一个函数:mpegts_open_section_filter()
我们来看看意欲何为。

static
MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
SectionCallback *section_cb,
void *opaque,
int check_crc)
{
MpegTSFilter *filter;
MpegTSSectionFilter *sec;

#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%xn", pid);
#endif
if (pid >= NB_PID_MAX || ts->pids[pid])
return NULL;
filter = av_mallocz(sizeof(MpegTSFilter));
if (!filter)
return NULL;
ts->pids[pid] = filter;
filter->type = MPEGTS_SECTION;
filter->pid = pid;
filter->last_cc = -1;
sec = &filter->u.section_filter;
sec->section_cb = section_cb;
sec->opaque = opaque;
sec->section_buf = av_malloc(MAX_SECTION_SIZE);
sec->check_crc = check_crc;
if (!sec->section_buf) {
av_free(filter);
return NULL;
}
return filter;
}

要完全明白这部分代码,其实需要分析作者对数据结构的定义:
依次为:

struct MpegTSContext;
|
V
struct MpegTSFilter;
|
V
+---------------+---------------+
| |
V V
MpegTSPESFilter MpegTSSectionFilter

其实很简单,就是struct MpegTSContext;中有NB_PID_MAX(8192)个TS的Filter,而每个
struct MpegTSFilter可能是PES的Filter或者Section的Filter。

我们先说为什么是8192,在前面的分析中:
给出过TS的语法结构:
Syntax No. of bits Mnemonic
transport_packet(){
sync_byte 8 bslbf
transport_error_indicator 1 bslbf
payload_unit_start_indicator 1 bslbf
transport_priority 1 bslbf
PID 13 uimsbf
transport_scrambling_control 2 bslbf
adaptation_field_control 2 bslbf
continuity_counter 4 uimsbf
if(adaptation_field_control=='10'
|| adaptation_field_control=='11'){
adaptation_field()
}
if(adaptation_field_control=='01'
|| adaptation_field_control=='11') {
for (i=0;i<N;i++){
data_byte 8 bslbf
}
}
}

而8192,则是 2^13=8192(PID)的最大数目,而为什么会有PES和Section的区分,请参考
ISO/IEC-13818-1,我实在不太喜欢重复已有的东西.

可见【3】【4】,就是挂载了两个Section类型的过滤器,其实在TS的两种负载中,section
是PES 的元数据,只有先解析了section,才能进一步解析PES数据,因此先挂上section的
过滤器。

挂载上了两种 section过滤器,如下:
=========================================================================
PID |Section Name |Callback
=========================================================================
SDT_PID(0x0011) |ServiceDescriptionTable|sdt_cb
| |
PAT_PID(0x0000) |ProgramAssociationTable|pat_cb

既然自是挂上Callback,自然是在后面的地方使用,因此,我们还是继续

【5】处的代码看看是最重要的地方了,简单看来:
handle_packets()
|
+->read_packet()
|
+->handle_packet()
|
+->write_section_data()

read_packet()很简单,就是去找sync_byte(0x47),而看来handle_packet()才会是我们真
正因该关注的地方了:)

这个函数很重要,我们贴出代码,以备分析:
/* handle one TS packet */
static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
AVFormatContext *s = ts->stream;
MpegTSFilter *tss;
int len, pid, cc, cc_ok, afc, is_start;
const uint8_t *p, *p_end;

##########################################################
获取该包的PID
##########################################################
pid = AV_RB16(packet + 1) & 0x1fff;
if(pid && discard_pid(ts, pid))
return;
##########################################################
是否是PES或者Section的开头(payload_unit_start_indicator)
##########################################################
is_start = packet[1] & 0x40;
tss = ts->pids[pid];

##########################################################
ts->auto_guess此时为0,因此不考虑下面的代码
##########################################################
if (ts->auto_guess && tss == NULL && is_start) {
add_pes_stream(ts, pid, -1, 0);
tss = ts->pids[pid];
}
if (!tss)
return;

##########################################################
代码说的很清楚,虽然检查,但不利用检查的结果
##########################################################
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
tss->last_cc = cc;

##########################################################
跳到adaptation_field_control
##########################################################
/* skip adaptation field */
afc = (packet[3] >> 4) & 3;
p = packet + 4;
if (afc == 0) /* reserved value */
return;
if (afc == 2) /* adaptation field only */
return;
if (afc == 3) {
/* skip adapation field */
p += p[0] + 1;
}

##########################################################
p已近到达TS包中的有效负载的地方
##########################################################
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end)
return;

ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;

if (tss->type == MPEGTS_SECTION) {
if (is_start) {
#############################################################
针对Section,符合部分第一个字节为pointerfield,该字段如果为0,
则表示后面紧跟着的是Section的开头,否则是某Section的End部分和
另一Section的开头,因此,这里的流程实际上由两个值is_start
(payload_unit_start_indicator)和len(pointer field)一起来决定
#############################################################
/* pointer field present */
len = *p++;
if (p + len > p_end)
return;
if (len && cc_ok) {
########################################################
1).is_start == 1
len > 0
负载部分由A Section的End部分和B Section的Start组成,把A的
End部分写入
########################################################
/* write remaining section bytes */
write_section_data(s, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return;
}
p += len;
if (p < p_end) {
########################################################
2).is_start == 1
len > 0
负载部分由A Section的End部分和B Section的Start组成,把B的
Start部分写入
或者:
3).
is_start == 1
len == 0
负载部分仅是一个Section的Start部分,将其写入
########################################################
write_section_data(s, tss,
p, p_end - p, 1);
}
} else {
if (cc_ok) {
########################################################
4).is_start == 0
负载部分仅是一个Section的中间部分部分,将其写入
########################################################
write_section_data(s, tss,
p, p_end - p, 0);
}
}
} else {
##########################################################
如果是PES类型,直接调用其Callback,但显然,只有Section部分
解析完成后才可能解析PES
##########################################################
tss->u.pes_filter.pes_cb(tss,
p, p_end - p, is_start);
}
}


write_section_data()函数则反复收集buffer中的数据,指导完成相关Section的重组过
程,然后调用之前注册的两个section_cb:

后面我们将分析之前挂在的两个section_cb,待续......

http://hi.baidu.com/bohryan/blog/item/95b49bc65fcdcaa28326ac20.html

16 11, 2010

FFMpeg对MPEG2TS流解码的流程分析

作者 helloxchen 17:48 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础


FFMpeg对MPEG2 TS流解码的流程分析

1.引子
gnxzzz广告都打出去了,不能没有反应.现在写东西很少了,一是年纪大了,好奇心少了
许多,;二则是这几天又犯了扁桃体炎,每天只要是快睡觉或刚起床,头晕脑涨,不过功
课还是的做的,是吧:)

2. 从简单说起
说道具体的音频或者视频格式,一上来就是理论,那是国内混资历的所谓教授的做为,对
于我们,不合适,还是用自己的方式理解这些晦涩不已的理论吧。

其实MPEG2是一族协议,至少已经成为ISO标准的就有以下几部分:
ISO/IEC-13818-1:系统部分;
ISO/IEC-13818-2:视频编码格式;
ISO/IEC-13818-3:音频编码格式;
ISO/IEC-13818-4: 一致性测试;
ISO/IEC-13818-5:软件部分;
ISO/IEC-13818-6:数字存储媒体命令与控制;
ISO/IEC-13818-7: 高级音频编码;
ISO/IEC-13818-8:系统解码实时接口;

我不是很想说实际的音视频编码格式,毕竟协议已经很清楚了,我主要想说说这些部分怎
么组合起来在实际应用中工作的。

第一部分(系统部分)很重要,是构成以MPEG2为基础的应用的基础. 很绕口,是吧,我简
单解释一下:比如DVD实际上是以系统部分定义的PS流为基础,加上版权管理等其他技术构
成的。而我们的故事主角,则是另外一种流格式,TS流,它在现阶段最大的应用是在数字
电视节目的传输与存储上,因此,你可以理解TS实际上是一种传输协议,与实际传输的负
载关系不大,只是在TS中传输了音频,视频或者其他数据。

先说一下为什么会有这两种格式的出现,PS适用于没有损耗的环境下面存储,而TS 则适用
于可能出现损耗或者错误的各种物理网络环境,比如你在公交上看到的电视,很有可能就
是基于TS的DVB-T的应用:)

我们再来看MPEG2协议中的一些概念,为理解代码做好功课:

ES(Elementary Stream):
wiki上说“An elementary stream (ES) is defined by MPEGcommunication protocol
is usually the output of an audio or video encoder”
恩,很简单吧,就是编码器编出的一组数据,可能是音频的,视频的,或者其他数据

说到着,其实可以对编码器的流程思考一下,无非是执行:采样,量化,编码这3个步骤
中的编码而已(有些设备可能会包含前面的采样和量化)。关于视频编码的基本理论,还是
请参考其它的资料。

PES(Packetized Elementary Stream):
wiki上说“allows an Elementary stream to be divided intopackets”
其实可以理解成,把一个源源不断的数据(音频,视频或者其他)流,打断成一段一段,以
便处理.

TS(Transport Stream):
PS(Program Stream):
这两个上面已经有所提及,后面会详细分析TS,我对PS格式兴趣不大.

3. 步入正题
才进入正题,恩,看来闲话太多了:(,直接看Code.

前面说过,TS是一种传输协议,因此,对应到FFmpeg,可以认为他是一种封装格式。因此
,对应的代码应该先去libavformat里面找,很容易找到,就是mpegts.c:)

还是逐步看过来:
[libavformat/utils.c]
int av_open_input_file(AVFormatContext **ic_ptr, const char *filename,
AVInputFormat *fmt,
int buf_size,
AVFormatParameters *ap)
{
int err, probe_size;
AVProbeData probe_data, *pd = &probe_data;
ByteIOContext *pb = NULL;

pd->filename = "";
if (filename)
pd->filename = filename;
pd->buf = NULL;
pd->buf_size = 0;

#########################################################################
【1】这段代码其实是为了针对不需要Open文件的容器Format的探测,其实就是使用
AVFMT_NOFILE标记的容器格式单独处理,现在只有使用了该标记的Demuxer很少,
只有image2_demuxer,rtsp_demuxer,因此我们分析TS时候可以不考虑这部分
#########################################################################
if (!fmt) {
/* guess format if no file can be opened */
fmt = av_probe_input_format(pd, 0);
}

/* Do not open file if the format does not need it. XXX: specific
hack needed to handle RTSP/TCP */
if (!fmt || !(fmt->flags & AVFMT_NOFILE)) {
/* if no file needed do not try to open one */
#####################################################################
【2】这个函数似乎很好理解,无非是带缓冲的IO的封装,不过我们既然到此了
,不妨跟踪下去,看看别人对带缓冲的IO操作封装的实现:)
#####################################################################
if ((err=url_fopen(&pb, filename, URL_RDONLY)) < 0) {
goto fail;
}
if (buf_size > 0) {
url_setbufsize(pb, buf_size);
}

for(probe_size= PROBE_BUF_MIN; probe_size<=PROBE_BUF_MAX && !fmt;probe_size<<=1){
int score= probe_size < PROBE_BUF_MAX ? AVPROBE_SCORE_MAX/4 : 0;
/* read probe data */
pd->buf= av_realloc(pd->buf, probe_size + AVPROBE_PADDING_SIZE);
##################################################################
【3】真正将文件读入到pd的buffer的地方,实际上最终调用FILE protocol
的file_read(),将内容读入到pd的buf,具体代码如果有兴趣可以自己跟踪
##################################################################
pd->buf_size = get_buffer(pb, pd->buf, probe_size);
memset(pd->buf+pd->buf_size, 0, AVPROBE_PADDING_SIZE);
if (url_fseek(pb, 0, SEEK_SET) < 0) {
url_fclose(pb);
if (url_fopen(&pb, filename, URL_RDONLY) < 0) {
pb = NULL;
err = AVERROR(EIO);
goto fail;
}
}
##################################################################
【4】此时的pd已经有了需要分析的原始文件,只需要查找相应容器format
的Tag比较,以判断读入的究竟为什么容器格式,这里
##################################################################
/* guess file format */
fmt = av_probe_input_format2(pd, 1, &score);
}
av_freep(&pd->buf);
}

/* if still no format found, error */
if (!fmt) {
err = AVERROR_NOFMT;
goto fail;
}

/* check filename in case an image number is expected */
if (fmt->flags & AVFMT_NEEDNUMBER) {
if (!av_filename_number_test(filename)) {
err = AVERROR_NUMEXPECTED;
goto fail;
}
}
err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);
if (err)
goto fail;
return 0;
fail:
av_freep(&pd->buf);
if (pb)
url_fclose(pb);
*ic_ptr = NULL;
return err;

}

【2】带缓冲IO的封装的实现
[liavformat/aviobuf.c]
int url_fopen(ByteIOContext **s, const char *filename, int flags)
{
URLContext *h;
int err;

err = url_open(&h, filename, flags);
if (err < 0)
return err;
err = url_fdopen(s, h);
if (err < 0) {
url_close(h);
return err;
}
return 0;
}

可以看到,下面的这个函数,先查找是否是FFmpeg支持的protocol的格式,如果文件名不
符合,则默认是FILE protocol格式,很显然,这里protocol判断是以URL的方式判读的,
因此基本上所有的IO接口函数都是url_xxx的形式。
在这也可以看到,FFmpeg支持的protocol有:
/* protocols */
REGISTER_PROTOCOL (FILE, file);
REGISTER_PROTOCOL (HTTP, http);
REGISTER_PROTOCOL (PIPE, pipe);
REGISTER_PROTOCOL (RTP, rtp);
REGISTER_PROTOCOL (TCP, tcp);
REGISTER_PROTOCOL (UDP, udp);
而大部分情况下,如果你不指明类似file://xxx,http://xxx格式,它都以FILE protocol
来处理。

[liavformat/avio.c]
int url_open(URLContext **puc, const char *filename, int flags)
{
URLProtocol *up;
const char *p;
char proto_str[128], *q;

p = filename;
q = proto_str;
while (*p != '' && *p != ':') {
/* protocols can only contain alphabetic chars */
if (!isalpha(*p))
goto file_proto;
if ((q - proto_str) < sizeof(proto_str) - 1)
*q++ = *p;
p++;
}
/* if the protocol has length 1, we consider it is a dos drive */
if (*p == '' || (q - proto_str) <= 1) {
file_proto:
strcpy(proto_str, "file");
} else {
*q = '';
}

up = first_protocol;
while (up != NULL) {
if (!strcmp(proto_str, up->name))
#################################################################
很显然,此时已经知道up,filename,flags
#################################################################
return url_open_protocol (puc, up, filename, flags);
up = up->next;
}
*puc = NULL;
return AVERROR(ENOENT);
}

[libavformat/avio.c]
int url_open_protocol (URLContext **puc, struct URLProtocol *up,
const char *filename, int flags)
{
URLContext *uc;
int err;

##########################################################################
【a】? 为什么这样分配空间
##########################################################################
uc = av_malloc(sizeof(URLContext) + strlen(filename) + 1);
if (!uc) {
err = AVERROR(ENOMEM);
goto fail;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 53
uc->av_class = &urlcontext_class;
#endif
##########################################################################
【b】? 这样的用意又是为什么
##########################################################################
uc->filename = (char *) &uc[1];
strcpy(uc->filename, filename);
uc->prot = up;
uc->flags = flags;
uc->is_streamed = 0; /* default = not streamed */
uc->max_packet_size = 0; /* default: stream file */
err = up->url_open(uc, filename, flags);
if (err < 0) {
av_free(uc);
*puc = NULL;
return err;
}

//We must be carefull here as url_seek() could be slow, for example for
//http
if( (flags & (URL_WRONLY | URL_RDWR))
|| !strcmp(up->name, "file"))
if(!uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0)
uc->is_streamed= 1;
*puc = uc;
return 0;
fail:
*puc = NULL;
return err;
}

上面这个函数不难理解,但有些地方颇值得玩味,比如,上面给出问号的地方,你明白为
什么这样Coding么:)

很显然,此时up->url_open()实际上调用的是file_open()[libavformat/file.c],看完
这个函数,对上面的内存分配,是否恍然大悟:)

上面只是分析了url_open(),还没有分析url_fdopen(s,h);这部分代码,也留给有好奇
心的你了:)

恩,为了追踪这个流程,走得有些远,但不是全然无用:)

终于来到了【4】,我们来看MPEG TS格式的侦测过程,这其实才是我们今天的主角

4. MPEG TS格式的探测过程
[liavformat/mpegts.c]
static int mpegts_probe(AVProbeData *p)
{
#if 1
const int size= p->buf_size;
int score, fec_score, dvhs_score;
#define CHECK_COUNT 10

if (size < (TS_FEC_PACKET_SIZE * CHECK_COUNT))
return -1;

score = analyze(p->buf, TS_PACKET_SIZE *CHECK_COUNT, TS_PACKET_SIZE, NULL);
dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE *CHECK_COUNT,TS_DVHS_PACKET_SIZE, NULL);
fec_score= analyze(p->buf, TS_FEC_PACKET_SIZE*CHECK_COUNT,TS_FEC_PACKET_SIZE, NULL);
// av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %dn", score, dvhs_score, fec_score);

// we need a clear definition for the returned score otherwise things willbecome messy sooner or later
if (score > fec_score && score > dvhs_score && score >6) return AVPROBE_SCORE_MAX + score - CHECK_COUNT;
else if(dvhs_score > score && dvhs_score > fec_score &&dvhs_score > 6) return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT;
else if( fec_score > 6) return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
else return -1;
#else
/* only use the extension for safer guess */
if (match_ext(p->filename, "ts"))
return AVPROBE_SCORE_MAX;
else
return 0;
#endif
}
之所以会出现3种格式,主要原因是:
TS标准是188Bytes,而小日本自己又弄了一个192Bytes的DVH-S格式,第三种的 204Bytes则
是在188Bytes的基础上,加上16Bytes的FEC(前向纠错).

static int analyze(const uint8_t *buf, int size, int packet_size, int *index)
{
int stat[packet_size];
int i;
int x=0;
int best_score=0;

memset(stat, 0, packet_size*sizeof(int));

##########################################################################
由于查找的特定格式至少3个Bytes,因此,至少最后3个Bytes不用查找
##########################################################################
for(x=i=0; i<size-3; i++){
######################################################################
参看后面的协议说明
######################################################################
if(buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] &0x30)){
stat[x]++;
if(stat[x] > best_score){
best_score= stat[x];
if(index) *index= x;
}
}

x++;
if(x == packet_size) x= 0;
}

return best_score;
}

这个函数简单说来,是在size大小的buf中,寻找满足特定格式,长度为packet_size的
packet的个数,显然,返回的值越大越可能是相应的格式(188/192/204)

其中的这个特定格式,其实就是协议的规定格式:

Syntax

No. of bits

Mnemonic

transport_packet(){

 

 

sync_byte

8

bslbf

transport_error_indicator

1

bslbf

payload_unit_start_indicator

1

bslbf

transport_priority

1

bslbf

PID

13

uimsbf

transport_scrambling_control

2

bslbf

adaptation_field_control

2

bslbf

continuity_counter

4

uimsbf

if(adaptation_field_control=='10'|| adaptation_field_control=='11'){

 

 

adaptation_field()

 

 

}

 

 

if(adaptation_field_control=='01' || adaptation_field_control=='11') {

 

 

for (i=0;i<N;i++){

 

 

data_byte

8

bslbf

}

 

 

}

 

 

}

 

 


其中的sync_byte固定为0x47,即上面的: buf[i] == 0x47
由于 transport_error_indicator为1的TS Packet实际有错误,表示携带的数据无意义,
这样的Packet显然没什么意义,因此: !(buf[i+1]& 0x80)
对于adaptation_field_control,如果为取值为 0x00,则表示为未来保留,现在不用,因
此: buf[i+3] & 0x30

这就是MPEG TS的侦测过程,很简单吧:)

后面我们分析如何从mpegts文件中获取stream的过程,待续......

参考:
1.http://en.wikipedia.org/wiki/MPEG_transport_stream

2.ISO/IEC-13818-1

http://hi.baidu.com/bohryan/blog/item/9fac3a920685fa86a877a426.html

16 11, 2010

MPEG-2TS码流编辑的原理及其应用

作者 helloxchen 17:36 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础

[作者:辽宁电视台 赵季伟]

 

在当今数字媒体不断发展、新媒体业务不断涌现 的前提下,实践证明襁褓中的新媒体只有两种经营方略可供选择:或是购买并集成整套节目,或是低成本深加工新节目,再不可能去按照传统生产模式去自采自编。 低成本的节目生产制作与发布,不仅成为数字媒体经营的主要手段,也成为传统媒体“改革工作流程”的重要举措,进而促成了对新型工作母机和简捷快速流程的迫 切需求。

在辽宁新媒体多业务综合服务平台上,先于国际和国内应用了MPEG-2传输流快速剪辑编辑系统(以下简称码流快编)。这项由辽 宁电视台与深圳奥维迅公司在2003年10月联合开发的新技术,为数字媒体低成本节目的制作、推广和运营提供了高效生产工作母机。尽管担负此项目源代码开 发的奥维迅公司出现了经营问题,在技术推广的中间环节发生梗塞,但并不能说明此项技术走到了尽头。回顾3年的应用实践及研发成果,需要的不是扬弃,而是演 进的升级,否则就是对可调控资源的莫大浪费。特别是针对第二代信源编解码国标AVS-P2的更新换代,很可能成为多业务内容整合的新一代产品的突破口。

一 工作原理

1. 功能目标

码流快编的应用目标是,通过对开放视频的采集,将DVB-S或C的传输流(Transport Stream,TS)节目作为信源,直接进行剪辑处理,再经过人工创意后,整合为新主题内容的新节目,以便直接进入频道集成或编辑频道节目播出,快速实现 数据层的内容整合,不仅简捷了采集制作的工作流程,而且为丰富媒体内容资产开辟了一条捷径。因为码流快编的工作流程无需先以解码后的视频记录于磁带,再以 磁带上载编辑机,经编辑后再下载成为磁带,再编码复用成为新内容的新节目。即便数字化完成以后,视频数据流仍不能用于经复用的数字传输,还需编码、转码、 打包等传输格式化以后,才能在数字信道上传输。而采用码流快编以后,不仅避免了解码后再采样编码所形成的视频损耗,还避免了在1∶1时间的上下载中所造成 的效率损耗。更重要的是在视频内容整合 中,一次性完成音/视频同步剪切、字幕处理和音/视频数据打包复用等连续作业。所以,它能够提高生产效率60%以 上。必要时还可进行节目包装的特技编辑,直接创建数据级和文件级的互联互通内容交换平台,在媒体资产管理下,顺利实现网络化与智能化的节目配送与发布。

由于码流快编是针对以TS为信源的再编辑系统,所以实行“高来高走,低来低走”,或是“高来低走”的应用策略,即高码率对应高 码率(包括兼容高清),低码率对应低码率,但码率连续可调,以适应高码率对应低码率的应用。理论和应用都说明,对比源节目和成品节目,经剪切和编辑处理的 图像保持了同等的视频质量,成为不劣化图像的创新工作流程和新型工作母机。

2. 设计特征

码流快编的低成本与高效率来自它的主要特征。常规的非线性编辑机是在编码一侧做文章,通过采集编码卡实现视频图像多层多轨的非线 性编辑;而码流快编则是在解 码一侧下功夫,通过对TS拆包还原为基本码流(Elementary Stream,ES),而后经编辑再封包成为TS,快速实现视频内容整合的业务应用。因而,码流快编除了运用非线性图像处理技术外,最大的特点是运用了 MPEG-2和DVB的系统原理,在此基础上进行图像处理的应用开发。

通过图1的系统概念,可以看出码流快编运用了一个逆向思维的方法,利用DVB系统传输的发/收互逆关系,将收端的单节目传输流 (SPTS)作为信源,通过ES实现以视频非线性处理的目标功能。这时的解决方案有两个:一是在TS 基础上直接进行图像处理的基础开发,实现与常规非编一 样的操作界面和编辑习惯,这样虽好,但是没有参照模型以及可利用的技术资源,必须从零开始的重写源代码;二是将TS转码为ES,以ES帧结构和句法格式还 原成为符合ITU-R.601建议的原始视频帧,就可以很方便地利用或附加现有的常规非编技术,以无卡站形式实现编辑。码流快编同时采用了两种方案,一方 面独立开发新产品系列,以适应于快速发展的数字电视业务需求,另一方面提高系统的兼容性,向后兼容传统非编,有利于在媒体资产管理下实现互联互通和投资保 护。图2说明了方案二的ES还原并显示原始视频的GOP帧结构。

通过图2可以认为,既然能够形成IBBP的句法帧,再转换成为全I帧格式并不难。这样一来,就可以利用原有的非线性编辑技术, 连续处理长与短GOP、全I帧与IBP帧结构、可变与固定码流的节目素材,实现不同节目格式的快编与混编。由此可见,码流快编的开发技术难点,是结合 DVB系统与MPEG-2标准,从译码过程中读出元数据,实现基于解码器的图像处理技术,并能兼容以编码卡为基础的非线性编辑技术。因而被业内称为“第一 个吃螃蟹者”。




3. 数据变换

既然要把TS作为节目源进行编辑,就需要将TS包中的数据变换为非线性编辑所能使用的元数据和视频流。它们是以码流快编作为工作母机进行生产的真正原料。

数据变换的第一个过程是拆DVB复用包。如图3所示,DVB的解码流程中分层译出了许多数据信息,如同步字节(Sync Byte)、节目特定信息(Program Specific Information,PSI)中PAT、PMT、NIT等列表、包识别(Packet Identification,PID)、节目时钟基准(Program Clock Reference,PCR)及PTS/DTS(后详解)和业务信息(Service Information,SI)等部分。这些信息不仅与DVB PSI/SI直接相关,与MPEG-2的句法结构也直接相关,它规范地传递了再生码流中音/视频所需的MPEG-2列表数据,通过这些信息的引导,准确进 入MPEG-2系统层的进一步译码。

第二个过程是拆MPEG-2系统复用包。MPEG-2系统定义了一个的码流层次化结构句法规则,以便于误码处理、随机搜索以及 内容编辑。它自上而下依次分为图像序列层(Video Sequence Layer,VSL)、图像组层(Group of Pictures Layer,GOPL)、图像层(Picture Layer,PL)、像素条层(Slice Layer,SL)、宏块层(Macro block Layer,ML)、像块层(Block Layer,BL)等6个层次,分别赋予每个层次不同的功能。图4说明了MPEG-2 体系的句法结构,通过这种分层排列的结构特征,MPEG-2提高了系统的灵活性和管理效率,使得每一层都可以用来支持一个特定的功能。码流快编大部分应用 于VSL、GOPL、PL层,特别是在GOPL,需要在还原时规范翻译PID、PCR、PTS/DTS等信息,将同步信息、闭合标记、断链标记等数据准确 插入GOP图像组,以形成每组GOP数据流的起点,才能保证图像帧的精确和连续帧的同步接续。

第三个过程是将拆包后所得数据信息,以规范的装填还原MPEG-2 ES以及元数据,因而装填数据是码流快编的重要技术环节。

(1)装填复用的基本码流包

依据MPEG-2 TS规范结构(如图5所示),复用的基本码流包(Packetized Elementary Stream,PES)是由包头、自适应区的ES特有信息和包数据3部分所组成。由于包头和ES特有信息二者可合成1个数据头,因而可认为1个PES包是 由包头和包数据(有效载荷)2个部分所组成。对有线、卫星、地面广播网接收的TS,经过解调和解扰处理后选取包长为188B的SPTS,并从包头中提取相 关信息,以PID区别不同SPTS包,以连续计数器的顺序计对标注PID的TS包重建一个独立分组的PES。根据自适应区中的填充数据,装填到不为TS包 整数倍的PES包中,以保证PES变长包的完整性。同时,依据包头及自适应区内的同步字节、原始程序参考时钟(Original Program Clock Reference,OPCR)、PCR等同步和识别信息,提供27MHz的解码同步时钟,装填共同时间基准、独立时间基准、可变包长和有效载荷等数据。

装填成为PES对码流快编具有格外重要的意义,因为PES包内含音/视频的ES以及包括PID的12个包头识别标志,当对 PES包的起点不能精确定位、对PES包头标志符不能准确识别时,就不能保证拆包后再打包的图像无缝接续和声画同步。这是在初期研发过程中遇到并获得突破 的技术难点。

(2)装填基本码流

根据MPEG-2规范的PES包结构(如图6),还需进一步装填为连续ES流。ES是指只包含1个信源的数据流,即视频数据流或 音频数据流。每个ES由若干个缓存器的特定存取单元(Access Unit,AU)所组成,而每个视频AU或音频AU都是由头部和编码数据的2部分。1个AU相当于编码的1帧视频图像或1个音频帧的取样。PES的包头为 恢复ES提供了向导。

对于PES包头,具有ES特有信息的显示时间标记(Presentation Time Stamp,PTS)、解码时间标记(Decode Time Stamp,DTS)标志、基本流时钟基准(Elementary Stream Clock Reference,ESCR)信息标志、基本流速率信息标志、数字存储媒体(Digital Storage Media,DSM)的特技信息标志等等,其中,唯有PTS/DTS标志,是解决视音频同步显示,防止输入缓存器上溢或下溢的关键所在。

在装填过程中,数据定位指示符引导PES还原所包含的视频、音频及所属其它数据流(如同步、数据和数据通道等),包头识别标志 的PTS/DTS指示了可变长度包数据的帧同步时间,当区分音/视频和其它数据以后,依据PTS/DTS对视频帧分配给特定的AU。其中尤以独立时间基准 是还原ES的同步基础。对于PES包数据,一方面通过扩展标志的数据包计数器,引导恢复数据流,另一方面利用循环冗余校验(Cyclic Redundancy Check,CRC)辅助检测并纠正可能存在的数据包丢失。




4. 精确帧定位

帧定位是精确编辑的基础,而精确的帧定位来自于精确的帧同步。在ES上实现逐帧精确的编辑,首要问题是实现精确帧的同步。

如前所述,装填后的ES变成仅含有1种性质的PES包,或视频ES,或音频ES。以视频为例,图7表明了在PTS/DTS标示的 独立同步时间基准指示下,顺序装填再顺序读出,形成连续ES的I1P4B2B3P7B5B6(N=7)GOP组帧顺序。由于PES的数据分组是可变长度的 数据包,但它的最大包数据容量为65526Byte。因此,在码流快编中必须为每个AU准备必不可少的缓存空间。

如图7所示,PTS表明图像帧出现在目标解码器(System Target Decoder,STD)的时间,DTS表明将存取单元全部字节从STD的ES解码缓存器移出的时刻。当以PTS/DTS为独立时间基准,定位和标志 PES的AU起始点后,对UA依次组成图像帧序为I1P4B2B3P7B5B6 I10B8B9的ES。对于I、P帧而言,PES的图像帧序为I1P4B2B3P7B5B6I10B8B9,应该P4比B2、B3在先,但显示时P4一定 要比B2、B3在后,这就必须重新排序。在PTS/DTS时间标志指引下,将P4提前插入数据流,经过缓存器重新排序,重建视频帧序 I1B2B3P4B5B6P7B8B9I10。显然,PTS/DTS是表明确定事件或确定信息,并以专用时标形态确定事件或信息的开始时刻。

值得注意的是,虽然在PES中应该每个I、P、B帧的包头都具有一个PTS和DTS,但由于B帧的解码时间和显示时间存在一致 性,因而对B帧而言,PTS与DTS具有相同作用,无须DTS,只须PTS。音频数据包也很有特点,虽然它可以含有多个存取单元,但由于它必须按照时间顺 序传送,所以音频包头中也不含DTS,只有1个PTS。

由此可见,PTS/DTS不仅直接关联帧定位,而且直接关系到码流快编的帧精确编辑。为此,码流快编的帧定位着重处理以下几个帧序特征:

* 当处理某个含有1个I帧的存取单元时,在其包头文件中应有DTS和PTS,且2个标记之间的时间间隔为1个图像周期,那么在双向编码时的P帧应在I帧之 后,在包头文件中就应有1个DTS和1个PTS,而这2个标记之间应存在3个图像周期的间隔,这样才能插入2个B帧。

* 当前处理IPBB时,I帧应延迟1个图像周期,P帧应延迟3个图像周期,而2个B帧则无需延迟,于是,最后的显示顺序就成为了IBBP帧序。

* 若需要改变GOP结构时,例如在I与P帧之间需要存在更多的B帧,则需调整B帧DTS与PTS之间的时间间隔。

* 当处理IPBB序列后,必在第1个B帧之前先对I和P帧进行解码,但每次只能对一帧图像进行解码,因此需要先解I帧并暂存后,待P帧被解码时,随即读出I帧,而后再读B帧。

* 在处理包头时,对PTS/DTS指示的某图像帧,特殊关注是否只有1个PTS时间标记,是否含有PTS和DTS的2个时间标记,以便快速确认I、P与B帧,以及音频包。

当作为编辑节目源的MPEG-2 TS被拆包以后,以PTS/DTS实现视频帧的精确定位,就能以帧精确的剪切编辑实现码流快编功能,同时,也能以 I1P4B2B3P7B5B6I10B8B9帧序显示I1B2B3P4B5B6P7B8B9I10帧序。那么,在此基础上嫁接现成的MPEG-2 IBP非线性编辑的成熟技术,既不需要高价的采集编码卡,也不需要上下载的转码,从采集、转码、拆包剪切,到特技处理、打包上传,一气呵成地构成了码流快 编系统。




二 实际应用

考虑到码流快编的兼容性和通用性,以及互联互通网络平台内容交换的需要,系统是在Windows平台上开发应用,系统流程详见图8。

事实上就应用层而言,看不出码流快编与常规非编究竟有什么区别。只是对照图8,才能看出常规应用时是以传统磁带记录的内容为节目 源,这就需要围绕采集编码卡,设立上下载工作站,通过将模拟节目采集编码为数据流以后,再进行剪切等编辑。因而,目前还需对广泛应用的模拟视频提供接口选 项,附加上下载工作站。它的应用界面及其采集参数选项参见图9。图9、10表示数据输入的采集操作界面,显示目标文件格式初始化选项与合成属性,以维系连 续应用的工作流程。

经采集所合成的码流格式文件,可以同时转换成为可供交换的各种流格式和连续可调的速率,以适应各种内容编辑的整合策略需求,并可利用网络,对连续生产的其它工作站输送和交换素材,直至包括特技的特殊内容加工。这些交换文件的合成属性以及应用格式选项参见图10。

虽然在码流快编上进行快速编辑并合成文件时,与常规操作不无不同,但是通过对TS文件的快速正、反向搜索,以每一帧的入点、出点 的精确定位,进行非线性编辑的剪切操作,制作成为新内容体裁的新文件,却被广电总局主管节目的领导所认可,被称之为“海量空中节目的过滤器”。同时,它还 可以根据内容的需要,通过内置的迭加台标、栏目、字幕等字幕软件,直接参与编辑节目内容信息编辑或局部遮盖,并可联立采集工作站,形成生产流水线,进行不 同任务种类的专项加工。如图11所示,在时间轨上的不同视频、音频、字幕等,均可按帧精确的快速合成为新节目或新素材。

如图12所示,当合成新节目或素材时,文件格式的也可以根据使用的需要加以选择。如果作为DVB播出节目的成品,则可选择 “MPEG-2TS流(DVB格式)”选项;如果需要作为进一步深加工的素材,则可选择“MPEG-2ES流(M2V+MPA)”选项,以素材集的形式存 放于共享应用的集中存储。这样编辑后的节目素材或成品,理论上可以保证与节目源完全相同的视频质量。

还值得一提的是,只要在此基础上再增加一块廉价的Matrox-RGX100采集卡,就可以进一步集成无限轨、无限层的第二代非线性编辑技术,成为多格式混编、混排的超级深度特技编辑机,为动漫、游戏等新媒体节目的快速加工创造工具。

三 结语

无论码流快编对节目制作的现实意义存在何种褒贬,但在一次数字版权还没有进阶到二次版权法规的时候,它的存在具有特殊重要意义。 毕竟能够通过仅12名(前期6人)的人员投入,以低成本、高效率的节目加工,为辽宁电视台提供了18套全省的数字电视节目,而且维系了近3年的节目生产, 至今赢得了全省近百万数字电视用户的向往与肯定。试想一下,倘若按照常规作法,只播出1套节目用12个编辑就很可能要“累死人了”。

实践证明,当国外的政治体制和法规观念与国内存在现实差异的时候,国际上不屑一顾的产品并不是国内市场没有需求;当国际与国内 市场趋于接轨的时候,对瞬息万变的市场不屑一顾也不是新技术的初衷;当码流快编因某种原因并没有得到推广的时候,不屑一顾地扬弃它的内核就不是实事求是。 最恰当的对策是继承技术原理的设计理念,与时俱进地升级新产品,才能获得广电和企业的整体效益。

摘自《现代电视技术》


from
http://www.tvpro.com.cn/jxlt/20061020/5560.html

16 11, 2010

TS基本概念和数据结构(转

作者 helloxchen 17:35 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础

)ES- Elementary Streams (原始流),对视频、音频信号及其他数据进行编码压缩后的数据流称为原始流。原始流包括访问单元,比如视频原始流的访问单元就是一副图像的编码数据。
(2) PES- Packetized Elementary Streams (分组的原始流),原始流形成的分组称为PES分组,是用来传递原始流的一种数据结构
(3)节目是节目元素的集合。节目元素可能是原始流,这些原始流有共同的时间基点,用来做同步显示。
(4) 传输流和节目流TS-Transport Stream 翻译为“传输流”PS-Program Stream 翻译为“节目流”PS用来传输和保存一道节目的编码数据或其他数据。PS的组成单位是PES分组。TS用来传输和保存多道节目的编码数据或其他数据,TS 的组成单位是节目。PS适用于不容易发生错误的环境,以及涉及到软件处理的应用,典型应用如DVD光盘的文件存储TS适用于容易发生错误的环境,典型应用 就是数字电视信号的传输。TS和PS是可以互相转换的,比如从TS中抽取一道节目的内容并产生有效的PS是可能。
(5)传输流分组和PES分组 原始流分成很多PES分组,保持串行顺序,一个PES分组只包含一个原始流的编码数据。PES分组长度很大,最大可为64K字节。PES分组分为“分组首部(header)”和“有效负载(payload)”。“有效负载”指跟随在首部字节之后的字节。首部的前4个字节构成分组的起始码,标识了该分组所属 原始流的类型和ID号。TS分组也就是传输流数据形成的数据包。每个TS分组长度为188字节,包括“分组首部”和“有效负载,前4个字节是分组首部,包 含了这个分组的一些信息。有些情况下需要更多的信息时,需在后面添加“调整字段(adaption field)”。两者之间的关系:PES分组是插入到TS分组中的,每个PES分组首部的第一字节就是TS分组有效负载的第一字节。一个PID值的TS分 组只带有来自一个原始流的数据。
(6)PSI 全称Program Specific Information,意为节目专用信息。传输流中是多路节目复用的,那么,怎么知道这些节目在传输流中的位置,区分属于不同节目呢?所以就还需要一些附加信息,这就是PSI。PSI也是插入到TS分组中的,它们的PID是特定值。MPEG-2中规定了4个PSI,包括PAT(节目关联表),CAT(条 件访问表),PMT(节目映射表),NIT(网络信息表),这些PSI包含了进行多路解调和显示节目的必要的和足够的信息。应用中可能包括更多的信息,比如DVB-T中定义了SDT(服务描述表),EIT(环境信息表),BAT(节目组相关表),TDT(时间日期表)等,统称为DVB-SI(服务信息)。 PSI的PID是特定的,含PSI的数据包必须周期性的出现在传输流中。
PMT (Program Map Table )节目映射表PMT所在分组的PID由PAT指定,所以要先解出PAT,再解PMT。PMT中包含了属于同一节目的视频、音频和数据原始流的PID。找到 了PMT,解多路复用器就可找到一道节目对应的每个原始流的PID,再根据原始流PID,去获取原始流。
PAT (Program Association Table )节目关联表PAT所在分组的PID=0 PAT中列出了传输流中存在的节目流PAT指定了传输流中每个节目对应PMT所在分组的PIDPAT的第一条数据指定了NIT所在分组的PID ,其他数据指定了PMT所在分组的PID。
CAT (Conditional Access Table )条件访问表CAT所在分组的PID=1CAT中列出了条件控制信息(ECM)和条件管理信息(EMM)所在分组的PID。CAT用于节目的加密和解密 NIT( Network Information Table)网络信息表NIT所在分组的PID由PAT指定NIT提供一组传输流的相关信息,以及于网络自身特性相关的信息,比如网络名称,传输参数(如 频率,调制方式等)。NIT一般是解码器内部使用的数据,当然也可以做为EPG的一个显示数据提供给用户做为参考。几种PSI之间的关系,如下图所示:首 先PAT中指定了传输流中所存在的节目,及每个节目对应的PMT的PID号。 比如Program1对应的PMT 的PID=22,然后找到PID=22的TS分组,解出PMT,得到这个节目中包含的原始流的PID,再根据原始流的PID去找相应的TS分组,获取原始流的数据,然后就可以送入解码器解码了。


数据结构(1)TS分组前面提到,TS分组由188个字节构成,其结构如下:
transport_packet(){
sync_byte // 8
transport_error_indicator //1
payload_unit_start_indicator //1
transport_priority // 1 PID //13
transport_scrambling_control // 2
adaptation_field_control //2
continuity_counter //4
if(adaptation_field_control=='10' || adaptation_field_control=='11'){
adaptation_field()
}
if(adaptation_field_control=='01' || adaptation_field_control=='11') {
for (i=0;i<N;i++){
data_byte //8
}
}
}

前面32bit的数据即TS分组首部,它指出了这个分组的属性。

sync_byte 同步字节,固定为0x47 ,表示后面的是一个TS分组,当然,后面包中的数据是不会出现0x47的

transport_error_indicator 传输错误标志位,一般传输错误的话就不会处理这个包了

payload_unit_start_indicator 这个位功能有点复杂,字面意思是有效负载的开始标志,根据后面有效负载的内容不同功能也不同,后面用到的时候再说。

transport_priority 传输优先级位,1表示高优先级,传输机制可能用到,解码好像用不着。

PID 这个比较重要,指出了这个包的有效负载数据的类型,告诉我们这个包传输的是什么内容。前面已经叙述过。

transport_scrambling_control 加密标志位,表示TS分组有效负载的加密模式。TS分组首部(也就是前面这32bit)是不应被加密的,00表示未加密。

adaption_field_control 翻译为“调整字段控制”,表示TS分组首部后面是否跟随有调整字段和有效负载。01仅含有效负载,10仅含调整字段,11含有调整字段和有效负载。为00的话解码器不进行处理。空分组没有调整字段

continuity_counter 一个4bit的计数器,范围0-15,具有相同的PID的TS分组传输时每次加1,到15后清0。不过,有些情况下是不计数的。如下:(1)TS分组无有 效负载(2)复制的TS分组和原分组这个值一样(3)后面讲到的一个标志discontinuity_indicator为1时

adaptation_field() 调整字段的处理

data_byte 有效负载的剩余部分,可能为PES分组,PSI,或一些自定义的数据。


(2) PAT数据结构如下:
program_association_section() {
table_id // 8
section_syntax_indicator // 1
'0' // 1
reserved // 2
section_length // 12
transport_stream_id // 16
reserved // 2
version_number // 5
current_next_indicator // 1
section_number // 8
last_section_number // 8
for (i=0; i<N;i++) {
program_number // 16
reserved // 3
if(program_number == '0') {
network_PID // 13
}
else {
program_map_PID // 13
}
}
CRC_32 // 32
}

table_id 固定为0x00 ,标志是该表是PAT

section_syntax_indicator 段语法标志位,固定为1

section_length 表示这个字节后面有用的字节数,包括CRC32。假如后面的字节加上前面的字节数少于188,后面会用0XFF填充。假如这个数值比较大,则PAT会分成几部分来传输。

transport_stream_id 该传输流的ID,区别于一个网络中其它多路复用的流。

version_number范围0-31,表示PAT的版本号,标注当前节目的版本.这是个非常有用的参数,当检测到这个字段改变时,说明TS流中的节目已经变化了,程序必须重新搜索节目.

current_next_indicator 表示发送的PAT是当前有效还是下一个PAT有效。

section_number分段的号码。PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段

last_section_number 最后一个分段的号码

program_number 节目号

network_PID 网络信息表(NIT)的PID,网络信息表提供了该物理网络的一些信息,和电视台相关的。节目号为0时对应的PID为network_PID

program_map_PID 节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个

CRC_32 CRC32校验码

上面program_number,network_PID,program_map_PID 是循环出现的。program_number等于0时对应network_PID,program_number等于其它值时对应program_map_PID。


(3)PMT PMT数据结构如下:
TS_program_map_section() {
table_id // 8
section_syntax_indicator // 1
'0' // 1
reserved // 2
section_length // 12
program_number // 16
reserved // 2
version_number // 5
current_next_indicator // 1
section_number // 8
last_section_number // 8
reserved // 3
PCR_PID // 13
reserved 4
program_info_length // 12
for (i=0; i<N; i++) {
descriptor()
}
for (i=0;i<N1;i++) {
stream_type // 8
reserved // 3
elementary_PID // 13
reserved // 4
ES_info_length // 12
for (i=0; i<N2; i++) {
descriptor()
}
}
CRC_32 // 32
}

table_id 固定为0x02 ,标志是该表是PMT。

section_syntax_indicator section_length version_numbercurrent_next_indicator 以上四个字段意思和PAT相同,可参考上面解释

section_number last_section_number 以上两个字段意思和PAT相同,不过值都固定为0x00,我觉得这样的原因可能是因为PMT不需要有先后顺序,因为先定义哪个节目都是无所谓。

program_number 节目号,表示该PMT对应的节目

PCR_PID PCR(节目时钟参考)所在TS分组的PID,根据PID可以去搜索相应的TS分组,解出PCR信息。

program_info_length 该节目的信息长度,在此字段之后可能会有一些字节描述该节目的信息

stream_type 指示了PID为elementary_PID的PES分组中原始流的类型,比如视频流,音频流等,见后面的表

elementary_PID 该节目中包括的视频流,音频流等对应的TS分组的PID

ES_info_length 该节目相关原始流的描述符的信息长度。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/a31898534/archive/2009/08/01/4399353.aspx

 

16 11, 2010

MPEG2 TS流

作者 helloxchen 17:22 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础

该说真正了解TS,还是看了朋友推荐的《数字电视业务信息及其编码》一书之后,MPEG2 TS和数字电视是紧密不可分割的,值得总结一下其中的一些关系。

ISO/IEC138181系统部分;ISO/IEC138182:视频;ISO/IEC138183:音频;ISO/IEC 138184:一致性测试;ISO/IEC138185:软件部分;ISO/IEC138186:数字存储媒体命令与控制;ISO/IEC 138187:高级音频编码;ISO/IEC138188:系统解码实时接口;

MPEG2系统任务包括:1.规定以包传输数据的协议;2.规定收发两端数据流同步的协议;3.提供多个数据流的复用和解复用协议;3.提供数据流加密的协议。以包形式存储和传送数据流是MPEG2系统之要点。

ES是直接从编码器出来的数据流,可以是编码过的视频数据流,音频数据流,或其他编码数据流的统称。ES流经过PES打包器之后,被转换成PES包。PES包由包头和payload组成,具体格式摘录如下:

500){this.resized=true;this.style.width=500;}"resized="true" />

可以看到PTS/DTS是打PES包里面的,这两个parameters是解决视音频同步显示,防止解码器输入缓存上溢或下溢的关键。PTS表示显示单元出现在系统目标解码器(STD: system target decoder)的时间,DTS表示将存取单元全部字节从STDES解码缓存器移走的时刻。每个IPB帧的包头都有一个PTSDTS,但PTSDTSB帧都是一样的,无须标出B帧的DTS。对I帧和P帧,显示前一定要存储于视频解码器的重新排序缓存器中,经过延迟(重新排序)后再显示,一定要分别标明PTSDTS

上节介绍过,ES首先需打包成PES流包,然后PES根据需要打包成PSTS包进行存储或传输。其每路ES只包含一路信源的编码数据流,所以每路PES也只包含相对应信源的数据流。

PS流而言,每个PES包头含有PTSDTS,流识别码,用于区别不同性质ES。然后通过PS复用器将PES包复用成PS包。实际上是将PES包分解为更细小的PS包。在解码的时候,解复用器将PS分解成一个个PES包,拆包器然后将PES包拆成视频和音频的ES,最后输入至各自解码器进行解码。一个问题是:各个ES在解码时,如何保证视音频的同步呢?除了PTSDTS的配合工作外,还有一个重要的参数是SCR(system clock reference)。在编码的时候,PTSDTSSCR都是由STC(system timeclock)生成的,在解码时,STC会再生,并通过锁相环路(PLLphase lock loop),用本地SCR相位与输入的瞬时SCR相位锁相比较,以确定解码过程是否同步,若不同步,则用这个瞬时SCR调整27MHz的本地时钟频率。最后,PTSDTSSCR一起配合,解决视音频同步播放的问题。PS格式摘录如下:

500){this.resized=true;this.style.width=500;}"resized="true" />

PS包的长度比较长且可变,主要用于无误码环境里,因为越长的话,同步越困难,且在丢包的情况下,重组也越困难。所以,PS适合于节目信息的编辑和本地内容应用的application

TS流也是由一个或多个PES组合而来的,他们可以具有相同的时间基准,也可以不同。其基本的复用思想是,对具有相同时间基准的多个PES现进行节目复用,然后再对相互有独立时间基准的各个PS进行传输复用,最终产生出TS

TS包由包头和包数据2部分组成,其中包头还可以包括扩展的自适用区。包头长度占4bytes,自使用区和包数据共占184bytes,整个TS包长度相当于4ATM包长。TS包的包头由如下图摘录所示的同步字节、传输误码指示符、有效载荷单元起始指示符、传输优先、包识别(PID-Packet Identification)、传输加扰控制、自适应区控制和连续计数器8个部分组成。

500){this.resized=true;this.style.width=500;}"resized="true" />

其中,可用同步字节位串的自动相关特性,检测数据流中的包限制,建立包同步;传输误码指示符,是指有不能消除误码时,采用误码校正解码器可表示1bit的误码,但无法校正;有效载荷单元起始指示符,表示该数据包是否存在确定的起始信息;传输优先,是给TS包分配优先权;PID值是由用户确定的,解码器根据PIDTS上从不同ES来的TS包区别出来,以重建原来的ES;传输加扰控制,可指示数据包内容是否加扰,但包头和自适应区永远不加扰;自适应区控制,用2 bit表示有否自适应区,即(01)表示有有用信息无自适应区,(10)表示无有用信息有自适应区,(11)表示有有用信息有自适应区,(00)无定义;连续计数器可对PID包传送顺序计数,据计数器读数,接收端可判断是否有包丢失及包传送顺序错误。显然,包头对TS包具有同步、识别、检错及加密功能。

TS包自适应区由自适应区长、各种标志指示符、与插入标志有关的信息和填充数据4部分组成。其中标志部分由间断指示符、随机存取指示符、ES优化指示符、PCR标志、接点标志、传输专用数据标志、原始PCR标志、自适应区扩展标志8个部分组成。重要的是标志部分的PCR字段,可给编解码器的27MHz时钟提供同步资料,进行同步。其过程是,通过PLL,用解码时本地用PCR相位与输入的瞬时PCR相位锁相比较,确定解码过程是否同步,若不同步,则用这个瞬时PCR调整时钟频率。因为,数字图像采用了复杂而不同的压缩编码算法,造成每幅图像的数据各不相同,使直接从压缩编码图像数据的开始部分获取时钟信息成为不可能。为此,选择了某些(而非全部)TS包的自适应区来传送定时信息。于是,被选中的TS包的自适应区,可用于测定包信息的控制bit和重要的控制信息。自适应区无须伴随每个包都发送,发送多少主要由选中的TS包的传输专用时标参数决定。标志中的随机存取指示符和接点标志,在节目变动时,为随机进入I帧压缩的数据流提供随机进入点,也为插入当地节目提供方便。自适应区中的填充数据是由于PES包长不可能正好转为TS包的整数倍,最后的TS包保留一小部分有用容量,通过填充字节加以填补,这样可以防止缓存器下溢,保持总码率恒定不变。

3节总结了MPEG2 TS的基本格式,其中包括PESPSTS,以及相关字段的介绍。那么作为一种传输流,TS将内容进行打包/复用,让其媒体内容变成TS传输,并最终在解码端解码。简单来看,TS是一个传输层的协议栈,它可以承载各种内容的传输,比如MPEGWMVH264,甚至是IP,那么其中的传输规范是如何定义的呢?这个即是PSI(节目特定信息)要做的事情。

PSI 由四张表构成:PATPMTCATNIT,这四张表分别描述了一个TS包括的所有ES流的传输结构。首先的一个概念是,TS是以包形式传播,在编解码端都需要以一定的包ID来标识TS流里承载的内容,比如,PAT表会存在于一个或多个TS包里,所以要用一个特别的包ID来表示,另外,不同的ES流也需要不同的包ID来标识。我们有了PATPMT这两种表,解码器就可以根据 PID,将TS上从不同ES来的TS包区分出来进行解码。

TS的解码分两步进行,其一,是从PID0TS包里,解析出PAT表,然后从PAT表里找到各个节目源的PID,一般此类节目源都由若干个ES流组成,并描述在PMT表里面,然后通过节目源的 PID,就可以在PMT表里检索到各个ESPID。其二,解码器根据PMT表里的ES流的PID,将TS流上的包进行区分,并按不同的ES流进行解码。所以,TS是经过节目复用和传输复用两层完成的,即在节目复用时,加入了PMT,在传输复用时,加入了PAT。同样在节目解复用时,可以得到PMT,在传输解复用时,可以得到PAT。下图很好地概述了其思想。

500){this.resized=true;this.style.width=500;}"resized="true" />

TS是支持多路复用的,所以它可用来传输经复用后的多层节目。在复用过程中,要注意的是,解码过程中所需要面对的时间参考和同步问题,因为解复用是需要各种信息同步进行的,所以在复用过程中,就需要插入相关的时间信息:PTSDTSPCR

TS形成过程中,PTSDTS是在ES打包成PES时,根据STC的参考,将其时钟信息注入PES包中的,而之后在PES切成TS时,再将 PIDPCR⒆⑷肫浒械摹T诙嘟谀扛从檬保扛鼋谀縏SPCR将会被提取出来,再进行分析,然后再根据统一的STC参考,将新的PCR生成并注入TS中去,最后,因为原来PAT表信息不在适用,所以新的PAT表需要再生成,并附加到新的TS流中去。经过这多层的复用之后,新的TS流即可以进入调制,传输阶段。过程可参见下图:

500){this.resized=true;this.style.width=500;}"resized="true" />

码过程要面对的问题是:解复用,视音频的同步,解码缓存器无上下溢。解复用即是将TS在同一信道里不同时序进行传输的节目分离出来;视音频同步由 DTS, PTSPCR三者协调完成,并且PCR是重建系统时间基准的绝对时标,而DTSPTS是解码和重现时刻的相对时标;对解码缓存器无上下溢的问题,必须借助于系统目标解码器(STD)模型来对其进行实现,基本思想如下:

1.                 TS流进入解码器后,首先由换向器,按照一定的时序关系,将各种ES流分解出来(其中也包括PSI信息流)。

2.                 分解过后的ES流会进入各自的传输缓存器,通过之后,其PES流进入各自的主存储器,注意的是:PSI信息流会进入系统缓存器,最后也到达主存储器。

3.                 最后,解码器根据DTS信息,从各个主存储器分别提取媒体或系统信息,进行解码,并根据PTS信息,将媒体内容进行显示处理。

其过程可参见下图:

500){this.resized=true;this.style.width=500;}"resized="true" />

from:
http://www.chinaavs.com/htmldata/6/2006_09/MPEG2-TS%D0%A1%BD%E1742_1.html

16 11, 2010

TS流解析之PMT表格解析(转)

作者 helloxchen 17:17 | 静态链接网址 | 最新回复 (0) | 引用 (0) | 视频基础

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表
unsigned section_syntax_indicator : 1; //固定为0x01
unsigned zero : 1; //0x01
unsigned reserved_1 : 2; //0x03
unsigned section_length : 12;//首先两位bit置为00,它指示段的byte数,由段长度域开始,包含CRC。
unsigned program_number : 16;// 指出该节目对应于可应用的Program mapPID
unsigned reserved_2 : 2; //0x03
unsigned version_number : 5; //指出TS流中Program map section的版本号
unsigned current_next_indicator : 1; //当该位置1时,当前传送的Program map section可用;
//当该位置0时,指示当前传送的Program mapsection不可用,下一个TS流的Program mapsection有效。
unsigned section_number : 8; //固定为0x00
unsigned last_section_number : 8; //固定为0x00
unsigned reserved_3 : 3; //0x07
unsigned PCR_PID : 13; //指明TS包的PID值,该TS包含有PCR域,
//该PCR值对应于由节目号指定的对应节目。
//如果对于私有数据流的节目定义与PCR无关,这个域的值将为0x1FFF。
unsigned reserved_4 : 4; //预留为0x0F
unsigned program_info_length : 12; //前两位bit为00。该域指出跟随其后对节目信息的描述的byte数。

std::vector<TS_PMT_Stream> PMT_Stream; //每个元素包含8位, 指示特定PID的节目元素包的类型。该处PID由elementary PID指定
unsigned reserved_5 : 3; //0x07
unsigned reserved_6 : 4; //0x0F
unsigned CRC_32 : 32;
} TS_PMT;

解析代码为:

HRESULT CTS_Stream_Parse::adjust_PMT_table ( TS_PMT * packet, unsignedchar * 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 0x12 0x00 0x02 0xb0 0x12 0x00 0x01 0xc1 0x00 0x00 0xe3 0xe90xf0 0x00 0x1b 0xe3 0xe9 0xf0 0x00 0xf0 0xaf 0xb4 0x4f 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff0xff 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


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/a31898534/archive/2009/08/0

 

原创粉丝点击