MPEG2-TS流深入解析

TS 文件为传输流文件,可以将其理解为一种单一码流、混合码流:

单一码流:TS流的基本组成单位,是长度为188字节的TS包;

混合码流:TS流有多种数据组成,一个TS包中的数据可以是视频、音频、填充数据、PSI/SI表格数据等;

TS(Transport Stream)流,也叫传输流,是一个又一个Packet形成,每一个Packet即为一个TS包,由4字节的Header和184字节的Data组成。 

TS包大小固定为 188 字节,必须以0x47开头,内部有3个部分:TS HeaderAdaptation FieldPayload,其中TS固定头(packet header)4个字节,Adaptation Field 可能存在也可能不存在,长度不固定。

其中,TS包中 Payload 所传输的信息包括两种类型:

  • 视频、音频、辅助数据的PES包;
  • 节目专用信息PSI

TS包也可以是空包,空包(0xFF)用来填充TS流,可能在重新进行多路复用时被插入或删除。视频、音频的ES流需进行打包形成视频、音频的 PES流。辅助数据(如图文电视信息)不需要打成PES包。

TS Header

固定占用 4 个字节。其中最重要的是:同步头(8bit)、PID(13bit)、counter计数器(4bit)。解析TS流要先找到PAT表,只要找到PAT就可以找到PMT,然后就可以找到音视频流了。

PAT表和PMT表需要定期插入TS流,因为用户随时可能加入TS流,这个间隔很小,通常每隔几个视频帧就要加入PAT和PMT。其中,PAT和PMT是必须的,还可以加入其他表如SDT(业务描述表)等,不过,HLS流只要PAT和PMT就可以了。

Structure

PID

PID信息非常关键,它直接表示了本次TS包的用途,目前根据PID,主要用以分辨对应的TS包类型,包括:PAT、CAT、NIT、PMT、以及媒体与数据等类型。

其中,比较重要的是PATPMT,如果PID为0x0000,那么此TS的Payload为PAT;如果为表中未列出的值,那么为分配给此TS包的一个唯一标识,用来区分TS包属于哪个节目,比如某视频流,或者某音频流,一个节目包含多个TS包。

很明显,PID就是用来做索引的,通过该值判断包类型,以及媒体包属于哪一个流。

Adaptation Field Control

这里说的很清楚,当该值为00时无视,为01时表示只有载荷数据(中间通过一个0x00分割),为10时只有Adaptation Field,当为11时Adaptation Field后紧跟着Payload(中间没有0x00分割)

Visual Diagram

Demuxer

#define MTS_PACKET_SIZE 188
#define MTS_TS_HEADER_SIZE 4
#define MTS_SYNC_BYTE 0x47

struct mts_packet_header {
    // --> fixed: 32bit
    uint32_t sync_byte : 8;                     // 同步字节固定为0x47
    uint32_t transport_error_indicator : 1;     // 传输错误标志位,一般传输错误的话就不会处理这个包了
    uint32_t payload_unit_start_indicator : 1;  // 有效负载的开始标志,一个完整的数据包开始时标记为1,包括PES和PSI
    uint32_t transport_priority : 1;            // 传输优先级位,1表示高优先级
    uint32_t PID : 13;                          // 有效负载数据的类型
    uint32_t transport_scrambling_control : 2;  // 加密标志位,0x00表示未加密
    uint32_t adaptation_field_control : 2;      // 调整字段控制,0x00不处理,0x01,0x02,0x03处理
    uint32_t continuity_counter : 4;            // 计数器,范围0-15
    // --> unfixed: ~
    struct mts_adaptation_field adaptation;  // 扩展区
};

static inline uint32_t mts_dec_packet_header(const uint8_t* data,
                                             size_t bytes,
                                             struct mts_packet_header* pkhd) {
    if (MTS_PACKET_SIZE != bytes)  // 包必须188字节
        return 0;
    if (pkhd == nullptr)
        return 0;
    if (MTS_SYNC_BYTE != data[0])  // 包第一字节必须是0x47
        return 0;
    /**
     * Fixed Packet Header
     * ISO_IEC_13818 Table 2-2
     */
    uint32_t len = 0;
    memset(pkhd, 0, sizeof(mts_packet_header));
    pkhd->sync_byte = MTS_SYNC_BYTE;                             // 8b
    pkhd->transport_error_indicator = (data[1] >> 7) & 0x01;     // 1b
    pkhd->payload_unit_start_indicator = (data[1] >> 6) & 0x01;  // 1b
    pkhd->transport_priority = (data[1] >> 5) & 0x01;            // 1b
    pkhd->PID = ((data[1] << 8) | data[2]) & 0x1FFF;             // 13b
    pkhd->transport_scrambling_control = (data[3] >> 6) & 0x03;  // 2b
    pkhd->adaptation_field_control = (data[3] >> 4) & 0x03;      // 2b
    pkhd->continuity_counter = data[3] & 0x0F;                   // 4b
    len += MTS_TS_HEADER_SIZE;

    // Adaptation Field Control
    if (0x02 == pkhd->adaptation_field_control || 
        0x03 == pkhd->adaptation_field_control) {
        mts_dec_adaptation_filed(data + 4, bytes - 4, &pkhd->adaptation);
        len += 1 + pkhd->adaptation.transport_private_data_length;
    }
    // --> PID
    return len;
}

