wkt 发布的文章

ssh端口映射

假设本机局域网地址:lh_ip,远程公共网服务器地址:rh_ip。

1

将本地的某个端口lport映射到远程主机的某个端口rport上

ssh remote_user@rh_ip -L lport:remote_host:rport

remote_host==127.0.0.1则表示远程主机rh_ip.
这样就可以通过访问本机lh_ip的lport, 来访问远程主机remote_host的rport端口.

2

将远程主机的某个端口rport映射到本地的某个端口lpor上

ssh user@rh_ip -R rport:local_host:lport

这样就可以通过访问rh_ip的rport端口, 来访问local_host的lport端口.
默认情况下rport仅bind到127.0.0.1上,如果需要端口bind到所有地址,
则需要在/etc/ssh/sshd_config中,添加:

GatewayPorts yes

3

保持连接

ssh user@rh_ip -o "ServerAliveInterval 30"

每30秒向服务器发送一次响应请求.

4

-N 不使用Shell交互, 仅用转发可用这个参数

ssh -N user@rh_ip 

5

自动接受SSH密钥指纹

ssh user@rh_ip -o "StrictHostKeyChecking=no"

Gtk应用内嵌网页与原生代码交互方法

在使用Gtk开发应用程序的过程中,如果需要内嵌网页,那么使用libwebkit2gtk是个非常自然和正确的选择。那么这里就可能原生程序代码可能需要跟网页交互的问题。

Gtk程序跟网页的交互,主要有两个方面:

1 Gtk程序需要调用网页js代码
2 网页需要调用Gtk程序的功能代码

需求1,使用webkit2gtk的内置webkit_web_view_run_javascript函数即可解决

需求2,使用webkit2gtk的内置的web extendsion扩展支持功能解决 或 window.webkit.messageHandlers..postMessage(value)

不多说看代码吧!

Gtk嵌入网页Demo程序

webviewgtk.c


/**
*
* Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved.
* @Time : 2021-04-04 12:18
* @Last Modified: 2022-12-10 17:27
* @File : webviewgtk.c
* @Description :
*
* 依赖下载:
* sudo apt install libwebkit2gtk-4.0-doc libwebkit2gtk-4.0-dev libgtk-3-dev
* gcc webviewgtk.c -o webviewgtk -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.0`
*
*/


#include <gtk/gtk.h>
#include <glib.h>
#include <webkit2/webkit2.h>

#include <sys/types.h>
#include <unistd.h>

static void
web_view_javascript_finished (GObject      *object,
                              GAsyncResult *result,
                              gpointer      user_data)
{
    WebKitJavascriptResult *js_result;
    JSCValue               *value;
    GError                 *error = NULL;

    js_result = webkit_web_view_run_javascript_finish (WEBKIT_WEB_VIEW (object), result, &error);
    if (!js_result) {
        g_warning ("Error running javascript: %s", error->message);
        g_error_free (error);
        return;
    }

    value = webkit_javascript_result_get_js_value (js_result);
    if (jsc_value_is_string (value)) {
        JSCException *exception;
        gchar        *str_value;

        str_value = jsc_value_to_string (value);
        exception = jsc_context_get_exception (jsc_value_get_context (value));
        if (exception)
            g_warning ("Error running javascript: %s", jsc_exception_get_message (exception));
        else
            g_print ("Script result: %s\n", str_value);
        g_free (str_value);
    } else {
        g_warning ("Error running javascript: unexpected return value");
    }
    webkit_javascript_result_unref (js_result);
}

static gboolean
on_webview_load_failed(WebKitWebView  *webview,
               WebKitLoadEvent load_event,
               gchar          *failing_uri,
               GError         *error,
               gpointer        user_data)
{
    g_printerr("%s: %s\n",failing_uri,error->message);
    return FALSE;
}

static void
handle_script_message (
  WebKitUserContentManager* self,
  WebKitJavascriptResult* js_result,
  gpointer user_data)
{
    JSCValue               *value;
    value = webkit_javascript_result_get_js_value (js_result);

    JSCException *exception;
    gchar        *str_value;

    str_value = jsc_value_to_string (value);
    exception = jsc_context_get_exception (jsc_value_get_context (value));
    if (exception)
        g_warning ("Error running javascript: %s", jsc_exception_get_message (exception));
    else
        g_printerr ("Script result: %s\n", str_value);

    g_printerr("%s: %s\n",__func__,str_value);

    g_free (str_value);

}

