gstreamer1.16插件笔记

gstreamer命令行,

basic tutorial(github my-demos),

plugins tutorial(github my-demos),

api手册 gst核心手册 file:///home/fang/gstreamer/gstreamer-1.16.2/docs/gst/html/libgstreamer.html  ,base api 手册 file:///home/fang/gstreamer/gst-plugins-base-1.16.2/docs/libs/html/index.html , base plugins 手册 file:///home/fang/gstreamer/gst-plugins-base-1.16.2/docs/plugins/html/index.html

https://gstreamer.freedesktop.org/documentation/plugin-development/introduction/basics.html?gi-language=c

 

Request and Sometimes pads

https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/request.html?gi-language=c

sometimes pad

sometimes pad 根据stream情况动态创建的pad, eg. demux根据stream是否有video/audio/subtitle创建video pad / audio pad / subtitle pad. sometimes pad 会自动消失(当element状态从paused->ready时)

//sometimes pad的模板是:
static GstStaticPadTemplate src_factory =
GST_STATIC_PAD_TEMPLATE (
  "src_%u",
  GST_PAD_SRC,
  GST_PAD_SOMETIMES,
  GST_STATIC_CAPS ("ANY")
);

//class_init 函数中需添加模板
gst_element_class_add_pad_template (element_class,
    gst_static_pad_template_get (&src_factory));

//创建pad的操作可以放任何位置
gchar *padname = g_strdup_printf ("src_%u", n);
pad = gst_pad_new_from_static_template (src_factory, padname);
g_free (padname);

//激活并添加pad,ok
gst_pad_set_active (pad, TRUE);
gst_element_add_pad (element, pad)

request pad 

//request模板
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink_%u",
  GST_PAD_SINK,
  GST_PAD_REQUEST,
  GST_STATIC_CAPS ("ANY")
);
//需要重新实现这两个函数
static GstPad *
gst_my_filter_request_new_pad (GstElement     *element,
                               GstPadTemplate *templ,
                               const gchar    *name,
                               const GstCaps  *caps)
{
  GstPad *pad;
  GstMyFilterInputContext *context;

  context = g_new0 (GstMyFilterInputContext, 1);
  pad = gst_pad_new_from_template (templ, name);
  gst_pad_set_element_private (pad, context);

  /* normally, you would set _chain () and _event () functions here */

  gst_element_add_pad (element, pad);
  return pad;
}

static void
gst_my_filter_release_pad (GstElement *element,
                           GstPad *pad)
{
  GstMyFilterInputContext *context;

  context = gst_pad_get_element_private (pad);
  g_free (context);

  gst_element_remove_pad (element, pad);
}

//class_init中
element_class->request_new_pad = gst_my_filter_request_new_pad;
element_class->release_pad = gst_my_filter_release_pad;

Glist

//定义一个gstreamer的list
GList *srcpadlist;
srcpadlist = g_list_append (srcpadlist, pad);//添加到list尾

//遍历list
GList *padlist;
for (padlist = srcpadlist; padlist != NULL; padlist = g_list_next (padlist)) {
  pad = GST_PAD (padlist->data); //拿到数据后第一件事就是强转

  GstEvent *event = gst_event_new (GST_EVENT_EOS);
  gst_pad_push_event (pad, gst_event_ref (event));
  gst_event_unref (event);
}

