当前位置: 首页 > article >正文

GStreamer —— 2.5、Windows下Qt加载GStreamer库后运行 - “教程5:GUI 工具包集成(gtk)“(附:完整源码)

运行效果

在这里插入图片描述

简介

     上一个教程演示了时间管理及seek操作。本教程介绍如何将 GStreamer 集成到图形用户中 接口 (GUI) 工具包,如 GTK+。基本上 GStreamer 负责媒体播放,而 GUI 工具包处理 用户交互。最有趣的部分是那些 库必须进行交互:指示 GStreamer 将视频输出到 GTK+ 窗口并将用户作转发到 GStreamer。特别是,您将学习:

          • 如何告诉 GStreamer 将视频输出到特定窗口 (而不是创建自己的窗口)。

          • 如何使用来自 GStreamer 的信息持续刷新 GUI。

          • 如何从 GStreamer 的多个线程更新 GUI,一个 在大多数 GUI 工具包上禁止作。

          • 一种仅订阅您感兴趣的消息的机制, 而不是收到所有通知。

     我们将使用 GTK+ 工具包构建一个媒体播放器,但这些概念适用于其他 例如,像 Qt 这样的工具包。最小值 了解 GTK+ 将有助于理解这一点 教程。重点是告诉 GStreamer 将视频输出到 我们的选择。一个常见的问题是 GUI 工具包通常只允许对 通过主(或应用程序)线程的图形“小部件”, 而 GStreamer 通常会生成多个线程来处理 不同的任务。从 的 SET SET THE S Ransomware 通常会失败,因为回调在 调用 thread,它不需要是主线程。此问题 可以通过在回调中的 GStreamer 总线上发布消息来解决: 消息将由主线程接收,然后主线程将做出反应 因此。最后,到目前为止,我们已经注册了一个函数,该函数得到了 每次公交车上出现消息时都打电话,这迫使我们 解析每条消息,看看我们是否对此感兴趣。在本教程中 使用不同的方法为每种 消息,因此解析更少,整体代码也更少。

GStreamer相关运行库
INCLUDEPATH += D:/Software/GStreamer/1.0/mingw_x86_64/include/gstreamer-1.0/gst
INCLUDEPATH += D:/Software/GStreamer/1.0/mingw_x86_64/include
INCLUDEPATH += D:/Software/GStreamer/1.0/mingw_x86_64/include/gstreamer-1.0
INCLUDEPATH += D:/Software/GStreamer/1.0/mingw_x86_64/include/glib-2.0
INCLUDEPATH += D:/Software/GStreamer/1.0/mingw_x86_64/lib/glib-2.0/include

LIBS += D:/Software/GStreamer/1.0/mingw_x86_64/lib/gstreamer-1.0.lib
LIBS += D:/Software/GStreamer/1.0/mingw_x86_64/lib/glib-2.0.lib
LIBS += D:/Software/GStreamer/1.0/mingw_x86_64/lib/gobject-2.0.lib

GTK3 相关运行库 - gtk官网下载安装教程

完整源码
#include <string.h>

#include <gtk.h>
#include <gst.h>
#include <gdk.h>


typedef struct _CustomData
{
    GstElement *playbin;            /* playbin元素 */

    GtkWidget *sink_widget;         /* 显示视频的窗口 */
    GtkWidget *slider;              /* 进度条滑块控件 */
    GtkWidget *streams_list;        /* 显示流信息的文本控件 */
    gulong slider_update_signal_id; /* 更新进度滑块信号的ID */

    GstState state;                 /* 管道状态 */
    gint64 duration;                /* 进度持续时间 */
} CustomData;

/* 单击“播放”按钮时调用此函数 */
static void play_cb (GtkButton *button, CustomData *data)
{
    gst_element_set_state (data->playbin, GST_STATE_PLAYING);
}

/* 单击PAUSE按钮时调用此函数 */
static void pause_cb (GtkButton *button, CustomData *data)
{
    gst_element_set_state (data->playbin, GST_STATE_PAUSED);
}

/* 单击STOP按钮时调用此函数 */
static void stop_cb (GtkButton *button, CustomData *data)
{
    gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* 当主窗口关闭时调用此函数 */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data)
{
    stop_cb (NULL, data);
    gtk_main_quit ();
}

/* 当滑块改变其位置时,会调用此函数。我们在这里寻求新的职位。 */
static void slider_cb (GtkRange *range, CustomData *data)
{
    gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
    gst_element_seek_simple (data->playbin, (GstFormat)(GST_FORMAT_TIME), (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), (gint64)(value * GST_SECOND));
}

