diff --git a/validate/gst/validate/gst-validate-pipeline-monitor.c b/validate/gst/validate/gst-validate-pipeline-monitor.c index b52282c9e5..d47186aaed 100644 --- a/validate/gst/validate/gst-validate-pipeline-monitor.c +++ b/validate/gst/validate/gst-validate-pipeline-monitor.c @@ -46,10 +46,27 @@ enum G_DEFINE_TYPE (GstValidatePipelineMonitor, gst_validate_pipeline_monitor, GST_TYPE_VALIDATE_BIN_MONITOR); +static void +gst_validate_pipeline_monitor_dispose (GObject * object) +{ + GstValidatePipelineMonitor *self = (GstValidatePipelineMonitor *) object; + + g_clear_object (&self->stream_collection); + if (self->streams_selected) { + g_list_free_full (self->streams_selected, gst_object_unref); + self->streams_selected = NULL; + } + + G_OBJECT_CLASS (gst_validate_pipeline_monitor_parent_class)->dispose (object); +} + static void gst_validate_pipeline_monitor_class_init (GstValidatePipelineMonitorClass * klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_validate_pipeline_monitor_dispose; } static void @@ -179,6 +196,33 @@ _bus_handler (GstBus * bus, GstMessage * message, } break; } + case GST_MESSAGE_STREAM_COLLECTION: + { + GstStreamCollection *collection = NULL; + gst_message_parse_stream_collection (message, &collection); + gst_object_replace ((GstObject **) & monitor->stream_collection, + (GstObject *) collection); + gst_object_unref (collection); + break; + } + case GST_MESSAGE_STREAMS_SELECTED: + { + guint i; + + if (monitor->streams_selected) { + g_list_free_full (monitor->streams_selected, gst_object_unref); + monitor->streams_selected = NULL; + } + + for (i = 0; i < gst_message_streams_selected_get_size (message); i++) { + GstStream *stream = + gst_message_streams_selected_get_stream (message, i); + + monitor->streams_selected = + g_list_append (monitor->streams_selected, stream); + } + break; + } default: break; } @@ -248,5 +292,10 @@ gst_validate_pipeline_monitor_new (GstPipeline * pipeline, gst_object_unref (bus); + if (g_strcmp0 (G_OBJECT_TYPE_NAME (pipeline), "GstPlayBin") == 0) + monitor->is_playbin = TRUE; + else if (g_strcmp0 (G_OBJECT_TYPE_NAME (pipeline), "GstPlayBin3") == 0) + monitor->is_playbin3 = TRUE; + return monitor; } diff --git a/validate/gst/validate/gst-validate-pipeline-monitor.h b/validate/gst/validate/gst-validate-pipeline-monitor.h index 6e50961960..502cb7d8a3 100644 --- a/validate/gst/validate/gst-validate-pipeline-monitor.h +++ b/validate/gst/validate/gst-validate-pipeline-monitor.h @@ -58,6 +58,16 @@ struct _GstValidatePipelineMonitor { guint print_pos_srcid; gboolean buffering; gboolean got_error; + + /* TRUE if monitoring a playbin2 pipeline */ + gboolean is_playbin; + /* TRUE if monitoring a playbin3 pipeline */ + gboolean is_playbin3; + + /* Latest collection received from GST_MESSAGE_STREAM_COLLECTION */ + GstStreamCollection *stream_collection; + /* Latest GstStream received from GST_MESSAGE_STREAMS_SELECTED */ + GList *streams_selected; }; /** diff --git a/validate/gst/validate/gst-validate-scenario.c b/validate/gst/validate/gst-validate-scenario.c index be72419cee..593be42166 100644 --- a/validate/gst/validate/gst-validate-scenario.c +++ b/validate/gst/validate/gst-validate-scenario.c @@ -52,6 +52,7 @@ #include "validate.h" #include #include +#include #define GST_VALIDATE_SCENARIO_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GST_TYPE_VALIDATE_SCENARIO, GstValidateScenarioPrivate)) @@ -70,6 +71,8 @@ GST_DEBUG_CATEGORY_STATIC (gst_validate_scenario_debug); gst_validate_register_action_type ((_tname), "core", (_function), (_params), (_desc), (_is_config)); \ } G_STMT_END +#define ACTION_EXPECTED_STREAM_QUARK g_quark_from_static_string ("ACTION_EXPECTED_STREAM_QUARK") + #define SCENARIO_LOCK(scenario) (g_mutex_lock(&scenario->priv->lock)) #define SCENARIO_UNLOCK(scenario) (g_mutex_unlock(&scenario->priv->lock)) enum @@ -145,6 +148,10 @@ struct _GstValidateScenarioPrivate GList *overrides; gchar *pipeline_name; + + /* 'switch-track action' currently waiting for + * GST_MESSAGE_STREAMS_SELECTED to be completed. */ + GstValidateAction *pending_switch_track; }; typedef struct KeyFileGroupName @@ -855,7 +862,7 @@ _check_select_pad_done (GstPad * pad, GstPadProbeInfo * info, } static gboolean -_execute_switch_track (GstValidateScenario * scenario, +execute_switch_track_default (GstValidateScenario * scenario, GstValidateAction * action) { guint index; @@ -923,6 +930,278 @@ _execute_switch_track (GstValidateScenario * scenario, return GST_VALIDATE_EXECUTE_ACTION_ERROR; } +static GstPadProbeReturn +_check_pad_event_selection_done (GstPad * pad, GstPadProbeInfo * info, + GstValidateAction * action) +{ + if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) { + gst_validate_action_set_done (action); + return GST_PAD_PROBE_REMOVE; + } + return GST_PAD_PROBE_OK; +} + +static gboolean +execute_switch_track_pb (GstValidateScenario * scenario, + GstValidateAction * action) +{ + gint index, n; + const gchar *type, *str_index; + + gint flags, current, tflag; + gchar *tmp, *current_txt; + + gint res = GST_VALIDATE_EXECUTE_ACTION_OK; + gboolean relative = FALSE, disabling = FALSE; + + if (!(type = gst_structure_get_string (action->structure, "type"))) + type = "audio"; + + tflag = + gst_validate_utils_flags_from_str (g_type_from_name ("GstPlayFlags"), + type); + current_txt = g_strdup_printf ("current-%s", type); + + tmp = g_strdup_printf ("n-%s", type); + g_object_get (scenario->pipeline, "flags", &flags, tmp, &n, + current_txt, ¤t, NULL); + + /* Don't try to use -1 */ + if (current == -1) + current = 0; + + g_free (tmp); + + if (gst_structure_has_field (action->structure, "disable")) { + disabling = TRUE; + flags &= ~tflag; + index = -1; + } else if (!(str_index = + gst_structure_get_string (action->structure, "index"))) { + if (!gst_structure_get_int (action->structure, "index", &index)) { + GST_WARNING ("No index given, defaulting to +1"); + index = 1; + relative = TRUE; + } + } else { + relative = strchr ("+-", str_index[0]) != NULL; + index = g_ascii_strtoll (str_index, NULL, 10); + } + + if (relative) { /* We are changing track relatively to current track */ + index = (current + index) % n; + } + + if (!disabling) { + GstState state, next; + GstPad *oldpad, *newpad; + tmp = g_strdup_printf ("get-%s-pad", type); + g_signal_emit_by_name (G_OBJECT (scenario->pipeline), tmp, current, + &oldpad); + g_signal_emit_by_name (G_OBJECT (scenario->pipeline), tmp, index, &newpad); + + gst_validate_printf (action, "Switching to track number: %i," + " (from %s:%s to %s:%s)\n", index, GST_DEBUG_PAD_NAME (oldpad), + GST_DEBUG_PAD_NAME (newpad)); + flags |= tflag; + g_free (tmp); + + if (gst_element_get_state (scenario->pipeline, &state, &next, 0) && + state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) { + GstPad *srcpad = NULL; + GstElement *combiner = NULL; + if (newpad == oldpad) { + srcpad = gst_pad_get_peer (oldpad); + } else if (newpad) { + combiner = GST_ELEMENT (gst_object_get_parent (GST_OBJECT (newpad))); + if (combiner) { + srcpad = gst_element_get_static_pad (combiner, "src"); + gst_object_unref (combiner); + } + } + + if (srcpad) { + gst_pad_add_probe (srcpad, + GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, + (GstPadProbeCallback) _check_pad_event_selection_done, action, + NULL); + gst_object_unref (srcpad); + + res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; + } else + res = GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + if (oldpad) + gst_object_unref (oldpad); + gst_object_unref (newpad); + } else { + gst_validate_printf (action, "Disabling track type %s", type); + } + + g_object_set (scenario->pipeline, "flags", flags, current_txt, index, NULL); + g_free (current_txt); + + return res; +} + +static GstStreamType +stream_type_from_string (const gchar * type) +{ + if (!g_strcmp0 (type, "video")) + return GST_STREAM_TYPE_VIDEO; + else if (!g_strcmp0 (type, "text")) + return GST_STREAM_TYPE_TEXT; + + /* default */ + return GST_STREAM_TYPE_AUDIO; +} + +/* Return a list of stream ID all the currently selected streams but the ones + * of type @type */ +static GList * +disable_stream (GstValidatePipelineMonitor * monitor, GstStreamType type) +{ + GList *streams = NULL, *l; + + for (l = monitor->streams_selected; l; l = g_list_next (l)) { + GstStream *s = l->data; + + if (gst_stream_get_stream_type (s) != type) { + streams = g_list_append (streams, (gpointer) s->stream_id); + } + } + + return streams; +} + +static GList * +switch_stream (GstValidatePipelineMonitor * monitor, GstValidateAction * action, + GstStreamType type, gint index, gboolean relative) +{ + guint nb_streams; + guint i, n = 0, current = 0; + GList *result = NULL, *l; + GstStream *streams[256], *s, *current_stream = NULL; + + /* Keep all streams which are not @type */ + for (l = monitor->streams_selected; l; l = g_list_next (l)) { + s = l->data; + + if (gst_stream_get_stream_type (s) != type) { + result = g_list_append (result, (gpointer) s->stream_id); + } else if (!current_stream) { + /* Assume the stream we want to switch from is the first one */ + current_stream = s; + } + } + + /* Calculate the number of @type streams */ + nb_streams = gst_stream_collection_get_size (monitor->stream_collection); + for (i = 0; i < nb_streams; i++) { + s = gst_stream_collection_get_stream (monitor->stream_collection, i); + + if (gst_stream_get_stream_type (s) == type) { + streams[n] = s; + + if (current_stream + && !g_strcmp0 (s->stream_id, current_stream->stream_id)) + current = n; + + n++; + } + } + + if (relative) { /* We are changing track relatively to current track */ + index = (current + index) % n; + } + + /* Add the new stream we want to switch to */ + s = streams[index]; + + gst_validate_printf (action, "Switching from stream %s to %s", + current_stream ? current_stream->stream_id : "", s->stream_id); + + return g_list_append (result, (gpointer) s->stream_id); +} + +static gboolean +execute_switch_track_pb3 (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateScenarioPrivate *priv = scenario->priv; + gint index; + GstStreamType stype; + const gchar *type, *str_index; + GList *new_streams = NULL; + GstValidatePipelineMonitor *monitor = + (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *) + scenario->pipeline, "validate-monitor")); + + if (!monitor->stream_collection) { + GST_ERROR ("No stream collection message received on the bus"); + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + if (!monitor->streams_selected) { + GST_ERROR ("No streams selected message received on the bus"); + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + type = gst_structure_get_string (action->structure, "type"); + stype = stream_type_from_string (type); + + if (gst_structure_has_field (action->structure, "disable")) { + gst_validate_printf (action, "Disabling track type %s", type); + new_streams = disable_stream (monitor, stype); + } else { + gboolean relative = FALSE; + + if (!(str_index = gst_structure_get_string (action->structure, "index"))) { + if (!gst_structure_get_int (action->structure, "index", &index)) { + GST_WARNING ("No index given, defaulting to +1"); + index = 1; + relative = TRUE; + } + } else { + relative = strchr ("+-", str_index[0]) != NULL; + index = g_ascii_strtoll (str_index, NULL, 10); + } + + new_streams = switch_stream (monitor, action, stype, index, relative); + } + + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (action), + ACTION_EXPECTED_STREAM_QUARK, g_list_copy (new_streams), + (GDestroyNotify) g_list_free); + + priv->pending_switch_track = action; + + if (!gst_element_send_event (scenario->pipeline, + gst_event_new_select_streams (new_streams))) { + GST_ERROR ("select-streams event not handled"); + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + } + + return GST_VALIDATE_EXECUTE_ACTION_ASYNC; +} + +static gboolean +_execute_switch_track (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidatePipelineMonitor *monitor = + (GstValidatePipelineMonitor *) (g_object_get_data ((GObject *) + scenario->pipeline, "validate-monitor")); + + if (monitor->is_playbin) + return execute_switch_track_pb (scenario, action); + else if (monitor->is_playbin3) + return execute_switch_track_pb3 (scenario, action); + + return execute_switch_track_default (scenario, action); +} + static gboolean _set_rank (GstValidateScenario * scenario, GstValidateAction * action) { @@ -2056,6 +2335,21 @@ _check_waiting_for_message (GstValidateScenario * scenario, } } +static gboolean +streams_list_contain (GList * streams, const gchar * stream_id) +{ + GList *l; + + for (l = streams; l; l = g_list_next (l)) { + GstStream *s = l->data; + + if (!g_strcmp0 (s->stream_id, stream_id)) + return TRUE; + } + + return FALSE; +} + static gboolean message_cb (GstBus * bus, GstMessage * message, GstValidateScenario * scenario) { @@ -2212,6 +2506,55 @@ message_cb (GstBus * bus, GstMessage * message, GstValidateScenario * scenario) g_print ("%s %d%% \r", "Buffering...", percent); break; } + case GST_MESSAGE_STREAMS_SELECTED: + { + guint i; + GList *streams_selected = NULL; + + for (i = 0; i < gst_message_streams_selected_get_size (message); i++) { + GstStream *stream = + gst_message_streams_selected_get_stream (message, i); + + streams_selected = g_list_append (streams_selected, stream); + } + + /* Is there a pending switch-track action waiting for the new streams to + * be selected? */ + if (priv->pending_switch_track) { + GList *expected, *l; + + expected = + gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST + (priv->pending_switch_track), ACTION_EXPECTED_STREAM_QUARK); + + if (g_list_length (expected) != g_list_length (streams_selected)) { + GST_VALIDATE_REPORT (priv->pending_switch_track->scenario, + SCENARIO_ACTION_EXECUTION_ERROR, + "Was expecting %d selected streams but got %d", + g_list_length (expected), g_list_length (streams_selected)); + goto action_done; + } + + for (l = expected; l; l = g_list_next (l)) { + const gchar *stream_id = l->data; + + if (!streams_list_contain (streams_selected, stream_id)) { + GST_VALIDATE_REPORT (priv->pending_switch_track->scenario, + SCENARIO_ACTION_EXECUTION_ERROR, + "Stream %s has not be activated", stream_id); + goto action_done; + } + } + + action_done: + gst_validate_action_set_done (priv->pending_switch_track); + priv->pending_switch_track = NULL; + } + + g_list_free_full (streams_selected, gst_object_unref); + break; + } + default: break; } diff --git a/validate/tools/gst-validate.c b/validate/tools/gst-validate.c index 510d93e699..da5b098ea1 100644 --- a/validate/tools/gst-validate.c +++ b/validate/tools/gst-validate.c @@ -35,6 +35,7 @@ #include #include #include +#include #ifdef G_OS_UNIX #include @@ -256,121 +257,6 @@ _execute_set_subtitles (GstValidateScenario * scenario, return TRUE; } -static GstPadProbeReturn -_check_pad_event_selection_done (GstPad * pad, GstPadProbeInfo * info, - GstValidateAction * action) -{ - if (GST_EVENT_TYPE (info->data) == GST_EVENT_STREAM_START) { - gst_validate_action_set_done (action); - return GST_PAD_PROBE_REMOVE; - } - return GST_PAD_PROBE_OK; -} - -static gboolean -_execute_switch_track (GstValidateScenario * scenario, - GstValidateAction * action) -{ - gint index, n; - const gchar *type, *str_index; - - gint flags, current, tflag; - gchar *tmp, *current_txt; - - gint res = GST_VALIDATE_EXECUTE_ACTION_OK; - gboolean relative = FALSE, disabling = FALSE; - - if (!(type = gst_structure_get_string (action->structure, "type"))) - type = "audio"; - - tflag = - gst_validate_utils_flags_from_str (g_type_from_name ("GstPlayFlags"), - type); - current_txt = g_strdup_printf ("current-%s", type); - - tmp = g_strdup_printf ("n-%s", type); - g_object_get (scenario->pipeline, "flags", &flags, tmp, &n, - current_txt, ¤t, NULL); - - /* Don't try to use -1 */ - if (current == -1) - current = 0; - - g_free (tmp); - - if (gst_structure_has_field (action->structure, "disable")) { - disabling = TRUE; - flags &= ~tflag; - index = -1; - } else if (!(str_index = - gst_structure_get_string (action->structure, "index"))) { - if (!gst_structure_get_int (action->structure, "index", &index)) { - GST_WARNING ("No index given, defaulting to +1"); - index = 1; - relative = TRUE; - } - } else { - relative = strchr ("+-", str_index[0]) != NULL; - index = g_ascii_strtoll (str_index, NULL, 10); - } - - if (relative) { /* We are changing track relatively to current track */ - index = (current + index) % n; - } - - if (!disabling) { - GstState state, next; - GstPad *oldpad, *newpad; - tmp = g_strdup_printf ("get-%s-pad", type); - g_signal_emit_by_name (G_OBJECT (scenario->pipeline), tmp, current, - &oldpad); - g_signal_emit_by_name (G_OBJECT (scenario->pipeline), tmp, index, &newpad); - - gst_validate_printf (action, "Switching to track number: %i," - " (from %s:%s to %s:%s)\n", index, GST_DEBUG_PAD_NAME (oldpad), - GST_DEBUG_PAD_NAME (newpad)); - flags |= tflag; - g_free (tmp); - - if (gst_element_get_state (scenario->pipeline, &state, &next, 0) && - state == GST_STATE_PLAYING && next == GST_STATE_VOID_PENDING) { - GstPad *srcpad = NULL; - GstElement *combiner = NULL; - if (newpad == oldpad) { - srcpad = gst_pad_get_peer (oldpad); - } else if (newpad) { - combiner = GST_ELEMENT (gst_object_get_parent (GST_OBJECT (newpad))); - if (combiner) { - srcpad = gst_element_get_static_pad (combiner, "src"); - gst_object_unref (combiner); - } - } - - if (srcpad) { - gst_pad_add_probe (srcpad, - GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - (GstPadProbeCallback) _check_pad_event_selection_done, action, - NULL); - gst_object_unref (srcpad); - - res = GST_VALIDATE_EXECUTE_ACTION_ASYNC; - } else - res = GST_VALIDATE_EXECUTE_ACTION_ERROR; - } - - if (oldpad) - gst_object_unref (oldpad); - gst_object_unref (newpad); - } else { - gst_validate_printf (action, "Disabling track type %s", type); - } - - g_object_set (scenario->pipeline, "flags", flags, current_txt, index, NULL); - g_free (current_txt); - - return res; -} - static void _register_playbin_actions (void) { @@ -394,41 +280,6 @@ _register_playbin_actions (void) "and action looks like 'set-subtitle, subtitle-file=en.srt'\n" "the subtitle URI will be set to 'file:///some/uri.mov.en.srt'\n", FALSE); - - /* Overriding default implementation */ - gst_validate_register_action_type ("switch-track", "validate-launcher", _execute_switch_track, - (GstValidateActionParameter []) { - { - .name = "type", - .description = "Selects which track type to change (can be 'audio', 'video'," - " or 'text').", - .mandatory = FALSE, - .types = "string", - .possible_variables = NULL, - .def = "audio", - }, - { - .name = "index", - .description = "Selects which track of this type to use: it can be either a number,\n" - "which will be the Nth track of the given type, or a number with a '+' or\n" - "'-' prefix, which means a relative change (eg, '+1' means 'next track',\n" - "'-1' means 'previous track')", - .mandatory = FALSE, - .types = "string: to switch track relatively\n" - "int: To use the actual index to use", - .possible_variables = NULL, - .def = "+1", - }, - {NULL} - }, - "The 'switch-track' command can be used to switch tracks.\n" - "The 'type' argument selects which track type to change (can be 'audio', 'video'," - " or 'text').\nThe 'index' argument selects which track of this type\n" - "to use: it can be either a number, which will be the Nth track of\n" - "the given type, or a number with a '+' or '-' prefix, which means\n" - "a relative change (eg, '+1' means 'next track', '-1' means 'previous\n" - "track'), note that you need to state that it is a string in the scenario file\n" - "prefixing it with (string).", FALSE); /* *INDENT-ON* */ }