插件开发内存管理
Gstmemory 设计文档
Gstmemory API参考
本文讨论GStreamer插件的内存管理。包括
- GstMemory,对内存访问的底层对象;
- GstBuffer,它用于在插件之间和应用程序之间交换数据。
- GstMeta。这个对象可以被放置在GstBuffer中,提供关联内存的额外信息。
- GstBufferPool,它可以用来更有效地批量管理具有相同大小的缓冲区。
- GstAllocator 内存分配器和自定义的实现.
1. Gstmemory
GstMemory一般是通过gst_allocator_alloc创建的一个管理内存区域的对象。这个内存对象指向一个“maxsize”大小的内存区域。该内存中可访问的区域是从“offset”和到“size”字节之间的部分。
struct _GstMemory {
GstMiniObject mini_object;
GstAllocator *allocator;
GstMemory *parent;
gsize maxsize;
gsize align;
gsize offset;
gsize size;
};
在创建GstMemory之后,它的maxsize不能在整个生命周期内都不能被更改,但是它的“偏移量”和“大小”可以被更改。
可以使用gsize gst_memory_get_sizes (GstMemory *mem, gsize *offset, gsize *maxsize)来获取属性。可以通过gst_memory_resize 改变其大小。
访问数据需要通过map和unmap函数来进行。
gst_memory_copy拷贝一般是相同的allocator按照自定义的方式创建一个相同类型的memory,然后复制可见的数据部分。
gst_memory_share是直接共享该内存对象,不执行内存复制,只共享内存区域。
可以使用gst_memory_new_wrapped来封装已经存在的一段内存。
[...]
GstMemory *mem;
GstMapInfo info;
gint i;
/* allocate 100 bytes */
mem = gst_allocator_alloc (NULL, 100, NULL);
/* get access to the memory in write mode */
gst_memory_map (mem, &info, GST_MAP_WRITE);
/* fill with pattern */
for (i = 0; i < info.size; i++)
info.data[i] = i;
/* release memory */
gst_memory_unmap (mem, &info);
[...]
2. GstBuffer
https://gstreamer.freedesktop.org/documentation/gstreamer/gstbuffer.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/additional/design/buffer.html?gi-language=c
GstBuffer 用来在Gstreamer pipeline上下游元件相互传递数据。因此它包含两个部分:GstMemory ,以及metadata(GstMemory 对应的描述信息)。
struct _GstBuffer {
GstMiniObject mini_object;
/*< public >*/ /* with COW */
GstBufferPool *pool;
/* timestamp */
GstClockTime pts;
GstClockTime dts;
GstClockTime duration;
/* media specific offset */
guint64 offset;
guint64 offset_end;
};
GstBuffer 需要满足可分配/释放、低碎片化、支持加入多块内存或子buffer、支持任意元数据、支持扩展/拷贝/分割等。
一个GstBuffer可能包含多个GstMemory ,存放实际的数据。
metadata一般有很多信息。
- DTS 和 PTS用于解码和显示的同步。
- duration,内容持续时间。
- 特定媒体的offset和offset_end值。对于视频,这是流中的帧数,对于音频,这是样本数。
- 其它任意GstMeta结构。
Gst_buffer_ref用于增加缓冲区的refcount。当您希望在将缓冲区推到下一个元素后保留该缓冲区的句柄时,必须这样做。
gst_buffer_append可以有效地合并buffer到一个更大的buffer中。只有在绝对需要的时候才会复制内存。
元素要么取消缓冲区的引用,要么使用gst_pad_push将其推出到src pad上。否则可能内存泄露。当引用计数为0的时候,memory和所有的metadata都会被取消引用,从bufferpool中分配的buffer会归还到pool。
[...]
GstBuffer *buffer;
GstMemory *mem;
GstMapInfo info;
/* make empty buffer */
buffer = gst_buffer_new ();
/* make memory holding 100 bytes */
mem = gst_allocator_alloc (NULL, 100, NULL);
/* add the buffer */
gst_buffer_append_memory (buffer, mem);
[...]
/* get WRITE access to the memory and fill with 0xff */
gst_buffer_map (buffer, &info, GST_MAP_WRITE);
memset (info.data, 0xff, info.size);
gst_buffer_unmap (buffer, &info);
[...]
/* free the buffer */
gst_buffer_unref (buffer);
[...]
2.1 创建
可以使用gst_buffer_new()创建一个GstBuffer,然后用gst_buffer_append_memory向它添加GstMemory 内存对象。
也可以使用方便的函数gst_buffer_new_allocate()一次执行这两个操作。
另外,可以使用gst_buffer_new_wrapped()来包装现有内存,该函数将创建GstMemory和GstBuffer,把某一内存地址填进去,不会进行memcpy。gst_buffer_new_wrapped_full()可以指定应该释放内存时调用的函数。
2.2 读写
一个GstBuffer,只有当其引用计数为1的的时候才可以写。因此需要通过gst_buffer_make_writable() 判断之后,才能进行更改时间戳、偏移量、元数据或添加和删除内存块。
读写GstMemory 数据时,可以通过单独获取和映射GstMemory对象。也可以使用gst_buffer_map()来访问GstBuffer的内存,它会将所有内存拷贝到一个大块,然后返回一个指针。map整个buffer更加方便,但是会涉及memcpy操作,所以不划算。当然buffer里面只有一个memory的时候是一样的。
2.2 增删
gst_buffer_insert_memory可以往指定的位置插入memory,gst_buffer_append_memory是自动插入到最后的位置(实际append是调用insert函数位置参数为-1)。gst_buffer_n_memory用来获取当前buffer中memory的数量。
gst_buffer_remove_memory可以移除buffer中的memory。
2.3 合并
gst_buffer_map_range可以将多个buffer合并到一个buffer。同样涉及到大量数据拷贝,一般很少使用.
3. GstMeta
struct _GstMeta {
GstMetaFlags flags;
const GstMetaInfo *info;
};
struct _GstMetaInfo {
GType api;
GType type;
gsize size;
GstMetaInitFunction init_func;
GstMetaFreeFunction free_func;
GstMetaTransformFunction transform_func;
};
GstMeta用来额外添加其它任意描述数据,如剪切、跨越、感兴趣区域等。我们可以通过GstMeta定义自己想要的数据,用来向下游传递。当然,有一套接口来做些操作。需要定义相关类和接口。在buffer释放的时候会自动调用free_func函数。
4. GstAllocator
GstMemory对象是由GstAllocator对象创建的。大多数分配器Allocator实现默认的gst_allocator_alloc()方法。但有些可能实现不同的方法,例如,当需要额外的参数来分配特定的DMA连续内存,或者从某模组内部来分配。gst_allocator_alloc参数没有指定分配器的话(NULL)使用默认分配器。
struct _GstAllocator
{
GstObject object;
const gchar *mem_type;
/*< public >*/
GstMemoryMapFunction mem_map;
GstMemoryUnmapFunction mem_unmap;
GstMemoryCopyFunction mem_copy;
GstMemoryShareFunction mem_share;
GstMemoryIsSpanFunction mem_is_span;
GstMemoryMapFullFunction mem_map_full;
GstMemoryUnmapFullFunction mem_unmap_full;
/*< private >*/
gpointer _gst_reserved[GST_PADDING - 2];
GstAllocatorPrivate *priv;
};
4.1分配参数
使用指定allocator来分配buffer的时候,可以配置参数GstAllocationParams:
struct _GstAllocationParams {
GstMemoryFlags flags; //内存标记
gsize align; //对齐长度-1
gsize prefix; //起始地址偏移
gsize padding; //
[...]
注意, align为对齐长度的值再减1. GST_MEMORY_FLAG_ZERO_PREFIXED和GST_MEMORY_FLAG_ZERO_PADDED分别是将对应区域清零. 其分布如下:
0-----------offset--------size------maxsize
|—prefixed—|---size—|---padded—|
由上图可以,实际分配的内存大小为maxsize=size + prefix + padding;
4.2 GstMemory API示例
对由GstMemory对象包装的内存的数据访问,总是受到gst_memory_map()和gst_memory_unmap()对的保护。当映射内存时,必须给出一个访问模式(读/写)。map函数返回一个指向有效内存区域的指针,然后可以根据请求的访问模式访问该内存区域。
下面是一个关于创建GstMemory对象并使用gst_memory_map()访问内存区域的示例。
GstMemory *mem;
GstMapInfo info;
gint i;
/* allocate 100 bytes */
mem = gst_allocator_alloc (NULL, 100, NULL);
/* get access to the memory in write mode */
gst_memory_map (mem, &info, GST_MAP_WRITE);
/* fill with pattern */
for (i = 0; i < info.size; i++)
info.data[i] = i;
/* release memory */
gst_memory_unmap (mem, &info);
4.3 实现一个分配器
对于系统内存、共享内存和由DMAbuf文件描述符支持的内存,都存在不同的分配器。如果要添加一种新的内存类型,就必须实现一个新的allocator对象。allocator实现需要继承GstAllocator,实现其对应的alloc和free接口。
struct _GstAllocatorClass {
GstObjectClass object_class;
/*< public >*/
GstMemory * (*alloc) (GstAllocator *allocator, gsize size, GstAllocationParams *params);
void (*free) (GstAllocator *allocator, GstMemory *memory);
/*< private >*/
gpointer _gst_reserved[GST_PADDING];
};
alloc的时候可能并没有真正分配,后续在需要读写的时候,调用gst_memory_map才具有可读写的实际内存。
static void my_memory_allocator_class_init (MyMemoryAllocatorClass * klass)
{
GstAllocatorClass *allocator_class;
allocator_class = (GstAllocatorClass *) klass;
allocator_class->alloc = _my_alloc;
allocator_class->free = _my_free;
}
static void my_memory_allocator_init (MyMemoryAllocator * allocator)
{
GstAllocator *alloc = GST_ALLOCATOR_CAST (allocator);
alloc->mem_type = "MyMemory";
alloc->mem_map = (GstMemoryMapFunction) _my_mem_map;
alloc->mem_unmap = (GstMemoryUnmapFunction) _my_mem_unmap;
alloc->mem_share = (GstMemoryShareFunction) _my_mem_share;
}
void my_memory_init (void)
{
GstAllocator *allocator;
allocator = g_object_new (my_memory_allocator_get_type (), NULL);
gst_allocator_register ("MyMemory", allocator);
}
使用gst_allocator_register向系统注册了新的allocator之后,可以通过注册的名字name来检索到该allocator。
alloc = gst_allocator_find ("MyMemory");
gst_allocation_params_init (¶ms);
mem = gst_allocator_alloc (alloc, 1024, ¶ms);
gst_memory_map (mem, &info, GST_MAP_READ);
之后在gst_memory_map时候,会通过memory的allocator指针,调用到之前设置的_my_mem_map函数。如果没有设置对应函数,则会调用到父类GstAllocator的对应函数.
gboolean gst_memory_map (GstMemory * mem, GstMapInfo * info, GstMapFlags flags)
{
[...]
mem->allocator->mem_map (mem, mem->maxsize, flags);
5. GstBufferList
当需要一次性推送多个Buffer的时候,可以用GstBufferList用来封装多个GstBuffer,然后用gst_pad_push_list来推送到下游。