1. 程式人生 > >FFMPEG之協議(文件)操作----AVIOContext, URLContext, URLProtocol

FFMPEG之協議(文件)操作----AVIOContext, URLContext, URLProtocol

定義 oid index 字節 .cn inf 目標 use 初始

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

協議操作對象結構:

技術分享

協議(文件)操作的頂層結構是AVIOContext,這個對象實現了帶緩沖的讀寫操作;FFMPEG的輸入對象AVFormat的pb字段指向一個AVIOContext。

AVIOContext的opaque實際指向一個URLContext對象,這個對象封裝了協議對象及協議操作對象,其中prot指向具體的協議操作對象,priv_data指向具體的協議對象。

URLProtocol為協議操作對象,針對每種協議,會有一個這樣的對象,每個協議操作對象和一個協議對象關聯,比如,文件操作對象為ff_file_protocol,它關聯的結構體是FileContext。


代碼分析:

初始化AVIOFormat函數調用關系:

技術分享

我們采用從底至上的方法分析源碼。

URLProtocol是FFMPEG操作文件的結構(包括文件,網絡數據流等等),包括open、close、read、write、seek等操作。

在在av_register_all()函數中,通過調用REGISTER_PROTOCOL()宏,所有的URLProtocol都保存在以first_protocol為鏈表頭的鏈表中。

URLProtocol結構體的定義為(簡化版,未完全列出所有成員):

  1. typedef struct URLProtocol {
  2. const char *name;
  3. int (*url_open)( URLContext *h, const char *url, int flags);
  4. int (*url_read)( URLContext *h, unsigned char *buf, int size);
  5. int (*url_write)(URLContext *h, const unsigned char *buf, int size);
  6. int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
  7. int (*url_close)(URLContext *h);
  8. int (*url_get_file_handle)(URLContext *h);
  9. struct URLProtocol *next; // 指向下一個URLProtocol對象(所有URLProtocol以鏈表鏈接在一起)
  10. int priv_data_size; // 和該URLProtocol對象關聯的對象的大小
  11. const AVClass *priv_data_class;
  12. } URLProtocol;

以文件協議為例,ff_file_protocol變量定義為:

  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. .priv_data_size = sizeof(FileContext),
  11. .priv_data_class = &file_class,
  12. };

從中可以看出,.priv_data_size的值為sizeof(FileContext),即ff_file_protocol和FileContext想關聯。


FileContext對象的定義為:

  1. typedef struct FileContext {
  2. const AVClass *class;
  3. int fd; <span style="white-space:pre"> </span>// 文件描述符
  4. int trunc; // 截斷屬性
  5. int blocksize; // 塊大小,每次讀寫文件最大字節數
  6. } FileContext;

返回去看ff_file_protocol裏的函數指針,以url_read成員為例,它指向file_read()函數,該函數的定義為:

  1. static int file_read(URLContext *h, unsigned char *buf, int size)
  2. {
  3. FileContext *c = h->priv_data;
  4. int r;
  5. size = FFMIN(size, c->blocksize);
  6. r = read(c->fd, buf, size);
  7. return (-1 == r)?AVERROR(errno):r;
  8. }

從代碼可以看出:

1. 調用此函數時,URLContext的priv_data指向一個FileContext對象;

2. 該函數每次最大只讀取FileContext.blocksize大小的數據。

從上面的代碼中,還可發現一個重要的對象:URLContext,根據代碼推測,這個對象的priv_data指向一個FileContext,下面來看看這個對象的定義及初始化:

定義:

  1. typedef struct URLContext {
  2. const AVClass *av_class; /**< information for av_log(). Set by url_open(). */
  3. struct URLProtocol *prot;
  4. void *priv_data;
  5. char *filename; /**< specified URL */
  6. int flags;
  7. int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */
  8. int is_streamed; /**< true if streamed (no seek possible), default = false */
  9. int is_connected;
  10. AVIOInterruptCB interrupt_callback;
  11. int64_t rw_timeout; /**< maximum time to wait for (network) read/write operation completion, in mcs */
  12. } URLContext;