int num = 3;
if (num >= 0 && num < g_list_length (srcpadlist)) {
  pad = GST_PAD (g_list_nth_data (srcpadlist, num); //取list中某index的值
  //
}

GstBuffer

//buffer的创建。size以字节为单位
GstBuffer *buf = gst_buffer_new_allocate (NULL, size, NULL);

//填充gstbuffer, Copy size bytes from src to buffer at offset
buf_size = gst_buffer_fill(buf, 0, (gpointer)mybuf, size);
gsize gst_buffer_extract (GstBuffer *buffer,
                    gsize offset,
                    gpointer dest,
                    gsize size);
//引用计数为0时自动释放,若是gst_pad_push就交给下游来做unref
gst_buffer_unref(outbuf);


//map一块gstbuffer
GstMapInfo map;
gst_buffer_map (buf, &map, GST_MAP_READ);
for (i = 0; i < map.size; i++) {//访问这块mapped buffer
    map.data[i];
}
//
gst_buffer_unmap (buf, &map);


//从gstbuffer快速得到一块子gstbuffer,不用unref,只用unref父gstbuffer即可
GstBuffer *sub = gst_buffer_copy_region (buf, 
    GST_BUFFER_COPY_ALL,
    offset,        //offset
    map.size - n - 1);//size

 

Different scheduling modes

https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/scheduling.html?gi-language=c

pull-mode就是sink端从source pad拉数据(查询数据)
push-mode就是source端向sink pad推数据(给数据), 上游 gst_pad_push () 下游实现 _chain ()
pad的activate一般发生在element的READY->PAUSED。先source pad然后sink pad。这个过程中 _activate () 会被调用,默认是push-mode。可以重写此函数来激活pull-mode

几种应用场景:

case1: (最常见)所有pad都是push-mode。这种情况下,sink pad需要 gst_pad_set_chain_function () 并实现chain函数。source pad调用 gst_pad_push。所有下游element也都要在push-mode了。对于特殊的source-element,需要起task(一般在change_state中)来获取数据并驱动整个pipeline。

case2: 某elemnt的sink pad为pull-mode,source-pad在push-mode。此时pad需要起task(thread),此thread拥有对所有sink-pad的数据随机访问权(gst_pad_pull_range ())并且可以向source-pad push数据。整个pipeline都由此element驱动。此时所有下游elements均处于push-mode,所有上游elements均处于pull-mode。

case3: 所有pad都是pull-mode。(应该就是case2中element上游的那些) 但是这里的pull-mode pads并没有起task,而是作为case2中element(下游element)的slave存在

对于case2的element,就是拿到了驱动pipeline的主动权,对于一些带有parse的demuxer/parser/decoder来说可以按需拿数据,比较方便。需要先check上游element是否支持pull-mode,然后在设置pull-mode。

//activate是在设置pad mode的时候调用的函数,在这里重写,发送query请求,询问上游值否支持pull-mode
static gboolean
gst_my_filter_activate (GstPad * pad, GstObject * parent)
{
  GstQuery *query;
  gboolean pull_mode;

  //first check what upstream scheduling is supported,
  //这里就要求peer element实现query并 gst_query_add_scheduling_mode ()
  query = gst_query_new_scheduling ();

  if (!gst_pad_peer_query (pad, query)) {
    gst_query_unref (query);
    goto activate_push;
  }

  pull_mode = gst_query_has_scheduling_mode_with_flags (query,
      GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);//see if pull-mode is supported
  gst_query_unref (query);

  if (!pull_mode)
    goto activate_push;

  /* now we can activate in pull-mode. GStreamer will also
   * activate the upstream peer in pull-mode */
  return gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE);

activate_push:
  {
    return gst_pad_activate_mode (pad, GST_PAD_MODE_PUSH, TRUE);//fallback to push-mode,这也是默认行为
  }
}

//这个函数应该是 gst_pad_activate_mode 设置为pull-mode后gstreamer会回调的吧
static gboolean
gst_my_filter_activate_pull (GstPad    * pad,
                 GstObject * parent,
                 GstPadMode  mode,
                 gboolean    active)
{
  gboolean res;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (mode) {
    case GST_PAD_MODE_PUSH:
      res = TRUE;
      break;
    case GST_PAD_MODE_PULL:
      if (active) {
        filter->offset = 0;
        //基于pull-mode pad 起task
        res = gst_pad_start_task (pad, (GstTaskFunction) gst_my_filter_loop, filter, NULL);
      } else {
        res = gst_pad_stop_task (pad);
      }
      break;
    default:
      res = FALSE;//unknown scheduling mode
      break;
  }
  return res;
}

//在init函数中:
gst_pad_set_activate_function (filter->sinkpad, gst_my_filter_activate);
gst_pad_set_activatemode_function (filter->sinkpad, gst_my_filter_activate_mode);


//这就是驱动整个pipeline的thread,拥有对input和output的全权控制
#define BLOCKSIZE 2048
static void
gst_my_filter_loop (GstMyFilter * filter)
{
  GstFlowReturn ret;
  guint64 len;
  GstBuffer *buf = NULL;

  if (!gst_pad_query_duration (filter->sinkpad, GST_FORMAT_BYTES, &len)) {
    GST_DEBUG_OBJECT (filter, "failed to query duration, pausing");
    goto stop;
  }

  if (filter->offset >= len) {
    GST_DEBUG_OBJECT (filter, "at end of input, sending EOS, pausing");
    gst_pad_push_event (filter->srcpad, gst_event_new_eos ());
    goto stop;
  }

  //从offset处读取 BLOCKSIZE 字节数据到gstbuffer
  //这里也要求其上游element的source-pad实现一个get range函数并通过 gst_pad_set_getrange_function () 设置上
  ret = gst_pad_pull_range (filter->sinkpad, filter->offset, BLOCKSIZE, &buf);
  if (ret != GST_FLOW_OK) {
    GST_DEBUG_OBJECT (filter, "pull_range failed: %s", gst_flow_get_name (ret));
    goto stop;
  }

  ret = gst_pad_push (filter->srcpad, buf);//now push buffer downstream
  buf = NULL; //gst_pad_push()获取了gstbuffer的所有权,下游会负责unref
  if (ret != GST_FLOW_OK) {
    GST_DEBUG_OBJECT (filter, "pad_push failed: %s", gst_flow_get_name (ret));
    goto stop;
  }

  filter->offset += BLOCKSIZE;//increase offset and wait for us to be called again
  return;

stop:
  GST_DEBUG_OBJECT (filter, "pausing task");
  gst_pad_pause_task (filter->sinkpad);
}
//上游element需要实现 get range 函数
static GstFlowReturn gst_my_filter_get_range (GstPad     * pad,
                     GstObject  * parent,
                     guint64      offset,
                     guint        length,
                     GstBuffer ** buf);
