1 GstMemory基本概念

  • GstMemory 是一个轻量级的引用计数对象(继承于GstMiniObject),用于包装内存区域。
  • GstBuffer 的就是由多个 GstMemory 构成。一个 GstMemory 对象有一个分配的内存区域,最大大小为 maxsize。在内存对象的生命周期内,最大大小不会改变。内存还有一个偏移量和大小属性,用于指定分配区域内有效的内存范围。
  • GstMemory 通常由分配器通过 gst_allocator_alloc 方法调用创建。当使用 NULL 作为分配器时,将使用默认分配器。可以通过 gst_allocator_register 注册新的分配器。分配器通过名称标识,并可以通过 gst_allocator_find 检索。可以使用 gst_allocator_set_default 来更改默认分配器。
  • 可以使用 gst_memory_new_wrapped 创建新的内存,它包装在其他地方分配的内存。
  • 对内存块的引用计数是通过 gst_memory_ref 和 gst_memory_unref 来执行的。
  • 可以分别使用 gst_memory_get_sizes 和 gst_memory_resize 来检索和更改内存的大小。
  • 获取对内存数据的访问是通过 gst_memory_map 来执行的。调用将返回一个指向内存区域中偏移量字节的指针。在完成内存访问后,应该调用 gst_memory_unmap。
  • 可以通过 gst_memory_copy 来复制内存,这将返回一个可写的副本。gst_memory_share 将创建一个新的内存块,它在自定义偏移量和自定义大小下与现有内存块共享内存。
  • 当 gst_memory_is_span 返回 TRUE 时,可以有效地合并内存。

1.1 GstMapInfo结构体

内存映射操作函数gst_memory_map的结构,存储在GstMapInfo结构体中。关键就是data成员,实际存储数据的地址。

/* filename: gstmemory.h */
/**
 * GstMapInfo:
 * @memory: 映射内存的指针
 * @flags: 映射内存时使用的标志
 * @data: (array length=size): 映射数据的指针
 * @size: 在 @data 中的有效大小
 * @maxsize: 在 @data 中的最大字节数
 * @user_data: 内存实现可以使用的额外私有用户数据,用于存储额外信息。
 *
 * 一个包含映射操作结果的结构体,例如 gst_memory_map()。它包含了数据和大小。
 *
 * #GstMapInfo 不能与 g_auto() 一起使用,因为不确定它是否需要使用 gst_buffer_unmap() 或 gst_memory_unmap() 来解映射。相反,可以在这种情况下使用 #GstBufferMapInfo 和 #GstMemoryMapInfo。
 */
 typedef struct {
  GstMemory *memory;
  GstMapFlags flags;
  guint8 *data;
  gsize size;
  gsize maxsize;
  /*< protected >*/
  gpointer user_data[4];

  /*< private >*/
  gpointer _gst_reserved[GST_PADDING];
} GstMapInfo;

2 GstMemory类型结构

2.1 GstMemory类型注册宏定义

/* filename: gstmemory.h */
GST_API GType _gst_memory_type;
#define GST_TYPE_MEMORY (_gst_memory_type)

GST_API
GType gst_memory_get_type(void);

/* filename: gstmemory.c */
GType _gst_memory_type = 0;
GST_DEFINE_MINI_OBJECT_TYPE (GstMemory, gst_memory);

/* gst_init调用该函数 */
void
_priv_gst_memory_initialize (void)
{
  _gst_memory_type = gst_memory_get_type ();
}

2.2 GstMemory类型相关枚举定义

2.2.1 GstMemoryFlags

/* filename: gstmemory.h */
/**
 * GstMemoryFlags:
 * @GST_MEMORY_FLAG_READONLY: 内存是只读的。不允许使用 #GST_MAP_WRITE 映射内存。
 * @GST_MEMORY_FLAG_NO_SHARE: 内存不得共享。当需要在缓冲区之间共享此内存时,必须制作副本。(已弃用:
 * 在新代码中不要使用,你应该为内存池创建一个自定义的 GstAllocator,而不是依赖于它们最初附加到的 GstBuffer。)
 * @GST_MEMORY_FLAG_ZERO_PREFIXED: 内存前缀被填充为 0 字节
 * @GST_MEMORY_FLAG_ZERO_PADDED: 内存填充被填充为 0 字节
 * @GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS: 内存在物理上是连续的。(自版本 1.2 起)
 * @GST_MEMORY_FLAG_NOT_MAPPABLE: 内存不能通过 gst_memory_map() 映射,无需任何前提条件。(自版本 1.2 起)
 * @GST_MEMORY_FLAG_LAST: 可用于自定义目的的第一个标志
 *
 * 包装内存的标志。
 */
