天天看點

FFmpeg記憶體IO模式(記憶體區作輸入或輸出)

作者: 葉餘 來源: 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);
    
    ......
}      

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 初稿

「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。
FFmpeg記憶體IO模式(記憶體區作輸入或輸出)

繼續閱讀