gstreamer&&deepstream 学习笔记(一)——gstreamer

主要参考:


https://blog.csdn.net/han2529386161/article/details/102896875
https://gstreamer.freedesktop.org/documentation/tutorials/index.html

Gstreamer 三个重要概念: 元件(element),箱柜(bin),衬垫(pad) 

衬垫基本概念:


下面的这个比喻可能对你理解衬垫(Pads)有所帮助。一个衬垫(Pads)很象一个物理设备上 
的插头。 例如一个家庭影院系统。一个家庭影院系统由一个功放(amplifier),一个 DVD 机,还 
有一个无声的视频投影组成。 我们需要连接 DVD 机到功放(amplifier),因为两个设备都有音 
频插口;我们还需要连接投影机到 DVD 机上,因为 两个设备都有视频处理插口。但我们很难 
将投影机与功放(amplifier)连接起来,因为他们之间处理的是不同的 插口。GStreamer 衬垫 
(Pads)的作用跟家庭影院系统中的插口是一样的。 
数据向元件(element)以外流出可以通过一个或者多个 source 衬垫(Pads),元件(element)接受数据是通过一个或者多个 
sink 衬垫(Pads)来完成的。Source 元件(element)和 sink 元件(element)分别有且仅有一个 sink 
衬垫(Pads)或者 source 衬垫(Pads)。数据在这里代表的是缓冲区(buffers) (GstBuffer 对象描述 
了数据的缓冲区(buffers)的信息)和事件(events) (GstEvent 对象描述了数据的事件(events) 
信息)。 
衬垫(Pads)是元件对外的接口。数据流从一个元件 
的源衬垫(source pad)到另一个元件的接收衬垫(sink pad)。衬垫的功能(capabilities)决定了一 
个元件所能处理的媒体类型。

元件elements

类型转换尽量用宏!(GST_OBJECT 
G_OBJECT 
GST_BIN
 )

每 个 GstElement 都 从 其 基 类 GstObject 继 承 了 至 少 一 个 “ 名 字 ” 属 性 。 这 
个 名 字 属 性 将 在 函 数 gst_element_factory_make ()或者函数 gst_element_factory_create 
()中使用到。你可通过函数 gst_object_set_name 设置该属性,通过 gst_object_get_name 得到
为gstElemnet设置某些属性(成员变量赋值)
g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);
参考自: <https://www.cnblogs.com/phinecos/archive/2009/06/07/1498166.html> 


Bin/pipeline 是一种特殊的GstElements


使用gst_pipeline_new ("my-pipeline");  创建pipline 使用gst_bin_new 创建bin
gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL); 
将其他element添加进去,
gst_element_link_many (source, filter, sink, NULL)),
gst_element_link () 以及 gst_element_link_pads()来 
实现。你可以使用不同的 gst_pad_link_* ()函数来得到单个衬垫的引用并将它们链接起来。 
使用 gst_bin_add() 
往箱柜中增加元件,使用 gst_bin_remove()移除箱柜中的元件。当你往箱 柜中增加一个元件 
后,箱柜会对该元件产生一个所属关系 ;当你销毁一个箱柜后,箱柜中的元件同样被销毁 
(dereferenced);当你将一个元件从箱柜移除后,该元件会被自动销毁(dereferenced)。

注意:在链接不同的元件之前,你需要确保这些元件都被加在同一个箱柜中,因为将一个元件 
加载到一个箱柜中会破坏该元件已存在的一些链接关系。同时,你不能直接链接不在同一箱 
柜或管道中的元件。如果你想要连接处于不同层次中的元件或衬垫,你将使用到精灵衬垫(关 
于精灵衬垫更多的信息将在后续章节中讲到) 

 

GstElemnet 状态:


GST_STATE_NULL: 默认状态。
GST_STATE_READY: 准备状态。元件会得到所有所需的全局资源,这些全局资源将被通过 该元件的数据流所使用。
GST_STATE_PAUSED: 在这种状态下,元件已经对流开始了处理,但此刻暂停了处理。
GST_STATE_PLAYING: PLAYING 状态除了当前运行时钟外,其它与 PAUSED 状态一模一样。 
改变状态接口:
gst_element_set_state()

 

