1. 程式人生 > >ffmpeg框架閱讀筆記二 : 尋找AVIOContext初始化過程,自定義初始化。

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緩衝區來進行初始化。後面再記錄完整測試過程