当前位置: 首页 > news >正文

音视频入门基础: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;}

相关文章:

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

一、引言 FFmpeg源码在解析完PMT表后&#xff0c;会得到该节目包含的视频和音频信息&#xff0c;从而找到音视频流。TS流的音视频流包含在PES流中。FFmpeg源码通过调用函数指针tss->u.pes_filter.pes_cb指向的回调函数解析PES流的PES packet&#xff1a; /* handle one TS…...

tomcat的安装以及配置(基于linuxOS)

目录 安装jdk环境 yum安装 验证JDK环境 安装tomcat应用 yum安装 ​编辑 使用yum工具进行安装 配置tomcat应用 关闭防火墙和selinux 查看端口开启情况 ​编辑 访问tomcat服务 安装扩展包 重启服务 查看服务 源码安装 进入tomcat官网进行下载 查找自己要用的to…...

因子分解(递归)

1.素分解式(简单版) 任务描述 编写函数&#xff0c;输出一个正整数的素数分解式。主函数的功能为输入若干正整数&#xff08;大于1&#xff09;&#xff0c;输出每一个数的素分解式。素数分解式是指将整数写成若干素数(从小到大)乘积的形式。例如&#xff1a; 202*2*5 362*2*…...

【Python】pandas库---数据分析

大学毕业那年&#xff0c;你成了社会底层群众里&#xff0c;受教育程度最高的一批人。 前言 这是我自己学习Python的第四篇博客总结。后期我会继续把Python学习笔记开源至博客上。 上一期笔记有关Python的NumPy数据分析&#xff0c;没看过的同学可以去看看&#xff1a;【Pyt…...

RabbitMQ 的7种工作模式

RabbitMQ 共提供了7种⼯作模式,进⾏消息传递,. 官⽅⽂档:RabbitMQ Tutorials | RabbitMQ 1.Simple(简单模式) P:⽣产者,也就是要发送消息的程序 C:消费者,消息的接收者 Queue:消息队列,图中⻩⾊背景部分.类似⼀个邮箱,可以缓存消息;⽣产者向其中投递消息,消费者从其中取出消息…...

负载均衡式在线OJ

文章目录 项目介绍所用技术与开发环境所用技术开发环境 项目框架compiler_server模块compiler编译功能comm/util.hpp 编译时的临时文件comm/log.hpp 日志comm/util.hpp 时间戳comm/util.hpp 检查文件是否存在compile_server/compiler.hpp 编译功能总体编写 runner运行功能资源设…...

【3D打印机】启庞KP3S热床加热失败报错err6

最近天冷&#xff0c;打印机预热突然失败&#xff0c;热床无法加热&#xff0c;过了一段时间报错err6&#xff0c;查看另一篇资料说是天气冷原因&#xff0c;导致代码的PID控温部分达不到预期加热效果&#xff0c;从而自检报错&#xff0c;然后资料通过修改3D打印机代码的方式进…...

基于 MATLAB 的图像增强技术分享

一、引言 图像增强是数字图像处理中的重要环节&#xff0c;其目的在于改善图像的视觉效果&#xff0c;使图像更清晰、细节更丰富、对比度更高&#xff0c;以便于后续的分析、识别与理解等任务。MATLAB 作为一款功能强大的科学计算软件&#xff0c;提供了丰富的图像处理工具和函…...

前端知识补充—HTML

1. HTML 1.1 什么是HTML HTML(Hyper Text Markup Language), 超⽂本标记语⾔ 超⽂本: ⽐⽂本要强⼤. 通过链接和交互式⽅式来组织和呈现信息的⽂本形式. 不仅仅有⽂本, 还可能包含图⽚, ⾳频, 或者⾃已经审阅过它的学者所加的评注、补充或脚注等等 标记语⾔: 由标签构成的语⾔…...

安卓从Excel文件导入数据到SQLite数据库的实现

在现代的移动应用开发中&#xff0c;数据的处理和管理是至关重要的一环。有时候&#xff0c;我们需要从外部文件&#xff08;如Excel文件&#xff09;中导入数据&#xff0c;以便在应用程序中使用。本文将介绍如何在Android应用中使用Java代码从一个Excel文件中导入数据到SQLit…...

C/C++基础知识复习(44)

1) C 中多态性在实际项目中的应用场景 多态性是面向对象编程&#xff08;OOP&#xff09;中的一个重要特性&#xff0c;指的是不同的对象可以通过相同的接口来表现不同的行为。在 C 中&#xff0c;多态通常通过虚函数&#xff08;virtual&#xff09;和继承机制来实现。实际项…...

