【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(二)
demux模块
从前面一篇文章中可以得知,demux模块的使用方法大致如下:
- 分配AVFormatContext
- 通过avformat_open_input(…)传入AVFormatContext指针和文件路径,启动demux
- 通过av_read_frame(…) 从AVFormatContext中读取demux后的audio/video/subtitle数据包AVPacket
AVFormatContext *ic = avformat_alloc_context();
avformat_open_input(&ic, filename, null, null);
while(1) {AVPacket *pkt = av_packet_alloc();av_read_frame(ic, pkt);..... use pkt data to do something.........;
}
在阅读源码之前,我们先提几个问题,再顺着问题阅读源码:
- AVFormatContext如何下载数据 ?
- 如何匹配到具体的demuxer ?
- demuxer的模板是什么样的? 如何新增一个demuxer ?
- demuxer是如何驱动起来的?
下面我们分别看下avformat_alloc_context(…), avformat_open_input(…), av_read_frame(…)分别做了什么?在文章结尾看看能否回答上面这几个问题。
avformat_alloc_context
ffmpeg\libavformat\option.c
AVFormatContext *avformat_alloc_context(void)
{FFFormatContext *const si = av_mallocz(sizeof(*si));AVFormatContext *s;s = &si->pub;s->av_class = &av_format_context_class;s->io_open = io_open_default;s->io_close = ff_format_io_close_default;s->io_close2= io_close2_default;av_opt_set_defaults(s);si->pkt = av_packet_alloc();si->parse_pkt = av_packet_alloc();si->shortest_end = AV_NOPTS_VALUE;return s;
}
上面代码中比较重要的函数是io_open,这个函数会创建AVIOContext数据下载模块。
int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,int flags, AVDictionary **options);
avformat_open_input
ffmpeg\libavformat\demux.c
int avformat_open_input(AVFormatContext **ps, const char *filename,const AVInputFormat *fmt, AVDictionary **options)
{AVFormatContext *s = *ps;FFFormatContext *si;AVDictionary *tmp = NULL;ID3v2ExtraMeta *id3v2_extra_meta = NULL;int ret = 0;//如果外部没有传入AVFormatContext,则在此分配if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM);FFFormatContext *si = ffformatcontext(s);//如果外部有传入AVInputFormat,则直接使用,否则后面init_input中会进行分配if (fmt)s->iformat = fmt;..........................................;//重点函数,下面会深入细节进行分析if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;s->probe_score = ret;.......................;//下面这一段代码比较关键,AVInputFormat分配之后,再来详细描述if (s->iformat->priv_data_size > 0) {if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {ret = AVERROR(ENOMEM);goto fail;}if (s->iformat->priv_class) {*(const AVClass **) s->priv_data = s->iformat->priv_class;av_opt_set_defaults(s->priv_data);if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)goto fail;}}...........................;//关键函数,用于下载第一笔数据和demux第一笔数据if (s->iformat->read_header)if ((ret = s->iformat->read_header(s)) < 0) {if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)goto close;goto fail;}}.......................;//更新codec信息,后面会详细讲解update_stream_avctx(s);......................;return 0;
}
通过上面代码可以梳理出有以下关键步骤:
- init_input() 执行后会找到数据下载具体的模块AVIOContext和具体的demux模块AVInputFormat,并进行初始化。
- 分配AVInputFormat中的priv_data_size,这个是指各各demux模块中私有的一个context结构图,如: ts格式demuxer中的struct MpegTSContext,mov格式demuxer中的struct MOVContext等
- AVInputFormat中的read_header() 会开始下载第一笔数据和demux第一笔数据
- update_stream_avctx(…) 获取并更新codec信息
下面深入分析下上面的四个步骤.
init_input
ffmpeg\libavformat\demux.c
static int init_input(AVFormatContext *s, const char *filename,AVDictionary **options)
{int ret;AVProbeData pd = { filename, NULL, 0 };int score = AVPROBE_SCORE_RETRY;//使用外部AVIOContext模块,我们不关注这种caseif (s->pb) {........................;return 0;}//这一步因为score不够会获取iformat失败if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))return score;//获取AVIOContext s->pbif ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)return ret;if (s->iformat)return 0;//在这里重新获取iformatreturn av_probe_input_buffer2(s->pb, &s->iformat, filename,s, 0, s->format_probesize);
}
下面来看下io_open和av_probe_input_buffer2的实现。
io_open赋值的位置:
ffmpeg\libavformat\option.c
AVFormatContext *avformat_alloc_context(void)
{FFFormatContext *const si = av_mallocz(sizeof(*si));AVFormatContext *s;..............;s->io_open = io_open_default;..............;
}
再来看看io_open_default的具体实现
ffmpeg\libavformat\option.c
static int io_open_default(AVFormatContext *s, AVIOContext **pb,const char *url, int flags, AVDictionary **options)
{..............................;return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}ffmpeg\libavformat\aviobuf.c
int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char *blacklist)
{URLContext *h;//获取到URLContextffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);//获取到AVIOContext ffio_fdopen(s, h);return 0;
}ffmpeg\libavformat\avio.c
int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb, AVDictionary **options,const char *whitelist, const char* blacklist,URLContext *parent)
{AVDictionary *tmp_opts = NULL;AVDictionaryEntry *e;ffurl_alloc(puc, filename, flags, int_cb);........set some options .......;ffurl_connect(*puc, options);.............;
}
// ffurl_alloc --------------- start ----------------------
ffmpeg\libavformat\avio.c
int ffurl_alloc(URLContext **puc, const char *filename, int flags,const AVIOInterruptCB *int_cb)
{const URLProtocol *p = NULL;//通过url链接找到对应的下载数据的protocolp = url_find_protocol(filename);if (p)return url_alloc_for_protocol(puc, p, filename, flags, int_cb);*puc = NULL;return AVERROR_PROTOCOL_NOT_FOUND;
}ffmpeg\libavformat\avio.c
static const struct URLProtocol *url_find_protocol(const char *filename)
{const URLProtocol **protocols;char proto_str[128], proto_nested[128], *ptr;size_t proto_len = strspn(filename, URL_SCHEME_CHARS);int i;//通过filename字符串找到proto_str,即是什么协议if (filename[proto_len] != ':' &&(strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||is_dos_path(filename))strcpy(proto_str, "file");elseav_strlcpy(proto_str, filename,FFMIN(proto_len + 1, sizeof(proto_str)));av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));if ((ptr = strchr(proto_nested, '+')))*ptr = '\0';//获取到配置好的所有URLProtocol列表//ffmpeg通过config来配置支持哪些protocol,编译之前config时会生成libavformat/protocol_list.c//里面会定义一个静态的全局数组url_protocols//static const URLProtocol * const url_protocols[] = {// &ff_http_protocol,// &ff_https_protocol,// &ff_tcp_protocol,// &ff_tls_protocol,// NULL };protocols = ffurl_get_protocols(NULL, NULL);if (!protocols)return NULL;for (i = 0; protocols[i]; i++) {const URLProtocol *up = protocols[i];//通过URLProtocol的name字段与proto_str进行匹配//如: const URLProtocol ff_http_protocol = {// .name = "http",// ........// }// const URLProtocol ff_tcp_protocol = {// .name = "tcp",// ........// }if (!strcmp(proto_str, up->name)) {av_freep(&protocols);return up;}if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&!strcmp(proto_nested, up->name)) {av_freep(&protocols);return up;}}av_freep(&protocols);if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with ""openssl, gnutls or securetransport enabled.\n");return NULL;
}得到URLProtocol后再生成URLContext
static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,const char *filename, int flags,const AVIOInterruptCB *int_cb)
{URLContext *uc;int err;...................;//分配URLContextuc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);..................;uc->av_class = &ffurl_context_class;uc->filename = (char *)&uc[1];strcpy(uc->filename, filename);uc->prot = up;uc->flags = flags;uc->is_streamed = 0; /* default = not streamed */uc->max_packet_size = 0; /* default: stream file */if (up->priv_data_size) {//分配网络协议模块自己的context结构体,如struct TCPContext,struct HTTPContextuc->priv_data = av_mallocz(up->priv_data_size);...................;if (up->priv_data_class) {char *start;*(const AVClass **)uc->priv_data = up->priv_data_class;av_opt_set_defaults(uc->priv_data);..................;}}if (int_cb)uc->interrupt_callback = *int_cb;*puc = uc;return 0;
....................;
}
至此URLContext和URLProtocol都已经得到了。其关系为URLContext.prot为其对应的URLProtocol
// ffurl_alloc --------------- end ----------------------
得到URLContext后再看看ffurl_connect做了什么?
// ffurl_connect--------------- start ----------------------
int ffurl_connect(URLContext *uc, AVDictionary **options)
{..............................;//调用具体的protocol开始下载数据err =uc->prot->url_open2 ? uc->prot->url_open2(uc,uc->filename,uc->flags,options) :uc->prot->url_open(uc, uc->filename, uc->flags);.......................;return 0;
}
// ffurl_connect--------------- end ----------------------
再来看看ffio_fdopen 如何分配AVIOContext
int ffio_fdopen(AVIOContext **s, URLContext *h)
{uint8_t *buffer = NULL;buffer = av_malloc(buffer_size);*s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,(int (*)(void *, uint8_t *, int)) ffurl_read,(int (*)(void *, uint8_t *, int)) ffurl_write,(int64_t (*)(void *, int64_t, int))ffurl_seek);(*s)->protocol_whitelist = av_strdup(h->protocol_whitelist);(*s)->protocol_blacklist = av_strdup(h->protocol_blacklist);if(h->prot) {(*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause;(*s)->read_seek =(int64_t (*)(void *, int, int64_t, int))h->prot->url_read_seek;if (h->prot->url_read_seek)(*s)->seekable |= AVIO_SEEKABLE_TIME;}((FFIOContext*)(*s))->short_seek_get = (int (*)(void *))ffurl_get_short_seek;(*s)->av_class = &ff_avio_class;return 0;
}
io_open_default总结下其主要工作:
- 通过播放文件的链接获取到具体的协议如:http/https/tcp等,然后在libavformat/protocol_list.c中定义一个静态的全局数组url_protocols遍历,通过协议名称匹配到对应的URLProtocol
- 分配生成URLContext,并通过URLProtocol.priv_data_size分配具体协议的context,如:struct TCPContext,struct HTTPContext,并将URLProtocol赋值给URLContext.proto字段。
- 取URLContext得后,调用ffurl_connect(…) 调用URLProtocol.url_open()进行初始化准备下载数据
- 通过ffio_fdopen(…) 分配AVIOContext, AVIOContext.opaque = URLContext
- 最终将AVIOContext赋值给AVFormatContext.pb 字段
下面再来看看av_probe_input_buffer2(…)函数如何找到具体的demux模块
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,const char *filename, void *logctx,unsigned int offset, unsigned int max_probe_size)
{AVProbeData pd = { filename ? filename : "" };uint8_t *buf = NULL;int ret = 0, probe_size, buf_offset = 0;int score = 0;int ret2;//从AVIOContext 中获取mime_typeif (pb->av_class) {uint8_t *mime_type_opt = NULL;char *semi;av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type_opt);pd.mime_type = (const char *)mime_type_opt;semi = pd.mime_type ? strchr(pd.mime_type, ';') : NULL;if (semi) {*semi = '\0';}}for (probe_size = PROBE_BUF_MIN; probe_size <= max_probe_size && !*fmt;probe_size = FFMIN(probe_size << 1,FFMAX(max_probe_size, probe_size + 1))) {score = probe_size < max_probe_size ? AVPROBE_SCORE_RETRY : 0;/* Read probe data. */if ((ret = av_reallocp(&buf, probe_size + AVPROBE_PADDING_SIZE)) < 0)goto fail;//读取probe数据if ((ret = avio_read(pb, buf + buf_offset,probe_size - buf_offset)) < 0) {.......................;}.................;/* Guess file format. */*fmt = av_probe_input_format2(&pd, 1, &score);...................;}.....................;
}const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,int is_opened, int *score_max)
{int score_ret;const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);if (score_ret > *score_max) {*score_max = score_ret;return fmt;} elsereturn NULL;
}const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,int is_opened, int *score_ret)
{AVProbeData lpd = *pd;const AVInputFormat *fmt1 = NULL;const AVInputFormat *fmt = NULL;int score, score_max = 0;//通过从AVIOContext中读取的probe buffer来判断nodat值if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {if (lpd.buf_size > id3len + 16) {nodat = ID3_ALMOST_GREATER_PROBE;} else if (id3len >= PROBE_BUF_MAX) {nodat = ID3_GREATER_MAX_PROBE;} elsenodat = ID3_GREATER_PROBE;}//遍历所有的demuxer,获取得分最高的一个while ((fmt1 = av_demuxer_iterate(&i))) {score = 0;if (fmt1->read_probe) {score = fmt1->read_probe(&lpd);if (score)av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {switch (nodat) {case NO_ID3:score = FFMAX(score, 1);break;case ID3_GREATER_PROBE:case ID3_ALMOST_GREATER_PROBE:score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);break;case ID3_GREATER_MAX_PROBE:score = FFMAX(score, AVPROBE_SCORE_EXTENSION);break;}}} else if (fmt1->extensions) {if (av_match_ext(lpd.filename, fmt1->extensions))score = AVPROBE_SCORE_EXTENSION;}if (av_match_name(lpd.mime_type, fmt1->mime_type)) {score = AVPROBE_SCORE_MIME;}}if (score > score_max) {score_max = score;fmt = fmt1;} else if (score == score_max)fmt = NULL;}if (nodat == ID3_GREATER_PROBE)score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);*score_ret = score_max;return fmt;
}
从上面的代码逻辑可以大致看出,选取demuxer的逻辑:
- 通过ibavformat/demuxer_list.c中配置定义的全局数组demuxer_list,如
static const AVInputFormat * const demuxer_list[] = {
&ff_flac_demuxer,
&ff_hls_demuxer,
&ff_matroska_demuxer,
&ff_mov_demuxer,
&ff_mp3_demuxer,
&ff_mpegts_demuxer,
&ff_ogg_demuxer,
&ff_wav_demuxer,
NULL };
- 从AVIOContext中获取到数据的prop如: mime_type, 后缀名以及其他prop与每一个AVInputFormat进行匹配,获取一个得分最高的AVInputFormat
AVFormatContext中的priv_data_size
再来重新看下avformat_open_input()中这段代码的含义
// s 为AVFormatContext, s->iformat为AVInputFormat类型if (s->iformat->priv_data_size > 0) {if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {ret = AVERROR(ENOMEM);goto fail;}if (s->iformat->priv_class) {*(const AVClass **) s->priv_data = s->iformat->priv_class;av_opt_set_defaults(s->priv_data);if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)goto fail;}}
以ff_mpegts_demuxer为例子,s->iformat->priv_data_size为struct MpegTSContext,因此av_mallocz(s->iformat->priv_data_size)实际分配了一个struct MpegTSContext。
*(const AVClass **) s->priv_data = s->iformat->priv_class;
这句话的实际含义是MpegTSContext.class = s->iformat->priv_class, 即为AVClass mpegts_class
从这里可以看出MpegTSContext 与AVInputFormat之间的关系,AVInputFormat提供demuxer统一接口,MpegTSContext为demuxer接口提供不同的操作上下文。
struct MpegTSContext {const AVClass *class; //这个成员必须是第一个/* user data */AVFormatContext *stream;/** raw packet size, including FEC if present */int raw_packet_size;...........;
};const AVInputFormat ff_mpegts_demuxer = {.name = "mpegts",.long_name = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),.priv_data_size = sizeof(MpegTSContext),.read_probe = mpegts_probe,.read_header = mpegts_read_header,.read_packet = mpegts_read_packet,.read_close = mpegts_read_close,.read_timestamp = mpegts_get_dts,.flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,.priv_class = &mpegts_class,
};static const AVClass mpegts_class = {.class_name = "mpegts demuxer",.item_name = av_default_item_name,.option = options,.version = LIBAVUTIL_VERSION_INT,
};
AVInputFormat中的read_header
下面再来看看avformat_open_input(…)中的read_header干了什么?
if (s->iformat->read_header)//s 为AVFormatContext, s->iformat为AVInputFormatif ((ret = s->iformat->read_header(s)) < 0) {if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)goto close;goto fail;}const AVInputFormat ff_mpegts_demuxer = {.name = "mpegts",.........................;.read_header = mpegts_read_header,.read_packet = mpegts_read_packet,..................;
};
以ff_mpegts_demuxer为例子,看看read_header具体做了什么?
static int mpegts_read_header(AVFormatContext *s)
{MpegTSContext *ts = s->priv_data;AVIOContext *pb = s->pb;.................;if (s->iformat == &ff_mpegts_demuxer) {seek_back(s, pb, pos);mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);mpegts_open_section_filter(ts, EIT_PID, eit_cb, ts, 1);handle_packets(ts, probesize / ts->raw_packet_size);/* if could not find service, enable auto_guess */ts->auto_guess = 1;av_log(ts->stream, AV_LOG_TRACE, "tuning done\n");s->ctx_flags |= AVFMTCTX_NOHEADER;} else {.......................;}seek_back(s, pb, pos);return 0;
}
具体再看下handle_packets函数,
static int handle_packets(MpegTSContext *ts, int64_t nb_packets)
{AVFormatContext *s = ts->stream;uint8_t packet[TS_PACKET_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];const uint8_t *data;int64_t packet_num;int ret = 0;..............................;ts->stop_parse = 0;packet_num = 0;memset(packet + TS_PACKET_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);for (;;) {packet_num++;if (nb_packets != 0 && packet_num >= nb_packets ||ts->stop_parse > 1) {ret = AVERROR(EAGAIN);break;}if (ts->stop_parse > 0)break;ret = read_packet(s, packet, ts->raw_packet_size, &data);if (ret != 0)break;ret = handle_packet(ts, data, avio_tell(s->pb));finished_reading_packet(s, ts->raw_packet_size);if (ret != 0)break;}ts->last_pos = avio_tell(s->pb);return ret;
}
主要看下read_packet 和 handle_packet```c
static int read_packet(AVFormatContext *s, uint8_t *buf, int raw_packet_size,const uint8_t **data)
{*AVIOContext *pb = s->pb*;int len;for (;;) {len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);if (len != TS_PACKET_SIZE)return len < 0 ? len : AVERROR_EOF;/* check packet sync byte */if ((*data)[0] != 0x47) {/* find a new packet start */if (mpegts_resync(s, raw_packet_size, *data) < 0)return AVERROR(EAGAIN);elsecontinue;} else {break;}}return 0;
}
int ffio_read_indirect(AVIOContext *s, unsigned char *buf, int size, const unsigned char **data)
{if (s->buf_end - s->buf_ptr >= size && !s->write_flag) {*data = s->buf_ptr;s->buf_ptr += size;return size;} else {*data = buf;return avio_read(s, buf, size);}
}
从上面的代码可以看出read_packet 是通过AVFormatContext.pb即AVIOContext 通过avio_read读取一个pkt。
handle_packet函数暂时还看不懂,先放着,后面再深入分析。
/* handle one TS packet */
static int handle_packet(MpegTSContext *ts, const uint8_t *packet, int64_t pos)
{MpegTSFilter *tss;int len, pid, cc, expected_cc, cc_ok, afc, is_start, is_discontinuity,has_adaptation, has_payload;const uint8_t *p, *p_end;pid = AV_RB16(packet + 1) & 0x1fff;is_start = packet[1] & 0x40;tss = ts->pids[pid];if (ts->auto_guess && !tss && is_start) {add_pes_stream(ts, pid, -1);tss = ts->pids[pid];}if (!tss)return 0;if (is_start)tss->discard = discard_pid(ts, pid);if (tss->discard)return 0;ts->current_pid = pid;afc = (packet[3] >> 4) & 3;if (afc == 0) /* reserved value */return 0;has_adaptation = afc & 2;has_payload = afc & 1;is_discontinuity = has_adaptation &&packet[4] != 0 && /* with length > 0 */(packet[5] & 0x80); /* and discontinuity indicated *//* continuity check (currently not used) */cc = (packet[3] & 0xf);expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;cc_ok = pid == 0x1FFF || // null packet PIDis_discontinuity ||tss->last_cc < 0 ||expected_cc == cc;tss->last_cc = cc;if (!cc_ok) {av_log(ts->stream, AV_LOG_DEBUG,"Continuity check failed for pid %d expected %d got %d\n",pid, expected_cc, cc);if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}if (packet[1] & 0x80) {av_log(ts->stream, AV_LOG_DEBUG, "Packet had TEI flag set; marking as corrupt\n");if (tss->type == MPEGTS_PES) {PESContext *pc = tss->u.pes_filter.opaque;pc->flags |= AV_PKT_FLAG_CORRUPT;}}p = packet + 4;if (has_adaptation) {int64_t pcr_h;int pcr_l;if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)tss->last_pcr = pcr_h * 300 + pcr_l;/* skip adaptation field */p += p[0] + 1;}/* if past the end of packet, ignore */p_end = packet + TS_PACKET_SIZE;if (p >= p_end || !has_payload)return 0;if (pos >= 0) {av_assert0(pos >= TS_PACKET_SIZE);ts->pos47_full = pos - TS_PACKET_SIZE;}if (tss->type == MPEGTS_SECTION) {if (is_start) {/* pointer field present */len = *p++;if (len > p_end - p)return 0;if (len && cc_ok) {/* write remaining section bytes */write_section_data(ts, tss,p, len, 0);/* check whether filter has been closed */if (!ts->pids[pid])return 0;}p += len;if (p < p_end) {write_section_data(ts, tss,p, p_end - p, 1);}} else {if (cc_ok) {write_section_data(ts, tss,p, p_end - p, 0);}}// stop find_stream_info from waiting for more streams// when all programs have received a PMTif (ts->stream->ctx_flags & AVFMTCTX_NOHEADER && ts->scan_all_pmts <= 0) {int i;for (i = 0; i < ts->nb_prg; i++) {if (!ts->prg[i].pmt_found)break;}if (i == ts->nb_prg && ts->nb_prg > 0) {int types = 0;for (i = 0; i < ts->stream->nb_streams; i++) {AVStream *st = ts->stream->streams[i];if (st->codecpar->codec_type >= 0)types |= 1<<st->codecpar->codec_type;}if ((types & (1<<AVMEDIA_TYPE_AUDIO) && types & (1<<AVMEDIA_TYPE_VIDEO)) || pos > 100000) {av_log(ts->stream, AV_LOG_DEBUG, "All programs have pmt, headers found\n");ts->stream->ctx_flags &= ~AVFMTCTX_NOHEADER;}}}} 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;
}
update_stream_avctx
static int update_stream_avctx(AVFormatContext *s)
{int ret;for (unsigned i = 0; i < s->nb_streams; i++) {AVStream *const st = s->streams[i];FFStream *const sti = ffstream(st);.......................;ret = avcodec_parameters_to_context(sti->avctx, st->codecpar);sti->need_context_update = 0;}return 0;
}
//将AVStream中的codecpar信息赋值给FFStream中的AVCodecContext
int avcodec_parameters_to_context(AVCodecContext *codec,const AVCodecParameters *par)
{int ret;codec->codec_type = par->codec_type;codec->codec_id = par->codec_id;codec->codec_tag = par->codec_tag;codec->bit_rate = par->bit_rate;codec->bits_per_coded_sample = par->bits_per_coded_sample;codec->bits_per_raw_sample = par->bits_per_raw_sample;codec->profile = par->profile;codec->level = par->level;switch (par->codec_type) {case AVMEDIA_TYPE_VIDEO:codec->pix_fmt = par->format;codec->width = par->width;.............;break;case AVMEDIA_TYPE_AUDIO:codec->sample_fmt = par->format;codec->sample_rate = par->sample_rate;codec->block_align = par->block_align;codec->frame_size = par->frame_size;..............;break;case AVMEDIA_TYPE_SUBTITLE:codec->width = par->width;codec->height = par->height;break;}......................;return 0;
}
av_read_frame
int av_read_frame(AVFormatContext *s, AVPacket *pkt)
{FFFormatContext *const si = ffformatcontext(s);const int genpts = s->flags & AVFMT_FLAG_GENPTS;int eof = 0;int ret;AVStream *st;.........................;for (;;) {.....................;read_frame_internal(s, pkt);.....................;}........................;return ret;
}
static int read_frame_internal(AVFormatContext *s, AVPacket *pkt)
{FFFormatContext *const si = ffformatcontext(s);int ret, got_packet = 0;AVDictionary *metadata = NULL;while (!got_packet && !si->parse_queue.head) {AVStream *st;FFStream *sti;/* read next packet */ret = ff_read_packet(s, pkt);..............................;}return ret;
}int ff_read_packet(AVFormatContext *s, AVPacket *pkt)
{FFFormatContext *const si = ffformatcontext(s);int err;for (;;) {PacketListEntry *pktl = si->raw_packet_buffer.head;AVStream *st;FFStream *sti;const AVPacket *pkt1;...................;err = s->iformat->read_packet(s, pkt);......................;}......................;
}
以MpegTSContext为例子,看看pkt是如何存储,如何被读出来的```c
static int mpegts_read_packet(AVFormatContext *s, AVPacket *pkt)
{MpegTSContext *ts = s->priv_data;int ret, i;pkt->size = -1;ts->pkt = pkt;//这个函数中会做具体的demux动作ret = handle_packets(ts, 0);..........................;return ret;
}
static int handle_packets(MpegTSContext *ts, int64_t nb_packets)
{AVFormatContext *s = ts->stream;uint8_t packet[TS_PACKET_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];const uint8_t *data;int64_t packet_num;int ret = 0;..........................;ts->stop_parse = 0;packet_num = 0;memset(packet + TS_PACKET_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);for (;;) {packet_num++;if (nb_packets != 0 && packet_num >= nb_packets ||ts->stop_parse > 1) {ret = AVERROR(EAGAIN);break;}if (ts->stop_parse > 0)break;//从网络io中读取未demux的数据,存储在data变量中ret = read_packet(s, packet, ts->raw_packet_size, &data);if (ret != 0)break;//在此函数中将data中的数据demux之后,存放至MpegTSContext.pktret = handle_packet(ts, data, avio_tell(s->pb));finished_reading_packet(s, ts->raw_packet_size);if (ret != 0)break;}ts->last_pos = avio_tell(s->pb);return ret;
}
问题解答
-
AVFormatContext如何下载数据 ?
下图中蓝色线路图表示数据下载过程,即通过AVIOContext下载。 -
如何匹配到具体的demuxer ?
读取media数据开头一段数据,遍历libavformat/demuxer_list.c中配置定义的全局数组demuxer_list,将此数据通过每一个demuer的read_probe(…)解析匹配得到一个socre或者通过mime_type和文件后缀名得到socre, 取socre最高的一个作为当前数据的demuxer模块。 -
demuxer的模板是什么样的? 如何新增一个demuxer ?
在libavformat/demuxer_list.c中增加一个ff_yxyts_demuxer
如:
static const AVInputFormat * const demuxer_list[] = {
&ff_flac_demuxer,
&ff_hls_demuxer,
&ff_yxyts_demuxer,
NULL };
struct YxyTSContext {const AVClass *class; //这个成员必须是第一个/* user data */AVFormatContext *stream;/** raw packet size, including FEC if present */int raw_packet_size;...........;
};
const AVInputFormat ff_yxyts_demuxer = {.name = "mpegts",.long_name = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),.priv_data_size = sizeof(YxyTSContext),.read_probe = yxyts_probe,.read_header = yxyts_read_header,.read_packet = yxyts_read_packet,.read_close = yxyts_read_close,.read_timestamp = yxyts_get_dts,.flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,.priv_class = &yxyts_class,
};
- demuxer是如何驱动起来的?
从下图中可以看出,需要上层应用主动调用av_read_frame(…)获取源数据并demux,然后将demux出来的audio/video的info信息存入AVStream中,具体的audio/video数据通过AVPacket返回给上层应用。
相关文章:

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(二)
demux模块 从前面一篇文章中可以得知,demux模块的使用方法大致如下: 分配AVFormatContext通过avformat_open_input(…)传入AVFormatContext指针和文件路径,启动demux通过av_read_frame(…) 从AVFormatContext中读取demux后的audio/video/subtitle数据包…...

