五、GstMemory
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_map
和mem_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
- 使用默认分配器分配GstMemory(不使用GstAllocationParams)
- 使用默认分配器分配GstMemory(使用GstAllocationParams),修改对齐参数,会有什么样的结果。
- 使用映射函数,获取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