//在init函数中:
gst_pad_set_getrange_function (filter->srcpad,
    gst_my_filter_get_range);

static GstFlowReturn
gst_my_filter_get_range (GstPad     * pad,
             GstObject  * parent,
             guint64      offset,
             guint        length,
             GstBuffer ** buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);
  //这里填充 *buf

  return GST_FLOW_OK;
}

 

Caps negotiation

https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/negotiation.html?gi-language=c
一个完整的gstreamer caps协商遵从以下规则:
1.下游element suggest一个其sinkpad支持的format。sinkpad的query函数中,suggestion作为结果返回
2.上游element决定使用什么format。此决定通过source-pad的caps event通知给下游element。下游element在其sinkpad的caps event中做 re-configure
3.下游element可以通知上游element它想使用另一个format,这个请求通过向上游发送 RECONFIGURE event 来传达。RECONFIGURE event 会导致上游element重启caps协商过程,同时pipeline也会改变状态


还有一类 ACCEPT_CAPS query 可用于快速查询一个element是否支持某一cap

几种常见的应用场景:

1。固定协商:gst_pad_new_from_static_template 后 gst_pad_use_fixed_caps (pad);使用固定caps,然后合适的时候主动调用 gst_caps_new_simple + gst_caps_set_simple 并 gst_pad_set_caps 即可。不接受re-configure


2。转换协商:在element的sinkpad的caps event函数中,根据sinkpad的cap设置source-pad的caps

static gboolean
gst_my_filter_setcaps (GstMyFilter *filter, GstCaps *caps)  //caps应该就是上游给下来的caps了
{
  GstStructure *structure;
  int rate, channels;
  gboolean ret;
  GstCaps *outcaps;

  structure = gst_caps_get_structure (caps, 0);
  ret = gst_structure_get_int (structure, "rate", &rate);
  ret = ret && gst_structure_get_int (structure, "channels", &channels);
  if (!ret)
    return FALSE;

  outcaps = gst_caps_new_simple ("audio/x-raw",
      "format", G_TYPE_STRING, GST_AUDIO_NE(S16),
      "rate", G_TYPE_INT, rate,
      "channels", G_TYPE_INT, channels, NULL);
  ret = gst_pad_set_caps (filter->srcpad, outcaps);
  gst_caps_unref (outcaps);

  return ret;
}

static gboolean
gst_my_filter_sink_event (GstPad *pad, GstObject *parent, GstEvent  *event)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;

      gst_event_parse_caps (event, &caps);
      ret = gst_my_filter_setcaps (filter, caps);
      break;
    }
    default:
      ret = gst_pad_event_default (pad, parent, event);
      break;
  }
  return ret;
}

3。动态协商:source-pad的cap需要根据上游的caps来选择,也要根据下游能接受的cap选择。sinkpad event接收到caps后,可以对下游发送 ACCEPT_CAPS query询问此cap是否支持,若支持则协商结束。否则query下游支持的cap list,从中选择本element可以transform的cap作为source-pad cap

static gboolean
gst_my_filter_setcaps (GstMyFilter *filter, GstCaps *caps)
{
  if (gst_pad_set_caps (filter->srcpad, caps)) {    //设置成功表示下游接受这个caps,协商完成
    filter->passthrough = TRUE;
  } else {
    GstCaps *othercaps, *newcaps;
    GstStructure *s = gst_caps_get_structure (caps, 0);
    GstStructure *others;

    gst_structure_get_int (s, "channels", &filter->channels);   //获得上游的channel,因为本element不做channel的转换
    othercaps = gst_pad_get_allowed_caps (filter->srcpad);    //查询source-pad支持的caps,这里应该就是要求下游element实现query了
    others = gst_caps_get_structure (othercaps, 0);
    gst_structure_set (others, "channels", G_TYPE_INT, filter->channels, NULL);//下游支持的caps中channel必须和上游发送的channel一致才行,so这里直接设置进去

    newcaps = gst_caps_copy_nth (othercaps, 0);  //caps是支持的format集合,类似list,其中第0个structure应该就是最优先的那个format
    gst_caps_unref (othercaps);
    gst_pad_fixate_caps (filter->srcpad, newcaps);    //samplerate等可能有几个备选值,这里直接择其一
    if (!gst_pad_set_caps (filter->srcpad, newcaps))    //这里设置还不成功就直接失败吧
      return FALSE;

    /* we are now set up, configure internally */
    filter->passthrough = FALSE;
    gst_structure_get_int (s, "rate", &filter->from_samplerate);
    others = gst_caps_get_structure (newcaps, 0);
    gst_structure_get_int (others, "rate", &filter->to_samplerate);
  }

  return TRUE;
}