PCIE 学习笔记(入门简介)
PCIE 学习笔记书到用时方恨少啊,一年前学PCIE的笔记,再拿出来瞅瞅。发到博客上,方便看。PCIE基础PCIE和PCI的不同PCIE采用差分信号传输,并且是dual-simplex传输——每条lane上有TX通道和RX通道,所以每条lane上的信号是…...

锁的优化机制了解嘛?请进!
点个关注,必回关 文章目录自旋锁:自适应锁:锁消除:锁粗化:偏向锁:轻量级锁:从JDK1.6版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的…...

5.点赞功能 Redis
Redis(1)简介Redis 是一个高性能的 key-value 数据库原子 – Redis的所有操作都是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。非关系形数据库数据全部存在内存中,性能高。(2&#…...
Java序列化和反序列化(详解)
一、理解Java序列化和反序列化 Serialization(序列化):将java对象以一连串的字节保存在磁盘文件中的过程,也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。 deserialization(反序列化):将保存在磁…...

【刷题篇】链表(上)
前言🌈前段时间我们学习了单向链表和双向链表,本期将带来3道与链表相关的OJ题来巩固对链表的理解。话不多说,让我们进入今天的题目吧!🚀本期的题目有:反转单链表、链表的中间结点、合并两个有序链表反转单链…...
ConcurrentHashMap设计思路
ConcurrentHashMap设计思路Hashtable vs ConcurrentHashMapHashtable vs ConcurrentHashMap Hashtable 对比 ConcurrentHashMap Hashtable 与 ConcurrentHashMap 都是线程安全的 Map 集合Hashtable 并发度低,整个 Hashtable 对应一把锁,同一时刻&#…...

Unity基于GraphView的行为树编辑器
这里写自定义目录标题概述基于GitHub上:目前这只是做了一些比较基础的功能节点开发,仅仅用于学习交流,非完成品。项目GitHub连接:[https://github.com/HengyuanLee/BehaviorTreeExamples](https://github.com/HengyuanLee/Behavio…...

网络流量传输MTU解析
基本概念 以太网的链路层对数据帧的长度会有一个限制,其最大值默认是1500字节,链路层的这个特性称为MTU,即最大传输单元 Maximum Transmission Unit,最大传输单元,指的是数据链路层的最大payload,由硬件网…...
30个HTML+CSS前端开发案例(四)
30个HTMLCSS前端开发案例(17-20)鼠标移入文字加载动画效果代码实现效果鼠标悬停缩放效果实现代码效果鼠标移入旋转动画实现代码效果loding加载动画实现代码效果资源包鼠标移入文字加载动画效果 代码实现 <!DOCTYPE html> <html><head&g…...
《TPM原理及应用指南》学习 —— TPM执行环境3
本文对应《A Practical Guide to TPM 2.0 — Using the Trusted Platform Module in the New Age of Security》的第6章第3节。 6.3 Summary —— 总结 Now that you have an execution environment (or maybe both of them) set up, you’re ready to run the code samples f…...

实验名称:经典同步问题:生成者与消费者问题
实验名称:经典同步问题:生成者与消费者问题 相关知识 信号量 信号量是用来协调不同进程间的数据对象,可用来保护共享资源,也能用来实现进程间及同一进程不同线程间的进程同步。分为二值信号灯和计算信号灯两种类型。 进程与线…...

EasyCVR视频云存储的架构解析与Sharelist云存挂载方法介绍
一、什么是视频云存储? 视频云存储主要用于为上层应用提供视频文件、结构化信息、事件信息的相关服务。云存储节点分为数据文件存储节点和结构化数据存储节点。数据文件存储节点主要用于视频、图片的存储。结构化数据存储节点用于存储结构化数据并提供相关服务。 …...

电机参数中力矩单位kgf.cm,Nm,mNm表示的含义
力的基本知识 质量和力的比例系数 质量和重力的关系有一个重力系数:g≈9.8 N/kg≈10,后面看到的1kgf就相当于1kg物体的力也就是10N 杠杆原理 对于同一个支点,在不考虑杠杆的重量的情况下,实现同样的作用效果,距离支点越近&…...

使用scikit-learn为PyTorch 模型进行超参数网格搜索
scikit-learn是Python中最好的机器学习库,而PyTorch又为我们构建模型提供了方便的操作,能否将它们的优点整合起来呢?在本文中,我们将介绍如何使用 scikit-learn中的网格搜索功能来调整 PyTorch 深度学习模型的超参数: 如何包装 P…...

Windeployqt 打包,缺少dll 的解决方法
Windeployqt 打包,缺少DLL 的原因分析,解决方法 很多同学使用工具windeployqt进行打包发布后,运行exe文件时,还是会出现下图所示的系统错误提示,这种情况就表示相关的DLL 库文件没有被正确打包。可是windeployqt明确显…...

第四章:搭建Windows server AD域和树域
由于Windows简单一点,我就先搞Windows了。AD域:视频教程:https://www.bilibili.com/video/BV1f84y1G72x/在创建AD域时要把网卡配置好这是打开网卡界面的命令DNS要改成自己的,因为在创建域的同时也会自动创建DNS打开服务器管理器&a…...

【解决方案】老旧小区升级改造,视频智能化能力如何提升居民安全感?
一、需求背景 随着我国社会经济的快速发展与进步,城市宜居程度成为城市发展的重要指标,城市的发展面临着更新、改造和宜居建设等。一方面,社区居民对生活的环境提出了更高的要求;另一方面,将“智慧城市”的概念引入社…...

【遇见青山】项目难点:缓存穿透的解决方案
【遇见青山】项目难点:缓存穿透的解决方案1.缓存穿透现象缓存空对象布隆过滤其他方案2.解决方案,缓存空数据1.缓存穿透现象 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据…...
单一职责原则|SOLID as a rock
文章目录 意图动机:违反单一职责原则解决方案:C++中单一职责原则的例子单一职责的优点1、可理解性2、可维护性3、可复用性在C++中用好SRP的标准总结本文是关于 SOLID as Rock 设计原则系列的五部分中的第一部分。 SOLID 设计原则侧重于开发 易于维护、可重用和可扩展的软件。…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...