Adaptation Field

紧跟着4个字节的固定头之后,是一个可能存在又可能不存在的扩展头,这个扩展头是否存在由固定头里的adaptation_field_control决定,如下图所示:

这个扩展头的长度由该区域第一个字节(8bit)决定,如果adaptation_field_length==0,那么整个扩展头的长度也就是1个字节,否则,整个扩展头的长度等于(8+length)bit。

打包 TS 流时 PATPMT 表是没有adaptation field的,只有当Payload是PES时,才会有Adaption Field,不够的长度直接补 0xff 即可。视频流和音频流都需要加 adaptation field,通常加在一个帧的第一个 TS 包和最后一个 TS 包里,中间的 TS 包不加。

Structure

PCR

其中PCR很重要,PCR是节目时钟参考,PCR、DTS、PTS 都是对同一个系统时钟的采样值,PCR 是递增的,因此可以将其设置为 DTS 值,音频数据不需要 PCR。如果没有PCR字段,iPad 是可以播放的,但 VLC 无法播放。

PCR分两部分编码:一个以系统时钟频率的 1/300 为单位,称为PCR_base共33bit;另一个以系统时钟频率为单位,称为PCR_ext,共9bit,整个PCR共42bit。 

PCR基数的时钟和PCR扩展的时钟是不同的,需要进行调整计算;

可以以此控制传输速率:

Demuxer

struct mts_adaptation_field {
    // --> fixed: 8bit
    uint32_t adaptation_field_length : 8;
    // --> unfixed: adaptation_field_length
    uint32_t discontinuity_indicator : 1;
    uint32_t random_access_indicator : 1;
    uint32_t elementary_stream_priority_indicator : 1;
    uint32_t PCR_flag : 1;
    uint32_t OPCR_flag : 1;
    uint32_t splicing_point_flag : 1;
    uint32_t transport_private_data_flag : 1;
    uint32_t adaptation_field_extension_flag : 1;
    uint64_t program_clock_reference_base : 33;  // if (PCR_flag==1)
    uint32_t program_clock_reference_extension : 9;
    uint64_t original_program_clock_reference_base : 33;  // if (OPCR_flag==1)
    uint32_t original_program_clock_reference_extension : 9;
    uint32_t splice_countdown : 8;                   // if (splicing_point_flag==1)
    uint32_t transport_private_data_length : 8;      // if (transport_private_data_flag==1)
    int8_t transport_private_data[64];               // 自规约不超过64字节
    uint32_t adaptation_field_extension_length : 8;  // if (adaptation_field_extension_flag==1)
    uint32_t ltw_flag : 1;
    uint32_t piecewise_rate_flag : 1;
    uint32_t seamless_splice_flag : 1;
    uint32_t ltw_valid_flag : 1;  // if (ltw_flag==1)
    uint32_t ltw_offset : 15;
    uint32_t piecewise_rate : 22;  // if (piecewise_rate_flag==1)
    uint32_t splice_type : 4;      // if (seamless_splice_flag==1)
    int64_t DTS_next_AU : 36;
};

