运行效果
简介
上一个教程演示了时间管理及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/includeLIBS += 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, ¤t)){/* 阻止“值已更改”信号,因此不调用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