static gboolean
gst_my_filter_sink_event (GstPad *pad, GstObject *parent, GstEvent  *event)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
    {
      GstCaps *caps;

      gst_event_parse_caps (event, &caps);
      ret = gst_my_filter_setcaps (filter, caps);
      break;
    }
    default:
      ret = gst_pad_event_default (pad, parent, event);
      break;
  }
  return ret;
}

static GstFlowReturn
gst_my_filter_chain (GstPad    *pad,
             GstObject *parent,
             GstBuffer *buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);
  GstBuffer *out;

  if (filter->passthrough)    //passthrough不做任何处理直接push出去
    return gst_pad_push (filter->srcpad, buf);

  out = gst_my_filter_convert (filter, buf);//需要做samplerate的转换
  gst_buffer_unref (buf);

  return gst_pad_push (filter->srcpad, out);
}

 

上游caps再协商:这是在已经协商完毕的情况下,发生诸如audio配置信息变化(如channel改变)/video配置信息变化(如resize)时候,重新协商caps。发送GST_EVENT_RECONFIGURE event触发,GST_QUERY_CAPS query 请求中将preferred caps作为结果返回。

 

实现一个CAPS query函数。CAPS query函数用于告诉 peer element 当前pad支持的formats和优先级顺序。(其实是不仅包含了当前pad还包含了更上游/下游支持的format的一个交集)

static gboolean
gst_my_filter_query (GstPad *pad, GstObject * parent, GstQuery * query)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_CAPS:
    {
      GstPad *otherpad;
      GstCaps *temp, *caps, *filt, *tcaps;
      gint i;

      otherpad = (pad == filter->srcpad) ? filter->sinkpad : filter->srcpad;
      caps = gst_pad_get_allowed_caps (otherpad);    //获得更上游/更下游支持的caps

      gst_query_parse_caps (query, &filt);    //从 query 中取出 caps filter

      for (i = 0; i < gst_caps_get_size (caps); i++) {
        GstStructure *structure = gst_caps_get_structure (caps, i);
        gst_structure_remove_field (structure, "rate");    //不关心rate所以就给去掉了
      }

      tcaps = gst_pad_get_pad_template_caps (pad);
      if (tcaps) {
        temp = gst_caps_intersect (caps, tcaps);//取 "更上游/更下游支持的caps" 与 "本element的pad_template_caps" 的交集
        gst_caps_unref (caps);
        gst_caps_unref (tcaps);
        caps = temp;
      }
      if (filt) {
        temp = gst_caps_intersect (caps, filt);    //在与 caps filter 取交集
        gst_caps_unref (caps);
        caps = temp;
      }
      gst_query_set_caps_result (query, caps);    //ok,作为 query 结果返回
      gst_caps_unref (caps);
      ret = TRUE;
      break;
    }
    default:
      ret = gst_pad_query_default (pad, parent, query);
      break;
  }
  return ret;
}

Memory allocation

https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html?gi-language=c#memory-allocation

音视频数据存储,效率很关键,比如video单帧数据就很庞大。gstreamer memory管理包括 lowlevel GstMemory,以及 GstBuffer GstMeta GstBufferPool

GstMemory

管理一块有maxsize固定字节的内存(但是却可通过 gst_memory_resize() 改变),start+offset存储数据。通过 GstAllocator 对象分配, GstAllocator会实现 gst_allocator_alloc() 来分配 system memory, shared memory, DMA 等。定制内存的分配需要我们自己实现一个GstAllocator对象

GstMemory *mem;
GstMapInfo info;
gint i;

mem = gst_allocator_alloc (NULL, 100, NULL);    //allocate 100 bytes, 第一个 NULL表示使用 default allocator

gst_memory_map (mem, &info, GST_MAP_WRITE);    //get access to the memory in write mode

for (i = 0; i < info.size; i++)
  info.data[i] = i;

gst_memory_unmap (mem, &info);
gst_allocator_free (NULL, mem);

GstBuffer