static inline uint32_t mts_dec_adaptation_filed(const uint8_t* data,  // 于adaptation_field起始
                                                size_t bytes,         // 剥离packet_header后的长度
                                                struct mts_adaptation_field* adp) {
    if (bytes > MTS_PACKET_SIZE)
        return -1;
    if (adp == nullptr)
        return -1;
    uint32_t i = 0, j = 0;
    /**
     * Adaptation Field Control
     * ISO_IEC_13818 Table 2-6
     */
    adp->adaptation_field_length = data[i++];  // 8b
    // field
    if (adp->adaptation_field_length > 0) {
        adp->discontinuity_indicator = (data[i] >> 7) & 0x01;               // 1b
        adp->random_access_indicator = (data[i] >> 6) & 0x01;               // 1b
        adp->elementary_stream_priority_indicator = (data[i] >> 5) & 0x01;  // 1b
        adp->PCR_flag = (data[i] >> 4) & 0x01;                              // 1b
        adp->OPCR_flag = (data[i] >> 3) & 0x01;                             // 1b
        adp->splicing_point_flag = (data[i] >> 2) & 0x01;                   // 1b
        adp->transport_private_data_flag = (data[i] >> 1) & 0x01;           // 1b
        adp->adaptation_field_extension_flag = (data[i] >> 0) & 0x01;       // 1b
        i++;
        // PCR
        if (adp->PCR_flag) {
            adp->program_clock_reference_base =
                ((uint64_t)data[i] << 25) | ((uint64_t)data[i + 1] << 17) | ((uint64_t)data[i + 2] << 9) |
                ((uint64_t)data[i + 3] << 1) | ((data[i + 4] >> 7) & 0x01);  // 33b
            // --> reserved: 6b
            adp->program_clock_reference_extension = ((data[i + 4] & 0x01) << 8) | data[i + 5];  // 9b
            i += 6;
        }
        // OPCR
        if (adp->OPCR_flag) {
            adp->original_program_clock_reference_base =
                (((uint64_t)data[i]) << 25) | ((uint64_t)data[i + 1] << 17) | ((uint64_t)data[i + 2] << 9) |
                ((uint64_t)data[i + 3] << 1) | ((data[i + 4] >> 7) & 0x01);  // 33b
            // --> reserved: 6b
            adp->original_program_clock_reference_extension = ((data[i + 4] & 0x01) << 1) | data[i + 5];  // 9b
            i += 6;
        }
        if (adp->splicing_point_flag) {
            adp->splice_countdown = data[i++];  // 8b
        }
        if (adp->transport_private_data_flag) {
            adp->transport_private_data_length = data[i++];  // 8b
            if (adp->transport_private_data_length > 0) {
                memcpy(&adp->transport_private_data, &data[i], adp->transport_private_data_length);
                i += adp->transport_private_data_length;
            }
        } else {
            adp->transport_private_data_length = 0;
        }
        if (adp->adaptation_field_extension_flag) {
            uint8_t reserved;
            adp->adaptation_field_extension_length = data[i++];  // 8b
            adp->ltw_flag = (data[i] >> 7) & 0x01;               // 1b
            adp->piecewise_rate_flag = (data[i] >> 6) & 0x01;    // 1b
            adp->seamless_splice_flag = (data[i] >> 5) & 0x01;   // 1b
            reserved = data[i] & 0x1F;                           // 5b
            i++;
            if (adp->ltw_flag) {
                adp->ltw_valid_flag = (data[i] >> 7) & 0x01;              // 1b
                adp->ltw_offset = ((data[i] & 0x7F) << 8) | data[i + 1];  // 15b
                i += 2;
            }
            if (adp->piecewise_rate_flag) {
                // --> reserved: 2b
                adp->piecewise_rate = ((data[i] & 0x3F) << 16) | (data[i + 1] << 8) | data[i + 2];  // 22b
                i += 3;
            }
            if (adp->seamless_splice_flag) {
                adp->splice_type = (data[i] >> 4) & 0x0F;  // 4b
                adp->DTS_next_AU = (((data[i] >> 1) & 0x07) << 30) | (data[i + 1] << 22) |
                                   (((data[i + 2] >> 1) & 0x7F) << 15) | (data[i + 3] << 7) |
                                   ((data[i + 4] >> 1) & 0x7F);  // 36b
                i += 5;
            }
            // reserved byte
        }
        // stuffing byte
    }
    return 0;
}

PAT

节目关联表(Program Association Table, PAT),得出所有节目的PID。N loop为一个数组,是节目列表及其对应的PID,每个节目就是指一段视频,具有唯一ID。

节目关联表每个 TS 流对应一张,用来描述该 TS 流中有多少个节目。TS流中,PAT包会重复实现,大约 0.5 秒出现一个,保证实时解码性。

PAT组成包括:ts_packet_header(4) + payload_unit_start_indicator(1) + PAT

例如:

 

Structure

Visual Diagram

Demuxer

struct mts_pmt_index {
    // --> fixed: 32bit
    uint32_t program_number : 16;   // 节目号
    uint32_t reserved : 3;          // 保留
    uint32_t program_map_PID : 13;  // 节目映射表的PID,节目号大于0时对应的PID,每个节目对应一个
    // --> unfixed: ~
    struct mts_pmt PMT;  // 扩展连接PMT表信息,如果table_id!=0x02则无效
};