typedef enum {
  GST_MEMORY_FLAG_READONLY      = GST_MINI_OBJECT_FLAG_LOCK_READONLY,
  GST_MEMORY_FLAG_NO_SHARE      = (GST_MINI_OBJECT_FLAG_LAST << 0),
  GST_MEMORY_FLAG_ZERO_PREFIXED = (GST_MINI_OBJECT_FLAG_LAST << 1),
  GST_MEMORY_FLAG_ZERO_PADDED   = (GST_MINI_OBJECT_FLAG_LAST << 2),
  GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS = (GST_MINI_OBJECT_FLAG_LAST << 3),
  GST_MEMORY_FLAG_NOT_MAPPABLE  = (GST_MINI_OBJECT_FLAG_LAST << 4),

  GST_MEMORY_FLAG_LAST          = (GST_MINI_OBJECT_FLAG_LAST << 16)
} GstMemoryFlags;

2.2.2 GstMapFlags

/* filename: gstmemory.h */
/**
 * GstMapFlags:
 * @GST_MAP_READ: 用于读取访问的映射
 * @GST_MAP_WRITE: 用于写入访问的映射
 * @GST_MAP_FLAG_LAST: 可用于自定义目的的第一个标志
 *
 * 映射内存时使用的标志
 */

typedef enum {
  GST_MAP_READ      = GST_LOCK_FLAG_READ,
  GST_MAP_WRITE     = GST_LOCK_FLAG_WRITE,

  GST_MAP_FLAG_LAST = (1 << 16)
} GstMapFlags;

2.3 GstMemory结构体

-关于parent成员:用户可能通过share函数,共享data内存空间,创建一个新的GstMemory。此时parent指向原始的GstMemory。

/**
 * GstMemory:
 * @mini_object: 父结构
 * @allocator: 指向 #GstAllocator 的指针
 * @parent: 父内存块
 * @maxsize: 分配的最大大小
 * @align: 内存的对齐方式
 * @offset: 有效数据开始的偏移量
 * @size: 有效数据的大小
 *
 * 用于内存实现的基础结构。自定义内存将把此结构作为它们结构的第一个成员。
 */
struct _GstMemory {
  GstMiniObject   mini_object;

  GstAllocator   *allocator;

  GstMemory      *parent;
  gsize           maxsize;
  gsize           align;
  gsize           offset;
  gsize           size;
};

3 GstMemory对象相关函数总结

3.1 GstMemory内存映射和解映射

因为GstMemory结构体中并不包含用户存储数据的data,通常用户会根据实际需要创建一个继承于GstMemory的结构体,存储data。使用用户继承于的GstAllocator的分配器,分配GstMemory内存。映射函数就是把data经过一些处理(偏移等操作),赋值到 GstMapInfo 结构体中。

映射和解映射函数实际调用的是,分配器中GstAllocator(分配GstMemory的分配器)中存储的mem_map_full或mem_mapmem_unmap_full或mem_unmap函数。

3.1.1 gst_memory_map

/**
 * gst_memory_map:
 * @brief: 因为GstMemory结构体中并不包含用户存储数据的data,通常用户会根据实际需要创建一个继承于GstMemory的结构体,存储data。
 *         使用用户继承于的GstAllocator的分配器,分配GstMemory内存。
 *         map函数就是把data经过一些处理(偏移等操作),赋值到GstMapInfo结构体中。
 * @mem: 一个 #GstMemory
 * @info: (out caller-allocates): 用于信息的指针
 * @flags: 映射标志
 *
 * 根据 @flags,用 @mem 中可访问的内存的指针和大小填充 @info。
 *
 * 出于各种原因,此函数可能返回 %FALSE:
 * - 由 @mem 支持的内存不能用给定的 @flags 访问。
 * - 内存已经以不同的映射方式被映射过。
 *
 * 只要 @mem 有效,并且直到调用 gst_memory_unmap(),@info 及其内容就保持有效。
 *
 * 对于每个 gst_memory_map() 调用,应该执行相应的 gst_memory_unmap() 调用。
 *
 * 返回值:如果映射操作成功,则返回 %TRUE。
 */
gboolean
gst_memory_map (GstMemory * mem, GstMapInfo * info, GstMapFlags flags)
{
  g_return_val_if_fail (mem != NULL, FALSE);
  g_return_val_if_fail (info != NULL, FALSE);

  if (!gst_memory_lock (mem, (GstLockFlags) flags))
    goto lock_failed;

  info->flags = flags;
  info->memory = mem;
  info->size = mem->size;
  info->maxsize = mem->maxsize - mem->offset;

  /* 关键部分 info->data才是存储用户数据的指针 */
  if (mem->allocator->mem_map_full)
    info->data = mem->allocator->mem_map_full (mem, info, mem->maxsize);
  else
    info->data = mem->allocator->mem_map (mem, mem->maxsize, flags);

  if (G_UNLIKELY (info->data == NULL))
    goto error;

  info->data = info->data + mem->offset;

  return TRUE;

  /* ERRORS */
lock_failed:
  {
    GST_CAT_DEBUG (GST_CAT_MEMORY, "mem %p: lock %d failed", mem, flags);
    memset (info, 0, sizeof (GstMapInfo));
    return FALSE;
  }
error:
  {
    /* something went wrong, restore the original state again
     * it is up to the subclass to log an error if needed. */
    GST_CAT_INFO (GST_CAT_MEMORY, "mem %p: subclass map failed", mem);
    gst_memory_unlock (mem, (GstLockFlags) flags);
    memset (info, 0, sizeof (GstMapInfo));
    return FALSE;
  }
}