包含一个或多个 GstMemory 还有 metadata(包括 pts / dts / buffer内容的duration/ offset and offset_end / 其他通过 GstMeta)

metadata 包括
1. pts / dts / buffer内容的duration
2. offset and offset_end : 对于video是 frame number, audio是 sample number
3. 其他通过 GstMeta 携带的信息

GstBuffer *buffer;
GstMemory *memory;
gint size, width, height;

size = width * height;
buffer = gst_buffer_new ();
memory = gst_allocator_alloc (NULL, size, NULL);
gst_buffer_insert_memory (buffer, -1, memory);

gst_buffer_new + gst_allocator_alloc 效果等同于 gst_buffer_new_allocate()
可用 gst_buffer_new_wrapped () 从 "char *data[size]" 得到 GstBuffer
可用 gst_buffer_map 获得读写 GstBuffer 的 access

  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 可向 GstBuffer 随意添加元信息。GstMeta系统可提高行为一致性。meta是gstreamer的一个单独的系统(就是说这些api是定制而来的)。
可以参考 gstvideometa.c.h

//为gst-buffer添加 gstreamer定义好的meta
#include 

[...]
  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;
[...]
//获取gst-buffer中的meta
#include 

[...]
  GstVideoCropMeta *meta;

  /* buffer points to a video frame, get the cropping metadata */
  meta = gst_buffer_get_video_crop_meta (buffer);

  if (meta) {
    /* render frame with cropping */
    _render_frame_cropped (buffer, meta->x, meta->y, meta->width, meta->height);
  } else {
    /* render frame */
    _render_frame (buffer);
  }
[...]

下面是添加一个自定义的meta: MyExample

#include 

//最重要的两个函数,meta_api_get_type & meta_get_info,
//一个是从gst-buffer获取meta时用到,一个是为gst-buffer添加meta时用到
#define MY_EXAMPLE_META_API_TYPE (my_example_meta_api_get_type())
#define MY_EXAMPLE_META_INFO (my_example_meta_get_info())
typedef struct _MyExampleMeta MyExampleMeta;

struct _MyExampleMeta {
  GstMeta       meta;    //必须放在第一位置

  gint          age;
  gchar        *name;
};

GType my_example_meta_api_get_type (void);
const GstMetaInfo *my_example_meta_get_info (void);


//gst_buffer_get_my_example_meta 就是我们提供的为gst-buffer获取自定义meta的api
//会返回一个 MyExampleMeta 类型的指针
#define gst_buffer_get_my_example_meta(b) ((MyExampleMeta*)gst_buffer_get_meta((b),MY_EXAMPLE_META_API_TYPE))

//gst_buffer_add_my_example_meta 就是我们提供的为gst-buffer添加自定义meta的api
//gstreamer是先添加了meta,然后返回一个 MyExampleMeta 类型的指针,让你填充meta.
//这个习惯和我们平时不一样,可以将这两步封装在一个函数里面
//#define gst_buffer_add_my_example_meta(b) ((MyExampleMeta*)gst_buffer_add_meta((b),MY_EXAMPLE_META_INFO, NULL))
MyExampleMeta * gst_buffer_add_my_example_meta (GstBuffer *buffer, gint age, const gchar *name);

实现文件

#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;
}
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;
}

GstBufferPool

GstBufferPool 管理具有同样属性的 GstBuffer. 可以配置 GstBufferPool 的最大最小尺寸,使用的 GstAllocator 等。但是配置操作只可以在 GstBufferPool inactivate状态下完成

ALLOCATION query是用来协商element之间的GstMeta GstBUfferPool GstAllocator。由source-pad发起,以协商了的caps为参数,在push buffer前完成。通过协商下游element才会知道要处理的media数据是什么样子的. sinkpad可以在应答中提出建议,source-pad基于这些建议作出决定。sinkpad应答query可以是
1.建议的 GstBufferPool(拥有建议的最大最小size等参数) 组成的数组
2.建议的 GstAllocator(拥有建议的flags/prefix/alignment/padding等参数) 组成的数组
3.下游支持的 GstMeta 组成的数组

#include 
#include 
#include 

  GstCaps *caps;
  GstQuery *query;
  GstStructure *structure;
  GstBufferPool *pool;
  GstStructure *config;
  guint size, min, max;

