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

杭州论坛网站建设/公司开发设计推荐

杭州论坛网站建设,公司开发设计推荐,wordpress没人维护了,网站建好用电脑做服务器demux模块 从前面一篇文章中可以得知,demux模块的使用方法大致如下: 分配AVFormatContext通过avformat_open_input(…)传入AVFormatContext指针和文件路径,启动demux通过av_read_frame(…) 从AVFormatContext中读取demux后的audio/video/subtitle数据包…

demux模块

从前面一篇文章中可以得知,demux模块的使用方法大致如下:

  1. 分配AVFormatContext
  2. 通过avformat_open_input(…)传入AVFormatContext指针和文件路径,启动demux
  3. 通过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.........;
}

在阅读源码之前,我们先提几个问题,再顺着问题阅读源码:

  1. AVFormatContext如何下载数据 ?
  2. 如何匹配到具体的demuxer ?
  3. demuxer的模板是什么样的? 如何新增一个demuxer ?
  4. 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;
}

通过上面代码可以梳理出有以下关键步骤:

  1. init_input() 执行后会找到数据下载具体的模块AVIOContext和具体的demux模块AVInputFormat,并进行初始化。
  2. 分配AVInputFormat中的priv_data_size,这个是指各各demux模块中私有的一个context结构图,如: ts格式demuxer中的struct MpegTSContext,mov格式demuxer中的struct MOVContext等
  3. AVInputFormat中的read_header() 会开始下载第一笔数据和demux第一笔数据
  4. 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总结下其主要工作:

  1. 通过播放文件的链接获取到具体的协议如:http/https/tcp等,然后在libavformat/protocol_list.c中定义一个静态的全局数组url_protocols遍历,通过协议名称匹配到对应的URLProtocol
  2. 分配生成URLContext,并通过URLProtocol.priv_data_size分配具体协议的context,如:struct TCPContext,struct HTTPContext,并将URLProtocol赋值给URLContext.proto字段。
  3. 取URLContext得后,调用ffurl_connect(…) 调用URLProtocol.url_open()进行初始化准备下载数据
  4. 通过ffio_fdopen(…) 分配AVIOContext, AVIOContext.opaque = URLContext
  5. 最终将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的逻辑:

  1. 通过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 };
  1. 从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;
}

问题解答

  1. AVFormatContext如何下载数据 ?
    下图中蓝色线路图表示数据下载过程,即通过AVIOContext下载。

  2. 如何匹配到具体的demuxer ?
    读取media数据开头一段数据,遍历libavformat/demuxer_list.c中配置定义的全局数组demuxer_list,将此数据通过每一个demuer的read_probe(…)解析匹配得到一个socre或者通过mime_type和文件后缀名得到socre, 取socre最高的一个作为当前数据的demuxer模块。

  3. 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,
};
  1. demuxer是如何驱动起来的?
    从下图中可以看出,需要上层应用主动调用av_read_frame(…)获取源数据并demux,然后将demux出来的audio/video的info信息存入AVStream中,具体的audio/video数据通过AVPacket返回给上层应用。
    ffmpeg_demuxer

相关文章:

【FFMPEG源码分析】从ffplay源码摸清ffmpeg框架(二)

demux模块 从前面一篇文章中可以得知&#xff0c;demux模块的使用方法大致如下: 分配AVFormatContext通过avformat_open_input(…)传入AVFormatContext指针和文件路径&#xff0c;启动demux通过av_read_frame(…) 从AVFormatContext中读取demux后的audio/video/subtitle数据包…...

PCIE 学习笔记(入门简介)

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

锁的优化机制了解嘛?请进!

点个关注&#xff0c;必回关 文章目录自旋锁&#xff1a;自适应锁&#xff1a;锁消除&#xff1a;锁粗化&#xff1a;偏向锁&#xff1a;轻量级锁&#xff1a;从JDK1.6版本之后&#xff0c;synchronized本身也在不断优化锁的机制&#xff0c;有些情况下他并不会是一个很重量级的…...

5.点赞功能 Redis

