Using Gstreamer in your Flutter project Pt.3: Windows

In the previous tutorial, we successfully developed a Flutter FFI plugin that compiles into a library, enabling us to generate a sine wave using GStreamer. We also created a Flutter application that calls the functions from this library. In this tutorial, we will focus on adapting this application to run on Windows.
This guide is tailored specifically for Windows, and all instructions will be provided accordingly.

Prerequisites

Before you begin, make sure you have the following foundational knowledge:

  • Basic understanding of the C programming language.

  • Familiarity with Dart/Flutter.

  • Basic knowledge of CMake.

  • Basic knowledge of GStreamer.

Required Tools and Software

For this tutorial, we will demonstrate on a Windows PC. The following tools and software are necessary:

  • GStreamer Libraries: Install GStreamer and its development libraries. Follow the GStreamer documentation for installation instructions.

  • Code Editor: You can use any code editor, but we will use Visual Studio Code (VSCode).

  • Flutter: Install Flutter by following the guidelines on the official Flutter website.

  • Visual Studio: Required for Flutter for Windows development. Download it from the official Visual Studio website.

  • CMake: Download from cmake.org. You can also install it through the Visual Studio installer on Windows.

Installing Dependencies

To proceed, we need to install GStreamer and other dependencies. We will use Chocolatey, a package manager for Windows. Open a PowerShell window with administrative privileges and run the following command to install Chocolatey:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

After Chocolatey is installed, run the following command to install GStreamer and Git:

choco install git gstreamer gstreamer-devel

This command will install the GStreamer runtime libraries as well as the development libraries. Note that we are also installing Git for version control.

Running the App Without Modifications

First, open the app project in Visual Studio Code. Try running it, and you will likely encounter several errors leading to a build failure. To diagnose the issue, build the project in verbose mode by running:

flutter build windows -vvv --debug

You will see errors similar to the following:

[  +78 ms] LINK : warning LNK4044: unrecognized option '/LC:/gstreamer/1.0/msvc_x86_64/lib'; ignored
[C:\Users\username\Projects\flutter_gst\app\flutter_gst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]
[   +4 ms] LINK : warning LNK4044: unrecognized option '/lgstreamer-1.0'; ignored
[C:\Users\username\Projects\flutter_gst\app\flutter_gst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]
...
[   +1 ms] native_binding.obj : error LNK2019: unresolved external symbol g_thread_new, referenced in function run_mainloop
[C:\Users\username\Projects\flutter_gst\app\flutter_gst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]
...

Debugging the Build Failure

The error messages indicate that the build is failing due to unrecognized options or unresolved symbols. One notable error is:

[        ]   C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.40.33807\bin\HostX64\x64\link.exe /ERRORREPORT:QUEUE
/OUT:"C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\Debug\native_binding.dll" /INCREMENTAL
/ILK:"native_binding.dir\Debug\native_binding.ilk" /NOLOGO "-LC:/gstreamer/1.0/msvc_x86_64/lib" "-lgstreamer-1.0" "-lgobject-2.0"
"-lglib-2.0" "-lintl" kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib
/MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG
/PDB:"C:/Users/fengj/Projects/fltgst/app/fltgst/build/windows/x64/plugins/native_binding/shared/Debug/native_binding.pdb"
/SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT
/IMPLIB:"C:/Users/fengj/Projects/fltgst/app/fltgst/build/windows/x64/plugins/native_binding/shared/Debug/native_binding.lib" /MACHINE:X64
/machine:x64 /DLL native_binding.dir\Debug\native_binding.obj

And we can see from the warning when linking the library LINK : warning LNK4044: 无法识别的选项“/LC:/gstreamer/1.0/msvc_x86_64/lib”;已忽略, this error translates to "unrecognized option '/LC:/gstreamer/1.0/msvc_x86_64/lib'; ignored." This suggests that the pkg-config output is using Unix-style options, which are not recognized by the MSVC linker on Windows.

Modifying CMakeLists.txt for Windows

To resolve these issues, we need to modify the CMakeLists.txt to handle the options correctly for the MSVC environment. Follow these steps:

  1. Open CMakeLists.txt in the native_binding/src directory.

  2. Modify the script to use pkg-config with MSVC syntax. Add the following code after pkg_search_module(GST REQUIRED gstreamer-1.0):

    IF(WIN32)
      find_program(CMAKE_PKGCONFIG_EXECUTABLE pkg-config)
      IF(CMAKE_PKGCONFIG_EXECUTABLE)
      # pkg-config.exe gstreamer-1.0 --libs --msvc-syntax
        EXEC_PROGRAM(${CMAKE_PKGCONFIG_EXECUTABLE}
        ARGS " --libs --msvc-syntax gstreamer-1.0"
        OUTPUT_VARIABLE GST_LDFLAGS)
        message("GST_LDFLAGS: ${GST_LDFLAGS}")
      ENDIF()
    ENDIF()

    Update the linking settings in CMakeLists.txt to use the generated GST_LDFLAGS. Replace the existing target_link_libraries line with the following:
    IF(WIN32)
      target_link_options(native_binding PRIVATE ${GST_LDFLAGS})
    ELSE()
      target_link_libraries(native_binding PRIVATE ${GST_LDFLAGS})
    ENDIF()

    Then we update the dependencies in app project, and then build it again with flutter pub get and then flutter build windows -vvv --debug.