[...]

  query = gst_query_new_allocation (caps, TRUE);  //find a pool for the negotiated caps now

  if (!gst_pad_peer_query (scope->srcpad, query)) {    //发起 query
    /* query failed, not a problem, we use the query defaults */
  }

  if (gst_query_get_n_allocation_pools (query) > 0) {
    gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);    //返回的 GstBufferPool 第0个应该是最推荐的, 就parse这个
  } else {
    pool = NULL;
    size = 0;
    min = max = 0;
  }

  if (pool == NULL) {
    /* we did not get a pool, make one ourselves then */
    pool = gst_video_buffer_pool_new ();
  }

  config = gst_buffer_pool_get_config (pool);
  //指示 bufferpool 使能 分配的GstBuffer 的特定option, 这样从GstBufferPool取得的GstBuffer就可以添加 GstVideoMeta metadata了
  gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
  gst_buffer_pool_config_set_params (config, caps, size, min, max);
  gst_buffer_pool_set_config (pool, config);

  /* and activate */
  gst_buffer_pool_set_active (pool, TRUE);

[...]

一些基类实现了 propose_allocation() 和 decide_allocation() 方法,方便了协商过程

propose_allocation () 需要为上游element返回一些推荐的 allocation parameters
decide_allocation () 需要根据下游提供的suggestions 决定使用的 allocation parameters

实现这些函数需要修改给定的 GstQuery (update 其中的 pool option或 allocation option)。
硬件相关的element可能对使用的buffer有特定的约束(eg.alignment/padding)。若是buffer生产者(eg. v4l2src/omxvideoenc 都可能是buffer生产者)能生产满足这些约束的buffers,就可以实现高效的数据传输。
一个 v4l2src -> omxvideoenc 的例子

1.v4l2 使用 omxvideoenc 生产的 buffers:
omxvideoenc query硬件要求并产生 GstVideoAlignment, 然后在 omxvideoenc 的 alloc_buffer 实现中,会将alignment以meta的形式添加到生产的 GstBuffer 中.

//这是gstreamer 预定义的videometa, 在 gstvideometa.c.h
meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE,
          GST_VIDEO_INFO_FORMAT (&pool->video_info),
          GST_VIDEO_INFO_WIDTH (&pool->video_info),
          GST_VIDEO_INFO_HEIGHT (&pool->video_info),
          GST_VIDEO_INFO_N_PLANES (&pool->video_info), offset, stride);
//下面继续更新刚刚添加的meta信息,可以看到这里是拿 align信息并填充
if (gst_omx_video_get_port_padding (pool->port, &pool->video_info, &align))
  gst_video_meta_set_alignment (meta, align);

omxvideoenc在 propose_allocation 中(即应答ALLOCATION query)返回这些推荐的pool,然后 v4l2src 在 decide_allocation 中(接收到 ALLOCATION query)获取单个Gstbuffer时可以拿到meta信息,这些mate信息可用来配置driver产生对应的数据并存放于Gstbuffer中。不然的话就只能送出自己的 buffer 后面 omxvideoenc 需要做 copy and transform。

2.v4l2 生产 buffers 供 omxvideoenc 使用:
omxvideoenc query硬件要求并产生 GstVideoAlignment,然后创建名为 video-meta 的 GstStructure 填充 align信息。在 propose_allocation 中(即应答ALLOCATION query) 将 这个GstStructure 作为 meta信息返回。v4l2src 在 decide_allocation 中(接收到 ALLOCATION query) 拿到这个参数得知 buffer layout,然后配置driver生产对应的数据填充GstBuffer并传递给omxvideoenc。否则的话就只能使用内定格式生产 Gstbuffer, omxvideoenc拿到再做转换

 

 

media类型和属性

element之间传递的数据有很多不同的media类型,这些media类型只有被其他element识别才有意义(不然根本没法link)。a capability is a combination of a media type and a set of properties。decoder/transcoder 其实就是从一个 media 类型转换到另一个 media 类型。gstreamer预先定义了许多可被所有element认可的media类型,同时也提供了一套自定义media type的框架(不过需要慎重考虑我们是否确实需要定义新的media类型,考虑一下是否已有类型已经够用?)


media类型通常是 type/x-name 的形式,但是只有类型的定义显然不够,还需要对应的typefind函数用于从输入的数据流中识别此类型。gstreamer提供的typefind机制包含typefind函数(数量不限,用于从输入流中识别特定的某一类型或某几类)和一个调用typefind函数的引擎(称之为 typefind core) 利用 typefind core 我们可以编写autopluggers动态检测media类型并组建pipeline。

(Typefinding is a normal part of a pipeline, it will read data for as long as the type of a stream is unknown. During this period, it will provide data to all plugins that implement a typefinder.)

typefind函数都集中放在了 gst-plugins-base/gst/typefind/gsttypefindfunctions.c 中(这样可以避免为了检测media类型而加载过多的plugin) 。可以看到 gsttypefindfunctions.c 中只有一个 GST_PLUGIN_DEFINE 但是 plugin_init 中使用 TYPE_FIND_REGISTER 定义了很多plugin,eg. video/x-h264, gst-inspect 可以看到 libgsttypefindfunctions.so 包含 video/x-h264 等 plugin。至于如何使用这个typefind func 就需要参看 https://gstreamer.freedesktop.org/documentation/application-development/advanced/autoplugging.html?gi-language=c 了。

