音视频入门基础: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完整代码 十…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