总线:


每一个管道(pipeline)默认包含一个总线,所以应用程序不需要再创建总线。应用程序只需要在总线上 
设置一个类似于对象的信号处理器的消息处理器。当主循环运行的时候,总线将会轮询这个 
消息处理器是否有新的消息,当消息被采集到后,总线将呼叫相应的回调函数来完成任务。 
使用总线的两种方法:
1.运行glib的主循环,使用侦听器进行侦听: 

gst_bus_add_watch ()/gst_bus_add_signal_watch ()


2.自己侦听总线消息:

gst_bus_peek () 和/或 gst_bus_poll ()

如何获取pipeline上的总线    :

gst_pipeline_get_bus (GST_PIPELINE (pipeline));


增加侦听器(一个回调函数):

gst_bus_add_watch (bus, my_bus_callback, NULL);

callback函数签名:

static gboolean 
my_bus_callback (GstBus *bus, 
GstMessage *message, 
gpointer data);

创建glib主循环+运行:

loop = g_main_loop_new (NULL, FALSE); 
g_main_loop_run (loop); 

注意:如果你使用了默认的 GLib 主循环来实现管道与应用程序的交互,建议你可以将“消息 
”信号链接到总线上, 而不必在管道上使用侦听器,这样对于所有可能的消息类型,你就不需用 
switch(),只要连接到所需要的信号格式 为"message::<type>",其中<Type>是一种消息类型(见 
下一节对消息类型的详细解释) 

上面的代码段也可以这样写: 

GstBus *bus; 
//.....
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline); 
gst_bus_add_signal_watch (bus); 
g_signal_connect (bus, "message::error", G_CALLBACK (cb_message_error), NULL); 
g_signal_connect (bus, "message::eos", G_CALLBACK (cb_message_eos), NULL); 


Bus 传递的消息类型:
1。error/warning/info
这些消息含有 一个带有主要的错误类型和消息的 GError,和一个任选的调试字符串。这两项都可以用 gst_message_parse_error (), _parse_warning () 以及 _parse_info ()三个函数来提取其信息。当 使用完毕后,错误和修正字符串都将被释放。
2.eos
3.tag(采样率,艺术家,歌曲名等)
解析:gst_message_parse_tag ()
释放:gst_tag_list_free ()

4.状态转换:
当状态成功的转换时发送该消息。函数 
gst_message_parse_state_changed ()可以用来解析转换中的新旧状态。 

5.缓冲:
当缓冲网络数据流时此消息被发送。你可以通过函数 
gst_message_get_structure ()的返回值, 来解析"buffer-percent" 属性,从而手动的得到缓冲进度 
(该缓冲进度以百分比的形式表示)。


Pads


Pads 根据时效性分类:
永久型,随机(动态)型,请求型

随机(动态)型:


gstElement在被创建时不会立即创建的衬垫如:ogg demuxer,
它在 Ogg 流中检测到一些元数据流时(例如 vorbis,theora ),它 
会为每个元数据流创建动态衬垫。同样,它也会在流终止时删除该衬垫。(对于动态pipeline很重要)
使用信号处理器得知某个动态型pad 被创建:
1.创建pipline,elements
2。 Add  elements to pipeline
3.使用

gst_element_link_pads (source, "src", demux, "sink");

将source 的src pad 与 demux的 sink pad 链接
4.监听demux的pad创建信号

g_signal_connect (demux, "pad-added", G_CALLBACK (cb_new_pad), NULL); 


回调函数:

static void 
cb_new_pad (GstElement *element, 
GstPad *pad, 
gpointer data) 
{ 
gchar *name; 
name = gst_pad_get_name (pad); 
g_print ("A new pad %s was created\n", name); 
g_free (name); 
/* here, you would setup a new pad link for the newly created pad */ 
[..] 
}