static void
on_button_clicked(GtkButton *button,
               WebKitWebView *webview)
{
    static gint t = 0;
    gchar buf[128]={0};
    g_snprintf(buf,sizeof(buf)-1,"change_span_id('_n%d')",t);
    t+=1;
    //在webview当前的html页面中直接运行js代码
    webkit_web_view_run_javascript(webview,buf,NULL,web_view_javascript_finished,NULL);
}

static void
webkit_web_extension_initialize (WebKitWebContext *context,
               gpointer          user_data)
{
    g_printerr("%s: %d\n",__FUNCTION__,getpid());
    //设置web extendsion扩张.so文件的搜索目录
    webkit_web_context_set_web_extensions_directory(context,".");
}


/**
** 创建window,添加webkit控件
**
**/
static void
on_activate (GtkApplication *app)
{
    GtkWindow *window;

    g_assert (GTK_IS_APPLICATION (app));

    window = gtk_application_get_active_window (app);
    if (window == NULL)
        window = g_object_new (GTK_TYPE_WINDOW,
                               "application", app,
                               "default-width", 600,
                               "default-height", 300,
                               NULL);

    //注册处理web extensions的初始化函数
    g_signal_connect(webkit_web_context_get_default(),"initialize-web-extensions",G_CALLBACK(webkit_web_extension_initialize),NULL);

    GtkWidget* webview = webkit_web_view_new();
    g_signal_connect (webview, "load-failed", G_CALLBACK (on_webview_load_failed), NULL);


    // 加载网页
    GFile *file = g_file_new_for_path("webview.html");
    gchar *uri = g_file_get_uri(file);
    webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), uri);
    g_free(uri);
    g_object_unref(file);
    
    // 注册 window.webkit.messageHandlers.msgToNative.postMessage(value)
    // 的回调函数
    WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW(webview));
    g_signal_connect (manager, "script-message-received::msgToNative",
                    G_CALLBACK (handle_script_message), NULL);
    webkit_user_content_manager_register_script_message_handler (manager, "msgToNative");

    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL,0);
    GtkWidget *button = gtk_button_new_with_label ("change span");
    gtk_box_pack_start(GTK_BOX(vbox),GTK_WIDGET(webview),TRUE,TRUE,0);
    gtk_box_pack_start(GTK_BOX(vbox),GTK_WIDGET(button),FALSE,TRUE,0);
    
    gtk_container_add(GTK_CONTAINER(window),GTK_WIDGET(vbox));
    gtk_widget_show_all(GTK_WIDGET(vbox));
    gtk_window_present (window);
    
    g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), webview);

}

int
main (int   argc,
      char *argv[])
{
    g_autoptr(GtkApplication) app = NULL;
    int ret;

    app = gtk_application_new ("com.weiketing.webkit_webview", G_APPLICATION_FLAGS_NONE);

    g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);

    ret = g_application_run (G_APPLICATION (app), argc, argv);

    return ret;
}

Web Extension Demo

web_exten.c

/**
*
* Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved.
* @Time : 2021-04-04 12:18
* @File : web_exten.c
* @Description :
*
* 依赖下载:
* sudo apt install libwebkit2gtk-4.0-doc libwebkit2gtk-4.0-dev libgtk-3-dev
* gcc  web_exten.c -o libweb_exten.so -shared -Wl,-soname,libweb_exten.so -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.0`
**/

#include <glib.h>
#include <webkit2/webkit-web-extension.h>
#include <sys/types.h>
#include <unistd.h>

static gint
js_app_add(gpointer *first,gint num)
{
    static gint N = 0;
    g_printerr("%s: %p\n",__FUNCTION__,first);
    N += num;
    return N;
}

static void 
window_object_cleared_callback (WebKitScriptWorld *world, 
                                WebKitWebPage     *web_page, 
                                WebKitFrame       *frame, 
                                gpointer           user_data)
{
    JSCContext* jsContext;

    jsContext = webkit_frame_get_js_context_for_script_world (frame, world);

    //添加一个js全局变量gtkValue
    jsc_context_set_value(jsContext,"gtkValue",jsc_value_new_string(jsContext,"__test_js_exten"));
    /* Use JSC API to add the JavaScript code you want */
    
    //注册一个名为NativeTest的js类
    JSCClass* app = jsc_context_register_class(jsContext,"NativeTest",NULL,NULL,NULL); // g_object_new(JSC_TYPE_CLASS,"name","JSApp","context",jsContext,NULL);


    //给JSCClass类添加add方法
    jsc_class_add_method(app,"add",G_CALLBACK(js_app_add),NULL,NULL,G_TYPE_INT,1,G_TYPE_INT,NULL);

    //创建一个obj,作为JSCClass类绑定实例,JSCClass方法回调的第一个参数就是obj
    GObject *obj = g_object_new(G_TYPE_OBJECT,NULL);
    jsc_context_set_value(jsContext,"GtkNative",jsc_value_new_object(jsContext,obj,app));
    g_printerr("%s: %d %p\n",__FUNCTION__,getpid(),obj);
    g_object_unref(obj);
}

G_MODULE_EXPORT void
webkit_web_extension_initialize (WebKitWebExtension *extension)
{
    //web extension的初始化函数
    g_signal_connect (webkit_script_world_get_default (), 
                      "window-object-cleared", 
                      G_CALLBACK (window_object_cleared_callback), 
                      NULL);
}

内嵌的网页示例,webview.html


<!DOCTYPE html>
<html lang="zh">
<!--filename: webview.html -->
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="Content-Language" content="zh-CN">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <meta name="referrer" content="always"/>
    <title>Webkit Webview test</title>
    <script type="text/javascript">
    
    function change_span_id(v=''){
        //alert("test")
        document.getElementById('span_id').innerHTML = 'test'+v+gtkValue
        return v
    }
    
    function native_add(num){
        //调用自定义添加的js接口
        i= GtkNative.add(num)
        document.getElementById('add').innerHTML = i
    }

    function send2Gtk(){
        e = document.getElementById('msg')
        window.webkit.messageHandlers.msgToNative.postMessage(e.value)
    }
    </script>
</head>
<body>
    <span>words for test: </span><span id="span_id"></span>
    <br/>
    <button onclick="change_span_id()">change span id</button>
    <br/>
    <span>Native add: </span><span id='add'></span>
    <br/>
    <button onclick="native_add(2)">Native Add</button>
    <br/>
    <input type="text" id="msg" />
    <br/>
    <button onclick="send2Gtk()">Send to Native</button>
</body>
</html>

Web Extension相比于window.webkit.messageHandlers..postMessage(value)

的优点是有返回值。

示例运行

安装依赖:

sudo apt install libwebkit2gtk-4.0-doc libwebkit2gtk-4.0-dev libgtk-3-dev

把webviewgtk.c,web_exten.c, webview.html放在同一目录下。

编译程序:


gcc  web_exten.c -o libweb_exten.so -shared -Wl,-soname,libweb_exten.so -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.0`
gcc webviewgtk.c -o webviewgtk -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.0`

运行程序:


./webviewgtk


Ubuntu 22.10 PipeWire 无声音的解决方法

Ubuntu 22.10默认使用PipeWire作为声音服务器,替换之前的PulseAudio。

PipeWire是个新东西,算是刚刚开始较大范围使用,结果就是表现的不太行。

至少在我自己的ThinkPad X1上就没有声音。解决的办法也很简单就用PulseAudio。

具体操作:

1 确保 PulseAudio 已安装

sudo apt install --yes pulseaudio pulseaudio-module-bluetooth pulseaudio-utils gstreamer1.0-pulseaudio

2 禁用 PipeWire

因为很多桌面系统组件都有依赖PipeWire,卸载PipeWire是个问题,所以就不卸载了。

想办法不让TA起来就可以啦。

具体操作:


systemctl --user disable pipewire{,-pulse}.service
systemctl --user stop pipewire{,-pulse}.{socket,service}
tmpdr=/usr/lib/systemd/user/backups
sudo mkdir -p ${tmpdr}
sudo mv /usr/lib/systemd/user/pipewire* ${tmpdr}

3 启用 PulseAudio

systemctl --user enable pulseaudio.service
systemctl --user start pulseaudio

结束语

经过上述操作,Ubuntu 22.10就能正常出声音了

虽然PipeWire相比PulseAudio是新技术,但还没有经过大量机器和时间的考验,

预计还得经过很长时间才能做到安装完成后立即可用。

