GLib
概述
- 实现跨平台
- 避免重复造轮子
GLib的上下层结构:
GLib的组成部分:
GLib代码组成:
GModule代码组成:
GObject代码组成:
GIO代码组成:530个文件,太多不列了!
如何使用glib
安装glib(ubuntu系统)
直接通过命令安装:
sudo apt install -y libglib2.0-dev
下载源码包自己编译安装:
git clone https://gitlab.gnome.org/GNOME/glib.git
cd glib
mkdir out
meson out
cd out
ninja
sudo ninja install
使用glib
包含相关的头文件,即可开展编程
#include <glib.h> //GLib
#include <gmodule.h> //GModlule
#include <glib-object.h> //GObject
#include <gio/gdesktopappinfo.h> //GIO
#include <gio/gfiledescriptorbased.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <gio/gunixfdmessage.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixmounts.h>
#include <gio/gunixoutputstream.h>
编译
--------------------------------------简易版--------------------------------------------------
gcc glib_test.c -o glib_test `pkg-config --libs --cflags glib-2.0`
fuqiang@fuqiang-virtual:~/workspace$ pkg-config --libs --cflags glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lglib-2.0
--------------------------------------完整版--------------------------------------------------
gcc glib_test.c -o glib_test `pkg-config --libs --cflags glib-2.0 gmodule-2.0 gobject-2.0 gio-2.0`
fuqiang@fuqiang-virtual:~/workspace/test_gstreamer$ pkg-config --libs --cflags glib-2.0 gmodule-2.0 gobject-2.0 gio-2.0
-pthread -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -Wl,--export-dynamic -lgmodule-2.0 -pthread -lgio-2.0 -lgobject-2.0 -lglib-2.0
优势
- 跨平台的动态库加载函数(GModule)
- 事件循环分发处理机制GMainLoop (GLib)
- 跨平台的线程(线程池)函数,信号量函数,互斥锁函数(GLib)
- 提供了异步队列、线性表、动态数组、单/双向链表、哈希表、多叉树、平衡二叉树、字符串等常用容器(GLib)
- 词法分析,xml语言解析、ini文件读写等功能(GLib)
- 命令行参数解析(GLib)
- 一套完整的log机制(GLib)
- slice内存分配结构(GLib)
- 简单的正则表达式功能(GLib)
- GObject面向对象封装设计(GObject)
- IO函数(GIO)
特性说明(docs.gtk.org/glib/index.html)
GModule(Dynamically loaded modules)
Gstreamer中的动态加载插件就是通过这个实现
#if (G_MODULE_IMPL == G_MODULE_IMPL_DL)
#include "gmodule-dl.c"
#elif (G_MODULE_IMPL == G_MODULE_IMPL_WIN32)
#include "gmodule-win32.c"
#elif (G_MODULE_IMPL == G_MODULE_IMPL_AR)
#include "gmodule-ar.c"
GModule结构体定义:
struct _GModule
{
gchar *file_name;
gpointer handle;
guint ref_count : 31; //引用计数,多次open
guint is_resident : 1; //需要常驻,不能unload
GModuleUnload unload; //回调函数
GModule *next; //加载的modules链表
};
一些代码:
GModule*
g_module_open_full (const gchar *file_name,
GModuleFlags flags,
GError **error)
{
...
module = g_module_find_by_name (file_name);
if (module)
{
module->ref_count++;
}
...
/* we don't call unload() if the initialization check failed. */
if (!check_failed)
g_module_symbol (module, "g_module_unload", (gpointer) &module->unload);
...
}
gboolean
g_module_close (GModule *module)
{
...
module->ref_count--;
if (!module->ref_count && !module->is_resident && module->unload)
{
GModuleUnload unload;
unload = module->unload;
module->unload = NULL;
unload (module);
}
...
}
官方示例:
// the function signature for 'say_hello'
typedef void (* SayHelloFunc) (const char *message);
gboolean
just_say_hello (const char *filename, GError **error)
{
SayHelloFunc say_hello;
GModule *module;
module = g_module_open (filename, G_MODULE_BIND_LAZY); //dlopen
if (module == NULL)
{
g_set_error (error, FOO_ERROR, FOO_ERROR_BLAH,
"%s", g_module_error ());
return FALSE;
}
if (!g_module_symbol (module, "say_hello", (gpointer *)&say_hello)) //dlsym
{
g_set_error (error, SAY_ERROR, SAY_ERROR_OPEN,
"%s: %s", filename, g_module_error ());
if (!g_module_close (module))
g_warning ("%s: %s", filename, g_module_error ());
return FALSE;
}
if (say_hello == NULL)
{
g_set_error (error, SAY_ERROR, SAY_ERROR_OPEN,
"symbol say_hello is NULL");
if (!g_module_close (module))
g_warning ("%s: %s", filename, g_module_error ());
return FALSE;
}
// call our function in the module
say_hello ("Hello world!");
if (!g_module_close (module)) //dlclose
g_warning ("%s: %s", filename, g_module_error ());
return TRUE;
}
GMainLoop(gmain.h/gmain.c)
- 基于IO多路复用(select/poll/epoll)
- 事件动态添加
- 优先级管理
#define G_PRIORITY_HIGH -100
#define G_PRIORITY_DEFAULT 0
#define G_PRIORITY_HIGH_IDLE 100
#define G_PRIORITY_DEFAULT_IDLE 200
#define G_PRIORITY_LOW 300 //background
结构体:
struct _GMainLoop
{
GMainContext *context;
gboolean is_running; /* (atomic) */
gint ref_count; /* (atomic) */
};
...
struct _GSource
{
/*< private >*/
gpointer callback_data;
GSourceCallbackFuncs *callback_funcs;
const GSourceFuncs *source_funcs;
guint ref_count;
GMainContext *context;
gint priority; //优先级管理
guint flags;
guint source_id;
GSList *poll_fds;
GSource *prev; //检索,添加,删除
GSource *next;
char *name;
GSourcePrivate *priv;
};
常规事件处理
#include<glib.h>
GMainLoop* loop;
gint counter = 10;
gboolean callback(gpointer arg)
{
if(--counter == 0){
g_main_loop_quit(loop);
return FALSE;
}
return TRUE;
}
int main(int argc, char* argv[])
{
if(g_thread_supported() == 0)
g_thread_init(NULL);
loop = g_main_loop_new(NULL, FALSE);
g_timeout_add(100, callback, NULL); //增加一个定时器,100毫秒运行一次callback
g_main_loop_run(loop);
g_main_loop_unref(loop);
return 0;
}
unref:
g_main_loop_unref (GMainLoop *loop)
{
g_return_if_fail (loop != NULL);
g_return_if_fail (g_atomic_int_get (&loop->ref_count) > 0);
if (!g_atomic_int_dec_and_test (&loop->ref_count))
return;
g_main_context_unref (loop->context);
g_free (loop);
}
自定义事件处理
#include <glib.h>
#include <stdio.h>
#include <strings.h>
GMainLoop* loop;
//当stdin有数据可读时被GSource调用的回调函数
gboolean callback(GIOChannel *channel)
{
gchar* str;
gsize len;
//从stdin读取一行字符串
g_io_channel_read_line(channel, &str, &len, NULL, NULL);
//去掉回车键()
while(len > 0 && (str[len-1] == '/r' || str[len-1] == '/n'))
str[--len]='/0';
//反转字符串
for(;len;len--)
g_print("%c",str[len-1]);
g_print("/n");
//判断结束符
if(strcasecmp(str, "q") == 0){
g_main_loop_quit(loop);
}
g_free(str);
}
void add_source(GMainContext *context)
{
GIOChannel* channel;
GSource* source;
//这里我们监视stdin是否可读, stdin的fd默认等于1
channel = g_io_channel_unix_new(1);
//g_io_create_watch创建一个默认的io监视作用的GSource,参数G_IO_IN表示监视stdin的读取状态
source = g_io_create_watch(channel, G_IO_IN);
g_io_channel_unref(channel);
//设置stdin可读的时候调用的回调函数
g_source_set_callback(source, (GSourceFunc)callback, channel, NULL);
//把GSource附加到GMainContext
g_source_attach(source, context);
g_source_unref(source);
}
int main(int argc, char* argv[])
{
GMainContext *context;
if(g_thread_supported() == 0)
g_thread_init(NULL);
context = g_main_context_new();
add_source(context);
//把Context赋给GMainLoop
loop = g_main_loop_new(context, FALSE);
g_print("input string('q' to quit)/n");
g_main_loop_run(loop);
g_main_loop_unref(loop);
g_main_context_unref(context);
return 0;
}
Thread(gthread.h/gthread.c)
typedef struct _GThread GThread;
typedef struct _GMutex GMutex;
typedef struct _GRecMutex GRecMutex;
typedef struct _GRWLock GRWLock;
typedef struct _GCond GCond;
官方示例:
#include <glib.h>
void thread_call(gpointer data)
{
gint i = GPOINTER_TO_INT(data);
g_print("%d\n", i);
g_print("Thread %d was created\n", i);
}
int main()
{
gint i;
GThread* thread = NULL;
GError* error = NULL;
if (!g_thread_supported())
{
g_thread_init(NULL);
}
for (i = 1; i < 10; i++)
{
thread = g_thread_create((GThreadFunc)thread_call,(gpointer)i, FALSE, &error);
g_usleep(500);
if (thread == NULL)
{
g_critical("Create thread error: %s\n",error->message);
g_error_free(error);
}
}
return 0;
}
ThreadPool(gthreadpool.h/gthreadpool.c)
频繁创建和销毁执行过程相似而数据不同的线程,系统的效率和资源的利用率将会受到很大的影响—>threadpool
#include <glib.h>
#include <glib/gprintf.h>
#include <unistd.h>
#include <sys/syscall.h>
//线程执行函数,进入后打印数据,并睡眠5秒
void thread_execute (gpointer data, gpointer user_data)
{
g_printf("thread_execute %ld in\n", syscall(__NR_gettid));
g_printf("thread_execute %ld data is : %d\n", syscall(__NR_gettid), *((gint *)data));
g_usleep (5000000);
g_printf("thread_execute %ld out\n", syscall(__NR_gettid));
}
gint data[10];
int main(int argc, char **argv)
{
g_printf ("main in\n");
gint count;
GError *gerr = NULL;
GThreadPool *gpool = NULL;
//最大同时执行2个线程,由于exclusive设置为FALSE,所以不会有错误发生,error被设置为NULL
gpool = g_thread_pool_new (thread_execute, NULL, 2, FALSE, NULL);
//线程池中最大允许线程数3个
if(!g_thread_pool_set_max_threads (gpool, 3, &gerr))
g_printf("g_thread_pool_set_max_threads is error: %s\n", gerr->message);
/**
* 实际线程池中最多有3个线程同时运行
*/
for (count = 0; count < (sizeof(data)/sizeof(gint)); count++)
{
data[count] = count;
g_thread_pool_push(gpool, (gpointer)(&(data[count])), &gerr);
if(gerr != NULL)
{
g_printf("g_thread_pool_push is error: %s\n", gerr->message);
}
}
g_usleep (100000);
//插入优先级高的新任务
if(g_thread_pool_move_to_front (gpool, (gpointer)(&(data[9]))))
g_printf("g_thread_pool_move_to_front is 9\n");
//立即释放,不继续执行任务队列
//g_thread_pool_free (gpool, TRUE, TRUE);
//等待任务队列中的任务全部执行完成
g_thread_pool_free (gpool, FALSE, TRUE);
g_printf ("main out\n");
return 0;
}
AsyncQueue(gasyncqueue.h/gasyncqueue.c)
#include <glib.h>
static gpointer test_aqueue_push_func(gpointer data)
{
GAsyncQueue *aqueue = NULL;
g_print("%s \n", __FUNCTION__);
g_print("delay 2 seconds to push data \n");
g_usleep(2*1000*1000);
aqueue = (GAsyncQueue *)data;
g_async_queue_push(aqueue, GINT_TO_POINTER(99));
return NULL;
}
static gpointer test_aqueue_pop_func(gpointer data)
{
gpointer val = NULL;
GAsyncQueue *aqueue = NULL;
g_print("%s \n", __FUNCTION__);
aqueue = (GAsyncQueue *)data;
val = g_async_queue_pop(aqueue);
g_print("val: %d \n", GPOINTER_TO_INT(val));
return NULL;
}
gint main(gint argc, gchar **argv)
{
GThread *thd_push, *thd_pop;
GAsyncQueue *aqueue = NULL;
aqueue = g_async_queue_new();
thd_push = g_thread_new("thd_push", test_aqueue_push_func, aqueue);
thd_pop = g_thread_new("thd_pop", test_aqueue_pop_func, aqueue);
g_thread_join(thd_push);
g_thread_join(thd_pop);
g_async_queue_unref(aqueue);
return 0;
}
线性表
- 顺序表GArray(garray.h/garray.c)
- 单链表GSList(gslist.h/gslist.c)
- 双链表GList(glist.h/glist.c)
词法分析
- XML语言解析GMarkupParser(gmarkup.h/gmarkup.c)
- ini文件解析GKeyFile(gkeyfile.h/gkeyfile.c)
命令行参数解析GOption(goption.h/goption.c)
#include <glib.h>
#include <locale.h>
static gint repeats = 2;
static gint max_size = 8;
static gboolean verbose = FALSE;
static gboolean beep = FALSE;
static GOptionEntry entries[] =
{
// longname shortname flag arg arg_data description arg_description
{"repeats" , 'r' , 0, G_OPTION_ARG_INT, &repeats, "Average over N repetitions" , "N" },
{"max-size" , 'm' , 0, G_OPTION_ARG_INT, &max_size, "Test up to 2^M items" , "M" },
{"verbose" , 'v' , 0, G_OPTION_ARG_NONE, &verbose, "Be verbose" , NULL},
{"beep" , 'b' , 0, G_OPTION_ARG_NONE, &beep, "Beep when done" , NULL},
{NULL}
};
int main (int argc, char *argv[])
{
GError *error = NULL;
GOptionContext *context = NULL;
GOptionGroup *group = NULL;
// 创建一个新的选项上下文
context = g_option_context_new("- test tree model performance" );
g_option_context_add_main_entries(context, entries, NULL);
//添加要在选项列表之前的--help输出中显示的字符串。 这通常是程序功能的摘要
g_option_context_set_summary(context, "This is a glib demo" );
// 解析命令行参数,识别已添加到上下文的选项
if (!g_option_context_parse(context, &argc, &argv, &error))
{
exit (1);
}
// 释放被解析的参数
g_option_context_free(context);
g_print("Now value is: repeats=%d, max_size=%d, verbose=%d, beep=%d\n",
repeats, max_size, verbose, beep);
return 0;
}
执行情况:
fuqiang@fuqiang-virtual:~/workspace/test_gstreamer$ ./glib_test_option -h
Usage:
glib_test_option [OPTION?] - test tree model performance
This is a glib demo
Help Options:
-h, --help Show help options
Application Options:
-r, --repeats=N Average over N repetitions
-m, --max-size=M Test up to 2^M items
-v, --verbose Be verbose
-b, --beep Beep when done
fuqiang@fuqiang-virtual:~/workspace/test_gstreamer$ ./glib_test_option
Now value is: repeats=2, max_size=8, verbose=0, beep=0
fuqiang@fuqiang-virtual:~/workspace/test_gstreamer$ ./glib_test_option -r 1000
Now value is: repeats=1000, max_size=8, verbose=0, beep=0
Log机制(gmessages.h/gmessages.c)
跨平台,统一linux和win32:
#ifdef G_OS_UNIX
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include <process.h> /* For getpid() */
#include <io.h>
#include <windows.h>
#endif
log等级划分:
#define g_error(...) G_STMT_START { \
g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_ERROR, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__); \
for (;;) ; \
} G_STMT_END
#define g_message(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__)
#define g_critical(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__)
#define g_warning(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__)
#define g_info(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__)
#define g_debug(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, \
__FILE__, G_STRINGIFY (__LINE__), \
G_STRFUNC, __VA_ARGS__)
使用:
g_error ("%s: failed to allocate memory", G_STRLOC);
内存分配
- gmem.h/gmem.c
- gslice.h/gslice.c
- 推荐使用gslice
gslice:
- 分层设计
- 用于优化许多大小相等的块的分配,线程内存空闲列表,快速满足已知结构的内存分配
- 对齐约束而未使用的内存用于缓存着色(块地址的随机分布),以提高CPU缓存利用率
gchar *mem[10000];
gint i;
// Allocate 10000 blocks.
for (i = 0; i < 10000; i++)
{
mem[i] = g_slice_alloc (50);
// Fill in the memory with some junk.
for (j = 0; j < 50; j++)
mem[i][j] = i * j;
}
// Now free all of the blocks.
for (i = 0; i < 10000; i++)
g_slice_free1 (50, mem[i]);
正则表达式GRegex(gregex.h/gregex.c)
#include <glib.h>
int main (int argc, char** argv)
{
GRegex* regex;
GMatchInfo *match_info;
GError *error = NULL;
const gchar *str = "11aa222bb33333cccc44444dddddddd";
const gchar *pat = "[0-9]+";
regex = g_regex_new(pat, 0, 0, &error);
g_regex_match(regex, str, 0, &match_info);
while (g_match_info_matches(match_info)) {
gchar* word = g_match_info_fetch(match_info, 0);
g_print("%s \n",word);
g_free(word);
g_match_info_next(match_info, NULL);
}
g_match_info_free(match_info);
g_regex_unref(regex);
return 0;
}
GObject(面向对象实现)
封装&继承:
G_DEFINE_TYPE:
G_DEFINE_TYPE
G_DEFINE_TYPE_EXTENDED
_G_DEFINE_TYPE_EXTENDED_BEGIN
_G_DEFINE_TYPE_EXTENDED_BEGIN_PRE
_G_DEFINE_TYPE_EXTENDED_BEGIN_REGISTER
_G_DEFINE_TYPE_EXTENDED_END
// G_DEFINE_TYPE
#define G_DEFINE_TYPE(TN, t_n, T_P) G_DEFINE_TYPE_EXTENDED (TN, t_n, T_P, 0, {})
#define G_DEFINE_TYPE_EXTENDED(TN, t_n, T_P, _f_, _C_) _G_DEFINE_TYPE_EXTENDED_BEGIN (TN, t_n, T_P, _f_) {_C_;} _G_DEFINE_TYPE_EXTENDED_END()
//_G_DEFINE_TYPE_EXTENDED_BEGIN
#define _G_DEFINE_TYPE_EXTENDED_BEGIN(TypeName, type_name, TYPE_PARENT, flags) \
_G_DEFINE_TYPE_EXTENDED_BEGIN_PRE(TypeName, type_name, TYPE_PARENT) \
_G_DEFINE_TYPE_EXTENDED_BEGIN_REGISTER(TypeName, type_name, TYPE_PARENT, flags) \
//_G_DEFINE_TYPE_EXTENDED_END
#define _G_DEFINE_TYPE_EXTENDED_END() \
/* following custom code */ \
} \
g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); \
} \
return g_define_type_id__volatile; \
} /* closes type_name##_get_type() */
// _G_DEFINE_TYPE_EXTENDED_BEGIN_PRE
#define _G_DEFINE_TYPE_EXTENDED_BEGIN_PRE(TypeName, type_name, TYPE_PARENT) \
\
static void type_name##_init (TypeName *self); \
static void type_name##_class_init (TypeName##Class *klass); \
static gpointer type_name##_parent_class = NULL; \
static gint TypeName##_private_offset; \
\
_G_DEFINE_TYPE_EXTENDED_CLASS_INIT(TypeName, type_name) \
\
G_GNUC_UNUSED \
static inline gpointer \
type_name##_get_instance_private (TypeName *self) \
{ \
return (G_STRUCT_MEMBER_P (self, TypeName##_private_offset)); \
} \
\
GType \
type_name##_get_type (void) \
{ \
static volatile gsize g_define_type_id__volatile = 0;
/* Prelude goes here */
// _G_DEFINE_TYPE_EXTENDED_BEGIN_REGISTER
#define _G_DEFINE_TYPE_EXTENDED_BEGIN_REGISTER(TypeName, type_name, TYPE_PARENT, flags) \
if (g_once_init_enter (&g_define_type_id__volatile)) \
{ \
GType g_define_type_id = \
g_type_register_static_simple (TYPE_PARENT, \
g_intern_static_string (#TypeName), \
sizeof (TypeName##Class), \
(GClassInitFunc)(void (*)(void)) type_name##_class_intern_init, \
sizeof (TypeName), \
(GInstanceInitFunc)(void (*)(void)) type_name##_init, \
(GTypeFlags) flags); \
{ /* custom code follows */
将下面代入:
- TypeName:MyObject
- type_name:my_object
- TYPE_PARENT:G_TYPE_OBJECT
static void my_object_init (MyObject *self);
static void my_object_class_init (MyObjectClass *klass);
static gpointer my_object_parent_class = NULL;
static gint MyObject_private_offset;
static void my_object_class_intern_init (gpointer klass)
{
my_object_parent_class = g_type_class_peek_parent (klass); //获取父类
if (MyObject_private_offset != 0)
g_type_class_adjust_private_offset (klass, &MyObject_private_offset);
my_object_class_init ((MyObjectClass*) klass); //子类初始化
}
G_GNUC_UNUSED
static inline gpointer
my_object_get_instance_private (TypeName *self)
{
return (G_STRUCT_MEMBER_P (self, MyObject_private_offset));
}
GType
my_object_get_type (void)
{
static volatile gsize g_define_type_id__volatile = 0;
if (g_once_init_enter (&g_define_type_id__volatile))
{
GType g_define_type_id = g_type_register_static_simple (G_TYPE_OBJECT,
g_intern_static_string ("MyObject"),
sizeof (MyObjectClass),
(GClassInitFunc)(void (*)(void)) my_object_class_intern_init,
sizeof (TypeName),
(GInstanceInitFunc)(void (*)(void)) my_object_init,
(GTypeFlags) flags);
g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
}
return g_define_type_id__volatile;
}
TypeNode:
/* --- structures --- */
struct _TypeNode
{
guint ref_count; /* (atomic) */
GTypePlugin *plugin;
guint n_children; /* writable with lock */
guint n_supers : 8;
guint n_prerequisites : 9;
guint is_classed : 1;
guint is_instantiatable : 1;
guint mutatable_check_cache : 1; /* combines some common path checks */
GType *children; /* writable with lock */
TypeData *data;
GQuark qname;
GData *global_gdata;
union {
GAtomicArray iface_entries; /* for !iface types */
GAtomicArray offsets;
} _prot;
GType *prerequisites;
GType supers[1]; /* flexible array */
};
gpointer
g_type_class_peek_parent (gpointer g_class)
{
TypeNode *node;
gpointer class = NULL;
g_return_val_if_fail (g_class != NULL, NULL);
node = lookup_type_node_I (G_TYPE_FROM_CLASS (g_class));
g_return_val_if_fail (node != NULL, NULL);
/* We used to acquire a read lock here. That is not necessary, since
* parent->data->class.class is constant as long as the derived class
* exists.
*/
if (node->is_classed && node->data && NODE_PARENT_TYPE (node))
{
node = lookup_type_node_I (NODE_PARENT_TYPE (node)); //#define NODE_PARENT_TYPE(node) (node->supers[1])
class = node->data->class.class;
}
else if (NODE_PARENT_TYPE (node))
g_warning (G_STRLOC ": invalid class pointer '%p'", g_class);
return class;
}
register:
GType
g_type_register_static (GType parent_type,
const gchar *type_name,
const GTypeInfo *info,
GTypeFlags flags)
{
TypeNode *pnode, *node;
GType type = 0;
g_assert_type_system_initialized ();
g_return_val_if_fail (parent_type > 0, 0);
g_return_val_if_fail (type_name != NULL, 0);
g_return_val_if_fail (info != NULL, 0);
if (!check_type_name_I (type_name) ||
!check_derivation_I (parent_type, type_name))
return 0;
if (info->class_finalize)
{
g_warning ("class finalizer specified for static type '%s'",
type_name);
return 0;
}
pnode = lookup_type_node_I (parent_type);
G_WRITE_LOCK (&type_rw_lock);
type_data_ref_Wm (pnode);
if (check_type_info_I (pnode, NODE_FUNDAMENTAL_TYPE (pnode), type_name, info))
{
node = type_node_new_W (pnode, type_name, NULL);
type_add_flags_W (node, flags);
type = NODE_TYPE (node);
type_data_make_W (node, info,
check_value_table_I (type_name, info->value_table) ? info->value_table : NULL);
}
G_WRITE_UNLOCK (&type_rw_lock);
return type;
}
static TypeNode*
type_node_any_new_W (TypeNode *pnode, //父节点
GType ftype,
const gchar *name, //要创建的子节点名称
GTypePlugin *plugin,
GTypeFundamentalFlags type_flags)
{
guint n_supers;
GType type;
TypeNode *node;
guint i, node_size = 0;
n_supers = pnode ? pnode->n_supers + 1 : 0; //父节点树
if (!pnode)
node_size += SIZEOF_FUNDAMENTAL_INFO; /* fundamental type info */
node_size += SIZEOF_BASE_TYPE_NODE (); /* TypeNode structure */
node_size += (sizeof (GType) * (1 + n_supers + 1)); /* self + ancestors + (0) for ->supers[] */
node = g_malloc0 (node_size);
if (!pnode) /* offset fundamental types */
{
node = G_STRUCT_MEMBER_P (node, SIZEOF_FUNDAMENTAL_INFO);
static_fundamental_type_nodes[ftype >> G_TYPE_FUNDAMENTAL_SHIFT] = node;
type = ftype;
}
else
type = (GType) node;
g_assert ((type & TYPE_ID_MASK) == 0);
node->n_supers = n_supers;
if (!pnode)
{
node->supers[0] = type;
node->supers[1] = 0;
node->is_classed = (type_flags & G_TYPE_FLAG_CLASSED) != 0;
node->is_instantiatable = (type_flags & G_TYPE_FLAG_INSTANTIATABLE) != 0;
if (NODE_IS_IFACE (node))
{
IFACE_NODE_N_PREREQUISITES (node) = 0;
IFACE_NODE_PREREQUISITES (node) = NULL;
}
else
_g_atomic_array_init (CLASSED_NODE_IFACES_ENTRIES (node));
}
else
{
node->supers[0] = type;
memcpy (node->supers + 1, pnode->supers, sizeof (GType) * (1 + pnode->n_supers + 1));
node->is_classed = pnode->is_classed;
node->is_instantiatable = pnode->is_instantiatable;
if (NODE_IS_IFACE (node))
{
IFACE_NODE_N_PREREQUISITES (node) = 0;
IFACE_NODE_PREREQUISITES (node) = NULL;
}
else
{
guint j;
IFaceEntries *entries;
entries = _g_atomic_array_copy (CLASSED_NODE_IFACES_ENTRIES (pnode),
IFACE_ENTRIES_HEADER_SIZE,
0);
if (entries)
{
for (j = 0; j < IFACE_ENTRIES_N_ENTRIES (entries); j++)
{
entries->entry[j].vtable = NULL;
entries->entry[j].init_state = UNINITIALIZED;
}
_g_atomic_array_update (CLASSED_NODE_IFACES_ENTRIES (node),
entries);
}
}
i = pnode->n_children++;
pnode->children = g_renew (GType, pnode->children, pnode->n_children);
pnode->children[i] = type;
}
TRACE(GOBJECT_TYPE_NEW(name, node->supers[1], type));
node->plugin = plugin;
node->n_children = 0;
node->children = NULL;
node->data = NULL;
node->qname = g_quark_from_string (name);
node->global_gdata = NULL;
g_hash_table_insert (static_type_nodes_ht,
(gpointer) g_quark_to_string (node->qname),
(gpointer) type);
g_atomic_int_inc ((gint *)&type_registration_serial);
return node;
}
目前为止,MyObject作为GObject的子类,已经完成了注册。
下面,看看当new一个instance的时候,进行那些操作
gpointer
g_object_new (GType object_type,
const gchar *first_property_name,
...)
{
GObject *object;
va_list var_args;
/* short circuit for calls supplying no properties */
if (!first_property_name)
return g_object_new_with_properties (object_type, 0, NULL, NULL);
va_start (var_args, first_property_name);
object = g_object_new_valist (object_type, first_property_name, var_args);
va_end (var_args);
return object;
}
GObject *
g_object_new_with_properties (GType object_type,
guint n_properties,
const char *names[],
const GValue values[])
{
GObjectClass *class, *unref_class = NULL;
GObject *object;
g_return_val_if_fail (G_TYPE_IS_OBJECT (object_type), NULL);
/* Try to avoid thrashing the ref_count if we don't need to (since
* it's a locked operation).
*/
class = g_type_class_peek_static (object_type);
if (class == NULL)
class = unref_class = g_type_class_ref (object_type);
if (n_properties > 0)
{
guint i, count = 0;
GObjectConstructParam *params;
params = g_newa (GObjectConstructParam, n_properties);
for (i = 0; i < n_properties; i++)
{
GParamSpec *pspec = find_pspec (class, names[i]);
if (!g_object_new_is_valid_property (object_type, pspec, names[i], params, count))
continue;
params[count].pspec = pspec;
params[count].value = (GValue *) &values[i];
count++;
}
object = g_object_new_internal (class, params, count);
}
else
object = g_object_new_internal (class, NULL, 0);
if (unref_class != NULL)
g_type_class_unref (unref_class);
return object;
}
static gpointer
g_object_new_internal (GObjectClass *class,
GObjectConstructParam *params,
guint n_params)
{
GObjectNotifyQueue *nqueue = NULL;
GObject *object;
guint i;
if G_UNLIKELY (CLASS_HAS_CUSTOM_CONSTRUCTOR (class))
return g_object_new_with_custom_constructor (class, params, n_params);
object = (GObject *) g_type_create_instance (class->g_type_class.g_type);
g_assert (g_object_is_aligned (object));
...
}
GTypeInstance*
g_type_create_instance (GType type)
{
TypeNode *node;
GTypeInstance *instance;
GTypeClass *class;
gchar *allocated;
gint private_size;
gint ivar_size;
guint i;
node = lookup_type_node_I (type);
if (G_UNLIKELY (!node || !node->is_instantiatable))
{
g_error ("cannot create new instance of invalid (non-instantiatable) type '%s'",
type_descriptive_name_I (type));
}
/* G_TYPE_IS_ABSTRACT() is an external call: _U */
if (G_UNLIKELY (!node->mutatable_check_cache && G_TYPE_IS_ABSTRACT (type)))
{
g_error ("cannot create instance of abstract (non-instantiatable) type '%s'",
type_descriptive_name_I (type));
}
class = g_type_class_ref (type);
private_size = node->data->instance.private_size;
ivar_size = node->data->instance.instance_size;
#ifdef ENABLE_VALGRIND
if (G_UNLIKELY (private_size && RUNNING_ON_VALGRIND))
{
private_size += ALIGN_STRUCT (1);
/* Allocate one extra pointer size... */
allocated = g_slice_alloc0 (private_size + ivar_size + sizeof (gpointer));
/* ... and point it back to the start of the private data. */
*(gpointer *) (allocated + private_size + ivar_size) = allocated + ALIGN_STRUCT (1);
/* Tell valgrind that it should treat the object itself as such */
VALGRIND_MALLOCLIKE_BLOCK (allocated + private_size, ivar_size + sizeof (gpointer), 0, TRUE);
VALGRIND_MALLOCLIKE_BLOCK (allocated + ALIGN_STRUCT (1), private_size - ALIGN_STRUCT (1), 0, TRUE);
}
else
#endif
allocated = g_slice_alloc0 (private_size + ivar_size);
instance = (GTypeInstance *) (allocated + private_size);
for (i = node->n_supers; i > 0; i--)
{
TypeNode *pnode;
pnode = lookup_type_node_I (node->supers[i]);
if (pnode->data->instance.instance_init)
{
instance->g_class = pnode->data->instance.class;
pnode->data->instance.instance_init (instance, class);
}
}
instance->g_class = class;
if (node->data->instance.instance_init)
node->data->instance.instance_init (instance, class);
#ifdef G_ENABLE_DEBUG
IF_DEBUG (INSTANCE_COUNT)
{
g_atomic_int_inc ((int *) &node->instance_count);
}
#endif
TRACE(GOBJECT_OBJECT_NEW(instance, type));
return instance;
}
看看GObject中有什么?
...
#define G_OBJECT_CLASS_TYPE(class) (G_TYPE_FROM_CLASS (class))
#define G_OBJECT_CLASS_NAME(class) (g_type_name (G_OBJECT_CLASS_TYPE (class)))
...
typedef struct _GObject GObject;
typedef struct _GObjectClass GObjectClass;
typedef struct _GObject GInitiallyUnowned;
typedef struct _GObjectClass GInitiallyUnownedClass;
...
struct _GObject
{
GTypeInstance g_type_instance;
/*< private >*/
guint ref_count; /* (atomic) */ //引用计数,管理生命周期
GData *qdata; //处理property,signal
};
struct _GObjectClass
{
GTypeClass g_type_class;
/*< private >*/
GSList *construct_properties;
/*< public >*/
/* seldom overridden */
GObject* (*constructor) (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
/* overridable methods */
void (*set_property) (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
void (*get_property) (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
void (*dispose) (GObject *object);
void (*finalize) (GObject *object);
/* seldom overridden */
void (*dispatch_properties_changed) (GObject *object,
guint n_pspecs,
GParamSpec **pspecs);
/* signals */
void (*notify) (GObject *object,
GParamSpec *pspec);
/* called when done constructing */
void (*constructed) (GObject *object);
/*< private >*/
gsize flags;
gsize n_construct_properties;
gpointer pspecs;
gsize n_pspecs;
/* padding */
gpointer pdummy[3];
};
...
constructer:
#include <stdio.h>
void __attribute__((constructor)) begin(void);
void __attribute__((destructor)) end(void);
int main (void)
{
printf ("\nInside main ()");
}
void begin (void)
{
printf ("\nIn begin ()");
}
void end (void)
{
printf ("\nIn end ()\n");
}
注册:
gtype.c
...
G_DEFINE_CONSTRUCTOR (gobject_init_ctor)
static void
gobject_init_ctor (void)
{
gobject_init ();
}
...
static void
gobject_init (void)
{
...
_g_object_type_init ();
...
gobject.c:
void
_g_object_type_init (void)
{
static gboolean initialized = FALSE;
static const GTypeFundamentalInfo finfo = {
G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE | G_TYPE_FLAG_DERIVABLE | G_TYPE_FLAG_DEEP_DERIVABLE,
};
GTypeInfo info = {
sizeof (GObjectClass),
(GBaseInitFunc) g_object_base_class_init,
(GBaseFinalizeFunc) g_object_base_class_finalize,
(GClassInitFunc) g_object_do_class_init,
NULL /* class_destroy */,
NULL /* class_data */,
sizeof (GObject),
0 /* n_preallocs */,
(GInstanceInitFunc) g_object_init,
NULL, /* value_table */
};
static const GTypeValueTable value_table = {
g_value_object_init, /* value_init */
g_value_object_free_value, /* value_free */
g_value_object_copy_value, /* value_copy */
g_value_object_peek_pointer, /* value_peek_pointer */
"p", /* collect_format */
g_value_object_collect_value, /* collect_value */
"p", /* lcopy_format */
g_value_object_lcopy_value, /* lcopy_value */
};
GType type G_GNUC_UNUSED /* when compiling with G_DISABLE_ASSERT */;
g_return_if_fail (initialized == FALSE);
initialized = TRUE;
/* G_TYPE_OBJECT
*/
info.value_table = &value_table;
type = g_type_register_fundamental (G_TYPE_OBJECT, g_intern_static_string ("GObject"), &info, &finfo, 0);
g_assert (type == G_TYPE_OBJECT);
g_value_register_transform_func (G_TYPE_OBJECT, G_TYPE_OBJECT, g_value_object_transform_value);
...
}
GType
g_type_register_fundamental (GType type_id,
const gchar *type_name,
const GTypeInfo *info,
const GTypeFundamentalInfo *finfo,
GTypeFlags flags)
{
TypeNode *node;
g_assert_type_system_initialized ();
g_return_val_if_fail (type_id > 0, 0);
g_return_val_if_fail (type_name != NULL, 0);
g_return_val_if_fail (info != NULL, 0);
g_return_val_if_fail (finfo != NULL, 0);
if (!check_type_name_I (type_name))
return 0;
if ((type_id & TYPE_ID_MASK) ||
type_id > G_TYPE_FUNDAMENTAL_MAX)
{
g_warning ("attempt to register fundamental type '%s' with invalid type id (%" G_GSIZE_FORMAT ")",
type_name,
type_id);
return 0;
}
if ((finfo->type_flags & G_TYPE_FLAG_INSTANTIATABLE) &&
!(finfo->type_flags & G_TYPE_FLAG_CLASSED))
{
g_warning ("cannot register instantiatable fundamental type '%s' as non-classed",
type_name);
return 0;
}
if (lookup_type_node_I (type_id))
{
g_warning ("cannot register existing fundamental type '%s' (as '%s')",
type_descriptive_name_I (type_id),
type_name);
return 0;
}
G_WRITE_LOCK (&type_rw_lock);
node = type_node_fundamental_new_W (type_id, type_name, finfo->type_flags);
type_add_flags_W (node, flags);
if (check_type_info_I (NULL, NODE_FUNDAMENTAL_TYPE (node), type_name, info))
type_data_make_W (node, info,
check_value_table_I (type_name, info->value_table) ? info->value_table : NULL);
G_WRITE_UNLOCK (&type_rw_lock);
return NODE_TYPE (node);
}
数据隐藏(private):
第一种:
typedef struct _PMDListNode PMDListNode;
struct _PMDListNode {
/* private */
PMDListNode *prev;
PMDListNode *next;
/* public */
void *data;
};
C语言认为,程序员应当知道自己正在干什么,而且保证自己的所作所为是正确的
第二种:
typedef struct _PMDListNode PMDListNode; //保留在h文件
struct _PMDListNode { //下面全部转到c文件中
PMDListNode *prev;
PMDListNode *next;
void *data;
};
GIO
Interface:
GFile | reference to a file |
GFileInfo | information about a file or filesystem |
GFileEnumerator | list files in directories |
GDrive | represents a drive |
GVolume | represents a file system in an abstract way |
GMount | represents a mounted file system |
Stream:
GInputStream | read data |
GOutputstream | write date |
GIOStream | read and write data |
GSocket | lowlevel platform independent socket object |
GSocketClient | high-level network client helper |
GSocketServer | high-level network server helper |
Gstreamer重要概念
Pipeline
- 通过代码搭建
- 通过命令行搭建
gst-launch-1.0 filesrc location='/home/fuqiang/Videos/1080P.mov’ ! qtdemux name=demux \
demux.audio_0 ! decodebin ! audioconvert ! audioresample ! autoaudiosink \
demux.video_0 ! decodebin ! videoconvert ! videoscale ! autovideosink
Element
- 基本组成单元,一切都是element,source/filter/sink/bin/pipeline
- bin/pipeline也是element,循环嵌套
element的创建:
source = gst_element_factory_make ("videotestsrc", "source");
sink = gst_element_factory_make ("autovideosink", "sink");
Bin
- 一组element链接成的逻辑element
- pipeline也是bin
GstElement *bin, *pipeline, *source, *sink;
/* init */
gst_init (&argc, &argv);
/* create */
pipeline = gst_pipeline_new ("my_pipeline");
bin = gst_bin_new ("my_bin");
source = gst_element_factory_make ("fakesrc", "source");
sink = gst_element_factory_make ("fakesink", "sink");
/* First add the elements to the bin */
gst_bin_add_many (GST_BIN (bin), source, sink, NULL);
/* add the bin to the pipeline */
gst_bin_add (GST_BIN (pipeline), bin);
/* link the elements */
gst_element_link (source, sink);
Pad&GhostPad
- element的接口
- src&sink,可以存在多个src和多个sink
- capbilities system
分类:
- Always:element创建的时候就存在,整个生命周期都存在
- Sometimes:element创建的时候不存在,后期根据具体情况创建
fuqiang@fuqiang-virtual:~/workspace/glib/glib$ gst-inspect-1.0 qtdemux
...
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstQTDemux
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
video/quicktime
video/mj2
audio/x-m4a
application/x-3gp
SRC template: 'audio_%u'
Availability: Sometimes
Capabilities:
ANY
SRC template: 'subtitle_%u'
Availability: Sometimes
Capabilities:
ANY
SRC template: 'video_%u'
Availability: Sometimes
Capabilities:
ANY
- Request:element创建的时候不存在,程序员创建
fuqiang@fuqiang-virtual:~/workspace/glib/glib$ gst-inspect-1.0 tee
...
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstTee
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
ANY
SRC template: 'src_%u'
Availability: On request
Capabilities:
ANY
- Ghost:bin使用
/* init */
gst_init (&argc, &argv);
/* create element, add to bin */
sink = gst_element_factory_make ("fakesrc", "sink");
bin = gst_bin_new ("mybin");
gst_bin_add (GST_BIN (bin), sink);
/* add ghostpad */
pad = gst_element_get_static_pad (sink, "sink");
gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
gst_object_unref (GST_OBJECT (pad));
数据传输方式:
- PUSH:gst_pad_push_data() —–> chain()
- PULL:比较少用,一般都是demux element会使用pull模式,方便处理数据,gst_pad_pull_range() —–> getrange()
static GstFlowReturn
gst_pad_push_data (GstPad * pad, GstPadProbeType type, void *data)
{
GstPad *peer;
...
/* do block probes */
PROBE_HANDLE (pad, type | GST_PAD_PROBE_TYPE_BLOCK, data, probe_stopped,
probe_handled);
...
PROBE_HANDLE (pad, type, data, probe_stopped, probe_handled);
...
if (G_UNLIKELY ((peer = GST_PAD_PEER (pad)) == NULL))
goto not_linked;
...
ret = gst_pad_chain_data_unchecked (peer, type, data);
...
}
static inline GstFlowReturn
gst_pad_chain_data_unchecked (GstPad * pad, GstPadProbeType type, void *data)
{
GstObject *parent;
...
PROBE_HANDLE (pad, type | GST_PAD_PROBE_TYPE_BLOCK, data, probe_stopped,
probe_handled);
PROBE_HANDLE (pad, type, data, probe_stopped, probe_handled);
ACQUIRE_PARENT (pad, parent, no_parent);
...
GstPadChainFunction chainfunc;
...
ret = chainfunc (pad, parent, GST_BUFFER_CAST (data));
...
}
void
gst_pad_set_chain_function_full (GstPad * pad, GstPadChainFunction chain,
gpointer user_data, GDestroyNotify notify)
{
g_return_if_fail (GST_IS_PAD (pad));
g_return_if_fail (GST_PAD_IS_SINK (pad));
if (pad->chainnotify)
pad->chainnotify (pad->chaindata);
GST_PAD_CHAINFUNC (pad) = chain;
pad->chaindata = user_data;
pad->chainnotify = notify;
}
#define GST_PAD_CHAINFUNC(pad) (GST_PAD_CAST(pad)->chainfunc)
Caps
示例
代码实现(openh264):
...
/* pad templates */
static GstStaticPadTemplate gst_openh264dec_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS
("video/x-h264, stream-format=(string)byte-stream, alignment=(string)au, "
SUPPORTED_PROFILE_STR
));
static GstStaticPadTemplate gst_openh264dec_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")));
/* class initialization */
G_DEFINE_TYPE_WITH_CODE (GstOpenh264Dec, gst_openh264dec,
GST_TYPE_VIDEO_DECODER,
GST_DEBUG_CATEGORY_INIT (gst_openh264dec_debug_category, "openh264dec", 0,
"debug category for openh264dec element"));
GST_ELEMENT_REGISTER_DEFINE_CUSTOM (openh264dec, openh264dec_element_init);
static void
gst_openh264dec_class_init (GstOpenh264DecClass * klass)
{
GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass);
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
&gst_openh264dec_sink_template);
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
&gst_openh264dec_src_template);
...
}
实际效果:
fuqiang@fuqiang-virtual:~/workspace/gstreamer/subprojects/gst-plugins-bad/ext/openh264$ gst-inspect-1.0 openh264dec
...
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstVideoDecoder
+----GstOpenh264Dec
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
video/x-h264
stream-format: byte-stream
alignment: au
profile: { (string)constrained-baseline, (string)baseline, (string)main, (string)high, (string)constrained-high, (string)progressive-high }
SRC template: 'src'
Availability: Always
Capabilities:
video/x-raw
format: I420
width: [ 1, 2147483647 ]
height: [ 1, 2147483647 ]
framerate: [ 0/1, 2147483647/1 ]
caps的协商
Fixed negotiation
static void
gst_wavenc_init (GstWavEnc * wavenc)
{
wavenc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_pad_set_chain_function (wavenc->sinkpad,
GST_DEBUG_FUNCPTR (gst_wavenc_chain));
gst_pad_set_event_function (wavenc->sinkpad,
GST_DEBUG_FUNCPTR (gst_wavenc_event));
gst_pad_use_fixed_caps (wavenc->sinkpad);
gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->sinkpad);
wavenc->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
gst_pad_use_fixed_caps (wavenc->srcpad);
gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad);
}
...
static GstFlowReturn
gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
...
if (G_UNLIKELY (!wavenc->sent_header)) {
GstStructure *s;
GstCaps *caps = gst_pad_get_allowed_caps (wavenc->srcpad);
...
gst_pad_set_caps (wavenc->srcpad, caps);
gst_caps_unref (caps);
...
}
...
flow = gst_pad_push (wavenc->srcpad, buf);
return flow;
}
static inline gboolean
gst_pad_set_caps (GstPad * pad, GstCaps * caps)
{
GstEvent *event;
gboolean res = TRUE;
g_return_val_if_fail (GST_IS_PAD (pad), FALSE);
g_return_val_if_fail (caps != NULL && gst_caps_is_fixed (caps), FALSE);
event = gst_event_new_caps (caps);
if (GST_PAD_IS_SRC (pad))
res = gst_pad_push_event (pad, event);
else
res = gst_pad_send_event (pad, event);
return res;
}
- 解码器
- 类型探测器,typefinder等
- 一些产生固定格式的源,filesrc等
- …
Transform negotiation
static gboolean
gst_wavenc_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
gboolean res = TRUE;
GstWavEnc *wavenc;
GstTagList *tags;
GstToc *toc;
wavenc = GST_WAVENC (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
gst_wavenc_sink_setcaps (pad, caps);
/* have our own src caps */
gst_event_unref (event);
break;
}
...
static gboolean
gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps)
{
GstWavEnc *wavenc;
GstStructure *structure;
const gchar *name;
gint chans, rate;
GstCaps *ccaps;
wavenc = GST_WAVENC (gst_pad_get_parent (pad));
ccaps = gst_pad_get_current_caps (pad);
if (wavenc->sent_header && ccaps && !gst_caps_can_intersect (caps, ccaps)) {
gst_caps_unref (ccaps);
GST_WARNING_OBJECT (wavenc, "cannot change format in middle of stream");
goto fail;
}
if (ccaps)
gst_caps_unref (ccaps);
structure = gst_caps_get_structure (caps, 0);
name = gst_structure_get_name (structure);
if (!gst_structure_get_int (structure, "channels", &chans) ||
!gst_structure_get_int (structure, "rate", &rate)) {
GST_WARNING_OBJECT (wavenc, "caps incomplete");
goto fail;
}
wavenc->channels = chans;
wavenc->rate = rate;
wavenc->channel_mask = 0;
if (strcmp (name, "audio/x-raw") == 0) {
GstAudioInfo info;
guint64 gstmask;
if (!gst_audio_info_from_caps (&info, caps)) {
GST_WARNING_OBJECT (wavenc, "Could not retrieve audio info from caps");
goto fail;
}
if (gst_audio_channel_positions_to_mask (info.position, wavenc->channels,
FALSE, &gstmask)) {
wavenc->channel_mask = gstmask_to_wavmask (gstmask, wavenc->destPos);
memcpy (wavenc->srcPos, info.position, sizeof (info.position));
GST_DEBUG_OBJECT (wavenc, "Channel mask input: 0x%" G_GINT64_MODIFIER "x"
" output: 0x%" G_GINT64_MODIFIER "x", gstmask, wavenc->channel_mask);
}
wavenc->audio_format = GST_AUDIO_INFO_FORMAT (&info);
if (GST_AUDIO_INFO_IS_INTEGER (&info))
wavenc->format = GST_RIFF_WAVE_FORMAT_PCM;
else if (GST_AUDIO_INFO_IS_FLOAT (&info))
wavenc->format = GST_RIFF_WAVE_FORMAT_IEEE_FLOAT;
else
goto fail;
wavenc->width = GST_AUDIO_INFO_WIDTH (&info);
} else if (strcmp (name, "audio/x-alaw") == 0) {
wavenc->format = GST_RIFF_WAVE_FORMAT_ALAW;
wavenc->width = 8;
} else if (strcmp (name, "audio/x-mulaw") == 0) {
wavenc->format = GST_RIFF_WAVE_FORMAT_MULAW;
wavenc->width = 8;
} else {
GST_WARNING_OBJECT (wavenc, "Unsupported format %s", name);
goto fail;
}
gst_object_unref (wavenc);
return TRUE;
fail:
gst_object_unref (wavenc);
return FALSE;
}
Dynamic negotiation
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstBaseTransform
+----GstVideoFilter
+----GstVideoConvert
static gboolean
gst_base_transform_sink_eventfunc (GstBaseTransform * trans, GstEvent * event)
{
gboolean ret = TRUE, forward = TRUE;
GstBaseTransformPrivate *priv = trans->priv;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
/* clear any pending reconfigure flag */
gst_pad_check_reconfigure (trans->srcpad);
ret = gst_base_transform_setcaps (trans, trans->sinkpad, caps);
if (!ret)
gst_pad_mark_reconfigure (trans->srcpad);
forward = FALSE;
break;
}
...
static gboolean
gst_base_transform_setcaps (GstBaseTransform * trans, GstPad * pad,
GstCaps * incaps)
{
GstBaseTransformPrivate *priv = trans->priv;
GstCaps *outcaps, *prev_incaps = NULL, *prev_outcaps = NULL;
/* find best possible caps for the other pad */
outcaps = gst_base_transform_find_transform (trans, pad, incaps); //获取一个合适的outcaps
if (!outcaps || gst_caps_is_empty (outcaps))
goto no_transform_possible;
/* configure the element now */
/* if we have the same caps, we can optimize and reuse the input caps */
if (gst_caps_is_equal (incaps, outcaps)) {
GST_INFO_OBJECT (trans, "reuse caps");
gst_caps_unref (outcaps);
outcaps = gst_caps_ref (incaps);
}
prev_incaps = gst_pad_get_current_caps (trans->sinkpad);
prev_outcaps = gst_pad_get_current_caps (trans->srcpad);
if (prev_incaps && prev_outcaps && gst_caps_is_equal (prev_incaps, incaps)
&& gst_caps_is_equal (prev_outcaps, outcaps)) {
GST_DEBUG_OBJECT (trans,
"New caps equal to old ones: %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT,
incaps, outcaps);
ret = TRUE;
} else {
/* call configure now */
if (!(ret = gst_base_transform_configure_caps (trans, incaps, outcaps)))
goto failed_configure;
if (!prev_outcaps || !gst_caps_is_equal (outcaps, prev_outcaps))
/* let downstream know about our caps */
ret = gst_pad_set_caps (trans->srcpad, outcaps);
}
if (ret) {
/* try to get a pool when needed */
ret = gst_base_transform_do_bufferpool (trans, outcaps);
}
...
static GstCaps *
gst_base_transform_find_transform (GstBaseTransform * trans, GstPad * pad,
GstCaps * caps)
{
GstBaseTransformClass *klass;
GstPad *otherpad, *otherpeer;
GstCaps *othercaps;
gboolean is_fixed;
klass = GST_BASE_TRANSFORM_GET_CLASS (trans);
otherpad = (pad == trans->srcpad) ? trans->sinkpad : trans->srcpad; //获取同element的另一个pad
otherpeer = gst_pad_get_peer (otherpad); //获取同element另一个pad的peer
/* see how we can transform the input caps. We need to do this even for
* passthrough because it might be possible that this element cannot support
* passthrough at all. */
othercaps = gst_base_transform_transform_caps (trans,
GST_PAD_DIRECTION (pad), caps, NULL); //获取同element的另一个pad的caps
/* The caps we can actually output is the intersection of the transformed
* caps with the pad template for the pad */
if (othercaps && !gst_caps_is_empty (othercaps)) {
GstCaps *intersect, *templ_caps;
templ_caps = gst_pad_get_pad_template_caps (otherpad); //获取同element的另一个pad的模板caps
GST_DEBUG_OBJECT (trans,
"intersecting against padtemplate %" GST_PTR_FORMAT, templ_caps);
intersect =
gst_caps_intersect_full (othercaps, templ_caps,
GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (othercaps);
gst_caps_unref (templ_caps);
othercaps = intersect;
}
/* check if transform is empty */
if (!othercaps || gst_caps_is_empty (othercaps))
goto no_transform;
/* if the othercaps are not fixed, we need to fixate them, first attempt
* is by attempting passthrough if the othercaps are a superset of caps. */
/* FIXME. maybe the caps is not fixed because it has multiple structures of
* fixed caps */
is_fixed = gst_caps_is_fixed (othercaps);
if (!is_fixed) {
GST_DEBUG_OBJECT (trans,
"transform returned non fixed %" GST_PTR_FORMAT, othercaps);
/* Now let's see what the peer suggests based on our transformed caps */
if (otherpeer) {
GstCaps *peercaps, *intersection, *templ_caps;
GST_DEBUG_OBJECT (trans,
"Checking peer caps with filter %" GST_PTR_FORMAT, othercaps);
peercaps = gst_pad_query_caps (otherpeer, othercaps);
GST_DEBUG_OBJECT (trans, "Resulted in %" GST_PTR_FORMAT, peercaps);
if (!gst_caps_is_empty (peercaps)) {
templ_caps = gst_pad_get_pad_template_caps (otherpad);
GST_DEBUG_OBJECT (trans,
"Intersecting with template caps %" GST_PTR_FORMAT, templ_caps);
intersection =
gst_caps_intersect_full (peercaps, templ_caps,
GST_CAPS_INTERSECT_FIRST);
GST_DEBUG_OBJECT (trans, "Intersection: %" GST_PTR_FORMAT,
intersection);
gst_caps_unref (peercaps);
gst_caps_unref (templ_caps);
peercaps = intersection;
GST_DEBUG_OBJECT (trans,
"Intersecting with transformed caps %" GST_PTR_FORMAT, othercaps);
intersection =
gst_caps_intersect_full (peercaps, othercaps,
GST_CAPS_INTERSECT_FIRST);
GST_DEBUG_OBJECT (trans, "Intersection: %" GST_PTR_FORMAT,
intersection);
gst_caps_unref (peercaps);
gst_caps_unref (othercaps);
othercaps = intersection;
} else {
gst_caps_unref (othercaps);
othercaps = peercaps;
}
is_fixed = gst_caps_is_fixed (othercaps);
} else {
GST_DEBUG_OBJECT (trans, "no peer, doing passthrough");
gst_caps_unref (othercaps);
othercaps = gst_caps_ref (caps);
is_fixed = TRUE;
}
}
if (gst_caps_is_empty (othercaps))
goto no_transform_possible;
GST_DEBUG ("have %sfixed caps %" GST_PTR_FORMAT, (is_fixed ? "" : "non-"),
othercaps);
/* second attempt at fixation, call the fixate vmethod */
/* caps could be fixed but the subclass may want to add fields */
if (klass->fixate_caps) {
othercaps =
klass->fixate_caps (trans, GST_PAD_DIRECTION (pad), caps, othercaps);
is_fixed = othercaps && gst_caps_is_fixed (othercaps);
GST_DEBUG_OBJECT (trans, "after fixating %" GST_PTR_FORMAT, othercaps);
}
return othercaps;
}
Plugins
element管理单元,一个插件(Plugin)包含1个或者多个element
fuqiang@fuqiang-virtual:~/workspace/glib/glib$ gst-inspect-1.0 video4linux2
Plugin Details:
Name video4linux2
Description elements for Video 4 Linux
Filename /usr/local/lib/x86_64-linux-gnu/gstreamer-1.0/libgstvideo4linux2.so
Version 1.20.3.1
License LGPL
Source module gst-plugins-good
Binary package GStreamer Good Plug-ins git
Origin URL Unknown package origin
v4l2deviceprovider: Video (video4linux2) Device Provider
v4l2radio: Radio (video4linux2) Tuner
v4l2sink: Video (video4linux2) Sink
v4l2src: Video (video4linux2) Source
4 features:
+-- 3 elements
+-- 1 device providers
Multithreading
- 自动管理机制,自动创建和销毁,比如软件解码器(创建多线程解码)
- 手动明确创建,queue
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_RECONFIGURE:
GST_QUEUE_MUTEX_LOCK (queue);
if (queue->srcresult == GST_FLOW_NOT_LINKED) {
/* when we got not linked, assume downstream is linked again now and we
* can try to start pushing again */
queue->srcresult = GST_FLOW_OK;
gst_pad_start_task (pad, (GstTaskFunction) gst_queue_loop, pad, NULL);
}
GST_QUEUE_MUTEX_UNLOCK (queue);
res = gst_pad_push_event (queue->sinkpad, event);
break;
default:
res = gst_pad_event_default (pad, parent, event);
break;
}
...
static void
gst_queue_loop (GstPad * pad)
{
GstQueue *queue;
GstFlowReturn ret;
queue = (GstQueue *) GST_PAD_PARENT (pad);
/* have to lock for thread-safety */
GST_QUEUE_MUTEX_LOCK_CHECK (queue, out_flushing);
while (gst_queue_is_empty (queue)) {
GST_CAT_DEBUG_OBJECT (queue_dataflow, queue, "queue is empty");
if (!queue->silent) {
GST_QUEUE_MUTEX_UNLOCK (queue);
g_signal_emit (queue, gst_queue_signals[SIGNAL_UNDERRUN], 0);
GST_QUEUE_MUTEX_LOCK_CHECK (queue, out_flushing);
}
/* we recheck, the signal could have changed the thresholds */
while (gst_queue_is_empty (queue)) {
GST_QUEUE_WAIT_ADD_CHECK (queue, out_flushing);
}
GST_CAT_DEBUG_OBJECT (queue_dataflow, queue, "queue is not empty");
if (!queue->silent) {
GST_QUEUE_MUTEX_UNLOCK (queue);
g_signal_emit (queue, gst_queue_signals[SIGNAL_RUNNING], 0);
g_signal_emit (queue, gst_queue_signals[SIGNAL_PUSHING], 0);
GST_QUEUE_MUTEX_LOCK_CHECK (queue, out_flushing);
}
}
ret = gst_queue_push_one (queue);
queue->srcresult = ret;
if (ret != GST_FLOW_OK)
goto out_flushing;
GST_QUEUE_MUTEX_UNLOCK (queue);
return;
/* ERRORS */
out_flushing:
...
}
通信机制
BUS(Message)
- pipeline主动报告运行状态,上行
- Message队列
- 应用程序获取感兴趣的Message并执行操作
- GST提供,异步
2种方式进行通信:
- 通过GMainLoop
#include <gst/gst.h>
static GMainLoop *loop;
static gboolean
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
{
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:{
GError *err;
gchar *debug;
gst_message_parse_error (message, &err, &debug);
g_print ("Error: %s\n", err->message);
g_error_free (err);
g_free (debug);
g_main_loop_quit (loop);
break;
}
default:
/* unhandled message */
break;
}
/* we want to be notified again the next time there is a message
* on the bus, so returning TRUE (FALSE means we want to stop watching
* for messages on the bus and our callback should not be called again)
*/
return TRUE;
}
gint
main (gint argc, gchar * argv[])
{
GstElement *pipeline;
GstBus *bus;
guint bus_watch_id;
/* init */
gst_init (&argc, &argv);
/* create pipeline, add handler */
pipeline = gst_pipeline_new ("my_pipeline");
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
bus_watch_id = gst_bus_add_watch (bus, my_bus_callback, NULL);
gst_object_unref (bus);
loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (loop);
/* clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
g_source_remove (bus_watch_id);
g_main_loop_unref (loop);
return 0;
}
- gst_bus_add_watch () 将消息处理程序附加到pipeline的消息总线
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);
Event
pipeline的插件之间进行通信的机制,或者应用程序向某个插件发送
- pipeline的element之间通信,上行,下行,双向
- 与缓冲区数据并行,由pad接收
- STREAM_LOCK
event列表如下(subprojects/gstreamer/gst/gstevent.h):
typedef enum {
GST_EVENT_UNKNOWN = GST_EVENT_MAKE_TYPE (0, 0),
/* bidirectional events */
GST_EVENT_FLUSH_START = GST_EVENT_MAKE_TYPE (10, _FLAG(BOTH)),
GST_EVENT_FLUSH_STOP = GST_EVENT_MAKE_TYPE (20, _FLAG(BOTH) | _FLAG(SERIALIZED)),
/* downstream serialized events */
GST_EVENT_STREAM_START = GST_EVENT_MAKE_TYPE (40, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_CAPS = GST_EVENT_MAKE_TYPE (50, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_SEGMENT = GST_EVENT_MAKE_TYPE (70, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_STREAM_COLLECTION = GST_EVENT_MAKE_TYPE (75, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY) | _FLAG(STICKY_MULTI)),
GST_EVENT_TAG = GST_EVENT_MAKE_TYPE (80, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY) | _FLAG(STICKY_MULTI)),
GST_EVENT_BUFFERSIZE = GST_EVENT_MAKE_TYPE (90, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_SINK_MESSAGE = GST_EVENT_MAKE_TYPE (100, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY) | _FLAG(STICKY_MULTI)),
GST_EVENT_STREAM_GROUP_DONE = GST_EVENT_MAKE_TYPE (105, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_EOS = GST_EVENT_MAKE_TYPE (110, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY)),
GST_EVENT_TOC = GST_EVENT_MAKE_TYPE (120, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY) | _FLAG(STICKY_MULTI)),
GST_EVENT_PROTECTION = GST_EVENT_MAKE_TYPE (130, _FLAG (DOWNSTREAM) | _FLAG (SERIALIZED) | _FLAG (STICKY) | _FLAG (STICKY_MULTI)),
/* non-sticky downstream serialized */
GST_EVENT_SEGMENT_DONE = GST_EVENT_MAKE_TYPE (150, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED)),
GST_EVENT_GAP = GST_EVENT_MAKE_TYPE (160, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED)),
/* sticky downstream non-serialized */
GST_EVENT_INSTANT_RATE_CHANGE = GST_EVENT_MAKE_TYPE (180, _FLAG(DOWNSTREAM) | _FLAG(STICKY)),
/* upstream events */
GST_EVENT_QOS = GST_EVENT_MAKE_TYPE (190, _FLAG(UPSTREAM)),
GST_EVENT_SEEK = GST_EVENT_MAKE_TYPE (200, _FLAG(UPSTREAM)),
GST_EVENT_NAVIGATION = GST_EVENT_MAKE_TYPE (210, _FLAG(UPSTREAM)),
GST_EVENT_LATENCY = GST_EVENT_MAKE_TYPE (220, _FLAG(UPSTREAM)),
GST_EVENT_STEP = GST_EVENT_MAKE_TYPE (230, _FLAG(UPSTREAM)),
GST_EVENT_RECONFIGURE = GST_EVENT_MAKE_TYPE (240, _FLAG(UPSTREAM)),
GST_EVENT_TOC_SELECT = GST_EVENT_MAKE_TYPE (250, _FLAG(UPSTREAM)),
GST_EVENT_SELECT_STREAMS = GST_EVENT_MAKE_TYPE (260, _FLAG(UPSTREAM)),
GST_EVENT_INSTANT_RATE_SYNC_TIME = GST_EVENT_MAKE_TYPE (261, _FLAG(UPSTREAM)),
/* custom events start here */
GST_EVENT_CUSTOM_UPSTREAM = GST_EVENT_MAKE_TYPE (270, _FLAG(UPSTREAM)),
GST_EVENT_CUSTOM_DOWNSTREAM = GST_EVENT_MAKE_TYPE (280, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED)),
GST_EVENT_CUSTOM_DOWNSTREAM_OOB = GST_EVENT_MAKE_TYPE (290, _FLAG(DOWNSTREAM)),
GST_EVENT_CUSTOM_DOWNSTREAM_STICKY = GST_EVENT_MAKE_TYPE (300, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED) | _FLAG(STICKY) | _FLAG(STICKY_MULTI)),
GST_EVENT_CUSTOM_BOTH = GST_EVENT_MAKE_TYPE (310, _FLAG(BOTH) | _FLAG(SERIALIZED)),
GST_EVENT_CUSTOM_BOTH_OOB = GST_EVENT_MAKE_TYPE (320, _FLAG(BOTH))
} GstEventType;
发送event():
static gboolean
gst_qtdemux_handle_sink_event (GstPad * sinkpad, GstObject * parent,
GstEvent * event)
{
GstQTDemux *demux = GST_QTDEMUX (parent);
gboolean res = TRUE;
GST_LOG_OBJECT (demux, "handling %s event", GST_EVENT_TYPE_NAME (event));
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_EOS:
/* If we are in push mode, and get an EOS before we've seen any streams,
* then error out - we have nowhere to send the EOS */
if (!demux->pullbased) {
gint i;
gboolean has_valid_stream = FALSE;
for (i = 0; i < QTDEMUX_N_STREAMS (demux); i++) {
if (QTDEMUX_NTH_STREAM (demux, i)->pad != NULL) {
has_valid_stream = TRUE;
break;
}
}
if (!has_valid_stream)
gst_qtdemux_post_no_playable_stream_error (demux);
else {
GST_DEBUG_OBJECT (demux, "Data still available after EOS: %u",
(guint) gst_adapter_available (demux->adapter));
if (gst_qtdemux_process_adapter (demux, TRUE) != GST_FLOW_OK) {
res = FALSE;
}
}
}
break;
...
static void
gst_qtdemux_push_event (GstQTDemux * qtdemux, GstEvent * event)
{
gboolean has_valid_stream = FALSE;
GstEventType etype = GST_EVENT_TYPE (event);
guint i;
GST_DEBUG_OBJECT (qtdemux, "pushing %s event on all source pads",
GST_EVENT_TYPE_NAME (event));
for (i = 0; i < QTDEMUX_N_STREAMS (qtdemux); i++) {
GstPad *pad;
QtDemuxStream *stream = QTDEMUX_NTH_STREAM (qtdemux, i);
GST_DEBUG_OBJECT (qtdemux, "pushing on track-id %u", stream->track_id);
if ((pad = stream->pad)) {
has_valid_stream = TRUE;
if (etype == GST_EVENT_EOS) {
/* let's not send twice */
if (stream->sent_eos)
continue;
stream->sent_eos = TRUE;
}
gst_pad_push_event (pad, gst_event_ref (event));
}
}
gst_event_unref (event);
/* if it is EOS and there are no pads, post an error */
if (!has_valid_stream && etype == GST_EVENT_EOS) {
gst_qtdemux_post_no_playable_stream_error (qtdemux);
}
}
Signal
- 应用程序控制插件运行状态
- Gobject提供,同步
/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
...
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
...
}
Probe
应用程序探测pad中的数据:
GstElement *sink = gst_element_factory_make ("ximagesink", "videosink");
//date probe
GstPad *pad = gst_element_get_static_pad (sink, "sink");
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
(GstPadProbeCallback) cb_have_data, NULL, NULL);
gst_object_unref (pad);
Query
Query列表:
ypedef enum {
GST_QUERY_UNKNOWN = GST_QUERY_MAKE_TYPE (0, 0),
GST_QUERY_POSITION = GST_QUERY_MAKE_TYPE (10, _FLAG(BOTH)),
GST_QUERY_DURATION = GST_QUERY_MAKE_TYPE (20, _FLAG(BOTH)),
GST_QUERY_LATENCY = GST_QUERY_MAKE_TYPE (30, _FLAG(BOTH)),
GST_QUERY_JITTER = GST_QUERY_MAKE_TYPE (40, _FLAG(BOTH)),
GST_QUERY_RATE = GST_QUERY_MAKE_TYPE (50, _FLAG(BOTH)),
GST_QUERY_SEEKING = GST_QUERY_MAKE_TYPE (60, _FLAG(BOTH)),
GST_QUERY_SEGMENT = GST_QUERY_MAKE_TYPE (70, _FLAG(BOTH)),
GST_QUERY_CONVERT = GST_QUERY_MAKE_TYPE (80, _FLAG(BOTH)),
GST_QUERY_FORMATS = GST_QUERY_MAKE_TYPE (90, _FLAG(BOTH)),
GST_QUERY_BUFFERING = GST_QUERY_MAKE_TYPE (110, _FLAG(BOTH)),
GST_QUERY_CUSTOM = GST_QUERY_MAKE_TYPE (120, _FLAG(BOTH)),
GST_QUERY_URI = GST_QUERY_MAKE_TYPE (130, _FLAG(BOTH)),
GST_QUERY_ALLOCATION = GST_QUERY_MAKE_TYPE (140, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED)),
GST_QUERY_SCHEDULING = GST_QUERY_MAKE_TYPE (150, _FLAG(UPSTREAM)),
GST_QUERY_ACCEPT_CAPS = GST_QUERY_MAKE_TYPE (160, _FLAG(BOTH)),
GST_QUERY_CAPS = GST_QUERY_MAKE_TYPE (170, _FLAG(BOTH)),
GST_QUERY_DRAIN = GST_QUERY_MAKE_TYPE (180, _FLAG(DOWNSTREAM) | _FLAG(SERIALIZED)),
GST_QUERY_CONTEXT = GST_QUERY_MAKE_TYPE (190, _FLAG(BOTH)),
GST_QUERY_BITRATE = GST_QUERY_MAKE_TYPE (200, _FLAG(DOWNSTREAM)),
} GstQueryType;
应用程序查询pipeline的运行状态:
GstQuery *query;
gint64 start, end;
query = gst_query_new_seeking (GST_FORMAT_TIME);
if (gst_element_query (data->playbin, query)) {
gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
if (data->seek_enabled) {
g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
GST_TIME_ARGS (start), GST_TIME_ARGS (end));
} else {
g_print ("Seeking is DISABLED for this stream.\n");
}
}
else {
g_printerr ("Seeking query failed.");
}
gst_query_unref (query);
}
响应SEEKING的Query:
static gboolean
gst_avi_demux_handle_src_query (GstPad * pad, GstObject * parent,
GstQuery * query)
{
...
case GST_QUERY_SEEKING:{
GstFormat fmt;
gst_query_parse_seeking (query, &fmt, NULL, NULL, NULL);
if (fmt == GST_FORMAT_TIME) {
gboolean seekable = TRUE;
if (avi->streaming) {
seekable = avi->seekable;
}
gst_query_set_seeking (query, GST_FORMAT_TIME, seekable,
0, stream->duration);
res = TRUE;
}
break;
}
...
状态机
typedef enum {
GST_STATE_VOID_PENDING = 0,
GST_STATE_NULL = 1,
GST_STATE_READY = 2,
GST_STATE_PAUSED = 3,
GST_STATE_PLAYING = 4
} GstState;
状态转换:
typedef enum /*< flags=0 >*/
{
GST_STATE_CHANGE_NULL_TO_READY = (GST_STATE_NULL<<3) | GST_STATE_READY,
GST_STATE_CHANGE_READY_TO_PAUSED = (GST_STATE_READY<<3) | GST_STATE_PAUSED,
GST_STATE_CHANGE_PAUSED_TO_PLAYING = (GST_STATE_PAUSED<<3) | GST_STATE_PLAYING,
GST_STATE_CHANGE_PLAYING_TO_PAUSED = (GST_STATE_PLAYING<<3) | GST_STATE_PAUSED,
GST_STATE_CHANGE_PAUSED_TO_READY = (GST_STATE_PAUSED<<3) | GST_STATE_READY,
GST_STATE_CHANGE_READY_TO_NULL = (GST_STATE_READY<<3) | GST_STATE_NULL
} GstStateChange;
应用实现
HelloWorld
#include <gst/gst.h>
int
main (int argc, char *argv[])
{
GstElement *pipeline;
GstBus *bus;
GstMessage *msg;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Build the pipeline */
pipeline =
gst_parse_launch
("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
NULL);
/* Start playing */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* See next tutorial for proper error message handling/parsing */
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
g_error ("An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
"variable set for more details.");
}
/* Free resources */
gst_message_unref (msg);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
gst_init(gst.c):只做了解析参数的动作
-----------------------------------------gst.c----------------------------------------------------------
gst_init (int *argc, char **argv[])
{
GError *err = NULL;
if (!gst_init_check (argc, argv, &err)) {
g_print ("Could not initialize GStreamer: %s\n",
err ? err->message : "unknown error occurred");
if (err) {
g_error_free (err);
}
exit (1);
}
}
gboolean
gst_init_check (int *argc, char **argv[], GError ** error)
{
#ifndef GST_DISABLE_OPTION_PARSING
GOptionGroup *group;
GOptionContext *ctx;
#endif
gboolean res;
g_mutex_lock (&init_lock);
if (gst_initialized) {
GST_DEBUG ("already initialized gst");
g_mutex_unlock (&init_lock);
return TRUE;
}
#ifndef GST_DISABLE_OPTION_PARSING
ctx = g_option_context_new ("- GStreamer initialization");
g_option_context_set_ignore_unknown_options (ctx, TRUE);
g_option_context_set_help_enabled (ctx, FALSE);
group = gst_init_get_option_group ();
g_option_context_add_group (ctx, group);
res = g_option_context_parse (ctx, argc, argv, error);
g_option_context_free (ctx);
#else
init_pre (NULL, NULL, NULL, NULL);
init_post (NULL, NULL, NULL, NULL);
res = TRUE;
#endif
gst_initialized = res;
g_mutex_unlock (&init_lock);
return res;
}
gst_parse_launch(gstparse.c):
-----------------------------------------gstparse.c----------------------------------------------------------
gstElement *
gst_parse_launch (const gchar * pipeline_description, GError ** error)
{
return gst_parse_launch_full (pipeline_description, NULL, GST_PARSE_FLAG_NONE, error);
}
GstElement *
gst_parse_launch_full (const gchar * pipeline_description,
GstParseContext * context, GstParseFlags flags, GError ** error)
{
#ifndef GST_DISABLE_PARSE
GstElement *element;
GError *myerror = NULL;
g_return_val_if_fail (pipeline_description != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
GST_CAT_INFO (GST_CAT_PIPELINE, "parsing pipeline description '%s'",
pipeline_description);
element = priv_gst_parse_launch (pipeline_description, &myerror, context,
flags);
/* don't return partially constructed pipeline if FATAL_ERRORS was given */
if (G_UNLIKELY (myerror != NULL && element != NULL)) {
if ((flags & GST_PARSE_FLAG_FATAL_ERRORS)) {
gst_object_unref (element);
element = NULL;
}
}
if (myerror)
g_propagate_error (error, myerror);
return element;
#else
gchar *msg;
GST_WARNING ("Disabled API called");
msg = gst_error_get_message (GST_CORE_ERROR, GST_CORE_ERROR_DISABLED);
g_set_error (error, GST_CORE_ERROR, GST_CORE_ERROR_DISABLED, "%s", msg);
g_free (msg);
return NULL;
#endif
}
-----------------------------------------grammer.tab.c------------------------------------------------------
GstElement *
priv_gst_parse_launch (const gchar *str, GError **error, GstParseContext *ctx,
GstParseFlags flags)
{
graph_t g;
gchar *dstr;
GSList *walk;
GstElement *ret;
yyscan_t scanner;
g_return_val_if_fail (str != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g.chain = NULL;
g.links = NULL;
g.error = error;
g.ctx = ctx;
g.flags = flags;
dstr = g_strdup (str);
priv_gst_parse_yylex_init (&scanner);
priv_gst_parse_yy_scan_string (dstr, scanner);
if (yyparse (scanner, &g) != 0) {
SET_ERROR (error, GST_PARSE_ERROR_SYNTAX,
"Unrecoverable syntax error while parsing pipeline %s", str);
priv_gst_parse_yylex_destroy (scanner);
g_free (dstr);
goto error1;
}
priv_gst_parse_yylex_destroy (scanner);
g_free (dstr);
/* ensure chain is not NULL */
if (!g.chain){
g.chain=gst_parse_chain_new ();
g.chain->elements=NULL;
g.chain->first.element=NULL;
g.chain->first.name=NULL;
g.chain->first.pads=NULL;
g.chain->last.element=NULL;
g.chain->last.name=NULL;
g.chain->last.pads=NULL;
};
/* ensure elements is not empty */
if(!g.chain->elements){
g.chain->elements= g_slist_prepend (NULL, NULL);
};
/* put all elements in our bin if necessary */
if(g.chain->elements->next){
GstBin *bin;
if (flags & GST_PARSE_FLAG_PLACE_IN_BIN)
bin = GST_BIN (gst_element_factory_make ("bin", NULL));
else
bin = GST_BIN (gst_element_factory_make ("pipeline", NULL));
g_assert (bin);
for (walk = g.chain->elements; walk; walk = walk->next) {
if (walk->data != NULL)
gst_bin_add (bin, GST_ELEMENT (walk->data));
}
g_slist_free (g.chain->elements);
g.chain->elements = g_slist_prepend (NULL, bin);
}
ret = (GstElement *) g.chain->elements->data;
g_slist_free (g.chain->elements);
g.chain->elements=NULL;
gst_parse_free_chain (g.chain);
g.chain = NULL;
/* resolve and perform links */
for (walk = g.links; walk; walk = walk->next) {
link_t *l = (link_t *) walk->data;
int err;
err=gst_resolve_reference( &(l->src), ret);
if (err) {
if(-1==err){
SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No src-element named \"%s\" - omitting link", l->src.name);
}else{
/* probably a missing element which we've handled already */
SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No src-element found - omitting link");
}
gst_parse_free_link (l);
continue;
}
err=gst_resolve_reference( &(l->sink), ret);
if (err) {
if(-1==err){
SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No sink-element named \"%s\" - omitting link", l->src.name);
}else{
/* probably a missing element which we've handled already */
SET_ERROR (error, GST_PARSE_ERROR_NO_SUCH_ELEMENT,
"No sink-element found - omitting link");
}
gst_parse_free_link (l);
continue;
}
gst_parse_perform_link (l, &g);
}
g_slist_free (g.links);
out:
#ifdef __GST_PARSE_TRACE
GST_CAT_DEBUG (GST_CAT_PIPELINE,
"TRACE: %u strings, %u chains and %u links left", __strings, __chains,
__links);
if (__strings || __chains || __links) {
g_warning ("TRACE: %u strings, %u chains and %u links left", __strings,
__chains, __links);
}
#endif /* __GST_PARSE_TRACE */
return ret;
error1:
if (g.chain) {
gst_parse_free_chain (g.chain);
g.chain=NULL;
}
g_slist_foreach (g.links, (GFunc)gst_parse_free_link, NULL);
g_slist_free (g.links);
if (error)
g_assert (*error);
ret = NULL;
goto out;
}
自建pipeline
#include <gst/gst.h>
int
main (int argc, char *argv[])
{
GstElement *pipeline, *source, *sink;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Create the elements */
source = gst_element_factory_make ("videotestsrc", "source");
sink = gst_element_factory_make ("autovideosink", "sink");
/* Create the empty pipeline */
pipeline = gst_pipeline_new ("test-pipeline");
if (!pipeline || !source || !sink) {
g_printerr ("Not all elements could be created.\n");
return -1;
}
/* Build the pipeline */
gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
if (gst_element_link (source, sink) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (pipeline);
return -1;
}
/* Modify the source's properties */
g_object_set (source, "pattern", 0, NULL);
/* Start playing */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (pipeline);
return -1;
}
/* Wait until error or EOS */
bus = gst_element_get_bus (pipeline);
msg =
gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Parse message */
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n",
GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
break;
case GST_MESSAGE_EOS:
g_print ("End-Of-Stream reached.\n");
break;
default:
/* We should not reach here because we only asked for ERRORs and EOS */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
/* Free resources */
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
动态pipeline
#include <gst/gst.h>
/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
GstElement *pipeline;
GstElement *source;
GstElement *convert;
GstElement *resample;
GstElement *sink;
} CustomData;
/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
int main(int argc, char *argv[]) {
CustomData data;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
gboolean terminate = FALSE;
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Create the elements */
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.resample = gst_element_factory_make ("audioresample", "resample");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");
/* Create the empty pipeline */
data.pipeline = gst_pipeline_new ("test-pipeline");
if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) {
g_printerr ("Not all elements could be created.\n");
return -1;
}
/* Build the pipeline. Note that we are NOT linking the source at this
* point. We will do it later. */
gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline);
return -1;
}
/* Set the URI to play */
g_object_set (data.source, "uri", "https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
/* Start playing */
ret = gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr ("Unable to set the pipeline to the playing state.\n");
gst_object_unref (data.pipeline);
return -1;
}
/* Listen to the bus */
bus = gst_element_get_bus (data.pipeline);
do {
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Parse message */
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error (msg, &err, &debug_info);
g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error (&err);
g_free (debug_info);
terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print ("End-Of-Stream reached.\n");
terminate = TRUE;
break;
case GST_MESSAGE_STATE_CHANGED:
/* We are only interested in state-changed messages from the pipeline */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
g_print ("Pipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
}
break;
default:
/* We should not reach here */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
} while (!terminate);
/* Free resources */
gst_object_unref (bus);
gst_element_set_state (data.pipeline, GST_STATE_NULL);
gst_object_unref (data.pipeline);
return 0;
}
/* This function will be called by the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
GstPadLinkReturn ret;
GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL;
g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));
/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
g_print ("We are already linked. Ignoring.\n");
goto exit;
}
/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps (new_pad);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
goto exit;
}
/* Attempt the link */
ret = gst_pad_link (new_pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret)) {
g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print ("Link succeeded (type '%s').\n", new_pad_type);
}
exit:
/* Unreference the new pad's caps, if we got them */
if (new_pad_caps != NULL)
gst_caps_unref (new_pad_caps);
/* Unreference the sink pad */
gst_object_unref (sink_pad);
}
插件实现
构建目录
fuqiang@fuqiang-VivoBook:~/workspace/gstreamer/subprojects/gst-plugins-bad/gst/myfilter$ tree
.
├── meson.build
├── myfilter.c
└── myfilter.h
实现meson.build
myfilter_sources = [
'myfilter.c',
]
gstmyfilter = library('gstmyfilter',
myfilter_sources,
c_args : gst_plugins_bad_args,
include_directories : [configinc],
dependencies : [gstbase_dep, gsttag_dep],
install : true,
install_dir : plugins_install_dir,
)
pkgconfig.generate(gstmyfilter, install_dir : plugins_pkgconfig_install_dir)
plugins += [gstmyfilter]
添加编译相关选项
fuqiang@fuqiang-VivoBook:~/workspace/gstreamer/subprojects/gst-plugins-bad$ git diff
diff --git a/subprojects/gst-plugins-bad/gst/meson.build b/subprojects/gst-plugins-bad/gst/meson.build
index 9cf62db983..063fff3aef 100644
--- a/subprojects/gst-plugins-bad/gst/meson.build
+++ b/subprojects/gst-plugins-bad/gst/meson.build
@@ -12,7 +12,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux',
'segmentclip', 'siren', 'smooth', 'speed', 'subenc', 'switchbin',
'timecode', 'transcode', 'videofilters',
'videoframe_audiolevel', 'videoparsers', 'videosignal',
- 'vmnc', 'y4m']
+ 'vmnc', 'y4m', 'myfilter']
if not get_option(plugin).disabled()
subdir(plugin)
endif
diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt
index b347dcb27b..320e4182c1 100644
--- a/subprojects/gst-plugins-bad/meson_options.txt
+++ b/subprojects/gst-plugins-bad/meson_options.txt
@@ -134,6 +134,7 @@ option('onnx', type : 'feature', value : 'auto', description : 'ONNX neural netw
option('openal', type : 'feature', value : 'auto', description : 'OpenAL plugin')
option('openexr', type : 'feature', value : 'auto', description : 'OpenEXR plugin')
option('openh264', type : 'feature', value : 'auto', description : 'H.264 video codec plugin')
+option('myfilter', type : 'feature', value : 'auto', description : 'test filter')
option('openjpeg', type : 'feature', value : 'auto', description : 'JPEG2000 image codec plugin')
option('openmpt', type : 'feature', value : 'auto', description : 'OpenMPT module music library plugin')
option('openni2', type : 'feature', value : 'auto', description : 'OpenNI2 library plugin')
实现头文件
#ifndef __GST_MYFILTER_H__
#define __GST_MYFILTER_H__
#include <gst/gst.h>
G_BEGIN_DECLS
#define GST_TYPE_MYFILTER (gst_my_filter_get_type())
G_DECLARE_FINAL_TYPE (GstMyFilter, gst_my_filter,
GST, PLUGIN_TEMPLATE, GstElement)
struct _GstMyFilter
{
GstElement element;
GstPad *sinkpad, *srcpad;
gboolean silent;
};
G_END_DECLS
#endif /* __GST_MYFILTER_H__ */
实现C文件
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gst/gst.h>
#include "gstmyfilter.h"
GST_DEBUG_CATEGORY_STATIC (gst_my_filter_debug);
#define GST_CAT_DEFAULT gst_my_filter_debug
/* Filter signals and args */
enum
{
/* FILL ME */
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_SILENT
};
/* the capabilities of the inputs and outputs.
*
* describe the real formats here.
*/
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("ANY")
);
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("ANY")
);
#define gst_my_filter_parent_class parent_class
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
static void gst_my_filter_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_my_filter_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
static gboolean gst_my_filter_sink_event (GstPad * pad,
GstObject * parent, GstEvent * event);
static GstFlowReturn gst_my_filter_chain (GstPad * pad,
GstObject * parent, GstBuffer * buf);
/* GObject vmethod implementations */
/* initialize the myfilter's class */
static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_my_filter_set_property;
gobject_class->get_property = gst_my_filter_get_property;
g_object_class_install_property (gobject_class, PROP_SILENT,
g_param_spec_boolean ("silent", "Silent", "Produce verbose output ?",
FALSE, G_PARAM_READWRITE));
gst_element_class_set_details_simple (gstelement_class,
"MyFilter",
"FIXME:Generic",
"FIXME:Generic Template Element", "fuqiang <<user@hostname.org>>");
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&src_factory));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&sink_factory));
}
/* initialize the new element
* instantiate pads and add them to element
* set pad calback functions
* initialize instance structure
*/
static void
gst_my_filter_init (GstMyFilter * filter)
{
filter->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
gst_pad_set_event_function (filter->sinkpad,
GST_DEBUG_FUNCPTR (gst_my_filter_sink_event));
gst_pad_set_chain_function (filter->sinkpad,
GST_DEBUG_FUNCPTR (gst_my_filter_chain));
GST_PAD_SET_PROXY_CAPS (filter->sinkpad);
gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
filter->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
GST_PAD_SET_PROXY_CAPS (filter->srcpad);
gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
filter->silent = FALSE;
}
static void
gst_my_filter_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstMyFilter *filter = GST_MYFILTER (object);
switch (prop_id) {
case PROP_SILENT:
filter->silent = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_my_filter_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstMyFilter *filter = GST_MYFILTER (object);
switch (prop_id) {
case PROP_SILENT:
g_value_set_boolean (value, filter->silent);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* GstElement vmethod implementations */
/* this function handles sink events */
static gboolean
gst_my_filter_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstMyFilter *filter;
gboolean ret;
filter = GST_MYFILTER (parent);
GST_LOG_OBJECT (filter, "Received %s event: %" GST_PTR_FORMAT,
GST_EVENT_TYPE_NAME (event), event);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
gst_event_parse_caps (event, &caps);
/* do something with the caps */
/* and forward */
ret = gst_pad_event_default (pad, parent, event);
break;
}
default:
ret = gst_pad_event_default (pad, parent, event);
break;
}
return ret;
}
/* chain function
* this function does the actual processing
*/
static GstFlowReturn
gst_my_filter_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
GstMyFilter *filter;
filter = GST_MYFILTER (parent);
if (filter->silent == FALSE)
g_print ("I'm plugged, therefore I'm in.\n");
/* just push out the incoming buffer without touching it */
return gst_pad_push (filter->srcpad, buf);
}
/* entry point to initialize the plug-in
* initialize the plug-in itself
* register the element factories and other features
*/
static gboolean
myfilter_init (GstPlugin * myfilter)
{
/* debug category for filtering log messages
*
* exchange the string 'Template myfilter' with your description
*/
GST_DEBUG_CATEGORY_INIT (gst_my_filter_debug, "myfilter",
0, "Template myfilter");
return gst_element_register (myfilter, "myfilter", GST_RANK_NONE,
GST_TYPE_MYFILTER);
}
/* PACKAGE: this is usually set by meson depending on some _INIT macro
* in meson.build and then written into and defined in config.h, but we can
* just set it ourselves here in case someone doesn't use meson to
* compile this code. GST_PLUGIN_DEFINE needs PACKAGE to be defined.
*/
#ifndef PACKAGE
#define PACKAGE "myfirstmyfilter"
#endif
/* gstreamer looks for this structure to register myfilters
*
* exchange the string 'Template myfilter' with your myfilter description
*/
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
myfilter,
"Template myfilter",
myfilter_init,
PACKAGE_VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
构建编译安装
meson --reconfigure -Dgst-plugins-bad:myfilter=enabled out
cd out
ninja
sudo ninja install
查看插件情况
fuqiang@fuqiang-VivoBook:~/workspace/gstreamer/subprojects/gst-plugins-bad/gst/myfilter$ gst-inspect-1.0 myfilter
Factory Details:
Rank marginal (64)
Long-name obentul example plugin
Klass Example/FirstExample
Description Shows the basic structure of a plugin
Author fuqiang <<user@hostname.org>>
Plugin Details:
Name myfilter
Description My filter plugin
Filename /usr/local/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmyfilter.so
Version 1.0
License LGPL
Source module myfilter
Binary package GStreamer template Plug-ins
Origin URL http://gstreamer.net/
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstMyFilter
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
ANY
SRC template: 'src'
Availability: Always
Capabilities:
ANY
Element has no clocking capabilities.
Element has no URI handling capabilities.
Pads:
SRC: 'src_%u'
Pad Template: 'src'
SINK: 'sink_%u'
Pad Template: 'sink'
Element Properties:
name : The name of the object
flags: 可读, 可写, 0x2000
String. Default: "myfilter0"
parent : The parent of the object
flags: 可读, 可写, 0x2000
Object of type "GstObject
运行
gst-launch-1.0 videotestsrc ! autovideosink
gst-launch-1.0 videotestsrc ! myfilter ! autovideosink
经典插件代码分析
openh264
openh264是开源的H.264编解码器,Gstreamer中集成了openh264的源码,并且实现了插件对接该编码器
编译
meson --reconfigure -Dgst-plugins-bad:openh264=enabled out
使用
gst-launch-1.0 filesrc location='/home/fuqiang/Videos/1080P.mov' ! qtdemux name=demux demux.video_0 ! queue ! h264parse ! openh264dec ! videoconvert ! videoscale ! autovideosink
openh264源码
openh264源码的代码结构如下:
fuqiang@fuqiang-virtual:~/workspace/gstreamer/subprojects/openh264$ tree -L 1
.
├── autotest
├── build
├── codec
├── code-coverage.sh
├── CODING_STYLE
├── CONTRIBUTORS
├── docs
├── gmpopenh264.info
├── include
├── LICENSE
├── Makefile
├── meson.build
├── meson_options.txt
├── module
├── openh264.def
├── openh264.pc.in
├── openh264.rc
├── openh264.rc.template
├── README.md
├── RELEASES
├── res
├── run_Test.sh
├── subprojects
├── test
├── testbin
└── ut.def
10 directories, 16 files
openh264编译完成后的生成目录:
fuqiang@fuqiang-virtual:~/workspace/gstreamer/out/subprojects/openh264$ tree -L 3
.
├── codec
│ ├── api
│ │ └── svc
│ ├── common
│ │ ├── libcommon.a
│ │ └── libcommon.a.p
│ ├── console
│ │ ├── common
│ │ ├── dec
│ │ └── enc
│ ├── decoder
│ │ ├── libdecoder.a
│ │ └── libdecoder.a.p
│ ├── encoder
│ │ ├── libencoder.a
│ │ └── libencoder.a.p
│ └── processing
│ ├── libprocessing.a
│ └── libprocessing.a.p
├── include
│ └── wels
│ ├── codec_api.h
│ ├── codec_app_def.h
│ ├── codec_def.h
│ └── codec_ver.h
├── libopenh264.a
├── libopenh264.a.p
├── libopenh264.so -> libopenh264.so.6
├── libopenh264.so.2.1.1
├── libopenh264.so.2.1.1.p
│ └── libopenh264.so.2.1.1.symbols
├── libopenh264.so.6 -> libopenh264.so.2.1.1
├── openh264.pc
├── openh264-static.pc
└── test
├── api
│ ├── test_api
│ └── test_api.p
├── common
│ ├── test_common
│ └── test_common.p
├── decoder
│ ├── test_decoder
│ └── test_decoder.p
├── encoder
│ ├── test_encoder
│ └── test_encoder.p
└── processing
├── test_processing
└── test_processing.p
30 directories, 20 files
openh264插件源码
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstVideoDecoder
+----GstOpenh264Dec
openh264插件源码如下:
fuqiang@fuqiang-virtual:~/workspace/gstreamer/subprojects/gst-plugins-bad/ext/openh264$ tree
.
├── gstopenh264dec.cpp
├── gstopenh264dec.h
├── gstopenh264element.c
├── gstopenh264elements.h
├── gstopenh264enc.cpp
├── gstopenh264enc.h
├── gstopenh264plugin.c
└── meson.build
0 directories, 8 files
编译完成后生成目录:
fuqiang@fuqiang-virtual:~/workspace/gstreamer/out/subprojects/gst-plugins-bad/ext/openh264$ tree
.
├── libgstopenh264.so
└── libgstopenh264.so.p
├── gstopenh264dec.cpp.o
├── gstopenh264element.c.o
├── gstopenh264enc.cpp.o
└── gstopenh264plugin.c.o
1 directory, 5 files
注册插件gstopenh264plugin.c:
static gboolean
plugin_init (GstPlugin * plugin)
{
gboolean ret = FALSE;
ret |= GST_ELEMENT_REGISTER (openh264dec, plugin);
ret |= GST_ELEMENT_REGISTER (openh264enc, plugin);
return ret;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
openh264,
"OpenH264 encoder/decoder plugin",
plugin_init, VERSION, "BSD", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
解码头文件:
#include <wels/codec_api.h>
#include <wels/codec_app_def.h>
#include <wels/codec_def.h>
G_BEGIN_DECLS
#define GST_TYPE_OPENH264DEC (gst_openh264dec_get_type())
#define GST_OPENH264DEC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OPENH264DEC,GstOpenh264Dec))
#define GST_OPENH264DEC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OPENH264DEC,GstOpenh264DecClass))
#define GST_IS_OPENH264DEC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OPENH264DEC))
#define GST_IS_OPENH264DEC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OPENH264DEC))
typedef struct _GstOpenh264Dec GstOpenh264Dec;
typedef struct _GstOpenh264DecClass GstOpenh264DecClass;
struct _GstOpenh264Dec
{
GstVideoDecoder base_openh264dec;
/*< private >*/
ISVCDecoder *decoder;
GstVideoCodecState *input_state;
guint width, height;
};
struct _GstOpenh264DecClass
{
GstVideoDecoderClass base_openh264dec_class;
};
GType gst_openh264dec_get_type(void);
G_END_DECLS
源码分析:
...
/* prototypes */
static gboolean gst_openh264dec_start (GstVideoDecoder * decoder);
static gboolean gst_openh264dec_stop (GstVideoDecoder * decoder);
static gboolean gst_openh264dec_set_format (GstVideoDecoder * decoder,
GstVideoCodecState * state);
static gboolean gst_openh264dec_reset (GstVideoDecoder * decoder,
gboolean hard);
static GstFlowReturn gst_openh264dec_finish (GstVideoDecoder * decoder);
static GstFlowReturn gst_openh264dec_handle_frame (GstVideoDecoder * decoder,
GstVideoCodecFrame * frame);
static gboolean gst_openh264dec_decide_allocation (GstVideoDecoder * decoder,
GstQuery * query);
static gboolean openh264dec_element_init (GstPlugin * plugin);
...
/* pad templates */
static GstStaticPadTemplate gst_openh264dec_sink_template
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS
("video/x-h264, stream-format=(string)byte-stream, alignment=(string)au, "
SUPPORTED_PROFILE_STR
));
static GstStaticPadTemplate gst_openh264dec_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")));
/* class initialization */
G_DEFINE_TYPE_WITH_CODE (GstOpenh264Dec, gst_openh264dec,
GST_TYPE_VIDEO_DECODER,
GST_DEBUG_CATEGORY_INIT (gst_openh264dec_debug_category, "openh264dec", 0,
"debug category for openh264dec element"));
GST_ELEMENT_REGISTER_DEFINE_CUSTOM (openh264dec, openh264dec_element_init);
static void
gst_openh264dec_class_init (GstOpenh264DecClass * klass)
{
GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass);
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
&gst_openh264dec_sink_template);
gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass),
&gst_openh264dec_src_template);
gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass),
"OpenH264 video decoder", "Decoder/Video", "OpenH264 video decoder",
"Ericsson AB, http://www.ericsson.com");
video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_openh264dec_start);
video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_openh264dec_stop);
video_decoder_class->set_format =
GST_DEBUG_FUNCPTR (gst_openh264dec_set_format);
video_decoder_class->reset = GST_DEBUG_FUNCPTR (gst_openh264dec_reset);
video_decoder_class->finish = GST_DEBUG_FUNCPTR (gst_openh264dec_finish);
video_decoder_class->handle_frame =
GST_DEBUG_FUNCPTR (gst_openh264dec_handle_frame);
video_decoder_class->decide_allocation =
GST_DEBUG_FUNCPTR (gst_openh264dec_decide_allocation);
}
static void
gst_openh264dec_init (GstOpenh264Dec * openh264dec)
{
openh264dec->decoder = NULL;
gst_video_decoder_set_packetized (GST_VIDEO_DECODER (openh264dec), TRUE);
gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (openh264dec), TRUE);
}
...
static gboolean
gst_openh264dec_start (GstVideoDecoder * decoder)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
gint ret;
SDecodingParam dec_param = { 0 };
if (openh264dec->decoder != NULL) {
openh264dec->decoder->Uninitialize ()
WelsDestroyDecoder (openh264dec->decoder);
openh264dec->decoder = NULL;
}
WelsCreateDecoder (&(openh264dec->decoder));
dec_param.uiTargetDqLayer = 255;
dec_param.eEcActiveIdc = ERROR_CON_FRAME_COPY;
#if OPENH264_MAJOR == 1 && OPENH264_MINOR < 6
dec_param.eOutputColorFormat = videoFormatI420;
#endif
dec_param.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC;
ret = openh264dec->decoder->Initialize (&dec_param);
GST_DEBUG_OBJECT (openh264dec,
"openh264_dec_start called, openh264dec %sinitialized OK!",
(ret != cmResultSuccess) ? "NOT " : "");
return (ret == cmResultSuccess);
}
static gboolean
gst_openh264dec_stop (GstVideoDecoder * decoder)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
if (openh264dec->decoder) {
openh264dec->decoder->Uninitialize ();
WelsDestroyDecoder (openh264dec->decoder);
openh264dec->decoder = NULL;
}
if (openh264dec->input_state) {
gst_video_codec_state_unref (openh264dec->input_state);
openh264dec->input_state = NULL;
}
openh264dec->width = openh264dec->height = 0;
return TRUE;
}
static gboolean
gst_openh264dec_set_format (GstVideoDecoder * decoder,
GstVideoCodecState * state)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
GST_DEBUG_OBJECT (openh264dec, "input caps: %" GST_PTR_FORMAT, state->caps);
if (openh264dec->input_state) {
gst_video_codec_state_unref (openh264dec->input_state);
openh264dec->input_state = NULL;
}
openh264dec->input_state = gst_video_codec_state_ref (state);
return TRUE;
}
static gboolean
gst_openh264dec_reset (GstVideoDecoder * decoder, gboolean hard)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
GST_DEBUG_OBJECT (openh264dec, "reset");
return TRUE;
}
static GstFlowReturn
gst_openh264dec_handle_frame (GstVideoDecoder * decoder,
GstVideoCodecFrame * frame)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
GstMapInfo map_info;
GstVideoCodecState *state;
SBufferInfo dst_buf_info;
DECODING_STATE ret;
guint8 *yuvdata[3];
GstFlowReturn flow_status;
GstVideoFrame video_frame;
guint actual_width, actual_height;
guint i;
guint8 *p;
guint row_stride, component_width, component_height, src_width, row;
if (frame == NULL) {
#if OPENH264_VERSION_CHECK (1,9)
/* Called with no videoframe for EOS logic. Drain out */
int end_of_stream = 1;
memset (&dst_buf_info, 0, sizeof (SBufferInfo));
openh264dec->decoder->SetOption (DECODER_OPTION_END_OF_STREAM,
&end_of_stream);
ret = openh264dec->decoder->FlushFrame (yuvdata, &dst_buf_info);
if (ret != dsErrorFree || dst_buf_info.iBufferStatus != 1) {
GST_DEBUG_OBJECT (decoder, "No more frames to retrieve at EOS");
return GST_FLOW_EOS;
}
#else
return GST_FLOW_EOS;
#endif
} else {
if (!gst_buffer_map (frame->input_buffer, &map_info, GST_MAP_READ)) {
GST_ERROR_OBJECT (openh264dec, "Cannot map input buffer!");
gst_video_codec_frame_unref (frame);
return GST_FLOW_ERROR;
}
GST_LOG_OBJECT (openh264dec, "handle frame, 1st NAL type %d",
map_info.size > 4 ? map_info.data[4] & 0x1f : -1);
memset (&dst_buf_info, 0, sizeof (SBufferInfo));
/* Use the unsigned long long OpenH264 timestamp to store the system_frame_number
* to track the original frame through any OpenH264 reordering */
dst_buf_info.uiInBsTimeStamp = frame->system_frame_number;
GST_LOG_OBJECT (decoder, "Submitting frame with PTS %" GST_TIME_FORMAT
" and frame ref %" G_GUINT64_FORMAT,
GST_TIME_ARGS (frame->pts), (guint64) frame->system_frame_number);
ret =
openh264dec->decoder->DecodeFrameNoDelay (map_info.data, map_info.size,
yuvdata, &dst_buf_info);
gst_buffer_unmap (frame->input_buffer, &map_info);
if (ret != dsErrorFree) {
/* Request a key unit from upstream */
GST_DEBUG_OBJECT (openh264dec, "Requesting a key unit");
gst_video_decoder_request_sync_point (decoder, frame,
(GstVideoDecoderRequestSyncPointFlags) 0);
GST_LOG_OBJECT (openh264dec, "error decoding nal, return code: %d", ret);
gst_video_codec_frame_unref (frame);
/* Get back the frame that was reported as errored */
frame =
gst_video_decoder_get_frame (decoder, dst_buf_info.uiOutYuvTimeStamp);
if (frame) {
GST_LOG_OBJECT (decoder,
"Dropping errored frame ref %" G_GUINT64_FORMAT,
(guint64) dst_buf_info.uiOutYuvTimeStamp);
return gst_video_decoder_drop_frame (decoder, frame);
}
return GST_FLOW_OK;
}
gst_video_codec_frame_unref (frame);
frame = NULL;
/* No output available yet */
if (dst_buf_info.iBufferStatus != 1) {
GST_LOG_OBJECT (decoder, "No buffer decoded yet");
return GST_FLOW_OK;
}
}
GST_LOG_OBJECT (decoder, "Got back frame with frame ref %" G_GUINT64_FORMAT,
(guint64) dst_buf_info.uiOutYuvTimeStamp);
/* OpenH264 lets us pass an int reference through
* so we can retrieve the input frame now */
frame = gst_video_decoder_get_frame (decoder, dst_buf_info.uiOutYuvTimeStamp);
if (!frame) {
/* Where did our frame go? This is a reference tracking error. */
GST_WARNING_OBJECT (decoder,
"Failed to look up frame ref %" G_GUINT64_FORMAT,
(guint64) dst_buf_info.uiOutYuvTimeStamp);
return GST_FLOW_OK;
}
actual_width = dst_buf_info.UsrData.sSystemBuffer.iWidth;
actual_height = dst_buf_info.UsrData.sSystemBuffer.iHeight;
if (!gst_pad_has_current_caps (GST_VIDEO_DECODER_SRC_PAD (openh264dec))
|| actual_width != openh264dec->width
|| actual_height != openh264dec->height) {
state =
gst_video_decoder_set_output_state (decoder, GST_VIDEO_FORMAT_I420,
actual_width, actual_height, openh264dec->input_state);
openh264dec->width = actual_width;
openh264dec->height = actual_height;
if (!gst_video_decoder_negotiate (decoder)) {
GST_ERROR_OBJECT (openh264dec,
"Failed to negotiate with downstream elements");
gst_video_codec_state_unref (state);
gst_video_codec_frame_unref (frame);
return GST_FLOW_NOT_NEGOTIATED;
}
} else {
state = gst_video_decoder_get_output_state (decoder);
}
flow_status = gst_video_decoder_allocate_output_frame (decoder, frame);
if (flow_status != GST_FLOW_OK) {
gst_video_codec_state_unref (state);
gst_video_codec_frame_unref (frame);
return flow_status;
}
if (!gst_video_frame_map (&video_frame, &state->info, frame->output_buffer,
GST_MAP_WRITE)) {
GST_ERROR_OBJECT (openh264dec, "Cannot map output buffer!");
gst_video_codec_state_unref (state);
gst_video_codec_frame_unref (frame);
return GST_FLOW_ERROR;
}
for (i = 0; i < 3; i++) {
p = GST_VIDEO_FRAME_COMP_DATA (&video_frame, i);
row_stride = GST_VIDEO_FRAME_COMP_STRIDE (&video_frame, i);
component_width = GST_VIDEO_FRAME_COMP_WIDTH (&video_frame, i);
component_height = GST_VIDEO_FRAME_COMP_HEIGHT (&video_frame, i);
src_width =
i <
1 ? dst_buf_info.UsrData.sSystemBuffer.
iStride[0] : dst_buf_info.UsrData.sSystemBuffer.iStride[1];
for (row = 0; row < component_height; row++) {
memcpy (p, yuvdata[i], component_width);
p += row_stride;
yuvdata[i] += src_width;
}
}
gst_video_codec_state_unref (state);
gst_video_frame_unmap (&video_frame);
return gst_video_decoder_finish_frame (decoder, frame);
}
static GstFlowReturn
gst_openh264dec_finish (GstVideoDecoder * decoder)
{
GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder);
GST_DEBUG_OBJECT (openh264dec, "finish");
/* Decoder not negotiated yet */
if (openh264dec->width == 0)
return GST_FLOW_OK;
/* Drain all pending frames */
while ((gst_openh264dec_handle_frame (decoder, NULL)) == GST_FLOW_OK);
return GST_FLOW_OK;
}
static gboolean
gst_openh264dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query)
{
GstVideoCodecState *state;
GstBufferPool *pool;
guint size, min, max;
GstStructure *config;
if (!GST_VIDEO_DECODER_CLASS (gst_openh264dec_parent_class)->decide_allocation
(decoder, query))
return FALSE;
state = gst_video_decoder_get_output_state (decoder);
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
config = gst_buffer_pool_get_config (pool);
if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
}
gst_buffer_pool_set_config (pool, config);
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
gst_object_unref (pool);
gst_video_codec_state_unref (state);
return TRUE;
}
static gboolean
openh264dec_element_init (GstPlugin * plugin)
{
if (openh264_element_init (plugin))
return gst_element_register (plugin, "openh264dec", GST_RANK_MARGINAL,
GST_TYPE_OPENH264DEC);
GST_ERROR ("Incorrect library version loaded, expecting %s", g_strCodecVer);
return FALSE;
}
音视频同步策略分析
- 以音频时钟做为参考时钟(要求参考时钟上的时间是线性递增的)
- 生成数据流时依据参考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间)
- 在播放时,读取数据上的时间戳,同时参考当前参考时钟上的时间来安排播放
- 如果数据块上的时间大于参考时钟的时间,则不急于播放,直到参考时钟达到数据块的开始时间
- 如果数据块上的时间小于参考时钟的时间,则应”尽快”播放或者干脆”丢弃”该数据块,以使得播放赶上播放进度
audiosink同步播放:
- 人对音频的敏感程度高于视频
- 视频由一帧帧离散的视频帧构成,音频是连续的PCM数据流
- 必须保证音频在播放的时候,能够严格的按照速率连续来进行,PCM流不能被延迟和打断
- 软件调度操作在时间上必定会存在一定程度的误差,所以音频播放的时效性不能依靠软件保证,只能由硬件来准确完成
- 音频播放时,只需要硬件从一个时刻有数据的buffer中去取数据,有硬件来保证播放的速率,软件只需要往这个buffer里面填写数据就可以
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstBaseSink
+----GstAudioBaseSink
+----GstAudioSink
+----GstAlsaSink
buffer传输流程:
static GstFlowReturn
gst_base_sink_chain_unlocked (GstBaseSink * basesink, GstPad * pad,
gpointer obj, gboolean is_list)
{
...
if (!is_list) {
/* For buffer lists do not set last buffer for now. */
gst_base_sink_set_last_buffer (basesink, GST_BUFFER_CAST (obj));
gst_base_sink_set_last_buffer_list (basesink, NULL);
if (bclass->render)
ret = bclass->render (basesink, GST_BUFFER_CAST (obj));
} else {
GstBufferList *buffer_list = GST_BUFFER_LIST_CAST (obj);
if (bclass->render_list)
ret = bclass->render_list (basesink, buffer_list);
/* Set the first buffer and buffer list to be included in last sample */
gst_base_sink_set_last_buffer (basesink, sync_buf);
gst_base_sink_set_last_buffer_list (basesink, buffer_list);
}
...
static GstFlowReturn
gst_audio_base_sink_render (GstBaseSink * bsink, GstBuffer * buf)
{
... //大量同步操作
{
written =
gst_audio_ring_buffer_commit (ringbuf, &sample_offset,
info.data + offset, samples, out_samples, &accum);
if (out_samples > written) {
out_samples -= written;
accum = 0;
} else
break;
samples -= written;
offset += written * bpf;
} while (TRUE);
-----------------------------------------------------------------------------------
static void
audioringbuffer_thread_func (GstAudioRingBuffer * buf)
{
GstAudioSink *sink;
GstAudioSinkClass *csink;
GstAudioSinkRingBuffer *abuf = GST_AUDIO_SINK_RING_BUFFER_CAST (buf);
WriteFunc writefunc;
GstMessage *message;
GValue val = { 0 };
gpointer handle;
sink = GST_AUDIO_SINK (GST_OBJECT_PARENT (buf));
csink = GST_AUDIO_SINK_GET_CLASS (sink);
writefunc = csink->write;
if (writefunc == NULL)
goto no_function;
while (TRUE) {
gint left, len;
guint8 *readptr;
gint readseg;
/* buffer must be started */
if (gst_audio_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) {
gint written;
left = len;
do {
written = writefunc (sink, readptr, left);
...
left -= written;
readptr += written;
} while (left > 0);
...
static gint
gst_alsasink_write (GstAudioSink * asink, gpointer data, guint length)
{
GstAlsaSink *alsa;
gint err;
gint cptr;
guint8 *ptr = data;
alsa = GST_ALSA_SINK (asink);
cptr = length / alsa->bpf;
GST_ALSA_SINK_LOCK (asink);
while (cptr > 0) {
/* start by doing a blocking wait for free space. Set the timeout
* to 4 times the period time */
err = snd_pcm_wait (alsa->handle, (4 * alsa->period_time / 1000));
if (err < 0) {
GST_DEBUG_OBJECT (asink, "wait error, %d", err);
} else {
GST_DELAY_SINK_LOCK (asink);
err = snd_pcm_writei (alsa->handle, ptr, cptr);
GST_DELAY_SINK_UNLOCK (asink);
}
..
}
videosink同步播放:
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstBaseSink
+----GstVideoSink
+----GstWaylandSink
buffer传输流程:
static GstFlowReturn
gst_base_sink_chain_unlocked (GstBaseSink * basesink, GstPad * pad,
gpointer obj, gboolean is_list)
{
ret = gst_base_sink_do_sync (basesink, GST_MINI_OBJECT_CAST (sync_buf),
&late, &step_end);
...
if (!is_list) {
/* For buffer lists do not set last buffer for now. */
gst_base_sink_set_last_buffer (basesink, GST_BUFFER_CAST (obj));
gst_base_sink_set_last_buffer_list (basesink, NULL);
if (bclass->render)
ret = bclass->render (basesink, GST_BUFFER_CAST (obj));
} else {
GstBufferList *buffer_list = GST_BUFFER_LIST_CAST (obj);
if (bclass->render_list)
ret = bclass->render_list (basesink, buffer_list);
/* Set the first buffer and buffer list to be included in last sample */
gst_base_sink_set_last_buffer (basesink, sync_buf);
gst_base_sink_set_last_buffer_list (basesink, buffer_list);
}
...
static GstFlowReturn
gst_video_sink_show_frame (GstBaseSink * bsink, GstBuffer * buf)
{
GstVideoSinkClass *klass;
klass = GST_VIDEO_SINK_GET_CLASS (bsink);
if (klass->show_frame == NULL) {
if (parent_class->render != NULL)
return parent_class->render (bsink, buf);
else
return GST_FLOW_OK;
}
return klass->show_frame (GST_VIDEO_SINK_CAST (bsink), buf);
}
static GstFlowReturn
gst_wayland_sink_show_frame (GstVideoSink * vsink, GstBuffer * buffer)
{
...
同步流程:
static GstFlowReturn
gst_base_sink_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
{
GstBaseSink *basesink;
basesink = GST_BASE_SINK (parent);
return gst_base_sink_chain_main (basesink, pad, buf, FALSE);
}
static GstFlowReturn
gst_base_sink_chain_main (GstBaseSink * basesink, GstPad * pad, gpointer obj,
gboolean is_list)
{
GstFlowReturn result;
if (G_UNLIKELY (basesink->pad_mode != GST_PAD_MODE_PUSH))
goto wrong_mode;
GST_BASE_SINK_PREROLL_LOCK (basesink);
result = gst_base_sink_chain_unlocked (basesink, pad, obj, is_list);
GST_BASE_SINK_PREROLL_UNLOCK (basesink);
done:
return result;
}
static GstFlowReturn
gst_base_sink_chain_unlocked (GstBaseSink * basesink, GstPad * pad,
gpointer obj, gboolean is_list)
{
GstBaseSinkClass *bclass;
GstBaseSinkPrivate *priv = basesink->priv;
GstFlowReturn ret = GST_FLOW_OK;
GstClockTime start = GST_CLOCK_TIME_NONE, end = GST_CLOCK_TIME_NONE;
GstSegment *segment;
GstBuffer *sync_buf;
gboolean late, step_end, prepared = FALSE;
...
sync_buf = GST_BUFFER_CAST (obj);
...
bclass = GST_BASE_SINK_GET_CLASS (basesink);
if (bclass->get_times)
bclass->get_times (basesink, sync_buf, &start, &end);
...
syncable =
gst_base_sink_get_sync_times (basesink, obj, &sstart, &sstop, &rstart,
&rstop, &rnext, &do_sync, &stepped, current, &step_end);
...
ret = gst_base_sink_do_sync (basesink, GST_MINI_OBJECT_CAST (sync_buf),
&late, &step_end);
...
ret = bclass->render (basesink, GST_BUFFER_CAST (obj));
...
代码细节分析
gstpad之间是如何link的?
代码实现在gstreamer/subprojects/gstreamer/gst/gstpad.h(gstpad.c)
GstPadLinkReturn
gst_pad_link (GstPad * srcpad, GstPad * sinkpad)
{
return gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_DEFAULT);
}
GstPadLinkReturn
gst_pad_link_full (GstPad * srcpad, GstPad * sinkpad, GstPadLinkCheck flags)
{
GstPadLinkReturn result;
GstElement *parent;
GstPadLinkFunction srcfunc, sinkfunc;
//输入参数合规化检查,确保输入参数确实为pad,并且src和sink的顺序没错
//#define GST_IS_PAD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PAD))
//#define GST_PAD_IS_SRC(pad) (GST_PAD_DIRECTION(pad) == GST_PAD_SRC)
//#define GST_PAD_IS_SINK(pad) (GST_PAD_DIRECTION(pad) == GST_PAD_SINK)
g_return_val_if_fail (GST_IS_PAD (srcpad), GST_PAD_LINK_REFUSED);
g_return_val_if_fail (GST_PAD_IS_SRC (srcpad), GST_PAD_LINK_WRONG_DIRECTION);
g_return_val_if_fail (GST_IS_PAD (sinkpad), GST_PAD_LINK_REFUSED);
g_return_val_if_fail (GST_PAD_IS_SINK (sinkpad), GST_PAD_LINK_WRONG_DIRECTION);
GST_TRACER_PAD_LINK_PRE (srcpad, sinkpad);
/* Notify the parent early. See gst_pad_unlink for details. */
if (G_LIKELY ((parent = GST_ELEMENT_CAST (gst_pad_get_parent (srcpad))))) {
if (G_LIKELY (GST_IS_ELEMENT (parent))) {
gst_element_post_message (parent,
gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad),
GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, TRUE));
} else {
gst_object_unref (parent);
parent = NULL;
}
}
//link检查,后面详细说明
/* prepare will also lock the two pads */
result = gst_pad_link_prepare (srcpad, sinkpad, flags);
if (G_UNLIKELY (result != GST_PAD_LINK_OK)) {
GST_CAT_INFO (GST_CAT_PADS, "link between %s:%s and %s:%s failed: %s",
GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad),
gst_pad_link_get_name (result));
goto done;
}
//配对赋值,分别将对端赋值给自己的peer
/* must set peers before calling the link function */
GST_PAD_PEER (srcpad) = sinkpad;
GST_PAD_PEER (sinkpad) = srcpad;
//检查事件,遍历srcpad中的event,不在sinkpad中的event进行标记
/* check events, when something is different, mark pending */
schedule_events (srcpad, sinkpad);
//获取linkfunc,并执行
/* get the link functions */
srcfunc = GST_PAD_LINKFUNC (srcpad);
sinkfunc = GST_PAD_LINKFUNC (sinkpad);
if (G_UNLIKELY (srcfunc || sinkfunc)) {
/* custom link functions, execute them */
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
if (srcfunc) {
GstObject *tmpparent;
ACQUIRE_PARENT (srcpad, tmpparent, no_parent);
/* this one will call the peer link function */
result = srcfunc (srcpad, tmpparent, sinkpad);
RELEASE_PARENT (tmpparent);
} else if (sinkfunc) {
GstObject *tmpparent;
ACQUIRE_PARENT (sinkpad, tmpparent, no_parent);
/* if no source link function, we need to call the sink link
* function ourselves. */
result = sinkfunc (sinkpad, tmpparent, srcpad);
RELEASE_PARENT (tmpparent);
}
no_parent:
GST_OBJECT_LOCK (srcpad);
GST_OBJECT_LOCK (sinkpad);
/* we released the lock, check if the same pads are linked still */
if (GST_PAD_PEER (srcpad) != sinkpad || GST_PAD_PEER (sinkpad) != srcpad)
goto concurrent_link;
if (G_UNLIKELY (result != GST_PAD_LINK_OK))
goto link_failed;
}
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
//发送signal给2个pad,PAD_LINKED
/* fire off a signal to each of the pads telling them
* that they've been linked */
g_signal_emit (srcpad, gst_pad_signals[PAD_LINKED], 0, sinkpad);
g_signal_emit (sinkpad, gst_pad_signals[PAD_LINKED], 0, srcpad);
GST_CAT_INFO (GST_CAT_PADS, "linked %s:%s and %s:%s, successful",
GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad));
if (!(flags & GST_PAD_LINK_CHECK_NO_RECONFIGURE))
gst_pad_send_event (srcpad, gst_event_new_reconfigure ());
done:
if (G_LIKELY (parent)) {
gst_element_post_message (parent,
gst_message_new_structure_change (GST_OBJECT_CAST (sinkpad),
GST_STRUCTURE_CHANGE_TYPE_PAD_LINK, parent, FALSE));
gst_object_unref (parent);
}
GST_TRACER_PAD_LINK_POST (srcpad, sinkpad, result);
return result;
/* ERRORS */
concurrent_link:
{
GST_CAT_INFO (GST_CAT_PADS, "concurrent link between %s:%s and %s:%s",
GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad));
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
/* The other link operation succeeded first */
result = GST_PAD_LINK_WAS_LINKED;
goto done;
}
link_failed:
{
GST_CAT_INFO (GST_CAT_PADS, "link between %s:%s and %s:%s failed: %s",
GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad),
gst_pad_link_get_name (result));
GST_PAD_PEER (srcpad) = NULL;
GST_PAD_PEER (sinkpad) = NULL;
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
goto done;
}
}
static GstPadLinkReturn
gst_pad_link_prepare (GstPad * srcpad, GstPad * sinkpad, GstPadLinkCheck flags)
{
GST_CAT_INFO (GST_CAT_PADS, "trying to link %s:%s and %s:%s",
GST_DEBUG_PAD_NAME (srcpad), GST_DEBUG_PAD_NAME (sinkpad));
GST_OBJECT_LOCK (srcpad);
//确保2个pad还没有进行link
if (G_UNLIKELY (GST_PAD_PEER (srcpad) != NULL))
goto src_was_linked;
GST_OBJECT_LOCK (sinkpad);
if (G_UNLIKELY (GST_PAD_PEER (sinkpad) != NULL))
goto sink_was_linked;
//检查pad的父类和祖父类,确保父类不同,并且祖父相同
/* check hierarchy, pads can only be linked if the grandparents
* are the same. */
if ((flags & GST_PAD_LINK_CHECK_HIERARCHY)
&& !gst_pad_link_check_hierarchy (srcpad, sinkpad))
goto wrong_hierarchy;
//检查2个pad的交集,确保有交集
/* check pad caps for non-empty intersection */
if (!gst_pad_link_check_compatible_unlocked (srcpad, sinkpad, flags))
goto no_format;
/* FIXME check pad scheduling for non-empty intersection */
return GST_PAD_LINK_OK;
src_was_linked:
{
GST_CAT_INFO (GST_CAT_PADS, "src %s:%s was already linked to %s:%s",
GST_DEBUG_PAD_NAME (srcpad),
GST_DEBUG_PAD_NAME (GST_PAD_PEER (srcpad)));
/* we do not emit a warning in this case because unlinking cannot
* be made MT safe.*/
GST_OBJECT_UNLOCK (srcpad);
return GST_PAD_LINK_WAS_LINKED;
}
sink_was_linked:
{
GST_CAT_INFO (GST_CAT_PADS, "sink %s:%s was already linked to %s:%s",
GST_DEBUG_PAD_NAME (sinkpad),
GST_DEBUG_PAD_NAME (GST_PAD_PEER (sinkpad)));
/* we do not emit a warning in this case because unlinking cannot
* be made MT safe.*/
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
return GST_PAD_LINK_WAS_LINKED;
}
wrong_hierarchy:
{
GST_CAT_INFO (GST_CAT_PADS, "pads have wrong hierarchy");
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
return GST_PAD_LINK_WRONG_HIERARCHY;
}
no_format:
{
GST_CAT_INFO (GST_CAT_PADS, "caps are incompatible");
GST_OBJECT_UNLOCK (sinkpad);
GST_OBJECT_UNLOCK (srcpad);
return GST_PAD_LINK_NOFORMAT;
}
}
static gboolean
gst_pad_link_check_hierarchy (GstPad * src, GstPad * sink)
{
GstObject *psrc, *psink;
//获取父类
psrc = GST_OBJECT_PARENT (src);
psink = GST_OBJECT_PARENT (sink);
/* if one of the pads has no parent, we allow the link */
if (G_UNLIKELY (psrc == NULL || psink == NULL))
goto no_parent;
/* only care about parents that are elements */
if (G_UNLIKELY (!GST_IS_ELEMENT (psrc) || !GST_IS_ELEMENT (psink)))
goto no_element_parent;
/* if the parents are the same, we have a loop */
if (G_UNLIKELY (psrc == psink))
goto same_parents;
//获取祖父类
/* if they both have a parent, we check the grandparents. We can not lock
* the parent because we hold on the child (pad) and the locking order is
* parent >> child. */
psrc = GST_OBJECT_PARENT (psrc);
psink = GST_OBJECT_PARENT (psink);
/* if they have grandparents but they are not the same */
if (G_UNLIKELY (psrc != psink))
goto wrong_grandparents;
return TRUE;
/* ERRORS */
no_parent:
{
GST_CAT_DEBUG (GST_CAT_CAPS,
"one of the pads has no parent %" GST_PTR_FORMAT " and %"
GST_PTR_FORMAT, psrc, psink);
return TRUE;
}
no_element_parent:
{
GST_CAT_DEBUG (GST_CAT_CAPS,
"one of the pads has no element parent %" GST_PTR_FORMAT " and %"
GST_PTR_FORMAT, psrc, psink);
return TRUE;
}
same_parents:
{
GST_CAT_DEBUG (GST_CAT_CAPS, "pads have same parent %" GST_PTR_FORMAT,
psrc);
return FALSE;
}
wrong_grandparents:
{
GST_CAT_DEBUG (GST_CAT_CAPS,
"pads have different grandparents %" GST_PTR_FORMAT " and %"
GST_PTR_FORMAT, psrc, psink);
return FALSE;
}
}
pipeline中buffer传递流程
我们以一个简单的pipeline来进行分析,pipeline如下所示:
gst-launch-1.0 filesrc location="1080P.mov" ! qtdemux name=demux demux.video_0 ! h264parse ! openh264dec ! waylandsink
几个插件的代码位置分别在:
basesrc----------------------->gstreamer/libs/gst/base
|---->filesrc--------------->gstreamer/plugins/elements
qtdemux----------------------->gst-plugins-good/gst/isomp4
baseparse--------------------->gstreamer/libs/gst/base
|---->h264parse------------->gst-plugins-bad/gst/videoparsers
videodecoder------------------>gst-plugins-base/gst-libs/gst/video
|---->openh264dec----------->gst-plugins-bad/ext/openh264
gstbasesink------------------->gstreamer/libs/gst/base
|---->gstvideosink---------->gst-plugins-base/gst-libs/gst/video
|---->waylandsink- ->gst-plugins-bad/ext/wayland
filesrc——>gstreamer/plugins/elements:
gstfilesrc.c
//向GstBuffer中填充从文件中读取的数据流
static GstFlowReturn
gst_file_src_fill (GstBaseSrc * basesrc, guint64 offset, guint length,
GstBuffer * buf)
{
GstFileSrc *src;
guint to_read, bytes_read;
int ret;
GstMapInfo info;
guint8 *data;
src = GST_FILE_SRC_CAST (basesrc);
if (G_UNLIKELY (offset != -1 && src->read_position != offset)) {
off_t res;
res = lseek (src->fd, offset, SEEK_SET);
if (G_UNLIKELY (res < 0 || res != offset))
goto seek_failed;
src->read_position = offset;
}
if (!gst_buffer_map (buf, &info, GST_MAP_WRITE))
goto buffer_write_fail;
data = info.data;
bytes_read = 0;
to_read = length;
while (to_read > 0) {
GST_LOG_OBJECT (src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x",
to_read, offset + bytes_read);
errno = 0;
ret = read (src->fd, data + bytes_read, to_read);
if (G_UNLIKELY (ret < 0)) {
if (errno == EAGAIN || errno == EINTR)
continue;
goto could_not_read;
}
/* files should eos if they read 0 and more was requested */
if (G_UNLIKELY (ret == 0)) {
/* .. but first we should return any remaining data */
if (bytes_read > 0)
break;
goto eos;
}
to_read -= ret;
bytes_read += ret;
src->read_position += ret;
}
gst_buffer_unmap (buf, &info);
if (bytes_read != length)
gst_buffer_resize (buf, 0, bytes_read);
GST_BUFFER_OFFSET (buf) = offset;
GST_BUFFER_OFFSET_END (buf) = offset + bytes_read;
return GST_FLOW_OK;
/* ERROR */
}
gstbasesrc.c
//创建,开始fill数据
static GstFlowReturn
gst_base_src_default_create (GstBaseSrc * src, guint64 offset,
guint size, GstBuffer ** buffer)
{
GstBaseSrcClass *bclass;
GstFlowReturn ret;
GstBuffer *res_buf;
bclass = GST_BASE_SRC_GET_CLASS (src);
if (G_UNLIKELY (!bclass->alloc))
goto no_function;
if (G_UNLIKELY (!bclass->fill))
goto no_function;
if (*buffer == NULL) {
/* downstream did not provide us with a buffer to fill, allocate one
* ourselves */
ret = bclass->alloc (src, offset, size, &res_buf);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto alloc_failed;
} else {
res_buf = *buffer;
}
if (G_LIKELY (size > 0)) {
/* only call fill when there is a size */
ret = bclass->fill (src, offset, size, res_buf);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto not_ok;
}
*buffer = res_buf;
return GST_FLOW_OK;
/* ERRORS */
}
//分配数据buffer的内存
static GstFlowReturn
gst_base_src_default_alloc (GstBaseSrc * src, guint64 offset,
guint size, GstBuffer ** buffer)
{
GstFlowReturn ret;
GstBaseSrcPrivate *priv = src->priv;
GstBufferPool *pool = NULL;
GstAllocator *allocator = NULL;
GstAllocationParams params;
GST_OBJECT_LOCK (src);
if (priv->pool) {
pool = gst_object_ref (priv->pool);
} else if (priv->allocator) {
allocator = gst_object_ref (priv->allocator);
}
params = priv->params;
GST_OBJECT_UNLOCK (src);
if (pool) {
ret = gst_buffer_pool_acquire_buffer (pool, buffer, NULL);
} else if (size != -1) {
*buffer = gst_buffer_new_allocate (allocator, size, ¶ms);
if (G_UNLIKELY (*buffer == NULL))
goto alloc_failed;
ret = GST_FLOW_OK;
} else {
GST_WARNING_OBJECT (src, "Not trying to alloc %u bytes. Blocksize not set?",
size);
goto alloc_failed;
}
done:
if (pool)
gst_object_unref (pool);
if (allocator)
gst_object_unref (allocator);
return ret;
/* ERRORS */
alloc_failed:
{
GST_ERROR_OBJECT (src, "Failed to allocate %u bytes", size);
ret = GST_FLOW_ERROR;
goto done;
}
}
//循环向外push数据
static void
gst_base_src_loop (GstPad * pad)
{
GstBaseSrc *src;
GstBuffer *buf = NULL;
GstFlowReturn ret;
gint64 position;
gboolean eos;
guint blocksize;
GList *pending_events = NULL, *tmp;
eos = FALSE;
src = GST_BASE_SRC (GST_OBJECT_PARENT (pad));
/* Just leave immediately if we're flushing */
GST_LIVE_LOCK (src);
if (G_UNLIKELY (src->priv->flushing || GST_PAD_IS_FLUSHING (pad)))
goto flushing;
GST_LIVE_UNLOCK (src);
/* Just return if EOS is pushed again, as the app might be unaware that an
* EOS have been sent already */
if (GST_PAD_IS_EOS (pad)) {
GST_DEBUG_OBJECT (src, "Pad is marked as EOS, pause the task");
gst_pad_pause_task (pad);
goto done;
}
gst_base_src_send_stream_start (src);
/* The stream-start event could've caused something to flush us */
GST_LIVE_LOCK (src);
if (G_UNLIKELY (src->priv->flushing || GST_PAD_IS_FLUSHING (pad)))
goto flushing;
GST_LIVE_UNLOCK (src);
/* check if we need to renegotiate */
if (gst_pad_check_reconfigure (pad)) {
if (!gst_base_src_negotiate_unlocked (src)) {
gst_pad_mark_reconfigure (pad);
if (GST_PAD_IS_FLUSHING (pad)) {
GST_LIVE_LOCK (src);
goto flushing;
} else {
goto negotiate_failed;
}
}
}
GST_LIVE_LOCK (src);
if (G_UNLIKELY (src->priv->flushing || GST_PAD_IS_FLUSHING (pad)))
goto flushing;
blocksize = src->blocksize;
/* if we operate in bytes, we can calculate an offset */
if (src->segment.format == GST_FORMAT_BYTES) {
position = src->segment.position;
/* for negative rates, start with subtracting the blocksize */
if (src->segment.rate < 0.0) {
/* we cannot go below segment.start */
if (position > src->segment.start + blocksize)
position -= blocksize;
else {
/* last block, remainder up to segment.start */
blocksize = position - src->segment.start;
position = src->segment.start;
}
}
} else
position = -1;
GST_LOG_OBJECT (src, "next_ts %" GST_TIME_FORMAT " size %u",
GST_TIME_ARGS (position), blocksize);
/* clean up just in case we got interrupted or so last time round */
if (src->priv->pending_bufferlist != NULL) {
gst_buffer_list_unref (src->priv->pending_bufferlist);
src->priv->pending_bufferlist = NULL;
}
ret = gst_base_src_get_range (src, position, blocksize, &buf);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
GST_INFO_OBJECT (src, "pausing after gst_base_src_get_range() = %s",
gst_flow_get_name (ret));
GST_LIVE_UNLOCK (src);
goto pause;
}
/* Note: at this point buf might be a single buf returned which we own or
* the first buf of a pending buffer list submitted via submit_buffer_list(),
* in which case the buffer is owned by the pending buffer list and not us. */
g_assert (buf != NULL);
/* push events to close/start our segment before we push the buffer. */
if (G_UNLIKELY (src->priv->segment_pending)) {
GstEvent *seg_event = gst_event_new_segment (&src->segment);
gst_event_set_seqnum (seg_event, src->priv->segment_seqnum);
src->priv->segment_seqnum = gst_util_seqnum_next ();
gst_pad_push_event (pad, seg_event);
src->priv->segment_pending = FALSE;
}
if (g_atomic_int_get (&src->priv->have_events)) {
GST_OBJECT_LOCK (src);
/* take the events */
pending_events = src->priv->pending_events;
src->priv->pending_events = NULL;
g_atomic_int_set (&src->priv->have_events, FALSE);
GST_OBJECT_UNLOCK (src);
}
/* Push out pending events if any */
if (G_UNLIKELY (pending_events != NULL)) {
for (tmp = pending_events; tmp; tmp = g_list_next (tmp)) {
GstEvent *ev = (GstEvent *) tmp->data;
gst_pad_push_event (pad, ev);
}
g_list_free (pending_events);
}
/* figure out the new position */
switch (src->segment.format) {
case GST_FORMAT_BYTES:
{
guint bufsize = gst_buffer_get_size (buf);
/* we subtracted above for negative rates */
if (src->segment.rate >= 0.0)
position += bufsize;
break;
}
case GST_FORMAT_TIME:
{
GstClockTime start, duration;
start = GST_BUFFER_TIMESTAMP (buf);
duration = GST_BUFFER_DURATION (buf);
if (GST_CLOCK_TIME_IS_VALID (start))
position = start;
else
position = src->segment.position;
if (GST_CLOCK_TIME_IS_VALID (duration)) {
if (src->segment.rate >= 0.0)
position += duration;
}
break;
}
case GST_FORMAT_DEFAULT:
if (src->segment.rate >= 0.0)
position = GST_BUFFER_OFFSET_END (buf);
else
position = GST_BUFFER_OFFSET (buf);
break;
default:
position = -1;
break;
}
if (position != -1) {
if (src->segment.rate >= 0.0) {
/* positive rate, check if we reached the stop */
if (src->segment.stop != -1) {
if (position >= src->segment.stop) {
if (g_atomic_int_get (&src->priv->automatic_eos))
eos = TRUE;
position = src->segment.stop;
}
}
} else {
/* negative rate, check if we reached the start. start is always set to
* something different from -1 */
if (position <= src->segment.start) {
if (g_atomic_int_get (&src->priv->automatic_eos))
eos = TRUE;
position = src->segment.start;
}
/* when going reverse, all buffers are DISCONT */
src->priv->discont = TRUE;
}
GST_OBJECT_LOCK (src);
src->segment.position = position;
GST_OBJECT_UNLOCK (src);
}
if (G_UNLIKELY (src->priv->discont)) {
GST_INFO_OBJECT (src, "marking pending DISCONT");
buf = gst_buffer_make_writable (buf);
GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
src->priv->discont = FALSE;
}
GST_LIVE_UNLOCK (src);
/* push buffer or buffer list */
if (src->priv->pending_bufferlist != NULL) {
ret = gst_pad_push_list (pad, src->priv->pending_bufferlist);
src->priv->pending_bufferlist = NULL;
} else {
ret = gst_pad_push (pad, buf);
}
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
if (ret == GST_FLOW_NOT_NEGOTIATED) {
goto not_negotiated;
}
GST_INFO_OBJECT (src, "pausing after gst_pad_push() = %s",
gst_flow_get_name (ret));
goto pause;
}
/* Segment pending means that a new segment was configured
* during this loop run */
if (G_UNLIKELY (eos && !src->priv->segment_pending)) {
GST_INFO_OBJECT (src, "pausing after end of segment");
ret = GST_FLOW_EOS;
goto pause;
}
done:
return;
/* special cases */
not_negotiated:
{
if (gst_pad_needs_reconfigure (pad)) {
GST_DEBUG_OBJECT (src, "Retrying to renegotiate");
return;
}
/* fallthrough when push returns NOT_NEGOTIATED and we don't have
* a pending negotiation request on our srcpad */
}
negotiate_failed:
{
GST_DEBUG_OBJECT (src, "Not negotiated");
ret = GST_FLOW_NOT_NEGOTIATED;
goto pause;
}
flushing:
{
GST_DEBUG_OBJECT (src, "we are flushing");
GST_LIVE_UNLOCK (src);
ret = GST_FLOW_FLUSHING;
goto pause;
}
pause:
{
GstEvent *event;
GST_DEBUG_OBJECT (src, "pausing task, reason %s", gst_flow_get_name (ret));
src->running = FALSE;
gst_pad_pause_task (pad);
if (ret == GST_FLOW_EOS) {
gboolean flag_segment;
GstFormat format;
gint64 position;
flag_segment = (src->segment.flags & GST_SEGMENT_FLAG_SEGMENT) != 0;
format = src->segment.format;
position = src->segment.position;
/* perform EOS logic */
if (src->priv->forced_eos) {
g_assert (g_atomic_int_get (&src->priv->has_pending_eos));
GST_OBJECT_LOCK (src);
event = src->priv->pending_eos;
src->priv->pending_eos = NULL;
GST_OBJECT_UNLOCK (src);
} else if (flag_segment) {
GstMessage *message;
message = gst_message_new_segment_done (GST_OBJECT_CAST (src),
format, position);
gst_message_set_seqnum (message, src->priv->seqnum);
gst_element_post_message (GST_ELEMENT_CAST (src), message);
event = gst_event_new_segment_done (format, position);
gst_event_set_seqnum (event, src->priv->seqnum);
} else {
event = gst_event_new_eos ();
gst_event_set_seqnum (event, src->priv->seqnum);
}
gst_pad_push_event (pad, event);
src->priv->forced_eos = FALSE;
} else if (ret == GST_FLOW_NOT_LINKED || ret <= GST_FLOW_EOS) {
event = gst_event_new_eos ();
gst_event_set_seqnum (event, src->priv->seqnum);
/* for fatal errors we post an error message, post the error
* first so the app knows about the error first.
* Also don't do this for FLUSHING because it happens
* due to flushing and posting an error message because of
* that is the wrong thing to do, e.g. when we're doing
* a flushing seek. */
GST_ELEMENT_FLOW_ERROR (src, ret);
gst_pad_push_event (pad, event);
}
goto done;
}
}
流程图如下:
qtdemux——>gst-plugins-good/gst/isomp4:
h264parse—->gst-plugins-bad/gst/videoparsers:
openh264dec–>gst-plugins-bad/ext/openh264:
waylandsink–>gst-plugins-bad/ext/wayland:Gstreamer
基础框架代码分析
gstquark
- 基于GLib中的GQuark
- 实现Gstreamer中常用的数字和字符串的映射
-------------------------------------------------------gstquark.h---------------------------------------------
typedef enum _GstQuarkId
{
GST_QUARK_FORMAT = 0,
GST_QUARK_CURRENT = 1,
GST_QUARK_DURATION = 2,
GST_QUARK_RATE = 3,
...
GST_QUARK_GAP_FLAGS = 202,
GST_QUARK_MAX = 203
} GstQuarkId;
extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];
#define GST_QUARK(q) _priv_gst_quark_table[GST_QUARK_##q] //通过这个宏来使用
-------------------------------------------------------gstquark.c---------------------------------------------
static const gchar *_quark_strings[] = {
"format", "current", "duration", "rate",
...
"gap-flags"
};
GQuark _priv_gst_quark_table[GST_QUARK_MAX];
void
_priv_gst_quarks_initialize (void) //初始化
{
gint i;
if (G_N_ELEMENTS (_quark_strings) != GST_QUARK_MAX)
g_warning ("the quark table is not consistent! %d != %d",
(int) G_N_ELEMENTS (_quark_strings), GST_QUARK_MAX);
for (i = 0; i < GST_QUARK_MAX; i++) {
_priv_gst_quark_table[i] = g_quark_from_static_string (_quark_strings[i]); //实现枚举和字符串的一一对应
}
}
-------------------------------------------------------gst.c---------------------------------------------
init_post (GOptionContext * context, GOptionGroup * group, gpointer data,
GError ** error)
{
GLogLevelFlags llf;
if (gst_initialized) {
GST_DEBUG ("already initialized");
return TRUE;
}
llf = G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL;
/* TODO: should we also set up a handler for the other gst libs/domains? */
g_log_set_handler (G_LOG_DOMAIN, llf, debug_log_handler, NULL);
_priv_gst_mini_object_initialize ();
_priv_gst_quarks_initialize (); //这里进行初始化
_priv_gst_allocator_initialize ();
...
gsterror
- 定义了一些通用的error类型及其标准打印
-----------------------------------------gsterror.h----------------------------------------------------
typedef enum
{
GST_CORE_ERROR_FAILED = 1,
GST_CORE_ERROR_TOO_LAZY,
GST_CORE_ERROR_NOT_IMPLEMENTED,
...
GST_CORE_ERROR_DISABLED,
GST_CORE_ERROR_NUM_ERRORS
} GstCoreError;
...
typedef enum
{
GST_LIBRARY_ERROR_FAILED = 1,
GST_LIBRARY_ERROR_TOO_LAZY,
GST_LIBRARY_ERROR_INIT,
...
GST_LIBRARY_ERROR_NUM_ERRORS
} GstLibraryError;
...
typedef enum
{
GST_RESOURCE_ERROR_FAILED = 1,
GST_RESOURCE_ERROR_TOO_LAZY,
GST_RESOURCE_ERROR_NOT_FOUND,
...
GST_RESOURCE_ERROR_NOT_AUTHORIZED,
GST_RESOURCE_ERROR_NUM_ERRORS
} GstResourceError;
...
typedef enum
{
GST_STREAM_ERROR_FAILED = 1,
GST_STREAM_ERROR_TOO_LAZY,
GST_STREAM_ERROR_NOT_IMPLEMENTED,
...
GST_STREAM_ERROR_DECRYPT_NOKEY,
GST_STREAM_ERROR_NUM_ERRORS
} GstStreamError;
...
#define GST_LIBRARY_ERROR gst_library_error_quark ()
#define GST_RESOURCE_ERROR gst_resource_error_quark ()
#define GST_CORE_ERROR gst_core_error_quark ()
#define GST_STREAM_ERROR gst_stream_error_quark ()
-----------------------------------------gsterror.c----------------------------------------------------
...
gchar *
gst_error_get_message (GQuark domain, gint code) //通过这个调用,domain是4种主要类型,code是里面的枚举类型
{
const gchar *message = NULL;
if (domain == GST_CORE_ERROR)
message = gst_error_get_core_error ((GstCoreError) code);
else if (domain == GST_LIBRARY_ERROR)
message = gst_error_get_library_error ((GstLibraryError) code);
else if (domain == GST_RESOURCE_ERROR)
message = gst_error_get_resource_error ((GstResourceError) code);
else if (domain == GST_STREAM_ERROR)
message = gst_error_get_stream_error ((GstStreamError) code);
else {
g_warning ("No error messages for domain %s", g_quark_to_string (domain));
return g_strdup_printf (_("No error message for domain %s."),
g_quark_to_string (domain));
}
if (message)
return g_strdup (message);
else
return
g_strdup_printf (_
("No standard error message for domain %s and code %d."),
g_quark_to_string (domain), code);
}
...
gstformat
- 注册一些常用format
-----------------------------------------------gstformat.h------------------------------------------------
typedef enum {
GST_FORMAT_UNDEFINED = 0, /* must be first in list */
GST_FORMAT_DEFAULT = 1,
GST_FORMAT_BYTES = 2,
GST_FORMAT_TIME = 3,
GST_FORMAT_BUFFERS = 4,
GST_FORMAT_PERCENT = 5
} GstFormat; //一些基本format
struct _GstFormatDefinition
{
GstFormat value;
const gchar *nick;
const gchar *description;
GQuark quark;
}; //format格式定义
GST_API const gchar* gst_format_get_name(GstFormat format);
GST_API GQuark gst_format_to_quark(GstFormat format);
GST_API GstFormat gst_format_register(const gchar *nick, const gchar *description);
GST_API GstFormat gst_format_get_by_nick(const gchar *nick);
GST_API gboolean gst_formats_contains(const GstFormat *formats, GstFormat format);
GST_API const GstFormatDefinition* gst_format_get_details(GstFormat format);
GST_API GstIterator* gst_format_iterate_definitions(void);
//外部API
-------------------------------------------------gstformat.c-----------------------------------------------
static GMutex mutex;
static GList *_gst_formats = NULL;
static GHashTable *_nick_to_format = NULL;
static GHashTable *_format_to_nick = NULL;
static guint32 _n_values = 1; /* we start from 1 because 0 reserved for UNDEFINED */
static GstFormatDefinition standard_definitions[] = {
{GST_FORMAT_DEFAULT, "default", "Default format for the media type", 0},
{GST_FORMAT_BYTES, "bytes", "Bytes", 0},
{GST_FORMAT_TIME, "time", "Time", 0},
{GST_FORMAT_BUFFERS, "buffers", "Buffers", 0},
{GST_FORMAT_PERCENT, "percent", "Percent", 0},
{GST_FORMAT_UNDEFINED, NULL, NULL, 0}
};
void
_priv_gst_format_initialize (void) //初始化,在gst.c的init_post中调用
{
GstFormatDefinition *standards = standard_definitions;
if (_nick_to_format == NULL) {
_nick_to_format = g_hash_table_new (g_str_hash, g_str_equal);
_format_to_nick = g_hash_table_new (NULL, NULL); //新建2个hash表
}
while (standards->nick) {
standards->quark = g_quark_from_static_string (standards->nick);
g_hash_table_insert (_nick_to_format, (gpointer) standards->nick,
standards);
g_hash_table_insert (_format_to_nick, GINT_TO_POINTER (standards->value),
standards); //初始化2个hash表
_gst_formats = g_list_append (_gst_formats, standards); //初始化1个list
standards++;
_n_values++;
}
...
}
GstFormat
gst_format_register (const gchar * nick, const gchar * description) //使用昵称和描述注册1个format
{
GstFormatDefinition *format;
GstFormat query;
...
query = gst_format_get_by_nick (nick); //查找是否已经存在
if (query != GST_FORMAT_UNDEFINED)
return query;
g_mutex_lock (&mutex);
format = g_slice_new (GstFormatDefinition); //分配内存
format->value = (GstFormat) _n_values;
format->nick = g_strdup (nick);
format->description = g_strdup (description);
format->quark = g_quark_from_static_string (format->nick);
g_hash_table_insert (_nick_to_format, (gpointer) format->nick, format);
g_hash_table_insert (_format_to_nick, GINT_TO_POINTER (format->value), //插入Hash表
format);
_gst_formats = g_list_append (_gst_formats, format); //插入list
_n_values++;
g_mutex_unlock (&mutex);
return format->value;
}
gstclock
- 维护时钟对象
- gstpipeline维护一个gstclock,pipeline管理其生命周期,pipeline将这个clock传递给它的element
- 多时钟源
- clock time&running time&stream time
GstClockTime
gst_clock_get_time (GstClock * clock) //获取给定时钟的当前时间,并根据偏移量和速率进行调整
{
GstClockTime ret;
gint seq;
do {
/* reget the internal time when we retry to get the most current
* timevalue */
ret = gst_clock_get_internal_time (clock); //获取给定时钟的当前时间
seq = read_seqbegin (clock); //顺序锁操作
/* this will scale for rate and offset */
ret = gst_clock_adjust_unlocked (clock, ret);
} while (read_seqretry (clock, seq));
return ret;
}
GstClockTime
gst_clock_get_internal_time (GstClock * clock)
{
GstClockTime ret;
GstClockClass *cclass;
cclass = GST_CLOCK_GET_CLASS (clock);
if (G_UNLIKELY (cclass->get_internal_time == NULL))
goto not_supported;
ret = cclass->get_internal_time (clock); //根据时钟的类型,转到子类中去获取各种clock的时间,比如CPU/AUDIO等
return ret;
}
GstClockTime
gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) //internal时间调整,转为external时间
{
GstClockTime ret, cinternal, cexternal, cnum, cdenom;
GstClockPrivate *priv = clock->priv;
/* get calibration values for readability */
cinternal = priv->internal_calibration;
cexternal = priv->external_calibration;
cnum = priv->rate_numerator;
cdenom = priv->rate_denominator; //调整参数
ret =
gst_clock_adjust_with_calibration (clock, internal, cinternal, cexternal,
cnum, cdenom); //具体的调整接口
/* make sure the time is increasing */
priv->last_time = MAX (ret, priv->last_time); //确保时间递增
return priv->last_time;
}
GstClockTime
gst_clock_adjust_with_calibration (GstClock * clock,
GstClockTime internal_target, GstClockTime cinternal,
GstClockTime cexternal, GstClockTime cnum, GstClockTime cdenom)
{
GstClockTime ret;
/* The formula is (internal - cinternal) * cnum / cdenom + cexternal
*
* Since we do math on unsigned 64-bit ints we have to special case for
* internal < cinternal to get the sign right. this case is not very common,
* though.
*/
if (G_LIKELY (internal_target >= cinternal)) {
ret = internal_target - cinternal;
ret = gst_util_uint64_scale (ret, cnum, cdenom);
ret += cexternal;
} else {
ret = cinternal - internal_target;
ret = gst_util_uint64_scale (ret, cnum, cdenom);
/* clamp to 0 */
if (G_LIKELY (cexternal > ret))
ret = cexternal - ret;
else
ret = 0;
}
return ret;
}