• SRS之TS封装PAT和PMT


    1. SrsTsContext::encode_pat_pmt

    在该函数中,将 PAT 和 PMT 封装到 TS Packet 中,并将这两个 TS packet 写入到 ts 文件中。

    /* the mpegts header specifed the video/audio pid. */
    #define TS_PMT_NUMBER 1
    #define TS_PMT_PID 0x1001
    
    /* Transport Stream packets are 188 bytes in length */
    #define SRS_TS_PACKET_SIZE          188
    
    
    int SrsTsContext::encode_pat_pmt(SrsFileWriter* writer, int16_t vpid, 
        SrsTsStream vs, int16_t apid, SrsTsStream as)
    {
        int ret = ERROR_SUCCESS;
        
        if (vs != SrsTsStreamVideoH264 && as != SrsTsStreamAudioAAC && as 
            != SrsTsStreamAudioMp3) {
            ret = ERROR_HLS_NO_STREAM;
            srs_error("hls: no pmt pcr pid, vs=%d, as=%d. ret=%d", vs, as, ret);
            return ret;
        }
        
        int16_t pmt_number = TS_PMT_NUMBER;
        int16_t pmt_pid = TS_PMT_PID;
        if (true) {
            /* 生成一个包含 PAT 数据的 TS packet */
            SrsTsPacket* pkt = SrsTsPacket::create_pat(this, pmt_number, pmt_pid);
            SrsAutoFree(SrsTsPacket, pkt);
            
            /* 一个 TS 包固定为 188 字节 */
            char* buf = new char[SRS_TS_PACKET_SIZE];
            SrsAutoFreeA(char, buf);
            
            /* 若不足 188 字节,则余下空间填 0xFF */
            /* set the left bytes with 0xFF */
            int nb_buf = pkt->size();
            srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
            memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
            
            SrsStream stream;
            if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
                return ret;
            }
            /* 将 TS packet 中的数据写入到 buf 中 */
            if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
                srs_error("ts encode ts packet failed. ret=%d", ret);
                return ret;
            }
            /* 将临时缓存 buf 中的 TS packet 数据写入到 .ts 文件中,这里该文件假设为
             * ./objs/nginx/html/live/livestream-0.ts.tmp
             * 这里调用的函数为  */
            if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) 
                != ERROR_SUCCESS) {
                srs_error("ts write ts packet failed. ret=%d", ret);
                return ret;
            }
        }
        if (true) {
            /* 生成一个包含 PMT 的 TS Packet,并初始化好所有的数据 */
            SrsTsPacket* pkt = SrsTsPacket::create_pmt(this, pmt_number, pmt_pid, 
                               vpid, vs, apid, as);
            SrsAutoFree(SrsTsPacket, pkt);
    
            /* 创建一个临时缓存 */
            char* buf = new char[SRS_TS_PACKET_SIZE];
            SrsAutoFreeA(char, buf);
    
            /* 若该 TS Packet 的头部加上 payload 的总字节数不足 188 字节,
             * 则余下填充 0xFF */
            /* set the left bytes with 0xFF. */
            int nb_buf = pkt->size();
            srs_assert(nb_buf < SRS_TS_PACKET_SIZE);
            memset(buf + nb_buf, 0xFF, SRS_TS_PACKET_SIZE - nb_buf);
    
            SrsStream stream;
            if ((ret = stream.initialize(buf, nb_buf)) != ERROR_SUCCESS) {
                return ret;
            }
            /* 将 TS Packet 中的数据写入到 stream 中 */
            if ((ret = pkt->encode(&stream)) != ERROR_SUCCESS) {
                srs_error("ts encode ts packet failed. ret=%d", ret);
                return ret;
            }
            /* 将数据写入到 ts 文件中 */
            if ((ret = writer->write(buf, SRS_TS_PACKET_SIZE, NULL)) 
                != ERROR_SUCCESS) {
                srs_error("ts write ts packet failed. ret=%d", ret);
                return ret;
            }
        }
        
        /* When PAT and PMT are writen, the context is ready now. */
        ready = true;
        
        return ret;
    }
    

    该函数中,首先调用 SrsTsPacket::create_pat 函数生成一个 pmt 包。

    1.1 SrsTsPacket::create_pat

    SrsTsPacket* SrsTsPacket::create_pat(SrsTsContext* context, 
        int16_t pmt_number, int16_t pmt_pid)
    {
        SrsTsPacket* pkt = new SrsTsPacket(context);
        
        /* TS 层由三部分组成:ts header, adaptation_field, payload(即 pes 数据) */
        
        /* 
         * 第一部分. TS Header: (4Bytes)
         * sync_byte(1B): 
         *     The sync_byte is a fixed 8-bit field whose value
         *     is '0100 0111' (0x47). Sync_byte emulation in the choice of
         *     values for other regularly occurring fields, such as PID, 
         *     should be avoided.同步字段应该避免和其他字段竞争,如 PID
         *
         * transport_error_indicator(1bit): 
         *     The transport_error_indicator is a 1-bit flag. When set to '1' 
         *     it indicators that at least 1 uncorrectable bit error exists 
         *     in the associated Transport Stream packet. This bit may be set
         *     to '1' by entities external to the transport layer. When set to
         *     '1' this bit shall not be reset to '0' unless the bit value(s) 
         *     in error have been corrected.
         *
         * payload_unit_start_indicator(1bit):
         *     The payload_unit_start_indicator is a 1-bit flag which has normative meaning 
         *     for Transport Stream packets that carray PES packets (refer to 2.4.3.6) or
         *     PSI data (refer to 2.4.4).
         *     
         *     When the payload of the Transport Stream packet contains PES packet data,
         *     the payload_unit_start_indicator has the following significance: a '1' 
         *     indicates that the payload of this Transport Stream packet will commence(start)
         *     with the first byte of a PES packet and a '0' indicates no PES packet shall 
         *     start in this Transport Stream packet. If the payload_unit_start_indicator is
         *     set to '1', then one and only one PES packet starts in this Transport Stream
         *     packet. This also applies to private streams of stream_type 6 (refer to 
         *     Table 2-29).
         *
         *     When the payload of the Transport Stream packet contains PSI data, the 
         *     payload_unit_start_indicator has the follwing significance: if the Transport
         *     Stream packet carries the first byte of a PSI section, the 
         *     payload_unit_start_indicator value shall be '1', indicating that the first
         *     byte of the payload of this Transport Stream packet carries the pointer_field.
         *     If the Transport Stream packet does not carry the first byte of a PSI section,
         *     the payload_unit_start_indicator value shall be '0', indicating that there is 
         *     no pointer_field in the payload. Refer to 2.4.4.1 and 2.4.4.2. This also 
         *     applies to private streams of stream_type 5 (refer to Table 2-29).
         *
         *     For null packets the payload_unit_start_indicator shall be set to '0'.
         *
         *     The meaning of this bit for Transport Stream packets carrying only private data 
         *     is not defined in this Specification.
         *     
         * transport_priority(1bit):
         *     The transport_priority is a 1-bit indicator. When set to '1' it indicates that
         *     the associated packet is of greater priority than other packets having the same
         *     PID which do not have the bit set to '1'. The transport mechanism can use this 
         *     to prioritize its data within an elementary stream. Depending on the appliction
         *     the transport_priority field may be coded regardless of the PID or wihtin one 
         *     PID only. This field may be changed by channel specific encoders or decoders.
         *
         * PID(13bits):
         *     The PID is a 13-bit field, indicating the type of the data stored in the packet
         *     payload. PID value 0x0000 is reserved for the Program Association Table (see 
         *     Table 2-25). PID value 0x0001 is reserved for the Conditional Access Table (
         *     see Table 2-27). PID values 0x0002 ~ 0x000F are reserved. PID value 0x1FFF is 
         *     reserved for null packets (see Table 2-3).
         *
         * transport_scrambling_control(2bits):
         *     This 2-bit field indicates the scrambling mode of the Transport Stream packet
         *     payload. The Transport Stream packet header, and the adaptation field when 
         *     present, shall not be scrambled. In the case of a null packet the value of 
         *     the transport_scrambling_control filed shall be set '00' (see Table 2-4).
         * 
         * adaption_field_control(2bits):
         *     This 2-bit field indicates whether this Transport Stream packet header is 
         *     followed by an adaptation field and/or payload (see Table 2-5).
         *  
         *     ITU-T Rec. H.222.0 | ISO/IEC 13818-1 decoders shall discard Transport 
         *     Stream packets with the adaptation_field_control field set to a value of '00'.
         *     In the case of a null packet the value of the adaptation_field_control
         *     shall be set to '01'.
         * 
         * continuity_counter(4bits):
         *     The continuity_counter is a 4-bit field incrementing with each Transport 
         *     Stream packet with the same PID. The continuity_counter wrap around to 0 after
         *     its maximum value. The continuity_counter shall not be incremented when 
         *     the adaptation_filed_control of the packet equal '00'(reserved) or '10'
         *     (adaptation field only).
         *
         *     In Transport Streams, duplicate packets may be sent as two, and only two,
         *     consecutive Transport Stream packets of the same PID. The duplicate packets 
         *     shall have the same continuity_counter value as the original packet and the
         *     adaptation_field_control field shall be equal to '01'(payload only) or 
         *     '11'(both). In duplicate packets each byte of the original packet shall be
         *     duplicated, with the exception that in the program clock reference fields, 
         *     if present, a valid value shall be encoded.
         *
         *     The continuity_counter in a particular Transport Stream packet is continuous 
         *     when it differs by a positive value of one from the continuity_counter value 
         *     in the previous Transport Stream packet of the same PID, or when either of 
         *     the nonincrementing conditions (adaptation_field_control set to '00' or '10',
         *     or duplicate packets as described above) are met. The continuity counter may 
         *     be discontinuous when the discontinuity_indicator is set to '1' (refer to 
         *     2.4.3.4). In the case of a null packet the value of the continuity_counter 
         *     is undefined.
         */
         
        /* 同步字节,固定为 0x47 */
        pkt->sync_byte = 0x47;
        pkt->transport_error_indicator = 0;
        /* 当前携带的是 PAT,因此负载起始标志位置为 1 */
        pkt->payload_unit_start_indicator = 1;
        pkt->transport_priority = 0;
        /* 指示当前负载的数据为 PAT, 0x00 */
        pkt->pid = SrsTsPidPAT;
        /* 不加密 */
        pkt->transport_scrambling_control = SrsTsScrambledDisabled;
        /* No adaptation_field, payload only */
        pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly;
        pkt->continuity_counter = 0;
        
        /*
         * 第二部分:adaptation_field
         * 这里 adaptation_field 为 NULL,因此该段无.
         */
        /* 没有 adaptation_field */
        pkt->adaptation_field = NULL;
        
        /*
         * 第三部分:payload
         * 这里的 payload 即为 PAT 数据
         */
        /* the PAT payload of PSI ts packet. */
        SrsTsPayloadPAT* pat = new SrsTsPayloadPAT(pkt);
        pkt->payload = pat;
        
        /*
         * pointer_field(1B): 
         *     This is an 8-bit field whose value shall be the number of bytes, immediately
         *     following the pointer_filed until the first byte of the first section that
         *     is present in the payload of the Transport Stream packet (so a value of 0x00
         *     in the pointer_field indicates that the section starts immediately after
         *     the pointer_filed). When at least one section begins in a given Transport
         *     Stream packet, then the payload_unit_start_indicator (refer to 2.4.3.2) 
         *     shall be set to 1 and the first Transport Stream packet, then the  
         *     payload_unit_start_indicator shall to set to 0 and no pointer shall be sent
         *     in the payload of that packet.
         *
         * table_id(8bits):
         *     This is an 8-bit field, which shall be set to 0x00 as shown in Table 2-26.
         *
         * section_syntax_indicator(1bit):
         *     The section_syntax_indicator is a 1-bit field which shall be set to '1'.
         *
         * const0_value(1bit):
         *     const value, must be '0'
         *
         * const1_value(2bits):
         *     revered value, must be '11'
         *
         * the specified psi info, for example, PAT fields.
         *
         * section_length(11bits):
         *     This is a 12-bit field, the first two bits of which shall be '00'. The  
         *     remaining 10 bits specify the number of bytes of the section, starting 
         *     immediately following the section_length field, and including the CRC.
         *     The value in this field shall not exceed 1021 (0x3FD).
         * 
         * CRC_32(32bits):
         *    This is a 32-bit field that contains the CRC value that gives a zero output
         *    of the register in the decoder defined in Annex A after processing the entire
         *    section.
         *    @remark, crc32(bytes without pointer field, before crcew field)
         */
        
        pat->pointer_field = 0;
        pat->table_id = SrsTsPsiIdPas;
        pat->section_syntax_indicator = 1;
        pat->section_length = 0; // calc in size.
        pat->transport_stream_id = 1;
        pat->version_number = 0;
        pat->current_next_indicator = 1;
        pat->section_number = 0;
        pat->last_section_number = 0;
        
        /* 这里是 PAT 中包含的节目流信息,可以用多个节目流,每个节目流固定为 4bytes 
         * 这里只生成一个节目,即节目的映射表 PMT */
        /* multiple 4B program data. */
        pat->programs.push_back(new SrsTsPayloadPATProgram(pmt_number, pmt_pid));
        
        /* PAT 的 32bits CRC 校验,在 编码时生成 */
        pat->CRC_32 = 0; // calc in encode.
        return pkt;
    }
    

    该函数首先构造一个 SrsTsPacket 类对象,用于封装一个包含 PAT 数据的 TS packet,然后再对该对象成员进行初始化。

    1.1.1 SrsTsPacket 构造

    /**
    * the packet in ts stream,
    * 2.4.3.2 Transport Stream packet layer, hls-mpeg-ts-iso13818-1.pdf, page 36
    * Transport Stream packets shall be 188 bytes long.
    */
    SrsTsPacket::SrsTsPacket(SrsTsContext* c)
    {
        context = c;
    
        sync_byte = 0;
        transport_error_indicator = 0;
        payload_unit_start_indicator = 0;
        transport_priority = 0;
        pid = SrsTsPidPAT;
        transport_scrambling_control = SrsTsScrambledDisabled;
        adaption_field_control = SrsTsAdaptationFieldTypeReserved;
        continuity_counter = 0;
        adaptation_field = NULL;
        payload = NULL;
    }
    

    在 SrsTsPacket::create_pat 函数中,构造 SrsTsPacket 并对其成员赋完值后,接着构造一个 SrsTsPayloadPAT 类对象,用于存放 PAT 数据。

    1.1.2 SrsTsPayloadPAT 构造

    /*
     * the PAT payload of PSI ts packet.
     * 2.4.4.3 Program association Table, hls->mpeg-ts-iso13818-1.pdf, page 61
     * The Program Association Table provides the correspondence between a 
     * program_number and the PID value of the Transport Stream packets which
     * carry the program definition. The program_number is the numeric label 
     * associated with a program.
     */
    SrsTsPayloadPAT::SrsTsPayloadPAT(SrsTsPacket* p) : SrsTsPayloadPSI(p)
    {
        /* 2bits, reverved value, must be '1' */
        const3_value = 3;
    }
    

    由该代码可知, SrsTsPayloadPAT 类的父类为 SrsTsPayloadPSI,因此会先构造该父类对象。

    1.1.3 SrsTsPayloadPSI 构造

    /**
    * the PSI payload of ts packet.
    * 2.4.4 Program specific information, hls-mpeg-ts-iso13818-1.pdf, page 59
    */
    SrsTsPayloadPSI::SrsTsPayloadPSI(SrsTsPacket* p) : SrsTsPayload(p)
    {
        pointer_field = 0;
        const0_value = 0;
        const1_value = 3;
        CRC_32 = 0;
    }
    

    回到 SrsTsPacket::create_pat 函数中,最后会构造一个 SrsTsPayloadPATProgram 类对象,表示 PAT(节目联动表)中包含的节目信息,可能有多个节目。

    1.1.4 SrsTsPayloadPATProgram 构造

    /**
     * the program of PAT of PSI ts packet.
     */
    SrsTsPayloadPATProgram::SrsTsPayloadPATProgram(int16_t n, int16_t p)
    {
        /*
         * number(16bits):
         *     Program_number is a 16-bit field. It specifies the program to which the 
         *     program_map_PID is applicable. When set to 0x0000, then the following PID
         *     reference shall be the network PID. For all other cases the value of this 
         *     field is user defined. This field shall not take any single value more than
         *     once within one version of the Program Association Table.
         */
        number = n;
        /*
         * pid(13bits):
         *     program_map_PID/network_PID 13bits
         *     network_PID - The network_PID is a 13-bit field, which is used only in 
         *     conjunction with the value of the program_number set to 0x0000, specifies 
         *     the PID of the Transport Stream packets which shall contain the Network
         *     Information Table. The value of the network_PID field is defined by the 
         *     user, but shall only take values as specified in Table 2-3. The presence of 
         *     the network_PID is optional.
         */
        pid = p;
        /*
         * const1_value(3bits):
         *     reverved value, must be '111'
         */
        const1_value = 0x07;
    }
    

    由前面知,这里只构造了一个节目,即 PMT。

    上面几个步骤中,构造好整个包含 PAT 数据的 TS packet 后,SrsTsContext::encode_pat_pmt 函数接着会调用 SrsTsPacket::encode 函数将该 TS packet 写入到一个临时 buf 中。

    1.2 SrsTsPacket::encode

    int SrsTsPacket::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
    
        /* TS Packet 分为三个部分:ts header,adaptation field, payload */
        
        /* 第一部分:ts header: 固定为 4 字节 */
        /* 4B ts packet header. */    
        if (!stream->require(4)) {
            ret = ERROR_STREAM_CASTER_TS_HEADER;
            srs_error("ts: mux header failed. ret=%d", ret);
            return ret;
        }
    
        /* 首先写入标志一个 TS 分组的开始:同步字节 0x47 */
        stream->write_1bytes(sync_byte);
        
        int16_t pidv = pid & 0x1FFF;
        pidv |= (transport_priority << 13) & 0x2000;
        pidv |= (transport_error_indicator << 15) & 0x8000;
        pidv |= (payload_unit_start_indicator << 14) & 0x4000;
        stream->write_2bytes(pidv);
        
        int8_t ccv = continuity_counter & 0x0F;
        ccv |= (transport_scrambling_control << 6) & 0xC0;
        ccv |= (adaption_field_control << 4) & 0x30;
        stream->write_1bytes(ccv);
        
        /* 第二部分: adaptation field
         * 该部分有无根据 ts header 中的 adaption_field_control 字段值控制的,
         * 若为 '10' 或 '11' 都表示有 adaptation field。
         */
        /* optional: adaptation field */
        if (adaptation_field) {
            if ((ret = adaptation_field->encode(stream)) != ERROR_SUCCESS) {
                srs_error("ts: mux af faield. ret=%d", ret);
                return ret;
            }
        }
        
        /* 第三部分:payload
         * 该部分的有无也是根据 ts header 中的 adaptation_field_control 字段值控制的,
         * 若为 '01' 或 '11' 都表示有 payload。
         */
        
        /* optional: payload. */
        if (payload) {
            /* 在编码 PAT 中,该 payload 指向子类对象 SrsTsPayloadPSI,
             * 因此调用该子类对象实现的 encode 函数 */
            if ((ret = payload->encode(stream)) != ERROR_SUCCESS) {
                srs_error("ts: mux payload failed. ret=%d", ret);
                return ret;
            }
        }
        
        return ret;
    }
    

    在该 SrsTsPacket::encode 函数中,首先将 ts header 写入到 stream 中,然后检测若是有 adaptation_field 的话,则将该 adaptation_field 写入到 stream 中。当然,由上面知,PAT 是没有 adaptation_field 的,但是有 payload,因此会调用 SrsTsPayloadPSI::encode 函数将 payload 数据写入到 stream 中。

    1.2.1 SrsTsPayloadPSI::encode

    int SrsTsPayloadPSI::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* 首先根据该字段是否为 1,表明在 PAT 数据前是否存在 pointer_field */
        if (packet->payload_unit_start_indicator) {
            if (!stream->require(1)) {
                ret = ERROR_STREAM_CASTER_TS_PSI;
                srs_error("ts: mux PSI failed. ret=%d", ret);
                return ret;
            }
            stream->write_1bytes(pointer_field);
        }
        
        /* 计算 PAT 开始数据到 CRC32 之间的校验码 */
        /* to calc the crc32 */
        char* ppat = stream->data() + stream->pos();
        int pat_pos = stream->pos();
        
        /* at least 3B for all psi. */
        if (!stream->require(3)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PSI failed. ret=%d", ret);
            return ret;
        }
        /* 1B */
        stream->write_1bytes(table_id);
        
        /* 2B */
        int16_t slv = section_length & 0x0FFF;
        slv |= (section_syntax_indicator << 15) & 0x8000;
        slv |= (const0_value << 14) & 0x4000;
        slv |= (const1_value << 12) & 0x3000;
        stream->write_2bytes(slv);
        
        /* no section, ignore. */
        if (section_length == 0) {
            srs_warn("ts: mux PAT ignore empty section");
            return ret;
        }
        
        if (!stream->require(section_length)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PAT section failed. ret=%d", ret);
            return ret;
        }
        
        /* 这里是对 PAT 的 section 部分进行编码
         * 调用子类 SrsTsPayloadPAT 实现的 psi_encode 函数 */
        /* call the virtual method of actual PSI. */
        if ((ret = psi_encode(stream)) != ERROR_SUCCESS) {
            return ret;
        }
        
        /* 4B */
        if (!stream->require(4)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PSI crc32 failed. ret=%d", ret);
            return ret;
        }
        /* cacl the crc32 of bytes in buf. */
        CRC_32 = srs_crc32(ppat, stream->pos() - pat_pos);
        stream->write_4bytes(CRC_32);
        
        return ret;
        
    }
    

    该函数首先将 TS 的 payload 数据,即 PAT 数据的 section 前的数据写入到 stream 中,然后根据 section_length 的值是否为 0,来检测是否需要写 section 部分的数据。若为 0,则表示没有 section 部分;否则,调用子类 SrsTsPayloadPAT 实现的 psi_encode 函数将 section 写入到 stream 中。

    1.2.2 SrsTsPayloadPAT::psi_encode

    int SrsTsPayloadPAT::psi_encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* at least 5B for PAT specified */
        if (!stream->require(5)) {
            ret = ERROR_STREAM_CASTER_TS_PAT;
            srs_error("ts: mux PAT failed. ret=%d", ret);
            return ret;
        }
        
        /* 2B */
        /* transport_stream_id:
         * 用于在一个网络中从其他的多路复用中识别此传递流,其值用户自定义 */
        stream->write_2bytes(transport_stream_id);
        
        /* 1B */
        /* current_next_indicator
         * 其值为 1 时,当前 PAT 可用。为 0 时,当前 PAT 不可用。
         */
        int8_t cniv = current_next_indicator & 0x01;
        /* version_number: 
         * PAT 版本号,PAT 每改变一次,版本号加1. 当 current_next_indicator 为 1 时,
         * version_number 为当前 PAT 版本号,否则为一下可用 PAT 版本号 */
        cniv |= (version_number << 1) & 0x3E;
        cniv |= (const1_value << 6) & 0xC0;
        stream->write_1bytes(cniv);
        
        /* 1B */
        /* section_number:
         * 当前 PAT 分段号码,PAT 第一个分段号码应为 0 */
        stream->write_1bytes(section_number);
        /* 1B */
        /* last_section_number:
         * PAT 最后一个分段号码 */
        stream->write_1bytes(last_section_number);
        
        /* 下面是 PAT 中包含的节目流 */
        /* multiple 4B program data. */
        for (int i = 0; i < (int)programs.size(); i ++) {
            SrsTsPayloadPATProgram* program = programs.at(i);
            /* 将节目信息编码到 stream 中 */
            if ((ret = program->encode(stream)) != ERROR_SUCCESS) {
                return ret;
            }
            
            /* 当前编码的是 PAT,由前知该 PAT 包含的就一个节目,即 PMT,
             * 因此这里 program->pid 即为表示 PMT 的 PID: 这里为 0x1001 */
            /* update the apply pid table */
            packet->context->set(program->pid, SrsTsPidApplyPMT);
        }
        
        /* 对于 PAT,这里 packet->pid 为 0x0000,即表示为 PAT 信息 */
        /* update the apply pid table */
        packet->context->set(packet->pid, SrsTsPidApplyPAT);
        
        return ret;
    }
    

    在该函数中,会调用 SrsTsPayloadPATProgram::encode 函数将节目信息写入到 stream 中。

    1.2.3 SrsTsPayloadPATProgram::encode

    int SrsTsPayloadPATProgram::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* at least 4B for PAT program specified. */
        if (!stream->require(4)) {
            ret = ERROR_STREAM_CASTER_TS_PAT;
            srs_error("ts: mux PAT failed. ret=%d", ret);
            return ret;
        }
        
        int tmpv = pid & 0x1FFF;
        /* program_number: 
         * 节目号,当其为 0 时,接下来的 PID 将是网络 PID。其余情况为
         * 普通 PMT 的 PID */
        tmpv |= (number << 16) & 0xFFFF0000;
        tmpv |= (const1_value << 13) & 0xE000;
        stream->write_4bytes(tmpv);
        
        return ret;
    }
    

    在 SrsTsPayloadPAT::psi_encode 函数中,每将一个节目信息写入到 stream 中后,就调用 SrsTsContext::set 函数更新 apply pid 表。

    1.2.4 SrsTsContext::set

    /*
     * set the pid apply, the parsed pid.
     */
    void SrsTsContext::set(int pid, SrsTsPidApply apply_pid, SrsTsStream stream)
    {
        SrsTsChannel* channel = NULL;
        
        if (pids.find(pid) == pids.end()) {
            channel = new SrsTsChannel();
            channel->context = this;
            pids[pid] = channel;
        } else {
            channel = pids[pid];
        }
        
        channel->pid = pid;
        /* the actually parsed ts pid */
        channel->apply = apply_pid;
        /* Stream type assignments */
        channel->stream = stream;
    }
    

    在该函数中,当 pids map 容器中没有找到 pid 对应的项时,则新构建一个 SrsTsChannel,并将该新构建的 SrsTsChannel 按 pid 放入到 pids map 容器中。

    1.2.5 SrsTsChannel 构造

    /*
     * the ts channel.
     */
    SrsTsChannel::SrsTsChannel()
    {
        pid = 0;
        apply = SrsTsPidApplyReserved;
        stream = SrsTsStreamReserved;
        msg = NULL;
        /* for encoder */
        continuity_counter = 0;
        context = NULL;
    }
    

    回到 SrsTsPayloadPSI::encode 函数中,将 PAT 中除了 CRC 外所有的数据都写入到 stream 中后,接着调用 srs_crc32 函数计算 crc 的值。

    1.2.6 srs_crc32

    /**
     * cacl the crc32 of bytes in buf.
     */
    u_int32_t srs_crc32(const void* buf, int size)
    {
        /* 该函数 libavformat/mpegtsenc.c */
        return mpegts_crc32((const u_int8_t*)buf, size);
    }
    

    至此,已经将该包含 PAT 的 TS packet 数据都写入到临时缓存中,回到 SrsTsContext::encode_pat_pmt 函数中,接着调用 SrsHlsCacheWriter::write 函数将临时缓存中的数据写入到 ts 文件中。

    1.3 SrsHlsCacheWriter::write

    /**
     * write to file. 
     * @param pnwrite the output nb_write, NULL to ignore.
     */
    int SrsHlsCacheWriter::write(void* buf, size_t count, ssize_t* pnwrite)
    {
        if (shoud_wirte_cache) {
            if (count > 0) {
                data.append((char*)buf, count);
            }
        }
        
        if (shuld_write_file) {
            return impl.write(buf, count, pnwrite);
        }
        
        return ERROR_SUCCESS;
    }
    

    这里接着调用 SrsFileWriter::write 将数据写入到 ts 文件中。

    1.3.1 SrsFileWriter::write

    /**
     * write to file. 
     * @param pnwrite the output nb_write, NULL to ignore.
     */
    int SrsFileWriter::write(void* buf, size_t count, ssize_t* pnwrite)
    {
        int ret = ERROR_SUCCESS;
        
        ssize_t nwrite;
        /* TODO: FIXME: use st_write. */
        if ((nwrite = ::write(fd, buf, count)) < 0) {
            ret = ERROR_SYSTEM_FILE_WRITE;
            srs_error("write to file %s failed. ret=%d", path.c_str(), ret);
            return ret;
        }
        
        if (pnwrite != NULL) {
            *pnwrite = nwrite;
        }
        
        return ret;
    }
    

    下图即为上面写入 ts 文件后的包含 PAT 数据的 TS Packet:

    回到 SrsTsContext::encode_pat_pmt 函数中,在构建好包含 PAT 的 TS packet 并将其写入到 ts 文件后,接着开始构建包含 PMT 的 TS packet,同时也将其写入到同一个 ts 文件中。

    首先,调用 SrsTsPacket::create_pmt 函数生成一个包含 PMT 的 TS Packet。

    1.4 SrsTsPacket::create_pmt

    /*
     * @pmt_number: PMT 节目的节目号,这里传入的为 TS_PMT_NUMBER(1)
     * @pmt_pid: 表示 PMT 的 PID,这里为 TS_PMT_PID(0x1001)
     * @vpid:表示 Video 的 PID,这里为 TS_VIDEO_AVC_PID(0x100)
     * @vs:表示 video 的流类型,为 SrsTsStreamVideoH264 (0x1b)
     * @apid: 表示 audio 的 PID,为 TS_AUDIO_AAC_PID (0x101)
     * @as: 表示 audio 的流类型,为 SrsTsStreamAudioAAC(0x0f)
     */
    SrsTsPacket* SrsTsPacket::create_pmt(SrsTsContext* context, 
        int16_t pmt_number, int16_t pmt_pid, int16_t vpid, 
        SrsTsStream vs, int16_t apid, SrsTsStream as)
    {
        /* 1. TS Packet 之 TS Header */
        SrsTsPacket* pkt = new SrsTsPacket(context);
        /* 同步字节 */
        pkt->sync_byte = 0x47;
        /* 传送错误标识 */
        pkt->transport_error_indicator = 0;
        /* 当 TS packet 包含有 PSI 信息的时候,该位置 1 */
        pkt->payload_unit_start_indicator = 1;
        /* 传送优先级低 */
        pkt->transport_priority = 0;
        /* 指示有效负载数据类型为 PMT,值为 0x1001 */
        pkt->pid = (SrsTsPid)pmt_pid;
        /* 加密控制,这里为禁止,即不加密 */
        pkt->transport_scrambling_control = SrsTsScrambledDisabled;
        /* No adaptation_field, payload only */
        pkt->adaption_field_control = SrsTsAdaptationFieldTypePayloadOnly;
        /* TODO: FIXME: maybe should continuous in channel. */
        /* 连续性结计数器 */
        pkt->continuity_counter = 0;
        
        /* 2. TS Packet 之 adaptation field */
        pkt->adaptation_field = NULL;
        
        /* 3. TS Packet 之 payload(这里负载数据为 PMT) */
        SrsTsPayloadPMT* pmt = new SrsTsPayloadPMT(pkt);
        pkt->payload = pmt;
        
        /* 为 0,表示随后的数据为有效负载 */
        pmt->pointer_field = 0;
        /* 设置 0x02,指示为 PMT */
        pmt->table_id = SrsTsPsiIdPms;
        /* 固定为 1 */
        pmt->section_syntax_indicator = 1;
        /* 高 2 位为 00,表明此字段之后的整个分段的字节数,包含 CRC32 */
        pmt->section_length = 0; // calc in size.
        /* PMT 对应的频道号,这里为 1 */
        pmt->program_number = pmt_number;
        /* PMT 版本号,PMT 每改变一次,版本号加 1。当 current_next_indicator 为 1 时,
         * version_number 为当前 PMT 版本号,否则为下一可用的 PMT 版本号 */
        pmt->version_number = 0;
        /* 其值为 1 时,当前 PMT 可用。为 0 时,当前 PMT 不可用 */
        pmt->current_next_indicator = 1;
        /* 当前 PMT 的分段号码,PMT 第一分段号码应为 0 */
        pmt->section_number = 0;
        /* PMT 最后一个分段号码 */
        pmt->last_section_number = 0;
        /* 节目描述信息,高 2 位应为 0,规定了其随后的 description 字节数,
         * 这里设置为 0,表示没有描述信息 */
        pmt->program_info_length = 0;
        
        /* must got one valid codec. */
        srs_assert(vs == SrsTsStreamVideoH264 || as == SrsTsStreamAudioAAC || 
                   as == SrsTsStreamAudioMp3);
        
        /* PCR_PID:
         * 规定频道中包含 PCR 字段的 TS 包的 PID。PCR 信息既可以单独作为一个 TS 包,
         * 也可以放在视频/音频里。这里是有视频则放在视频中,否则放在音频中.
         */
        
        /* if mp3 or aac specified, use audio to carry pcr. */
        if (as == SrsTsStreamAudioAAC || as == SrsTsStreamAudioMp3) {
            /* use audio to carray pcr by defualt.
             * for hls, there must be at least one audio channel. */
            pmt->PCR_PID = apid;
            pmt->infos.push_back(new SrsTsPayloadPMTESInfo(as, apid));
        }
        
        /* if h.264 specified, use video to carry pcr. */
        if (vs == SrsTsStreamVideoH264) {
            pmt->PCR_PID = vpid;
            pmt->infos.push_back(new SrsTsPayloadPMTESInfo(vs, vpid));
        }
        
        pmt->CRC_32 = 0; // calc in encode.
        return pkt;
    }
    

    该函数中,TS Packet 的 payload 数据(即 PMT)用 SrsTsPayloadPMT 构造。

    1.4.1 SrsTsPayloadPMT 构造

    /*
     * the PMT payload of PSI ts packet.
     * 2.4.4.8 Program Map Table, hls-mpeg-ts-iso13818-1.pdf, page 64
     * The Program Map Table provides the mapping between program numbers 
     * and the program elements that comprise them. A single instance of 
     * such a mapping is referred to as a "program definition". The program 
     * map talbe is the complete collection of all program definitions for a 
     * Transport Stream. This table shall be transmitted in packets, the PID
     * values of which are selected by the encoder. More than one PID value may 
     * be used, if desired. The table is contained in one or more sections with
     * the following syntax. It may be segmented to occupy multiple sections. In
     * each section, the section number field shall be set to zero. Sections are 
     * identified by the program_number field.
     */
    SrsTsPayloadPMT::SrsTsPayloadPMT(SrsTsPacket* p) : SrsTsPayloadPSI(p)
    {
        const1_value0 = 3;
        const1_value1 = 7;
        const1_value2 = 0x0f;
        program_info_length = 0;
        program_info_desc = NULL;
    }
    

    该类的父类为 SrsTsPayloadPSI。

    在 SrsTsPacket::create_pmt 函数中,该 PMT 对应的音视频 PES 信息是通过 SrsTsPayloadPMTESInfo 来构造,然后分别将代表音频和视频的 SrsTsPayloadPMTESInfo 对象放入到 pmt->infos vector 容器中。

    1.4.2 SrsTsPayloadPMTESInfo 构造

    /**
     * the esinfo for PMT program.
     */
    SrsTsPayloadPMTESInfo::SrsTsPayloadPMTESInfo(SrsTsStream st, int16_t epid)
    {
        /* stream_type(8bits):
         * This is an 8-bit field specifying the type of program element carried 
         * within the packets with the PID whose value is specified by the 
         * elementary_PID. The values of stream_type are specified in Table 2-29. */
        /* 流类型,表明是音频(这里为 0x0f)还是视频(这里为 0x1b) */
        stream_type = st;
        /* 视频(这里为 0x100)/音频(这里为 0x101)流等的 PID */
        elementary_PID = epid;
    
        const1_value0 = 7;
        const1_value1 = 0x0f;
        /* 高 2 位应为 0,规定了其随后的 descriptor() 字节数,这里设为 0,
         * 表示没有描述信息 */
        ES_info_length = 0;
        ES_info = NULL;
    }
    

    回到 SrsTsContext::encode_pat_pmt 函数中,构造好包含 PMT 信息的 TS Pakcet 后,接着调用 SrsTsPacket::encode 函数将包含 PMT 的 TS Packet 的数据写入到 stream 中。

    1.5 SrsTsPacket::encode

    int SrsTsPacket::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
    
        /* TS Packet 分为三个部分:ts header,adaptation field, payload */
        
        /* 第一部分:ts header: 固定为 4 字节 */
        /* 4B ts packet header. */    
        if (!stream->require(4)) {
            ret = ERROR_STREAM_CASTER_TS_HEADER;
            srs_error("ts: mux header failed. ret=%d", ret);
            return ret;
        }
    
        /* 首先写入标志一个 TS 分组的开始:同步字节 0x47 */
        stream->write_1bytes(sync_byte);
        
        int16_t pidv = pid & 0x1FFF;
        pidv |= (transport_priority << 13) & 0x2000;
        pidv |= (transport_error_indicator << 15) & 0x8000;
        pidv |= (payload_unit_start_indicator << 14) & 0x4000;
        stream->write_2bytes(pidv);
        
        int8_t ccv = continuity_counter & 0x0F;
        ccv |= (transport_scrambling_control << 6) & 0xC0;
        ccv |= (adaption_field_control << 4) & 0x30;
        stream->write_1bytes(ccv);
        
        /* 第二部分: adaptation field
         * 该部分有无根据 ts header 中的 adaption_field_control 字段值控制的,
         * 若为 '10' 或 '11' 都表示有 adaptation field。
         */
        /* optional: adaptation field */
        if (adaptation_field) {
            if ((ret = adaptation_field->encode(stream)) != ERROR_SUCCESS) {
                srs_error("ts: mux af faield. ret=%d", ret);
                return ret;
            }
        }
        
        /* 第三部分:payload
         * 该部分的有无也是根据 ts header 中的 adaptation_field_control 字段值控制的,
         * 若为 '01' 或 '11' 都表示有 payload。
         */
        
        /* optional: payload. */
        if (payload) {
            /* 在编码 PMT 中,该 payload 指向子类 SrsTsPayloadPSI 的子类对象,
             * SrsTsPayloadPMT,其中只有 SrsTsPayloadPSI 类实现了 encode 函数,
             * 因此调用该类对象实现的 encode 函数 */
            if ((ret = payload->encode(stream)) != ERROR_SUCCESS) {
                srs_error("ts: mux payload failed. ret=%d", ret);
                return ret;
            }
        }
        
        return ret;
    }
    

    该函数中,在将 TS Packet 的有效负载数据(即 PMT)写入到 stream 中时,调用的函数是 SrsTsPayloadPSI::encode。

    1.5.1 SrsTsPayloadPSI::encode

    int SrsTsPayloadPSI::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* 首先根据该字段是否为 1,表明在 PMT 数据前是否存在 pointer_field */
        if (packet->payload_unit_start_indicator) {
            if (!stream->require(1)) {
                ret = ERROR_STREAM_CASTER_TS_PSI;
                srs_error("ts: mux PSI failed. ret=%d", ret);
                return ret;
            }
            stream->write_1bytes(pointer_field);
        }
        
        /* 计算 PMT 开始数据到 CRC32 之间的校验码 */
        /* to calc the crc32 */
        char* ppat = stream->data() + stream->pos();
        int pat_pos = stream->pos();
        
        /* at least 3B for all psi. */
        if (!stream->require(3)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PSI failed. ret=%d", ret);
            return ret;
        }
        /* 1B */
        stream->write_1bytes(table_id);
        
        /* 2B */
        int16_t slv = section_length & 0x0FFF;
        slv |= (section_syntax_indicator << 15) & 0x8000;
        slv |= (const0_value << 14) & 0x4000;
        slv |= (const1_value << 12) & 0x3000;
        stream->write_2bytes(slv);
        
        /* no section, ignore. */
        if (section_length == 0) {
            srs_warn("ts: mux PAT ignore empty section");
            return ret;
        }
        
        if (!stream->require(section_length)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PAT section failed. ret=%d", ret);
            return ret;
        }
        
        /* 这里是对 PMT 的 section 部分进行编码
         * 调用子类 SrsTsPayloadPMT 实现的 psi_encode 函数 */
        /* call the virtual method of actual PSI. */
        if ((ret = psi_encode(stream)) != ERROR_SUCCESS) {
            return ret;
        }
        
        /* 4B */
        if (!stream->require(4)) {
            ret = ERROR_STREAM_CASTER_TS_PSI;
            srs_error("ts: mux PSI crc32 failed. ret=%d", ret);
            return ret;
        }
        /* cacl the crc32 of bytes in buf. */
        CRC_32 = srs_crc32(ppat, stream->pos() - pat_pos);
        stream->write_4bytes(CRC_32);
        
        return ret;
        
    }
    

    在该函数中,调用 SrsTsPayloadPMT 实现的 psi_encode 函数将 PMT 的 section 部分的数据写入到 stream 中。

    1.5.2 SrsTsPayloadPMT::psi_encode

    int SrsTsPayloadPMT::psi_encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* at least 9B for PMT specified */
        if (!stream->require(9)) {
            ret = ERROR_STREAM_CASTER_TS_PMT;
            srs_error("ts: mux PMT failed. ret=%d", ret);
            return ret;
        }
        
        /* 2B */
        /* 频道号码,表示当前 PMT 关联到的频道,取值为 0x0001 */
        stream->write_2bytes(program_number);
        
        /* 1B */
        int8_t cniv = current_next_indicator & 0x01;
        cniv |= (const1_value0 << 6) & 0xC0;
        cniv |= (version_number << 1) & 0xFE;
        stream->write_1bytes(cniv);
        
        /* 1B */
        /* 固定为 0x00 */
        stream->write_1bytes(section_number);
        
        /* 1B: 固定为 0x00 */
        stream->write_1bytes(last_section_number);
        
        /* 2B */
        int16_t ppv = PCR_PID & 0x1FFF;
        ppv |= (const1_value1 << 13) & 0xE000;
        stream->write_2bytes(ppv);
        
        /* 2B */
        int16_t pilv = program_info_length & 0xFFF;
        pilv |= (const1_value2 << 12) & 0xF000;
        stream->write_2bytes(pilv);
        
        /* program_info_length: 
         * 节目描述信息,指定为 0x000 表示没有 */
        if (program_info_length > 0) {
            if (!stream->require(program_info_length)) {
                ret = ERROR_STREAM_CASTER_TS_PMT;
                srs_error("ts: mux PMT program info failed. ret=%d", ret);
                return ret;
            }
    
            stream->write_bytes(program_info_desc, program_info_length);
        }
        
        for (int i = 0; i < (int)infos.size(); i ++) {
            SrsTsPayloadPMTESInfo* info = infos.at(i);
            if ((ret = info->encode(stream)) != ERROR_SUCCESS) {
                return ret;
            }
    
            /* update the apply pid table */
            switch (info->stream_type) {
                case SrsTsStreamVideoH264:
                case SrsTsStreamVideoMpeg4:
                    packet->context->set(info->elementary_PID, SrsTsPidApplyVideo, 
                                         info->stream_type);
                    break;
                case SrsTsStreamAudioAAC:
                case SrsTsStreamAudioAC3:
                case SrsTsStreamAudioDTS:
                case SrsTsStreamAudioMp3:
                    packet->context->set(info->elementary_PID, SrsTsPidApplyAudio, 
                                         info->stream_type);
                    break;
                default:
                    srs_warn("ts: drop pid=%#x, stream=%#x", 
                             info->elementary_PID, info->stream_type);
                    break;
            }
        }
    
        /* update the apply pid table. */
        packet->context->set(packet->pid, SrsTsPidApplyPMT);
    
        return ret;
    }
    

    该函数接着调用 SrsTsPayloadPMTESInfo::encode 函数将该 PMT 包含的音视频 PES 信息写入到 stream 中。

    1.5.3 SrsTsPayloadPMTESInfo::encode

    int SrsTsPayloadPMTESInfo::encode(SrsStream* stream)
    {
        int ret = ERROR_SUCCESS;
        
        /* 5B */
        if (!stream->require(5)) {
            ret = ERROR_STREAM_CASTER_TS_PMT;
            srs_error("ts: mux PMT es info failed. ret=%d", ret);
            return ret;
        }
        
        stream->write_1bytes(stream_type);
        
        int16_t epv = elementary_PID & 0x1FFF;
        epv |= (const1_value0 << 13) & 0xE000;
        stream->write_2bytes(epv);
        
        int16_t eilv = ES_info_length & 0x0FFF;
        eilv |= (const1_value1 << 12) & 0xF000;
        stream->write_2bytes(eilv);
        
        if (ES_info_length > 0) {
            if (!stream->require(ES_info_length)) {
                ret = ERROR_STREAM_CASTER_TS_PMT;
                srs_error("ts: mux PMT es info data failed. ret=%d", ret);
                return ret;
            }
            stream->write_bytes(ES_info, ES_info_length);
        }
    
        return ret;
    }
    

    下图即为上面写入 ts 文件后的包含 PMT 数据的 TS Packet:

  • 相关阅读:
    linux性能监控 -CPU、Memory、IO、Network等指标的讲解
    Linux中ctrl+z 、ctrl+c、 ctrl+d区别
    linux下程序启动后后台运行实现
    最近纠结致死的一个java报错java.net.SocketException: Connection reset 终于得到解决
    【Unity笔记】将角色的碰撞体朝向鼠标点击方向——角色朝向鼠标
    【Unity笔记】碰撞器(Collision)与触发器(Trigger)的区别
    【Unity笔记】鼠标射线由指定层接收
    【Unity笔记】Awake()和Start()的区别
    【Unity笔记】第三人称相机跟随
    【Unity笔记】经典的鼠标点击射线检测碰撞
  • 原文地址:https://www.cnblogs.com/jimodetiantang/p/9146855.html
Copyright © 2020-2023  润新知