本文介绍了在GStreamer下载方法, 使用过程中的部分依赖,以及在Windows上编译配置GStreamer 过程,为学习GStreamer 创建条件。
一、glib路径:https://www.linuxfromscratch.org/blfs/view/svn/general/glib2.htmlhttps://www.linuxfromscratch.org/blfs/view/svn/general/glib2.html
二、准备GObject相关知识,代码在glib中
GObject又称为GLib对象系统,和glibc,libc没有关系,glibc是linux下的GNU C函数库,libc是linux下的ANSI C函数库。面向对像的方法常常用私有来隐藏信息,而用C语言实现的系统中,私有却没有那么简单。一种方法是将私有成员分离到独立的结构体中;另外一种方法是将私有结构体存放到源码文件,对外结构体中仅仅保存一个指针,指向这个结构。
GObject相关URL:GObject – 2.0https://docs.gtk.org/gobject/
三、下载gst-template:git clone git://anongit.freedesktop.org/gstreamer/gst-template.git 可以利用其中的工具来产生plugin,方法是:../tools/make_element plugin_name
四、第三方依赖库下载
Gstreamer的功能有时候依赖第三方库,这些库在Gstreamer的subprojects下面没有对应的工程。如果自己下载编译,修改meson.build其实是个挺费时的工程,这个时候,可以到https://mesonbuild.com/Wrapdb-projects.html#meson-wrapdb-packages 这个网站上下载Meson WrapDB packages,然后将对应的.warp文件放到subprojects/目录下即可。如编译openssl就可以下载openssl.wrap。
五、编译三方库过程中,只生成*.dll文件,未生成*.lib的故障解决:故障原因由未导出任何API导致,可以由以下方式解决,如在.c文件中增加一个API,并且export。
#if defined(_WIN32)
__declspec(dllexport) int OPENSSL_generate_lib_func()
{
return 1;
}
#endif
但这种方式治标不治本,编译产生的函数声明等等都没有导出来,导致链接的时候符号查找不到,可以在链接时,加入/DEF:openssl.def选项,
openssl_cflags = ['/DEF:openssl.def',]
libssl_dep = declare_dependency(
include_directories: include_directories,
dependencies: dependencies + [libcrypto_dep],
link_with: libssl_lib,
link_args:openssl_cflags,
)
官方指导链接:Built-in optionshttps://mesonbuild.com/Builtin-options.htmlopenssl.def是描述需要导出符号的文件:
LIBRARY crypto
EXPORTS
a2i_IPADDRESS
ASN1_ANY_it
ASN1_item_d2i
ASN1_item_free
ASN1_item_i2d
ASN1_item_new
详情参见官方指导文档:/DEF (Specify Module-Definition File) | Microsoft Docs
可以在Gstreamer的顶层meson_options.txt中加入option('openssl', type : 'feature', value : 'auto'),在meson.build的subprojects中加入 ['openssl', { 'option': get_option('openssl'), 'match_gst_version': false}],就可以编译openssl了。
事实上.def文件要配置全是很难的,这里给出一种方式:
1. 首先,您可以创建静态库。修改meson.build,加入
libcrypto_lib = static_library(
'crypto',
dependencies: dependencies,
sources: libcrypto_sources,
include_directories: include_directories,
c_args: c_args,
install: true,
link_args:openssl_cflags,
)
libssl_lib = static_library(
'ssl',
dependencies: dependencies + [libcrypto_dep],
sources: libssl_sources,
include_directories: include_directories,
c_args: c_args,
link_args:openssl_cflags,
install: true,
)
2. 然后使用“dumpbin/linkermember”从静态库中导出所有符号。
dumpbin /LINKERMEMBER libcrypto.a > libcrypto.txt
dumpbin /LINKERMEMBER libssl.a > libssl.txt
3. 将所有符号放入.def文件。
4. 使用.def文件创建dll。
不过,使用.def创建的dll,在使用时,涉及到具体的函数时,仍然需要import,用__declspec(dllimport),详见:EXPORTS | Microsoft DocsLearn more about: EXPORTShttps://docs.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-160,换一种思维,不用编译成动态库,编译成静态库或许也可以。 有时候很多函数的声明或者定义在宏当中实现,不易查找位置,可以在编译的时候,重新对此函数进行另外一种定义,编译器报错并报告之前定义或者声明的位置,就可以找到函数原型了。
本人的PC机常年用于开发,具备一些基础工具。如果您在按步骤操作过程中遇到工具不足,需要install的情况,请根据自己的情况安装相应工具包。
1. 安装Visual Studio 2019,python,本人安装的python版本是Python 3.8.0,更新pip,安装 ninja和menson等等。
pip install --upgrade pip
pip install meson
pip install ninja
2. 以管理员身分运行x64 Native Tools Command Prompt for VS 2019,cd到GStreamer大包代码的顶层目录,然后输入meson build,meson是可以执行程序的名称,build是将产生ninja编译脚本所在的目录。
创建编译目录
The Meson build system
Version: 0.60.3
Source dir: F:\Source\OpenSource\gstreamer
Build dir: F:\Source\OpenSource\gstreamer\build
Build type: native build
Project name: gstreamer-full
Project version: 1.19.3.1
C compiler for the host machine: cl (msvc 19.29.30139 "???? x64 ?? Microsoft (R) C/C++ ????????? 19.29.30139 ??")
C linker for the host machine: link link 14.29.30139.0
Host machine cpu family: x86_64
Host machine cpu: x86_64
Program python3 found: YES (c:\program files\python.exe)
WARNING: Broken python installation detected. Python files installed by Meson might not be found by python interpreter.
This warning can be avoided by setting "python.platlibdir" option.
WARNING: Broken python installation detected. Python files installed by Meson might not be found by python interpreter.
This warning can be avoided by setting "python.purelibdir" option.
Program uname found: YES (C:\Program Files (x86)\Montage-tech\Brolga\msys\1.0\bin\uname.EXE)
Compiler for C supports arguments /utf-8: YES
这个过程很慢,耐心等待吧,编译完成之后,发现文件夹目录有些变化。
首先是生成了build目录,其次在subprojects下面,多了许多依赖的插件。我们以FFMPEG为例,去看一下这些插件的目录 :
当前使用的ffmpeg版本是FFmpeg 4.4.1 release。
3. 到build目录ninja编译,或者ninja -C build编译
ninja -C build
编译过程中可能会有些小错误出现,您可以会遇到不同的,需要我们自己手动解决一下:
FAILED: subprojects/glib/glib/libglib-2.0-0.dll.p/gstdio.c.obj
../subprojects/glib/glib/gstdio.c:66:16: error: redefinition of 'struct _REPARSE_DATA_BUFFER'
c:\mingw\include\winnt.h:3586:16: note: originally defined here
typedef struct _REPARSE_DATA_BUFFER
_REPARSE_DATA_BUFFER在winnt.h中也有定义,此处重新定义就冲突了,我们将此处的定义类型名重新修改一下或者直接删除就好,笔者看了winnt.h中关于此类型的定义和此处定义内容一致,因此也没必要保留两份。
FAILED: subprojects/glib/glib/libglib-2.0-0.dll.p/gstdio.c.obj
../subprojects/glib/glib/gstdio.c:225:24: error: implicit declaration of function '_alloca' [-Werror=implicit-function-declaration]
char *drive_letter = _alloca (drive_letter_size);
_alloca函数未定义,_alloca是内存分配函数,但是_alloca是在栈(stack)上申请空间,用完马上就释放。它包含在malloc.h头文件中,有的机器上很难实现,移植性较差。此处我们将malloc.h头文件包含到.c文件即可。报toupper函数未定义时,需要#include
[3/8930] Compiling C object subprojects/glib/glib/libglib-2.0-0.dll.p/giowin32.c.obj
../subprojects/glib/glib/giowin32.c:1791:118: error: 'ERROR_BROKEN_PIPE' undeclared (first use in this function)
ERROR_BROKEN_PIPE未定义,这是win32 api的错误码,定义在winerror.h中。FSCTL_GET_REPARSE_POINT未定义,这包含在winioctl.h中。
比较容易搜索到的错误我就不再列举了,有些编译不过的是test文档,我们并不需要,注释掉就好了,还有一些功能,可能我们永远也用不到,所以也可以注释掉。笔者使用的VS版本是VS 2019社区版。笔者试过2010,2017的环境编译都有比较多的困难,用bash,MSYS2编译出错也会让你修改得怀疑人生。只有x64 Native Tools Command Prompt for VS 2019这个工具是最适合的,错误也相对较少。工具的选择我的建议是到GStreamer下载的官方网站,选用官方发布文件所用的编译工具Download GStreamerhttps://gstreamer.freedesktop.org/download/#windows比如如下Release版本对应的开发工具就是vs 2019:
官方给出的编译教程如下:
Building from source using Meson
编译成功 了咱们看一下:
F:\Source\OpenSource\gstreamer\build>ninja
ninja: no work to do.
再次输入ninja没有需要重新编译的文件。如果有文件重新修改后需要编译,只需要ninja即可,无需再次进行meson。接下来修改一下build.ninja文件,把编译好的文件放到指定位置以便我们引用,找到build meson-install: CUSTOM_COMMAND PHONY | all这句,增加一个--destdir选项:
build meson-install: CUSTOM_COMMAND PHONY | all
DESC = Installing$ files.
COMMAND = "C:\Python38\Scripts\meson" "install" "--no-rebuild" --destdir "C:\gstreamer"
pool = console
可以使用ninja -C build devenv在编译的时候就修改环境变量,以便您也可以直接使用刚刚编译出来的工具。
4. ninja install
ninja install
安装完后的目录像这个样子:
GStreamer中的宏非常多,理论上讲,Windows编译时,在build.ninja文件的$ARGS参数里面加上/P /C参数就可以将文件里面的宏展开,生成临时文件。但由于GStreamer的依赖非常多,这些依赖的编码字符集也不一样,在宏展开时就常常会出现问题,导致加上/P /C参数的后代码编译不过。这里提供一种技巧示例,如我们想看gst_object_get_type ()这个函数的定义,这个函数就是用宏定义的,定义如下:
G_DEFINE_TYPE_WITH_CODE (GstPad, gst_pad, GST_TYPE_OBJECT, G_ADD_PRIVATE (GstPad) _do_init);
我们将这个定义复制到.c文件,命名为test.c,然后文件内容如下:
#include
#include
G_DEFINE_TYPE_WITH_CODE (GstPad, gst_pad, GST_TYPE_OBJECT, G_ADD_PRIVATE (GstPad) _do_init);
在x64 Native Tools Command Prompt for VS 2019环境使用如下命令编译
"cl" "-IC:\gstreamer\include" "-IC:\gstreamer\include\gstreamer-1.0" "-IC:\gstreamer\include\glib-2.0" "-IC:\gstreamer\lib\glib-2.0\include" /P /C test.c
这样,就会生成test.i中间文件,展开的结果如下所示:
static void gst_pad_init(GstPad *self);
static void gst_pad_class_init(GstPadClass *klass);
static GType gst_pad_get_type_once(void);
static gpointer gst_pad_parent_class = ((void *)0);
static gint GstPad_private_offset;
static void gst_pad_class_intern_init(gpointer klass)
{
gst_pad_parent_class = g_type_class_peek_parent(klass);
if (GstPad_private_offset != 0) {
g_type_class_adjust_private_offset(klass, &GstPad_private_offset);
}
gst_pad_class_init((GstPadClass *) klass);
}
static inline gpointer gst_pad_get_instance_private(GstPad *self)
{
return (((gpointer)((guint8 *)(self) + (glong)(GstPad_private_offset))));
}
GType gst_pad_get_type(void)
{
static gsize static_g_define_type_id = 0;
if ((g_once_init_enter((&static_g_define_type_id)))) {
GType g_define_type_id = gst_pad_get_type_once();
(g_once_init_leave((&static_g_define_type_id), (gsize)(g_define_type_id)));
}
return static_g_define_type_id;
}
static GType gst_pad_get_type_once(void)
{
GType g_define_type_id = g_type_register_static_simple(
(gst_object_get_type()), g_intern_static_string("GstPad"), sizeof(GstPadClass),
(GClassInitFunc)(void (*)(void)) gst_pad_class_intern_init, sizeof(GstPad),
(GInstanceInitFunc)(void (*)(void)) gst_pad_init, (GTypeFlags) 0);
{
{
{
GstPad_private_offset = g_type_add_instance_private(g_define_type_id, sizeof(GstPadPrivate));
}
_do_init;
}
}
return g_define_type_id;
};
这样我们就能够明白gst_pad_get_type是怎么回事了,它是一外部函数,在gst_init时被调用。您也可以使用如下方式来展开文件的宏:
"cl" "-IC:\gstreamer\include" "-IC:\gstreamer\include\gstreamer-1.0" "-IC:\gstreamer\include\glib-2.0" "-IC:\gstreamer\lib\glib-2.0\include" "-IC:\gstreamer\include\gstreamer-1.0\gst" "-IC:\gstreamer\include\gstreamer-1.0\gst" /P /C f:\source\opensource\gstreamer\gstreamer\subprojects\gstreamer\gst\Gstelement.c
在VS的调试UI,您也可以将宏复制到.c文件中,用鼠标指针指向它,VS会自动将其展开
也可以只针对单个文件展开宏,比如展开x509_vfy.c中的宏,可以这样修改build.ninja这个文件:
使用GStreamer之前,需要将C的头文件.h和需要的库文件.lib包含到我们的环境中:
3. 增加依赖库
4.设置运行环境,将GStreamer运行时需要的.dll文件库路径配置到环境中。
5.代码编写,使用的代码可参考:Basic tutorial 1: Hello world! 笔者的代码如下:
#include
int main(int argc, char* argv[])
{
/* Initialize GStreamer */
gst_init(&argc, &argv);
g_printf("GStreamer Say Hello Word!");
return 0;
}
6.运行结果
至此,GStreamer下载,编译链接成功,成功产生出第一个测试用例,后续部分陆续完成中,待整理好后一并奉上。本文实操过程中,也是屡屡踩坑,希望广大从来朋友以此为鉴,少走弯路。
gst_debug_set_default_threshold(level)函数设计日志的默认调试级别
gst_debug_remove_log_function(func)去掉默认的日志打印函数
gst_debug_add_log_function(func,data,notify)添加新的日志打印函数
gst_debug_set_threshold_for_name(name,level)设置单个模块的调试级别
日志相关函数定义在subprojects\gstreamer\gst\gstinfo.h中,GST_DISABLE_GST_DEBUG作为调试开关,定义中subprojects/gstreamer/gst/gstconfig.h.in中,有的时候,调试日志如GST_MEMDUMP等等会点用大量CPU时间,所以Gstreamer相关软件版本发布的时候,最好关掉这类日志,来看一个例子:
gst_debug_set_active是总开关,设置以后,调试信息才会发送到调试信息处理函数,否则不会发送任何调试信息,在嵌入式平台建议不要打开这个调试信息。
如果调试过程中遇到inline函数或者宏不能断住,可以在gstinfo.c中可以找出void gst_debug_print_stack_trace (void)和gchar *gst_debug_get_stack_trace (GstStackTraceFlags flags)函数,用来打印调用栈信息。
用同一包代码,Linux编译相对简单,同样的meson build,可能会出现几个小错误:
meson.build:1:0: ERROR: wrap-redirect /home/xxx/gstreamer/subprojects/harfbuzz\subprojects\libpng.wrap filename does not exist
meson.build:1:0: ERROR: wrap-redirect /home/xxx/gstreamer/subprojects/fontconfig\subprojects\gperf.wrap filename does not exist
meson.build:1:0: ERROR: wrap-redirect /home/xxx/gstreamer/subprojects/openh264\subprojects\gtest.wrap filename does not exist
meson.build:1:0: ERROR: wrap-redirect /home/xxx/gstreamer/subprojects/pango\subprojects\gi-docgen.wrap filename does not exist
打开gstreamer/subprojects/libpng.wrap,gstreamer/subprojects/gperf.wrap,gstreamer/subprojects/gtest.wrap,gstreamer/subprojects/gi-docgen.wrap文件,将路径的"\"符号修改成"/"就好。
然后ninja -C build,一路顺序编译结束,未遇到编译错误。