ffmpeg框架閱讀筆記二 : 尋找AVIOContext初始化過程,自定義初始化。
在avformat_open_input中,有一個 init_input函式,它的作用是開啟輸入媒體,初始化所有與媒體讀寫有關的結構們,例如/AVIOContext,AVInputFormat等等。分析init_input函式,找出AVIOContext的初始化過程。以下對於init_input函式的分析程式碼摘自 http://blog.csdn.NET/nkmnkm/article/details/7043241,表示感謝!
補充一下:
[cpp] view plain copy
//引數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; //輸入格式可通過AVProbeData(包含檔名/buffer)傳入av_probe_input_format函式來獲得。 //忽略 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); //ID3v2是某些MP3檔案的標籤幀,包含歌曲的某些資訊。是ID3v1(尾部128byte)的擴充套件。 //讀一下媒體的頭部,在read_header()中主要是做某種格式的初始化工作,比如填充自己的 //私有結構,根據流的數量分配流結構並初始化,把檔案指標指向資料區開始處等. //當mp3檔案時,呼叫ff_mp3_demuxer的read_header() 。在裡面檢測媒體資訊,如果沒有則說明沒有id3v2頭,跳轉到尾部,去讀id3v1資訊。 if (!(s->flags & AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((ret = s->iformat->read_header(s, &ap)) < 0)//內部使用一系列函式,分析mp3頭部資訊 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;
}
分析ff_mp3_demuxer的read_header()函式
ffmpeg先讀取id3v2,讀不到則跳轉至距離檔案尾部128位元組處讀取id3v1.
[cpp] view plain copy
static int mp3_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
AVStream *st;
int64_t off;
st = av_new_stream(s, 0); if (!st) return AVERROR(ENOMEM); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = CODEC_ID_MP3; st->need_parsing = AVSTREAM_PARSE_FULL; st->start_time = 0; // lcm of all mp3 sample rates av_set_pts_info(st, 64, 1, 14112000); off = avio_tell(s->pb); //沒有必須的媒體資訊(無id3v2頭或者損壞),則讀id3v1資訊 if (!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) ff_id3v1_read(s); if (mp3_parse_vbr_tags(s, st, off) < 0) //分析如果是vbr格式,計算時長。cbr和vbr的時長計算有相關文章。 avio_seek(s->pb, off, SEEK_SET); /* the parameters will be extracted from the compressed bitstream */ return 0;
}
[cpp] view plain copy
//開啟輸入媒體並填充其AVInputFormat結構
static int init_input(AVFormatContext *s, const char *filename)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
//當呼叫者已指定了pb(資料取得的方式)--一般不會這樣.除非自定義AVIOContext,不使用file或者pipe
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))))//根據AVProbeData的內容來獲取格式。可以根據檔名或者
//buffer資料。內部首先匹配demuxer的AVInputformat全域性變數,然後呼叫其read_probe成員。該函式在對應格式中初始化,讀格式資訊
//如果已指定了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);
}
通過以上分析,看出當用戶不提供pb即AVIOContext,需要檔案,並且根據檔案無法猜出iformat的時候,就要開啟檔案進行探測。此處有一個疑問:當從檔名中猜出iformat時,此時pb是否也被初始化?因為有檔案就肯定需要pb的。沒有讀原始碼,此時暫不需要。
依然回來尋找AVIOContext的初始化函式,找到avio_open函式,輸入引數是s->pb,可能在裡面對其初始化,分析之。
[cpp] view plain copy
//開啟一個地址指向的媒體
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;
}
找到函式ffio_fdopen,第一個引數s。分析函式ffio_fdopen。
[cpp] view plain copy
int ffio_fdopen(AVIOContext **s, URLContext *h)
{
uint8_t *buffer;
int buffer_size, max_packet_size;
max_packet_size = h->max_packet_size;
if (max_packet_size) {
buffer_size = max_packet_size; /* no need to bufferize more than one packet */
} else {
buffer_size = IO_BUFFER_SIZE;
}
//建立buffer緩衝 用來初始化s
buffer = av_malloc(buffer_size);
if (!buffer)
return AVERROR(ENOMEM);
//為s分配記憶體空間
*s = av_mallocz(sizeof(AVIOContext));
if(!*s) {
av_free(buffer);
return AVERROR(ENOMEM);
}
//初始化AVIOContext
if (ffio_init_context(*s, buffer, buffer_size,
h->flags & AVIO_FLAG_WRITE, h,
(void*)ffurl_read, (void*)ffurl_write, (void*)ffurl_seek) < 0) {
av_free(buffer);
av_freep(s);
return AVERROR(EIO);
}
if FF_API_OLD_AVIO
(*s)->is_streamed = h->is_streamed;
endif
(*s)->seekable = h->is_streamed ? 0 : AVIO_SEEKABLE_NORMAL;
(*s)->max_packet_size = max_packet_size;
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;
}
return 0;
}
找到初始化函式ffio_init_context,分析。
[cpp] view plain copy
nt ffio_init_context(AVIOContext *s,
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
s->buffer = buffer;
s->buffer_size = buffer_size;
s->buf_ptr = buffer;
s->opaque = opaque;
url_resetbuf(s, write_flag ? AVIO_FLAG_WRITE : AVIO_FLAG_READ);
s->write_packet = write_packet;
s->read_packet = read_packet;
s->seek = seek;
s->pos = 0;
s->must_flush = 0;
s->eof_reached = 0;
s->error = 0;
if FF_API_OLD_AVIO
s->is_streamed = 0;
endif
s->seekable = AVIO_SEEKABLE_NORMAL;
s->max_packet_size = 0;
s->update_checksum= NULL;
if(!read_packet && !write_flag){
s->pos = buffer_size;
s->buf_end = s->buffer + buffer_size;
}
s->read_pause = NULL;
s->read_seek = NULL;
return 0;
}
對AVIOContex的初始化,ffmpeg提供了另外一個api,
[cpp] view plain copy
AVIOContext *avio_alloc_context(
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size),
int64_t (*seek)(void *opaque, int64_t offset, int whence))
{
AVIOContext *s = av_mallocz(sizeof(AVIOContext));
if (!s)
return NULL;
ffio_init_context(s, buffer, buffer_size, write_flag, opaque,
read_packet, write_packet, seek);
return s;
}
根據以上分析,知道建立並初始化AVIOContext的步驟:av_mallocz(sizeof(AVIOContext)) ——–> ffio_init_context.
可以自定義回撥函式,buffer緩衝區來進行初始化。後面再記錄完整測試過程