运行avformat_open_input函数是ffmpeg初始化之后做的第一件事。从大面意思上来讲,函数作用是打开输入流,但是流具体是如何打开的,具体做了哪些事情,我们慢慢来分析研究下:
<pre name="code" class="cpp">int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) { AVFormatContext *s = *ps; int ret = 0; AVDictionary *tmp = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL;//保存MP3的一些信息,比如作者,风格,年代什么的 //如果s没有初始化,则创建一个 if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); if (!s->av_class) { av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n"); return AVERROR(EINVAL); } //如果指定了fmt,直接进行赋值 if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, *options, 0); if ((ret = av_opt_set_dict(s, &tmp)) < 0)//没有设置option参数,该步可以跳过 goto fail; //探测输入流的格式是此步实现的。 if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; ..... }
/* Open input file and probe the format if necessary. */ static int init_input(AVFormatContext *s, const char *filename, AVDictionary **options) { int ret; AVProbeData pd = { filename, NULL, 0 };//使用AVProbeData进行探测,里面的参数是文件名 int score = AVPROBE_SCORE_RETRY; //调用者已经指定了Pb(一般不会这么做),除非自己去实现让ffmpeg去内存中获取数据 if (s->pb) { //AVFormatContext中的AVIOContext *pb是否已经初始化,里面保存着对底层数据的读写操作。 s->flags |= AVFMT_FLAG_CUSTOM_IO; //如果我们自己指定了pb,会执行到这,此处代表自己的IO if (!s->iformat) //如果iformat没有指定,需要进行iformat探测和文件格式探测 //指定了pb但是没指定iformat,以pb读取媒体数据进行探测 return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize); else if (s->iformat->flags & AVFMT_NOFILE) av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and " "will be ignored with AVFMT_NOFILE format.\n"); return 0; } /*常规来讲会执行到这儿,首先如果指定了iformat,并且不需要文件,直接就返回
*如果没有指定iformat,就根据文件后缀猜测iformat
*/ if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) || (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score)))) return score; //如果从文件名中也猜测不错媒体格式,只能进行文件探测了,先打开文件 if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags, &s->interrupt_callback, options)) < 0) return ret; if (s->iformat) return 0; return av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize); }
ffmpeg根据文件后缀探测媒体格式的步骤如下:
AVInputFormat *av_probe_input_format2(AVProbeData *pd, int is_opened, int *score_max) { int score_ret; AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);// if (score_ret > *score_max) { *score_max = score_ret; return fmt; } else return NULL; }
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened, int *score_ret) { AVProbeData lpd = *pd; AVInputFormat *fmt1 = NULL, *fmt; int score, nodat = 0, score_max = 0; const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];//32 if (!lpd.buf) lpd.buf = zerobuffer; if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) { int id3len = ff_id3v2_tag_len(lpd.buf); if (lpd.buf_size > id3len + 16) { lpd.buf += id3len; lpd.buf_size -= id3len; } else if (id3len >= PROBE_BUF_MAX) { nodat = 2; } else nodat = 1; } fmt = NULL; while ((fmt1 = av_iformat_next(fmt1))) {//循环获取所有的注册格式,格式是在初始化的时候注册的 if (!is_opened == !(fmt1->flags & AVFMT_NOFILE)) continue; score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(&lpd); if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) { if (nodat == 0) score = FFMAX(score, 1); else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1); else score = FFMAX(score, AVPROBE_SCORE_EXTENSION); } } else if (fmt1->extensions) { if (av_match_ext(lpd.filename, fmt1->extensions)) score = AVPROBE_SCORE_EXTENSION; } if (score > score_max) { score_max = score; fmt = fmt1; } else if (score == score_max) fmt = NULL; } if (nodat == 1) score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max); *score_ret = score_max; return fmt; }
int avio_open2(AVIOContext **s, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options)//第三个参数是中断回调机制,以后再讲,此处可以不用理解其意义 { URLContext *h; int err; err = ffurl_open(&h, filename, flags, int_cb, options);//根据文件名称查找对应的协议 if (err < 0) return err; err = ffio_fdopen(s, h);//调用ffio_init_context将url中的ffurl_read,ffurl_write,ffurl_seek注册到AVIOContext中 if (err < 0) { ffurl_close(h); return err; } return 0; }
int ffurl_open(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options) { int ret = ffurl_alloc(puc, filename, flags, int_cb);//根据文件名判断协议,如果是文件的话,那就是file协议 if (ret < 0) return ret; if (options && (*puc)->prot->priv_data_class && (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0) goto fail; if ((ret = av_opt_set_dict(*puc, options)) < 0) goto fail; ret = ffurl_connect(*puc, options);//打开具体的协议,比如file if (!ret) return 0; fail: ffurl_close(*puc); *puc = NULL; return ret; }
int ffurl_alloc(URLContext **puc, const char *filename, int flags, const AVIOInterruptCB *int_cb) { URLProtocol *p = NULL; if (!first_protocol) {//注册协议链表 av_log(NULL, AV_LOG_WARNING, "No URL Protocols are registered. " "Missing call to av_register_all()?\n"); } p = url_find_protocol(filename);//根据文件名称判断 if (p) return url_alloc_for_protocol(puc, p, filename, flags, int_cb); *puc = NULL; if (av_strstart("https:", filename, NULL)) av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile with openssl or gnutls enabled.\n"); return AVERROR_PROTOCOL_NOT_FOUND; }
//探测流格式,在AVIOContext *pb已经指定的情况下调用! int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt, const char *filename, void *logctx, unsigned int offset, unsigned int max_probe_size) { AVProbeData pd = { filename ? filename : "" }; //需要探测的文件名 uint8_t *buf = NULL; uint8_t *mime_type; int ret = 0, probe_size, buf_offset = 0; int score = 0; if (!max_probe_size) max_probe_size = PROBE_BUF_MAX; else if (max_probe_size < PROBE_BUF_MIN) { av_log(logctx, AV_LOG_ERROR, "Specified probe size value %u cannot be < %u\n", max_probe_size, PROBE_BUF_MIN); return AVERROR(EINVAL); } if (offset >= max_probe_size) return AVERROR(EINVAL); if (!*fmt && pb->av_class && av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type) >= 0 && mime_type) { if (!av_strcasecmp(mime_type, "audio/aacp")) { *fmt = av_find_input_format("aac"); } av_freep(&mime_type); } //循环探测 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)//为buf分配内存空间 return ret; //使用avio_read()读取一段数据 if ((ret = avio_read(pb, buf + buf_offset, probe_size - buf_offset)) < 0) { /* Fail if error was not end of file, otherwise, lower score. */ if (ret != AVERROR_EOF) { av_free(buf); return ret; } score = 0; ret = 0; /* error was end of file, nothing read */ } buf_offset += ret;//偏移量指向数据的结尾 if (buf_offset < offset)//如果读取的数据还没有达到指定要读取的offset数,继续读取 continue; pd.buf_size = buf_offset - offset; pd.buf = &buf[offset]; //缓存中没有数据的部分要清零 memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE); /* Guess file format. */ *fmt = av_probe_input_format2(&pd, 1, &score);//开始探测容器格式 if (*fmt) { /* This can only be true in the last iteration. */ if (score <= AVPROBE_SCORE_RETRY) { av_log(logctx, AV_LOG_WARNING, "Format %s detected only with low score of %d, " "misdetection possible!\n", (*fmt)->name, score); } else av_log(logctx, AV_LOG_DEBUG, "Format %s probed with size=%d and score=%d\n", (*fmt)->name, probe_size, score); #if 0 FILE *f = fopen("probestat.tmp", "ab"); fprintf(f, "probe_size:%d format:%s score:%d filename:%s\n", probe_size, (*fmt)->name, score, filename); fclose(f); #endif } } if (!*fmt) { av_free(buf); return AVERROR_INVALIDDATA; } /* Rewind. Reuse probe buffer to avoid seeking. */ ret = ffio_rewind_with_probe_data(pb, &buf, buf_offset); return ret < 0 ? ret : score; }
/*可以看出,上面探测的步骤为: 1.分配内存 2.读取数据 3.探测格式 下面是如何读取数据 */ int avio_read(AVIOContext *s, unsigned char *buf, int size) { int len, size1; size1 = size; while (size > 0) { len = s->buf_end - s->buf_ptr;//AVIOContext的剩余buf的长度 if (len > size) len = size; if (len == 0 || s->write_flag) {//若没有剩余的数据 if((s->direct || size > s->buffer_size) && !s->update_checksum){ if(s->read_packet) len = s->read_packet(s->opaque, buf, size); if (len <= 0) { /* do not modify buffer if EOF reached so that a seek back can be done without rereading data */ s->eof_reached = 1; if(len<0) s->error= len; break; } else { s->pos += len; s->bytes_read += len; size -= len; buf += len; s->buf_ptr = s->buffer; s->buf_end = s->buffer/* + len*/; } } else { fill_buffer(s); len = s->buf_end - s->buf_ptr; if (len == 0) break; } } else { memcpy(buf, s->buf_ptr, len); buf += len; s->buf_ptr += len; size -= len; } } if (size1 == size) { if (s->error) return s->error; if (url_feof(s)) return AVERROR_EOF; } return size1 - size; }