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:
SRS之TS封装PAT和PMT的更多相关文章
- TS数据流PAT和PMT分析(转载)
转自:http://www.cnblogs.com/hjj801006/p/3837435.html TS流,是基于packet的位流格式,每个packet是188个字节或者204个字 节(一般是18 ...
- TS数据流PAT和PMT分析
TS流,是基于packet的位流格式,每个packet是188个字节或者204个字节(一般是188字节,204字节格式是在188字节的packet后面加上16字节的CRC数据,其他格式相同),解析TS ...
- 简单解析PAT、PMT的程序
刚开始学习有关TS.PAT.PMT方面的内容,参考了别人的一些程序,然后写了一个简单的解析TS的小程序.如果有地方错误,请发邮件给我843036544@qq.com. #include<stdi ...
- TS流PAT/PMT详解
一 从TS流开始 从MPEG-2到DVB,看着看着突然就出现了一大堆表格,什么PAT.PMT.CAT……如此多的表该怎样深入了解呢? 我们知道,数字电视机顶盒接收到的是一段段的码流,我们称之为TS(T ...
- 从TS流到PAT和PMT
转自:https://blog.csdn.net/rongdeguoqian/article/details/18214627 一 从TS流开始 最近开始学习数字电视机顶盒的开发,从MPEG-2到DV ...
- 修复FFMPEG 复用 PAT、PMT发送间隔小于25ms的错误
目录 分析ffmpeg源码 分析问题 修改源码解决问题 分析ffmpeg源码 分析问题 mpegtsenc.c 找到发送PAT.PMT的函数 /* send SDT, PAT and PMT tabl ...
- TS封装格式
ts流最早应用于数字电视领域,其格式非常复杂包含的配置信息表多达十几个,视频格式主要是mpeg2.苹果公司发明的http live stream流媒体是基于ts文件的,不过他大大简化了传统的ts流,只 ...
- PAT、PMT、SDT详解
下面针对解复用程序详细分析一下PAT,PMT和SDT三类表格的格式. 如下图,四个频道复用 PAT---Program Association Table,节目关联表 .PAT表携带以下信息: (1) ...
- 视音频编解码学习工程:TS封装格式分析器
=====================================================视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习工 ...
随机推荐
- springboot-oracle工程win下正常,centos下不能访问数据库
工程在win下正常运行,部署到centos下出现下述异常: ### Error querying database. Cause: org.springframework.jdbc.CannotGet ...
- Vue学习笔记(一) 利用idea 搭建 vue 项目
环境准备工作: 安装node.js 环境 -- 略 安装vue-li 全局安装vue-cli,在命令行中执行npm install -g vue-cli idea准备工作: 安装vue.js Fi ...
- HTML5之fileReader异步读取文件及文件切片读取
fileReader的方法与事件 fileReade实现图片预加载 fileReade实现文件读取进度条 fileReade的与file.s实现文件切片读取 一.fileReader的方法与事件 1. ...
- day1-css练习[新浪首页顶部栏]
直接贴代码吧: html代码 <div class="border-01"> <div class="border-001"> < ...
- Nginx作为代理服务之反向代理
Nginx作为代理服务之反向代理 需求:我们需要访问一个服务,但是服务端只接受8080端口,所以需要在nginx中配置反向代理,帮助客户端代理实现. 1. 创建一个html放入到一个文件夹中 2. 在 ...
- 命令行工具--curl
目录 命令:curl 一.简介 二.使用案例 1.基本用法 2.保存访问的网页 3.测试网页返回值 4.指定proxy服务器以及其端口 5.cookie 6.模仿浏览器 7.伪造referer(盗链) ...
- 怎么处理U盘无法正常弹出的情况?
我们都知道U盘和移动硬盘在使用完毕后需要点击“安全删除硬件并弹出”后才能拔出,这样可以避免U盘还在工作时被拔出而造成的故障. 但有时我们点击“安全删除硬件并弹出”时,系统会提示U盘正在工作,没有办法停 ...
- deep_learning_CNN
AI学习笔记——卷积神经网络(CNN) image.png 上篇文章简单地地介绍了神经网络和深度学习,在神经网络中,每一层的每个神经元都与下一层的每个神经元相连(如下图), 这种连接关系叫全连接(Fu ...
- 使用 Eclipse 构建的时候会出现 run as 中没有 maven package 选项
注:该方法来自我学习时别人分享的出现问题的解决方法,并没有亲自测试,仅供参考 是因为建的是普通 java 工程,需要把它转换成 maven project. 1.右键工程--maven--Disabl ...
- Python3.8新特性-- 海象操作符
“理论联系实惠,密切联系领导,表扬和自我表扬”——我就是老司机,曾经写文章教各位怎么打拼职场的老司机. 不记得没关系,只需要知道:有这么一位老司机, 穿上西装带大家打拼职场! 操起键盘带大家打磨技术! ...