And we still got error when building:

[  +88 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_thread_new,函数 run_mainloop 中引用了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +2 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_main_loop_new,函数 setup_pipeline 中引用了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_main_loop_run,函数 run_main_loop_thread 中引用了该符

[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_main_loop_unref,函数 run_mainloop 中引用了该符号    
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_main_loop_is_running,函数 run_mainloop 中引用了该符 

[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_printerr,函数 setup_pipeline 中引用了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +4 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 g_type_check_instance_cast,函数 setup_pipeline 中引用 
了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[  +13 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_object_unref,函数 setup_pipeline 中引用了该
符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +3 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_element_factory_make,函数 setup_pipeline 中 
引用了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_element_set_state,函数 start_pipeline 中引用
了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_bin_get_type,函数 setup_pipeline 中引用了该 
符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +3 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_pipeline_new,函数 setup_pipeline 中引用了该 
符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +2 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_element_link_many,函数 setup_pipeline 中引用
了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +2 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_bin_add_many,函数 setup_pipeline 中引用了该
符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +4 ms] native_binding.obj : error LNK2019: 无法解析的外部符号 __imp_gst_init,函数 init 中引用了该符号
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]       
[   +1 ms]
C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\Debug\native_binding.dll :     
fatal error LNK1120: 15 个无法解析的外部命令
[C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\native_binding.vcxproj]      

And again, few lines above, we find the command that caused the error:

[   +1 ms]   C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.40.33807\bin\HostX64\x64\link.exe
/ERRORREPORT:QUEUE
/OUT:"C:\Users\fengj\Projects\fltgst\app\fltgst\build\windows\x64\plugins\native_binding\shared\Debug\native_binding.dll"/INCREMENTAL /ILK:"native_binding.dir\Debug\native_binding.ilk" /NOLOGO kernel32.lib user32.lib gdi32.lib winspool.lib   
shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker'
uiAccess='false'" /manifest:embed /DEBUG
/PDB:"C:/Users/fengj/Projects/fltgst/app/fltgst/build/windows/x64/plugins/native_binding/shared/Debug/native_binding.pdb"/SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT
/IMPLIB:"C:/Users/fengj/Projects/fltgst/app/fltgst/build/windows/x64/plugins/native_binding/shared/Debug/native_binding.lib" /MACHINE:X64  /machine:x64 "/libpath:C:/gstreamer/1.0/msvc_x86_64/lib gstreamer-1.0.lib gobject-2.0.lib glib-2.0.lib 
intl.lib" /DLL native_binding.dir\Debug\native_binding.obj

Pay attention to the last two argument, you will see a double quote, and when you find the start of that double quote, it's from the GST_LDFLAGS we generated from pkg-config! And my guess is, the double quote makes the linker thinks the entire object inside the double quote is a single option, which is not what we want, so we need to find a way to get rid of the double quote.
Luckly, microsoft has drop some hint on what we can do:
https://learn.microsoft.com/en-us/cpp/build/reference/linking?view=msvc-170#link-environment-variables :

The LIB variable can contain one or more path specifications, separated by semicolons. One path must point to the \lib subdirectory of your Visual C++ installation.

So we replace the white space with a semicolon.
Head back to native_binding/lib/CMakeListst.txt, and add some text to the first IF(WIN32) block, so it look like this:

...
IF(WIN32)
  find_program(CMAKE_PKGCONFIG_EXECUTABLE pkg-config)
  IF(CMAKE_PKGCONFIG_EXECUTABLE)
  # pkg-config.exe gstreamer-1.0 --libs --msvc-syntax
    EXEC_PROGRAM(${CMAKE_PKGCONFIG_EXECUTABLE}
    ARGS " --libs --msvc-syntax gstreamer-1.0"
    OUTPUT_VARIABLE GST_LDFLAGS)
    # replace spaces with semicolons so that we don't have quotation marks in command line option
    string(REPLACE " " ";" GST_LDFLAGS ${GST_LDFLAGS})
    message("GST_LDFLAGS: ${GST_LDFLAGS}")
  ENDIF()
ENDIF()
...

Rebuilding the Project

After making the changes, update the dependencies in your app project and rebuild it:

  1. Update the dependencies:

    flutter pub get

  2. Build the project:

    flutter build windows -vvv --debug

And the build success:

[  +40 ms] Building Windows application... (completed in 15.6s)
[   +5 ms]Built build\windows\x64\runner\Debug\fltgst.exe
[   +2 ms] "flutter windows" took 16,516ms.
[ +274 ms] ensureAnalyticsSent: 267ms
[   +1 ms] Running 0 shutdown hooks
[        ] Shutdown hooks complete
[        ] exiting with code 0

Run the app, and you should hear the sine wave.

Debugging

Just like before, but add the following too configurations in .vscode/launch.json file:

        {
            "name":"Windows native",
            "type":"cppvsdbg",
            "request": "launch",
            "program": "${workspaceRoot}/build/windows/runner/Debug/fltgst.exe",
            "cwd": "${fileDirname}",
        },

Conclusion

By following this tutorial, you have adapted a Flutter FFI plugin to work on a Windows platform, including handling the specific requirements of the MSVC linker. Ensure you follow each step carefully to resolve any build issues, and consult the GStreamer and Flutter documentation for further troubleshooting tips. Happy coding!

GitHub Repository

Find the repository and files updated in this tutorial at: