運行效果
?
簡介
?????上一個教程演示了時間管理及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