struct mts_pat {
    // --> fixed: 64bit
    uint32_t table_id : 8;                  // 固定, 0x00, 标志是该表是PAT表
    uint32_t section_syntax_indicator : 1;  // 段语法标志位,固定为1
    uint32_t zero : 1;                      // '0'
    uint32_t reserved : 2;                  // 保留
    uint32_t section_length : 12;           // 表示从下一个字段开始到CRC32(含)之间有用的字节数
    uint32_t transport_stream_id : 16;      // 该传输流的ID,区别于一个网络中其它多路复用的流
    uint32_t reserved2 : 2;                 // 保留
    uint32_t version_number : 5;            // 范围0-31,表示PAT的版本号
    uint32_t current_next_indicator : 1;    // 发送的PAT是当前有效还是下一个PAT有效
    uint32_t sector_number : 8;             // 分段的号码,PAT可能分为多段传输,第一段为00,以后每个分段加1,最多可能有256个分段
    uint32_t last_sector_number : 8;        // 最后一个分段的号码
    // --> unfixed: ~
    std::vector<mts_pmt_index> pmts;  // PMT列表
    uint32_t network_PID;             // if (program_number == '0')
    // --> fixed: 32bit
    int64_t CRC : 32;  // CRC32校验码
};

static inline uint32_t mts_dec_pat(struct mts_pat* pat,
                                   const uint8_t* data,
                                   size_t bytes) {
    if (pat == nullptr)
        return -1;
    uint32_t i = 0;
    /**
     * Program association Table
     * Table 2-25 Program association section
     */
    memset(pat, 0, sizeof(mts_pat));
    pat->table_id = data[0];
    pat->section_syntax_indicator = (data[1] >> 7) & 0x01;
    pat->zero = (data[1] >> 6) & 0x01;
    pat->reserved = (data[1] >> 4) & 0x03;
    pat->section_length = ((data[1] & 0x0F) << 8) | data[2];  // 从下一个字段开始到CRC32(含)之间有用的字节数
    pat->transport_stream_id = (data[3] << 8) | data[4];
    pat->reserved2 = (data[5] >> 6) & 0x03;
    pat->version_number = (data[5] >> 1) & 0x1F;
    pat->current_next_indicator = data[5] & 0x01;
    pat->sector_number = data[6];
    pat->last_sector_number = data[7];
    // --> 64bit
    //    printf("PAT: %0x %0x %0x %0x %0x %0x %0x %0x\n",
    //           (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3],
    //           (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6], (unsigned int)data[7]);

    if (PAT_TID_PAS != pat->table_id)  // Table 2-26
        return -1;
    if (1 != pat->section_syntax_indicator)  // 固定是1
        return -1;
    if (bytes < pat->section_length + 3)  // 基础长度
        return -1;

    /**
     * for (i = 0; i < N; i++) {}
     */
    for (i = 8 /*loop start*/; i < pat->section_length - 4 /*CRC32*/; i += 4 /*loop size*/) {
        unsigned int program_number = (data[i] << 8) | data[i + 1];
        unsigned int reserved_3 = data[i + 2] >> 5;
        if (program_number == 0x00) {
            pat->network_PID = (data[i + 2] & 0x1F) << 8 | data[i + 3];
        } else {
            struct mts_pmt_index pmt {};
            memset(&pmt, 0, sizeof(mts_pmt_index));
            pmt.program_map_PID = (data[i + 2] & 0x1F) << 8 | data[i + 3];
            pmt.reserved = 0;
            pmt.program_number = program_number;
            pat->pmts.push_back(pmt);
        }
    }
    // CRC
    uint32_t offset = pat->section_length + 3;
    pat->CRC = (data[offset - 4] & 0x000000FF) << 24 |
               (data[offset - 3] & 0x000000FF) << 16 |
               (data[offset - 2] & 0x000000FF) << 8 |
               (data[offset - 1] & 0x000000FF);
    return 0;
}

PMT

节目映射表(PMT)的意义在于,它给出了节目号与组成这个节目元素之间的映射;也就是说,PMT是连接节目号与节目元素的桥梁。

我们知道,一个电视节目至少包含了视频和音频数据,而每一个节目的视音频数据都是以包的形式在TS流中传输的,所以说,一个TS流包含了多个节目的视频和音频数据包。

节目映射表的 PID 是由 PAT 表提供给出的。表征一路节目所有流信息,包含:VideoPID、AudioPID、DataPID。

PMT组成包括:ts_packet_header(4) + payload_unit_start_indicator(1) + PMT

例如:

 

Structure

Visual Diagram

Demuxer

