1 #include <stdlib.h>
2
3 #include <gst/gst.h>
4 #include <gst/video/video.h>
5 #include <gst/controller/gstinterpolationcontrolsource.h>
6 #include <gst/controller/gstdirectcontrolbinding.h>
7 #include <gst/va/gstvadisplay.h>
8
9 #define CHANGE_DIR_WITH_EVENT 0
10
11 static gint num_buffers = 50;
12 static gboolean camera = FALSE;
13 static gboolean randomcb = FALSE;
14 static gboolean randomdir = FALSE;
15 static gboolean randomsharpen = FALSE;
16 static gboolean randomcrop = FALSE;
17
18 static GOptionEntry entries[] = {
19 {"num-buffers", 'n', 0, G_OPTION_ARG_INT, &num_buffers,
20 "Number of buffers (<= 0 : forever)", "N"},
21 {"camera", 'c', 0, G_OPTION_ARG_NONE, &camera,
22 "Use default v4l2src as video source", NULL},
23 {"random-cb", 'r', 0, G_OPTION_ARG_NONE, &randomcb,
24 "Change colorbalance randomly every second (if supported)", NULL},
25 {"random-dir", 'd', 0, G_OPTION_ARG_NONE, &randomdir,
26 "Change video direction randomly every second (if supported)", NULL},
27 {"random-sharpen", 's', 0, G_OPTION_ARG_NONE, &randomsharpen,
28 "Change sharpen filter randomly every second (if supported)", NULL},
29 {"random-crop", 'p', 0, G_OPTION_ARG_NONE, &randomcrop,
30 "Change cropping randomly every 150 miliseconds", NULL},
31 {NULL},
32 };
33
34 struct _app
35 {
36 GMainLoop *loop;
37 GstObject *display;
38 GstElement *pipeline;
39 GstElement *vpp;
40 GstElement *crop;
41 GMutex mutex;
42
43 GstControlSource *sharpen;
44 gint right, left, top, bottom;
45 gint ldir, rdir, tdir, bdir;
46 };
47
48 static GstBusSyncReply
context_handler(GstBus * bus,GstMessage * msg,gpointer data)49 context_handler (GstBus * bus, GstMessage * msg, gpointer data)
50 {
51 struct _app *app = data;
52 const gchar *context_type;
53
54 switch (GST_MESSAGE_TYPE (msg)) {
55 case GST_MESSAGE_HAVE_CONTEXT:{
56 GstContext *context = NULL;
57
58 gst_message_parse_have_context (msg, &context);
59 if (context) {
60 context_type = gst_context_get_context_type (context);
61
62 if (g_strcmp0 (context_type,
63 GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR) == 0) {
64 const GstStructure *s = gst_context_get_structure (context);
65 GstObject *display = NULL;
66
67 gst_printerr ("got have context %s from %s: ", context_type,
68 GST_MESSAGE_SRC_NAME (msg));
69
70 gst_structure_get (s, "gst-display", GST_TYPE_OBJECT, &display, NULL);
71 gst_printerrln ("%s", display ?
72 GST_OBJECT_NAME (display) : "no gst display");
73 gst_context_unref (context);
74
75 if (display) {
76 g_mutex_lock (&app->mutex);
77 gst_object_replace (&app->display, display);
78 gst_object_unref (display);
79 g_mutex_unlock (&app->mutex);
80 }
81 }
82 }
83
84 gst_message_unref (msg);
85
86 return GST_BUS_DROP;
87 }
88 case GST_MESSAGE_NEED_CONTEXT:
89 gst_message_parse_context_type (msg, &context_type);
90
91 if (g_strcmp0 (context_type, GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR) == 0) {
92 GstContext *context;
93 GstStructure *s;
94
95 gst_printerr ("got need context %s from %s: ", context_type,
96 GST_MESSAGE_SRC_NAME (msg));
97
98 g_mutex_lock (&app->mutex);
99 if (!app->display) {
100 g_mutex_unlock (&app->mutex);
101 gst_printerrln ("no gst display yet");
102 gst_message_unref (msg);
103 return GST_BUS_DROP;
104 }
105
106 context =
107 gst_context_new (GST_VA_DISPLAY_HANDLE_CONTEXT_TYPE_STR, TRUE);
108 s = gst_context_writable_structure (context);
109 gst_structure_set (s, "gst-display", GST_TYPE_OBJECT, app->display,
110 NULL);
111 gst_printerrln ("%s", GST_OBJECT_NAME (app->display));
112 gst_element_set_context (GST_ELEMENT (GST_MESSAGE_SRC (msg)), context);
113 gst_context_unref (context);
114 g_mutex_unlock (&app->mutex);
115
116 }
117
118 gst_message_unref (msg);
119
120 return GST_BUS_DROP;
121
122 default:
123 break;
124 }
125
126 return GST_BUS_PASS;
127 }
128
129 static gboolean
message_handler(GstBus * bus,GstMessage * msg,gpointer data)130 message_handler (GstBus * bus, GstMessage * msg, gpointer data)
131 {
132 struct _app *app = data;
133
134 switch (GST_MESSAGE_TYPE (msg)) {
135 case GST_MESSAGE_EOS:
136 g_main_loop_quit (app->loop);
137 break;
138 case GST_MESSAGE_ERROR:{
139 gchar *debug = NULL;
140 GError *err = NULL;
141
142 gst_message_parse_error (msg, &err, &debug);
143
144 if (err) {
145 gst_printerrln ("GStreamer error: %s\n%s", err->message,
146 debug ? debug : "");
147 g_error_free (err);
148 }
149
150 if (debug)
151 g_free (debug);
152
153 g_main_loop_quit (app->loop);
154 break;
155 }
156 default:
157 break;
158 }
159
160 return TRUE;
161 }
162
163 static void
config_simple(struct _app * app)164 config_simple (struct _app *app)
165 {
166 GParamSpec *pspec;
167 GObjectClass *g_class = G_OBJECT_GET_CLASS (app->vpp);
168 const static gchar *props[] = { "brightness", "hue", "saturation",
169 "contrast"
170 };
171 gfloat max;
172 guint i;
173
174 if (camera && (pspec = g_object_class_find_property (g_class, "skin-tone"))) {
175 if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_BOOLEAN) {
176 g_object_set (app->vpp, "skin-tone", TRUE, NULL);
177 } else {
178 max = ((GParamSpecFloat *) pspec)->maximum;
179 g_object_set (app->vpp, "skin-tone", max, NULL);
180 }
181
182 return;
183 }
184
185 for (i = 0; i < G_N_ELEMENTS (props); i++) {
186 pspec = g_object_class_find_property (g_class, props[i]);
187 if (!pspec)
188 continue;
189
190 max = ((GParamSpecFloat *) pspec)->maximum;
191 g_object_set (app->vpp, props[i], max, NULL);
192 }
193 }
194
195 static gboolean
build_pipeline(struct _app * app)196 build_pipeline (struct _app *app)
197 {
198 GstElement *src;
199 GstBus *bus;
200 GError *err = NULL;
201 GString *cmd = g_string_new (NULL);
202 const gchar *source = camera ? "v4l2src" : "videotestsrc";
203
204 g_string_printf (cmd, "%s name=src ! tee name=t "
205 "t. ! queue ! videocrop name=crop ! vapostproc name=vpp ! "
206 "fpsdisplaysink video-sink=autovideosink "
207 "t. ! queue ! vapostproc ! timeoverlay ! autovideosink", source);
208
209 app->pipeline = gst_parse_launch (cmd->str, &err);
210 g_string_free (cmd, TRUE);
211 if (err) {
212 gst_printerrln ("Couldn't create pipeline: %s", err->message);
213 g_error_free (err);
214 return FALSE;
215 }
216
217 if (num_buffers > 0) {
218 src = gst_bin_get_by_name (GST_BIN (app->pipeline), "src");
219 g_object_set (src, "num-buffers", num_buffers, NULL);
220 gst_object_unref (src);
221 }
222
223 app->vpp = gst_bin_get_by_name (GST_BIN (app->pipeline), "vpp");
224 if (!randomcb && !randomdir && !randomsharpen && !randomcrop)
225 config_simple (app);
226
227 app->crop = gst_bin_get_by_name (GST_BIN (app->pipeline), "crop");
228
229 bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
230 gst_bus_set_sync_handler (bus, context_handler, app, NULL);
231 gst_bus_add_watch (bus, message_handler, app);
232 gst_object_unref (bus);
233
234 return TRUE;
235 }
236
237 static gboolean
change_cb_randomly(gpointer data)238 change_cb_randomly (gpointer data)
239 {
240 struct _app *app = data;
241 GstColorBalance *cb;
242 GList *channels;
243
244 if (!GST_COLOR_BALANCE_GET_INTERFACE (app->vpp))
245 return G_SOURCE_REMOVE;
246
247 cb = GST_COLOR_BALANCE (app->vpp);
248 channels = (GList *) gst_color_balance_list_channels (cb);
249 for (; channels && channels->data; channels = channels->next) {
250 GstColorBalanceChannel *channel = channels->data;
251 gint value =
252 g_random_int_range (channel->min_value, channel->max_value + 1);
253
254 gst_color_balance_set_value (cb, channel, value);
255 }
256
257 return G_SOURCE_CONTINUE;
258 }
259
260 static gboolean
change_dir_randomly(gpointer data)261 change_dir_randomly (gpointer data)
262 {
263 struct _app *app = data;
264 GObjectClass *g_class = G_OBJECT_GET_CLASS (app->vpp);
265 GParamSpec *pspec;
266
267 pspec = g_object_class_find_property (g_class, "video-direction");
268 if (!pspec)
269 return G_SOURCE_REMOVE;
270
271 /* choose either sent direction by property or by event */
272 #if !CHANGE_DIR_WITH_EVENT
273 {
274 GEnumClass *enumclass;
275 guint idx, value;
276
277 enumclass = G_PARAM_SPEC_ENUM (pspec)->enum_class;
278 idx = g_random_int_range (0, enumclass->n_values);
279 value = enumclass->values[idx].value;
280
281 g_object_set (app->vpp, "video-direction", value, NULL);
282 }
283 #else
284 {
285 GstEvent *event;
286 guint idx;
287 static const gchar *orientation[] = {
288 "rotate-0", "rotate-90", "rotate-180", "rotate-270",
289 "flip-rotate-0", "flip-rotate-90", "flip-rotate-180", "flip-rotate-270",
290 "undefined",
291 };
292
293 idx = g_random_int_range (0, G_N_ELEMENTS (orientation));
294
295 event = gst_event_new_tag (gst_tag_list_new (GST_TAG_IMAGE_ORIENTATION,
296 orientation[idx], NULL));
297 gst_element_send_event (app->pipeline, event);
298 }
299 #endif
300
301 return G_SOURCE_CONTINUE;
302 }
303
304 static inline GParamSpec *
vpp_has_sharpen(GstElement * vpp)305 vpp_has_sharpen (GstElement * vpp)
306 {
307 GObjectClass *g_class = G_OBJECT_GET_CLASS (vpp);
308 return g_object_class_find_property (g_class, "sharpen");
309 }
310
311 static gboolean
change_sharpen_randomly(gpointer data)312 change_sharpen_randomly (gpointer data)
313 {
314 struct _app *app = data;
315 GParamSpec *pspec;
316 gdouble value;
317
318 pspec = vpp_has_sharpen (app->vpp);
319 if (!pspec)
320 return G_SOURCE_REMOVE;
321 value = g_random_double_range (G_PARAM_SPEC_FLOAT (pspec)->minimum,
322 G_PARAM_SPEC_FLOAT (pspec)->maximum);
323
324 gst_timed_value_control_source_set (GST_TIMED_VALUE_CONTROL_SOURCE
325 (app->sharpen), GST_SECOND, value);
326
327 return G_SOURCE_CONTINUE;
328 }
329
330 static gboolean
change_crop_randomly(gpointer data)331 change_crop_randomly (gpointer data)
332 {
333 struct _app *app = data;
334
335 g_object_set (app->crop, "bottom", app->bottom, "top", app->top, "left",
336 app->left, "right", app->right, NULL);
337
338 app->top += app->tdir;
339 if (app->top >= 80)
340 app->tdir = -10;
341 else if (app->top < 10)
342 app->tdir = 10;
343
344 app->bottom += app->bdir;
345 if (app->bottom >= 60)
346 app->bdir = -10;
347 else if (app->bottom < 10)
348 app->bdir = 10;
349
350 app->left += app->ldir;
351 if (app->left >= 100)
352 app->ldir = -10;
353 else if (app->left < 10)
354 app->ldir = 10;
355
356 app->right += app->rdir;
357 if (app->right >= 80)
358 app->rdir = -10;
359 else if (app->right < 10)
360 app->rdir = 10;
361
362 return G_SOURCE_CONTINUE;
363 }
364
365 static gboolean
parse_arguments(int * argc,char *** argv)366 parse_arguments (int *argc, char ***argv)
367 {
368 GOptionContext *ctxt;
369 GError *err = NULL;
370
371 ctxt = g_option_context_new ("— Multiple VA postprocessors");
372 g_option_context_add_main_entries (ctxt, entries, NULL);
373 g_option_context_add_group (ctxt, gst_init_get_option_group ());
374
375 if (!g_option_context_parse (ctxt, argc, argv, &err)) {
376 gst_printerrln ("option parsing failed: %s", err->message);
377 g_error_free (err);
378 return FALSE;
379 }
380
381 g_option_context_free (ctxt);
382 return TRUE;
383 }
384
385 int
main(int argc,char ** argv)386 main (int argc, char **argv)
387 {
388 GstBus *bus;
389 struct _app app = { NULL, };
390 int ret = EXIT_FAILURE;
391
392 if (!parse_arguments (&argc, &argv))
393 return EXIT_FAILURE;
394
395 g_mutex_init (&app.mutex);
396
397 app.loop = g_main_loop_new (NULL, TRUE);
398
399 if (!build_pipeline (&app))
400 goto gst_failed;
401
402 if (randomcb)
403 g_timeout_add_seconds (1, change_cb_randomly, &app);
404
405 if (randomdir) {
406 #if CHANGE_DIR_WITH_EVENT
407 gst_util_set_object_arg (G_OBJECT (app.vpp), "video-direction", "auto");
408 #endif
409 g_timeout_add_seconds (1, change_dir_randomly, &app);
410 }
411
412 if (randomsharpen && vpp_has_sharpen (app.vpp)) {
413 GstControlBinding *bind;
414
415 app.sharpen = gst_interpolation_control_source_new ();
416 bind = gst_direct_control_binding_new_absolute (GST_OBJECT (app.vpp),
417 "sharpen", app.sharpen);
418 gst_object_add_control_binding (GST_OBJECT (app.vpp), bind);
419 g_object_set (app.sharpen, "mode", GST_INTERPOLATION_MODE_LINEAR, NULL);
420
421 change_sharpen_randomly (&app);
422 g_timeout_add_seconds (1, change_sharpen_randomly, &app);
423 }
424
425 if (randomcrop) {
426 app.bdir = app.ldir = app.rdir = app.tdir = 10;
427 g_timeout_add (150, change_crop_randomly, &app);
428 }
429
430 gst_element_set_state (app.pipeline, GST_STATE_PLAYING);
431
432 g_main_loop_run (app.loop);
433
434 gst_element_set_state (app.pipeline, GST_STATE_NULL);
435
436 bus = gst_pipeline_get_bus (GST_PIPELINE (app.pipeline));
437 gst_bus_remove_watch (bus);
438 gst_object_unref (bus);
439
440 gst_clear_object (&app.display);
441
442 ret = EXIT_SUCCESS;
443
444 gst_clear_object (&app.vpp);
445 gst_clear_object (&app.pipeline);
446 gst_clear_object (&app.sharpen);
447 gst_clear_object (&app.crop);
448
449 gst_failed:
450 g_mutex_clear (&app.mutex);
451 g_main_loop_unref (app.loop);
452
453 gst_deinit ();
454
455 return ret;
456 }
457