5.启动 pipeline

gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING); 
loop = g_main_loop_new (NULL, FALSE); 
g_main_loop_run (loop);


请求型衬垫:


根据请求被创建:

//example:请求 tee 创建 src pad (tee 元件)
static void 
some_function (GstElement *tee) 
{ 
GstPad * pad; 
gchar *name; 
pad = gst_element_get_request_pad (tee, "src%d"); //请求pad  "src%d"为pad template name 由 gst-inspact 可以得到
name = gst_pad_get_name (pad); 
g_print ("A new pad %s was created\n", name); 
g_free (name); 
/* here, you would link the pad */ 
[..] 
/* and, after doing that, free our reference */ 
gst_object_unref (GST_OBJECT (pad)); 
}

请求兼容性衬垫:
使用:

gst_element_get_compatible_pad (mux, tolink_pad);  //tolink_pad 需要被兼容的pad mux:产生衬垫的element
gst_pad_link (tolinkpad, pad);  //将两个pad连接起来

Pad 的capabilities:

当通过该衬垫模板创建了一个衬垫后,该衬垫允许通过的 媒体类型
衬 垫 的 功 能 通 过 GstCaps 对 象 来 进 行 描 述 。 一 个 GstCaps 对 象 会 包 含 一 
个 或 多 个 GstStructure 。 一 个 GstStructure 描述一种媒体类型。一个被数据流通过的衬垫 
(negotiated pad)存在功能集(capabilities set),每种功能只包含一个 GstStructure 结构。结构中 
只包含固定的值。但上述约束并不对尚未有数据流通过的衬垫 (unnegotiated 
pads)或衬垫模板有效。

 

 

衬垫性能用途:

衬垫的功能(Capabilities)(简称 caps)描述了两个衬垫之间的数据流类型,或者它们所支持的数

据流类型。功能主要用于以下用途:

自动填充

(Autoplugging): 根据元件的功能自动找到可连接的元件。所有的自动充填器

(autopluggers)都采用的这种方法。

兼容性检测(Compatibility detection):

 当两个个衬垫连接时,GStreamer 会验证它们是否采 用的同样的数据流格式进行交互。连接并验证两个衬垫是否兼容的过程叫”功能谈判”(caps

negotiation)。

元数据 (Metadata):通过读取衬垫的功能(capabilities),应用程序能够提供有关当前流经衬垫 的正在播放的媒体类型信息。而这个信息我们叫做元数据(Metadata)。

过滤 (Filtering):

 应用程序可以通过衬垫的功能(capabilities)来给两个交互的衬垫之间的媒

体类型加以限制,这些被限制的媒体类型的集合应该是两个交互的衬垫共同支持的格式集的子集。

举例来说:应用程序可以使用"filtered caps"指明两个 交互的衬垫所支持的视频大小(固 定或不固定)。在本手册的后面部分 Section 18.2,你可以看到一个使用带过滤功能(filtered

caps)衬垫的例子。你可以往你的管道中插入一个 capsfilter 元件,并设置其衬垫的功能

(capabilities)属性,从而实现衬垫的功能(capabilities)的过滤。功能过滤器(caps filters)一般放在

一些转换元件 后 面 , 将 数 据 在 特 定 的 位 置 强 制 转 换 成 特 定 的 输 出 格 式 。 这

些 转 换 元 件 有 :audioconvert、audioresample、ffmpegcolorspace 和 videoscale。

获取pad capabilities:

gst_caps_get_structure()得到一个功能的 GstStructure,通过 gst_caps_get_size()得到一个

GstCaps 对象中的 GstStructure 数量

简易衬垫的功能(capabilities)(simple caps )是指仅有一个 GstStructure,固定衬垫的功能

(capabilities)(fixed caps)指其仅有一个 GstStructure,且没有可变的数据类型(像范围或列表等)。

另外还有两种特殊的功能 - 任意衬垫的功能(capabilities)(ANY caps)和空衬垫的功能

(capabilities)(empty caps)。