struct mts_pmt {
    // --> fixed: 96bit
    uint32_t table_id : 8;                  // 固定, 0x02, 表示PMT表
    uint32_t section_syntax_indicator : 1;  // 固定, 0x01
    uint32_t zero : 1;                      // 0x01
    uint32_t reserved : 2;                  // 保留, 0x03
    uint32_t section_length : 12;           // 首先两位bit置为00,它指示段的byte数,由段长度域开始,包含CRC
    uint32_t program_number : 16;           // 指出该节目对应于可应用的 Program map PID
    uint32_t reserved2 : 2;                 // 保留, 0x03
    uint32_t version_number : 5;            // 指出TS流中 Program map section 的版本号
    uint32_t current_next_indicator : 1;    // 当该位置1时,当前传送的 Program map section 可用
    uint32_t sector_number : 8;             // 固定, 0x00
    uint32_t last_sector_number : 8;        // 固定, 0x00
    uint32_t reserved3 : 3;                 // 保留, 0x07
    uint32_t PCR_PID : 13;                  // 指明TS包的PID值,该TS包含有PCR域
    uint32_t reserved4 : 4;                 // 保留, 0x0F
    uint32_t program_info_length : 12;      // 前两位bit为00。该域指出跟随其后对节目信息的描述的byte数
    // --> unfixed: ~
    uint32_t program_info;              // 节目信息
    std::vector<mts_es_stream> stream;  // ES列表, 一个节目有很多流
    // --> fixed: 32bit
    int64_t CRC : 32;  // CRC32校验码
};

struct mts_es_stream {
    // --> fixed: 40bit
    uint32_t stream_type : 8;      // 指示特定PID的节目元素包的类型。该处PID由elementary PID指定
    uint32_t reserved : 3;         // 保留, 0x07
    uint32_t elementary_PID : 13;  // 该域指示TS包的PID值。这些TS包含有相关的节目元素
    uint32_t reserved2 : 4;        // 保留, 0x0F
    uint32_t ES_info_length : 12;  // 前两位bit为00。该域指示跟随其后的描述相关节目元素的byte数
    // --> unfixed: ~
    uint32_t ES_info;
    // --> unfixed: ~
    struct mts_pes PES;
};

static inline uint32_t mts_dec_pmt(struct mts_pmt* pmt,
                                   const uint8_t* data,
                                   size_t bytes) {
    uint32_t i = 0;
    /**
     * Program map Table
     * Table 2-28 Transport Stream program map section
     */
    memset(pmt, 0, sizeof(mts_pmt));
    pmt->table_id = data[0];
    pmt->section_syntax_indicator = (data[1] >> 7) & 0x01;
    pmt->zero = (data[1] >> 6) & 0x01;
    pmt->reserved = (data[1] >> 4) & 0x03;
    pmt->section_length = ((data[1] & 0x0F) << 8) | data[2];
    pmt->program_number = (data[3] << 8) | data[4];
    pmt->reserved2 = (data[5] >> 6) & 0x03;
    pmt->version_number = (data[5] >> 1) & 0x1F;
    pmt->current_next_indicator = data[5] & 0x01;
    pmt->sector_number = data[6];
    pmt->last_sector_number = data[7];
    pmt->reserved3 = (data[8] >> 5) & 0x07;
    pmt->PCR_PID = ((data[8] & 0x1F) << 8) | data[9];
    pmt->reserved4 = (data[10] >> 4) & 0x0F;
    pmt->program_info_length = ((data[10] & 0x0F) << 8) | data[11];
    // printf("PMT: %0x %0x %0x %0x %0x %0x %0x %0x, %0x, %0x, %0x, %0x\n", (unsigned int)data[0], (unsigned int)data[1], (unsigned int)data[2], (unsigned int)data[3], (unsigned int)data[4], (unsigned int)data[5], (unsigned int)data[6],(unsigned int)data[7],(unsigned int)data[8],(unsigned int)data[9],(unsigned int)data[10],(unsigned int)data[11]);

    if (PAT_TID_PMS != pmt->table_id)  // Table 2-26
        return -1;
    if (1 != pmt->section_syntax_indicator)  // 固定是1
        return -1;
    if (0 != pmt->sector_number)  // 固定0x00
        return -1;
    if (0 != pmt->last_sector_number)  // 固定0x00
        return -1;
    if (bytes < pmt->section_length + 3)  // 基础长度
        return -1;

    /**
     * for (i = 0; i < N1; i++) {}
     */
    uint16_t last_info_length = 0;  // 每一次循环体长度都是变量
    for (i = 12 + pmt->program_info_length /*loop start*/; i < pmt->section_length - 4 /*CRC32*/; i += 5 + last_info_length /*loop size*/) {
        uint16_t stream_type = data[i];
        uint16_t elementary_PID = ((data[i + 1] & 0x1F) << 8) | data[i + 2];
        uint16_t ES_info_length = ((data[i + 3] & 0x0F) << 8) | data[i + 4];
        //  printf("PMT: pn: %0x, pid: %0x, codec: %0x, es_len: %d\n",
        //        (unsigned int)pmt->program_number, (unsigned int)pid, (unsigned int)data[i], (unsigned int)len);
        struct mts_es_stream stream {};
        memset(&stream, 0, sizeof(mts_es_stream));
        stream.stream_type = stream_type;
        stream.reserved = 0;
        stream.elementary_PID = elementary_PID;
        stream.reserved2 = 0;
        stream.ES_info_length = ES_info_length;
        pmt->stream.push_back(stream);
    }
    // CRC
    uint32_t offset = pmt->section_length + 3;
    pmt->CRC = (data[offset - 4] & 0x000000FF) << 24 |
               (data[offset - 3] & 0x000000FF) << 16 |
               (data[offset - 2] & 0x000000FF) << 8 |
               (data[offset - 1] & 0x000000FF);
    return 0;
}

