目标
本教程展示如何在图形用户界面(图形用户界面)工具包中集成格莱韦纳。 Gtk+ .基本上,格威斯特处理媒体播放时,图形界面工具包手操作。最有趣的部分是那些图书馆必须进行交互的部分:指示格莱韦勒将视频输出到agtk+窗口,并将用户操作转发到格莱韦勒。
特别是,你将学到:
-
如何告诉GStreamer输出视频到一个特定的窗口(而不是创建自己的窗口)。
-
如何持续更新来自GStreamer的信息。
-
如何从多线程的GStreamer更新图形界面,禁止在大多数图形界面工具包上操作。
-
只订阅你感兴趣的消息的机制,而不是被通知所有的消息。
导言
我们将利用 Gtk+ 但这些概念适用于其他工具包 Qt ,比如说。对 Gtk+ 会有助于理解它。
重点是告诉格莱维斯特将视频输出到我们选择的窗口。
一个常见的问题是图形化工具包通常只允许通过主线程(或应用程序)操作图形化的"小部件",而GStreamer通常生成多个线程来处理不同的任务。召唤 Gtk+ 回调过程中的函数通常会失败,因为回调过程中执行的线程不需要是主线程。这个问题可以通过在回调过程中的格兰姆总线上张贴一条消息来解决:消息将由主线程接收,然后相应地处理。
最后,到目前为止我们已经注册了handle_message
每次有消息出现在公共汽车上,我们就会呼出它的作用,这就迫使我们去看它是否对我们感兴趣。在这种图托利亚方法中,使用了不同的方法来为每种消息注册回调,因此分析较少,整体代码较少。
一个媒体播放器
让我们写一个非常简单的媒体播放器基于游戏机,这一次,与图形界面!
把这个代码复制到一个命名的文本文件中basic-tutorial-5.c
。
#include <string.h>
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gdk/gdk.h>
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
GstElement *playbin; /* Our one and only pipeline */
GtkWidget *sink_widget; /* The widget where our video will be displayed */
GtkWidget *slider; /* Slider widget to keep track of current position */
GtkWidget *streams_list; /* Text widget to display info about the streams */
gulong slider_update_signal_id; /* Signal ID for the slider update signal */
GstState state; /* Current state of the pipeline */
gint64 duration; /* Duration of the clip, in nanoseconds */
} CustomData;
/* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_PLAYING);
}
/* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_PAUSED);
}
/* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_READY);
}
/* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
stop_cb (NULL, data);
gtk_main_quit ();
}
/* This function is called when the slider changes its position. We perform a seek to the
* new position here. */
static void slider_cb (GtkRange *range, CustomData *data) {
gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
(gint64)(value * GST_SECOND));
}
/* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
static void create_ui (CustomData *data) {
GtkWidget *main_window; /* The uppermost window, containing all other windows */
GtkWidget *main_box; /* VBox to hold main_hbox and the controls */
GtkWidget *main_hbox; /* HBox to hold the video sink and the stream info text widget */
GtkWidget *controls; /* HBox to hold the buttons and the slider */
GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
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);
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);
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);
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);
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);
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);
}
/* This function is called periodically to refresh the GUI */
static gboolean refresh_ui (CustomData *data) {
gint64 current = -1;
/* We do not want to update anything unless we are in the PAUSED or PLAYING states */
if (data->state < GST_STATE_PAUSED)
return TRUE;
/* If we didn't know it yet, query the stream duration */
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 {
/* Set the range of the slider to the clip duration, in SECONDS */
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)) {
/* Block the "value-changed" signal, so the slider_cb function is not called
* (which would trigger a seek the user has not requested) */
g_signal_handler_block (data->slider, data->slider_update_signal_id);
/* Set the position of the slider to the current pipeline position, in SECONDS */
gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
/* Re-enable the signal */
g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
}
return TRUE;
}
/* This function is called when new metadata is discovered in the stream */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
/* We are possibly in a GStreamer working thread, so we notify the main
* thread of this event through a message in the bus */
gst_element_post_message (playbin,
gst_message_new_application (GST_OBJECT (playbin),
gst_structure_new_empty ("tags-changed")));
}
/* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
GError *err;
gchar *debug_info;
/* Print error details on the screen */
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);
/* Set the pipeline to READY (which stops playback) */
gst_element_set_state (data->playbin, GST_STATE_READY);
}
/* This function is called when an End-Of-Stream message is posted on the bus.
* We just set the pipeline to READY (which stops playback) */
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);
}
/* This function is called when the pipeline changes states. We use it to
* keep track of the current state. */
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) {
/* For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state */
refresh_ui (data);
}
}
}
/* Extract metadata from all the streams and write it to the text widget in the GUI */
static void analyze_streams (CustomData *data) {
gint i;
GstTagList *tags;
gchar *str, *total_str;
guint rate;
gint n_video, n_audio, n_text;
GtkTextBuffer *text;
/* Clean current contents of the widget */
text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
gtk_text_buffer_set_text (text, "", -1);
/* Read some properties */
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);
for (i = 0; i < n_video; i++) {
tags = NULL;
/* Retrieve the stream's video tags */
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 (i = 0; i < n_audio; i++) {
tags = NULL;
/* Retrieve the stream's audio tags */
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 (i = 0; i < n_text; i++) {
tags = NULL;
/* Retrieve the stream's subtitle tags */
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);
}
}
}
/* This function is called when an "application" message is posted on the bus.
* Here we retrieve the message posted by the tags_cb callback */
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) {
/* If the message is the "tags-changed" (only one we are currently issuing), update
* the stream info GUI */
analyze_streams (data);
}
}
int main(int argc, char *argv[]) {
CustomData data;
GstStateChangeReturn ret;
GstBus *bus;
GstElement *gtkglsink, *videosink;
/* Initialize GTK */
gtk_init (&argc, &argv);
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Initialize our data structure */
memset (&data, 0, sizeof (data));
data.duration = GST_CLOCK_TIME_NONE;
/* Create the elements */
data.playbin = gst_element_factory_make ("playbin", "playbin");
videosink = gst_element_factory_make ("glsinkbin", "glsinkbin");
gtkglsink = gst_element_factory_make ("gtkglsink", "gtkglsink");
/* Here we create the GTK Sink element which will provide us with a GTK widget where
* GStreamer will render the video at and we can add to our UI.
* Try to create the OpenGL version of the video sink, and fallback if that fails */
if (gtkglsink != NULL && videosink != NULL) {
g_printerr ("Successfully created GTK GL Sink");
g_object_set (videosink, "sink", gtkglsink, NULL);
/* The gtkglsink creates the gtk widget for us. This is accessible through a property.
* So we get it and use it later to add it to our gui. */
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;
}
/* Set the URI to play */
g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
/* Set the video-sink */
g_object_set (data.playbin, "video-sink", videosink, NULL);
/* Connect to interesting signals in 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);
/* Create the GUI */
create_ui (&data);
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
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);
/* Start playing */
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;
}
/* Register a function that GLib will call every second */
g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
/* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
gtk_main ();
/* Free resources */
gst_element_set_state (data.playbin, GST_STATE_NULL);
gst_object_unref (data.playbin);
gst_object_unref (videosink);
return 0;
}
如果您需要帮助来编译此代码,请参阅 建立教程 平台部分: Linux , Macosx 或 窗户 ,或在Linux上使用此特定命令:
gcc basic-tutorial-5.c -o basic-tutorial-5 `pkg-config --cflags --libs gtk+-3.0 gstreamer-1.0`
如果您需要帮助来运行此代码,请参阅 运行教程 平台部分: Linux , Macosx 或 窗户 .
本教程打开一个Gtk+窗口,并显示一个电影,附带的音频。媒体是从互联网上获得的,所以窗口可能需要几秒钟才能出现,这取决于您的连接速度。窗口有一些Gtk+按钮,可以暂停、停止和播放电影,还有一个幻灯片显示流的当前位置,可以拖动来更改流。此外,关于流的信息显示在窗口右侧边缘的列上。
记住,没有延迟管理(缓冲),所以在缓慢连接时,电影可能会在几秒钟后停止。你看看 基本教程12:流媒体 解决这个问题。
必需图书馆:
gtk+-3.0 gstreamer-1.0
工作流
关于本教程的结构,我们不再使用前置函数定义:函数将在被使用之前定义。另外,为了解释清楚,片段代码显示的顺序并不总是与程序顺序相匹配。使用线号来定位完整代码中的片段。
本教程大部分由回调函数组成,这些函数将从GStreamer 或Gtk+调用,所以让我们回顾一下main
函数,记录所有这些回调。
int main(int argc, char *argv[]) {
CustomData data;
GstStateChangeReturn ret;
GstBus *bus;
GstElement *gtkglsink, *videosink;
/* Initialize GTK */
gtk_init (&argc, &argv);
/* Initialize GStreamer */
gst_init (&argc, &argv);
/* Initialize our data structure */
memset (&data, 0, sizeof (data));
data.duration = GST_CLOCK_TIME_NONE;
/* Create the elements */
data.playbin = gst_element_factory_make ("playbin", "playbin");
videosink = gst_element_factory_make ("glsinkbin", "glsinkbin");
gtkglsink = gst_element_factory_make ("gtkglsink", "gtkglsink");
/* Here we create the GTK Sink element which will provide us with a GTK widget where
* GStreamer will render the video at and we can add to our UI.
* Try to create the OpenGL version of the video sink, and fallback if that fails */
if ((gtkglsink) && (videosink)) {
g_printerr ("Successfully created GTK GL Sink");
g_object_set (videosink, "sink", gtkglsink, NULL);
/* The gtkglsink creates the gtk widget for us. This is accessible through a property.
* So we get it and use it later to add it to our gui. */
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;
}
/* Set the URI to play */
g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
/* Set the video-sink */
g_object_set (data.playbin, "video-sink", videosink, NULL);
标准的GStreamer初始化和播放器管道创建,与ttk初始化。我们还创建了我们的视频接收器元素,它将会呈现到一个 GTK小部件中。稍后我们将在用户界面中使用这个小部件。"
/* Connect to interesting signals in 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);
我们有兴趣在流中出现新标记(元数据)时得到通知。为了简单起见,我们将处理来自同一回调的各种标记(视频、音频和文本)tags_cb
.
/* Create the GUI */
create_ui (&data);
此函数中发生了所有Gtk+小部件的创建和信号注册。它只包含与gtk相关的函数调用,所以我们将跳过它的定义。它注册的信号传递用户命令,如下所示,在回顾这些命令时。
/* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
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);
在…中 播放教程1:播放器的使用 ,gst_bus_add_watch()
它注册一个函数,接收发送到流线型总线的每一条消息。我们可以通过使用信号来实现更细的粒度,它允许我们只对我们感兴趣的消息进行注册。打电话gst_bus_add_signal_watch()
我们指示总线在每次收到消息时发出信号.这个信号有名字message::detail
在哪里detail
是触发信号发射的信息。例如,当总线接收到消息时,它会发出带有名称的信号。message::eos
.
本教程使用的是Signals
只有我们所关心的那些人才能知道。如果我们注册了message
信号,我们会收到每条信息,就像 gst_bus_add_watch()
会的。
记住,为了让公共汽车的手表正常工作(不管是什么 gst_bus_add_watch()
或者gst_bus_add_signal_watch()
),必须开始Main Loop
在跑。在这种情况下,它隐藏在 Gtk+ 主回路。
/* Register a function that GLib will call every second */
g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
在将控制权转移到ttk+之前,我们使用g_timeout_add_seconds ()
要注册另一个回调,这一次要有超时,所以每秒钟都要调用一次。我们要用它来刷新图形界面refresh_ui
职能。
在此之后,我们完成了设置,并可以启动GtK+主循环。当有趣的事情发生时,我们将从回调中重新获得控制权。让我们回顾一下回调。每个回调都有一个不同的签名,这取决于谁会调用它。您可以在信号文档中查找签名(参数的含义和返回值)。
/* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_PLAYING);
}
/* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_PAUSED);
}
/* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) {
gst_element_set_state (data->playbin, GST_STATE_READY);
}
这三个小的回调与游戏关联,暂停和停止按钮在图形界面.他们简单地将管道设置为响应状态。注意在停止状态下我们把管道设置为 READY
.我们可以把管道一直运到 NULL
但是,这种转变将会稍微慢一些,因为某些资源(如音频设备)将需要释放,安德雷获得。
/* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
stop_cb (NULL, data);
gtk_main_quit ();
}
gtk_main_quit()
最终会打电话给gtk_main_run()
在…中main
终止,在这种情况下,完成程序。在这里,当主窗口关闭后,我们就称之为管道关闭(只是为了整洁)。
/* This function is called when the slider changes its position. We perform a seek to the
* new position here. */
static void slider_cb (GtkRange *range, CustomData *data) {
gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
(gint64)(value * GST_SECOND));
}
这是一个例子,如何能够很容易地实现一个复杂的图形界面元素,如导引器条(允许寻找的滑块),这要归功于托格韦勒和Gtk+的协作。如果滑块被拖到了新的位置,告诉格莱韦勒去寻找那个位置gst_element_seek_simple()
(见 基本教程4:时间管理 )。这个机架已经设置好了,所以它的值代表秒。
值得一提的是,一些性能(和响应性)可以通过做一些节流,这是,不响应每个单一用户请求寻求。由于寻索操作一定要花上一段时间,所以通常最好先等半秒钟(例如)再让另一次寻索。否则,如果用户疯狂地拖拉滑块,应用程序可能看起来没有反应,这不会在新滑块排队前完成任何尝试。
/* This function is called periodically to refresh the GUI */
static gboolean refresh_ui (CustomData *data) {
gint64 current = -1;
/* We do not want to update anything unless we are in the PAUSED or PLAYING states */
if (data->state < GST_STATE_PAUSED)
return TRUE;
此功能将移动滑块以反映当前的媒体位置。先说,如果我们不在PLAYING
声明,我们在这里没有什么要做的(加上,位置和持续时间查询通常会失败)。
/* If we didn't know it yet, query the stream duration */
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 {
/* Set the range of the slider to the clip duration, in SECONDS */
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)) {
/* Block the "value-changed" signal, so the slider_cb function is not called
* (which would trigger a seek the user has not requested) */
g_signal_handler_block (data->slider, data->slider_update_signal_id);
/* Set the position of the slider to the current pipeline position, in SECONDS */
gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
/* Re-enable the signal */
g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
}
return TRUE;
我们查询当前的管道位置,并相应地设置管道位置。会引发 value-changed
信号,我们用来知道用户什么时候拖滑块。由于我们不希望寻求发生,除非用户请求,我们禁用了value-changed
在这次行动中,信号发射g_signal_handler_block()
和 g_signal_handler_unblock()
.
返回的TRUE
从这个函数将保持它在未来被调用。如果我们回来FALSE
计时器会被移除。
/* This function is called when new metadata is discovered in the stream */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
/* We are possibly in a GStreamer working thread, so we notify the main
* thread of this event through a message in the bus */
gst_element_post_message (playbin,
gst_message_new_application (GST_OBJECT (playbin),
gst_structure_new_empty ("tags-changed")));
}
这是本教程的重点之一。当媒体中发现新的标记时,这个功能就会得到召唤, 从一条细线 这是来自应用程序(或主)线程以外的线程。我们在这里要做的是更新一个Gtk+小部件来反射这些新信息,但是 gtk+不允许从主线程操作 .
解决办法就是playbin
在公共汽车上张贴一条信息,然后返回到呼叫线程。在适当的时候,主线程将会接收到此消息并更新tttk。
gst_element_post_message()
将发送信息张贴到总线上。gst_message_new_application()
创建一个新的信息APPLICATION
类型。格兰姆消息有不同的ypyp,这种特殊的类型是为应用程序保留的:它将通过不受格兰姆影响的总线。这些类型的列表可以放在GstMessageType
文件。
信息可以通过嵌入的信息传递额外信息 GstStructure
,这是一个非常灵活的数据容器。在这里,我们创造了一个新的结构gst_structure_new()
,然后命名tags-changed
,以避免混淆,以免我们想要发送其他应用程序消息。
稍后,一旦进入主线程,总线将接收此消息message::application
我们已经联系到 application_cb
职能:
/* This function is called when an "application" message is posted on the bus.
* Here we retrieve the message posted by the tags_cb callback */
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) {
/* If the message is the "tags-changed" (only one we are currently issuing), update
* the stream info GUI */
analyze_streams (data);
}
}
一旦我确定tags-changed
我们称之为 analyze_streams
功能,也用于 播放教程1:播放器的使用 更详细。它基本上是从流中恢复标记,并将它们写入图形界面中的文本小部件中。
…error_cb
,eos_cb
和state_changed_cb
它们实际上并不值得解释,因为它们和以前的所有教程一样,但现在是从它们自己的功能。
就这样了!本教程中的代码数量似乎令人生畏,但所需的概念却很少而且简单。如果你已经遵循了以前的教程,并有少量的知识,你可能会理解这个现在可以享受你自己的媒体播放器!
练习
如果这个媒体播放器对您不够好,请尝试将显示流信息的文本更改为属性列表视图(或树视图)。然后,当用户选择一个不同的流时,使GLUVER开关流!要转换流,你需要阅读 播放教程1:播放器的使用 .
结论
本教程显示:
-
如何使用
gtksink
元素。 -
如何通过注册使用
g_timeout_add_seconds ()
. -
如何通过总线将信息传递到主线程
gst_element_post_message()
. -
如何只通知有趣的讯息,使公共汽车发出的信号
gst_bus_add_signal_watch()
使用信号细节区分所有消息类型。
这使您可以构建一个有点完整的媒体播放器,带有一个适当的用户界面。