註釋已經很明了,接下來看看這個結構體的初始化,在url.h(URLContext定義的地方)文件中很容易發現有一個ffurl_open()函數,猜測這應該是個初始化函數,從

該函數的內容可知,它首先調用了ffurl_alloc()申請空間,然後調用了ffurl_connect()建立連接。因此我們的目標轉向這兩個函數

先看ffurl_alloc()函數,該函數先調用url_find_protocol()查找和輸入文件名稱對應的協議對象,然後調用url_alloc_for_protocol()申請空間。

url_find_protocol()易知:filename和協議匹配的規則是finename的前綴名(本地文件被統一處理為file前綴)和協議的name字段所指向的字符串相等。

再看url_alloc_for_protocol()函數:

  1. static int url_alloc_for_protocol(URLContext **puc, struct URLProtocol *up,
  2. const char *filename, int flags,
  3. const AVIOInterruptCB *int_cb)
  4. {
  5. URLContext *uc;
  6. int err;
  7. // ...
  8. uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1); // 註意申請的空間大小
  9. if (!uc) {
  10. err = AVERROR(ENOMEM);
  11. goto fail;
  12. }
  13. uc->av_class = &ffurl_context_class;
  14. uc->filename = (char *)&uc[1]; // 指向uc+sizeof(URLContext)的低昂
  15. strcpy(uc->filename, filename);
  16. uc->prot = up;
  17. uc->flags = flags;
  18. uc->is_streamed = 0; /* default = not streamed */
  19. uc->max_packet_size = 0; /* default: stream file */
  20. if (up->priv_data_size) {
  21. uc->priv_data = av_mallocz(up->priv_data_size);
  22. if (!uc->priv_data) {
  23. err = AVERROR(ENOMEM);
  24. goto fail;
  25. }
  26. if (up->priv_data_class) {
  27. int proto_len= strlen(up->name);
  28. char *start = strchr(uc->filename, ‘,‘);
  29. *(const AVClass **)uc->priv_data = up->priv_data_class;
  30. av_opt_set_defaults(uc->priv_data);
  31. // ...
  32. }
  33. }

由上代碼可以看出,URLContext的filename保存了輸入文件名,prot字段保存了查找到的協議操作對象指針,flags由入參指定,is_streamed及max_packet_size默認值為0,

priv_data指向了一個由協議操作對象的priv_data_size指定大小的空間(考慮ff_file_protocol,即指向了一個sizeof(FileContext)大小的空間),且該空間的被初始化為0。

至此,URLContext的大部分內容已初始化,ffurl_alloc()工作已經完成,接著看ffurl_connect(),從代碼易知,它調用了prot->url_open()函數打開協議,並設置了URLContext的

is_connected和is_streamed為1。