PES

PES层(ts层中payload)是在每一个视频/音频帧上加入了时间戳等信息,pes包内容很多,我们只留下最常用的。

pts是显示时间戳、dts是解码时间戳,视频数据两种时间戳都需要,音频数据只需要pts,所以pts和dts相同。

有pts和dts两种时间戳是B帧引起的,I帧和P帧的pts等于dts。如果一个视频没有B帧,则pts永远和dts相同。从文件中顺序读取视频帧,取出的帧顺序和dts顺序相同。dts算法比较简单,初始值 + 增量即可,pts计算比较复杂,需要在dts的基础上加偏移量。

音频的pes中只有pts(同dts),视频的I、P帧两种时间戳都要有,视频B帧只要pts(同dts)。打包pts和dts就需要知道视频帧类型,但是通过容器格式我们是无法判断帧类型的,必须解析h.264内容才可以获取帧类型。

PMT组成包括:ts_packet_header(4) + adaptation_field_length(1) + adaptation_field(adaptation_field_length) + PMT

例如:

 

 

Structure

Visual Diagram

Demuxer

struct mts_pes {
    // --> fixed: 48bit
    uint32_t packet_start_code_prefix : 24;  // 起始码:0000 0000 0000 0000 0000 0001
    uint32_t stream_id : 8;                  // 指示基本流的类型和编号
    uint32_t PES_packet_length : 16;         // 指示PES包中跟随该字段最后字节的字节数
    // --> fixed: 24bit
    uint32_t reserved : 2;                   // '10'
    uint32_t PES_scrambling_control : 2;     // 有效载荷的加扰方式
    uint32_t PES_priority : 1;               // 有效载荷的优先级
    uint32_t data_alignment_indicator : 1;   // 关键帧
    uint32_t copyright : 1;                  // 有效载荷的素材依靠版权所保护
    uint32_t original_or_copy : 1;           // 1:有效载荷的内容是原始的, 0:有效载荷的内容是复制的
    uint32_t PTS_DTS_flags : 2;              // 10:PTS存在, 11:PTS和DTS均存在, 00:无PTS也无DTS, 01:禁用
    uint32_t ESCR_flag : 1;                  // 1:ESCR_base,ESCR_extend均存在, 0:不存在
    uint32_t ES_rate_flag : 1;               // 1:ES_rate存在, 0:不存在
    uint32_t DSM_trick_mode_flag : 1;        // 1:DSM_trick_mode_存在, 0:不存在
    uint32_t additional_copy_info_flag : 1;  // 1:additional_copy_info存在, 0:不存在
    uint32_t PES_CRC_flag : 1;               // 1:PES_CRC存在, 0:不存在
    uint32_t PES_extension_flag : 1;         // 1:PES_extension存在, 0:不存在
    uint32_t PES_header_data_length : 8;     // 1:PES_header_data存在, 0:不存在
    // --> unfixed: ~
    uint64_t DTS : 33;        // if (PTS_DTS_flags == '11')
    uint64_t PTS : 33;        // if (PTS_DTS_flags == '10' || PTS_DTS_flags == '11')
    uint64_t ESCR_base : 33;  // if (ESCR_flag == '1')
    uint32_t ESCR_extension : 9;
    uint32_t ES_rate : 22;  // if (ES_rate_flag == '1')
};