//example:get pad capabilities
static void

read_video_props (GstCaps *caps)

{

gint width, height; const GstStructure *str;

g_return_if_fail (gst_caps_is_fixed (caps));

str = gst_caps_get_structure (caps, 0);

if (!gst_structure_get_int (str, "width", &width) ||

!gst_structure_get_int (str, "height", &height)) {

g_print ("No width/height available\n");

return;

}

g_print ("The video size of this set of capabilities is %dx%d\n",

width, height);

}

 

使用caps过滤器限制lainggeelemnet之间流通的数据类型:

static gboolean

link_elements_with_filter (GstElement *element1, GstElement *element2)

{

gboolean link_ok;

GstCaps *caps;

caps = gst_caps_new_simple ("video/x-raw-yuv",

"format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('I', '4', '2', '0'),

"width", G_TYPE_INT, 384,

"height", G_TYPE_INT, 288,

"framerate", GST_TYPE_FRACTION, 25, 1,

NULL);

link_ok = gst_element_link_filtered (element1, element2, caps); //创建capsfilter element 并插入在 element1 与 element2 之间

gst_caps_unref (caps);

if (!link_ok) {

g_warning ("Failed to link element1 and element2!");

}

return link_ok;

}

 

static gbooleanlink_elements_with_filter (GstElement *element1, GstElement *element2)

{

gboolean link_ok;

GstCaps *caps;

caps = gst_caps_new_full (

gst_structure_new ("video/x-raw-yuv",

"width", G_TYPE_INT, 384,

"height", G_TYPE_INT, 288,

"framerate", GST_TYPE_FRACTION, 25, 1,

NULL),

gst_structure_new ("video/x-raw-rgb",

"width", G_TYPE_INT, 384,

"height", G_TYPE_INT, 288,

"framerate", GST_TYPE_FRACTION, 25, 1,

NULL),

NULL);

link_ok = gst_element_link_filtered (element1, element2, caps);

gst_caps_unref (caps);

if (!link_ok) {

g_warning ("Failed to link element1 and element2!");

}

return link_ok;

}

精灵衬垫(Ghost pads) 


给Bin设置的pad(其实是 bin中的 某个 element的某个 pad 的引用)
这样可以将Bin当成普通元件用
GhostPad可以用到任何一种元件上。
1.创建 ghost pad:

gst_ghost_pad_new ("sink", pad) //ghost pad name,引用目标。


2.将ghost pad 加入到element上:

gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad)); 

缓冲区/事件(buffer/events)


--pipeline 中流动的数据=buffer(数据们)+events(信息,流终止的信号)
One source element create one buffer and transfer the data to next elemnet.
The consist of the buffer:
 1. a pointer to memory
    2. The length of the memory
    3. Timestamp for the buffer
    4. Reference counter
Example:

static void 
seek_to_time (GstElement *element, 
guint64 time_ns) 
{ 
GstEvent *event; 
event = gst_event_new_seek (1.0, GST_FORMAT_TIME, 
GST_SEEK_FLAG_NONE, 
GST_SEEK_METHOD_SET, time_ns, 
GST_SEEK_TYPE_NONE, G_GUINT64_CONSTANT (0)); 
gst_element_send_event (element, event); 
} 


以上代码主要是说明其具体的工作原理,快捷算法是一个函数 gst_element_seek ()。 

获取流当前处理的位置/长度:gst_element_query()/
(gst_element_query_position()/
gst_element_query_duration())

