From 04f87e2d06cbb5b67c07260908098d3eb489fafd Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 19 Apr 2012 17:25:32 +0200 Subject: [PATCH] Added code comments and more --- gst-sdk/tutorials/basic-tutorial-5.c | 368 +++++++++++++++------------ 1 file changed, 203 insertions(+), 165 deletions(-) diff --git a/gst-sdk/tutorials/basic-tutorial-5.c b/gst-sdk/tutorials/basic-tutorial-5.c index 5dccd35408..639cc25d19 100644 --- a/gst-sdk/tutorials/basic-tutorial-5.c +++ b/gst-sdk/tutorials/basic-tutorial-5.c @@ -1,5 +1,8 @@ +#include + #include #include +#include #include #if defined (GDK_WINDOWING_X11) @@ -10,35 +13,29 @@ #include #endif -#include -#include - /* Structure to contain all our information, so we can pass it around */ typedef struct _CustomData { - GstElement *playbin2; /* Our one and only pipeline */ + GstElement *playbin2; /* Our one and only pipeline */ - GtkWidget *main_window; - GtkWidget *video_window; - GtkWidget *slider; - GtkWidget *streams_list; - gboolean updating_slider; + 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; - gint64 duration; + GstState state; /* Current state of the pipeline */ + gint64 duration; /* Duration of the clip, in nanoseconds */ } CustomData; -/* Forward definition of the message processing function */ -static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data); - +/* This function is called when the GUI toolkit creates the physical window that will hold the video. + * At this point we can retrieve its handler (which has a different meaning depending on the windowing system) + * and pass it to GStreamer through the XOverlay interface. */ static void realize_cb (GtkWidget *widget, CustomData *data) { GdkWindow *window = gtk_widget_get_window (widget); guintptr window_handle; - /* This is here just for pedagogical purposes, GDK_WINDOW_XID will call it - * as well */ if (!gdk_window_ensure_native (window)) g_error ("Couldn't create native window needed for GstXOverlay!"); + /* Retrieve window handler from GDK */ #if defined (GDK_WINDOWING_WIN32) window_handle = (guintptr)GDK_WINDOW_HWND (window); #elif defined (GDK_WINDOWING_QUARTZ) @@ -46,32 +43,42 @@ static void realize_cb (GtkWidget *widget, CustomData *data) { #elif defined (GDK_WINDOWING_X11) window_handle = GDK_WINDOW_XID (window); #endif + /* Pass it to playbin2, which implements XOverlay and will forward it to the video sink */ gst_x_overlay_set_window_handle (GST_X_OVERLAY (data->playbin2), window_handle); } +/* This function is called when the PLAY button is clicked */ static void play_cb (GtkButton *button, CustomData *data) { gst_element_set_state (data->playbin2, 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->playbin2, 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->playbin2, 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 (); } -static gboolean draw_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *data) { +/* This function is called everytime the video window needs to be redrawn (due to damage/exposure, + * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise, + * we simply draw a black rectangle to avoid garbage showing up. */ +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; + /* Cairo is a 2D graphics library which we use here to clean the video window. + * It is used by GStreamer for other reasons, so it will always be available to us. */ gtk_widget_get_allocation (widget, &allocation); cr = gdk_cairo_create (window); cairo_set_source_rgb (cr, 0, 0, 0); @@ -83,24 +90,30 @@ static gboolean draw_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *d return FALSE; } +/* 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) { - if (!data->updating_slider) { - 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)); - } + 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)); } +/* This creates all the GTK+ widgets that compose our application, and registers the callbacks */ static void create_ui (CustomData *data) { - GtkWidget *controls, *main_box, *main_hbox; - GtkWidget *play_button, *pause_button, *stop_button; + GtkWidget *main_window; /* The uppermost window, containing all other windows */ + GtkWidget *video_window; /* The drawing area where the video will be shown */ + GtkWidget *main_box; /* VBox to hold main_hbox and the controls */ + GtkWidget *main_hbox; /* HBox to hold the video_window and the stream info text widget */ + GtkWidget *controls; /* HBox to hold the buttons and the slider */ + GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */ - data->main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - g_signal_connect (G_OBJECT (data->main_window), "delete-event", G_CALLBACK (delete_event_cb), data); - - data->video_window = gtk_drawing_area_new (); - gtk_widget_set_double_buffered (data->video_window, FALSE); - g_signal_connect (data->video_window, "realize", G_CALLBACK (realize_cb), data); - g_signal_connect (data->video_window, "expose_event", G_CALLBACK (draw_cb), data); + 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); @@ -113,8 +126,8 @@ static void create_ui (CustomData *data) { data->slider = gtk_hscale_new_with_range (0, 100, 1); gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0); - g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data); - + 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); @@ -123,56 +136,187 @@ static void create_ui (CustomData *data) { 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), data->video_window, TRUE, TRUE, 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 (data->main_window), main_box); - gtk_window_set_default_size (GTK_WINDOW (data->main_window), 640, 480); + gtk_container_add (GTK_CONTAINER (main_window), main_box); + gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480); - gtk_widget_show_all (data->main_window); - - gtk_widget_realize (data->main_window); + gtk_widget_show_all (main_window); } +/* This function is called periodically to refresh the GUI */ static gboolean refresh_ui (CustomData *data) { GstFormat fmt = GST_FORMAT_TIME; gint64 current = -1; - + /* We do not want to update anything unless we are in the PLAYING state */ - if (data->state != GST_STATE_PLAYING) return TRUE; + if (data->state != GST_STATE_PLAYING) + 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->playbin2, &fmt, &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->playbin2, &fmt, ¤t)) { - data->updating_slider = TRUE; + /* 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 positoin, in SECONDS */ gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND); - data->updating_slider = FALSE; + /* Re-enable the signal */ + g_signal_handler_unblock (data->slider, data->slider_update_signal_id); } return TRUE; } -static void reset_app (CustomData *data) { - gst_object_unref (data->playbin2); -} - +/* This function is called when new metadata is discovered in the stream */ static void tags_cb (GstElement *playbin2, 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 (playbin2, gst_message_new_application (GST_OBJECT (playbin2), gst_structure_new ("tags-changed", NULL))); } +/* 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->playbin2, 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->playbin2, 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->playbin2)) { + data->state = new_state; + g_print ("State set to %s\n", gst_element_state_get_name (new_state)); + } +} + +/* 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->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; + /* Retrieve the stream's video tags */ + 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; + /* Retrieve the stream's audio tags */ + 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; + /* Retrieve the stream's subtitle tags */ + 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); + } + } +} + +/* 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 (msg->structure), "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; @@ -204,10 +348,16 @@ int main(int argc, char *argv[]) { 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 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.playbin2); - gst_bus_add_watch (bus, (GstBusFunc)handle_message, &data); + 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 */ @@ -218,126 +368,14 @@ int main(int argc, char *argv[]) { return -1; } - /* Register a function that GTK will call every second */ - g_timeout_add (1000, (GSourceFunc)refresh_ui, &data); + /* 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.playbin2, GST_STATE_NULL); - reset_app (&data); + gst_object_unref (data.playbin2); return 0; } - -/* Extract metadata from all the streams */ -static void analyze_streams (CustomData *data) { - gint i; - GstTagList *tags; - gchar *str, total_str[8192]; - guint rate; - gint n_video, n_audio, n_text; - GtkTextBuffer *text; - GtkTextIter text_start, text_end; - - /* Clean current contents of the widget */ - text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list)); - gtk_text_buffer_get_start_iter (text, &text_start); - gtk_text_buffer_get_end_iter (text, &text_end); - gtk_text_buffer_delete (text, &text_start, &text_end); - - /* Read some properties */ - 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; - /* Retrieve the stream's video tags */ - g_signal_emit_by_name (data->playbin2, "get-video-tags", i, &tags); - if (tags) { - g_snprintf (total_str, sizeof (total_str), "video stream %d:\n", i); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str); - g_snprintf (total_str, sizeof (total_str), " codec: %s\n", str ? str : "unknown"); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - 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->playbin2, "get-audio-tags", i, &tags); - if (tags) { - g_snprintf (total_str, sizeof (total_str), "\naudio stream %d:\n", i); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) { - g_snprintf (total_str, sizeof (total_str), " codec: %s\n", str); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - g_free (str); - } - if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) { - g_snprintf (total_str, sizeof (total_str), " language: %s\n", str); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - g_free (str); - } - if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) { - g_snprintf (total_str, sizeof (total_str), " bitrate: %d\n", rate); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - } - 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->playbin2, "get-text-tags", i, &tags); - if (tags) { - g_snprintf (total_str, sizeof (total_str), "\nsubtitle stream %d:\n", i); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) { - g_snprintf (total_str, sizeof (total_str), " language: %s\n", str); - gtk_text_buffer_insert_at_cursor (text, total_str, -1); - g_free (str); - } - gst_tag_list_free (tags); - } - } -} - -static gboolean handle_message (GstBus *bus, GstMessage *msg, CustomData *data) { - GError *err; - gchar *debug_info; - - switch (GST_MESSAGE_TYPE (msg)) { - case GST_MESSAGE_ERROR: - 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); - break; - case GST_MESSAGE_EOS: - g_print ("End-Of-Stream reached.\n"); - gst_element_set_state (data->playbin2, GST_STATE_READY); - break; - case GST_MESSAGE_STATE_CHANGED: { - 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)); - } - } break; - case GST_MESSAGE_APPLICATION: - if (g_strcmp0 (gst_structure_get_name (msg->structure), "tags-changed") == 0) { - analyze_streams (data); - } - break; - } - - return TRUE; -}