前段时间在开发内存方式的音视频解码接口,所谓内存方式解码,就是解码器的输入是一段buffer数据,不像文件解码那样可以通过文件名去获取一些格式信息,内存解码需要去解析buffer中的二进制码流来得到输入数据的封装格式和编码格式。FFMPEG中其实已经给了内存读取的样例,可以拿来作为参考,下面就用avio_reading.c里面的代码来做一点分析。
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavformat/avio.h>
#include <libavutil/file.h>
定义一个结构体,用来存放输入内存地址及其剩余大小
struct buffer_data {
uint8_t *ptr;
size_t size; ///< size left in the buffer
};
定义一个从内存中读取数据的函数,在avio_alloc_context中通过函数指针方式绑定,并在以后的数据读取中调用,函数的功能就是从地址opaque中读取数据到地址buf中,buf_size是每次读取的数据大小。
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);
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];
/* register codecs and formats and other lavf/lavc components*/
av_register_all();
/* 将文件内容读入到一段内存中 */
/* slurp file content into buffer */
ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL);
if (ret < 0)
goto end;
/*初始化输入指针*/
/* fill opaque structure used by the AVIOContext read callback */
bd.ptr = buffer;
bd.size = buffer_size;
/* 为结构体AVFormatContext指针分配空间并初始化 */
if (!(fmt_ctx = avformat_alloc_context())) {
ret = AVERROR(ENOMEM);
goto end;
}
/* 分配一个buffer,用于存放从内存中读取到的数据,如果输入数据量大于该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;
}
/* 将分配好的AVIOContext赋值给AVFormatContext中的AVIOContext指针 */
fmt_ctx->pb = avio_ctx;
/* 现在,我们就可以通过avformat_open_input读取内存数据并获取数据基本信息了,由于所有信息未知,因此,函数后三个参数均为NULL */
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;
}
/* 打印第0个输入流的详细信息 */
av_dump_format(fmt_ctx, 0, input_filename, 0);
end:
/* 操作完成后,关闭AVFormatContext,释放分配的内存空间 */
avformat_close_input(&fmt_ctx);
/*下面这一句一定不能省!!!因为avio_ctx在前面是由我们自己分配的,在使用过程中,*/
/*avio_ctx地址可能会发生变化,包括其关联的buffer地址也会变化,因此,avformat_close_input在释放时,*/
/*可能无法释放这部分内存,在我自己的解码器中,就遇到了这个问题,以为调用avformat_close_input就万事大吉了,*/
/*没想到解码许多文件时,内存蹭蹭往上涨,直到耗尽所有内存资源。查了好久才发现这个大坑!*/
/* 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;
}