與其說是分析,不如說是學習,隻是看在自己第一次寫系列文章的份上,給足自己面子,取個有"深度"的題目!如有人被題目所蒙騙進來,還望見諒!
urlprotocol,urlcontext和byteiocontext是ffmpeg操作檔案(即i/o,包括網絡資料流)的結構,這幾個結構現實的功能類似于c++的多态繼承吧,c++的多态是通過子類繼承實作,而ffmpeg的“多态”是通過靜态對像現實。這部分的代碼非常值得c程式借鑒,我是說,如果你要在c裡實作類似c++多态性的功能;比如當你要區分你老婆和情人之間的不同功能時。
好了,先來看一下這三個struct的定義吧
typedef struct urlprotocol {
const char *name; //rotocol名稱
int (*url_open)(urlcontext *h, const char *url, int flags); //open函數指針對象,以下類似
int (*url_read)(urlcontext *h, unsigned char *buf, int size);
int (*url_write)(urlcontext *h, unsigned char *buf, int size);
int64_t (*url_seek)(urlcontext *h, int64_t pos, int whence);
int (*url_close)(urlcontext *h);
struct urlprotocol *next; //指向下一個urlprotocol,具體說明見備注1
int (*url_read_pause)(urlcontext *h, int pause);
int64_t (*url_read_seek)(urlcontext *h, int stream_index,int64_t timestamp, int flags);
int (*url_get_file_handle)(urlcontext *h);
} urlprotocol;
備注1:ffmpeg所有的protol類型都用這個變量串成一個連結清單,表頭為avio.c裡的urlprotocol *first_protocol = null;
每個檔案類似都有自己的一個urlprotocol靜态對象,如libavformat/file.c裡
urlprotocol file_protocol = {
"file",
file_open,
file_read,
file_write,
file_seek,
file_close,
.url_get_file_handle = file_get_handle,
};
再通過av_register_protocol()将他們連結成連結清單。在ffmpeg中所有的urlprotocol對像值都在編譯時确定。
typedef struct urlcontext {
#if libavformat_version_major >= 53
const avclass *av_class; ///< information for av_log(). set by url_open().
#endif
struct urlprotocol *prot; //指向具體的i/0類型,在運作時通過檔案url确定,如是file類型時就是file_protocol
int flags;
int is_streamed; /**< true if streamed (no seek possible), default = false */
int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */
void *priv_data; //指向具體的i/o句柄
char *filename; /**< specified url */
} urlcontext;
不同于urlprotocol對象值在編譯時确定,urlcontext對象值是在運作過程中根據輸入的i/o類型動态确定的。這一動一靜組合起到了c++的多态繼承一樣的作用。urlcontext像是基類,為大家共同所有,而urlprotocol像是子類部分。
typedef struct {
unsigned char *buffer;
int buffer_size;
unsigned char *buf_ptr, *buf_end;
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);
int64_t pos; /**< position in the file of the current buffer */
int must_flush; /**< true if the next seek should flush */
int eof_reached; /**< true if eof reached */
int write_flag; /**< true if open for writing */
int is_streamed;
int max_packet_size;
unsigned long checksum;
unsigned char *checksum_ptr;
unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
int error; ///< contains the error code or 0 if no error happened
int (*read_pause)(void *opaque, int pause);
int64_t (*read_seek)(void *opaque, int stream_index,
int64_t timestamp, int flags);
} byteiocontext;
byteiocontext是urlcontext和urlprotocol 一個擴充,也是ffmpeg提供給使用者的接口,urlcontext和urlprotocol對使用者是透明,我們所有的操作是通過byteiocontext現實。這幾個struct的相關的關鍵函數有:
int av_open_input_file(avformatcontext **ic_ptr, const char *filename,
avinputformat *fmt,
int buf_size,
avformatparameters *ap)
{
int url_fopen(byteiocontext **s, const char *filename, int flags)
{
url_open(urlcontext **puc, const char *filename, int flags)
{
urlprotocol *up;
//根據filename确定up
url_open_protocol (urlcontext **puc, struct urlprotocol *up, const char *filename, int flags)
{
//初始化urlcontext對像,并通過 up->url_open()将i/o打開将i/o fd指派給urlcontext的priv_data對像
}
}
url_fdopen(byteiocontext **s, urlcontext *h)
{
//初始化byteiocontext 對像
}
}
}
我們先看一下音視訊播放器的大概結構(個人想法,不保證正确):1、資料源輸入(input)->2、檔案格式解析器(demux)->3、音視訊解碼(decoder)->4、顔色空間轉換(僅視訊)->5、渲染輸出(render output)。前一篇介紹的幾個struct是資料源輸入子產品裡的内容,哪麼這一帖所講的就是第二個子產品即檔案格式解析器裡用到的内容。
avinputformat、avoutputformat與urlprotocol類似,每一種檔案類型都有一個avinputformat和avoutputformat靜态對像并通過av_register_input_format和av_register_output_format函數鍊成一個表,其表頭在utils.c:
/** head of registered input format linked list */
avinputformat *first_iformat = null;
/** head of registered output format linked list */
avoutputformat *first_oformat = null;
avinputformat和avoutputformat的定義分别在avformat.h,代碼很長,不貼出來浪費空間了。
當程式運作時,avinputformat對像的
int buf_size,
avformatparameters *ap)
fmt = av_probe_input_format(pd, 0);//傳回該檔案的avinputformat類型
至于avoutputformat嘛,下次再說吧,晚安!
avformatcontext在ffmpeg裡是一個非常重要的的結構,是其它輸入、輸出相關資訊的一個容器,需要注意的是其中兩個成員:
struct avinputformat *iformat;//資料輸入格式
struct avoutputformat *oformat;//資料輸出格式
這兩個成員不能同時指派,即avformatcontext不能同時做為輸入、輸出格式的容器。avformatcontext和avicontext、flvcontext等xxxcontext之間像前面講的 urlcontext和 urlprotocol的關系一樣,是一種"多态"關系,即avformatcontext對像記錄着運作時大家共有的資訊,而各個xxxcontext記錄自己檔案格式的資訊,如avicontext、flvcontext等。avinputformat->priv_data_size記錄相對應的xxxcontext的大小,該值大小在編譯時靜态确定。avformatcontext的void *priv_data記錄xxxcontext指針。
avformatcontext對像的初始化主要在 avinputformat的read_header函數中進行,read_header是個函數指針,指向
具體的檔案類型的read_header,如flv_read_header(),avi_read_header()等,avformatcontext、avinputformat和xxxcontext組成一起共同完成資料輸入子產品,可以出來粗魯的認為,avformatcontext是一個類容器,avinputformat是這個類的操作函數集合,xxxcontext代表該類的私有資料對像。avformatcontext還有個重要的成員 avstream *streams[max_streams];也是在read_header裡初始化,這個等會兒再講。
前幾篇說的都還是資料源檔案格式解析部分,哪麼解析完後呢,讀出的資料流儲存在哪呢?正是現在講的avstream對像,在avinputformat的read_header中初始化avformatcontext對像時,他會解析出該輸入檔案有哪些類型的資料流,并初始化avformatcontext的avstream *streams[max_streams];一個avstream代表一個流對像,如音頻流、視訊流,nb_streams記錄流對像個數。主版本号大于53時max_streams為100,小于53為20。avstream也是個容器,其
void *priv_data;//
成員變量指向具體的stream類型對像,如avistream。其
avcodeccontext *actx;//記錄具體的編解容器,這個下面會講
也在這讀頭檔案資訊裡初始化。
主要相關的函數有
av_open_input_stream(avformatcontext **ic_ptr,byteiocontext *pb, const char *filename,avinputformat *fmt, avformatparameters *ap)
fmt.read_header()//調用具體的avinputformat的read_header,如avi_read_header
//根據檔案頭資訊初始化avstream *streams及avstream裡的
//void *priv_data和avcodeccontext *actx;成員對像
}
他們之間的關系和urlprotocol、urlcontext之間是一樣的,avcodeccontext動态的記錄一個解碼器的上下文資訊,而avcodec是每個解碼器都會擁有一個自己的靜态對像,并通過avcodec_register()函數注冊成一個連結清單,表頭在utils.c裡定義
static avcodec *first_avcodec = null;
avcodeccontext的enum codecid codec_id成員記錄者目前資料流的codec,void *priv_data記錄具體codec所對應的上下文資訊對像的指針,如msrlecontext。這三個結合起來現實資料解碼的作用。我們可以傻逼的認為avcodeccontext是這個解碼子產品的容器類,codec是操作函數集合,類似msrlecontext的就是操作資料對像。
他們之間關系的确立:
每一個解碼類型都會有自己的codec靜态對像,codec的int priv_data_size記錄該解碼器上下文的結構大小,如msrlecontext。這些都是編譯時确定的,程式運作時通過avcodec_register_all()将所有的解碼器注冊成一個連結清單。在av_open_input_stream()函數中調用avinputformat的read_header()中讀檔案頭資訊時,會讀出資料流的codecid,即确定了他的解碼器codec。
typedef struct avpicture {
uint8_t *data[4];
int linesize[4]; ///< number of bytes per line
} avpicture;
typedef struct avframe
uint8_t *data[4]; // 有多重意義,其一用null 來判斷是否被占用
int linesize[4];
uint8_t *base[4]; // 有多重意義,其一用null 來判斷是否配置設定記憶體
//......其他的資料
} avframe;
從定義上可知,avpicture是avframe的一個子集,他們都是資料流在編解過程中用來儲存資料緩存的對像,從int av_read_frame(avformatcontext *s, avpacket *pkt)函數看,從資料流讀出的資料首先是儲存在avpacket裡,也可以了解為一個avpacket最多隻包含一個avframe,而一個avframe可能包含好幾個avpacket,avpacket是種資料流分包的概念。記錄一些音視訊相關的屬性值,如pts,dts等,定義如下:
typedef struct avpacket {
int64_t pts;
int64_t dts;
uint8_t *data;
int size;
int stream_index;
int flags;
int duration;
void (*destruct)(struct avpacket *);
void *priv;
int64_t pos; ///< byte position in stream, -1 if unknown
int64_t convergence_duration;
} avpacket;