打包TS流

做这个东西很久了,从去年十二月份开始的,快5个月了。。。期间因为工作一直断断续续,直到最近才有了些进展,也就到此为止吧。

先说下我做的是什么吧,总的来说,就是把H264视频流与AAC音频流封装成TS格式。要完成这么个功能首先要解析H264和AAC,获得视频帧和音频帧以及一些关键信息,比如帧率、采样率什么的为以后的打包做工作。分析到每一帧数据后,再加上PES头,封装成PES数据,在这个过程要注意打PTS、DTS,以及PES类型的设定,一般来说,视频PTS与DTS都要打,而音频只需要打PTS。最后一步就是将PES数据封装成TS了,这一步的坑比较多。做完这些,最难个人感觉就是音视频同步了,至今还比较疑惑,视频播出来总是有瑕疵。

一、解析h264

H264是以0x00 00 01或0x00 00 00 01作为分割NALU单元分割符的,整个H264也是以NALU作为基本单元的,NALU的结构如图一所示:


 图1

NALU包含头和负载RBSP,头部的句法结构如图二所示:

·图2

这一个字节便包含了整个头部的信息,也就是去除分割符后的头一个字节。nal_unit_type指明的NALU的类型,不多说了,贴图吧。。。

图3

NALU的负载RBSP在读取有效数据之前首先要做去除竞争码的操作,遇到0x00 00 03将字节0x03去掉即可。

·0x00 00 00  -----> 0x00 00 03 00

·0x00 00 01  -----> 0x00 00 03 01

·0x00 00 02  -----> 0x00 00 03 02

·0x00 00 03  -----> 0x00 00 03 03

负载的类型由前文nal_unit_type指定,当值大于等于1,小于等于5的时候指明该负载是片数据(slice),去除竞争码后的负载采用的是哥伦布编码。在看片数据句法结构的时候可以留意到每个字段后面都有一些符号说明,如ue(v)se(v),给张图看的比较清楚

图4

我贴的是片头句法的一部分,在合成TS的时候只要判断片类型就可以了,也就是说,只要将图4的slice_type分析出即可,要想完整的解出片还是有点难度的,不过判断片的类型并不难。

回到前面的哥伦布编码,这是一种变长编码,括号里面带v的均是指编码的长度不固定。