3.1.2 gst_memory_unmap

/**
 * gst_memory_unmap:
 * @mem: 一个 #GstMemory
 * @info: 一个 #GstMapInfo
 *
 * 释放通过 gst_memory_map() 获取的内存。
 */
void
gst_memory_unmap (GstMemory * mem, GstMapInfo * info)
{
  g_return_if_fail (mem != NULL);
  g_return_if_fail (info != NULL);
  g_return_if_fail (info->memory == mem);

  if (mem->allocator->mem_unmap_full)
    mem->allocator->mem_unmap_full (mem, info);
  else
    mem->allocator->mem_unmap (mem);
  gst_memory_unlock (mem, (GstLockFlags) info->flags);
}

3.2 GstMemory检索内存和更改内存

查看源代码,gst_memory_resize仅仅是修改了存储数据GstMemory->size,实际的内存大小并没有修改。

gsize
gst_memory_get_sizes (GstMemory * mem, gsize * offset, gsize * maxsize);


void
gst_memory_resize (GstMemory * mem, gssize offset, gsize size);

3.3 GstMemory拷贝、共享和合并内存函数

本质上都是调用的分配器下的实例结构体中存储的拷贝、共享、合并函数。

GstMemory *
gst_memory_copy (GstMemory * mem, gssize offset, gssize size);

GstMemory *
gst_memory_share (GstMemory * mem, gssize offset, gssize size);

gboolean
gst_memory_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset);

3.4 GstMemory包装一个用户提供的data

/**
 * gst_memory_new_wrapped:
 * @brief: 用户提供data内存,然后创建一个GstMemory进行管理该内存空间
 * @flags: #GstMemoryFlags
 * @data: (array length=size) (element-type guint8) (transfer none): 要包装的数据
 * @maxsize: @data 的分配大小
 * @offset: 在 @data 中的偏移量
 * @size: 有效数据的大小
 * @user_data: (nullable): 用户数据
 * @notify: (nullable) (scope async) (closure user_data): 当内存被释放时,用 @user_data 调用此函数
 *
 * 分配一个新的内存块来包装给定的 @data。
 *
 * 如果 @flags 包含 #GST_MEMORY_FLAG_ZERO_PREFIXED 和 #GST_MEMORY_FLAG_ZERO_PADDED,
 * 则前缀/填充必须分别用 0 填充。
 *
 * 返回值:(transfer full) (nullable): 一个新的 #GstMemory。
 */
GstMemory *
gst_memory_new_wrapped (GstMemoryFlags flags, gpointer data,
    gsize maxsize, gsize offset, gsize size, gpointer user_data,
    GDestroyNotify notify)
{
  GstMemorySystem *mem;

  g_return_val_if_fail (data != NULL, NULL);
  g_return_val_if_fail (offset + size <= maxsize, NULL);

  mem =
      _sysmem_new (flags, NULL, data, maxsize, 0, offset, size, user_data,
      notify);

  return (GstMemory *) mem;
}

4 GstMemory对象示例程序及问题总结

4.1 分配器默认内存对齐原因

示例程序地址/assets/GStreamerStudy/CoreObject/05_GstMemory/gstmemory_align.c

  1. 使用默认分配器分配GstMemory(不使用GstAllocationParams)
  2. 使用默认分配器分配GstMemory(使用GstAllocationParams),修改对齐参数,会有什么样的结果。
  3. 使用映射函数,获取data地址
/**
* 关于对齐数的问题:
* 1. 为了让系统读取用户内存的时候可以从0x0开始,0x8等便于一次性读取内存地址(因为64位系统一次性可以读取8字节),
*    分配内存的时候默认使用了linux下的8对齐。
* 
* 2. 分配内存函数会检测申请的内存地址的最后一个字节是否是 0x1 到 0x7 或者 0x9 到 0xF
*    gst_memory_alignment 是 0x7,只要align < 0x7,align就是0x7;大于0x7,则 align |= gst_memory_alignment
*    align |= gst_memory_alignment; 
*    if ((aoffset = ((guintptr) data & align))) {
*      aoffset = (align + 1) - aoffset;
*      data += aoffset;
*      maxsize -= aoffset;
*    }
*/

4.2 映射函数锁问题

示例程序地址/assets/GStreamerStudy/CoreObject/05_GstMemory/gstmemory_map.c

学习GstMiniObject中就发现存在gst_mini_object_lock已经有写锁的时候,还能上成功写锁问题。

如果有两个线程同时写内存,我就没办法知道那个线程先写完。

所以我认为:最好是 gst_memory_map (memory, &info, GST_LOCK_FLAG_WRITE | GST_LOCK_FLAG_EXCLUSIVE) 独有锁和写锁一起。

4.3 GstMemory共享内存

共享内存能合并的前提是同一个parent(也就是同一块地址)

示例程序地址/assets/GStreamerStudy/CoreObject/05_GstMemory/gstmemory_span.c