FFmpeg架構之I/O模組分析

摩西2016發表於2017-09-16

本文轉自http://blog.csdn.net/leixiaohua1020/article/details/12752223

注意:這篇轉載的文章比較早,寫得很清晰,但是新版的ffmpeg的很多資料結構的名字已經改了。因此只能作參考。(例如ByteIOContext已經改名為AVIOContext)


1概述

ffmpeg專案的資料IO部分主要是在libavformat庫中實現,某些對於記憶體的操作部分在libavutil庫中。資料IO是基於檔案格式(Format)以及檔案傳輸協議(Protocol)的,與具體的編解碼標準無關。 ffmpeg工程轉碼時資料IO層次關係如圖所示:


對於上面的資料IO流程,具體可以用下面的例子來說明,我們從一個http伺服器獲取音視訊資料,格式是flv的,需要通過轉碼後變成avi格式,然後通過udp協議進行釋出。

其過程就如下所示:

1、讀入http協議資料流,根據http協議獲取真正的檔案資料(去除無關報文資訊);

2、根據flv格式對資料進行解封裝;

3、讀取幀進行轉碼操作;

4、按照目標格式avi進行封裝;

5、通過udp協議傳送出去。

2相關資料結構介紹

在libavformat庫中與資料IO相關的資料結構主要有URLProtocol、URLContext、ByteIOContext、AVFormatContext等,各結構之間的關係如圖所示。


1、URLProtocol結構

表示廣義的輸入檔案,該結構體提供了很多的功能函式,每一種廣義的輸入檔案(如:file、pipe、tcp、rtp等等)對應著一個URLProtocol結構,在av_register_all()中將該結構體初始化為一個連結串列,表頭為avio.c裡的URLProtocol *first_protocol = NULL;儲存所有支援的輸入檔案協議,該結構體的定義如下:

[cpp] view plain copy
  1. typedef struct URLProtocol   
  2. {   
  3. const char *name;   
  4. int (*url_open)(URLContext *h, const char *url, int flags);   
  5. int (*url_read)(URLContext *h, unsigned char *buf, int size);   
  6. int (*url_write)(URLContext *h, const unsigned char *buf, int size);   
  7. int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);   
  8. int (*url_close)(URLContext *h); struct URLProtocol *next;   
  9. int (*url_read_pause)(URLContext *h, int pause);  
  10. int64_t (*url_read_seek)(URLContext *h, int stream_index,   
  11.         int64_t timestamp, int flags);  
  12. int (*url_get_file_handle)(URLContext *h);   
  13. int priv_data_size;  
  14. const AVClass *priv_data_class;   
  15. int flags;   
  16. int (*url_check)(URLContext *h, int mask);  
  17. } URLProtocol;  

注意到,URLProtocol是一個連結串列結構,這是為了協議的統一管理,ffmpeg專案中將所有的用到的協議都存放在一個全域性變數first_protocol中,協議的註冊是在av_register_all中完成的,新新增單個協議可以呼叫av_register_protocol2函式實現。而協議的註冊就是將具體的協議物件新增至first_protocol連結串列的末尾。 URLProtocol在各個具體的檔案協議中有一個具體的例項,如在file協議中定義為:

[cpp] view plain copy
  1. URLProtocol ff_file_protocol = {   
  2. .name = "file",   
  3. .url_open = file_open,   
  4. .url_read = file_read,   
  5. .url_write = file_write,   
  6. .url_seek = file_seek,   
  7. .url_close = file_close,  
  8. .url_get_file_handle = file_get_handle,   
  9. .url_check = file_check,  
  10. };  

2、URLContext結構

URLContext提供了與當前開啟的具體的檔案協議(URL)相關資料的描述,在該結構中定義了指定當前URL(即filename項)所要用到的具體的URLProtocol,即:提供了一個在URLprotocol連結串列中找到具體項的依據,此外還有一些其它的標誌性的資訊,如flags, is_streamed等。它可以看成某一種協議的載體。其結構定義如下:

[cpp] view plain copy
  1. typedef struct URLContext   
  2. {   
  3. const AVClass *av_class; ///< information for av_log(). Set by url_open().   
  4. struct URLProtocol *prot;   
  5. int flags;   
  6. int is_streamed; //< true if streamed (no seek possible), default = false * int max_packet_size;  
  7. void *priv_data;   
  8. char *filename; //< specified URL   
  9. int is_connected;   
  10. } URLContext;  

那麼ffmpeg依據什麼資訊初始化URLContext?然後又是如何初始化URLContext的呢?在開啟一個URL時,全域性函式ffurl_open會根據filename的字首資訊來確定URL所使用的具體協議,併為該協議分配好資源,再呼叫ffurl_connect函式開啟具體協議,即呼叫協議的url_open,呼叫關係如下:

int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, AVInputFormat *fmt, int buf_size, AVFormatParameters *ap)

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options)

static int init_input(AVFormatContext *s, const char *filename)

int avio_open(AVIOContext **s, const char *filename, int flags)

int ffurl_open(URLContext **puc, const char *filename, int flags)

int ffurl_alloc(URLContext **puc, const char *filename, int flags)

static int url_alloc_for_protocol (URLContext **puc, struct URLProtocol *up, const char *filename, int flags)

淺藍色部分的函式完成了URLContext函式的初始化,URLContext使ffmpeg外所暴露的介面是統一的,而不是對於不同的協議用不同的函式,這也是物件導向思維的體現。在此結構中還有一個值得說的是priv_data項,這是結構的一個可擴充套件項,具體協議可以根據需要新增相應的結構,將指標儲存在這就行。

