gstreamer/tests/examples/va/multiple-vpp.c
Víctor Manuel Jáquez Leal 5c5083586d example: va: Add skin tone enhancement.
If camera is used as input stream and skin tone parameter is available
in vapostproc, and no random changes are enabled, the skin tone will
be enabled.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2470>
2021-08-18 14:51:01 +02:00

453 lines
12 KiB
C

#include <stdlib.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <gst/controller/gstinterpolationcontrolsource.h>
#include <gst/controller/gstdirectcontrolbinding.h>
#include <gst/va/gstvadisplay.h>
#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);
gst_printerrln ("GStreamer error: %s\n%s", err->message,
debug ? debug : "");
if (debug)
g_free (debug);
if (err)
g_error_free (err);
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;
}