如何创建一个GStreamer插件

GStreamer是一个开源的多媒体框架,用于创建各种媒体处理组件。它的最大亮点就是其管道 (pipeline)与插件(plugin)机制, 为扩展提供了多种可能.

Plugin 创建的基本步骤

  1. 环境设置

    • 确保你的开发环境中安装了GStreamer开发库和头文件。
  2. 创建插件基础结构

    • 使用gst-plugin模板生成插件的基础结构。
  3. 编写插件代码

    • 定义插件的元数据,如名称、描述、版本等。
    • 创建一个或多个元素,每个元素可以是源(source)、汇(sink)、过滤器(filter)等。
  4. 注册元素

    • 在插件的初始化函数中注册你的元素。
  5. 实现元素功能

    • 根据元素的类型,实现必要的功能,比如数据的输入、处理和输出。
  6. 编译插件

    • 使用pkg-configmake来编译你的插件。
  7. 测试插件

    • 使用GStreamer的命令行工具gst-launch-1.0来测试你的插件。

大致框架

GStreamer 插件有三大类

  1. source: 产生媒体数据
  2. sink: 接收媒体数据
  3. filter: 处理媒体数据

以 filter 元素中最常见的 Transform (filter 插件的一种)为例 , 它的继承结构如下


GObject
    ╰──GInitiallyUnowned
        ╰──GstObject
            ╰──GstElement
                ╰──GstBaseTransform

从 GstBaseTransform 继承下来写个插件, 也就是一个用于处理数据的过滤器元素。而使用 GstBaseTransform 实现的元素, 其输出的大小和上限完全由输入上限和缓冲区大小决定的。
这些元素包括直接将一个缓冲区转换为另一个缓冲区、就地修改缓冲区内容的元素,以及将多个输入缓冲区整理为一个输出缓冲区或将一个输入缓冲区扩展为多个输出缓冲区的元素。它提供的能力如下:

  • 一进一出: 一个 sinkpad 和一个 srcpad
  • 使用自定义 transform_caps 函数实现 sink pad和 src_pad 的可能的媒体格式。
    默认情况下,sink_pad 和 src_pad 会使用相同的格式。
  • 处理状态更改
  • 执行刷新 (flushing)
  • 默认采用推送模式 (push mode)
  • 如果子类转换可以对任意数据进行操作,则使用拉取模式 (pull mode)

它有 2 个主要处理函数:

  • transform():将输入缓冲区转换为输出缓冲区。输出缓冲区保证可写且不同于输入缓冲区。
  • transform_ip():就地转换输入缓冲区。输入缓冲区可写且大小大于或等于输出缓冲区。

转换 transform 可以以以下的模式运行:

  • passthrough 直通:元素不会对缓冲区进行更改,缓冲区直接推送,两侧的容量必须相同。元素可以选择实现 transform_ip() 函数来查看数据,缓冲区不必可写。

  • in-place 就地:可以直接对输入缓冲区进行更改以获取输出缓冲区。转换必须实现 transform_ip() 函数。

  • copy-transform 复制转换:通过将输入缓冲区复制并转换为新的输出缓冲区来执行转换。转换必须实现 transform() 函数。

当未提供 transform() 函数时,仅允许就地和直通操作,这意味着源和目标容量必须相等,或者源缓冲区大小大于或等于目标缓冲区。
当未提供 transform_ip() 函数时,仅支持直通和复制转换。提供此功能是一种可以避免缓冲区复制的优化。

GStreamer 插件的代码可以从模板代码生成, 一般有如下三个工具可供使用

  1. gst-template:这是一个Git仓库,包含了GStreamer插件的源代码模板。你可以通过Git克隆这个仓库来获取样板代码。使用make_element工具,可以基于模板创建新的插件代码。例如,克隆gst-template仓库后,进入gst-template/gst-plugin/src目录,然后运行make_element命令来生成插件的基础代码 。

  2. gst-plugins-bad:在gst-plugins-bad项目中,有一个名为gst-project-maker的工具,它可以用来创建一个新的GStreamer项目,包括必要的构建系统和插件框架。

  3. gst-element-maker:这是一个命令行工具,位于gst-plugins-bad/tools目录下。使用这个工具,你可以通过命令行指定元素的名称和基类来生成样板代码。例如,使用./gst-element-maker my_element audiosink命令可以生成一个音频接收器(audiosink)元素的样板代码 。

而创建一个基本的GStreamer 的代码大致如下:

#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>

/* 定义一个结构体来存储你的元素的私有数据 */
typedef struct _MyElement {
  GstBaseTransform base_transform;
  /* <私有成员> */
} MyElement;

/* 定义一个结构体来存储你的元素的类数据 */
typedef struct _MyElementClass {
  GstBaseTransformClass base_transform_class;
  /* <私有成员> */
} MyElementClass;