static inline uint32_t mts_dec_pes(struct mts_pes* pes,
                                   const uint8_t* data,
                                   size_t bytes) {
    uint32_t i = 0;
    if (0x00 != data[0] || 0x00 != data[1] || 0x01 != data[2]) return 0;
    if (0x02 != ((data[6] >> 6) & 0x03)) return 0;
    /**
     * PES Table
     *Table 2-17 PES packet
     */
    memset(pes, 0, sizeof(mts_pes));
    pes->packet_start_code_prefix = 0x000001;
    pes->stream_id = data[3];
    pes->PES_packet_length = (data[4] << 8) | data[5];
    pes->reserved = (data[6] >> 6) & 0x03;
    pes->PES_scrambling_control = (data[6] >> 4) & 0x03;
    pes->PES_priority = (data[6] >> 3) & 0x01;
    pes->data_alignment_indicator = (data[6] >> 2) & 0x01;
    pes->copyright = (data[6] >> 1) & 0x01;
    pes->original_or_copy = data[6] & 0x01;
    pes->PTS_DTS_flags = (data[7] >> 6) & 0x03;
    pes->ESCR_flag = (data[7] >> 5) & 0x01;
    pes->ES_rate_flag = (data[7] >> 4) & 0x01;
    pes->DSM_trick_mode_flag = (data[7] >> 3) & 0x1;
    pes->additional_copy_info_flag = (data[7] >> 2) & 0x1;
    pes->PES_CRC_flag = (data[7] >> 1) & 0x1;
    pes->PES_extension_flag = data[7] & 0x1;
    pes->PES_header_data_length = data[8];
    i += 9;

    // PTS_DTS
    if (0x02 & pes->PTS_DTS_flags) {
        pes->PTS = ((((uint64_t)data[i] >> 1) & 0x07) << 30) |
                   ((uint64_t)data[i + 1] << 22) |
                   ((((uint64_t)data[i + 2] >> 1) & 0x7F) << 15) |
                   ((uint64_t)data[i + 3] << 7) |
                   ((data[i + 4] >> 1) & 0x7F);
        i += 5;
    }
    if (0x01 & pes->PTS_DTS_flags) {
        pes->DTS = ((((uint64_t)data[i] >> 1) & 0x07) << 30) |
                   ((uint64_t)data[i + 1] << 22) |
                   ((((uint64_t)data[i + 2] >> 1) & 0x7F) << 15) |
                   ((uint64_t)data[i + 3] << 7) |
                   ((data[i + 4] >> 1) & 0x7F);
        i += 5;
    } else if (0x02 & pes->PTS_DTS_flags) {
        pes->DTS = pes->PTS;
    }
    // ESCR
    if (pes->ESCR_flag) {
        pes->ESCR_base = ((((uint64_t)data[i] >> 3) & 0x07) << 30) |
                         (((uint64_t)data[i] & 0x03) << 28) |
                         ((uint64_t)data[i + 1] << 20) |
                         ((((uint64_t)data[i + 2] >> 3) & 0x1F) << 15) |
                         (((uint64_t)data[i + 2] & 0x3) << 13) |
                         ((uint64_t)data[i + 3] << 5) |
                         ((data[i + 4] >> 3) & 0x1F);
        pes->ESCR_extension = ((data[i + 4] & 0x03) << 7) |
                              ((data[i + 5] >> 1) & 0x7F);
        i += 6;
    }
    // ES_rate
    if (pes->ES_rate_flag) {
        pes->ES_rate = ((data[i] & 0x7F) << 15) |
                       (data[i + 1] << 7) |
                       ((data[i + 2] >> 1) & 0x7F);
        i += 3;
    }
    // DSM_trick_mode
    if (pes->DSM_trick_mode_flag) {
        i += 1;
        return 0;
        // 事实上不止于1, 如果真遇到该标志再重新实现
    }
    // additional_copy_info
    if (pes->additional_copy_info_flag) {
        i += 1;
    }
    // PES_CRC
    if (pes->PES_CRC_flag) {
        i += 2;
    }
    // PES_extension
    if (pes->PES_extension_flag) {
        i += 1;
        return 0;
        // 事实上不止于1, 如果真遇到该标志再重新实现
    }
    if (pes->PES_packet_length > 0) {
        if (pes->PES_packet_length < pes->PES_header_data_length + 3)
            return 0;
        pes->PES_packet_length -= pes->PES_header_data_length + 3;
    }
    if (pes->PES_packet_length < 0)
        return 0;
    return pes->PES_header_data_length + 9;
}

点播视频dts算法

dts = 初始值 + 90000 / video_frame_rate,初始值可以随便指定,但是最好不要取0,video_frame_rate就是帧率,比如23、30。

pts和dts是以timescale为单位的,1s = 90000 time scale , 一帧就应该是90000/video_frame_rate 个timescale。

用一帧的timescale除以采样频率就可以转换为一帧的播放时长。

点播音频dts算法

dts = 初始值 + (90000 * audio_samples_per_frame) / audio_sample_rate,audio_samples_per_frame这个值与编解码相关,aac取值1024,mp3取值1158,audio_sample_rate是采样率,比如24000、41000。AAC一帧解码出来是每声道1024个sample,也就是说一帧的时长为1024/sample_rate秒。所以每一帧时间戳依次0,1024/sample_rate,...,1024*n/sample_rate秒。

直播视频的dts和pts应该直接用直播数据流中的时间,不应该按公式计算。

ES

ES层指的就是音视频数据。一般的,视频为H.264视频,音频为AAC音频。

