插件开发内存管理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来推送到下游。