從開啟檔案開始.入口函數是avformat_open_input(),下面是對此函數的分析
//參數ps包含一切媒體相關的上下文結構,有它就有了一切,本函數如果開啟媒體成功,//會返回一個AVFormatContext的執行個體.//參數filename是媒體檔案名或URL.//參數fmt是要開啟的媒體格式的操作結構,因為是讀,所以是inputFormat.此處可以//傳入一個調用者定義的inputFormat,對應命令列中的 -f xxx段,如果指定了它,//在開啟檔案中就不會探測檔案的實際格式了,以它為準了.//參數options是對某種格式的一些操作,是為了在命令列中可以對不同的格式傳入//特殊的巨集指令引數而建的, 為了瞭解流程,完全可以無視它.int avformat_open_input(AVFormatContext **ps,const char *filename,AVInputFormat *fmt,AVDictionary **options){AVFormatContext *s = *ps;int ret = 0;AVFormatParameters ap = { { 0 } };AVDictionary *tmp = NULL;//建立上下文結構if (!s && !(s = avformat_alloc_context()))return AVERROR(ENOMEM);//如果使用者指定了輸入格式,直接使用它if (fmt)s->iformat = fmt;//忽略if (options)av_dict_copy(&tmp, *options, 0);if ((ret = av_opt_set_dict(s, &tmp)) < 0)goto fail;//開啟輸入媒體(如果需要的話),初始化所有與媒體讀寫有關的結構們,比如//AVIOContext,AVInputFormat等等if ((ret = init_input(s, filename)) < 0)goto fail;//執行完此函數後,s->pb和s->iformat都已經指向了有效執行個體.pb是用於讀寫資料的,它//把媒體資料當做流來讀寫,不管是什麼媒體格式,而iformat把pb讀出來的流按某種媒體格//式進行分析,也就是說pb在底層,iformat在上層.//很多靜態影像檔格式,都被當作一個格式處理,比如要開啟.jpeg檔案,需要的格式//名為image2.此處還不是很瞭解具體細節,作不得准哦./* check filename in case an image number is expected */if (s->iformat->flags & AVFMT_NEEDNUMBER) {if (!av_filename_number_test(filename)) {ret = AVERROR(EINVAL);goto fail;}}s->duration = s->start_time = AV_NOPTS_VALUE;//上下文中儲存下檔案名稱av_strlcpy(s->filename, filename, sizeof(s->filename));/* allocate private data *///為當前格式分配私人資料,主要用於某格式的讀寫操作時所用的私人結構.//此結構的大小在定義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;}}/* e.g. AVFMT_NOFILE formats will not have a AVIOContext *///從mp3檔案中讀ID3資料並儲存之.if (s->pb)ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC);//讀一下媒體的頭部,在read_header()中主要是做某種格式的初始化工作,比如填充自己的//私人結構,根據流的數量分配流結構並初始化,把檔案指標指向資料區開始處等.if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)if ((ret = s->iformat->read_header(s, &ap)) < 0)goto fail;//儲存資料區開始的位置if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->pb && !s->data_offset)s->data_offset = avio_tell(s->pb);s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;if (options) {av_dict_free(options);*options = tmp;}*ps = s;//執行成功return 0;//執行失敗fail: av_dict_free(&tmp);if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))avio_close(s->pb);avformat_free_context(s);*ps = NULL;return ret;}
下面分析init_input():
//開啟輸入媒體並填充其AVInputFormat結構static int init_input(AVFormatContext *s, const char *filename){int ret;AVProbeData pd = { filename, NULL, 0 };//當調用者已指定了pb(資料取得的方式)--一般不會這樣.if (s->pb) {s->flags |= AVFMT_FLAG_CUSTOM_IO;if (!s->iformat)//如果已指定了pb但沒指定iformat,以pb讀取媒體資料進行探測,取得.取得iformat.return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);else if (s->iformat->flags & AVFMT_NOFILE)//如果已指定pb也指定了iformat,但是又指定了不需要檔案(也包括URL指定的地址),這就矛盾了,//此時應是不需要pb的,因為不需操作檔案,提示一下吧,也不算錯.av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and ""will be ignored with AVFMT_NOFILE format.\n");return 0;}//一般會執行到這裡if ((s->iformat && s->iformat->flags & AVFMT_NOFILE)|| (!s->iformat && (s->iformat = av_probe_input_format(&pd, 0))))//如果已指定了iformat並且不需要檔案,也就不需要pb了,可以直接返回//如果沒指定iformat,但是可以從檔案名稱中猜出iformat,也成功.return 0;//如果從檔案名稱中也猜不出媒體格式,則只能開啟這個檔案進行探測了,先開啟檔案if ((ret = avio_open(&s->pb, filename, AVIO_FLAG_READ)) < 0)return ret;if (s->iformat)return 0;//再探測之return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);}
再看一下檔案開啟過程:
//開啟一個地址指向的媒體int avio_open(AVIOContext **s, const char *filename, int flags){//URLContext代表一個URL地址指向的媒體檔案,本地路徑也算一種.它封裝了//操作一個媒體檔案的相關資料,最重要的是prot變數,是URLProtocol型的.//prot代表一個特定的協義和協議操作函數們,URLContext包含不同的prot,//就可以通過URLContext使用不同的協議讀寫媒體資料,比如tcp,http,本地//檔案用file協議. URLContext *h; int err; //建立並初始化URLContext,其prot通過檔案名稱確定.然後開啟這個媒體檔案 err = ffurl_open(&h, filename, flags); if (err < 0) return err; //其實檔案已經在上邊真正開啟了.這裡只是填充AVIOContext.使它記錄下 //URLContext,以及填充讀寫資料的函數指標. err = ffio_fdopen(s, h); if (err < 0) { ffurl_close(h); return err; } return 0;}
下面是探測函數
int av_probe_input_buffer(AVIOContext *pb,AVInputFormat **fmt,const char *filename,void *logctx,unsigned int offset,unsigned int max_probe_size){AVProbeData pd = { filename ? filename : "", NULL, -offset };unsigned char *buf = NULL;int ret = 0, probe_size;//計算最多探測資料的位元組數if (!max_probe_size) {max_probe_size = PROBE_BUF_MAX;} else if (max_probe_size > PROBE_BUF_MAX) {max_probe_size = PROBE_BUF_MAX;} else if (max_probe_size < PROBE_BUF_MIN) {return AVERROR(EINVAL);}if (offset >= max_probe_size) {return AVERROR(EINVAL);}//迴圈直到探測完指定的資料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))) {int score = probe_size < max_probe_size ? AVPROBE_SCORE_MAX / 4 : 0;int buf_offset = (probe_size == PROBE_BUF_MIN) ? 0 : probe_size >> 1;void *buftmp;if (probe_size < offset) {continue;}/* read probe data *///分配讀取資料存放的緩衝buftmp = av_realloc(buf, probe_size + AVPROBE_PADDING_SIZE);if (!buftmp) {av_free(buf);return AVERROR(ENOMEM);}buf = buftmp;//利用pb讀資料到緩衝的剩餘空間中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 */}pd.buf_size += ret;pd.buf = &buf[offset];//緩衝中沒有資料的部分要清0memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);/* guess file format *///從一個開啟的檔案只探測媒體格式*fmt = av_probe_input_format2(&pd, 1, &score);if (*fmt) {if (score <= AVPROBE_SCORE_MAX / 4) { //this can only be true in the last iterationav_log(logctx,AV_LOG_WARNING,"Format %s detected only with low score of %d, misdetection possible!\n",(*fmt)->name, score);} elseav_log(logctx, AV_LOG_DEBUG,"Format %s probed with size=%d and score=%d\n",(*fmt)->name, probe_size, score);}//不成功,繼續}if (!*fmt) {av_free(buf);return AVERROR_INVALIDDATA;}/* rewind. reuse probe buffer to avoid seeking *///把探測時讀入的資料儲存到pb中,為的是真正讀時直接利用之.if ((ret = ffio_rewind_with_probe_data(pb, buf, pd.buf_size)) < 0)av_free(buf);return ret;}