Android App 自带自签名证书CA

我们都知道自签名https证书默认是不被系统信任的。
所以访问使用自签名证书的网站,默认一般都是终止访问,提示错误。
面对自签名证书导致的错误提示,我们可以:

1、无视继续;  
2、用户手动导入自签名证书CA(Certificate Authority)进系统并信任证书;  
3、应用自带自签名证书;  

本文主要说一下Android App自带自签名证书CA一些做法。

常规App

这里常规App是指使用Android SDK开发,或者基于Android SDK API的框架开发的App。

网络安全配置文件

在Manifest.xml的application添加android:networkSecurityConfig,如:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest ... >
        <application android:networkSecurityConfig="@xml/network_security_config"
        ...
        >
            ...
        </application>
    </manifest>

添加文件res/xml/network_security_config.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <base-config>
            <trust-anchors>
                <certificates src="@raw/extra_cas"/>
                <certificates src="system"/>
            </trust-anchors>
        </base-config>
    </network-security-config>

自签证书的CA,就写进文件res/raw/extra_cas(可以放多个):

-----BEGIN CERTIFICATE-----
Content Of CA1 .....
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Content Of CA2 .....
-----END CERTIFICATE-----
.....

适用范围

目前网络安全配置文件,仅适用Android 7.0+。
测试有效的API:WebView,URLConnection,okhttp
无效的API:Flutter HttpClient

Flutter

网络安全配置文件对Flutter开发的App是无效的。需要另想办法。

纯fluttr项目

在项目源码目录下,创建assets目录,然后CA保存到文件asset/extra_cas。
然后添加代码如下:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  static Object ca = "";

  @override
  Widget build(BuildContext context) {
    if (ca == "")ca = setupRootCA();
    ......
  }

  static Object setupRootCA (){
    var ft = rootBundle.load('assets/extra_cas');
    ft.then((ByteData value) {
      SecurityContext.defaultContext.setTrustedCertificatesBytes(value.buffer.asUint8List());
      return null;
    });
    return Object();
  }

......
}

原先使用HttpClient的代码,无需任何修改就可以支持使用自签名证书的https链接了。

混合项目

原生代码和flutter混合的项目,如果不介意在apk放两个相同CAs文件,
其实很简单的(flutter和原生用不同文件)。
那么如果我们想项目就放一份CAs文件呢?假设就用res/raw/extra_cas这个文件,还是有办法的。
具体做方法:

原生侧

网络安全配置还是照样搞
在Flutter入口Activity,添加代码:

public class MainActivity extends FlutterActivity {

......

    //读取CAs文件的内容
    protected byte[] getTrustedCA(){
        InputStream in = getResources().openRawResource(R.raw.extra_cas);
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        byte[] buf = new byte[4096];
        int n = 0;
        while (true){
            try {
                if ((n = in.read(buf))<=0) break;
                bao.write(buf,0,n);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bao.toByteArray();
    }

    //对接Flutter的调用
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), "flutter.app.share")
                .setMethodCallHandler(
                        (call, result) -> {
                            if (call.method.contentEquals("getTrustedCA")) {
                                result.success(getTrustedCA());
                            }
                        }
                );
    }

......

}

Flutter侧

修改之前的方法setupRootCA:

  static Object setupRootCA ()
  {
    Future ft = const MethodChannel('flutter.app.share').invokeMethod('getTrustedCA');
    ft.then((value)  {
      Uint8List ca = value;
      SecurityContext.defaultContext.setTrustedCertificatesBytes(ca);
      return null;
    });
    return Object();
  }

恶心的flutter

https的证书一般都给出自己有效的域名和IP。创建证书时一般域名和IP存不同字段名称的。
但是flutter似乎不验证IP字段的,所以使用IP访问会出现hostname不匹配的问题。
这事搞得我曾经怀疑人生,IP明明已经列进证书,Androd原生代码、PC浏览器、curl都没问题。
flutter就是不行,最后通过把IP列进域名字段就正常了。不按套路出牌,也不说明,真是累死人。

参考链接

Create your own Certificate Authority (CA) using OpenSSL

使用Openssl生成自签证书

Network security configuration

HttpClient

SecurityContext

一般想要逐帧处理视频图像,可以用opencv,代码可以如下:

