从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发

基于Windows的GStreamer从源码下载、编译到开发

        本文介绍了在GStreamer下载方法, 使用过程中的部分依赖,以及在Windows上编译配置GStreamer 过程,为学习GStreamer 创建条件。

 GStreamer 代码下载

  1. Sign up · GitLabfreedesktop.org GitLab loginhttps://gitlab.freedesktop.org/users/sign_up上注册一个用户。
  2. 在PC机上用ssh-keygen -t rsa -C "[email protected]"生成id_rsa.pub值,把这个值贴到下面Key框里,如果这一步不会,请搜索参考 ssh-keygen的用法。从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第1张图片
  3. git clone [email protected]:gstreamer/gstreamer.git  或 git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git 下载源码。下载下来的代码是整个工程,目录结构如下:​

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第2张图片

相关基础知识

一、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,换一种思维,不用编译成动态库,编译成静态库或许也可以。 有时候很多函数的声明或者定义在宏当中实现,不易查找位置,可以在编译的时候,重新对此函数进行另外一种定义,编译器报错并报告之前定义或者声明的位置,就可以找到函数原型了。

GStreamer源码Windows编译:

    本人的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编译脚本所在的目录。

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第3张图片

 创建编译目录

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 

        这个过程很慢,耐心等待吧,编译完成之后,发现文件夹目录有些变化。

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第4张图片

        首先是生成了build目录,其次在subprojects下面,多了许多依赖的插件。我们以FFMPEG为例,去看一下这些插件的目录 :

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第5张图片

         它包括了整包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:

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第6张图片

     官方给出的编译教程如下:

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的GStreamer从源码下载、编译到开发_第7张图片

展开GStreamer中的宏

        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会自动将其展开从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第8张图片

        也可以只针对单个文件展开宏,比如展开x509_vfy.c中的宏,可以这样修改build.ninja这个文件:

GStreamer之HelloWord

         使用GStreamer之前,需要将C的头文件.h和需要的库文件.lib包含到我们的环境中:

1. 包含头文件路径从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第9张图片

2. 包含库文件路径从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第10张图片

 3. 增加依赖库

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第11张图片

4.设置运行环境,将GStreamer运行时需要的.dll文件库路径配置到环境中。从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第12张图片

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相关软件版本发布的时候,最好关掉这类日志,来看一个例子:

从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第13张图片

         gst_debug_set_active是总开关,设置以后,调试信息才会发送到调试信息处理函数,否则不会发送任何调试信息,在嵌入式平台建议不要打开这个调试信息。从零开始成为GStreamer专家——基于Windows的GStreamer从源码下载、编译到开发_第14张图片

        如果调试过程中遇到inline函数或者宏不能断住,可以在gstinfo.c中可以找出void gst_debug_print_stack_trace (void)和gchar *gst_debug_get_stack_trace (GstStackTraceFlags flags)函数,用来打印调用栈信息。

Linux上编译

        用同一包代码,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,一路顺序编译结束,未遇到编译错误。

你可能感兴趣的:(音视频,c语言,c++)