部分图片文字来自网络文章,侵权必删
内存分配和管理是多媒体中非常重要的主题。一个高清视频可能会使用几MB来存储单个图像帧,尽可能复用内存并减少内存拷贝非常重要。多媒体系统通常使用专用芯片(如 DSP 或 GPU)来执行繁重的工作(尤其是视频),这些专用芯片通常对其操作的内存及其访问方式有严格的要求。
本章讨论 GStreamer 插件可用的内存管理功能。我们将首先讨论管理对内存访问的低级 GstMemory 对象,然后继续讨论GstMemory的主要使用者之一:GstBuffer,它用于在element之间和与应用程序之间交换数据。我们还将讨论 GstMeta,此对象可以放置在缓冲区上以提供额外信息。我们还将讨论 GstBufferPool,它允许更有效地管理相同大小的缓冲区。
结束最后我们将研究 GST_QUERY_ALLOCATION 查询,它用于协商元素之间的内存管理选项。
一、GstMemory
GstMemory 是一个管理内存区域的对象,此内存对象指向“最大尺寸(maxsize)”的内存区域。可以访问的内存区域从“偏移量(offset)”开始,大小为“size”字节。创建 GstMemory 后其最大尺寸将无法再更改,但其“offset”和“size”可以更改。
/**
* GstMemory:
* @mini_object: parent structure
* @allocator: pointer to the #GstAllocator
* @parent: parent memory block
* @maxsize: the maximum size allocated
* @align: the alignment of the memory
* @offset: the offset where valid data starts
* @size: the size of valid data
*
* Base structure for memory implementations. Custom memory will put this structure
* as the first member of their structure.
*/
struct _GstMemory {
GstMiniObject mini_object;
GstAllocator *allocator;
GstMemory *parent;
gsize maxsize;
gsize align;
gsize offset;
gsize size;
};
1. GstAllocator
GstMemory 对象由 GstAllocator 对象创建。大多数分配器都实现默认的 gst_allocator_alloc() 方法,但有些分配器可能会实现不同的方法,例如当需要其他参数来分配特定内存时。
系统内存、共享内存和由 DMAbuf 文件描述符支持的内存存在不同的分配器。如果需要实现对新内存类型的支持,您必须实现新的分配器对象。
2. GstMemory 应用接口示例
对 GstMemory 对象包装的内存的数据访问始终受到 gst_memory_map() 和 gst_memory_unmap()函数对的保护:应用程序使用gst_memory_map映射内存(指定读写访问模式),该函数返回指向有效内存区域的指针,然后可以根据请求的访问模式对其进行访问;访问结束后应用程序使用gst_memory_unmap函数解除内存映射。
以下是创建 GstMemory 对象并使用 gst_memory_map/gst_memory_unmap函数对 访问内存区域的示例:
[...]
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);
[...]
二、GstBuffer
GstBuffer 是一个轻量级对象,包含内存和元数据,它从上游element传递到下游elenent,表征被下游element获取的多媒体内容。GstBuffer 包含一个或多个 GstMemory 对象,这些对象保存缓冲区的数据。
/**
* GstBuffer:
* @mini_object: the parent structure
* @pool: pointer to the pool owner of the buffer
* @pts: presentation timestamp of the buffer, can be #GST_CLOCK_TIME_NONE when the
* pts is not known or relevant. The pts contains the timestamp when the
* media should be presented to the user.
* @dts: decoding timestamp of the buffer, can be #GST_CLOCK_TIME_NONE when the
* dts is not known or relevant. The dts contains the timestamp when the
* media should be processed.
* @duration: duration in time of the buffer data, can be #GST_CLOCK_TIME_NONE
* when the duration is not known or relevant.
* @offset: a media specific offset for the buffer data.
* For video frames, this is the frame number of this buffer.
* For audio samples, this is the offset of the first sample in this buffer.
* For file data or compressed data this is the byte offset of the first
* byte in this buffer.
* @offset_end: the last offset contained in this buffer. It has the same
* format as @offset.
*
* The structure of a #GstBuffer. Use the associated macros to access the public
* variables.
*/
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;
};
缓冲区中的元数据包括:
- DTS 和 PTS 时间戳(pts/dts):它们表示缓冲区内容的解码时间戳和显示时间戳,用于element间缓冲区同步调度。如果缓冲区不需要这两个时间戳,侧这两个时间戳可以是 GST_CLOCK_TIME_NONE。
- 缓冲区内容的持续时间(duration):当未知/未定义时此持续时间可以是 GST_CLOCK_TIME_NONE。
- 特定于媒体的偏移量(offset)和 偏移结束值(offset_end):对于视频这是流中的帧号;对于音频这是样本号。对于其他类型的媒体,这两个值可能有不同的定义。
1. GstBuffer的可写属性
当GstBuffer的引用计数正好为 1 时,意味着只有一个对象持有对缓冲区的引用,此时该缓冲区是可写的。只有当缓冲区可写时您才能修改它,您可以更改时间戳、偏移量、元数据或添加和删除内存块,前提是先调用 gst_buffer_make_writable()函数表征该缓冲区可写。
2. API 示例
您可以使用 gst_buffer_new () 创建 GstBuffer,然后向其中添加内存对象。您也可以使用便捷函数 gst_buffer_new_allocate () 同时执行两个操作,或者使用 gst_buffer_new_wrapped_full () 包装现有内存,并指定内存释放时应该调用的函数。
您可以通过单独获取并映射 GstMemory 对象,亦或使用 gst_buffer_map () 来访问 GstBuffer 的内存。后者将所有内存合并为一个大块然后为您提供指向它的指针。下面是如何创建缓冲区并访问其内存的示例:
[...]
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);
[...]
三、GstMeta
您可以使用 GstMeta 系统向缓冲区添加任意结构,这些结构描述了缓冲区的额外属性,例如裁剪、步幅、感兴趣区域等。
元数据系统将 API 规范(元数据及其 API 的样子)与实现(工作原理)分开,这导致同一 API 可以有不同的实现。一个典型的例子就是在不同的视频解码器上,GstMeta的实现是不同的,取决于我们正在运行的硬件。
/**
* GstMetaFlags:
* @GST_META_FLAG_NONE: no flags
* @GST_META_FLAG_READONLY: metadata should not be modified
* @GST_META_FLAG_POOLED: metadata is managed by a bufferpool
* @GST_META_FLAG_LOCKED: metadata should not be removed
* @GST_META_FLAG_LAST: additional flags can be added starting from this flag.
*
* Extra metadata flags.
*/
typedef enum {
GST_META_FLAG_NONE = 0,
GST_META_FLAG_READONLY = (1 << 0),
GST_META_FLAG_POOLED = (1 << 1),
GST_META_FLAG_LOCKED = (1 << 2),
GST_META_FLAG_LAST = (1 << 16)
} GstMetaFlags;
/**
* GstMetaInfo:
* @api: tag identifying the metadata structure and api
* @type: type identifying the implementor of the api
* @size: size of the metadata
* @init_func: function for initializing the metadata
* @free_func: function for freeing the metadata
* @transform_func: function for transforming the metadata
* @serialize_func: function for serializing the metadata into a #GstStructure,
* or %NULL if not supported by this meta. (Since 1.24)
* @deserialize_func: function for deserializing the metadata from a
* #GstStructure, or %NULL if not supported by this meta. (Since 1.24)
*
* The #GstMetaInfo provides information about a specific metadata
* structure.
*/
struct _GstMetaInfo {
GType api;
GType type;
gsize size;
GstMetaInitFunction init_func;
GstMetaFreeFunction free_func;
GstMetaTransformFunction transform_func;
GstMetaSerializeFunction serialize_func;
GstMetaDeserializeFunction deserialize_func;
GstMetaClearFunction clear_func;
/* No padding needed, GstMetaInfo is always allocated by GStreamer and is
* not subclassable or stack-allocatable, so we can extend it as we please
* just like interfaces */
};
/**
* GstMeta:
* @flags: extra flags for the metadata
* @info: pointer to the #GstMetaInfo
*/
struct _GstMeta {
GstMetaFlags flags;
const GstMetaInfo *info;
};
1. API 示例
分配新的 GstBuffer 后,您可以使用元数据特定的 API 向其添加元数据。按照惯例,名为 FooBar 的元数据 API 应提供两种方法:
agst_buffer_add_foo_bar_meta () 和 gst_buffer_get_foo_bar_meta ()
这两个函数都应返回指向包含元数据字段的 FooBarMeta 结构的指针。一些 _add_*_meta () 可以具有额外的参数,这些参数通常用于为您配置元数据结构。让我们看一下用于指定视频帧裁剪区域的元数据。
#include <gst/video/gstvideometa.h>
[...]
GstVideoCropMeta *meta;
/* buffer points to a video frame, add some cropping metadata */
meta = gst_buffer_add_video_crop_meta (buffer);
/* configure the cropping metadata */
meta->x = 8;
meta->y = 8;
meta->width = 120;
meta->height = 80;
[...]
element可以在渲染帧时使用缓冲区上的元数据,如下所示:
#include <gst/video/gstvideometa.h>
[...]
GstVideoCropMeta *meta;
/* buffer points to a video frame, get the cropping metadata */
meta = gst_buffer_get_video_crop_meta (buffer);
/* 如果存在meda元数据信息,则按照元数据信息渲染;否则之间渲染*/
if (meta) {
/* render frame with cropping */
_render_frame_cropped (buffer, meta->x, meta->y, meta->width, meta->height);
} else {
/* render frame */
_render_frame (buffer);
}
[...]
2. 实施新的 GstMeta
在下一部分中,我们将展示如何向系统添加新的元数据并在缓冲区上使用它。
2.1 定义metadata应用接口
首先需要定义我们的 API 是什么样子,并且将此 API 注册到系统。这一点非常重要,因为当element协商它们准备交换哪种元数据时会使用我们定义的API。API 定义同时包含任意标签(tags),这些标签提示元数据中包含的内容,这在缓冲区通过pipeline时保护元数据非常重要(换言之,element之间会交换buffer和meta两种信息:buffer带有数据源,meta表征数据源信息)。
首先我们开始制作 my-example-meta.h 头文件,该文件将包含 API 的定义和元数据的结构。
#include <gst/gst.h>
typedef struct _MyExampleMeta {
GstMeta meta;
gint age;
gchar *name;
} MyExampleMeta;
GType my_example_meta_api_get_type (void);
#define MY_EXAMPLE_META_API_TYPE (my_example_meta_api_get_type())
#define gst_buffer_get_my_example_meta(b) \
((MyExampleMeta*)gst_buffer_get_meta((b),MY_EXAMPLE_META_API_TYPE))
元数据 API 定义了 gint 和gchar的结构,结构中的第一个字段必须是 GstMeta。同时我们还定义了一个 my_example_meta_api_get_type () 函数用来注册我们的元数据 API,以及一个便捷的 gst_buffer_get_my_example_meta ()函数 ,该函数将使用我们的新 API 查找并返回元数据。
接下来看看 my_example_meta_api_get_type () 函数在 my-example-meta.c 文件中是如何实现的:
#include "my-example-meta.h"
GType my_example_meta_api_get_type (void)
{
static volatile GType type;
static const gchar *tags[] = { "foo", "bar", NULL };
if (g_once_init_enter (&type)) {
GType _type = gst_meta_api_type_register ("MyExampleMetaAPI", tags);
g_once_init_leave (&type, _type);
}
return type;
}
可以看到它只是使用 gst_meta_api_type_register() 函数为 API 注册一个名称和一些标签,返回一个新的 GType 指针。
2.2 实现metadata应用接口
接下来,我们可以为已注册的元数据 API GType 实现一个实现。
元数据 API 的实现细节保存在 GstMetaInfo 结构中,您可以使用 my_example_meta_get_info () 函数和 MY_EXAMPLE_META_INFO 宏将该结构提供给元数据 API 实现的用户,您还可以提供一种将元数据实现添加到 aGstBuffer 的方法。
您的 my-example-meta.h 头文件将需要以下附加内容:
[...]
/* implementation */
const GstMetaInfo *my_example_meta_get_info (void);
#define MY_EXAMPLE_META_INFO (my_example_meta_get_info())
MyExampleMeta * gst_buffer_add_my_example_meta (GstBuffer *buffer,
gint age,
const gchar *name);
让我们看看这些函数在 my-example-meta.c 文件中是如何实现的。
[...]
static gboolean
my_example_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer)
{
MyExampleMeta *emeta = (MyExampleMeta *) meta;
emeta->age = 0;
emeta->name = NULL;
return TRUE;
}
static gboolean
my_example_meta_transform (GstBuffer * transbuf, GstMeta * meta,
GstBuffer * buffer, GQuark type, gpointer data)
{
MyExampleMeta *emeta = (MyExampleMeta *) meta;
/* we always copy no matter what transform */
gst_buffer_add_my_example_meta (transbuf, emeta->age, emeta->name);
return TRUE;
}
static void
my_example_meta_free (GstMeta * meta, GstBuffer * buffer)
{
MyExampleMeta *emeta = (MyExampleMeta *) meta;
g_free (emeta->name);
emeta->name = NULL;
}
const GstMetaInfo *my_example_meta_get_info (void)
{
static const GstMetaInfo *meta_info = NULL;
if (g_once_init_enter (&meta_info)) {
const GstMetaInfo *mi = gst_meta_register (MY_EXAMPLE_META_API_TYPE,
"MyExampleMeta",
sizeof (MyExampleMeta),
my_example_meta_init,
my_example_meta_free,
my_example_meta_transform);
g_once_init_leave (&meta_info, mi);
}
return meta_info;
}
MyExampleMeta *gst_buffer_add_my_example_meta (GstBuffer *buffer,
gint age,
const gchar *name)
{
MyExampleMeta *meta;
g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
meta = (MyExampleMeta *) gst_buffer_add_meta (buffer,
MY_EXAMPLE_META_INFO, NULL);
meta->age = age;
meta->name = g_strdup (name);
return meta;
}
gst_meta_register () 函数完成注册细节:您实现的 API 和元数据结构的大小,以及初始化和释放内存区域的方法。您还可以实现一个转换函数,当对缓冲区执行特定转换时该函数将被调用。
最后您实现一个 gst_buffer_add_*_meta(),它将元数据实现添加到缓冲区并设置元数据的值。
四、GstBufferPool
GstBufferPool 对象提供了一个方便的基类,用于管理可重用缓冲区列表。此对象的关键是所有缓冲区都具有相同的属性,例如大小、填充、元数据和对齐。
可以配置 GstBufferPool 来管理特定大小的缓冲区的最小和最大数量。它还可以配置为使用特定的 GstAllocator 来分配缓冲区的内存。缓冲池还支持启用特定的选项,例如将 GstMeta 添加到池的缓冲区或在缓冲区的内存上启用特定填充。
GstBufferPool 可以处于非活动状态或活动状态:在非活动状态下您可以配置池;在活动状态下您无法再更改配置,但可以从池中获取和释放缓冲区。在以下部分中,我们将介绍如何使用 GstBufferPool。
**
* GstBufferPool:
* @object: the parent structure
* @flushing: whether the pool is currently gathering back outstanding buffers
*
* The structure of a #GstBufferPool. Use the associated macros to access the public
* variables.
*/
struct _GstBufferPool {
GstObject object;
/*< protected >*/
gint flushing;
/*< private >*/
GstBufferPoolPrivate *priv;
gpointer _gst_reserved[GST_PADDING];
};
struct _GstBufferPoolPrivate
{
GstAtomicQueue *queue;
GstPoll *poll;
GRecMutex rec_lock;
gboolean started;
gboolean active;
gint outstanding; /* number of buffers that are in use */
gboolean configured;
GstStructure *config;
guint size;
guint min_buffers;
guint max_buffers;
guint cur_buffers;
GstAllocator *allocator;
GstAllocationParams params;
};
/**
* GstAllocator:
* @mem_map: the implementation of the GstMemoryMapFunction
* @mem_unmap: the implementation of the GstMemoryUnmapFunction
* @mem_copy: the implementation of the GstMemoryCopyFunction
* @mem_share: the implementation of the GstMemoryShareFunction
* @mem_is_span: the implementation of the GstMemoryIsSpanFunction
* @mem_map_full: the implementation of the GstMemoryMapFullFunction.
* Will be used instead of @mem_map if present. (Since: 1.6)
* @mem_unmap_full: the implementation of the GstMemoryUnmapFullFunction.
* Will be used instead of @mem_unmap if present. (Since: 1.6)
*
* The #GstAllocator is used to create new memory.
*/
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;
};
1. API示例
可以有许多不同的 GstBufferPool 实现,它们都是 GstBufferPoolbase 类的子类。对于此示例,我们假设我们以某种方式可以访问缓冲池,要么是因为我们自己创建了它,要么是因为我们通过 ALLOCATION 查询获得了一个缓冲池,如下所示。
GstBufferPool 最初处于非活动状态,因此我们可以对其进行配置。尝试配置非非活动状态的 GstBufferPool 将失败。同样尝试激活未配置的缓冲池也将失败。
GstStructure *config;
[...]
/* get config structure */
config = gst_buffer_pool_get_config (pool);
/* set caps, size, minimum and maximum buffers in the pool */
gst_buffer_pool_config_set_params (config, caps, size, min, max);
/* configure allocator and parameters */
gst_buffer_pool_config_set_allocator (config, allocator, ¶ms);
/* store the updated configuration again */
gst_buffer_pool_set_config (pool, config);
[...]
GstBufferPool 的配置保存在通用的 GstStructure 中,可以使用 gst_buffer_pool_get_config() 获取,有API可以获取和设置此结构中的配置选项。结构更改完成后使用 gst_buffer_pool_set_config() 函数更新 GstBufferPool 中的配置。
GstBufferPool支持如下配置选项:
- 要分配的缓冲区的上限(caps)。
- 缓冲区的大小:这是池中缓冲区的建议大小,池子可能会决定分配更大的缓冲区以添加填充。
- 池中缓冲区的最小和最大数量:当最小值不为0 时缓冲池将预先分配此数量的缓冲区;当最大值不为 0 时,缓冲池将分配最多最大数量的缓冲区。
- 要使用的分配器和参数:某些缓冲池可能会忽略分配器并使用其内部分配器。
- 用字符串标识的其他任意缓冲池选项:缓冲池使用 gst_buffer_pool_get_options() 列出支持的选项,您可以使用 gst_buffer_pool_has_option() 询问是否支持某个选项。可以通过使用 gst_buffer_pool_config_add_option () 将该选项添加到配置结构中来启用该选项,这些选项用于启用诸如让池在缓冲区上设置元数据,或为填充添加额外的配置选项之类的功能。
在缓冲池上设置配置后,可以使用 gst_buffer_pool_set_active (pool, TRUE) 激活该池。此后您可以使用 gst_buffer_pool_acquire_buffer () 从池中检索缓冲区,如下所示:
[...]
GstFlowReturn ret;
GstBuffer *buffer;
ret = gst_buffer_pool_acquire_buffer (pool, &buffer, NULL);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto pool_failed;
[...]
检查 acquire 函数的返回值很重要,因为它可能会失败:当您的元素关闭时它将停用缓冲池,然后对 acquire 的所有调用都将返回 GST_FLOW_FLUSHING。
从池中获取的所有缓冲区都将将其池成员设置为原始池。当缓冲区上的引用减少为0时,GStreamer 将自动调用 gst_buffer_pool_release_buffer() 将缓冲区释放回池。应用程序(或任何其他下游element)不需要知道缓冲区是否来自于池,您只需取消引用即可。
总结
这里介绍了GStreamer缓存的核心内容:memory,buffer,meta以及bufferpool,完成了element间交互数据的来龙去脉。