jsmpeg系列四 源碼ts.js TS格式解析流程

一犬缨、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
image.png
image.png

結(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個(gè)PES包

1.ts header47 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ù)惠昔。

第2個(gè)PES包

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é)

  1. 獲取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表;
  1. 獲取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è)多視頻摄乒、多音頻流混合的文件悠反;
  1. 根據(jù)PMT可以知道當(dāng)前網(wǎng)絡(luò)中傳輸?shù)囊曨l(音頻)類型(H264)残黑,相應(yīng)的PID,PCR的PID等信息斋否。
image.png

在搜索完所有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包胶台。狭莱。。要分別組合概作。
  1. 設(shè)置demux 模塊的視頻Filter 為相應(yīng)視頻的PID和stream type等。
  2. 從視頻Demux Filter 后得到的TS數(shù)據(jù)包中的payload 數(shù)據(jù)就是 one piece of PES,在TS header中有一些關(guān)于此 payload屬于哪個(gè) PES的 第多少個(gè)數(shù)據(jù)包默怨。
  3. 拼接好的PES包的包頭會(huì)有 PTS讯榕,DTS信息,去掉PES的header就是 ES匙睹。PTS,DTS信息在 pes頭部,當(dāng)PTS_DTS_flag = ‘10’時(shí)愚屁,有PTS,當(dāng)是‘11’時(shí)痕檬,PTS,DTS都有霎槐。
  4. 直接將ES包送給decoder就可以進(jìn)行解碼。解碼出來的數(shù)據(jù)就是一幀一幀的視頻數(shù)據(jù)梦谜,這些數(shù)據(jù)至少應(yīng)當(dāng)與PES中的PTS關(guān)聯(lián)一下丘跌,以便進(jìn)行視音頻同步。
  5. I唁桩,B闭树,P 幀就在ES中,通過picture_header()的picture_start_code來辨別是哪個(gè)幀荒澡。
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末报辱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子单山,更是在濱河造成了極大的恐慌碍现,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米奸,死亡現(xiàn)場(chǎng)離奇詭異昼接,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躏升,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門辩棒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人膨疏,你說我怎么就攤上這事一睁。” “怎么了佃却?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵者吁,是天一觀的道長。 經(jīng)常有香客問我饲帅,道長复凳,這世上最難降的妖魔是什么瘤泪? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮育八,結(jié)果婚禮上对途,老公的妹妹穿的比我還像新娘。我一直安慰自己髓棋,他們只是感情好实檀,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著按声,像睡著了一般膳犹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上签则,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天须床,我揣著相機(jī)與錄音,去河邊找鬼渐裂。 笑死豺旬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芯义。 我是一名探鬼主播哈垢,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扛拨!你這毒婦竟也來了耘分?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤绑警,失蹤者是張志新(化名)和其女友劉穎求泰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體计盒,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渴频,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了北启。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卜朗。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咕村,靈堂內(nèi)的尸體忽然破棺而出场钉,到底是詐尸還是另有隱情,我是刑警寧澤懈涛,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布逛万,位于F島的核電站,受9級(jí)特大地震影響批钠,放射性物質(zhì)發(fā)生泄漏宇植。R本人自食惡果不足惜得封,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望指郁。 院中可真熱鬧忙上,春花似錦、人聲如沸闲坎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箫柳。三九已至,卻和暖如春啥供,著一層夾襖步出監(jiān)牢的瞬間悯恍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國打工伙狐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涮毫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓贷屎,卻偏偏與公主長得像罢防,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子唉侄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 參考【PSI/SI學(xué)習(xí)系列】第一章:預(yù)備知識(shí)【PSI/SI學(xué)習(xí)系列】第二章:從TS到PAT和PMT 最近開始學(xué)習(xí)數(shù)...
    合肥黑閱讀 6,543評(píng)論 0 6
  • 引言 標(biāo)準(zhǔn)文檔給自己的定義是:論述了將音頻和視頻的一個(gè)或多個(gè)基本流以及其他數(shù)據(jù)組合成為一個(gè)單獨(dú)的流或多個(gè)流咒吐,以適于...
    easyhao閱讀 3,789評(píng)論 0 5
  • 做這個(gè)東西很久了,從去年十二月份開始的属划,快5個(gè)月了恬叹。。同眯。期間因?yàn)楣ぷ饕恢睌鄶嗬m(xù)續(xù)绽昼,直到最近才有了些進(jìn)展,也就到此為...
    0_0啊閱讀 12,415評(píng)論 1 13
  • 1. 簡介 在本篇文章中, 我會(huì)詳細(xì)的記錄我學(xué)習(xí)MPEG2TSExtractor的全部過程. 與其說是一篇技術(shù)博客...
    TankWitch閱讀 3,717評(píng)論 1 5
  • ''瘦比思念'',現(xiàn)在是離開我們微微的海濱活動(dòng)的第二天明肮,對(duì)海邊的思念菱农,就已經(jīng)開始在我們心中浮現(xiàn)。思念似乎沒法衡量晤愧,...
    不椒_c閱讀 253評(píng)論 0 0