本文為作者原創,轉載請注明出處:https://www.cnblogs.com/leisure_chn/p/10318145.html
所謂內存IO,在FFmpeg中叫作“buffered IO”或“custom IO”,指的是將一塊內存緩沖區用作FFmpeg的輸入或輸出。與內存IO操作對應的是指定URL作為FFmpeg的輸入或輸出,比如URL可能是普通文件或網絡流地址等。這兩種輸入輸出模式我們暫且稱作“內存IO模式”和“URL-IO模式”。
本文源碼基於FFmpeg 4.1版本,為幫助理解,可參考FFmpeg工程examples中如下兩份代碼:
https://github.com/FFmpeg/FFmpeg/blob/n4.1/doc/examples/avio_reading.c
https://github.com/FFmpeg/FFmpeg/blob/n4.1/doc/examples/remuxing.c
1. 內存區作輸入
1.1 用法
用法如示例中注釋的步驟,如下:
// @opaque : 是由用戶提供的參數,指向用戶數據
// @buf : 作為FFmpeg的輸入,此處由用戶准備好buf中的數據
// @buf_size: buf的大小
// @return : 本次IO數據量
static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
int fd = *((int *)opaque);
int ret = read(fd, buf, buf_size);
return ret;
}
int main()
{
AVFormatContext *ifmt_ctx = NULL;
AVIOContext *avio_in = NULL;
uint8_t *ibuf = NULL;
size_t ibuf_size = 4096;
int fd = -1;
// 打開一個FIFO文件的讀端
fd = open_fifo_for_read("/tmp/test_fifo");
// 1. 分配緩沖區
ibuf = av_malloc(ibuf_size);
// 2. 分配AVIOContext,第三個參數write_flag為0
avio_in = avio_alloc_context(ibuf, ibuf_size, 0, &fd, &read_packet, NULL, NULL);
// 3. 分配AVFormatContext,並指定AVFormatContext.pb字段。必須在調用avformat_open_input()之前完成
ifmt_ctx = avformat_alloc_context();
ifmt_ctx->pb = avio_in;
// 4. 打開輸入(讀取封裝格式文件頭)
avformat_open_input(&ifmt_ctx, NULL, NULL, NULL);
......
}
當啟用內存IO模式后(即ifmt_ctx->pb
有效時),將會忽略avformat_open_input()
第二個參數url
的值。在上述示例中,打開了FIFO的讀端,並在回調函數中將FIFO中的數據填入內存緩沖區ibuf,內存緩沖區ibuf將作為FFmpeg的輸入。在上述示例中,因為打開的是一個命名管道FIFO,FIFO的數據雖然在內存中,但FIFO有名字("/tmp/test_fifo"),所以此例也可以使用URL-IO模式,如下:
AVFormatContext *ifmt_ctx = NULL;
avformat_open_input(&ifmt_ctx, "/tmp/test_fifo", NULL, NULL);
而對於其他一些場合,當有效音視頻數據位於內存,而這片內存並無一個URL屬性可用時,則只能使用內存IO模式來取得輸入數據。
1.2 回調時機
回調函數何時被回調呢?所有需要從輸入源中讀取數據的時刻,都將調用回調函數。和輸入源是普通文件相比,只不過輸入源變成了內存區,其他各種外在表現並無不同。
如下各函數在不同的階段從輸入源讀數據,都會調用回調函數:
avformat_open_input()
從輸入源讀取封裝格式文件頭
avformat_find_stream_info()
從輸入源讀取一段數據,嘗試解碼,以獲取流信息
av_read_frame()
從輸入源讀取數據包
2. 內存區作輸出
2.1 用法
用法如示例中注釋的步驟,如下:
// @opaque : 是由用戶提供的參數,指向用戶數據
// @buf : 作為FFmpeg的輸出,此處FFmpeg已准備好buf中的數據
// @buf_size: buf的大小
// @return : 本次IO數據量
static int write_packet(void *opaque, uint8_t *buf, int buf_size)
{
int fd = *((int *)opaque);
int ret = write(fd, buf, buf_size);
return ret;
}
int main()
{
AVFormatContext *ofmt_ctx = NULL;
AVIOContext *avio_out = NULL;
uint8_t *obuf = NULL;
size_t obuf_size = 4096;
int fd = -1;
// 打開一個FIFO文件的寫端
fd = open_fifo_for_write("/tmp/test_fifo");
// 1. 分配緩沖區
obuf = av_malloc(obuf_size);
// 2. 分配AVIOContext,第三個參數write_flag為1
AVIOContext *avio_out = avio_alloc_context(obuf, obuf_size, 1, &fd, NULL, write_packet, NULL);
// 3. 分配AVFormatContext,並指定AVFormatContext.pb字段。必須在調用avformat_write_header()之前完成
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, NULL);
ofmt_ctx->pb=avio_out;
// 4. 將文件頭寫入輸出文件
avformat_write_header(ofmt_ctx, NULL);
......
}
當啟用內存IO模式后(即ofmt_ctx->pb
有效時),FFmpeg會將輸出寫入內存緩沖區obuf,用戶可在回調函數中將obuf中的數據取走。在上述示例中,因為打開的是一個命名管道FIFO,FIFO的數據雖然在內存中,但FIFO有名字("/tmp/test_fifo"),所以此例也可以使用URL-IO模式,如下:
AVFormatContext *ofmt_ctx = NULL;
avformat_alloc_output_context2(&ofmt_ctx, "/tmp/test_fifo", NULL, NULL);
而對於其他一些場合,需將數據輸出到內存,而這片內存並無一個URL屬性可用時,則只能使用內存IO模式。
2.2 回調時機
回調函數何時被回調呢?所有輸出數據的時刻,都將調用回調函數。和輸出是普通文件相比,只不過輸出變成了內存區,其他各種外在表現並無不同。
如下各函數在不同的階段會輸出數據,都會調用回調函數:
avformat_write_header()
將流頭部信息寫入輸出區
av_interleaved_write_frame()
將數據包寫入輸出區
av_write_trailer()
將流尾部信息寫入輸出區
3. 實現機制
如下是與內存IO操作相關的一些關鍵數據結構及函數,我們從API接口層面來看一下內存IO的實現機制,而不深入分析內部源碼。FFmpeg的API注釋非常詳細,從注釋中能得到很多有用信息。
3.1 struct AVIOContext
/**
* Bytestream IO Context.
* New fields can be added to the end with minor version bumps.
* Removal, reordering and changes to existing fields require a major
* version bump.
* sizeof(AVIOContext) must not be used outside libav*.
*
* @note None of the function pointers in AVIOContext should be called
* directly, they should only be set by the client application
* when implementing custom I/O. Normally these are set to the
* function pointers specified in avio_alloc_context()
*/
typedef struct AVIOContext {
......
/*
* The following shows the relationship between buffer, buf_ptr,
* buf_ptr_max, buf_end, buf_size, and pos, when reading and when writing
* (since AVIOContext is used for both):
*
**********************************************************************************
* READING
**********************************************************************************
*
* | buffer_size |
* |---------------------------------------|
* | |
*
* buffer buf_ptr buf_end
* +---------------+-----------------------+
* |/ / / / / / / /|/ / / / / / /| |
* read buffer: |/ / consumed / | to be read /| |
* |/ / / / / / / /|/ / / / / / /| |
* +---------------+-----------------------+
*
* pos
* +-------------------------------------------+-----------------+
* input file: | | |
* +-------------------------------------------+-----------------+
*
*
**********************************************************************************
* WRITING
**********************************************************************************
*
* | buffer_size |
* |--------------------------------------|
* | |
*
* buf_ptr_max
* buffer (buf_ptr) buf_end
* +-----------------------+--------------+
* |/ / / / / / / / / / / /| |
* write buffer: | / / to be flushed / / | |
* |/ / / / / / / / / / / /| |
* +-----------------------+--------------+
* buf_ptr can be in this
* due to a backward seek
*
* pos
* +-------------+----------------------------------------------+
* output file: | | |
* +-------------+----------------------------------------------+
*
*/
unsigned char *buffer; /**< Start of the buffer. */
int buffer_size; /**< Maximum buffer size */
unsigned char *buf_ptr; /**< Current position in the buffer */
unsigned char *buf_end; /**< End of the data, may be less than
buffer+buffer_size if the read function returned
less data than requested, e.g. for streams where
no more data has been received yet. */
void *opaque; /**< A private pointer, passed to the read/write/seek/...
functions. */
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
......
} AVIOContext;
注意:此數據結構中的成員不應由用戶程序直接訪問。當使用內存IO模式時,用戶應調用avio_alloc_context()
對此結構的read_packet
和write_packet
函數指針進行賦值。
3.2 AVIOContext* AVFormatContext.pb
/**
* Format I/O context.
* ......
*/
typedef struct AVFormatContext {
......
/**
* I/O context.
*
* - demuxing: either set by the user before avformat_open_input() (then
* the user must close it manually) or set by avformat_open_input().
* - muxing: set by the user before avformat_write_header(). The caller must
* take care of closing / freeing the IO context.
*
* Do NOT set this field if AVFMT_NOFILE flag is set in
* iformat/oformat.flags. In such a case, the (de)muxer will handle
* I/O in some other way and this field will be NULL.
*/
AVIOContext *pb;
......
}
struct AVFormatContext
結構中與內存IO操作相關的重要成員是AVIOContext *pb
,有如下規則:
- 解復用過程:在調用
avformat_open_input()
前由用戶手工設置,因為從avformat_open_input()
開始有讀輸入的操作。 - 復用過程:在調用
avformat_write_header()
前由用戶手工設置,因為從avformat_write_header()
開始有寫輸出的操作。
3.3 輸入時:avformat_open_input()
/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
打開輸入流讀取頭部信息。如果使用內存IO模式,應在此之前分配AVFormatContext
並設置其pb
成員。
3.4 輸出時:avformat_write_header()
/**
* Allocate the stream private data and write the stream header to
* an output media file.
*
* @param s Media file handle, must be allocated with avformat_alloc_context().
* Its oformat field must be set to the desired output format;
* Its pb field must be set to an already opened AVIOContext.
* @param options An AVDictionary filled with AVFormatContext and muxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return AVSTREAM_INIT_IN_WRITE_HEADER on success if the codec had not already been fully initialized in avformat_init,
* AVSTREAM_INIT_IN_INIT_OUTPUT on success if the codec had already been fully initialized in avformat_init,
* negative AVERROR on failure.
*
* @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_init_output.
*/
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
將流頭部信息寫入輸出文件。在調用此函數前,AVFormatContext.pb
成員必須設置為一個已經打開的AVIOContext
。AVFormatContext.pb
賦值方式分為兩種情況:
[1]. URL-IO模式:調用avio_open()
或avio_open2()
,形如
avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
[2]. 內存IO模式:調用avio_alloc_context()
分配AVIOContext
,然后為pb
賦值,形如:
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, NULL);
ofmt_ctx->pb=avio_out;
3.5 內存IO模式:avio_alloc_context()
/**
* Allocate and initialize an AVIOContext for buffered I/O. It must be later
* freed with avio_context_free().
*
* @param buffer Memory block for input/output operations via AVIOContext.
* The buffer must be allocated with av_malloc() and friends.
* It may be freed and replaced with a new buffer by libavformat.
* AVIOContext.buffer holds the buffer currently in use,
* which must be later freed with av_free().
* @param buffer_size The buffer size is very important for performance.
* For protocols with fixed blocksize it should be set to this blocksize.
* For others a typical size is a cache page, e.g. 4kb.
* @param write_flag Set to 1 if the buffer should be writable, 0 otherwise.
* @param opaque An opaque pointer to user-specific data.
* @param read_packet A function for refilling the buffer, may be NULL.
* For stream protocols, must never return 0 but rather
* a proper AVERROR code.
* @param write_packet A function for writing the buffer contents, may be NULL.
* The function may not change the input buffers content.
* @param seek A function for seeking to specified byte position, may be NULL.
*
* @return Allocated AVIOContext or NULL on failure.
*/
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));
- opaque是
read_packet
/write_packet
的第一個參數,指向用戶數據。 - buffer和buffer_size是
read_packet
/write_packet
的第二個和第三個參數,是供FFmpeg使用的數據區。
buffer
用作FFmpeg輸入時,由用戶負責向buffer
中填充數據,FFmpeg取走數據。
buffer
用作FFmpeg輸出時,由FFmpeg負責向buffer
中填充數據,用戶取走數據。 - write_flag是緩沖區讀寫標志,讀寫的主語是指FFmpeg。
write_flag
為1時,buffer
用於寫,即作為FFmpeg輸出。
write_flag
為0時,buffer
用於讀,即作為FFmpeg輸入。 - read_packet和write_packet是函數指針,指向用戶編寫的回調函數。
3.6 URL-IO模式:avio_open()
/**
* Create and initialize a AVIOContext for accessing the
* resource indicated by url.
* @note When the resource indicated by url has been opened in
* read+write mode, the AVIOContext can be used only for writing.
*
* @param s Used to return the pointer to the created AVIOContext.
* In case of failure the pointed to value is set to NULL.
* @param url resource to access
* @param flags flags which control how the resource indicated by url
* is to be opened
* @return >= 0 in case of success, a negative value corresponding to an
* AVERROR code in case of failure
*/
int avio_open(AVIOContext **s, const char *url, int flags);
4. 修改記錄
2019-01-24 V1.0 初稿