目标
本教程展示了如何在GStreamer集成一个GUI(比如:GTK+)。
最基本的原则是GStreamer处理多媒体的播放而GUI处理和用户的交互。
在这个教程里面,我们可以学到:
如何告诉GStreamer输出视频到一个window
如何持续的刷新GUI
在GStreamer多线程时如何保持UI的更新
一个仅发送给你订阅的消息而不是所有消息的机制
介绍
我们下面就用GTK+这样一个GUI工具来些一个播放器,但基本概念是可以推广到其它工具的(比如QT)。
如果你对GTK+有一定的了解有助于理解本教程。
最重要的是告诉GStreamer把视频输出到哪个window,而这个是依赖于操作系统的,好在GStreamer提供了一个平台无关的抽象层。
这个抽象层称为XOverlay,它让应用可以告诉一个视频输出到哪个window进行渲染。
另一个问题是GUI工具通常只能用主线程来进行绘制,但GStreamer通常使用多线程来处理不同的任务。
在回调函数中调用GTK+的函数通常会失败,因为回调函数是在调用线程运行的,往往不是主线程。
这个问题的解决方法是给GStreamer发送一个消息——消息的接收是在主线程。
在现在为止,我们仅仅注册了一个handle_message函数,每次总线上有消息出现是这个函数会呗调用。
这个函数中我们会解析每个消息,看是否是我们关注的。
本教程我们采用另一种方法:给每一个消息注册一个回调函数,这样解析代码会少一点。
GTK+实现的播放器
我们使用playbin2实现一个很简单的播放器,但这次,我们有GUI!
- #include <string.h>
-
- #include <gtk/gtk.h>
- #include <gst/gst.h>
- #include <gst/interfaces/xoverlay.h>
-
- #include <gdk/gdk.h>
- #if defined (GDK_WINDOWING_X11)
- #include <gdk/gdkx.h>
- #elif defined (GDK_WINDOWING_WIN32)
- #include <gdk/gdkwin32.h>
- #elif defined (GDK_WINDOWING_QUARTZ)
- #include <gdk/gdkquartz.h>
- #endif
-
-
- typedef struct _CustomData {
- GstElement *playbin2;
-
- GtkWidget *slider;
- GtkWidget *streams_list;
- gulong slider_update_signal_id;
-
- GstState state;
- gint64 duration;
- } CustomData;
-
-
-
-
- static void realize_cb (GtkWidget *widget, CustomData *data) {
- GdkWindow *window = gtk_widget_get_window (widget);
- guintptr window_handle;
-
- if (!gdk_window_ensure_native (window))
- g_error ("Couldn't create native window needed for GstXOverlay!");
-
-
- #if defined (GDK_WINDOWING_WIN32)
- window_handle = (guintptr)GDK_WINDOW_HWND (window);
- #elif defined (GDK_WINDOWING_QUARTZ)
- window_handle = gdk_quartz_window_get_nsview (window);
- #elif defined (GDK_WINDOWING_X11)
- window_handle = GDK_WINDOW_XID (window);
- #endif
-
- gst_x_overlay_set_window_handle (GST_X_OVERLAY (data->playbin2), window_handle);
- }
-
-
- static void play_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_PLAYING);
- }
-
-
- static void pause_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_PAUSED);
- }
-
-
- static void stop_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_READY);
- }
-
-
- static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
- stop_cb (NULL, data);
- gtk_main_quit ();
- }
-
-
-
-
- static gboolean expose_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *data) {
- if (data->state < GST_STATE_PAUSED) {
- GtkAllocation allocation;
- GdkWindow *window = gtk_widget_get_window (widget);
- cairo_t *cr;
-
-
-
- gtk_widget_get_allocation (widget, &allocation);
- cr = gdk_cairo_create (window);
- cairo_set_source_rgb (cr, 0, 0, 0);
- cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
- cairo_fill (cr);
- cairo_destroy (cr);
- }
-
- return FALSE;
- }
-
-
-
- static void slider_cb (GtkRange *range, CustomData *data) {
- gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
- gst_element_seek_simple (data->playbin2, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
- (gint64)(value * GST_SECOND));
- }
-
-
- static void create_ui (CustomData *data) {
- GtkWidget *main_window;
- GtkWidget *video_window;
- GtkWidget *main_box;
- GtkWidget *main_hbox;
- GtkWidget *controls;
- GtkWidget *play_button, *pause_button, *stop_button;
-
- main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
- g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
-
- video_window = gtk_drawing_area_new ();
- gtk_widget_set_double_buffered (video_window, FALSE);
- g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
- g_signal_connect (video_window, "expose_event", G_CALLBACK (expose_cb), data);
-
- play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
- g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);
-
- pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
- g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);
-
- stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
- g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);
-
- data->slider = gtk_hscale_new_with_range (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_hbox_new (FALSE, 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_hbox_new (FALSE, 0);
- gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
- gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);
-
- main_box = gtk_vbox_new (FALSE, 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);
- }
-
-
- static gboolean refresh_ui (CustomData *data) {
- GstFormat fmt = GST_FORMAT_TIME;
- 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->playbin2, &fmt, &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->playbin2, &fmt, ¤t)) {
-
-
- 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 *playbin2, gint stream, CustomData *data) {
-
-
- gst_element_post_message (playbin2,
- gst_message_new_application (GST_OBJECT (playbin2),
- gst_structure_new ("tags-changed", NULL)));
- }
-
-
- 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);
-
-
- gst_element_set_state (data->playbin2, GST_STATE_READY);
- }
-
-
-
- static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
- g_print ("End-Of-Stream reached.\n");
- gst_element_set_state (data->playbin2, 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->playbin2)) {
- 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) {
-
- refresh_ui (data);
- }
- }
- }
-
-
- 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;
-
-
- text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
- gtk_text_buffer_set_text (text, "", -1);
-
-
- g_object_get (data->playbin2, "n-video", &n_video, NULL);
- g_object_get (data->playbin2, "n-audio", &n_audio, NULL);
- g_object_get (data->playbin2, "n-text", &n_text, NULL);
-
- for (i = 0; i < n_video; i++) {
- tags = NULL;
-
- g_signal_emit_by_name (data->playbin2, "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;
-
- g_signal_emit_by_name (data->playbin2, "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;
-
- g_signal_emit_by_name (data->playbin2, "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);
- }
- }
- }
-
-
-
- static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
- if (g_strcmp0 (gst_structure_get_name (msg->structure), "tags-changed") == 0) {
-
-
- analyze_streams (data);
- }
- }
-
- int main(int argc, charchar *argv[]) {
- CustomData data;
- GstStateChangeReturn ret;
- GstBus *bus;
-
-
- gtk_init (&argc, &argv);
-
-
- gst_init (&argc, &argv);
-
-
- memset (&data, 0, sizeof (data));
- data.duration = GST_CLOCK_TIME_NONE;
-
-
- data.playbin2 = gst_element_factory_make ("playbin2", "playbin2");
-
- if (!data.playbin2) {
- g_printerr ("Not all elements could be created.\n");
- return -1;
- }
-
-
- g_object_set (data.playbin2, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
-
-
- g_signal_connect (G_OBJECT (data.playbin2), "video-tags-changed", (GCallback) tags_cb, &data);
- g_signal_connect (G_OBJECT (data.playbin2), "audio-tags-changed", (GCallback) tags_cb, &data);
- g_signal_connect (G_OBJECT (data.playbin2), "text-tags-changed", (GCallback) tags_cb, &data);
-
-
- create_ui (&data);
-
-
- bus = gst_element_get_bus (data.playbin2);
- 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);
-
-
- ret = gst_element_set_state (data.playbin2, 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.playbin2);
- return -1;
- }
-
-
- g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
-
-
- gtk_main ();
-
-
- gst_element_set_state (data.playbin2, GST_STATE_NULL);
- gst_object_unref (data.playbin2);
- return 0;
- }
工作流程
不管本教程的数据结构是怎么样的,我们都不再打算继续使用前向引用的方式。
但是,作为显示声明时,代码的顺序并不是一成不变的。
- #include <gdk/gdk.h>
- #if defined (GDK_WINDOWING_X11)
- #include <gdk/gdkx.h>
- #elif defined (GDK_WINDOWING_WIN32)
- #include <gdk/gdkwin32.h>
- #elif defined (GDK_WINDOWING_QUARTZ)
- #include <gdk/gdkquartz.h>
- #endif
第一件值得注意的是我们需要根据平台来确定包含的头文件,也就是说,这个代码并非是平台无关的了。
幸运的时,并没有很多的window系统,我们仅仅需要考虑Linux下的X11,Windows下的Win32或者MacOSX下的Quartz。
本教程绝大部分都是在实现由GStreamer或者GTK+调用的回调函数,这些回调函数都在main函数中被注册进去的。
- int main(int argc, charchar *argv[]) {
- CustomData data;
- GstStateChangeReturn ret;
- GstBus *bus;
-
-
- gtk_init (&argc, &argv);
-
-
- gst_init (&argc, &argv);
-
-
- memset (&data, 0, sizeof (data));
- data.duration = GST_CLOCK_TIME_NONE;
-
-
- data.playbin2 = gst_element_factory_make ("playbin2", "playbin2");
-
- if (!data.playbin2) {
- g_printerr ("Not all elements could be created.\n");
- return -1;
- }
-
-
- g_object_set (data.playbin2, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
标准的GStreamer初始化和playbin2 pipeline的建立以及GTK+的初始化,没有更多的新内容。
-
- g_signal_connect (G_OBJECT (data.playbin2), "video-tags-changed", (GCallback) tags_cb, &data);
- g_signal_connect (G_OBJECT (data.playbin2), "audio-tags-changed", (GCallback) tags_cb, &data);
- g_signal_connect (G_OBJECT (data.playbin2), "text-tags-changed", (GCallback) tags_cb, &data);
我们希望在流里面出现新的tags的时候有通知给到我们,
简单来说,我们希望通过tags_cb回调函数处理所有的tag(音频,视频或者字幕)。
这个函数里会创建所有的GTK+的widget以及注册所有的信号。
这里当然只能调用和GTK相关的函数,所以我们简单略过去。
这里的信号是给到用户命令,在下面讲回调函数(_cb后缀即回调函数)的时候再逐一展开。
-
- bus = gst_element_get_bus (data.playbin2);
- 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);
在Playback教程01(后面的一个教程),我们用gst_bus_add_watch()方法来注册一个监视所有发送到GStreamer总线上的消息。
我们也可以用信号来实现,同样达到一个很好的粒度,而且我们可以只注册我们关注的消息。
通过gst_bus_add_signal_watch()方法我们让总线在收到消息时发出一个信号,
这个信号的名字是形如:message::detail的样式,这里的detail取决于收到的消息。
比如:当收到EOS消息时,会发出message::eos的信号。
本教程使用注册信号的方法来保证只有我们关注的消息才会调用回调,
如果我们注册message信号,那么每个消息都会调用回调,就和使用gst_bus_add_watch()方法一样。
请记住,要监视总线的工作(使用gst_bus_add_watch()方法或者gst_bus_add_signal_watch()方法),必须保证GLib的主循环在运行。
在本教程中,GTK+的主循环隐含了这个部分。
-
- g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
在控制权转到GTK+之前,我们用g_timeout_add_seconds()方法来注册另一个回调函数,
这次是定时回调,每1s调一次,我们用来刷新GUI(调用refresh_ui函数)。
在这些之后,我们完成了设置,然后就可以开始GTK+的主循环了。
当我们关注的事件发生时,我们会再次拿到控制权。
让我们看一下这些回调吧,每个回调有不同的签名(这里签名是输入参数和返回值),你可以在文档中查到。
-
-
-
- static void realize_cb (GtkWidget *widget, CustomData *data) {
- GdkWindow *window = gtk_widget_get_window (widget);
- guintptr window_handle;
-
- if (!gdk_window_ensure_native (window))
- g_error ("Couldn't create native window needed for GstXOverlay!");
-
-
- #if defined (GDK_WINDOWING_WIN32)
- window_handle = (guintptr)GDK_WINDOW_HWND (window);
- #elif defined (GDK_WINDOWING_QUARTZ)
- window_handle = gdk_quartz_window_get_nsview (window);
- #elif defined (GDK_WINDOWING_X11)
- window_handle = GDK_WINDOW_XID (window);
- #endif
-
- gst_x_overlay_set_window_handle (GST_X_OVERLAY (data->playbin2), window_handle);
- }
代码的注释已经说得很清楚了。
在应用的生命周期里,这是我们知道了GStreamer应该做视频渲染的window的handle。
我们从window系统获得这个handle之后通过XOverlay接口gst_x_overlay_set_window_handle()传给了playbin2。
playbin2会把这个handle传给视频输出,这样输出时就不再自行创建一个window而是使用这个。
playbin2和XOverlay让这个过程变得很简单,没有太多需要解释的地方。
-
- static void play_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_PLAYING);
- }
-
-
- static void pause_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_PAUSED);
- }
-
-
- static void stop_cb (GtkButton *button, CustomData *data) {
- gst_element_set_state (data->playbin2, GST_STATE_READY);
- }
这三个是播放器上PLAY,PAUSE以及STOP按钮的回调函数。
他们也仅仅是简单地把pipeline转到相应的状态。
请注意在STOP时我们是把pipeline切换到READY状态。
虽然我们可以做到每次都把pipeline置到NULL状态,但是这样一来转换速度就会变慢,尤其是有些资源(比如声音)还会经历释放和重新获得等过程。
-
- static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
- stop_cb (NULL, data);
- gtk_main_quit ();
- }
gtk_main_quit()让main函数里的gtk_main()结束,这样也就是让这个应用终止了。
所以我们在pipeline停了,主窗口关闭的时候调用它。
-
-
-
- static gboolean expose_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *data) {
- if (data->state < GST_STATE_PAUSED) {
- GtkAllocation allocation;
- GdkWindow *window = gtk_widget_get_window (widget);
- cairo_t *cr;
-
-
-
- gtk_widget_get_allocation (widget, &allocation);
- cr = gdk_cairo_create (window);
- cairo_set_source_rgb (cr, 0, 0, 0);
- cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
- cairo_fill (cr);
- cairo_destroy (cr);
- }
-
- return FALSE;
- }
当有数据流过时(PAUSED或PLAYING状态),视频输出部分负责刷新window的视频内容。
但是,反过来说,不在这样的情况下,视频内容就没有刷新了,这时我们要去刷新。
在这个例子中,我们用黑色矩形填充window。
-
-
- static void slider_cb (GtkRange *range, CustomData *data) {
- gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
- gst_element_seek_simple (data->playbin2, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
- (gint64)(value * GST_SECOND));
- }
这是一个用GStreamer配合GTK+,用非常简单的方法实现搜索条之类比较复杂的UI元件的例子。
如果搜索条被拉到了一个新的位置,用gst_element_seek_simple()方法告诉GStreamer跳转到那个位置
(参考《GStreamer基础教程04——时间管理》)。
因为搜索需要花费一点时间,所以常常在搜索结束后隔半秒才可以开始下一次搜索。
否则,应用在用户疯狂地拖动的时候会表现的几乎没有响应。
-
- static gboolean refresh_ui (CustomData *data) {
- GstFormat fmt = GST_FORMAT_TIME;
- gint64 current = -1;
-
-
- if (data->state < GST_STATE_PAUSED)
- return TRUE;
这个函数会把滑块移到当前的位置,而且如果我们不在PLAYING状态,那么我们什么都不用做。
-
- if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
- if (!gst_element_query_duration (data->playbin2, &fmt, &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->playbin2, &fmt, ¤t)) {
-
-
- 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;
我们查询pipeline当前的位置,并把滑块设置到相应的位置。
这会触发value-changed信号,而我们用这个信号来判断用户拖动了滑块。
所以我们在操作时用g_signal_handler_block()和g_signal_handler_unblock()来临时关闭value-changed信号。
如果返回TRUE,那么定时器继续工作,下次还会调这个回调函数;如果返回FALSE,那么关闭定时器。
-
- static void tags_cb (GstElement *playbin2, gint stream, CustomData *data) {
-
-
- gst_element_post_message (playbin2,
- gst_message_new_application (GST_OBJECT (playbin2),
- gst_structure_new ("tags-changed", NULL)));
- }
这是本教程的一个重点。
在播放时发现新的tag时会调用这个函数,所以这个函数并不是在应用的主线程中被调用。
这个时候我们需要刷新GTK+的widget来响应,但GTK+不允许非主线程的其他线程操作UI。
解决方法是让playbin2发出一个消息然后回到调用线程,这样,主线程会收到消息然后刷新GTK。
gst_element_post_message()方法让一个GStreamer element发送一个特定消息到总线上。
gst_message_new_application()创建一个新的APPLICATION类型的消息。
GStreamer有不同类型的消息,而这个特定的消息是预留给应用的,它会不受影响的通过总线。
在GstMessageType文档里面有所有类型的列表。
消息可以通过他们自己的GstStructure来携带一些信息。
在这里,我们用gst_structure_new来创建了一个新的数据结构并起名tags-changed,避免和其他的消息混淆。
一旦切换到主线程,总线会接受到消息,并发出message:application信号,这个信号会调用application_cb回调函数:
-
-
- static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
- if (g_strcmp0 (gst_structure_get_name (msg->structure), "tags-changed") == 0) {
-
-
- analyze_streams (data);
- }
- }
一旦我们确定是tags-changed消息,我们会调用analyze_stream函数,这个函数在后面的教程中还会用到,在那里我们会给出更详细的解释。
基本上做的工作是把流里面的tags拿出来,写到GUI得文本widget里面。
至于error_cb、eos_cb和state_changed_cb因为和前面的教程一样,所以不拿出来详细解释了。
本教程的代码看上去有点令人生畏,但是概念比较少而且容易理解。
如果你看过前面的教程并对GTK有一定的了解,你就会比较容易的理解本教程还可以写一个自己的播放器!
下面给张播放器的截图: