Hello,
I am using GStreamer to play recorded video . I need to add UI Controls , for that I am using GTK4 platform . I created a sample application to merge gstreamer with gtk4 . Here I am attaching the code snippet regarding to it.
#include <gst/gst.h>
#include <gtk/gtk.h>
#include <string.h>
#include <gdk/gdk.h>
int play_flag = 0;
/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
GstElement *pipeline; /* Our pipeline */
GtkWidget *sink_widget; /* Widget where our video will be displayed */
GtkWidget *slider; /* Slider widget to keep track of current position */
GtkWidget *streams_list;
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;
GstElement *source;
GstElement *decode;
GstElement *video_queue, *v4l2convert, *videosink, *audio_queue, *audioconvert, *audioresample, *audiosink;
/* This function is called when the PLAY button is clicked */
static void play_cb(GtkButton *button, CustomData *data) {
if (play_flag == 1) {
gst_element_set_state(data->pipeline, GST_STATE_READY);
play_flag = 0;
}
gst_element_set_state(data->pipeline, 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->pipeline, 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->pipeline, 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);
}
/* 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->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
(gint64)(value * GST_SECOND));
}
/* Dynamic pad handler to link decodebin pads */
static void pad_added_handler(GstElement *src, GstPad *new_pad, CustomData *data) {
GstPad *sink_pad = NULL;
GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL;
new_pad_caps = gst_pad_get_current_caps(new_pad);
new_pad_struct = gst_caps_get_structure(new_pad_caps, 0);
new_pad_type = gst_structure_get_name(new_pad_struct);
if (g_str_has_prefix(new_pad_type, "video/x-raw")) {
sink_pad = gst_element_get_static_pad(video_queue, "sink");
} else if (g_str_has_prefix(new_pad_type, "audio/x-raw")) {
sink_pad = gst_element_get_static_pad(audio_queue, "sink");
}
if (gst_pad_is_linked(sink_pad)) {
g_object_unref(sink_pad);
if (new_pad_caps != NULL)
gst_caps_unref(new_pad_caps);
return;
}
if (gst_pad_link(new_pad, sink_pad) != GST_PAD_LINK_OK) {
g_printerr("Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print("Link succeeded (type '%s').\n", new_pad_type);
}
if (new_pad_caps != NULL)
gst_caps_unref(new_pad_caps);
gst_object_unref(sink_pad);
}
/* 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->pipeline, 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->pipeline, 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 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->pipeline, 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");
play_flag = 1;
}
/* 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->pipeline)) {
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);
}
}
}
static void activate(GtkApplication *app, CustomData *data) {
GtkWidget *window;
GtkWidget *box;
GtkWidget *button_play;
GtkWidget *button_pause;
GtkWidget *button_stop;
GstBus *bus;
GstElement *gtkglsink;
window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "Video Player");
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_window_set_child(GTK_WINDOW(window), box);
/* Initialize our data structure */
memset(data, 0, sizeof(CustomData));
data->duration = GST_CLOCK_TIME_NONE;
// Create GStreamer pipeline
data->pipeline = gst_pipeline_new("video-audio-player");
source = gst_element_factory_make("filesrc", "file-source");
decode = gst_element_factory_make("decodebin", "decoder");
video_queue = gst_element_factory_make("queue", "video-queue");
v4l2convert = gst_element_factory_make("videoconvert", "video-convert");
videosink = gst_element_factory_make("gtksink", "video-output"); //autovideosink
audio_queue = gst_element_factory_make("queue", "audio-queue");
audioconvert = gst_element_factory_make("audioconvert", "audio-convert");
audioresample = gst_element_factory_make("audioresample", "audio-resample");
audiosink = gst_element_factory_make("autoaudiosink", "audio-output");
if (!source || !decode || !video_queue || !v4l2convert || !videosink ||
!audio_queue || !audioconvert || !audioresample || !audiosink) {
g_printerr("Not all elements could be created.\n");
return;
}
g_object_set(G_OBJECT(source), "location", "my_video-4.mp4", NULL);
/* Build the pipeline. We add all elements into the pipeline */
gst_bin_add_many(GST_BIN(data->pipeline), source, decode, video_queue, v4l2convert, videosink,
audio_queue, audioconvert, audioresample, audiosink, NULL);
/* Link the file source to the decoder */
if (!gst_element_link(source, decode)) {
g_printerr("Failed to link filesrc and decodebin.\n");
gst_object_unref(data->pipeline);
return;
}
// Link decodebin dynamically
g_signal_connect(decode, "pad-added", G_CALLBACK(pad_added_handler), data);
/* Link video elements */
if (!gst_element_link_many(video_queue, v4l2convert, videosink, NULL)) {
g_printerr("Video elements could not be linked.\n");
gst_object_unref(data->pipeline);
return;
}
/* Link audio elements */
if (!gst_element_link_many(audio_queue, audioconvert, audioresample, audiosink, NULL)) {
g_printerr("Audio elements could not be linked.\n");
gst_object_unref(data->pipeline);
return;
}
// Add the video sink widget to the UI only if the sink is a gtksink or gtkglsink
if (g_strcmp0(gst_element_get_name(videosink), "video-output") == 0) {
printf("videooutput yes\n");
g_object_get(videosink, "widget", &data->sink_widget, NULL);
}
if (data->sink_widget) {
gtk_box_append(GTK_BOX(box), data->sink_widget);
} else {
g_printerr("No video widget could be retrieved from the videosink.\n");
}
// Continue setting up the rest of the UI and pipeline handling...
button_play = gtk_button_new_with_label("Play");
button_pause = gtk_button_new_with_label("Pause");
button_stop = gtk_button_new_with_label("Stop");
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);
gtk_box_append(GTK_BOX(box), button_play);
gtk_box_append(GTK_BOX(box), button_pause);
gtk_box_append(GTK_BOX(box), button_stop);
gtk_box_append(GTK_BOX(box), data->slider);
g_signal_connect(button_play, "clicked", G_CALLBACK(play_cb), data);
g_signal_connect(button_pause, "clicked", G_CALLBACK(pause_cb), data);
g_signal_connect(button_stop, "clicked", G_CALLBACK(stop_cb), data);
gtk_widget_show(window);
bus = gst_element_get_bus(data->pipeline);
gst_bus_add_signal_watch(bus);
g_signal_connect(bus, "message::error", G_CALLBACK(error_cb), data);
g_signal_connect(bus, "message::eos", G_CALLBACK(eos_cb), data);
g_signal_connect(bus, "message::state-changed", G_CALLBACK(state_changed_cb), data);
// Start playing
gst_element_set_state(data->pipeline, GST_STATE_PLAYING);
/* Register a function that GLib will call every second */
// g_timeout_add(1000, (GSourceFunc)refresh_ui, &data);
g_timeout_add_seconds(1, (GSourceFunc)refresh_ui, data);
/* Free resources */
gst_object_unref(bus);
}
int main(int argc, char *argv[]) {
GtkApplication *app;
int status;
CustomData data;
gst_init(&argc, &argv);
app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), &data);
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
While running the application , I am facing some critical warnings and no window is displayed. Here I am attaching the critical warnings which I have receiving.
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot register existing type 'GtkWidget'
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot add class private field to invalid type '<invalid>'
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot add private field to invalid (non-instantiatable) type '<invalid>'
(video:512357): GLib-GObject-CRITICAL **: 21:24:17.400: g_type_add_interface_static: assertion 'G_TYPE_IS_INSTANTIATABLE (instance_type)' failed
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot register existing type 'GtkBuildable'
(video:512357): GLib-GObject-CRITICAL **: 21:24:17.400: g_type_interface_add_prerequisite: assertion 'G_TYPE_IS_INTERFACE (interface_type)' failed
(video:512357): GLib-CRITICAL **: 21:24:17.400: g_once_init_leave: assertion 'result != 0' failed
(video:512357): GLib-GObject-CRITICAL **: 21:24:17.400: g_type_add_interface_static: assertion 'G_TYPE_IS_INSTANTIATABLE (instance_type)' failed
(video:512357): Gtk-CRITICAL **: 21:24:17.400: gtk_widget_get_events: assertion 'GTK_IS_WIDGET (widget)' failed
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot register existing type 'GtkWidget'
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot add class private field to invalid type '<invalid>'
(video:512357): GLib-GObject-WARNING **: 21:24:17.400: cannot add private field to invalid (non-instantiatable) type '<invalid>'
(video:512357): GLib-GObject-CRITICAL **: 21:24:17.400: g_type_add_interface_static: assertion 'G_TYPE_IS_INSTANTIATABLE (instance_type)' failed
Can anyone suggest how to resolve this issue or any alternative methods are highly appreciable