音视频入门基础:MPEG2-TS专题(19)——FFmpeg源码中,解析TS流中的PES流的实现

一、引言

FFmpeg源码在解析完PMT表后,会得到该节目包含的视频和音频信息,从而找到音视频流。TS流的音视频流包含在PES流中。FFmpeg源码通过调用函数指针tss->u.pes_filter.pes_cb指向的回调函数解析PES流的PES packet:

/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{
//...if (tss->type == MPEGTS_SECTION) {//...}else {int ret;// Note: The position here points actually behind the current packet.if (tss->type == MPEGTS_PES) {if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,pos - ts->raw_packet_size)) < 0)return ret;}}return 0;
//...
}

而函数指针tss->u.pes_filter.pes_cb指向的回调函数是mpegts_push_data函数。

二、mpegts_push_data函数的定义

mpegts_push_data函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/mpegts.c中:

/* return non zero if a packet could be constructed */
static int mpegts_push_data(MpegTSFilter *filter,const uint8_t *buf, int buf_size, int is_start,int64_t pos)
{PESContext *pes   = filter->u.pes_filter.opaque;MpegTSContext *ts = pes->ts;const uint8_t *p;int ret, len;if (!ts->pkt)return 0;if (is_start) {if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) {ret = new_pes_packet(pes, ts->pkt);if (ret < 0)return ret;ts->stop_parse = 1;} else {reset_pes_packet_state(pes);}pes->state         = MPEGTS_HEADER;pes->ts_packet_pos = pos;}p = buf;while (buf_size > 0) {switch (pes->state) {case MPEGTS_HEADER:len = PES_START_SIZE - pes->data_index;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == PES_START_SIZE) {/* we got all the PES or section header. We can now* decide */if (pes->header[0] == 0x00 && pes->header[1] == 0x00 &&pes->header[2] == 0x01) {/* it must be an MPEG-2 PES stream */pes->stream_id = pes->header[3];av_log(pes->stream, AV_LOG_TRACE, "pid=%x stream_id=%#x\n", pes->pid, pes->stream_id);if ((pes->st && pes->st->discard == AVDISCARD_ALL &&(!pes->sub_st ||pes->sub_st->discard == AVDISCARD_ALL)) ||pes->stream_id == STREAM_ID_PADDING_STREAM)goto skip;/* stream not present in PMT */if (!pes->st) {if (ts->skip_changes)goto skip;if (ts->merge_pmt_versions)goto skip; /* wait for PMT to merge new stream */pes->st = avformat_new_stream(ts->stream, NULL);if (!pes->st)return AVERROR(ENOMEM);pes->st->id = pes->pid;mpegts_set_stream_info(pes->st, pes, 0, 0);}pes->PES_packet_length = AV_RB16(pes->header + 4);/* NOTE: zero length means the PES size is unbounded */if (pes->stream_id != STREAM_ID_PROGRAM_STREAM_MAP &&pes->stream_id != STREAM_ID_PRIVATE_STREAM_2 &&pes->stream_id != STREAM_ID_ECM_STREAM &&pes->stream_id != STREAM_ID_EMM_STREAM &&pes->stream_id != STREAM_ID_PROGRAM_STREAM_DIRECTORY &&pes->stream_id != STREAM_ID_DSMCC_STREAM &&pes->stream_id != STREAM_ID_TYPE_E_STREAM) {FFStream *const pes_sti = ffstream(pes->st);pes->state = MPEGTS_PESHEADER;if (pes->st->codecpar->codec_id == AV_CODEC_ID_NONE && !pes_sti->request_probe) {av_log(pes->stream, AV_LOG_TRACE,"pid=%x stream_type=%x probing\n",pes->pid,pes->stream_type);pes_sti->request_probe = 1;}} else {pes->pes_header_size = 6;pes->state      = MPEGTS_PAYLOAD;pes->data_index = 0;}} else {/* otherwise, it should be a table *//* skip packet */
skip:pes->state = MPEGTS_SKIP;continue;}}break;/**********************************************//* PES packing parsing */case MPEGTS_PESHEADER:len = PES_HEADER_SIZE - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == PES_HEADER_SIZE) {pes->pes_header_size = pes->header[8] + 9;pes->state           = MPEGTS_PESHEADER_FILL;}break;case MPEGTS_PESHEADER_FILL:len = pes->pes_header_size - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == pes->pes_header_size) {const uint8_t *r;unsigned int flags, pes_ext, skip;flags = pes->header[7];r = pes->header + 9;pes->pts = AV_NOPTS_VALUE;pes->dts = AV_NOPTS_VALUE;if ((flags & 0xc0) == 0x80) {pes->dts = pes->pts = ff_parse_pes_pts(r);r += 5;} else if ((flags & 0xc0) == 0xc0) {pes->pts = ff_parse_pes_pts(r);r += 5;pes->dts = ff_parse_pes_pts(r);r += 5;}pes->extended_stream_id = -1;if (flags & 0x01) { /* PES extension */pes_ext = *r++;/* Skip PES private data, program packet sequence counter and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;r    += skip;if ((pes_ext & 0x41) == 0x01 &&(r + 2) <= (pes->header + pes->pes_header_size)) {/* PES extension 2 */if ((r[0] & 0x7f) > 0 && (r[1] & 0x80) == 0)pes->extended_stream_id = r[1];}}/* we got the full header. We parse it and get the payload */pes->state = MPEGTS_PAYLOAD;pes->data_index = 0;if (pes->stream_type == 0x12 && buf_size > 0) {int sl_header_bytes = read_sl_header(pes, &pes->sl, p,buf_size);pes->pes_header_size += sl_header_bytes;p += sl_header_bytes;buf_size -= sl_header_bytes;}if (pes->stream_type == STREAM_TYPE_METADATA &&pes->stream_id == STREAM_ID_METADATA_STREAM &&pes->st->codecpar->codec_id == AV_CODEC_ID_SMPTE_KLV &&buf_size >= 5) {/* skip metadata access unit header - see MISB ST 1402 */pes->pes_header_size += 5;p += 5;buf_size -= 5;}if (   pes->ts->fix_teletext_pts&& (   pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT|| pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_SUBTITLE)) {AVProgram *p = NULL;int pcr_found = 0;while ((p = av_find_program_from_stream(pes->stream, p, pes->st->index))) {if (p->pcr_pid != -1 && p->discard != AVDISCARD_ALL) {MpegTSFilter *f = pes->ts->pids[p->pcr_pid];if (f) {AVStream *st = NULL;if (f->type == MPEGTS_PES) {PESContext *pcrpes = f->u.pes_filter.opaque;if (pcrpes)st = pcrpes->st;} else if (f->type == MPEGTS_PCR) {int i;for (i = 0; i < p->nb_stream_indexes; i++) {AVStream *pst = pes->stream->streams[p->stream_index[i]];if (pst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)st = pst;}}if (f->last_pcr != -1 && !f->discard) {// teletext packets do not always have correct timestamps,// the standard says they should be handled after 40.6 ms at most,// and the pcr error to this packet should be no more than 100 ms.// TODO: we should interpolate the PCR, not just use the last oneint64_t pcr = f->last_pcr / 300;pcr_found = 1;if (st) {const FFStream *const sti = ffstream(st);FFStream *const pes_sti   = ffstream(pes->st);pes_sti->pts_wrap_reference = sti->pts_wrap_reference;pes_sti->pts_wrap_behavior  = sti->pts_wrap_behavior;}if (pes->dts == AV_NOPTS_VALUE || pes->dts < pcr) {pes->pts = pes->dts = pcr;} else if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT &&pes->dts > pcr + 3654 + 9000) {pes->pts = pes->dts = pcr + 3654 + 9000;} else if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_SUBTITLE &&pes->dts > pcr + 10*90000) { //10secpes->pts = pes->dts = pcr + 3654 + 9000;}break;}}}}if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT &&!pcr_found) {av_log(pes->stream, AV_LOG_VERBOSE,"Forcing DTS/PTS to be unset for a ""non-trustworthy PES packet for PID %d as ""PCR hasn't been received yet.\n",pes->pid);pes->dts = pes->pts = AV_NOPTS_VALUE;}}}break;case MPEGTS_PAYLOAD:do {int max_packet_size = ts->max_packet_size;if (pes->PES_packet_length && pes->PES_packet_length + PES_START_SIZE > pes->pes_header_size)max_packet_size = pes->PES_packet_length + PES_START_SIZE - pes->pes_header_size;if (pes->data_index > 0 &&pes->data_index + buf_size > max_packet_size) {ret = new_pes_packet(pes, ts->pkt);if (ret < 0)return ret;pes->PES_packet_length = 0;max_packet_size = ts->max_packet_size;ts->stop_parse = 1;} else if (pes->data_index == 0 &&buf_size > max_packet_size) {// pes packet size is < ts size packet and pes data is padded with 0xff// not sure if this is legal in ts but see issue #2392buf_size = max_packet_size;}if (!pes->buffer) {pes->buffer = buffer_pool_get(ts, max_packet_size);if (!pes->buffer)return AVERROR(ENOMEM);}memcpy(pes->buffer->data + pes->data_index, p, buf_size);pes->data_index += buf_size;/* emit complete packets with known packet size* decreases demuxer delay for infrequent packets like subtitles from* a couple of seconds to milliseconds for properly muxed files. */if (!ts->stop_parse && pes->PES_packet_length &&pes->pes_header_size + pes->data_index == pes->PES_packet_length + PES_START_SIZE) {ts->stop_parse = 1;ret = new_pes_packet(pes, ts->pkt);pes->state = MPEGTS_SKIP;if (ret < 0)return ret;}} while (0);buf_size = 0;break;case MPEGTS_SKIP:buf_size = 0;break;}}return 0;
}

该函数的作用就是:解析TS流中的PES流的PES packet(PES包)。即解析TS流中的一个PES packet。

形参filter:输出型参数,指向一个MpegTSFilter类型变量。

形参buf:输入型参数。存放一个transport packet(TS包)去掉TS Header后的有效数据,即ES packet的数据。

形参buf_size:输入型参数。形参buf指向的缓冲区的大小。

形参is_start:输入型参数。如果值为true,表示该transport packet的TS Header的payload_unit_start_indicator属性的值为1,说明携带的是PES的第一个包。

形参pos:输入型参数,文件位置指针当前位置相对于TS文件的文件首的偏移字节数 减去 188字节。

返回值:返回0表示解析成功,返回一个负数表示出错。

三、mpegts_push_data函数的内部实现分析

mpegts_push_data函数内部使用了状态机的设计模式,会根据枚举变量pes->state的值执行不同的处理逻辑,pes->state取值如下:

/* TS stream handling */enum MpegTSState {MPEGTS_HEADER = 0,MPEGTS_PESHEADER,MPEGTS_PESHEADER_FILL,MPEGTS_PAYLOAD,MPEGTS_SKIP,
};

mpegts_push_data函数中首先判断形参is_start的值,如果为true,说明携带的是PES的第一个包。让pes->state赋值为MPEGTS_HEADER:

    if (is_start) {if (pes->state == MPEGTS_PAYLOAD && pes->data_index > 0) {ret = new_pes_packet(pes, ts->pkt);if (ret < 0)return ret;ts->stop_parse = 1;} else {reset_pes_packet_state(pes);}pes->state         = MPEGTS_HEADER;pes->ts_packet_pos = pos;}

下面分情况讨论。

(一)pes->state的值为MPEGTS_HEADER

pes->state的值为MPEGTS_HEADER时,mpegts_push_data函数会执行下面代码块解析该PES packet的PES packet header中的固定长度部分:

        switch (pes->state) {case MPEGTS_HEADER:len = PES_START_SIZE - pes->data_index;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == PES_START_SIZE) {/* we got all the PES or section header. We can now* decide */if (pes->header[0] == 0x00 && pes->header[1] == 0x00 &&pes->header[2] == 0x01) {/* it must be an MPEG-2 PES stream */pes->stream_id = pes->header[3];av_log(pes->stream, AV_LOG_TRACE, "pid=%x stream_id=%#x\n", pes->pid, pes->stream_id);if ((pes->st && pes->st->discard == AVDISCARD_ALL &&(!pes->sub_st ||pes->sub_st->discard == AVDISCARD_ALL)) ||pes->stream_id == STREAM_ID_PADDING_STREAM)goto skip;/* stream not present in PMT */if (!pes->st) {if (ts->skip_changes)goto skip;if (ts->merge_pmt_versions)goto skip; /* wait for PMT to merge new stream */pes->st = avformat_new_stream(ts->stream, NULL);if (!pes->st)return AVERROR(ENOMEM);pes->st->id = pes->pid;mpegts_set_stream_info(pes->st, pes, 0, 0);}pes->PES_packet_length = AV_RB16(pes->header + 4);/* NOTE: zero length means the PES size is unbounded */if (pes->stream_id != STREAM_ID_PROGRAM_STREAM_MAP &&pes->stream_id != STREAM_ID_PRIVATE_STREAM_2 &&pes->stream_id != STREAM_ID_ECM_STREAM &&pes->stream_id != STREAM_ID_EMM_STREAM &&pes->stream_id != STREAM_ID_PROGRAM_STREAM_DIRECTORY &&pes->stream_id != STREAM_ID_DSMCC_STREAM &&pes->stream_id != STREAM_ID_TYPE_E_STREAM) {FFStream *const pes_sti = ffstream(pes->st);pes->state = MPEGTS_PESHEADER;if (pes->st->codecpar->codec_id == AV_CODEC_ID_NONE && !pes_sti->request_probe) {av_log(pes->stream, AV_LOG_TRACE,"pid=%x stream_type=%x probing\n",pes->pid,pes->stream_type);pes_sti->request_probe = 1;}} else {pes->pes_header_size = 6;pes->state      = MPEGTS_PAYLOAD;pes->data_index = 0;}} else {/* otherwise, it should be a table *//* skip packet */
skip:pes->state = MPEGTS_SKIP;continue;}}

宏PES_START_SIZE定义在libavformat/mpegts.c中,值为6,表示PES packet header中的固定长度部分总共占6个字节:

#define PES_START_SIZE  6

上述代码块中,首先将该PES packet的PES packet header中的固定长度部分拷贝到数组pes->header中:

            len = PES_START_SIZE - pes->data_index;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;

判断PES packet header中的packet_start_code_prefix属性的值是否为0x000001,如果是,说明这是PES packet,读取stream_id属性的值,赋值给变量pes->stream_id:

                /* we got all the PES or section header. We can now* decide */if (pes->header[0] == 0x00 && pes->header[1] == 0x00 &&pes->header[2] == 0x01) {/* it must be an MPEG-2 PES stream */pes->stream_id = pes->header[3];av_log(pes->stream, AV_LOG_TRACE, "pid=%x stream_id=%#x\n", pes->pid, pes->stream_id);//...
}

如果在之前解析PMT表的时候没有找到该PES流对应的音视频流,跳过本次循环:

                    /* stream not present in PMT */if (!pes->st) {if (ts->skip_changes)goto skip;if (ts->merge_pmt_versions)goto skip; /* wait for PMT to merge new stream */pes->st = avformat_new_stream(ts->stream, NULL);if (!pes->st)return AVERROR(ENOMEM);pes->st->id = pes->pid;mpegts_set_stream_info(pes->st, pes, 0, 0);}

读取PES_packet_length属性的值,赋值给变量pes->PES_packet_length:

                    pes->PES_packet_length = AV_RB16(pes->header + 4);/* NOTE: zero length means the PES size is unbounded */

通过PES packet header中的stream_id的值判断PES packet header中是否存在Optional PES header,如果存在,让pes->state赋值为MPEGTS_PESHEADER:

                    if (pes->stream_id != STREAM_ID_PROGRAM_STREAM_MAP &&pes->stream_id != STREAM_ID_PRIVATE_STREAM_2 &&pes->stream_id != STREAM_ID_ECM_STREAM &&pes->stream_id != STREAM_ID_EMM_STREAM &&pes->stream_id != STREAM_ID_PROGRAM_STREAM_DIRECTORY &&pes->stream_id != STREAM_ID_DSMCC_STREAM &&pes->stream_id != STREAM_ID_TYPE_E_STREAM) {FFStream *const pes_sti = ffstream(pes->st);pes->state = MPEGTS_PESHEADER;if (pes->st->codecpar->codec_id == AV_CODEC_ID_NONE && !pes_sti->request_probe) {av_log(pes->stream, AV_LOG_TRACE,"pid=%x stream_type=%x probing\n",pes->pid,pes->stream_type);pes_sti->request_probe = 1;}} else {pes->pes_header_size = 6;pes->state      = MPEGTS_PAYLOAD;pes->data_index = 0;}

(二)pes->state的值为MPEGTS_PESHEADER

pes->state的值为MPEGTS_PESHEADER时,mpegts_push_data函数会执行下面代码块:

        /**********************************************//* PES packing parsing */case MPEGTS_PESHEADER:len = PES_HEADER_SIZE - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == PES_HEADER_SIZE) {pes->pes_header_size = pes->header[8] + 9;pes->state           = MPEGTS_PESHEADER_FILL;}break;

宏PES_HEADER_SIZE定义在libavformat/mpegts.c中,值为9,表示PES packet header中的固定长度部分 + Optional PES header中的PES_header_data_length属性之前的部分(包含PES_header_data_length属性)总共占9个字节:

#define PES_HEADER_SIZE 9

上述代码块中,首先将该PES packet的Optional PES header中的PES_header_data_length属性之前的部分(包含PES_header_data_length属性)拷贝到pes->header + pes->data_index指向的缓冲区中:

            len = PES_HEADER_SIZE - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;

读取Optional PES header中的PES_header_data_length属性,赋值给变量pes->pes_header_size。让变量pes->state赋值为MPEGTS_PESHEADER_FILL:

            if (pes->data_index == PES_HEADER_SIZE) {pes->pes_header_size = pes->header[8] + 9;pes->state           = MPEGTS_PESHEADER_FILL;}

(三)pes->state的值为MPEGTS_PESHEADER_FILL

pes->state的值为MPEGTS_PESHEADER_FILL时,mpegts_push_data函数会执行下面代码块:

            len = pes->pes_header_size - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;if (pes->data_index == pes->pes_header_size) {const uint8_t *r;unsigned int flags, pes_ext, skip;flags = pes->header[7];r = pes->header + 9;pes->pts = AV_NOPTS_VALUE;pes->dts = AV_NOPTS_VALUE;if ((flags & 0xc0) == 0x80) {pes->dts = pes->pts = ff_parse_pes_pts(r);r += 5;} else if ((flags & 0xc0) == 0xc0) {pes->pts = ff_parse_pes_pts(r);r += 5;pes->dts = ff_parse_pes_pts(r);r += 5;}pes->extended_stream_id = -1;if (flags & 0x01) { /* PES extension */pes_ext = *r++;/* Skip PES private data, program packet sequence counter and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;r    += skip;if ((pes_ext & 0x41) == 0x01 &&(r + 2) <= (pes->header + pes->pes_header_size)) {/* PES extension 2 */if ((r[0] & 0x7f) > 0 && (r[1] & 0x80) == 0)pes->extended_stream_id = r[1];}}/* we got the full header. We parse it and get the payload */pes->state = MPEGTS_PAYLOAD;pes->data_index = 0;if (pes->stream_type == 0x12 && buf_size > 0) {int sl_header_bytes = read_sl_header(pes, &pes->sl, p,buf_size);pes->pes_header_size += sl_header_bytes;p += sl_header_bytes;buf_size -= sl_header_bytes;}if (pes->stream_type == STREAM_TYPE_METADATA &&pes->stream_id == STREAM_ID_METADATA_STREAM &&pes->st->codecpar->codec_id == AV_CODEC_ID_SMPTE_KLV &&buf_size >= 5) {/* skip metadata access unit header - see MISB ST 1402 */pes->pes_header_size += 5;p += 5;buf_size -= 5;}if (   pes->ts->fix_teletext_pts&& (   pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT|| pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_SUBTITLE)) {AVProgram *p = NULL;int pcr_found = 0;while ((p = av_find_program_from_stream(pes->stream, p, pes->st->index))) {if (p->pcr_pid != -1 && p->discard != AVDISCARD_ALL) {MpegTSFilter *f = pes->ts->pids[p->pcr_pid];if (f) {AVStream *st = NULL;if (f->type == MPEGTS_PES) {PESContext *pcrpes = f->u.pes_filter.opaque;if (pcrpes)st = pcrpes->st;} else if (f->type == MPEGTS_PCR) {int i;for (i = 0; i < p->nb_stream_indexes; i++) {AVStream *pst = pes->stream->streams[p->stream_index[i]];if (pst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)st = pst;}}if (f->last_pcr != -1 && !f->discard) {// teletext packets do not always have correct timestamps,// the standard says they should be handled after 40.6 ms at most,// and the pcr error to this packet should be no more than 100 ms.// TODO: we should interpolate the PCR, not just use the last oneint64_t pcr = f->last_pcr / 300;pcr_found = 1;if (st) {const FFStream *const sti = ffstream(st);FFStream *const pes_sti   = ffstream(pes->st);pes_sti->pts_wrap_reference = sti->pts_wrap_reference;pes_sti->pts_wrap_behavior  = sti->pts_wrap_behavior;}if (pes->dts == AV_NOPTS_VALUE || pes->dts < pcr) {pes->pts = pes->dts = pcr;} else if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT &&pes->dts > pcr + 3654 + 9000) {pes->pts = pes->dts = pcr + 3654 + 9000;} else if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_SUBTITLE &&pes->dts > pcr + 10*90000) { //10secpes->pts = pes->dts = pcr + 3654 + 9000;}break;}}}}if (pes->st->codecpar->codec_id == AV_CODEC_ID_DVB_TELETEXT &&!pcr_found) {av_log(pes->stream, AV_LOG_VERBOSE,"Forcing DTS/PTS to be unset for a ""non-trustworthy PES packet for PID %d as ""PCR hasn't been received yet.\n",pes->pid);pes->dts = pes->pts = AV_NOPTS_VALUE;}}}break;

上述代码块中,首先将该PES packet的Optional PES header中的可选字段占用的总字节数,以及包含在此PES packet header的任何填充字节拷贝到pes->header + pes->data_index指向的缓冲区中:

            len = pes->pes_header_size - pes->data_index;if (len < 0)return AVERROR_INVALIDDATA;if (len > buf_size)len = buf_size;memcpy(pes->header + pes->data_index, p, len);pes->data_index += len;p += len;buf_size -= len;

读取Optional PES header中的PTS_DTS_flags、ESCR_flag、ES_rate_flag、DSM_trick_mode_flag、additional_copy_info_flag、PES_CRC_flag、PES_extension_flag这7个属性(这7个属性加起来总共1个字节),赋值给变量flags:

                const uint8_t *r;unsigned int flags, pes_ext, skip;flags = pes->header[7];r = pes->header + 9;pes->pts = AV_NOPTS_VALUE;pes->dts = AV_NOPTS_VALUE;

如果Optional PES header中的PTS_DTS_flags属性的值为'10',表示PES packet header中会存在PTS。读取PTS的值赋值给变量pes->pts,让pes->dts也等于PTS:

                if ((flags & 0xc0) == 0x80) {pes->dts = pes->pts = ff_parse_pes_pts(r);r += 5;}

如果PTS_DTS_flags属性的值为'11',PES packet header中会同时存在PTS和DTS,读取PTS的值赋值给变量pes->pts,读取DTS的值赋值给变量pes->dts:

                if ((flags & 0xc0) == 0x80) {//...} else if ((flags & 0xc0) == 0xc0) {pes->pts = ff_parse_pes_pts(r);r += 5;pes->dts = ff_parse_pes_pts(r);r += 5;}

如果PES_extension_flag属性的值为1,表示PES packet header有PES_extension域,解析PES_extension域:

                if (flags & 0x01) { /* PES extension */pes_ext = *r++;/* Skip PES private data, program packet sequence counter and P-STD buffer */skip  = (pes_ext >> 4) & 0xb;skip += skip & 0x9;r    += skip;if ((pes_ext & 0x41) == 0x01 &&(r + 2) <= (pes->header + pes->pes_header_size)) {/* PES extension 2 */if ((r[0] & 0x7f) > 0 && (r[1] & 0x80) == 0)pes->extended_stream_id = r[1];}}

至此,mpegts_push_data函数已解析完整个PES packet header,让变量pes->state赋值为MPEGTS_PAYLOAD:

                /* we got the full header. We parse it and get the payload */pes->state = MPEGTS_PAYLOAD;pes->data_index = 0;

(四)pes->state的值为MPEGTS_PAYLOAD

pes->state的值为MPEGTS_PAYLOAD时,mpegts_push_data函数会执行下面代码块:

case MPEGTS_PAYLOAD:do {int max_packet_size = ts->max_packet_size;if (pes->PES_packet_length && pes->PES_packet_length + PES_START_SIZE > pes->pes_header_size)max_packet_size = pes->PES_packet_length + PES_START_SIZE - pes->pes_header_size;if (pes->data_index > 0 &&pes->data_index + buf_size > max_packet_size) {ret = new_pes_packet(pes, ts->pkt);if (ret < 0)return ret;pes->PES_packet_length = 0;max_packet_size = ts->max_packet_size;ts->stop_parse = 1;} else if (pes->data_index == 0 &&buf_size > max_packet_size) {// pes packet size is < ts size packet and pes data is padded with 0xff// not sure if this is legal in ts but see issue #2392buf_size = max_packet_size;}if (!pes->buffer) {pes->buffer = buffer_pool_get(ts, max_packet_size);if (!pes->buffer)return AVERROR(ENOMEM);}memcpy(pes->buffer->data + pes->data_index, p, buf_size);pes->data_index += buf_size;/* emit complete packets with known packet size* decreases demuxer delay for infrequent packets like subtitles from* a couple of seconds to milliseconds for properly muxed files. */if (!ts->stop_parse && pes->PES_packet_length &&pes->pes_header_size + pes->data_index == pes->PES_packet_length + PES_START_SIZE) {ts->stop_parse = 1;ret = new_pes_packet(pes, ts->pkt);pes->state = MPEGTS_SKIP;if (ret < 0)return ret;}} while (0);buf_size = 0;break;

上述代码块的主要作用就是将该PES packet的PES packet data bytes(PES包的负载)拷贝到pes->buffer->data + pes->data_index指向的缓冲区中:

                memcpy(pes->buffer->data + pes->data_index, p, buf_size);pes->data_index += buf_size;

ts->pkt指向一个AVPacket类型的变量,让ts->pkt得到该PES packet的数据:

                /* emit complete packets with known packet size* decreases demuxer delay for infrequent packets like subtitles from* a couple of seconds to milliseconds for properly muxed files. */if (!ts->stop_parse && pes->PES_packet_length &&pes->pes_header_size + pes->data_index == pes->PES_packet_length + PES_START_SIZE) {ts->stop_parse = 1;ret = new_pes_packet(pes, ts->pkt);pes->state = MPEGTS_SKIP;if (ret < 0)return ret;}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/890413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch-分词器详解

什么是分词器 1、分词器介绍 对文本进行分析处理的一种手段&#xff0c;基本处理逻辑为按照预先制定的分词规则&#xff0c;把原始文档分割成若干更小粒度的词项&#xff0c;粒度大小取决于分词器规则。 常用的中文分词器有ik按照切词的粒度粗细又分为:ik_max_word和ik_smart&…

Docker 入门:如何使用 Docker 容器化 AI 项目(一)

引言 在人工智能&#xff08;AI&#xff09;项目的开发和部署过程中&#xff0c;环境配置和依赖管理往往是开发者遇到的挑战之一。开发者通常需要在不同的机器上运行同样的代码&#xff0c;确保每个人使用的环境一致&#xff0c;才能避免 “在我的机器上可以运行”的尴尬问题。…

ExcelVBA编程输出ColorIndex与对应颜色色谱

标题 ExcelVBA编程输出ColorIndex与对应颜色色谱 正文 解决问题编程输出ColorIndex与对应色谱共56&#xff0c;打算分4纵列输出&#xff0c;标题是ColorIndex,Color,Name 1. 解释VBA中的ColorIndex属性 在VBA&#xff08;Visual Basic for Applications&#xff09;中&#xff…

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题

2024年11月 蓝桥杯青少组 STEMA考试 Scratch真题&#xff08;选择题&#xff09; 题目总数&#xff1a;5 总分数&#xff1a;50 选择题 第 1 题 单选题 Scratch运行以下程宇后&#xff0c;小兔子会&#xff08; &#xff09;。 A. 变小 B. 变大 C. 变色 D. …

springboot470基于协同过滤算法的东北特产销售系统的实现(论文+源码)_kaic

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

37. Three.js案例-绘制部分球体

37. Three.js案例-绘制部分球体 实现效果 知识点 WebGLRenderer WebGLRenderer 是Three.js中的一个渲染器类&#xff0c;用于将3D场景渲染到网页上。 构造器 WebGLRenderer( parameters : Object ) 参数类型描述parametersObject渲染器的配置参数&#xff0c;可选。 常用…

leetcode 面试经典 150 题:长度最小的子数组

链接长度最小的子数组题序号209题型数组解题方法滑动窗口难度中等 题目 给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件…

【游戏设计原理】22 - 石头剪刀布

一、游戏基础&#xff1a;拳头、掌心、分指 首先&#xff0c;石头剪刀布&#xff08;又名“Roshambo”&#xff09;看似简单&#xff0c;实际上可是个“深藏玄机”的零和博弈&#xff08;听起来很高深&#xff0c;其实就是输赢相抵消的意思&#xff09;。游戏中有三种手势&…

五、windows上vscode构建c/c++环境

1、安装vscode 官网下载界面&#xff1a;https://code.visualstudio.com/Download 请根据电脑系统安装所需版本点击下载链接&#xff08;一般情况下点击windows按钮即可&#xff09;鼠标左键双击&#xff0c;即可运行安装程序&#xff0c;点击【确认】&#xff1b;选择安装路径…

三格电子——新品IE103转ModbusTCP网关

型号&#xff1a;SG-TCP-IEC103 产品概述 IE103转ModbusTCP网关型号SG-TCP-IEC103&#xff0c;是三格电子推出的工业级网关&#xff08;以下简称网关&#xff09;&#xff0c;主要用于IEC103数据采集、DLT645-1997/2007数据采集&#xff0c;IEC103支持遥测和遥信&#xff0c;可…

数据结构---------二叉树前序遍历中序遍历后序遍历

以下是用C语言实现二叉树的前序遍历、中序遍历和后序遍历的代码示例&#xff0c;包括递归和非递归&#xff08;借助栈实现&#xff09;两种方式&#xff1a; 1. 二叉树节点结构体定义 #include <stdio.h> #include <stdlib.h>// 二叉树节点结构体 typedef struct…

UE5仿漫威争锋灵蝶冲刺技能

这两天玩了一下漫威争锋Marvel Rivals&#xff0c;发现是UE5做的&#xff0c;对里面一些角色技能挺感兴趣的&#xff0c;想简单复刻一下技能功能&#xff0c;顺便复习一下学过的知识 首先把摄像机设置调整一下 CameraBoom里搜索lag 把摄像机延迟关掉 &#xff0c;这样摄像机就…

常用类晨考day15

1.基本数据类型以及对应包装类 Byte Short Integer Long Float Double Boolean Character 2.什么是自动拆箱和装箱&#xff0c;jdk版本有什么要求&#xff1f;代码举 例并标明 Integer a 100; // 装箱 int b a; // 拆箱 从JDK1.5才开始支持 3.NumberFormatException是什么异常…

etcd+京东hotkey探测使用

qhotKey链接 京东hotkey把热点数据默认缓存在了本地缓存caffeine中&#xff0c;也可以存到redis中&#xff0c;但是京东hotkey的SDK没有redis的实现方法&#xff0c;因此需要自己实现。 官方目录结构下&#xff1a;分别是client客户端&#xff08;要打包引入到自己的项目&…

如何实现层叠布局

文章目录 1 概念介绍2 使用方法3 示例代码我们在上一章回中介绍了GirdView Widget,本章回中将介绍Stack这种Widget,闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 在Flutter中Stack主要用来叠加显示其它的Widget,类似我们日常生活中的楼层或者说PS中的图层,因此它也是一…

Java 上机实践11(组件及事件处理)

&#xff08;大家好&#xff0c;今天分享的是Java的相关知识&#xff0c;大家可以在评论区进行互动答疑哦~加油&#xff01;&#x1f495;&#xff09; 目录 Plug&#xff1a;程序实现 方法一&#xff08;记事本&#xff09; 方法二&#xff08;IDEA&#xff09; 实验一&am…

本地如何启动casdoor

1、下载代码 GitHub - casdoor/casdoor at v1.777.0 下载对应tag的代码&#xff0c;我这里选择的时v1.777.0版本 通过网盘分享的文件&#xff1a;casdoor-1.777.0.zip 链接: https://pan.baidu.com/s/1fPNqyJYeyfZnem_LtEc0hw 提取码: avpd 2、启动后端 1、使用goland编译…

CSDN外链失效3:

参考我之前的博客&#xff1a; 外链失效博客1&#xff1a;随想笔记1&#xff1a;CSDN写博客经常崩溃&#xff0c;遇到外链图片转存失败怎么办_csdn外链图片转存失败-CSDN博客 外链失效博客2&#xff1a;网络随想2&#xff1a;转语雀_md格式转语雀lake格式-CSDN博客 markdown…

Kubernates

kubernates是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes的目标是让部署容器化的应用简单并且高效&#xff08;powerful&#xff09;,Kubernetes提供了应用部署&#xff0c;规划&#xff0c;更新&#xff0c;维护的一种机制。 架构…

Pycharm 更改字体大小

更改代码字体的大小 更改软件字体的大小