/* 定义GST_PLUGIN_DEFINE宏来注册插件 */
GST_PLUGIN_DEFINE (
    GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    myelement,
    "My GStreamer Element Plugin",
    plugin_init,
    "1.0",
    "LGPL",
    "GStreamer",
    "http://gstreamer.net/"
)

/* 插件初始化函数 */
static gboolean plugin_init (GstPlugin * plugin) {
  /* 这里注册你的元素 */
  return gst_element_register (plugin, "myelement", GST_RANK_NONE, MY_ELEMENT_GET_TYPE());
}

/* 定义元素的类初始化函数 */
static void my_element_class_init (MyElementClass *klass) {
  /* 初始化你的元素的类数据 */
}

/* 定义元素的实例初始化函数 */
static void my_element_init (MyElement *myelement) {
  /* 初始化你的元素的实例数据 */
}

/* 定义元素的类型 */
G_DEFINE_TYPE (MyElement, my_element, GST_TYPE_BASE_TRANSFORM);

/* 元素的实现函数,例如transform_ip */
static GstFlowReturn my_element_transform_ip (GstBaseTransform *trans, GstBuffer *buf) {
  /* 处理输入缓冲区 */
  return GST_FLOW_OK;
}

/* 编译插件时的额外标志 */
#define PACKAGE "myelement"
#define ELEMENT_LONGNAME "My Element"
#define ELEMENT_CLASS my_element_parent_class
#define ELEMENT_TYPE (my_element_get_type ())
#define ELEMENT_NAME "myelement"

/* 插件的入口点 */
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
                   GST_VERSION_MINOR,
                   myelement,
                   ELEMENT_LONGNAME,
                   plugin_init,
                   "1.0",
                   "LGPL",
                   "GStreamer",
                   "http://gstreamer.net/")

例如想把一个彩色视频变成黑白视频的插件


#include <gst/gst.h>
#include <gst/video/gstvideofilter.h>

/* 定义灰度转换元素的类和实例 */
typedef struct _GrayscaleFilter {
  GstVideoFilter videofilter;
} GrayscaleFilter;

typedef struct _GrayscaleFilterClass {
  GstVideoFilterClass parent_class;
} GrayscaleFilterClass;

/* 定义GST_PLUGIN_DEFINE宏来注册插件 */
GST_PLUGIN_DEFINE (
    GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    grayscalefilter,
    "Grayscale video filter",
    plugin_init,
    "1.0",
    "LGPL",
    "GStreamer",
    "http://gstreamer.net/"
)

/* 初始化插件 */
static gboolean plugin_init (GstPlugin * plugin) {
  return gst_element_register (plugin, "grayscalefilter", GST_RANK_NONE, GRAYSCALEFILTER_TYPE);
}

/* 初始化元素类型 */
G_DEFINE_TYPE (GrayscaleFilter, grayscalefilter, GST_TYPE_VIDEO_FILTER);

/* 重写父类的transform_frame_ip方法,实现灰度转换逻辑 */
static GstFlowReturn grayscalefilter_transform_frame_ip (GstVideoFilter * filter, GstVideoFrame * frame) {
  GstVideoFormat format = GST_VIDEO_FRAME_FORMAT (frame);
  const int width = GST_VIDEO_FRAME_WIDTH (frame);
  const int height = GST_VIDEO_FRAME_HEIGHT (frame);
  GstMapInfo map;

  /* 检查是否是支持的格式 */
  if (format != GST_VIDEO_FORMAT_RGB && format != GST_VIDEO_FORMAT_BGR) {
    return GST_FLOW_ERROR;
  }

  /* 映射帧的内存 */
  if (!gst_buffer_map (frame->buffer, &map, GST_MAP_READWRITE)) {
    return GST_FLOW_ERROR;
  }

  /* 转换为灰度,这里以RGB为例,BGR类似 */
  if (format == GST_VIDEO_FORMAT_RGB) {
    guint8 *data = map.data;
    for (int i = 0; i < height; i++) {
      for (int j = 0; j < width; j++) {
        guint8 y = 0.299 * data[0] + 0.587 * data[1] + 0.114 * data[2];
        data[0] = y; // R
        data[1] = y; // G
        data[2] = y; // B
        data += 3;
      }
    }
  }

  /* 解除映射 */
  gst_buffer_unmap (frame->buffer, &map);

  return GST_FLOW_OK;
}

/* 初始化类 */
static void grayscalefilter_class_init (GrayscaleFilterClass *klass) {
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  GstVideoFilterClass *video_filter_class = GST_VIDEO_FILTER_CLASS (klass);

  gst_element_class_set_static_metadata (element_class,
    "Grayscale Filter",
    "Filter/Effect/Video",
    "Convert video to grayscale",
    "Your Name <your.email@example.com>");

  video_filter_class->transform_frame_ip = GST_DEBUG_FUNCPTR (grayscalefilter_transform_frame_ip);
}

/* 初始化实例 */
static void grayscalefilter_init (GrayscaleFilter *filter) {
  // 初始化实例数据(如果需要)
}

-- 本文参考月之暗面 AI 的回答

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容