gsttypefindelement.c.h 实现了一个 类型(应该这就是所提到的 typefind core),但是没有定义plugin ?(decodebin直接使用factory_make则 typefind 必然被注册为一个plugin了,只是可能被封装在gstreamer的GstTypeFind框架中了)

关于 typefind

以下内容参考了博文 https://blog.csdn.net/houxiaoni01/article/details/99333907  (讲的非常清晰)http://blog.chinaunix.net/uid-24922718-id-3267099.html 和 https://blog.csdn.net/acs713/article/details/7754213

decodebin2 是动态加载机制实现的一个解码bin,动态加载核心便是在bin中加载了一个typefind的插件,该插件就是实现对于所注册插件的查找功能。来看下decodebin2中是如何使用typefind插件的。

在 gstdecodebin2.c 文件中可以看到,在初始化 decodebin2 管道的过程中(gst_decode_bin_init),添加了一个typefind的插件并将其 add 到 bin。

static void
gst_decode_bin_init (GstDecodeBin * decode_bin)
{
  ...
  /* we create the typefind element only once */
  decode_bin->typefind = gst_element_factory_make ("typefind", "typefind");
  ...
}

在decodebin状态切换到PAUSED的时候,也会带动typefind的状态切换为PAUSED。在激活pad的时候,有查询scheduling,确定使用push模式还是pull模式。而typefind将会选择PULL模式,这将会创建task运行 gst_type_find_element_loop() 函数。typefind 从source读取数据,然后从这部分数据解析信息,查找相应的caps,知道相应的caps之后,接着将会把caps信息通过信号发送出去,等待别人的处理。下面来看看在task中是如何查找的

static void
gst_type_find_element_loop (GstPad * pad)
{
    if (typefind->mode == MODE_TYPEFIND) {
        /* 这个peer一般是proxy pad */
        peer = gst_pad_get_peer (pad);
        if (peer) {
            gchar *ext;
            ...
            /* 查询文件的大小等信息 */
            if (!gst_pad_query_duration (peer, GST_FORMAT_BYTES, &size)) {
                GST_WARNING_OBJECT (typefind, "Could not query upstream length!");
                gst_object_unref (peer);
                
                ret = GST_FLOW_ERROR;
                goto pause;
            }
            
            /* 通过上游的uri,查询source的文件后缀,比如返回mp4 */
            ext = gst_type_find_get_extension (typefind, pad);
            
            /* 查找caps */
            found_caps =
            gst_type_find_helper_get_range (GST_OBJECT_CAST (peer),
                                    GST_OBJECT_PARENT (peer),
                                    (GstTypeFindHelperGetRangeFunction) (GST_PAD_GETRANGEFUNC (peer)),
                                    (guint64) size, ext, &probability);
            ...
        }
        ...
    }
    ...
}

gst_type_find_helper_get_range()函数又是如何查找caps的呢,将会先根据后缀名,对gstreamer支持的caps进行一个排序,方便后续对比,排序之后,将会预读取数据,分析具体的数据,是否可以使用相应的caps。下面看代码。

GstCaps *
gst_type_find_helper_get_range (GstObject * obj, GstObject * parent,
    GstTypeFindHelperGetRangeFunction func, guint64 size,
    const gchar * extension, GstTypeFindProbability * prob)
{
    ...
    /* 1. 通过该函数,将会返回注册到gstreamer core的 GST_TYPE_TYPE_FIND_FACTORY 类型的 GstPluginFeature */
    type_list = gst_type_find_factory_get_list ();
    
    /* 2. 将会在这里,根据之前从uri得到的文件后缀,对Feature链表
    * 进行排序,文件后缀名与feature的extension信息对比,相同的
    * 将会将feature移动到表头 */
    if (extension) {
        for (l = type_list; l; l = next) {
        ...
        }
    }
    ...
    /* 3. 将会在这里调用feature的功能探测函数预读取数据,确认文件数据格式 */
    for (l = type_list; l; l = l->next) {
        helper.factory = GST_TYPE_FIND_FACTORY (l->data);
        gst_type_find_factory_call_function (helper.factory, &find);
        if (helper.best_probability >= GST_TYPE_FIND_MAXIMUM)
            break;
    }
    ...
}

第一步的操作,该函数将会返回 GST_TYPE_TYPE_FIND_FACTORY 类型的 feature,这些feature都是查找类型的feature。它们在注册feature的时候,将会注册相应的探测函数,在探测函数中通过读取数据,然后判断类型,是否需要该feature可以解析处理的,就是这样的一个功能,gstreamer才会知道,当前数据该用哪个caps。

