本文为作者原创,转载请注明出处: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_packetwrite_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成员必须设置为一个已经打开的AVIOContextAVFormatContext.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));
  • opaqueread_packet/write_packet的第一个参数,指向用户数据。
  • bufferbuffer_sizeread_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_packetwrite_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模式(内存区作输入或输出)的更多相关文章

  1. 输入和输出--java的NIO

    Java的NIO 实际开发中NIO使用到的并不多,我并不是说NIO使用情景不多,是说我自己接触的并不是很多,前面我在博客园和CSDN上转载了2篇别人写的文章,这里来大致总结下Java的NIO,大概了解 ...

  2. 了解一下C++输入和输出的概念

    我们经常用到的输入和输出,都是以终端为对象的,即从键盘输入数据,运行结果输出到显示器屏幕上.从操作系统的角度看,每一个与主机相连的输入输出设备都被看作一个文件.除了以终端为对象进行输入和输出外,还经常 ...

  3. C++学习42 输入和输出的概念

    我们经常用到的输入和输出,都是以终端为对象的,即从键盘输入数据,运行结果输出到显示器屏幕上.从操作系统的角度看,每一个与主机相连的输入输出设备都被看作一个文件.除了以终端为对象进行输入和输出外,还经常 ...

  4. 使用WinIO库实现保护模式下的IO和内存读写

    问题已解决: 原因是函数的调用方式与WinIO中不一致,使用的时候漏掉了__stdcall. 函数原定义为: 在实际的GPIO读写中遇到以下问题: SetPortVal可正常写入,但是GetPortV ...

  5. System.IO之内存映射文件共享内存

    内存映射文件是利用虚拟内存把文件映射到进程的地址空间中去,在此之后进程操作文件,就 像操作进程空间里的地址一样了,比如使用c语言的memcpy等内存操作的函数.这种方法能够很好的应用在需要频繁处理一个 ...

  6. Linux操作系统基础(四)保护模式内存管理(2)【转】

    转自:http://blog.csdn.net/rosetta/article/details/8570681 Linux操作系统基础(四)保护模式内存管理(2) 转载请注明出处:http://blo ...

  7. java学习笔记之IO编程—内存流、管道流、随机流

    1.内存操作流 之前学习的IO操作输入和输出都是从文件中来的,当然,也可以将输入和输出的位置设置在内存上,这就需要用到内存操作流,java提供两类内存操作流 字节内存操作流:ByteArrayOutp ...

  8. 统计和分析系统性能【IO CPU 内存】的工具集合

    统计和分析系统性能[IO CPU 内存]的工具集合 blktrace http://www.oschina.net/p/blktrace 获取磁盘写入的信息 root@demo:~/install/p ...

  9. iOS程序中的内存分配 栈区堆区全局区

    在计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同.一.内存分区 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等.栈是系统数据结构,对 ...

随机推荐

  1. 简易数据库实现 UNIX环境高级编程(APUE)第二十章 A Database Library

    将课程的源代码 使用C++写了一部分 LINUX WINDOW均可运行 #ifndef MYDB_H #define MYDB_H #include <iostream> #include ...

  2. CMD命令启动和关闭SQL服务

    1.开启:net start mssqlserver 2.关闭:net stop mssqlserver

  3. 搭建django

    虚拟环境配置 pip install virtualenv 一般需要安装一个管理工具:pip install virtualenvwrapper-win 只有它可以使用workon 创建虚拟环境:mk ...

  4. JavaSE 初学进度条JProgressBar

    预备知识 创建进度条类后将其直接加入JFrame看看效果 public class JProgressBarDemo2 { public static void main(String args[]) ...

  5. poj3130 (半平面交

    题意:判断是否存在内核. 半平面交存板子. /* gyt Live up to every day */ #include<cstdio> #include<cmath> #i ...

  6. Python Day 3

    阅读目录: 内容回顾: 变量(标识符)的命名规范: 常量: 格式化输入\输出: 注释: 基本数据类型: 运算符: ##内容回顾 1.语言的分类: -- 机器语言:直接编写0,1指令,直接能被硬件执行. ...

  7. input实现图片或视频上传(样式+代码)

    背景:vue/element.ui 1..html: <div v-show="recordForm.resourceType==1"> <el-form-ite ...

  8. Python序列结构

    python中常用的序列结构由列表.元组.字典.字符串.集合等,列表.元组.字符串等有序序列以及range对象均支持双向索引 是否有序 序列结构 是否是可变序列 有序序列 元组 不可变序列 有序序列 ...

  9. CUDA并行编程思维过程

    CUDA并行编程思维过程 1)确定应用程序中需要且可以并行化的部分 2)将并行化代码中需要用到的数据分离出来,具体方法是用API函数在并行技术设备上分配内存空间 3)用API函数将数据传输到并行计算设 ...

  10. We FALL ASleep At Night, We Do REST Right

    We Do Sleep At Night, We Do REST Right 前言 REST 起源 REST 约束 客户端 - 服务端 无状态 缓存 统一接口 分层系统 按需代码 统一接口约束 资源识 ...