#include #include #include #include #include #include #define CHANGE_DIR_WITH_EVENT 0 static gint num_buffers = 50; static gboolean camera = FALSE; static gboolean randomcb = FALSE; static gboolean randomdir = FALSE; static gboolean randomsharpen = FALSE; static gboolean randomcrop = FALSE; static GOptionEntry entries[] = { {"num-buffers", 'n', 0, G_OPTION_ARG_INT, &num_buffers, "Number of buffers (<= 0 : forever)", "N"}, {"camera", 'c', 0, G_OPTION_ARG_NONE, &camera, "Use default v4l2src as video source", NULL}, {"random-cb", 'r', 0, G_OPTION_ARG_NONE, &randomcb, "Change colorbalance randomly every second (if supported)", NULL}, {"random-dir", 'd', 0, G_OPTION_ARG_NONE, &randomdir, "Change video direction randomly every second (if supported)", NULL}, {"random-sharpen", 's', 0, G_OPTION_ARG_NONE, &randomsharpen, "Change sharpen filter randomly every second (if supported)", NULL}, {"random-crop", 'p', 0, G_OPTION_ARG_NONE, &randomcrop, "Change cropping randomly every 150 miliseconds", NULL}, {NULL}, }; struct _app { GMainLoop *loop; GstObject *display; GstElement *pipeline; GstElement *vpp; GstElement *crop; GMutex mutex; GstControlSource *sharpen; gint right, left, top, bottom; gint ldir, rdir, tdir, bdir; }; static GstBusSyncReply context_handler (GstBus * bus, GstMessage * msg, gpointer data) { struct _app *app = data; const gchar *context_type; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_HAVE_CONTEXT:{ GstContext *context = NULL; gst_message_parse_have_context (msg, &context); if (context) { context_type = gst_context_get_context_type (context); if (g_strcmp0 (context_type, GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR) == 0) { const GstStructure *s = gst_context_get_structure (context); GstObject *display = NULL; gst_printerr ("got have context %s from %s: ", context_type, GST_MESSAGE_SRC_NAME (msg)); gst_structure_get (s, "gst-display", GST_TYPE_OBJECT, &display, NULL); gst_printerrln ("%s", display ? GST_OBJECT_NAME (display) : "no gst display"); gst_context_unref (context); if (display) { g_mutex_lock (&app->mutex); gst_object_replace (&app->display, display); gst_object_unref (display); g_mutex_unlock (&app->mutex); } } } gst_message_unref (msg); return GST_BUS_DROP; } case GST_MESSAGE_NEED_CONTEXT: gst_message_parse_context_type (msg, &context_type); if (g_strcmp0 (context_type, GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR) == 0) { GstContext *context; GstStructure *s; gst_printerr ("got need context %s from %s: ", context_type, GST_MESSAGE_SRC_NAME (msg)); g_mutex_lock (&app->mutex); if (!app->display) { g_mutex_unlock (&app->mutex); gst_printerrln ("no gst display yet"); gst_message_unref (msg); return GST_BUS_DROP; } context = gst_context_new (GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR, TRUE); s = gst_context_writable_structure (context); gst_structure_set (s, "gst-display", GST_TYPE_OBJECT, app->display, NULL); gst_printerrln ("%s", GST_OBJECT_NAME (app->display)); gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), context); gst_context_unref (context); g_mutex_unlock (&app->mutex); } gst_message_unref (msg); return GST_BUS_DROP; default: break; } return GST_BUS_PASS; } static gboolean message_handler (GstBus * bus, GstMessage * msg, gpointer data) { struct _app *app = data; switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_EOS: g_main_loop_quit (app->loop); break; case GST_MESSAGE_ERROR:{ gchar *debug = NULL; GError *err = NULL; gst_message_parse_error (msg, &err, &debug); if (err) { gst_printerrln ("GStreamer error: %s\n%s", err->message, debug ? debug : ""); g_error_free (err); } if (debug) g_free (debug); g_main_loop_quit (app->loop); break; } default: break; } return TRUE; } static void config_simple (struct _app *app) { GParamSpec *pspec; GObjectClass *g_class = G_OBJECT_GET_CLASS (app->vpp); const static gchar *props[] = { "brightness", "hue", "saturation", "contrast" }; gfloat max; guint i; if (camera && (pspec = g_object_class_find_property (g_class, "skin-tone"))) { if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_BOOLEAN) { g_object_set (app->vpp, "skin-tone", TRUE, NULL); } else { max = ((GParamSpecFloat *) pspec)->maximum; g_object_set (app->vpp, "skin-tone", max, NULL); } return; } for (i = 0; i < G_N_ELEMENTS (props); i++) { pspec = g_object_class_find_property (g_class, props[i]); if (!pspec) continue; max = ((GParamSpecFloat *) pspec)->maximum; g_object_set (app->vpp, props[i], max, NULL); } } static gboolean build_pipeline (struct _app *app) { GstElement *src; GstBus *bus; GError *err = NULL; GString *cmd = g_string_new (NULL); const gchar *source = camera ? "v4l2src" : "videotestsrc"; g_string_printf (cmd, "%s name=src ! tee name=t " "t. ! queue ! videocrop name=crop ! vapostproc name=vpp ! " "fpsdisplaysink video-sink=autovideosink " "t. ! queue ! vapostproc ! timeoverlay ! autovideosink", source); app->pipeline = gst_parse_launch (cmd->str, &err); g_string_free (cmd, TRUE); if (err) { gst_printerrln ("Couldn't create pipeline: %s", err->message); g_error_free (err); return FALSE; } if (num_buffers > 0) { src = gst_bin_get_by_name (GST_BIN (app->pipeline), "src"); g_object_set (src, "num-buffers", num_buffers, NULL); gst_object_unref (src); } app->vpp = gst_bin_get_by_name (GST_BIN (app->pipeline), "vpp"); if (!randomcb && !randomdir && !randomsharpen && !randomcrop) config_simple (app); app->crop = gst_bin_get_by_name (GST_BIN (app->pipeline), "crop"); bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline)); gst_bus_set_sync_handler (bus, context_handler, app, NULL); gst_bus_add_watch (bus, message_handler, app); gst_object_unref (bus); return TRUE; } static gboolean change_cb_randomly (gpointer data) { struct _app *app = data; GstColorBalance *cb; GList *channels; if (!GST_COLOR_BALANCE_GET_INTERFACE (app->vpp)) return G_SOURCE_REMOVE; cb = GST_COLOR_BALANCE (app->vpp); channels = (GList *) gst_color_balance_list_channels (cb); for (; channels && channels->data; channels = channels->next) { GstColorBalanceChannel *channel = channels->data; gint value = g_random_int_range (channel->min_value, channel->max_value + 1); gst_color_balance_set_value (cb, channel, value); } return G_SOURCE_CONTINUE; } static gboolean change_dir_randomly (gpointer data) { struct _app *app = data; GObjectClass *g_class = G_OBJECT_GET_CLASS (app->vpp); GParamSpec *pspec; pspec = g_object_class_find_property (g_class, "video-direction"); if (!pspec) return G_SOURCE_REMOVE; /* choose either sent direction by property or by event */ #if !CHANGE_DIR_WITH_EVENT { GEnumClass *enumclass; guint idx, value; enumclass = G_PARAM_SPEC_ENUM (pspec)->enum_class; idx = g_random_int_range (0, enumclass->n_values); value = enumclass->values[idx].value; g_object_set (app->vpp, "video-direction", value, NULL); } #else { GstEvent *event; guint idx; static const gchar *orientation[] = { "rotate-0", "rotate-90", "rotate-180", "rotate-270", "flip-rotate-0", "flip-rotate-90", "flip-rotate-180", "flip-rotate-270", "undefined", }; idx = g_random_int_range (0, G_N_ELEMENTS (orientation)); event = gst_event_new_tag (gst_tag_list_new (GST_TAG_IMAGE_ORIENTATION, orientation[idx], NULL)); gst_element_send_event (app->pipeline, event); } #endif return G_SOURCE_CONTINUE; } static inline GParamSpec * vpp_has_sharpen (GstElement * vpp) { GObjectClass *g_class = G_OBJECT_GET_CLASS (vpp); return g_object_class_find_property (g_class, "sharpen"); } static gboolean change_sharpen_randomly (gpointer data) { struct _app *app = data; GParamSpec *pspec; gdouble value; pspec = vpp_has_sharpen (app->vpp); if (!pspec) return G_SOURCE_REMOVE; value = g_random_double_range (G_PARAM_SPEC_FLOAT (pspec)->minimum, G_PARAM_SPEC_FLOAT (pspec)->maximum); gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE (app->sharpen), GST_SECOND, value); return G_SOURCE_CONTINUE; } static gboolean change_crop_randomly (gpointer data) { struct _app *app = data; g_object_set (app->crop, "bottom", app->bottom, "top", app->top, "left", app->left, "right", app->right, NULL); app->top += app->tdir; if (app->top >= 80) app->tdir = -10; else if (app->top < 10) app->tdir = 10; app->bottom += app->bdir; if (app->bottom >= 60) app->bdir = -10; else if (app->bottom < 10) app->bdir = 10; app->left += app->ldir; if (app->left >= 100) app->ldir = -10; else if (app->left < 10) app->ldir = 10; app->right += app->rdir; if (app->right >= 80) app->rdir = -10; else if (app->right < 10) app->rdir = 10; return G_SOURCE_CONTINUE; } static gboolean parse_arguments (int *argc, char ***argv) { GOptionContext *ctxt; GError *err = NULL; ctxt = g_option_context_new ("— Multiple VA postprocessors"); g_option_context_add_main_entries (ctxt, entries, NULL); g_option_context_add_group (ctxt, gst_init_get_option_group ()); if (!g_option_context_parse (ctxt, argc, argv, &err)) { gst_printerrln ("option parsing failed: %s", err->message); g_error_free (err); return FALSE; } g_option_context_free (ctxt); return TRUE; } int main (int argc, char **argv) { GstBus *bus; struct _app app = { NULL, }; int ret = EXIT_FAILURE; if (!parse_arguments (&argc, &argv)) return EXIT_FAILURE; g_mutex_init (&app.mutex); app.loop = g_main_loop_new (NULL, TRUE); if (!build_pipeline (&app)) goto gst_failed; if (randomcb) g_timeout_add_seconds (1, change_cb_randomly, &app); if (randomdir) { #if CHANGE_DIR_WITH_EVENT gst_util_set_object_arg (G_OBJECT (app.vpp), "video-direction", "auto"); #endif g_timeout_add_seconds (1, change_dir_randomly, &app); } if (randomsharpen && vpp_has_sharpen (app.vpp)) { GstControlBinding *bind; app.sharpen = gst_interpolation_control_source_new (); bind = gst_direct_control_binding_new_absolute (GST_OBJECT (app.vpp), "sharpen", app.sharpen); gst_object_add_control_binding (GST_OBJECT (app.vpp), bind); g_object_set (app.sharpen, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL); change_sharpen_randomly (&app); g_timeout_add_seconds (1, change_sharpen_randomly, &app); } if (randomcrop) { app.bdir = app.ldir = app.rdir = app.tdir = 10; g_timeout_add (150, change_crop_randomly, &app); } gst_element_set_state (app.pipeline, GST_STATE_PLAYING); g_main_loop_run (app.loop); gst_element_set_state (app.pipeline, GST_STATE_NULL); bus = gst_pipeline_get_bus (GST_PIPELINE (app.pipeline)); gst_bus_remove_watch (bus); gst_object_unref (bus); gst_clear_object (&app.display); ret = EXIT_SUCCESS; gst_clear_object (&app.vpp); gst_clear_object (&app.pipeline); gst_clear_object (&app.sharpen); gst_clear_object (&app.crop); gst_failed: g_mutex_clear (&app.mutex); g_main_loop_unref (app.loop); gst_deinit (); return ret; }