这些feature的注册是在 gst-plugins-base-1.xx.xx/gst/typefind目录下的 gsttypefindfunctions.c 文件,在该文件的 plugin_init() 函数中分别通过宏 TYPE_FIND_REGISTER_START_WITH、TYPE_FIND_REGISTER_RIFF、TYPE_FIND_REGISTER向gstreamer core注册了相应的GST_TYPE_TYPE_FIND_FACTORY类型的feature,在注册的时候,赋值了相应的探测function、extension等信息。接着,在我们通过gst_type_find_helper_get_range()函数探测caps的时候,就会有第一步的获取feature,第二步的根据extension排序,第三步的通过功能探测函数确认最终的feature。

介绍到这里,小伙伴们应该知道gst_type_find_helper_get_range()返回的是什么了吧,它返回的就是支持解析、处理输入数据的caps,比如输入的是封装格式为MOV的mp4文件,那么返回的是video/quicktime;输入的是编码格式为H264的RAW数据,返回的是video/x-h264。

回到gst_type_find_element_loop()函数,当查找到caps的时候,又将会发送相应的信号,代码如下:

static void
gst_type_find_element_loop (GstPad * pad)
{
    if (typefind->mode == MODE_TYPEFIND) {
        ...
        /* 查找caps */
        ...
        /* Set to MODE_NORMAL before emitting have-type, in case it triggers a seek */
        typefind->mode = MODE_NORMAL;
        gst_type_find_element_emit_have_type (typefind, probability, found_caps);
    }
    ...
}

在gst_type_find_element_loop()函数中通过gst_type_find_element_emit_have_type()函数将caps信息生成caps EVENT然后保存在pad,接着发送have-type信号。
在 decodebin2 的状态切换函数,从READY切换到PAUSED的时候,将会监听 "have-type" 信号,因此信号被 decodebin2 捕获后,触发 回调函数 type_found

static GstStateChangeReturn
gst_decode_bin_change_state (GstElement * element, GstStateChange transition)
{
    switch (transition) {
        case GST_STATE_CHANGE_READY_TO_PAUSED:
            ...
            /* connect a signal to find out when the typefind element found
            * a type */
            dbin->have_type_id =
                    g_signal_connect (dbin->typefind, "have-type",
                    G_CALLBACK (type_found), dbin);
            ...
            break;
        ...
    }
    ...
}

在回调函数 type_found 中,会对 typefind 的 srcpad 进行设定,然后重点看 type_found -> analyze_new_pad() 函数。

analyze_new_pad 中为typefind添加了srcpad后,发送"autoplug-continue"信号,处理函数会返回结果告知我们是否有必要进一步进行动态加载,若需要进一步实现动态加载,会发送信号"autoplug-factories"信号。

      /* 1.d else get the factories and if there's no compatible factory goto
       * unknown_type */
      g_signal_emit (G_OBJECT (dbin),
          gst_decode_bin_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, dpad, caps,
          &factories);

autoplug_factories()处理后将结果返回 到 GValueArray *factories 中。autoplug_factories()->gst_decode_bin_update_factories_list->gst_element_factory_list_get_elements。
autoplug_factories()->gst_element_factory_list_filter 得到满足caps的可能元件的工厂列表。


    static GValueArray *
    gst_decode_bin_autoplug_factories (GstElement * element, GstPad * pad,
        GstCaps * caps)
    {
      GList *list, *tmp;
      GValueArray *result;
      GstDecodeBin *dbin = GST_DECODE_BIN_CAST (element);

      GST_DEBUG_OBJECT (element, "finding factories");

      /* return all compatible factories for caps */
      g_mutex_lock (dbin->factories_lock);
      gst_decode_bin_update_factories_list (dbin);
      list =
          gst_element_factory_list_filter (dbin->factories, caps, GST_PAD_SINK,
          FALSE);
      g_mutex_unlock (dbin->factories_lock);

      result = g_value_array_new (g_list_length (list));
      for (tmp = list; tmp; tmp = tmp->next) {
        GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data);
        GValue val = { 0, };

        g_value_init (&val, G_TYPE_OBJECT);
        g_value_set_object (&val, factory);
        g_value_array_append (result, &val);
        g_value_unset (&val);
      }
      gst_plugin_feature_list_free (list);

      GST_DEBUG_OBJECT (element, "autoplug-factories returns %p", result);

      return result;
    }

然后从中选择出最合适的元件。


回到 analyze_new_pad(),connect_pad() 中会通过 gst_element_factory_create()创建并连接最终的 element,得到element之后,将其添加到bin


 

你可能感兴趣的:(音视频学习)