/* 这将创建组成我们应用程序的所有GTK+小部件,并注册回调 */
static void create_ui (CustomData *data)
{
    /* 主窗口 */
    GtkWidget *main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);

    /* 播放按钮 */
    GtkWidget *play_button = gtk_button_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);

    /* 暂停按钮 */
    GtkWidget *pause_button = gtk_button_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);

    /* 停止按钮 */
    GtkWidget *stop_button = gtk_button_new_from_icon_name ("media-playback-stop", GTK_ICON_SIZE_SMALL_TOOLBAR);
    g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);

    /* 进度条滑块控件 */
    data->slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
    gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
    data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data);

    /* 显示流信息的文本控件 */
    data->streams_list = gtk_text_view_new ();
    gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);

    /* HBox用于按住按钮和滑块 */
    GtkWidget *controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
    gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
    gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
    gtk_box_pack_start (GTK_BOX (controls), data->slider, TRUE, TRUE, 2);

    /* HBox,用于容纳视频接收器和流信息文本小部件 */
    GtkWidget *main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start (GTK_BOX (main_hbox), data->sink_widget, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);

    /* VBox用于容纳main_hbox和控件 */
    GtkWidget *main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
    gtk_container_add (GTK_CONTAINER (main_window), main_box);
    gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);

    /* 显示主窗口 */
    gtk_widget_show_all (main_window);
}

/* 定期调用此函数以刷新GUI */
static gboolean refresh_ui (CustomData *data)
{
    gint64 current = -1;

    /* 除非我们处于暂停或播放状态,否则我们不想更新任何内容 */
    if (data->state < GST_STATE_PAUSED){return TRUE;}

    /* 如果我们还不知道,请查询流持续时间 */
    if (!GST_CLOCK_TIME_IS_VALID (data->duration))
    {
        if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration))
        {
            g_printerr ("Could not query current duration.\n");
        }
        else
        {
            /* 将滑块的范围设置为剪辑持续时间,单位为秒 */
            gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
        }
    }

    if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, &current))
    {
        /* 阻止“值已更改”信号,因此不调用slider_cb函数(这将触发用户未请求的寻道) */
        g_signal_handler_block (data->slider, data->slider_update_signal_id);

        /* 将滑块的位置设置为当前管道位置,单位为秒 */
        gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);

        /* 重新启用信号 */
        g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
    }

    return TRUE;
}

/* 当在流中发现新的元数据时,会调用此函数 */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data)
{
    /* 我们可能处于GStreamer工作线程中,因此我们通过总线中的消息通知主线程此事件 */
    gst_element_post_message (playbin, gst_message_new_application (GST_OBJECT (playbin), gst_structure_new_empty ("tags-changed")));
}

/* 当总线上发布错误消息时,会调用此函数 */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data)
{
    GError *err;
    gchar *debug_info;

    /* 在屏幕上打印错误详细信息 */
    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);

    /* 将管道设置为READY(停止播放) */
    gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* 当总线上发布流结束消息时,会调用此函数。我们只是将管道设置为READY(停止播放) */
static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data)
{
    g_print ("End-Of-Stream reached.\n");
    gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* 当管道状态发生变化时,会调用此函数。我们用它来跟踪当前状态。 */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data)
{
    GstState old_state, new_state, pending_state;
    gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
    if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin))
    {
        data->state = new_state;
        g_print ("State set to %s\n", gst_element_state_get_name (new_state));
        if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
        {
            /* 为了提高响应速度,我们在达到PAUSED状态后立即刷新GUI */
            refresh_ui (data);
        }
    }
}

/* 从所有流中提取元数据并将其写入GUI中的文本小部件 */
static void analyze_streams (CustomData *data)
{
    /* 清理小部件的当前内容 */
    GtkTextBuffer *text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
    gtk_text_buffer_set_text (text, "", -1);

    /* 阅读一些属性 */
    gint n_video, n_audio, n_text;
    g_object_get (data->playbin, "n-video", &n_video, NULL);
    g_object_get (data->playbin, "n-audio", &n_audio, NULL);
    g_object_get (data->playbin, "n-text", &n_text, NULL);

    guint rate;
    gchar *str, *total_str;
    GstTagList *tags;
    for (gint i = 0; i < n_video; i++)
    {
        tags = NULL;
        /* 检索流的视频标签 */
        g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
        if (tags)
        {
            total_str = g_strdup_printf ("video stream %d:\n", i);
            gtk_text_buffer_insert_at_cursor (text, total_str, -1);
            g_free (total_str);
            gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
            total_str = g_strdup_printf ("  codec: %s\n", str ? str : "unknown");
            gtk_text_buffer_insert_at_cursor (text, total_str, -1);
            g_free (total_str);
            g_free (str);
            gst_tag_list_free (tags);
        }
    }

    for (gint i = 0; i < n_audio; i++)
    {
        tags = NULL;
        /* 检索流的音频标签 */
        g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
        if (tags)
        {
            total_str = g_strdup_printf ("\naudio stream %d:\n", i);
            gtk_text_buffer_insert_at_cursor (text, total_str, -1);
            g_free (total_str);
            if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str))
            {
                total_str = g_strdup_printf ("  codec: %s\n", str);
                gtk_text_buffer_insert_at_cursor (text, total_str, -1);
                g_free (total_str);
                g_free (str);
            }
            if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str))
            {
                total_str = g_strdup_printf ("  language: %s\n", str);
                gtk_text_buffer_insert_at_cursor (text, total_str, -1);
                g_free (total_str);
                g_free (str);
            }
            if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate))
            {
                total_str = g_strdup_printf ("  bitrate: %d\n", rate);
                gtk_text_buffer_insert_at_cursor (text, total_str, -1);
                g_free (total_str);
            }
            gst_tag_list_free (tags);
        }
    }

    for (gint i = 0; i < n_text; i++)
    {
        tags = NULL;
        /* 检索流的字幕标签 */
        g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
        if (tags)
        {
            total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
            gtk_text_buffer_insert_at_cursor (text, total_str, -1);
            g_free (total_str);
            if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str))
            {
                total_str = g_strdup_printf ("  language: %s\n", str);
                gtk_text_buffer_insert_at_cursor (text, total_str, -1);
                g_free (total_str);
                g_free (str);
            }
            gst_tag_list_free (tags);
        }
    }
}