或根据哥伦布编码动态确定,或由前文指定,符号ue(v)se(v)说明解码的长度可根据哥伦布编码的规则动态指定,而u(v)这里的解码长度得由上文指定,有几个字段都是如此,如何由上文确定长度的资料不太好找,只找到一个英文的网站,它说明了几个字段(http://m.blog.csdn.net/article/details?id=40302581)

解码哥伦布编码的字段可以选用网上的库,看起来还行,但因为是在研究学习这个东西,

我选择了自己实现这个算法,也不是特别复杂。首先说下无符号的哥伦布编码,按网上的定义,哥伦布的编码方式有0阶、1阶、2阶...每种好像都不一样,也没有都去研究,因为H264用到的只是0阶的哥伦布编码,弄懂这个就够用了。

1.1无符号0阶指数哥伦布编码(ue)

按照算法的描述,应先将数据以二进制方式排列,接着连续读取M个0比特位,碰到1比特位结束读取,这时解码的数据长度就已经确定了,为2*M+1个比特,包含前面已经读取的M个0比特。再将最后的M位比特转化成十进制数值W,即可获得解码结果为W-1,到此为止,一次完整的无符号解码就完成了。

·无符号哥伦布编码测试例子:

                                                  (解码后)

0x4C  0x85  0x31  0xC4  0x09 =====> 1  2  3  4  5  6  7  8

1.2有符号0阶指数哥伦布编码(se)

有符号的哥伦布编码建立在无符号的基础上,按照无符号的解码步骤得到十进制数n后,再实行以下的动作即可。

·获取n被2除后的商w(注意这个商是去除了小数的整数部分)

·n不能被2整除,w加1为解码结果

·n可以被2整除,w的相反数即为解码结果

                                                (解码后)

0x4C  0x85  0x31  0xC4  0x09 =====>1  -1  2  -2  3  -3  4  -4

1.3帧类型

前面在解析片数据的时候有提到过片的类型,实际上这里指明的是片所装载的视频帧的类型,按定义分为I,P,B,SP,SI,图5给出slice_type与帧类型的对应关系

图5

我在解码的时候只关心了I,P,B帧,另外的两种帧的意义感觉和I,P帧类似,所以没做过多的研究。I,P,B帧的差别在于编码方式的差异,I帧数据不依赖其他的帧数据,可独立解码,而P帧使用前向预测编码,它的解码依靠前面解码的I帧和P帧,B帧用了双向预测编码,解码B帧图像,需要同时参考前后的帧数据。它们压缩数据的能力也依次增强,一般来说,B帧比较多的网络视频流同等比特量所传输的视频质量会更高,但是相应的,播放器的解码压力也更大。由于P、B帧的解码需要参考其他帧,所以在解码的时候可能会产生误码扩散的情况,但由于播放器在遇到I帧的时候会丢弃之前解码的视频数据,再以当前的I帧作为以后P、B帧的参考帧,因此这一举措及时的阻止了错误的扩散,说到这里,我得额外提下HLS协议,这个协议在制作切片的时候总是以I帧开头,不仅是为了在播放视频的时候立马刷出图像,我想更多的是因为H264这种编码格式的限制而不得不以I帧开头,因为只有I帧是可以独立解码的,如果开头的是其他需要参考的帧,他们的解码毫无意义。

二、打包PES

PES是对裸流的一层封装,H264和AAC就是两种裸流,他们就像还没装订的书,随意的被堆放在角落里,PES的封装就是将散乱的纸打上页码装成书的过程。这里的页码指的就是PTS与DTS,显示时间戳和解码时间戳,这个时间可以指示播放器何时显示一帧图像或者音频,一般来说视频帧PTS、DTS都需要打,尤其是在有B帧的情况下,因为B帧需要参考前后两帧,所以它要等到前后帧都被解码出后才能解此B帧,因此B帧的显示时间和解码时间是不一致的,而音频帧没有这种情况,解一帧放一帧即可,它是不需要DTS的。PES头的句法结构如图6


 图6(1)


  图6(2)

PES的整结构没有完整的贴上来,只贴到PTS、DTS这部分句法,其实在打包TS的时候,这部分已经够用了,其他的可以不去关心。接下来说明一下几个重要的字段。

·packet_start_code_prefix

这3个字节是PES的起始码,将PES包一一隔开,起始是固定的0x00 0x00 0x01

·stream_id

指明PES负载的类型,视频为0xE0,音频为0xC0

·PES_packe_length

说明在此字段最后一个字节之后PES分组的字节数。‘0’值表明PES分组的长度既没有说明也没有限制,这种情况只有在PES分组的有效负载是传送流分组中的视频原始流时才允许。

·PTS_DTS_flag

2位标志,若为‘10’,则PES分组首部有PTS字段;若为‘11’,则PES分组首部有PTS和DTS字段;若为’00’则PTS和DTS都不在PES分组首部出现;‘01’值被禁止。

·PES_header_data_length

说明在此字段最后一个字节还剩余的PES头部分组数据量,一般可能指的是PTS与DTS部分编码的长度。

PES分组中比较重要的字段就上面这些的,对于PTS与DTS的编解码,按照PTS_DTS_flag以及相应的句法指示去做即可。

三、合成TS

TS流每个包的大小都介于188-204字节之间,一般就是188字节,每个PES包基本都大于188字节,所以在把PES封装成TS时,需要将PES分割成多个TS包,每个包都有唯一的ID标识它的类型,音视频PES打包成TS包的ID由TS中的PMT表指定,PMT表被单独打成一个TS包,它也有ID标识,这个标识由PAT表指定,与PMT类似,PAT也被打成独立的包,它的ID是固定的0,所以,不管是解码还是编码TS流,都应从PAT表入手。

3.1 TS包格式

  图7

TS包由头与负载组成,头部占四个字节,如果还有自适应区,那么自适应区的长度由第五个字节指定。这里先简要的分析下TS头的前四个字节。

·sync_byte

此处是同步字节,固定为0x47

·transport_error_indicator

这个比特位我没有用到过,一直都是0

·payload_unit_start_indicator

此比特位为1,标志着PES包负载的开始,不仅如此,如果是PAT、PMT包,这里也被置为1

·transport_priority

我暂没用到,默认是0

·PID

每个包种类的标识

·transport_scrambling_control

我暂未使用

·adaptation_field_control

此字段标识是否有自适应区

00:是保留值。

01:负载中只有有效载荷。

10:负载中只有自适应字段。

11:先有自适应字段,再有有效载荷。

如果含有自适应区,那么头四个字节后就是自适应区的码流结构,最后才是TS包的负载部分

·continuity_counter

这个字段的值从00 - 15反复连续变化

3.2自适应区格式

 图8

·adaptation_field_length

这个字节指明了自适应区的长度,不包含此字节

·PCR_flag

如果此位被置为1,按照句法格式,接下来存在PCR值,这个值按照特定的公式转化成了program_reference_baseprogram_clock_reference_extension两个字段的值

PCR是音视频同步的关键参数,它决定播放器什么时候播放一帧音频或视频,它本质上是一个类似于时间戳的东西,它可以用PES包的PTS或DTS来赋值,假设这个值是timestamp,那么就有

program_clock_reference_base=  timestamp       (mod)  2^33

program_clock_reference_extension  = (300 * timestamp)  (mod)  300

PCR  =program_clock_reference_base* 300  +program_clock_reference_extension

3.3PAT

 图9

PAT表指明了PMT表的PID,每个PMT表又可以指出音视频包的PID值,从这里可以看出一个PMT表就代表一个音视频流,或者说一个节目,当有多种PMT表时,说明在这个TS流中存在多种音视频流,多个节目,从而实现了节目复用的效果,实际上PAT表是可以索引多种PMT表的,但我在打流的时候只上了一种PMT,没关系,东西在精不在多,把一种研究透了,其他的也就是复制粘贴的效果。

图10贴的是PAT表负载的section部分,简单的划分下PAT表四个区域,第一个是头四个字节,第二是自适应区,不过这个自适应区没内容,因此标志自适应长度的那个字节为0,第三个就是这个section部分,在里面指明了PMT表的PID值,第四个是填充区域,严格来说,它和section共同组成了ts包负载的部分,一般填充区域的比特位都置为1。

·table_id

PAT表此值为0

·section_syntax_indicator

此位置为1

·reserved

像这种标志比特位都是置为1的

·section_length

这12位指出了从该字段之后到CRC_32(包含它)为止的字节数

·CRC_32

4字节的循环校验码,它根据section区域动态计算得出

3.4 PMT

图10(1)
 图10(2)

PMT表的作用是定位音视频包的PID值,以方便播放器找到并解码,接下来说下几个重要的字段。

·table_id

此处的值固定为0x02

·PCR_PID

这个字段指明了PCR值所在TS包的PID

·program_info_length

指定了接下来描述子decriptor()的大小,没什么需要描述的就置为0吧

·stream_type

流类型,如下图所示


H264的视频,流类型为0x1b,AAC音频一般为0x0f,随后的PID值就是对应的TS包标识了

·ES_info_length

说明ES流的附加信息,可有可无,如果没有附加信息,这个字段值为0

·CRC_32

与PAT的校验码类似

3.5打包H264视频帧

一般来说,一个负载为I、P、B片的PES包可看做一帧,也许说的不太准确,一帧视频从概念上说是连环画的完整的一页,而一片呢可能就只是一页的局部画面了,但我打包的时候是根据I、B、P片为单位的,为了好理解也叫一帧吧。

梳理下从H264到TS的过程,首先H264的单元叫NALU,一个I、B、P片就对应一个NALU,NALU加上PES头打上PTS或DTS后得到一个PES包,我们现在要做的就是将这个包含视频数据的PES包分割成一个又一个的TS包,也就是打包一帧视频。

PES包数据在打包TS时,均作为TS包的负载部分。装载PES的第一个TS包,前面说过,payload_unit_start_indicator字段要为1,这标志着一帧PES数据的开始。一般打包视频帧的时候也会打上PCR的值,不是每帧都要打,不过对于I帧最好还是打上。还有一点要注意的是,在从NALU到PES的时候,在每帧数据前都要加上0x000000 01 09 xx这六个字节,然后再打PES头,xx的值好像可以任意选定,如此打出的流苹果之类的播放器才能支持。

最后再说下I帧,在打包I帧的时候比打包其他两帧有更多需要注意的问题,首先,在I帧的前面最好能打上PAT与PMT表,如此一来,在任意一段的TS流中,只要包含了I帧,那么有很大几率可以被解码播放,这也是TS流的一个特点。H264除开包含I、B、P的NALU外,还有两种包含视频信息的NALU,SPS与PPS,分别是序列参数集与图像参数集,他们对于解码I、B、P片有重要作用,没了这两种NALU播放器是解码不出视频的,所以为了保证视频能正常解码,在打包TS流时,这两种NALU被放到了I帧数据的前面,它们是与I帧数据一起被封装成PES包再分割成TS的,这与其他帧一个NALU对应一个PES包有所不同。

在分割视频的一个PES包时,TS头的continuity_counter值要保持连续,从0-15反复变化。它接着上一次打包视频帧时最后那个TS包的coutinuity_counter值连续变化。

3.6打包AAC音频帧

AAC的格式比较简单,它的单元以0xFFF做为起始码,我们打包的就是一个以0xFFF开头的AAC单元。我的做法是一个AAC单元对应一个PES包,再分割成一个个TS包,这样做可能有些浪费数据量,因为一个AAC单元相比一帧视频要小得多,在打包成TS时,为了保持每个TS包都是188字节,在装载PES数据的最后一个TS包时往往要加上填充字节,填充字节是加在自适应区的,由于一个AAC单元所装载的数据量很小,所以相比于视频帧来说,AAC单元的数量多,那么最后打成的TS包就含有较多无用的填充字节,浪费传输的带宽,我看FFMPEG在打音频帧的时候是10多个AAC单元被同时包成一个PES包的,我不知道它这样打的规则,所以也没有用,而且一个AAC单元对应一个PES包的做法实现起来比较简单并且播放器也能正常解码,所以就采用了这种打法。

打音频帧比视频帧简单多了,除了开头包的payload_unit_start_indicator要为1,以及coutinuity_counter值连续变化之外,没有别的要求,不过还是要提醒一点的是coutinuity_counter值是接着上一次打包音频数据的coutinuity_counter来的。

3.7音视频同步

这个问题我也在探究中,所以我这里不做说明,只贴出几个参考的链接,这些链接中包含了PTS与DTS的打法,但是没有PCR的打法,我目前就是视频帧PES包的PTS直接就作为TS包的PCR值了。

[TS流打包总结]

http://blog.csdn.net/yuan1125/article/details/51540918

[ffmpeg转码MPEG2-TS的音视频同步机制分析]

http://blog.chinaunix.net/uid-26000296-id-3483782.html

四、后记

这次学习打TS流的过程我很满意,虽然花费的时间比较长,但这也算得上我第一次探究学习的经历吧,以前要学一样东西,资料总是齐备的,看几眼就能知道个大概,因为是在学校,有教材与老师领着,但这仅限于教材上的东西,在进入社会工作后,很多知识与技能都是没有完备的教程的,即使有,可能也不一定能找得到。学习这些资料缺失的知识是一次探究学习的过程,因为资料稀缺,为了了解其中一个知识面,不得不借助搜索工具大量查阅,在翻阅了几十个网站终于得到一句话甚至是一个字的线索时,那时不得不说,我是惊喜和满足的,到最后我能打成一段可以播放的TS流时,我觉得这五个月再长也值了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,491评论 0 3
  • [TOC] 音视频&流媒体 是什么促使我要写这一篇音视频入门文章?那是因为和一妹子打赌码率的概念,结果输了;对一个...
    AllenWu阅读 4,627评论 1 25
  • 原文地址: http://www.360doc.com/content/16/0531/19/33873500_5...
    rogerwu1228阅读 3,629评论 0 9
  • 数字电视有哪几种层次的码流?如何形成?各自特点? ES (Elementary Stream) — 数字电视各组成...
    Annnnnn阅读 2,772评论 0 2
  • 写完“引”已然两个半月了,而我总算在国庆假日的第四天,翻出了“高四”时的手稿以及那忧郁一年的七张素画,还没看...
    合秣阅读 162评论 0 0