一犬缨、TS HEADER
參考
TS科普 2 包頭
TS流格式學(xué)習(xí)
Ts流解析中難點(diǎn)說明
百度文庫 最直白明了的TS流分析
在jsmpeg系列二 TS碼流 PAT PMT有提到TS header的結(jié)構(gòu)冠摄,下面重點(diǎn)介紹其中幾個(gè)。
名稱 | 長度 | 說明 |
---|---|---|
sync_byte | 8bit | 同步字節(jié),固定為0x47 |
transport_error_indicator | 1bit | 傳輸錯(cuò)誤指示符翎蹈,表明在ts頭的adapt域后由一個(gè)無用字節(jié)隅熙,通常都為0,這個(gè)字節(jié)算在adapt域長度內(nèi) |
payload_unit_start_indicator | 1bit | 負(fù)載單元起始標(biāo)示符森逮,一個(gè)完整的數(shù)據(jù)包開始時(shí)標(biāo)記為1 |
transport_priority | 1bit | 傳輸優(yōu)先級(jí),0為低優(yōu)先級(jí)磁携,1為高優(yōu)先級(jí)褒侧,通常取0 |
pid | 13bit | pid值(Packet ID號(hào)碼,唯一的號(hào)碼對(duì)應(yīng)不同的包) |
transport_scrambling_control | 2bit | 傳輸加擾控制谊迄,00表示未加密 |
adaptation_field_control | 2bit | 是否包含自適應(yīng)區(qū)闷供,‘00’保留;‘01’為無自適應(yīng)域统诺,僅含有效負(fù)載歪脏;‘10’為僅含自適應(yīng)域,無有效負(fù)載篙议;‘11’為同時(shí)帶有自適應(yīng)域和有效負(fù)載唾糯。 |
continuity_counter | 4bit | 遞增計(jì)數(shù)器,從0-f鬼贱,起始值不一定取0移怯,但必須是連續(xù)的 |
1.sync_byte 0x47
用UltraEdit打開的一個(gè)TS流,我們發(fā)現(xiàn)每隔188個(gè)字節(jié)就有一個(gè)47(可以看做是包頭)
2.transport_error_indicator
錯(cuò)誤指示位这难,如果為1舟误,表明該包有錯(cuò)誤。
3.payload_unit_start_indicator起始符
要說明的是一個(gè)Ts包(188字節(jié))往往是放不下一個(gè)PES包的姻乓,那就需要截取發(fā)送嵌溢,那么截取出來的包中眯牧,肯定有的是含有包頭的,但是有的是不含有包頭的赖草, 這個(gè)區(qū)分是靠這個(gè)字段的学少。所以在解析的時(shí)候,如果他置1秧骑,那么他后面的就是一個(gè)包頭版确,既然是包頭,那就可以進(jìn)一步的解析乎折。
該TS包的payload為PES:如果標(biāo)志位為1绒疗,則該TS包的payload第一個(gè)Byte是某一PES包的第一個(gè)byte;如果標(biāo)志位為0骂澄,則沒有PES包從該TS包開始吓蘑。
該TS包的payload為PSI/SI:如果標(biāo)志位為1,則該TS包的第一個(gè)byte是pointer_field,pointer_field指向某一PSI section的第一個(gè)byte開始位置坟冲;如果標(biāo)志位為0磨镶,則不存在pointer_field且此TS包不包含任何PSI section的第一個(gè)byte
以上兩種情況,先說一下第二種情況健提。在jsmpeg系列二 TS碼流 PAT PMT解析PAT部分中棋嘲,曾經(jīng)出現(xiàn)過包頭為47 40 00 1C 00 00 B0 1D...
解析PID=0x0000,這說明是PAT表矩桂。然后payload_unit_start_indicator=1,說明在包頭后需要除去一個(gè)字節(jié)才是有效數(shù)據(jù)痪伦。當(dāng)時(shí)并未說明原因侄榴,現(xiàn)在看起來這個(gè)00正是第二種情況提到的pointer_field(程序特殊信息指針)。讀到這個(gè)pointer_field=0后网沾,需要跳0后癞蚕,才是PSI section的第一個(gè)byte開始位置,這相當(dāng)于沒有跳辉哥,所以說只需要去除pointer_field本身這個(gè)字節(jié)桦山,就是有效數(shù)據(jù)了。
4.adaptation_field_control
指出TS packet header后是否跟adaption field 和/或 payload.
- 00 reserved for future use by ISO/IEC
- 01 no adaptation_field,payload only
- 10 adaptation_field only,no payload
- 11 adaptation_field followed by payload
結(jié)論:首先應(yīng)該通過adaption_field_control 調(diào)整字段來確認(rèn)醋旦,有沒有調(diào)整字段恒水,有跳過(獲取調(diào)整字段里面的length),在確認(rèn)了調(diào)整字段后指針已經(jīng)指向了有效負(fù)荷區(qū)饲齐。
ts.js中parsePacket方法有如下片斷做印證:
// Extract current payload
if (adaptationField & 0x1) {
if ((adaptationField & 0x2)) {
var adaptationFieldLength = this.bits.read(8);
this.bits.skip(adaptationFieldLength << 3);
}
...
首先钉凌,& 0x1
的判斷,就濾掉了00 和 10這兩種情況捂人,也就是說沒有payload后面就不用執(zhí)行了御雕。然后& 0x2
就是鎖定11這種情況矢沿,即payload前面還有adaptation_field。根據(jù)上面的圖酸纲,可以this.bits.read(8)獲得adaptationFieldLength捣鲸,然后跳過去。
5.continuity_counter
連續(xù)計(jì)數(shù)器闽坡。相同PID的TS包此值比前一個(gè)包增加1栽惶,達(dá)到最大值15后從0開始。但若adaptation_field_control的值為00或10時(shí)不增1无午。另外媒役,重復(fù)包(duplicate packet)也不增1。重復(fù)包是指除了PCR(如果有)以外整個(gè)包都與前一個(gè)包一樣的包宪迟。
如果除了以上特殊情況酣衷,還出現(xiàn)不連續(xù),說明有包丟失次泽。
6.payload
Payload包括PES\PSI/SI穿仪,但同一packet只包含PES或PSI/SI。PES以0x000001開始意荤,如果開始字節(jié)不是0x000001啊片,則內(nèi)容是PSI,PSI部分以1個(gè)字節(jié)的pointer_field開始玖像。
if (transport_packet_header.adaptation_field_control & 0x02)// 10 11
//10’僅含調(diào)整字段紫谷,無有效負(fù)載;‘11’調(diào)整字段后為 有效負(fù)載捐寥。
{
size = * buff + 1; //adaptation_field(buff);
buff += size; // 跳過調(diào)整字段
leng -= size; // 剩下的包長度
}
if (transport_packet_header.adaptation_field_control & 0x01)
{ // 01 只有有效負(fù)載
if (buff[0] == 0x00 && buff[1] == 0x00 && buff[2] == 0x01)
{//pes包的包頭是 0x 00 00 01
//log("dvbstrPESstream_ID");
pes_packet(buff, leng,& transport_packet_header);
}
else {
//PSI 數(shù)據(jù)笤昨。
//核心點(diǎn):這里的pointer只在PES或者PSI的開始包中有,
//其大小為8位,其值為從這里到真正有效負(fù)載開始的距離,
//而是不是開始包的判斷哪當(dāng)然是依靠payload_unit_start_indicato字段握恳。
int pointer = * buff + 1;
// printf("zhangfeionline__%d\n",pointer);
if (transport_packet_header.payload_unit_start_indicator)
// 是開始包瞒窒,那么里面就包含了一個(gè)字節(jié)的
// pointer_field(程序特殊信息指針),需要解析出跳過
{
buff += pointer;
leng -= pointer;
}
// PSI
...
再來個(gè)例子乡洼,有個(gè)188bytes的TS包是47 40 10 37 01 00 00 40...
:
(1)ts header 47 40 10 37崇裁,解析出來PID=0x010,說明是個(gè)NIT表束昵,adaptation_field_control=11b拔稳,說明adaptation_field followed by payload
(2)然后去讀adaptationFieldLength,讀到了第5個(gè)字節(jié)01妻怎,也就是說要跳1字節(jié)才能到達(dá)payload壳炎。把第6字節(jié)00跳過,來到了第7字節(jié)00
(3)payload_unit_start_indicator=1,并且該表是NIT匿辩,屬于PSI第二種情況腰耙,第一個(gè)字節(jié)是pointer_field,即第7字節(jié)00铲球,不用跳了挺庞,第8字節(jié)40即NIT section。
二稼病、PES
pes層是在每一個(gè)視頻/音頻幀上加入了時(shí)間戳等信息选侨,pes包內(nèi)容很多,我們只留下最常用的然走。
pes start code | 3B | 開始碼援制,固定為0x000001 |
stream id | 1B | 音頻取值(0xc0-0xdf),通常為0xc0;視頻取值(0xe0-0xef)芍瑞,通常為0xe0 |
pes packet length | 2B | 后面pes數(shù)據(jù)的長度晨仑,0表示長度不限制,只有視頻數(shù)據(jù)長度會(huì)超過0xffff |
flag | 1B | 通常取值0x80拆檬,表示數(shù)據(jù)不加密洪己、無優(yōu)先級(jí)、備份的數(shù)據(jù) |
flag | 1B | 取值0x80表示只含有pts竟贯,取值0xc0表示含有pts和dts |
pes data length | 1B | 后面數(shù)據(jù)的長度答捕,取值5或10 |
pts | 5B | 33bit值 |
dts | 5B | 33bit值 |
關(guān)于pes start code開始碼,固定為0x000001屑那,可以參考buffer.js的findStartCode方法拱镐。
關(guān)于stream id,音頻取值(0xc0-0xdf),通常為0xc0;視頻取值(0xe0-0xef)持际,通常為0xe0,可以參考ts.js常量
TS.STREAM = {
PACK_HEADER: 0xBA,
SYSTEM_HEADER: 0xBB,
PROGRAM_MAP: 0xBC,
PRIVATE_1: 0xBD,
PADDING: 0xBE,
PRIVATE_2: 0xBF,
AUDIO_1: 0xC0,
VIDEO_1: 0xE0,
DIRECTORY: 0xFF
};
以下參考TS協(xié)議解析第三部分(PES)痢站,對(duì)第1個(gè)PES包47 48 14 10 00 00 01 C0 01 88 80 80 05 21 00 01 96 07 FF FD 85 00 33 22...
解析
1.ts header
47 48 14 10
解析
- pid = 0x814,在PMT中查找音頻是program_map_PID為0x814
- payload_unit_start_indicator=1,有包頭选酗,也就是幀頭
- adaptation_field_control=01,no adaptation_field,payload only
- continuity_counter=0000
2.pes start code
找到了00 00 01起始碼
3.stream id
47 48 14 10 00 00 01 C0
4.pes packet length
0x01 88,即十進(jìn)制的392岳枷。也就是這幀長度是392字節(jié)芒填。
5.flag
80:1000 0000
10:默認(rèn)規(guī)定
00:PES加擾控制
0:PES優(yōu)先級(jí)
0:數(shù)據(jù)定位指示符
0:版權(quán)
0:原始的或復(fù)制的
6.flag
80:1000 0000
10:PTS_DTS_flags,10代表后面將會(huì)有PTS信息空繁。
000000:分別代表其他6個(gè)標(biāo)志殿衰,0表示后面沒有對(duì)應(yīng)的信息。
7.pes data length 05
PES頭數(shù)據(jù)長度盛泡,表示后面還有0x05個(gè)字節(jié)闷祥,之后就是一幀的數(shù)據(jù)內(nèi)容。
PES頭數(shù)據(jù)具體包含哪些內(nèi)容有前面的標(biāo)志位來確定,哪些信息得標(biāo)志位1凯砍,就包含哪些信息箱硕。排列順序分別是PTS DTS ESCR ES速率 DSM特技方式 附件的復(fù)制信息 前PES的CRC PES 擴(kuò)展,如果還有多余的字節(jié)沒用悟衩,就用填充字節(jié)0xFF填充剧罩。本例子中,PES頭數(shù)據(jù)只包含PTS數(shù)據(jù)座泳。
8.pts
21 00 01 96 07:5個(gè)字節(jié)總共40位
9.幀數(shù)據(jù)
從96 07后面的FF FD 85 00 33 22...
這些都是MP3格式數(shù)據(jù)惠昔。
10.上面解析完第1個(gè)PES包后,又找到47開頭的第二個(gè)PES包挑势,如上圖镇防。對(duì)ts header47 08 14 11
解析
- pid = 0x814
- payload_unit_start_indicator = 0 表示不是幀頭,不含PES包頭數(shù)據(jù)潮饱,只有PES負(fù)載(PES負(fù)載就是一幀數(shù)據(jù))
- adaptation_field_control=01来氧,no adaptation_field,payload only
- continuity_counter=0001
11.幀數(shù)據(jù)
第二個(gè)PES包,去除包頭后饼齿,68 4D 8C...
全是MP3格式數(shù)據(jù)饲漾。
三、解析流程
1.TS.prototype.resync
不復(fù)制源碼了缕溉,大概是遍歷this.bits考传,以0x47為開頭,去找到5個(gè)連續(xù)的包证鸥。
2.TS.prototype.parsePacket
這個(gè)方法上面已經(jīng)解析了一部分僚楞,再過一下。先調(diào)用resync去同步枉层,然后讀取Ts header.判斷出有payload后泉褐,通過nextBytesAreStartCode去找PES開始碼0x000001,找到了就把這幾個(gè)數(shù)跳過去this.bits.skip(24)
鸟蜡。
下面的數(shù)據(jù)就是PES包解析了膜赃,參考本文第二部分的格式說明。
3.計(jì)算PTS
在jsmpeg系列二 TS碼流 PAT PMT有提到PTS的概念揉忘,但是沒有寫具體算法跳座。ts.js中這段代碼是有點(diǎn)奇怪的:
// The Presentation Timestamp is encoded as 33(!) bit
// integer, but has a "marker bit" inserted at weird places
// in between, making the whole thing 5 bytes in size.
// You can't make this shit up...
this.bits.skip(4);
var p32_30 = this.bits.read(3);
this.bits.skip(1);
var p29_15 = this.bits.read(15);
this.bits.skip(1);
var p14_0 = this.bits.read(15);
this.bits.skip(1);
// Can't use bit shifts here; we need 33 bits of precision,
// so we're using JavaScript's double number type. Also
// divide by the 90khz clock to get the pts in seconds.
pts = (p32_30 * 1073741824 + p29_15 * 32768 + p14_0)/90000;
這里搜索到向高手請(qǐng)教MPEG2碼流(TS流)系列問題一:PTS怎么用,原文如下
摘錄一段《13818-1》 P65頁里面泣矛,對(duì)PTS疲眷、DTS都有的情形:
if (PTS_DTS_flags ==‘11’ ) {
'0011' 4 bslbf
PTS [32..30] 3 bslbf
marker_bit 1 bslbf
PTS [29..15] 15 bslbf
marker_bit 1 bslbf
PTS [14..0] 15 bslbf
marker_bit 1 bslbf
'0001' 4 bslbf
DTS [32..30] 3 bslbf
marker_bit 1 bslbf
DTS [29..15] 15 bslbf
marker_bit 1 bslbf
DTS [14..0] 15 bslbf
marker_bit 1 bslbf
}
從上面摘錄可見,PTS和DTS的格式相同您朽,都是由一個(gè)3 bits和兩個(gè)15 bits組成狂丝,之間用兩個(gè)1 bit的“marker_bit”分開。剛好經(jīng)過一下午暴搜,再加上MPEG-2 TS packet analyser軟件的幫助几颜,我也大概了解到倍试,鬧了半天PTS/DTS就是一個(gè)33 bits的整形數(shù),那中間的“marker_bit”木有用菠剩,是用來跳過的易猫。
4.detect if the PES packet is complete 這一段參考注釋,沒細(xì)看
if (streamId) {
// Attempt to detect if the PES packet is complete. For Audio (and
// other) packets, we received a total packet length with the PES
// header, so we can check the current length.
// For Video packets, we have to guess the end by detecting if this
// TS packet was padded - there's no good reason to pad a TS packet
// in between, but it might just fit exactly. If this fails, we can
// only wait for the next PES header for that stream.
var pi = this.pesPacketInfo[streamId];
if (pi) {
var start = this.bits.index >> 3;
var complete = this.packetAddData(pi, start, end);
var hasPadding = !payloadStart && (adaptationField & 0x2);
if (complete || (this.guessVideoFrameEnd && hasPadding)) {
this.packetComplete(pi);
}
}
}
5.packetComplete
TS.prototype.packetComplete = function(pi) {
pi.destination.write(pi.pts, pi.buffers);
pi.totalLength = 0;
pi.currentLength = 0;
pi.buffers = [];
};
通過pi.destination把解析好的數(shù)據(jù)傳遞出去具壮。
四准颓、ts.js對(duì)外部提供的調(diào)用
1.write
在player.js中
var Player = function(url, options) {
this.options = options || {};
if (options.source) {
this.source = new options.source(url, options);
options.streaming = !!this.source.streaming;
}
else if (url.match(/^wss?:\/\//)) {
this.source = new JSMpeg.Source.WebSocket(url, options);
options.streaming = true;
}
else if (options.progressive !== false) {
this.source = new JSMpeg.Source.AjaxProgressive(url, options);
options.streaming = false;
}
else {
this.source = new JSMpeg.Source.Ajax(url, options);
options.streaming = false;
}
this.maxAudioLag = options.maxAudioLag || 0.25;
this.loop = options.loop !== false;
this.autoplay = !!options.autoplay || options.streaming;
this.demuxer = new JSMpeg.Demuxer.TS(options);
this.source.connect(this.demuxer);
...
這里指定了幾種不同的source,當(dāng)然也可以在options中自定義棺妓。牽涉到三個(gè)類:
- websocket.js
- ajax-progressive.js
- ajax.js
progressive -
whether to load data in chunks (static files only).When enabled, playback can begin before the whole source has been completely loaded. Default true.
這三個(gè)類都提供了一致的接口攘已,提供給player.js調(diào)用。比如上面的this.source.connect(this.demuxer);
怜跑,還有后面的this.source.start();
ajax的兩個(gè)方式都是以XMLHttpRequest下載數(shù)據(jù)样勃,這里細(xì)節(jié)不上源碼了,僅以websocket.js為例:
WSSource.prototype.connect = function(destination) {
this.destination = destination;
};
WSSource.prototype.onMessage = function(ev) {
if (this.destination) {
this.destination.write(ev.data);
}
};
可以看到connect方法把JSMpeg.Demuxer.TS給傳入到不同的source里性芬。最終在收到二進(jìn)制數(shù)據(jù)后峡眶,轉(zhuǎn)交給JSMpeg.Demuxer.TS的write方法去處理。
2.connect
在player.js中
if (options.video !== false) {
this.video = new JSMpeg.Decoder.MPEG1Video(options);
this.renderer = !options.disableGl && JSMpeg.Renderer.WebGL.IsSupported()
? new JSMpeg.Renderer.WebGL(options)
: new JSMpeg.Renderer.Canvas2D(options);
this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.VIDEO_1, this.video);
this.video.connect(this.renderer);
}
if (options.audio !== false && JSMpeg.AudioOutput.WebAudio.IsSupported()) {
this.audio = new JSMpeg.Decoder.MP2Audio(options);
this.audioOut = new JSMpeg.AudioOutput.WebAudio(options);
this.demuxer.connect(JSMpeg.Demuxer.TS.STREAM.AUDIO_1, this.audio);
this.audio.connect(this.audioOut);
}
可以看出植锉,demuxer把視頻和音頻的解碼器給連接起來辫樱。
TS.prototype.connect = function(streamId, destination) {
this.pesPacketInfo[streamId] = {
destination: destination,
currentLength: 0,
totalLength: 0,
pts: 0,
buffers: []
};
};
3.write
TS.prototype.write = function(buffer) {
if (this.leftoverBytes) {
var totalLength = buffer.byteLength + this.leftoverBytes.byteLength;
this.bits = new JSMpeg.BitBuffer(totalLength);
this.bits.write([this.leftoverBytes, buffer]);
}
else {
this.bits = new JSMpeg.BitBuffer(buffer);
}
while (this.bits.has(188 << 3) && this.parsePacket()) {}
var leftoverCount = this.bits.byteLength - (this.bits.index >> 3);
this.leftoverBytes = leftoverCount > 0
? this.bits.bytes.subarray(this.bits.index >> 3)
: null;
};
上述代碼188<<3相當(dāng)于188*8,即判斷只要還有完整的TS包俊庇,就一直parsePacket狮暑。如果沒有完整包,則把剩余的數(shù)據(jù)放入leftoverBytes辉饱,在下次執(zhí)行write寫入時(shí)搬男,把數(shù)據(jù)拼起來繼續(xù)解析。
4.總結(jié)
全文至此彭沼,已經(jīng)看到parsePacket最終用傳入的destination繼續(xù)解析了缔逛。destination一個(gè)是JSMpeg.Decoder.MPEG1Video,另一個(gè)是JSMpeg.Decoder.MP2Audio姓惑,后續(xù)系列文章將會(huì)看一下這兩個(gè)類译株。
五、TS 流解碼過程概述
參考
TS流解碼分析之I,P,B幀以及PTS,DTS
TS文件解析流程
打包TS流
將H264與AAC打包Ipad可播放的TS流的總結(jié)
- 獲取TS中的PAT挺益,從PAT表里面找到所有的PMT表的map_id。
- 注意1:PAT表并不一定在文件的起始位置乘寒,TS流這種對(duì)于電視直播的Live流需要保證在任何時(shí)間打開電視你都能看到畫面望众,所以PAT表是被隨機(jī)插到TS流的Packet中的,比如間隔10幀插一個(gè)PAT表和PMT表。所以TS流文件的第一個(gè)TS Packet可能是一個(gè)PES包烂翰,但是這個(gè)PES包更可能是續(xù)包夯缺,它沒有解碼器需要的Header,所以這種包可以在播放中被忽略甘耿,因?yàn)樗赡苁卿浿魄耙粠腎踊兜、P、B包的一個(gè)斷包佳恬,根本解碼不出數(shù)據(jù)捏境;
- 注意2:記得檢測(cè)PAT中的current_next_indicator這個(gè)flag,如果這個(gè)flag被置1毁葱,則忽略本次讀到的這個(gè)PAT包垫言,繼續(xù)往下搜索PAT包;
- 注意3:如果PAT包因?yàn)槿菁{的PMT的map_id很多倾剿,一個(gè)TS Packet的188個(gè)字節(jié)或許放不完筷频,則last_section_number不是0了,你得根據(jù)當(dāng)前的section_number(第一個(gè)是0)前痘,然后不斷的搜索下去凛捏,把TS Packet去掉頭后的數(shù)據(jù)組合成一個(gè)完整的PAT表;
- 獲取TS中的PMT芹缔,建立流id表坯癣。
在通過PAT表找到所有的PMT表的id后,則需要開始繼續(xù)跑文件乖菱,查找PMT表了坡锡,一般情況下,PMT表在TS文件中的位置跟在PAT表的后面窒所,但是也有不同鹉勒,所以我推薦在查找PAT表完成后,把指針Seek到文件的0位置吵取,從頭開始查找PMT表禽额。這樣可能能更快的找到PMT表也說不定,當(dāng)然你用當(dāng)前的位置繼續(xù)向下找PMT表也是沒問題的皮官。
- 注意1:PMT表也有跟PAT表一樣的分段特性脯倒,一樣檢查last_section_number這個(gè)是不是有情況。也有current_next_indicator的特性捺氢,都得檢查藻丢;
- 注意2:當(dāng)PAT表里提供了多張PMT表的id后,則表明文件是一個(gè)多視頻摄乒、多音頻流混合的文件悠反;
- 根據(jù)PMT可以知道當(dāng)前網(wǎng)絡(luò)中傳輸?shù)囊曨l(音頻)類型(H264)残黑,相應(yīng)的PID,PCR的PID等信息斋否。
在搜索完所有PMT表后梨水,保存其中的流類型和流id,此時(shí)我們有一張表茵臭,表里保存了所有的視頻流id和音頻流id疫诽,下面我們把文件指針Seek到0,我們開始一點(diǎn)點(diǎn)的查找TS Packet旦委。在這之前有一些需要注意的地方:
- 確定你要播放的視頻和音頻流:因?yàn)槲募锌赡苡卸鄠€(gè)視頻奇徒、音頻流,并且這些流的編碼也不同社证,比如日本的電視在播放時(shí)會(huì)用1080i的MPEG2和240P+360P的H264同時(shí)傳輸逼龟,這樣錄制下來的TS流則會(huì)有3個(gè)視頻流(id),并且音頻也是傳輸3條追葡,也就是有6條流腺律,但是我們?cè)赑C或者碟機(jī)中播放的時(shí)候,一般都是播放一條視頻和一條音頻宜肉,則我們必需根據(jù)用戶選擇播放那條視頻和音頻(如果你希望讓用戶選擇的話)匀钧,比如我們希望播放MPEG2的視頻,所以在不斷的跑讀TS Packet的過程中谬返,我們要忽略掉除了MPEG2流的視頻id之斯,那些全部Skip即可,音頻同理遣铝。
- 如何查找一個(gè)音頻\視頻幀的頭佑刷,以及它的長度:這個(gè)問題也比較簡單,在跑TS Packet的過程中酿炸,找到PES包瘫絮,如果TS頭表明payload_unit_start_indicator為1,則這個(gè)PES包此流id的某一幀起始包填硕,去掉PES頭后的ES流就是編碼后的流的起始數(shù)據(jù)麦萤。而后面的針對(duì)這條流的PES包,只要沒有payload_unit_start_indicator標(biāo)志扁眯,都是這個(gè)包的續(xù)包壮莹,這些續(xù)包把頭去掉后,跟上一個(gè)包的數(shù)據(jù)組合起來姻檀,就一個(gè)編碼后的ES數(shù)據(jù)命满。
這里有一個(gè)需要注意的,在找到一個(gè)包表明它是payload_unit_start_indicator后绣版,往下查找可能會(huì)查找到其他流id的payload_unit_start_indicator的PES包胶台。狭莱。。要分別組合概作。
- 設(shè)置demux 模塊的視頻Filter 為相應(yīng)視頻的PID和stream type等。
- 從視頻Demux Filter 后得到的TS數(shù)據(jù)包中的payload 數(shù)據(jù)就是 one piece of PES,在TS header中有一些關(guān)于此 payload屬于哪個(gè) PES的 第多少個(gè)數(shù)據(jù)包默怨。
- 拼接好的PES包的包頭會(huì)有 PTS讯榕,DTS信息,去掉PES的header就是 ES匙睹。PTS,DTS信息在 pes頭部,當(dāng)PTS_DTS_flag = ‘10’時(shí)愚屁,有PTS,當(dāng)是‘11’時(shí)痕檬,PTS,DTS都有霎槐。
- 直接將ES包送給decoder就可以進(jìn)行解碼。解碼出來的數(shù)據(jù)就是一幀一幀的視頻數(shù)據(jù)梦谜,這些數(shù)據(jù)至少應(yīng)當(dāng)與PES中的PTS關(guān)聯(lián)一下丘跌,以便進(jìn)行視音頻同步。
- I唁桩,B闭树,P 幀就在ES中,通過picture_header()的picture_start_code來辨別是哪個(gè)幀荒澡。