/* 当总线上发布“应用程序”消息时,会调用此函数。在这里,我们检索tags_cb回调发出的消息 */
static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data)
{
    if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0)
    {
        /* 如果消息是“标签已更改”(我们目前只发布一个),请更新流信息GUI */
        analyze_streams (data);
    }
}

int main(int argc, char *argv[])
{
    CustomData data;
    memset (&data, 0, sizeof (data));
    data.duration = GST_CLOCK_TIME_NONE;

    /* 初始化GTK */
    gtk_init (&argc, &argv);

    /* 初始化GStreamer */
    gst_init (&argc, &argv);

    /* 创建元素 */
    data.playbin = gst_element_factory_make ("playbin", "playbin");
    GstElement *videosink = gst_element_factory_make ("glsinkbin", "glsinkbin");
    GstElement *gtkglsink = gst_element_factory_make ("gtkglsink", "gtkglsink");

    /* 创建了GTK Sink元素,它将为我们提供一个GTK小部件,GStreamer将在其中渲染视频,我们可以将其添加到UI中。尝试创建OpenGL版本的视频接收器,如果失败则回退*/
    if (gtkglsink != NULL && videosink != NULL)
    {
        g_printerr ("Successfully created GTK GL Sink");

        g_object_set (videosink, "sink", gtkglsink, NULL);

        /* gtkglsink为我们创建gtk小部件。这可以通过属性访问。 */
        g_object_get (gtkglsink, "widget", &data.sink_widget, NULL);
    }
    else
    {
        g_printerr ("Could not create gtkglsink, falling back to gtksink.\n");

        videosink = gst_element_factory_make ("gtksink", "gtksink");
        g_object_get (videosink, "widget", &data.sink_widget, NULL);
    }

    if (!data.playbin || !videosink) { g_printerr ("Not all elements could be created.\n"); return -1; }

    /* 设置播放源 */
    g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

    /* 设置video-sink  */
    g_object_set (data.playbin, "video-sink", videosink, NULL);

    /* 连接到playbin中的信号 */
    g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
    g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
    g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);

    /* 创建ui */
    create_ui (&data);

    /* 指示总线为每条接收到的消息发出信号,并连接到感兴趣的信号 */
    GstBus *bus = gst_element_get_bus (data.playbin);
    gst_bus_add_signal_watch (bus);
    g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
    g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
    g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
    g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
    gst_object_unref (bus);

    /* 开始播放 */
    GstStateChangeReturn ret = gst_element_set_state (data.playbin, 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.playbin); gst_object_unref (videosink); return -1;
    }

    /* 注册一个每秒都会调用的定时器函数 */
    g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);

    /* 启动GTK主循环。在调用gtk_main_quit之前,我们不会重新获得控制权 */
    gtk_main ();

    /* 释放资源 */
    gst_element_set_state (data.playbin, GST_STATE_NULL);
    gst_object_unref (data.playbin);
    gst_object_unref (videosink);

    return 0;
}

关注

笔者 - jxd


http://www.kler.cn/a/577490.html

相关文章:

  • Scala(Array,List,Set,Map,Tuple,字符串 使用的简单介绍)
  • 1. 树莓派上配置机器人环境(具身智能机器人套件)
  • 每日一题-哞叫题(蓝桥杯)【模拟】
  • 国家二级运动员证书有什么用·棒球1号位
  • DeepSeek开源Day4:DualPipeEPLB技术详解
  • STM32驱动OLED屏幕全解析:从原理到温度显示实战(上) | 零基础入门STM32第五十三步
  • React Native v0.78 更新
  • 国产替代新篇章:领麦微红外测温传感器赋能3D打印精准制造
  • Linux安装Anaconda和Jupyter
  • 求最大公约数【C/C++】
  • Ubuntu 下 nginx-1.24.0 源码分析 - conf_ctx
  • 机器学习数学基础:39.样本和隐含和残差协方差矩阵
  • 动态HTTP代理与静态HTTP代理:优缺点详析
  • 浅论数据库聚合:合理使用LambdaQueryWrapper和XML
  • P4268 [USACO18FEB] Directory Traversal G
  • 使用Lua和lua-resty-http-simple库的爬虫程序爬取图片
  • linyu-im
  • 蓝桥杯备赛:一道数学题(练思维(同余的应用))
  • 解决MySQL迁移到达梦数据库报错“字符串截断”的问题
  • Android Studio右上角Gradle 的Task展示不全