Redis&#xff08;1&#xff09;简介Redis 是一个高性能的 key-value 数据库原子 – Redis的所有操作都是原子性的。多个操作也支持事务&#xff0c;即原子性&#xff0c;通过MULTI和EXEC指令包起来。非关系形数据库数据全部存在内存中&#xff0c;性能高。&#xff08;2&#…...

Java序列化和反序列化(详解)

一、理解Java序列化和反序列化 Serialization(序列化)&#xff1a;将java对象以一连串的字节保存在磁盘文件中的过程&#xff0c;也可以说是保存java对象状态的过程。序列化可以将数据永久保存在磁盘上(通常保存在文件中)。 deserialization(反序列化)&#xff1a;将保存在磁…...

【刷题篇】链表(上)

前言&#x1f308;前段时间我们学习了单向链表和双向链表&#xff0c;本期将带来3道与链表相关的OJ题来巩固对链表的理解。话不多说&#xff0c;让我们进入今天的题目吧&#xff01;&#x1f680;本期的题目有&#xff1a;反转单链表、链表的中间结点、合并两个有序链表反转单链…...

ConcurrentHashMap设计思路

ConcurrentHashMap设计思路Hashtable vs ConcurrentHashMapHashtable vs ConcurrentHashMap Hashtable 对比 ConcurrentHashMap Hashtable 与 ConcurrentHashMap 都是线程安全的 Map 集合Hashtable 并发度低&#xff0c;整个 Hashtable 对应一把锁&#xff0c;同一时刻&#…...

Unity基于GraphView的行为树编辑器

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

网络流量传输MTU解析

基本概念 以太网的链路层对数据帧的长度会有一个限制&#xff0c;其最大值默认是1500字节&#xff0c;链路层的这个特性称为MTU&#xff0c;即最大传输单元 Maximum Transmission Unit&#xff0c;最大传输单元&#xff0c;指的是数据链路层的最大payload&#xff0c;由硬件网…...

30个HTML+CSS前端开发案例(四)

30个HTMLCSS前端开发案例&#xff08;17-20&#xff09;鼠标移入文字加载动画效果代码实现效果鼠标悬停缩放效果实现代码效果鼠标移入旋转动画实现代码效果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…...

实验名称:经典同步问题:生成者与消费者问题

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

EasyCVR视频云存储的架构解析与Sharelist云存挂载方法介绍

一、什么是视频云存储&#xff1f; 视频云存储主要用于为上层应用提供视频文件、结构化信息、事件信息的相关服务。云存储节点分为数据文件存储节点和结构化数据存储节点。数据文件存储节点主要用于视频、图片的存储。结构化数据存储节点用于存储结构化数据并提供相关服务。 …...

电机参数中力矩单位kgf.cm,Nm,mNm表示的含义

力的基本知识 质量和力的比例系数 质量和重力的关系有一个重力系数&#xff1a;g≈9.8 N/kg≈10,后面看到的1kgf就相当于1kg物体的力也就是10N 杠杆原理 对于同一个支点&#xff0c;在不考虑杠杆的重量的情况下&#xff0c;实现同样的作用效果&#xff0c;距离支点越近&…...

使用scikit-learn为PyTorch 模型进行超参数网格搜索

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

Windeployqt 打包,缺少dll 的解决方法

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

第四章:搭建Windows server AD域和树域

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

【解决方案】老旧小区升级改造,视频智能化能力如何提升居民安全感?

一、需求背景 随着我国社会经济的快速发展与进步&#xff0c;城市宜居程度成为城市发展的重要指标&#xff0c;城市的发展面临着更新、改造和宜居建设等。一方面&#xff0c;社区居民对生活的环境提出了更高的要求&#xff1b;另一方面&#xff0c;将“智慧城市”的概念引入社…...

【遇见青山】项目难点:缓存穿透的解决方案

【遇见青山】项目难点&#xff1a;缓存穿透的解决方案1.缓存穿透现象缓存空对象布隆过滤其他方案2.解决方案&#xff0c;缓存空数据1.缓存穿透现象 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据…...

单一职责原则|SOLID as a rock

