ffmpeg-AVIOContext

本文要介绍的结构体是 AVIOContext.
AVIOContext 是 字节流 IO Context,管理输入输出数据

注意:AVIOContext 里的函数指针不能直接调用,它们应该让客户端程序在自定义 I/O 时设置。

在读取和写入时,buffer,buf_ptr,buf_ptr_max, buf_end, buf_size, pos 之间的关系如图,

**********************************************************************************
*                                   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: |             |                                              |
*               +-------------+----------------------------------------------+
*

AVIOContext 结构在 libformat/avio.h 文件中,

typedef struct AVIOContext {
    /**
    * 私有选项 class
    *
    * 通过 avio_open2() 创建 AVIOContext 时, av_class会被设置* 同时传递 options 给 protocols
    *
    * 如果 AVIOContext 是手动分配的, 那么 av_class 则可以被调用* 者设置
    *
    * 警告 --这个变量可能为 NULL, 要保证不传递 AVIOContext 给
    * 任何 av_opt_* 函数.
    */
    const AVClass *av_class;

    unsigned char *buffer;  /**< 缓存. */
    int buffer_size;        /**< 最大缓存大小 */
    unsigned char *buf_ptr; /**< 当前的缓存位置 */
    unsigned char *buf_end; /**< 缓存结束的位置. */
    /**< 私有指针, 传递给 read/write/seek/...函数. */
    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;
    int eof_reached;        /**< true if was unable to read due to error or eof */
    int write_flag;         /**< true if open for writing */
    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 */
    /**
    * 暂停或者恢复播放网络流协议,如 MMS.
    */
    int (*read_pause)(void *opaque, int pause);
    /**
    * 在指定的流(stream_index) 中寻找给定的时间戳.
    * 一些不支持字节位置寻找的网络流协议需要该API。
    */
    int64_t (*read_seek)(void *opaque, int stream_index,
                        int64_t timestamp, int flags);
    int seekable;

    /**
    * 最大文件大小, 用来限制分配
    * libavformat 访问,外部不能访问该变量
    */
    int64_t maxsize;

    /**
    * avio_read and avio_write should if possible be satisfied directly
    * instead of going through a buffer, and avio_seek will always
    * call the underlying seek function directly.
    */
    int direct;

    /**
    * Bytes read statistic
    * This field is internal to libavformat and access from outside is not allowed.
    */
    int64_t bytes_read;

    /**
    * seek statistic
    * This field is internal to libavformat and access from outside is not allowed.
    */
    int seek_count;

    /**
    * 写入统计
    * This field is internal to libavformat and access from outside is not allowed.
    */
    int writeout_count;

    /**
    * 原本的缓存大小( libavformat 内部字段)
    * used internally after probing and ensure seekback to reset the buffer size
    */
    int orig_buffer_size;

    /**
    * Threshold to favor readahead over seek.
    * This is current internal only, do not use from outside.
    */
    int short_seek_threshold;

    /**
    * ',' 分隔,协议白名单
    */
    const char *protocol_whitelist;

    /**
    * ',' 分隔,协议黑名单
    */
    const char *protocol_blacklist;

    /** 替代 write_packet 的回调. */
    int (*write_data_type)(void *opaque, uint8_t *buf, int buf_size,
                        enum AVIODataMarkerType type, int64_t time);
    /**
    * If set, don't call write_data_type separately for AVIO_DATA_MARKER_BOUNDARY_POINT,
    * but ignore them and treat them as AVIO_DATA_MARKER_UNKNOWN (to avoid needlessly
    * small chunks of data returned from the callback).
    */
    int ignore_boundary_point;

    /**
    * Internal, not meant to be used from outside of AVIOContext.
    */
    enum AVIODataMarkerType current_type;
    int64_t last_time;

    /**
    * A callback that is used instead of short_seek_threshold.
    * This is current internal only, do not use from outside.
    */
    int (*short_seek_get)(void *opaque);

    int64_t written;

    /**
    * Maximum reached position before a backward seek in the write buffer,
    * used keeping track of already written data for a later flush.
    */
    unsigned char *buf_ptr_max;

    /**
    * Try to buffer at least this amount of data before flushing it
    */
    int min_packet_size;
} AVIOContext;

AVIOContext 通过 avio_alloc_context(…) 初始化

