本文要介绍的结构体是 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;
}