註意,av_opt_set_defaults(uc->priv_data)為為priv_data所指向的結構也進行了默認賦值操作,針對ff_file_protocol,賦值字段及值可參考file_options[]的定義:

  1. static const AVOption file_options[] = {
  2. { "truncate", "truncate existing files on write", offsetof(FileContext, trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM },
  3. { "blocksize", "set I/O operation maximum block size", offsetof(FileContext, blocksize), AV_OPT_TYPE_INT, { .i64 = INT_MAX }, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
  4. { NULL }
  5. };

即,truncate字段賦值為1,blocksize字段賦值為INT_MAX。

最後看看file_open()函數,從代碼容易看出,該函數打開了文件,並將文件句柄保存在FileContext的fd字段。並且,該函數還根據文件屬性設置了FileContext的is_streamed的值。

URLContext再往上一層是AVIOContext,該結構體的定義為:

  1. typedef struct AVIOContext {
  2. const AVClass *av_class;
  3. unsigned char *buffer; /**< 讀寫緩沖buffer起始地址 */
  4. int buffer_size; /**< buffer大小 */
  5. unsigned char *buf_ptr; /**< Current position in the buffer */
  6. unsigned char *buf_end; /**< End of the data */
  7. void *opaque; /**< 指向URLContext */
  8. int (*read_packet)(void *opaque, uint8_t *buf, int buf_size); /* 指向ffurl_read() */
  9. int (*write_packet)(void *opaque, uint8_t *buf, int buf_size); /* 指向ffurl_write() */
  10. int64_t (*seek)(void *opaque, int64_t offset, int whence); /* 指向ffurl_seek() */
  11. int64_t pos; /**< 當前buffer對應的文件內容中的位置 */
  12. int must_flush; /**< true if the next seek should flush */
  13. int eof_reached; /**< true if eof reached */
  14. int write_flag; /**< true if open for writing */
  15. int max_packet_size;
  16. unsigned long checksum;
  17. unsigned char *checksum_ptr;
  18. unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);
  19. int error; /**< contains the error code or 0 if no error happened */
  20. int (*read_pause)(void *opaque, int pause);/
  21. int64_t (*read_seek)(void *opaque, int stream_index,
  22. int64_t timestamp, int flags);
  23. int seekable; // 是否可seek,0表示不可搜索
  24. int64_t maxsize;
  25. int direct;
  26. int64_t bytes_read;
  27. int seek_count;
  28. int writeout_count;
  29. int orig_buffer_size;
  30. }AVIOContext;

avio_open2()負責初始化AVIOContext,從代碼易知,該函數首先調用ffurl_open()申請了一個URLContext對象並打開了文件。然後調用ffio_fdopen()申請一個AVIOContext對象並賦初值。

ffio_fdopen()先申請了一個讀寫緩沖buffer(結合文件協議,buffer大小為IO_BUFFER_SIZE),然後調用avio_alloc_context()賦值,註意ffurl_read,ffurl_write,ffurl_seek的地址作為入參被傳入。

最終調用的是ffio_init_context賦值,結合入參可知:

AVIOContext.buffer指向申請到的buffer, AVIOContext.orig_buffer_size和AVIOContext.buffer_size值為buffer_size(I_BUFFER_SIZE),buf_ptr字段初始化為buffer的開始地址,opaque指向URLContext對象。

再關註下avio_read()函數:

  1. int avio_read(AVIOContext *s, unsigned char *buf, int size)
  2. {
  3. int len, size1;
  4. size1 = size;
  5. while (size > 0) {
  6. len = s->buf_end - s->buf_ptr;
  7. if (len > size)
  8. len = size;
  9. if (len == 0 || s->write_flag) {
  10. if((s->direct || size > s->buffer_size) && !s->update_checksum){
  11. if(s->read_packet)
  12. len = s->read_packet(s->opaque, buf, size);
  13. if (len <= 0) {
  14. /* do not modify buffer if EOF reached so that a seek back can
  15. be done without rereading data */
  16. s->eof_reached = 1;
  17. if(len<0)
  18. s->error= len;
  19. break;
  20. } else {
  21. s->pos += len;
  22. s->bytes_read += len;
  23. size -= len;
  24. buf += len;
  25. s->buf_ptr = s->buffer;
  26. s->buf_end = s->buffer/* + len*/;
  27. }
  28. } else {
  29. fill_buffer(s);
  30. len = s->buf_end - s->buf_ptr;
  31. if (len == 0)
  32. break;
  33. }
  34. } else {
  35. memcpy(buf, s->buf_ptr, len);
  36. buf += len;
  37. s->buf_ptr += len;
  38. size -= len;
  39. }
  40. }
  41. if (size1 == size) {
  42. if (s->error) return s->error;
  43. if (url_feof(s)) return AVERROR_EOF;
  44. }
  45. return size1 - size;
  46. }

從代碼易知,該函數實現了緩沖讀寫功能,即調用者傳遞的size比buffer_size大,則buffer_size的倍數部分會直接讀取到buf,余數部分會首先讀取buffer_size大小的數據到buffer(fill_buffer()函數),然後再拷貝到入參buf指向的地址中。

AVIOContext再往上一層是AVFormatContext對象,也是ffmpeg的頂層對象,AVFormatContext的pb字段指向一個AVIOContext對象。

FFMPEG之協議(文件)操作----AVIOContext, URLContext, URLProtocol