#!/usr/bin/env python
# _*_ coding: utf-8 _*_
#
#
# Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved.
# @Time : 2020-06-26 11:51 
# @File : test_opencv.py
# @Description :
#
#

import sys
import cv2
import numpy as np
import os

f = sys.argv[1]
cap = cv2.VideoCapture(sys.argv[1])
fps = cap.get(cv2.CAP_PROP_FPS)
# create a window named `image`
cv2.imshow("image", np.random.randint(0, 255, (64, 64, 3), dtype='uint8'))
# set window's title
cv2.setWindowTitle("image", os.path.basename(f))
while True:
    ret, frame = cap.read()
    if ret is False:
        break
    cv2.imshow("image", frame)
    # 控制帧率
    cv2.waitKey(int(1000/fps))

运行:
       python3 test_opencv.py path_of_the_video.webm

不过使用opencv不会播放声音,当逐帧处理图像的同时还需要播放声音时,可以考虑使用gstreamer。
python3代码:
 

#!/usr/bin/env python3
# _*_ coding: utf-8 _*_
#
#
# Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved.
# @Time : 2020-06-26 10:26
# @File : test_gst.py
# @Description :
#
#

import os
import sys

import gi

gi.require_version('Gst', '1.0')
gi.require_version("GstVideo", "1.0")
from gi.repository import Gst, GLib, GstVideo
import numpy as np
import cv2


# noinspection PyMethodMayBeStatic
class GstDemo:

    def __init__(self):
        self.frame_idx = 0
        self.player = Gst.ElementFactory.make("playbin", "player")
        appsink = Gst.ElementFactory.make("appsink", "sink")
        appsink.set_property("emit-signals", True)  # 发送new-sample信号

        caps = Gst.Caps.new_empty_simple("video/x-raw")
        caps.set_value('format', "BGRA")  # BGR有点问题,所以不用
        appsink.set_property("caps", caps)  # 设置appsink接受BGRA的数据格式

        appsink.connect("new-sample", self._on_new_sample)

        self.player.set_property("video-sink", appsink)
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.connect("message", self.on_message)
        self.loop = GLib.MainLoop()

    def _on_new_sample(self, sink):
        """
        读取并处理视频帧图像
        :param sink:
        :return:
        """
        self.frame_idx += 1
        sample = sink.emit('pull_sample')
        if sample is None:
            return Gst.FlowReturn.EOS
        caps = sample.get_caps()
        buffer = sample.get_buffer()
        structure = caps.get_structure(0)
        _, width = structure.get_int("width")
        _, height = structure.get_int("height")
        success, map_info = buffer.map(Gst.MapFlags.READ)
        if self.frame_idx == 1:
            print("caps: ", caps.to_string(), "size:", map_info.size)
        img = np.ndarray(
            shape=(height, width, 4),  # BGRA 是4通道
            dtype=np.uint8,
            buffer=map_info.data)
        img = img[:, :, :3]  # 放弃Alpha通道

        # 在主线程调用cv2.imshow,否则crash
        GLib.idle_add(self._idle_show_image, img)
        os.makedirs("/tmp/cv", exist_ok=True)
        cv2.imwrite("/tmp/cv/{:06d}.jpg".format(self.frame_idx), img)
        return Gst.FlowReturn.OK

    def _idle_show_image(self, image):
        cv2.imshow("test", image)
        return GLib.SOURCE_REMOVE

    def on_message(self, bus, message):
        t = message.type
        if t == Gst.MessageType.EOS:
            self.player.set_state(Gst.State.NULL)
            self.loop.quit()
        elif t == Gst.MessageType.ERROR:
            self.player.set_state(Gst.State.NULL)
            err, debug = message.parse_error()
            print("Error: %s" % err, debug)
            self.loop.quit()

    def start(self):
        for filepath in sys.argv[1:]:
            if os.path.isfile(filepath):
                filepath = os.path.abspath(filepath)
                self.player.set_property("uri", "file://" + filepath)
                self.player.set_state(Gst.State.PLAYING)
                break


Gst.init(None)
demo = GstDemo()
demo.start()
demo.loop.run()

运行测试:

       python3 test_gst.py path_of_the_video.webm

C代码:

/**
*
* Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved.
* @Time : 2020-06-25 09:22
* @File : test_gst1.c
* @Description :
*
**/