【day13】深入面向对象编程

【day12】回顾 在正文开始之前&#xff0c;先让我们回顾一下【day12】中的关键内容&#xff1a; 接口&#xff08;Interface&#xff09;&#xff1a; interface关键字用于定义接口。implements关键字用于实现接口。 接口成员&#xff1a; 抽象方法&#xff1a;需要在实现类中…...

《 火星人 》

题目描述 人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言&#xff0c;但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的&#xff0c;首先&#xff0c;火星人把一个非常大的数字告诉人类科学家&#xff0c;科学家破解这…...

盒子模型(内边距的设置)

所有元素都可以设置内边距属性和外边距属性大体相同&#xff0c;可参考上一篇&#xff0c;但有区别 内边距不能设置为负值padding-方向&#xff1a;尺寸 注意&#xff1a;使用内边距padding之后元素整体会变大&#xff0c;因为他是直接加上了内边距的大小&#xff0c;不改变元素…...

CentOS7网络配置,解决不能联网、ping不通外网、主机的问题

1. 重置 关闭Centos系统 编辑->虚拟网络编辑器 还原默认设置 2. 记录基本信息 查看网关地址,并记录在小本本上 查看网段,记录下 3. 修改网卡配置 启动Centos系统 非root用户,切换root su root查看Mac地址 ifconfig 或 ip addr记录下来 修改配置文件 vim /et…...

如何测继电器是否正常

继电器是一种电控制器件&#xff0c;广泛应用于自动控制、电力保护等领域。为了确保继电器的正常工作&#xff0c;定期检测其状态是非常必要的。以下是一些常用的方法来测试继电器是否正常工作&#xff1a; 1. 视觉检查&#xff1a; - 观察继电器的外观是否有损坏、变形或烧焦…...

最优二叉搜索树【东北大学oj数据结构10-4】C++

题面 最优二叉搜索树是由 n 个键和 n1 个虚拟键构造的二叉搜索树&#xff0c;以最小化搜索操作的成本期望值。 给定一个序列 Kk1​,k2​,...,kn​&#xff0c;其中 n 个不同的键按排序顺序 &#xff0c;我们希望构造一个二叉搜索树。 对于每个关键 ki​&#xff0c;我们有一个…...

ESP32应用开发-Webserver

文章目录 库调用实例实现思路技术要点 1. 前端涉及的文件需要包装再发送2. http-GET路由3. http-POST路由 开发环境&#xff1a;Arduino 库调用 #include <WebServer.h> #include <ArduinoJson.h> //IDE没有自带&#xff0c;需自行安装实例 WebServer server…...

【IMU:视觉惯性SLAM系统】

视觉惯性SLAM系统简介 相机&#xff08;单目/双目/RGBD)与IMU结合起来就是视觉惯性&#xff0c;通常以单目/双目IMU为主。 IMU里面有个小芯片可以测量角速度与加速度&#xff0c;可分为6轴(6个自由度)和9轴&#xff08;9个自由度&#xff09;IMU&#xff0c;具体的关于IMU的介…...

前端开发 之 12个鼠标交互特效下【附完整源码】

前端开发 之 12个鼠标交互特效下【附完整源码】 文章目录 前端开发 之 12个鼠标交互特效下【附完整源码】七&#xff1a;粒子烟花绽放特效1.效果展示2.HTML完整代码 八&#xff1a;彩球释放特效1.效果展示2.HTML完整代码 九&#xff1a;雨滴掉落特效1.效果展示2.HTML完整代码 十…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案

在大数据时代&#xff0c;海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构&#xff0c;在处理大规模数据抓取任务时展现出强大的能力。然而&#xff0c;随着业务规模的不断扩大和数据抓取需求的日益复杂&#xff0c;传统…...

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决

问题&#xff1a; pgsql数据库通过备份数据库文件进行还原时&#xff0c;如果表中有自增序列&#xff0c;还原后可能会出现重复的序列&#xff0c;此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”&#xff0c;…...

数据库——redis

一、Redis 介绍 1. 概述 Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的、高性能的内存键值数据库系统&#xff0c;具有以下核心特点&#xff1a; 内存存储架构&#xff1a;数据主要存储在内存中&#xff0c;提供微秒级的读写响应 多数据结构支持&…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

持续交付的进化:从DevOps到AI驱动的IT新动能

文章目录 一、持续交付的本质&#xff1a;从手动到自动的交付飞跃关键特性案例&#xff1a;电商平台的高效部署 二、持续交付的演进&#xff1a;从CI到AI驱动的未来发展历程 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/101f72defaf3493ba0ba376bf09367a2.png)中国…...