3、AVIOContext結構

AVIOContext(即:ByteIOContext)是由URLProtocol和URLContext結構擴充套件而來,也是ffmpeg提供給使用者的介面,它將以上兩種不帶緩衝的讀取檔案抽象為帶緩衝的讀取和寫入,為使用者提供帶緩衝的讀取和寫入操作。資料結構定義如下:

[cpp] view plain copy
  1. typedef struct {   
  2. unsigned char *buffer; /**< Start of the buffer. */   
  3. int buffer_size; /**< Maximum buffer size */   
  4. unsigned char *buf_ptr; /**< Current position in the buffer */   
  5. unsigned char *buf_end;   
  6. void *opaque; /關聯URLContext  
  7. int (*read_packet)(void *opaque,uint8_t *buf,int buf_size);  
  8. int (*write_packet)(void *opaque,uint8_t *buf,int buf_size);   
  9. int64_t (*seek)(void *opaque,int64_t offset,int whence);   
  10. int64_t pos;   
  11. int must_flush;  
  12. int eof_reached; /**< true if eof reached */   
  13. int write_flag; /**< true if open for writing */   
  14. int max_packet_size;   
  15. unsigned long checksum;   
  16. unsigned char *checksum_ptr;   
  17. unsigned long (*update_checksum)(unsigned long checksum,const uint8_t *buf,unsigned int size);   
  18. int error;   
  19. int (*read_pause)(void *opaque,int pause)   
  20. int64_t (*read_seek)(void *opaque,int stream_index,int64_t timestamp,int flags);   
  21. int seekable;   
  22. } AVIOContext;  

結構簡單的為使用者提供讀寫容易實現的四個操作,read_packet write_packet read_pause read_seek,極大的方便了檔案的讀取,四個函式在加了緩衝機制後被中轉到,URLContext指向的實際的檔案協議讀寫函式中。 下面給出0.8版本中是如何將AVIOContext的讀寫操作中轉到實際檔案中的。 在avio_open()函式中呼叫了ffio_fdopen()函式完成了對AVIOContex的初始化,其呼叫過程如下:

int avio_open(AVIOContext **s, const char *filename, int flags)

ffio_fdopen(s, h); //h是URLContext指標 ffio_init_context(*s, buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h, (void*)

ffurl_read,(void*)ffurl_write,(void*)ffurl_seek)

藍色部分的函式呼叫完成了對AVIOContext的初始化,在初始化的過程中,將AVIOContext的read_packet、write_packet、seek分別初始化為:ffurl_read ffurl_write ffurl_seek,而這三個函式又將具體的讀寫操作中轉為:h->prot->url_read、h->prot->url_write、h->prot->url_seek,另外兩個變數初始化時也被相應的中轉,如下:

(*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;

所以,可以簡要的描述為:AVIOContext的介面口是加了緩衝後的URLProtocol的函式介面。

在aviobuf.c中定義了一系列關於ByteIOContext這個結構體的函式,如下 put_xxx系列:

[cpp] view plain copy
  1. put_xxx系列:  
  2. void put_byte(ByteIOContext *s, int b);  
  3. void put_buffer(ByteIOContext *s, const unsigned char *buf, int size);  
  4. void put_le64(ByteIOContext *s, uint64_t val);  
  5. void put_be64(ByteIOContext *s, uint64_t val);  
  6. void put_le32(ByteIOContext *s, unsigned int val);  
  7. void put_be32(ByteIOContext *s, unsigned int val);  
  8. void put_le24(ByteIOContext *s, unsigned int val);  
  9. void put_be24(ByteIOContext *s, unsigned int val);  
  10. void put_le16(ByteIOContext *s, unsigned int val);  
  11. void put_be16(ByteIOContext *s, unsigned int val);  
  12. void put_tag(ByteIOContext *s, const char *tag);  
  13. get_xxx系列:  
  14. int get_buffer(ByteIOContext *s, unsigned char *buf, int size);  
  15. int get_partial_buffer(ByteIOContext *s, unsigned char *buf, int size);  
  16. int get_byte(ByteIOContext *s);  
  17. unsigned int get_le24(ByteIOContext *s);  
  18. unsigned int get_le32(ByteIOContext *s);  
  19. uint64_t get_le64(ByteIOContext *s);  
  20. unsigned int get_le16(ByteIOContext *s);  
  21. char *get_strz(ByteIOContext *s, char *buf, int maxlen);  
  22. unsigned int get_be16(ByteIOContext *s);  
  23. unsigned int get_be24(ByteIOContext *s);  
  24. unsigned int get_be32(ByteIOContext *s);  
  25. uint64_t get_be64(ByteIOContext *s);  


這些put_xxx及get_xxx函式是用於從緩衝區buffer中寫入或者讀取若干個位元組,對於讀寫整型資料,分別實現了大端和小端位元組序的版本。而緩衝區buffer中的資料又是從何而來呢,有一個fill_buffer的函式,在fill_buffer函式中呼叫了ByteIOContext結構的read_packet介面。在呼叫put_xxx函式時,並沒有直接進行真正寫入操作,而是先快取起來,直到快取達到最大限制或呼叫flush_buffer函式對緩衝區進行重新整理,才使用write_packet函式進行寫入操作。

相關文章