音视频入门基础: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表后,会得到该节目包含的视频和音频信息,从而找到音视频流。TS流的音视频流包含在PES流中。FFmpeg源码通过调用函数指针tss->u.pes_filter.pes_cb指向的回调函数解析PES流的PES packet: /* handle one TS…...

tomcat的安装以及配置(基于linuxOS)
目录 安装jdk环境 yum安装 验证JDK环境 安装tomcat应用 yum安装 编辑 使用yum工具进行安装 配置tomcat应用 关闭防火墙和selinux 查看端口开启情况 编辑 访问tomcat服务 安装扩展包 重启服务 查看服务 源码安装 进入tomcat官网进行下载 查找自己要用的to…...
因子分解(递归)
1.素分解式(简单版) 任务描述 编写函数,输出一个正整数的素数分解式。主函数的功能为输入若干正整数(大于1),输出每一个数的素分解式。素数分解式是指将整数写成若干素数(从小到大)乘积的形式。例如: 202*2*5 362*2*…...

【Python】pandas库---数据分析
大学毕业那年,你成了社会底层群众里,受教育程度最高的一批人。 前言 这是我自己学习Python的第四篇博客总结。后期我会继续把Python学习笔记开源至博客上。 上一期笔记有关Python的NumPy数据分析,没看过的同学可以去看看:【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
最近天冷,打印机预热突然失败,热床无法加热,过了一段时间报错err6,查看另一篇资料说是天气冷原因,导致代码的PID控温部分达不到预期加热效果,从而自检报错,然后资料通过修改3D打印机代码的方式进…...
基于 MATLAB 的图像增强技术分享
一、引言 图像增强是数字图像处理中的重要环节,其目的在于改善图像的视觉效果,使图像更清晰、细节更丰富、对比度更高,以便于后续的分析、识别与理解等任务。MATLAB 作为一款功能强大的科学计算软件,提供了丰富的图像处理工具和函…...

前端知识补充—HTML
1. HTML 1.1 什么是HTML HTML(Hyper Text Markup Language), 超⽂本标记语⾔ 超⽂本: ⽐⽂本要强⼤. 通过链接和交互式⽅式来组织和呈现信息的⽂本形式. 不仅仅有⽂本, 还可能包含图⽚, ⾳频, 或者⾃已经审阅过它的学者所加的评注、补充或脚注等等 标记语⾔: 由标签构成的语⾔…...
安卓从Excel文件导入数据到SQLite数据库的实现
在现代的移动应用开发中,数据的处理和管理是至关重要的一环。有时候,我们需要从外部文件(如Excel文件)中导入数据,以便在应用程序中使用。本文将介绍如何在Android应用中使用Java代码从一个Excel文件中导入数据到SQLit…...
C/C++基础知识复习(44)
1) C 中多态性在实际项目中的应用场景 多态性是面向对象编程(OOP)中的一个重要特性,指的是不同的对象可以通过相同的接口来表现不同的行为。在 C 中,多态通常通过虚函数(virtual)和继承机制来实现。实际项…...

【day13】深入面向对象编程
【day12】回顾 在正文开始之前,先让我们回顾一下【day12】中的关键内容: 接口(Interface): interface关键字用于定义接口。implements关键字用于实现接口。 接口成员: 抽象方法:需要在实现类中…...
《 火星人 》
题目描述 人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这…...
盒子模型(内边距的设置)
所有元素都可以设置内边距属性和外边距属性大体相同,可参考上一篇,但有区别 内边距不能设置为负值padding-方向:尺寸 注意:使用内边距padding之后元素整体会变大,因为他是直接加上了内边距的大小,不改变元素…...

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

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

最优二叉搜索树【东北大学oj数据结构10-4】C++
题面 最优二叉搜索树是由 n 个键和 n1 个虚拟键构造的二叉搜索树,以最小化搜索操作的成本期望值。 给定一个序列 Kk1,k2,...,kn,其中 n 个不同的键按排序顺序 ,我们希望构造一个二叉搜索树。 对于每个关键 ki,我们有一个…...
ESP32应用开发-Webserver
文章目录 库调用实例实现思路技术要点 1. 前端涉及的文件需要包装再发送2. http-GET路由3. http-POST路由 开发环境:Arduino 库调用 #include <WebServer.h> #include <ArduinoJson.h> //IDE没有自带,需自行安装实例 WebServer server…...

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

前端开发 之 12个鼠标交互特效下【附完整源码】
前端开发 之 12个鼠标交互特效下【附完整源码】 文章目录 前端开发 之 12个鼠标交互特效下【附完整源码】七:粒子烟花绽放特效1.效果展示2.HTML完整代码 八:彩球释放特效1.效果展示2.HTML完整代码 九:雨滴掉落特效1.效果展示2.HTML完整代码 十…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...