static gboolean 
cb_print_position (GstElement *pipeline) 
{ 
GstFormat fmt = GST_FORMAT_TIME; 
gint64 pos, len; 
if (gst_element_query_position (pipeline, &fmt, &pos) 
&& gst_element_query_duration (pipeline, &fmt, &len)) { 
g_print ("Time: %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r", 
GST_TIME_ARGS (pos), GST_TIME_ARGS (len)); 
} 
/* call me again */ 
return TRUE; 
} 
gint 
main (gint argc, 
gchar *argv[]) 
{ 
GstElement *pipeline; 
[..] 
/* run pipeline */ 
g_timeout_add (200, (GSourceFunc) cb_print_position, pipeline); 
g_main_loop_run (loop); 

事件的工作方式与查询类似。查询中的分发有着同事件一样的处理机制 (当然也有着同样 
的局限性),它们被发送给顶层(toplevel)的管道并进行 相应的处理。尽管在应用程序与元件间 
使用事件进行交互的方法有很多,我们现在仅关注定位,也叫事件定位 (seek-event)。一个 事件 
定位包含了播放率 (playback rate),定位偏移格式 (seek offset format)(一些偏移的数据单元:时 
间、音频样本、视频帧或一串字节),可选的相关查询标志(比如是否清空内部缓冲区),查询方 
式(指明了给予怎样的 相关偏移),查询偏移。

static void 
seek_to_time (GstElement *pipeline, 
gint64 time_nanoseconds) 
{ 
if (!gst_element_seek (pipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 
GST_SEEK_TYPE_SET, time_nanoseconds, 
GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) { 
g_print ("Seek failed!\n"); 
} 
} 


查询通常在管道处于 PAUSED 或 PLAYING 状态才被执行并返回结果。(当处于 PLAYING 状 
态时,管道会自动暂停, 
执行查询,然后将重新置位,再回到 PLAYING 状态) 
认识到查询不会立即发生这一点非常重要,只有当函数 gst_element_seek () 返回时才会完成 
查询动作。根据包含的元件不同,实际的查询可能会在另一个线程中执行(数据流线程 
(streaming thread)),在新位置的缓存到达像接收元件这样的下游(downstream)元件时,需要一 
些时间开销。
根据系 统中可用元件自动建立一个最佳的管道。这个过程叫 做自动加载 
(autoplugging),使用typefind 插件,并监听
"have-type" 信号:

static gboolean 
idle_exit_loop (gpointer data) 
{ 
g_main_loop_quit ((GMainLoop *) data); 
/* once */ 
return FALSE; 
} 

static void 
cb_typefound (GstElement *typefind, 
guint probability, 
GstCaps *caps, 
gpointer data) 
{ 
GMainLoop *loop = data; 
gchar *type; 
type = gst_caps_to_string (caps); 
g_print ("Media type %s found, probability %d%%\n", type, probability); 
g_free (type); 
/* since we connect to a signal in the pipeline thread context, we need 
* to set an idle handler to exit the main loop in the mainloop context. 
* Normally, your app should not need to worry about such things. */ 
g_idle_add (idle_exit_loop, loop); 
}


g_signal_connect (typefind, "have-type", G_CALLBACK (cb_typefound), loop);


补充:

GStreamer 高级组件


Playbin 是一个元件,它可以通过标准 GStreamer API 来创建(像 
gst_element_factory_make())。这个工厂元件通常简称 为“playbin”。作为一个GstPipeline(同时也是 GstElement), playbin 能够自动支持管道的所有特性,包括错误处 
理, 标签支持,状态处理 ,得到流位置信息,查询等。 
建立一条 playbin 如同创建一个 playbin 元件的实例一般简单。通过 URI 特性来设置文 
件地址(这必须是一个有效的 URI,格式为"<protocol>://< location> ",例如 
file:///tmp/my.ogg or http://www.example.org/stream.ogg )。然后设置元件的状态 
为 GST_STATE_PLAYING state 。至此, playbin 将建立一条播放媒体文件的管道。 


gst log输出类:GstDebugCategory 

用于 gstreamer输出日志控制
创建:
GST_DEBUG_CATEGORY (NVDS_APP);
初始化:
 GST_DEBUG_CATEGORY_INIT (NVDS_APP, "NVDS_APP", 0, NULL);

GST_DEBUG_CATEGORY_EXTERN

参考自: <https://gstreamer.freedesktop.org/documentation/gstreamer/gstinfo.html?gi-language=c#GST_DEBUG_CATEGORY_EXTERN> 

声明外部变量 log category

调用 输出log
GST_CAT_XXX(category,“” ) 

GST_MESSAGE_SRC_NAME(message) 

参考自:<https://gstreamer.freedesktop.org/documentation/gstreamer/gstmessage.html?gi-language=c#GST_MESSAGE_SRC> 

获取产生message的source gstElement

Gstelement bin 开启 message-forward ,使得 message可以向前传递
Forward all children messages, even those that would normally be filtered by the bin. This can be interesting when one wants to be notified of the EOS state of individual elements, 

参考自: <https://gstreamer.freedesktop.org/documentation/gstreamer/gstbin.html?gi-language=python#GstBin:message-forward> 

 

重要概念:GstBuffer

GstBuffer :上游插件向下游插件传递的数据,包含了内存以及metadata数据。
创建:gst_buffer_new
为 buffer 创建存储空间:
GstMemory memory

memory = gst_allocator_alloc (NULL, size, NULL);
   gst_buffer_insert_memory (buffer, -1, memory);

来自 <https://gstreamer.freedesktop.org/documentation/gstreamer/gstbuffer.html?gi-language=c#GstBuffer> 

或者直接调用:
    gst_buffer_new_allocate 创建gstBuffer 同时预分配一定大小的空间

buffer可以包含一堆 GstMemory 对象:
gst_buffer_n_memory 查看有多少 GstMemory attach到 gstBuffer 上
 gst_buffer_peek_memory
获取gstMemory:
GstMemory *
gst_buffer_get_memory (GstBuffer * buffer,
                       guint idx)

Buffer 的DTS decode timestamp
PTS:The buffer PTS refers to the timestamp when the buffer content should be presented to the user and is not always monotonically increasing.

gst_buffer_ref 增加gstBuffer的引用计数

 gst_buffer_copy_region 实现buffer的浅拷贝

 gst_buffer_make_writable 用于原地(in place)修改一个gstBuffer
修改 gstbuffer的flag:
 GST_BUFFER_FLAG_SET 
 GST_BUFFER_FLAG_UNSET 
 GST_BUFFER_FLAG_IS_SET
获取flags:
gst_buffer_get_flags (GstBuffer * buffer)

Buffer 合并:
 gst_buffer_append

buffer的meta data操作:
 gst_buffer_add_meta
gst_buffer_get_meta

对于element 而言 gstBuffer 应当 调用 
 gst_buffer_unref 减计数 或者 
 gst_pad_push 发送到pad上

 GstBufferPool :buffer的内存池,当buffer基于此创建时,当此buffer被unref 内存会返回给对应的bufferPool
The GstParentBufferMeta is a meta which can be attached to a GstBuffer to hold a reference to another buffer that is only released when the child GstBuffer is released.
Typically, GstParentBufferMeta is used when the child buffer is directly using the GstMemory of the parent buffer, and wants to prevent the parent buffer from being returned to a buffer pool until the GstMemory is available for re-use.

GstBuffer *
gst_buffer_new_wrapped (gpointer data,
                        gsize size)
基于一块内存创建buffer,线程安全

GstBuffer *
gst_buffer_new_wrapped_bytes (GBytes * bytes)
Creates a new GstBuffer that wraps the given bytes. The data inside bytes cannot be NULL and the resulting buffer will be marked as read only.


gboolean
gst_buffer_map (GstBuffer * buffer,
                GstMapInfo * info,
                GstMapFlags flags)

将buffer 的合并内存块指针返回到GstMapInfo中(将分散的buffer memory 合并映射),使用完后需要: gst_buffer_unmap 

获取buffer 中data的两种方法:
1.使用GstMapInfo
2.独立的获取GstMemory(peek or get)

The _map() and _unmap() functions will always return the memory of all blocks as one large contiguous region. Using these functions might be more convenient than accessing the individual memory blocks at the expense of being more expensive because it might perform memcpy operations.
 

  • 0
    点赞
  • 8
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

Lord_Rebel

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值