H.264视频的ES层

打包h.264数据我们必须给视频数据加上一个nalu(Network Abstraction Layer unit),nalu包括start code和nalu header,start code固定为0x00000001(帧开始)或0x000001(帧中)。h.264的数据是由slice组成的,slice的内容包括:视频、sps、pps等。nalu type决定了后面的h.264数据内容。

F

1B

forbidden_zero_bit,h.264规定必须取0

NRI

2B

nal_ref_idc,取值0~3,指示这个nalu的重要性,I帧、sps、pps通常取3,P帧通常取2,B帧通常取0

Type

5B

NALU类型

AAC音频的ES层

打包aac音频必须加上一个adts(Audio Data Transport Stream)头,共7Byte,adts包括fixed_header和variable_header两部分,各28bit。

syncword

12b

固定为0xfff

id

1b

0表示MPEG-4,1表示MPEG-2

layer

2b

固定为00

protection_absent

1b

固定为1

profile

2b

取值0~3,1表示aac

sampling_frequency_index

4b

表示采样率,0: 96000 Hz,1: 88200 Hz,2: 64000 Hz,3:48000 Hz,4: 44100 Hz,5: 32000 Hz,6: 24000 Hz,7: 22050 Hz,8: 16000 Hz,9: 12000 Hz,10: 11025 Hz,11: 8000 Hz,12: 7350 Hz

private_bit

1b

固定为0

channel_configuration

3b

取值0~7,1: 1 channel: front-center,2: 2 channels: front-left, front-right,3: 3 channels: front-center, front-left, front-right,4: 4 channels: front-center, front-left, front-right, back-center

original_copy

1b

固定为0

home

1b

固定为0

附录

#define MTS_PACKET_SIZE 188   // TS包长
#define MTS_TS_HEADER_SIZE 4  
#define MTS_PES_HEADER_LEN 6  

#define H264_NAL_IDR 5
#define H264_NAL_AUD 9

#define MTS_SYNC_BYTE 0x47  // TS起始

enum E_TS_MEDIA_TYPE {
    TS_UNKNOWN = 0x00,
    TS_AUDIO = 0x01,
    TS_VIDEO = 0x02,
};

enum E_TS_PID {
    TS_PID_PAT = 0x00,
    TS_PID_CAT = 0x01,
    TS_PID_SDT = 0x11,
};

enum E_PAT_TID {
    PAT_TID_PAS = 0x00,
    PAT_TID_CAS = 0x01,
    PAT_TID_PMS = 0x02,
    PAT_TID_SDS = 0x03,
    PAT_TID_SDT = 0x42,
};

enum E_PSI_STREAM_TYPE {
    STREAM_VIDEO_MPEG4 = 0x10,
    STREAM_VIDEO_H264 = 0x1b,
    STREAM_VIDEO_H265 = 0x24,
    STREAM_VIDEO_SVAC = 0x80,
    STREAM_AUDIO_MP3 = 0x04,
    STREAM_AUDIO_AAC = 0x0f,
    STREAM_AUDIO_G711A = 0x90,
    STREAM_AUDIO_G711U = 0x91,
    STREAM_AUDIO_G729 = 0x99,
    STREAM_AUDIO_OPUS = 0x9C,
};

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
mpeg2-tsMPEG-2 Transport Stream)是一种用于数字广播和视频传输的协议。它的目标是将音频、视频和其他数据以数据包的形式进行传输和存储。 mpeg2-ts的在线解析涉及将以mpeg2-ts格式编码的媒体文件进行解析和分析,以便播放或进行其他操作。解析过程包括以下几个步骤: 1. 读取文件头信息:首先读取文件的头部信息,其中包括文件的标识符、版本号、编码信息等。通过分析头部信息,可以确定文件的类型和结构。 2. 解析PAT表(Program Association Table):PAT表记录了各个节目的信息和各个PID(Packet ID)的映射关系。通过解析PAT表,可以获取到TS中的所有节目的PID。 3. 解析PMT表(Program Map Table):PMT表描述了各个节目中包含音频、视频和其他数据的PID,并提供了的详细信息。通过解析PMT表,可以获取到各个节目中各个的PID以及编码格式等相关信息。 4. 解析数据包:根据PID的信息,解析相应的数据包,提取出音频、视频和其他数据。对于音视频数据,还需要进行解码和解封装操作,以便进行播放或进一步处理。 5. 分析和处理数据:分析音视频和其他数据的格式、编码方式以及其他相关信息,以便进行后续处理,如解码、转码、封装等。 通过对mpeg2-ts文件进行在线解析,可以实现对媒体文件的播放、编辑、转码等功能。同时,也可以提取音视频数据进行分析和处理,以满足不同的应用需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毕加索解锁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值