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:

posted @ 2018-06-06 19:37  季末的天堂  阅读(1429)  评论(0编辑  收藏  举报