文章目录 意图动机:违反单一职责原则解决方案:C++中单一职责原则的例子单一职责的优点1、可理解性2、可维护性3、可复用性在C++中用好SRP的标准总结本文是关于 SOLID as Rock 设计原则系列的五部分中的第一部分。 SOLID 设计原则侧重于开发 易于维护、可重用和可扩展的软件。…...

使用百度地图官方WEB API,提示 “ APP 服务被禁用“ 问题的解决方法

问题描述 项目上用了百度地图官方WEB API&#xff0c;打开界面时百度地图无法打开&#xff0c;出现弹窗&#xff1a; APP被您禁用啦。详情查看&#xff1a;http://lbsyun.baidu.com/apiconsole/key#。 原因分析&#xff1a; 查看错误信息&#xff1a;"status":240,…...

nodejs如何实现Digest摘要认证?

文章目录1.前言2. 原理3. 过程4. node实现摘要认证5. 前端如何Digest摘要登录认证&#xff08;下面是海康的设备代码&#xff09;1.前言 根据项目需求&#xff0c;海康设备ISAPI协议需要摘要认证&#xff0c;那么什么是摘要认证&#xff1f;估计不少搞到几年的前端连摘要认证都…...

【C#项目】图书馆管理系统-WinForm+MySQL

文章目录前言一、业务梳理与需求分析1.功能描述2.实现步骤3.功能逻辑图二、数据库设计1.实体-关系&#xff08;E-R图&#xff09;概念模型设计2.数据表设计三、WinForm界面交互设计1、界面交互逻辑2、项目树3、主界面登录界面4、 图书查询界面5、图书借阅界面6、图书插入界面7、…...

RNN循环神经网络原理理解

一、基础 正常的神经网络 一般情况下&#xff0c;输入层提供数据&#xff0c;全连接进入隐藏层&#xff0c;隐藏层可以是多层&#xff0c;层与层之间是全连接&#xff0c;最后输出到输出层&#xff1b;通过不断的调整权重参数和偏置参数实现训练的效果。深度学习的网络都是水…...

一句话设计模式1: 单例模式

单例模式:全局唯一的对象。 文章目录 单例模式:全局唯一的对象。前言一、为什么要全局唯一?二、如何实现单例1. 注入到spring中2. 饿汉式3. 懒汉式第一种: 静态内部类第二种: synchronized 关键字第二种: 双重锁检查总结前言 单例可以说是设计模式中很常用的模式了,但也可以说…...

新版国家标准GB/T 28181—2022将于2023年7月1日正式实施,与GB/T 28181—2016差别有哪些?

新版国家标准GB/T28181-2022《公共安全视频监控联网系统信息传输、交换、控制技术要求》已于2022年12月30日发布&#xff0c;将于2023年7月1日正式实施。与GB/T 28181—2016相比&#xff0c;除结构调整和编辑性改动外&#xff0c;主要技术变化如下。——更改了标准范围&#xf…...

剑指 Offer 41. 数据流中的中位数

题目 如何得到一个数据流中的中位数&#xff1f;如果从数据流中读出奇数个数值&#xff0c;那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值&#xff0c;那么中位数就是所有数值排序之后中间两个数的平均值。 例如&#xff0c;[2,3,4] 的中位数是…...

分布式架构下,Session共享有什么方案?

分布式架构下&#xff0c;Session共享有什么方案&#xff1f; 1.不要有Session&#xff1a;但是确实在某些场景下&#xff0c;是可以没有session的&#xff0c;其实在很多借口类系统当中&#xff0c;都提倡【API无状态服务】&#xff1b; 也就是每一次的接口访问&#xff0c;都…...

瀚博半导体载天VA1 加速卡安装过程

背景&#xff1a; 想用 瀚博半导体载天VA1 加速卡 代替 NVIDIA 显卡跑深度学习模型 感谢瀚博的周工帮助解答。 正文&#xff1a; 小心拔出 NVIDIA 显卡&#xff0c;在PCIe 接口插上瀚博半导体载天VA1加速卡&#xff0c;如图&#xff1a; 这时显示屏连接主板的集成显卡 卸载…...

服务降级和熔断机制

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;服务降级和熔断机制 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪…...