#include <gst/gst.h>
#include <gst/app/app.h>
#include <gst/video/video.h>
#include <cairo.h>
#include <stdio.h>
#include <glib/gstdio.h>

#include <gtk/gtk.h>

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define FORMAT "BGRx" /* BGRA */
#else
#define FORMAT "xRGB" /* ARGB */
#endif

static GstFlowReturn
on_new_sample (GstAppSink *appsink, GtkWidget *image)
{
    static gint frame_idx = 0;

    GstSample *sample = gst_app_sink_pull_sample(appsink);
    if(sample == NULL){
        if(gst_app_sink_is_eos(appsink))
            return GST_FLOW_EOS;
    }

    GstBuffer *buffer = gst_sample_get_buffer(sample);
    GstCaps *caps = gst_sample_get_caps(sample);
    GstMapInfo map_info;
    gint width,height;

    gst_buffer_map(buffer,&map_info,GST_MAP_READ);
    GstStructure *structure = gst_caps_get_structure(caps,0);
    gst_structure_get_int(structure,"width",&width);
    gst_structure_get_int(structure,"height",&height);

    frame_idx += 1;
    cairo_format_t format;
    cairo_surface_t *surface;
    format = CAIRO_FORMAT_ARGB32;
    surface = cairo_image_surface_create_for_data (map_info.data,
        format, width, height, cairo_format_stride_for_width(format,width));
    gtk_image_set_from_surface(GTK_IMAGE(image),surface);

    char filename[128] = {0};
    g_mkdir_with_parents("/tmp/pictures",0700);
    snprintf(filename,sizeof(filename),"/tmp/pictures/%06d.png",frame_idx);
    cairo_status_t st = cairo_surface_write_to_png(surface,filename);
    if(st != CAIRO_STATUS_SUCCESS){
        g_printerr("st:%s\n",cairo_status_to_string(st));
    }

    cairo_surface_destroy(surface);
    gst_sample_unref(sample);
    return GST_FLOW_OK;
}

static void
on_bus_message (GstBus    *bus,
               GstMessage *message,
               GstElement *pipeline)
{
    switch(GST_MESSAGE_TYPE(message)){
    case GST_MESSAGE_EOS:
    case GST_MESSAGE_ERROR:
        gst_element_set_state(pipeline,GST_STATE_NULL);
        gtk_main_quit();
        break;
    default:
        break;
    }
}

static void
on_win_destroy(GtkWidget*win,GstElement *pipeline)
{
    gst_element_set_state(pipeline,GST_STATE_NULL);
    gtk_main_quit();
}

int main(int argc, char *argv[]) {
    GstElement *pipeline,*appsink;
    GstCaps *caps;
    GstBus *bus;
    GtkWidget *win,*image;

    gtk_init(&argc,&argv);
    gst_init (&argc, &argv);

    win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    image = gtk_image_new();
    gtk_container_add(GTK_CONTAINER(win),image);
    gtk_widget_show_all(win);
    gtk_window_present(GTK_WINDOW(win));

    gchar *args = g_strdup_printf("playbin uri=file://%s",argv[1]);
    pipeline = gst_parse_launch (args, NULL);
    g_free(args);

    appsink = gst_element_factory_make("appsink","sink");
    caps = gst_caps_new_simple("video/x-raw","format", G_TYPE_STRING, FORMAT, NULL);
    g_object_set(appsink,"emit-signals",TRUE,NULL);
    g_object_set(appsink,"caps",caps,NULL);
    g_signal_connect(appsink,"new-sample",G_CALLBACK(on_new_sample),image);

    g_object_set(pipeline,"video-sink",appsink,NULL);

    bus = gst_element_get_bus (pipeline);
    gst_bus_add_signal_watch(bus);
    g_signal_connect(bus,"message",G_CALLBACK(on_bus_message),pipeline);

    g_signal_connect(win,"destroy",G_CALLBACK(on_win_destroy),pipeline);
    gst_element_set_state (pipeline, GST_STATE_PLAYING);

    gtk_main();
    gst_object_unref (bus);
    gst_object_unref (pipeline);
    return 0;
}

运行测试:

      gcc test_gst1.c -o test_gst1 pkg-config --cflags --libs gstreamer-1.0 gstreamer-app-1.0 gstreamer-video-1.0 cairo gtk+-3.0
     ./test_gst1 path_of_the_video.mp4