AVIOContext* avio_alloc_context    (
    unsigned char *     buffer,
    int     buffer_size,
    int     write_flag,
    void *     opaque,
    int(*)(void *opaque, uint8_t *buf, int buf_size)     read_packet,
    int(*)(void *opaque, uint8_t *buf, int buf_size)     write_packet,
    int64_t(*)(void *opaque, int64_t offset, int whence)     seek 
)    

分配和初始化 AVIOContext 来做 I/O 缓存. 使用完后必须调用
avio_context_free() 来释放内存

参数:

  • buffer:AVIOContext 执行 IO 操作的缓存区. 通过 av_malloc() 先分配大小. 可以通过 libavformat 来释放或使用新的缓冲区替代. AVIOContext 缓冲区里持有当前的缓冲, 之后必须通过 av_free() 来释放.

  • buffer_size:缓冲大小,会影响性能. 对于固定 blocksize 的协议,则要设置为 blocksize. 对于其他情况,典型的大小是一个缓冲页大小, e.g. 4kb.

  • write_flag:1 缓冲区可执行写操作, 0 不能执行写操作

  • opaque:一个 opaque 指向用户特定数据.
  • read_packet:IO的读取回调函数,用于重新填充缓冲区,可以为NULL. 对于流协议, 决不能返回0,而必须返回正确的AVERROR代码.
  • write_packet:IO的写回调函数,将缓冲区内容写入, 可为 NULL. 该函数不会修改输入的内容
  • seek:寻找指定位置的 seek 函数,可为 NULL.

通过 av_freep 释放 AVIOContext 指针

av_freep(&avio_ctx);

Example

使用 libavformat demuxer,通过自定义一个 AVIOContext 读取 callback 访问多媒体内容.

完整代码

struct buffer_data {
    uint8_t *ptr;
    size_t size; ///< size left in the buffer
};

static int read_packet(void *opaque, uint8_t *buf, int buf_size)
{
    struct buffer_data *bd = (struct buffer_data *)opaque;
    buf_size = FFMIN(buf_size, bd->size);
    if (!buf_size)
        return AVERROR_EOF;
    printf("ptr:%p size:%zu\n", bd->ptr, bd->size);
    /* copy internal buffer data to buf */
    memcpy(buf, bd->ptr, buf_size);
    bd->ptr  += buf_size;
    bd->size -= buf_size;
    return buf_size;
}

int main(int argc, char *argv[]) {
    AVFormatContext *fmt_ctx = NULL;
    AVIOContext *avio_ctx = NULL;
    uint8_t *buffer = NULL, *avio_ctx_buffer = NULL;
    size_t buffer_size, avio_ctx_buffer_size = 4096;
    char *input_filename = NULL;
    int ret = 0;
    struct buffer_data bd = { 0 };
    if (argc != 2) {
        fprintf(stderr, "usage: %s input_file\n"
                "API example program to show how to read from a custom buffer "
                "accessed through AVIOContext.\n", argv[0]);
        return 1;
    }
    input_filename = argv[1];
    /* 读取文件内容到缓冲区 */
    ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
    if (ret < 0)
        goto end;
    /* 通过 AVIOContext callback 填充 opaque 结构体 */
    bd.ptr  = buffer;
    bd.size = buffer_size;
    if (!(fmt_ctx = avformat_alloc_context())) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    // 分配 avio_ctx_buffer 内存空间
    avio_ctx_buffer = av_malloc(avio_ctx_buffer_size);
    if (!avio_ctx_buffer) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    // 初始化 AVIOContext
    avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,
                                0, &bd, &read_packet, NULL, NULL);
    if (!avio_ctx) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    fmt_ctx->pb = avio_ctx;
    ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open input\n");
        goto end;
    }
    ret = avformat_find_stream_info(fmt_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not find stream information\n");
        goto end;
    }
    av_dump_format(fmt_ctx, 0, input_filename, 0);
end:
    // 关闭 AVFormatContext
    avformat_close_input(&fmt_ctx);
    /* note: the internal buffer could have changed, and be != avio_ctx_buffer */
    if (avio_ctx) {
        // 释放内存
        av_freep(&avio_ctx->buffer);
        av_freep(&avio_ctx);
    }
    av_file_unmap(buffer, buffer_size);
    if (ret < 0) {
        fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
        return 1;
    }
    return 0;
}