FFMpeg分析詳細分析

fmms 12年前發布 | 75K 次閱讀 FFmpeg 多媒體處理

  與其說是分析,不如說是學習,只是看在自己第一次寫系列文章的份上,給足自己面子,取個有"深度"的題目!如有人被題目所蒙騙進來,還望見諒!

      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;</pre>

 

備注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,

};</pre>

再通過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;</pre>

不同于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;</pre>

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 對像

     }

}

}</pre>

我們先看一下音視頻播放器的大概結構(個人想法,不保證正確):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 av_open_input_file(AVFormatContext *ic_ptr, const char filename,

                   AVInputFormat *fmt,

                   int buf_size,

                   AVFormatParameters *ap)

{

 fmt = av_probe_input_format(pd, 0);//返回該文件的AVInputFormat類型

}</pre>

至于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;//</p>

成員變量指向具體的Stream類型對像,如AVIStream。其

AVCodecContext *actx;//記錄具體的編解容器,這個下面會講

也在這讀頭文件信息里初始化。

主要相關的函數有

int av_open_input_file(AVFormatContext *ic_ptr, const char filename,

                   AVInputFormat *fmt,

                   int buf_size,

                   AVFormatParameters *ap)

{

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;成員對像

     }        

}

} </pre>

 

  他們之間的關系和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;</pre>

從定義上可知,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;</pre>

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!