MPEG-PS封裝格式


據傳輸媒體的質量不同,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[4]; 
    unsigned char system_clock_reference_base21 : 2;
    unsigned char marker_bit : 1;
    unsigned char system_clock_reference_base1 : 3;
    unsigned char fix_bit : 2;   
    unsigned char system_clock_reference_base22;
    unsigned char system_clock_reference_base31 : 2;
    unsigned char marker_bit1 : 1;
    unsigned char system_clock_reference_base23 : 5;
    unsigned char system_clock_reference_base32;
    unsigned char system_clock_reference_extension1 : 2;
    unsigned char marker_bit2 : 1;
    unsigned char system_clock_reference_base33 : 5;
    unsigned char marker_bit3 : 1;
    unsigned char system_clock_reference_extension2 : 7;
    unsigned char program_mux_rate1;
    unsigned char program_mux_rate2;
    unsigned char marker_bit5 : 1;
    unsigned char marker_bit4 : 1;
    unsigned char program_mux_rate3 : 6;
    unsigned char pack_stuffing_length : 3;
    unsigned char reserved : 5;

    pack_header()
    {
        pack_start_code[0] = 0x00;
        pack_start_code[1] = 0x00;
        pack_start_code[2] = 0x01;
        pack_start_code[3] = 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 = 0;
        system_clock_reference_extension2 = 0;
    }

    void getSystem_clock_reference_base(UINT64 &_ui64SCR)
    {
        _ui64SCR = (system_clock_reference_base1 << 30) | (system_clock_reference_base21 << 28)
            | (system_clock_reference_base22 << 20) | (system_clock_reference_base23 << 15)
            | (system_clock_reference_base31 << 13) | (system_clock_reference_base32 << 5)
            | (system_clock_reference_base33);
    }

    void setSystem_clock_reference_base(UINT64 _ui64SCR)
    {
        system_clock_reference_base1 = (_ui64SCR >> 30) & 0x07;
        system_clock_reference_base21 = (_ui64SCR >> 28) & 0x03;
        system_clock_reference_base22 = (_ui64SCR >> 20) & 0xFF;
        system_clock_reference_base23 = (_ui64SCR >> 15) & 0x1F;
        system_clock_reference_base31 = (_ui64SCR >> 13) & 0x03;
        system_clock_reference_base32 = (_ui64SCR >> 5) & 0xFF;
        system_clock_reference_base33 = _ui64SCR & 0x1F;
    }

    void getProgram_mux_rate(unsigned int &_uiMux_rate)
    {
        _uiMux_rate = (program_mux_rate1 << 14) | (program_mux_rate2 << 6) | program_mux_rate3;
    }

    void setProgram_mux_rate(unsigned int _uiMux_rate)
    {
        program_mux_rate1 = (_uiMux_rate >> 14) & 0xFF;
        program_mux_rate2 = (_uiMux_rate >> 6) & 0xFF;
        program_mux_rate3 = _uiMux_rate & 0x3F;
    }
};

這樣的好處是可以直接通過

pack_header header;
header.setProgram_mux_rate(25200);
header.setSystem_clock_reference_base(0);
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 = 32 //pack_start_code
        + 2 // '01'
        + 3 //system_clock_reference_base [32..30]
        + 1 //marker_bit
        + 15 //system_clock_reference_base [29..15]
        + 1 //marker_bit
        + 15 //system_clock_reference_base [14..0]
        + 1 //marker_bit
        + 9 //system_clock_reference_extension
        + 1 //marker_bit
        + 22 // program_mux_rate
        + 1 //marker_bit
        + 1 //marker_bit
        + 5 //reserved
        + 3; //pack_stuffing_length

    if (stuffingLength > 0)
    {
        for (int i = 0; i < stuffingLength; i++)
        {
            calcBinaryBitLen += 8;
        }
    }

    if ((calcBinaryBitLen / 8) > binaryLen)
    {
        if (binary)
            delete[] binary;

        binary = new BYTE[calcBinaryBitLen / 8];
    }

    binaryLen = calcBinaryBitLen / 8;

    BYTE* p = binary;
    bits_buffer_t bw;

    bits_initwrite(&bw, binaryLen, p);
    bits_write(&bw, 32, PACK_HEADER_START_CODE); //pack_start_code
    bits_write(&bw, 2, 0x1); // '01'
    bits_write(&bw, 3, (SCRBase >> 30) & 0x07); //system_clock_reference_base [32..30]
    bits_write(&bw, 1, 1); //marker_bit
    bits_write(&bw, 15, (SCRBase >> 15) & 0x7FFF);  //system_clock_reference_base [29..15]
    bits_write(&bw, 1, 1);  //marker_bit
    bits_write(&bw, 15, SCRBase & 0x7FFF); //system_clock_reference_base [14..0]
    bits_write(&bw, 1, 1); //marker_bit
    bits_write(&bw, 9, SCRExt); //system_clock_reference_extension
    bits_write(&bw, 1, 1); //marker_bit
    bits_write(&bw, 22, programMuxRate & 0x3FFFFF);  // program_mux_rate
    bits_write(&bw, 1, 1); //marker_bit
    bits_write(&bw, 1, 1); //marker_bit
    bits_write(&bw, 5, 0x1F); //reserved
    bits_write(&bw, 3, stuffingLength & 0x07);  //pack_stuffing_length

    if (stuffingLength > 0)
    {
        for (int i = 0; i < stuffingLength; i++)
        {
            bits_write(&bw, 8, 0xFF); //stuffing
        }
    }
    return 1;
}

對於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要加上填充的字節數)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM