]> git.sesse.net Git - vlc/blob - modules/access/v4l2/controls.c
v4l2: use buffer PTS where available (fix #5474)
[vlc] / modules / access / v4l2 / controls.c
1 /*****************************************************************************
2  * controls.c : Video4Linux2 device controls for vlc
3  *****************************************************************************
4  * Copyright (C) 2002-2011 VLC authors and VideoLAN
5  *
6  * Authors: Benjamin Pracht <bigben at videolan dot org>
7  *          Richard Hosking <richard at hovis dot net>
8  *          Antoine Cellerier <dionoea at videolan d.t org>
9  *          Dennis Lou <dlou99 at yahoo dot com>
10  *
11  * This program is free software; you can redistribute it and/or modify it
12  * under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation; either version 2.1 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24  *****************************************************************************/
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <assert.h>
33 #include <sys/ioctl.h>
34
35 #include <vlc_common.h>
36
37 #include "v4l2.h"
38
39 typedef struct vlc_v4l2_ctrl_name
40 {
41     const char name[28];
42     uint32_t cid;
43 } vlc_v4l2_ctrl_name_t;
44
45 /* NOTE: must be sorted by ID */
46 static const vlc_v4l2_ctrl_name_t controls[] =
47 {
48     { "brightness", V4L2_CID_BRIGHTNESS },
49     { "contrast", V4L2_CID_CONTRAST },
50     { "saturation", V4L2_CID_SATURATION },
51     { "hue", V4L2_CID_HUE },
52     { "audio-volume", V4L2_CID_AUDIO_VOLUME },
53     { "audio-balance", V4L2_CID_AUDIO_BALANCE },
54     { "audio-bass", V4L2_CID_AUDIO_BASS },
55     { "audio-treble", V4L2_CID_AUDIO_TREBLE },
56     { "audio-mute", V4L2_CID_AUDIO_MUTE },
57     { "audio-loudness", V4L2_CID_AUDIO_LOUDNESS },
58     { "auto-white-balance", V4L2_CID_AUTO_WHITE_BALANCE },
59     { "do-white-balance", V4L2_CID_DO_WHITE_BALANCE },
60     { "red-balance", V4L2_CID_RED_BALANCE },
61     { "blue-balance", V4L2_CID_BLUE_BALANCE },
62     { "gamma", V4L2_CID_GAMMA },
63     { "autogain", V4L2_CID_AUTOGAIN },
64     { "gain", V4L2_CID_GAIN },
65     { "hflip", V4L2_CID_HFLIP },
66     { "vflip", V4L2_CID_VFLIP },
67     { "power-line-frequency", V4L2_CID_POWER_LINE_FREQUENCY },
68     { "hue-auto", V4L2_CID_HUE_AUTO },
69     { "white-balance-temperature", V4L2_CID_WHITE_BALANCE_TEMPERATURE },
70     { "sharpness", V4L2_CID_SHARPNESS },
71     { "backlight-compensation", V4L2_CID_BACKLIGHT_COMPENSATION },
72     { "chroma-gain-auto", V4L2_CID_CHROMA_AGC },
73     { "color-killer", V4L2_CID_COLOR_KILLER },
74     { "color-effect", V4L2_CID_COLORFX },
75     { "rotate", V4L2_CID_ROTATE },
76     { "bg-color", V4L2_CID_BG_COLOR }, // NOTE: output only
77     { "chroma-gain", V4L2_CID_CHROMA_GAIN },
78     { "brightness-auto", V4L2_CID_AUTOBRIGHTNESS },
79     { "band-stop-filter", V4L2_CID_BAND_STOP_FILTER },
80
81     { "illuminators-1", V4L2_CID_ILLUMINATORS_1 }, // NOTE: don't care?
82     { "illuminators-2", V4L2_CID_ILLUMINATORS_2 },
83 #define CTRL_CID_KNOWN(cid) \
84     ((((uint32_t)cid) - V4L2_CID_BRIGHTNESS) \
85         <= (V4L2_CID_BAND_STOP_FILTER - V4L2_CID_BRIGHTNESS))
86 };
87
88 struct vlc_v4l2_ctrl
89 {
90     int                   fd;
91     uint32_t              id;
92     uint8_t               type;
93     char                  name[32];
94     int32_t               default_value;
95     struct vlc_v4l2_ctrl *next;
96 };
97
98 static int ControlSet (const vlc_v4l2_ctrl_t *c, int_fast32_t value)
99 {
100     struct v4l2_control ctrl = {
101         .id = c->id,
102         .value = value,
103     };
104     if (v4l2_ioctl (c->fd, VIDIOC_S_CTRL, &ctrl) < 0)
105         return -1;
106     return 0;
107 }
108
109 static int ControlSet64 (const vlc_v4l2_ctrl_t *c, int64_t value)
110 {
111     struct v4l2_ext_control ext_ctrl = {
112         .id = c->id,
113         .size = 0,
114     };
115     ext_ctrl.value64 = value;
116     struct v4l2_ext_controls ext_ctrls = {
117         .ctrl_class = V4L2_CTRL_ID2CLASS(c->id),
118         .count = 1,
119         .error_idx = 0,
120         .controls = &ext_ctrl,
121     };
122
123     if (v4l2_ioctl (c->fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls) < 0)
124         return -1;
125     return 0;
126 }
127
128 static int ControlSetStr (const vlc_v4l2_ctrl_t *c, const char *restrict value)
129 {
130     struct v4l2_ext_control ext_ctrl = {
131         .id = c->id,
132         .size = strlen (value) + 1,
133     };
134     ext_ctrl.string = (char *)value;
135     struct v4l2_ext_controls ext_ctrls = {
136         .ctrl_class = V4L2_CTRL_ID2CLASS(c->id),
137         .count = 1,
138         .error_idx = 0,
139         .controls = &ext_ctrl,
140     };
141
142     if (v4l2_ioctl (c->fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls) < 0)
143         return -1;
144     return 0;
145 }
146
147 static int ControlSetCallback (vlc_object_t *obj, const char *var,
148                                vlc_value_t old, vlc_value_t cur, void *data)
149 {
150     const vlc_v4l2_ctrl_t *ctrl = data;
151     int ret;
152
153     switch (ctrl->type)
154     {
155         case V4L2_CTRL_TYPE_INTEGER:
156         case V4L2_CTRL_TYPE_MENU:
157         case V4L2_CTRL_TYPE_BITMASK:
158         case V4L2_CTRL_TYPE_INTEGER_MENU:
159             ret = ControlSet (ctrl, cur.i_int);
160             break;
161         case V4L2_CTRL_TYPE_BOOLEAN:
162             ret = ControlSet (ctrl, cur.b_bool);
163             break;
164         case V4L2_CTRL_TYPE_BUTTON:
165             ret = ControlSet (ctrl, 0);
166             break;
167         case V4L2_CTRL_TYPE_INTEGER64:
168             ret = ControlSet64 (ctrl, cur.i_int);
169             break;
170         case V4L2_CTRL_TYPE_STRING:
171             ret = ControlSetStr (ctrl, cur.psz_string);
172             break;
173         default:
174             assert (0);
175     }
176
177     if (ret)
178     {
179         msg_Err (obj, "cannot set control %s: %m", var);
180         return VLC_EGENERIC;
181     }
182     (void) old;
183     return VLC_SUCCESS;
184 }
185
186 static void ControlsReset (vlc_object_t *obj, vlc_v4l2_ctrl_t *list)
187 {
188     while (list != NULL)
189     {
190         switch (list->type)
191         {
192             case V4L2_CTRL_TYPE_INTEGER:
193             case V4L2_CTRL_TYPE_MENU:
194             case V4L2_CTRL_TYPE_INTEGER_MENU:
195                 var_SetInteger (obj, list->name, list->default_value);
196                 break;
197             case V4L2_CTRL_TYPE_BOOLEAN:
198                 var_SetBool (obj, list->name, list->default_value);
199                 break;
200             default:;
201         }
202         list = list->next;
203     }
204 }
205
206 static int ControlsResetCallback (vlc_object_t *obj, const char *var,
207                                   vlc_value_t old, vlc_value_t cur, void *data)
208 {
209     ControlsReset (obj, data);
210     (void) var; (void) old; (void) cur;
211     return VLC_SUCCESS;
212 }
213
214 static void ControlsSetFromString (vlc_object_t *obj,
215                                    const vlc_v4l2_ctrl_t *list)
216 {
217     char *buf = var_InheritString (obj, CFG_PREFIX"set-ctrls");
218     if (buf == NULL)
219         return;
220
221     char *p = buf;
222     if (*p == '{')
223         p++;
224
225     char *end = strchr (p, '}');
226     if (end != NULL)
227         *end = '\0';
228 next:
229     while (p != NULL && *p)
230     {
231         const char *name, *value;
232
233         p += strspn (p, ", ");
234         name = p;
235         end = strchr (p, ',');
236         if (end != NULL)
237             *(end++) = '\0';
238         p = end; /* next name/value pair */
239
240         end = strchr (name, '=');
241         if (end == NULL)
242         {
243             /* TODO? support button controls that way? */
244             msg_Err (obj, "syntax error in \"%s\": missing '='", name);
245             continue;
246         }
247         *(end++) = '\0';
248         value = end;
249
250         for (const vlc_v4l2_ctrl_t *c = list; c != NULL; c = c->next)
251             if (!strcasecmp (name, c->name))
252                 switch (c->type)
253                 {
254                     case V4L2_CTRL_TYPE_INTEGER:
255                     case V4L2_CTRL_TYPE_BOOLEAN:
256                     case V4L2_CTRL_TYPE_MENU:
257                     case V4L2_CTRL_TYPE_INTEGER_MENU:
258                     {
259                         long val = strtol (value, &end, 0);
260                         if (*end)
261                         {
262                             msg_Err (obj, "syntax error in \"%s\": "
263                                      " not an integer", value);
264                             goto next;
265                         }
266                         ControlSet (c, val);
267                         break;
268                     }
269
270                     case V4L2_CTRL_TYPE_INTEGER64:
271                     {
272                         long long val = strtoll (value, &end, 0);
273                         if (*end)
274                         {
275                             msg_Err (obj, "syntax error in \"%s\": "
276                                      " not an integer", value);
277                             goto next;
278                         }
279                         ControlSet64 (c, val);
280                         break;
281                     }
282
283                     case V4L2_CTRL_TYPE_STRING:
284                         ControlSetStr (c, value);
285                         break;
286
287                     case V4L2_CTRL_TYPE_BITMASK:
288                     {
289                         unsigned long val = strtoul (value, &end, 0);
290                         if (*end)
291                         {
292                             msg_Err (obj, "syntax error in \"%s\": "
293                                      " not an integer", value);
294                             goto next;
295                         }
296                         ControlSet (c, val);
297                         break;
298                     }
299
300                     default:
301                         msg_Err (obj, "setting \"%s\" not supported", name);
302                         goto next;
303                 }
304
305         msg_Err (obj, "control \"%s\" not available", name);
306     }
307     free (buf);
308 }
309
310 static int cidcmp (const void *a, const void *b)
311 {
312     const uint32_t *id = a;
313     const vlc_v4l2_ctrl_name_t *name = b;
314
315     return (int32_t)(*id - name->cid);
316 }
317
318 /**
319  * Creates a VLC-V4L2 control structure:
320  * In particular, determines a name suitable for a VLC object variable.
321  * \param query V4L2 control query structure [IN]
322  * \return NULL on error
323  */
324 static vlc_v4l2_ctrl_t *ControlCreate (int fd,
325                                        const struct v4l2_queryctrl *query)
326 {
327     vlc_v4l2_ctrl_t *ctrl = malloc (sizeof (*ctrl));
328     if (unlikely(ctrl == NULL))
329         return NULL;
330
331     ctrl->fd = fd;
332     ctrl->id = query->id;
333     ctrl->type = query->type;
334
335     /* Search for a well-known control */
336     const vlc_v4l2_ctrl_name_t *known;
337     known = bsearch (&query->id, controls, sizeof (controls) / sizeof (*known),
338                      sizeof (*known), cidcmp);
339     if (known != NULL)
340         strcpy (ctrl->name, known->name);
341     else
342     /* Fallback to automatically-generated control name */
343     {
344         size_t i;
345         for (i = 0; query->name[i]; i++)
346         {
347             unsigned char c = query->name[i];
348             if (c == ' ' || c == ',')
349                 c = '_';
350             if (c < 128)
351                 c = tolower (c);
352             ctrl->name[i] = c;
353         }
354         ctrl->name[i] = '\0';
355     }
356
357     ctrl->default_value = query->default_value;
358     return ctrl;
359 }
360
361
362 #define CTRL_FLAGS_IGNORE \
363     (V4L2_CTRL_FLAG_DISABLED /* not implemented at all */ \
364     |V4L2_CTRL_FLAG_READ_ONLY /* value is constant */ \
365     |V4L2_CTRL_FLAG_VOLATILE /* value is (variable but) read-only */)
366
367 static vlc_v4l2_ctrl_t *ControlAddInteger (vlc_object_t *obj, int fd,
368                                            const struct v4l2_queryctrl *query)
369 {
370     msg_Dbg (obj, " integer  %s (%08"PRIX32")", query->name, query->id);
371     if (query->flags & CTRL_FLAGS_IGNORE)
372         return NULL;
373
374     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
375     if (unlikely(c == NULL))
376         return NULL;
377
378     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
379     {
380         free (c);
381         return NULL;
382     }
383
384     vlc_value_t val;
385     struct v4l2_control ctrl = { .id = query->id };
386
387     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
388     {
389         msg_Dbg (obj, "  current: %3"PRId32", default: %3"PRId32,
390                  ctrl.value, query->default_value);
391         val.i_int = ctrl.value;
392         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
393     }
394     val.i_int = query->minimum;
395     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
396     val.i_int = query->maximum;
397     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
398     if (query->step != 1)
399     {
400         val.i_int = query->step;
401         var_Change (obj, c->name, VLC_VAR_SETSTEP, &val, NULL);
402     }
403     val.i_int = query->default_value;
404     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
405     return c;
406 }
407
408 static vlc_v4l2_ctrl_t *ControlAddBoolean (vlc_object_t *obj, int fd,
409                                            const struct v4l2_queryctrl *query)
410 {
411     msg_Dbg (obj, " boolean  %s (%08"PRIX32")", query->name, query->id);
412     if (query->flags & CTRL_FLAGS_IGNORE)
413         return NULL;
414
415     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
416     if (unlikely(c == NULL))
417         return NULL;
418
419     if (var_Create (obj, c->name, VLC_VAR_BOOL | VLC_VAR_ISCOMMAND))
420     {
421         free (c);
422         return NULL;
423     }
424
425     vlc_value_t val;
426     struct v4l2_control ctrl = { .id = query->id };
427
428     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
429     {
430         msg_Dbg (obj, "  current: %s, default: %s",
431                  ctrl.value ? " true" : "false",
432                  query->default_value ? " true" : "false");
433         val.b_bool = ctrl.value;
434         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
435     }
436     val.b_bool = query->default_value;
437     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
438     return c;
439 }
440
441 static vlc_v4l2_ctrl_t *ControlAddMenu (vlc_object_t *obj, int fd,
442                                         const struct v4l2_queryctrl *query)
443 {
444     msg_Dbg (obj, " menu     %s (%08"PRIX32")", query->name, query->id);
445     if (query->flags & CTRL_FLAGS_IGNORE)
446         return NULL;
447
448     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
449     if (unlikely(c == NULL))
450         return NULL;
451
452     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_HASCHOICE
453                                                   | VLC_VAR_ISCOMMAND))
454     {
455         free (c);
456         return NULL;
457     }
458
459     vlc_value_t val;
460     struct v4l2_control ctrl = { .id = query->id };
461
462     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
463     {
464         msg_Dbg (obj, "  current: %"PRId32", default: %"PRId32,
465                  ctrl.value, query->default_value);
466         val.i_int = ctrl.value;
467         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
468     }
469     val.i_int = query->minimum;
470     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
471     val.i_int = query->maximum;
472     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
473     val.i_int = query->default_value;
474     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
475
476     /* Import menu choices */
477     for (uint_fast32_t idx = query->minimum;
478          idx <= (uint_fast32_t)query->maximum;
479          idx++)
480     {
481         struct v4l2_querymenu menu = { .id = query->id, .index = idx };
482
483         if (v4l2_ioctl (fd, VIDIOC_QUERYMENU, &menu) < 0)
484             continue;
485         msg_Dbg (obj, "  choice %"PRIu32") %s", menu.index, menu.name);
486
487         vlc_value_t text;
488         val.i_int = menu.index;
489         text.psz_string = (char *)menu.name;
490         var_Change (obj, c->name, VLC_VAR_ADDCHOICE, &val, &text);
491     }
492     return c;
493 }
494
495 static vlc_v4l2_ctrl_t *ControlAddButton (vlc_object_t *obj, int fd,
496                                           const struct v4l2_queryctrl *query)
497 {
498     msg_Dbg (obj, " button   %s (%08"PRIX32")", query->name, query->id);
499     if (query->flags & CTRL_FLAGS_IGNORE)
500         return NULL;
501
502     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
503     if (unlikely(c == NULL))
504         return NULL;
505
506     if (var_Create (obj, c->name, VLC_VAR_VOID | VLC_VAR_ISCOMMAND))
507     {
508         free (c);
509         return NULL;
510     }
511     return c;
512 }
513
514 static vlc_v4l2_ctrl_t *ControlAddInteger64 (vlc_object_t *obj, int fd,
515                                             const struct v4l2_queryctrl *query)
516 {
517     msg_Dbg (obj, " 64-bits  %s (%08"PRIX32")", query->name, query->id);
518     if (query->flags & CTRL_FLAGS_IGNORE)
519         return NULL;
520
521     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
522     if (unlikely(c == NULL))
523         return NULL;
524
525     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
526     {
527         free (c);
528         return NULL;
529     }
530
531     struct v4l2_ext_control ext_ctrl = { .id = c->id, .size = 0, };
532     struct v4l2_ext_controls ext_ctrls = {
533         .ctrl_class = V4L2_CTRL_ID2CLASS(c->id),
534         .count = 1,
535         .error_idx = 0,
536         .controls = &ext_ctrl,
537     };
538
539     if (v4l2_ioctl (c->fd, VIDIOC_G_EXT_CTRLS, &ext_ctrls) >= 0)
540     {
541         vlc_value_t val = { .i_int = ext_ctrl.value64 };
542
543         msg_Dbg (obj, "  current: %"PRId64, val.i_int);
544         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
545     }
546
547     return c;
548 }
549
550 static vlc_v4l2_ctrl_t *ControlAddClass (vlc_object_t *obj, int fd,
551                                          const struct v4l2_queryctrl *query)
552 {
553     msg_Dbg (obj, "control class %s:", query->name);
554     (void) fd;
555     return NULL;
556 }
557
558 static vlc_v4l2_ctrl_t *ControlAddString (vlc_object_t *obj, int fd,
559                                           const struct v4l2_queryctrl *query)
560 {
561     msg_Dbg (obj, " string   %s (%08"PRIX32")", query->name, query->id);
562     if ((query->flags & CTRL_FLAGS_IGNORE) || query->maximum > 65535)
563         return NULL;
564
565     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
566     if (unlikely(c == NULL))
567         return NULL;
568
569     if (var_Create (obj, c->name, VLC_VAR_STRING | VLC_VAR_ISCOMMAND))
570     {
571         free (c);
572         return NULL;
573     }
574
575     /* Get current value */
576     char *buf = malloc (query->maximum + 1);
577     if (likely(buf != NULL))
578     {
579         struct v4l2_ext_control ext_ctrl = {
580             .id = c->id,
581             .size = query->maximum + 1,
582         };
583         ext_ctrl.string = buf;
584         struct v4l2_ext_controls ext_ctrls = {
585             .ctrl_class = V4L2_CTRL_ID2CLASS(c->id),
586             .count = 1,
587             .error_idx = 0,
588             .controls = &ext_ctrl,
589         };
590
591         if (v4l2_ioctl (c->fd, VIDIOC_G_EXT_CTRLS, &ext_ctrls) >= 0)
592         {
593             vlc_value_t val = { .psz_string = buf };
594
595             msg_Dbg (obj, "  current: \"%s\"", buf);
596             var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
597         }
598         free (buf);
599     }
600
601     return c;
602 }
603
604 static vlc_v4l2_ctrl_t *ControlAddBitMask (vlc_object_t *obj, int fd,
605                                            const struct v4l2_queryctrl *query)
606 {
607     msg_Dbg (obj, " bit mask %s (%08"PRIX32")", query->name, query->id);
608     if (query->flags & CTRL_FLAGS_IGNORE)
609         return NULL;
610
611     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
612     if (unlikely(c == NULL))
613         return NULL;
614
615     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
616     {
617         free (c);
618         return NULL;
619     }
620
621     vlc_value_t val;
622     struct v4l2_control ctrl = { .id = query->id };
623
624     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
625     {
626         msg_Dbg (obj, "  current: 0x%08"PRIX32", default: 0x%08"PRIX32,
627                  ctrl.value, query->default_value);
628         val.i_int = ctrl.value;
629         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
630     }
631     val.i_int = 0;
632     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
633     val.i_int = (uint32_t)query->maximum;
634     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
635     val.i_int = query->default_value;
636     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
637     return c;
638 }
639
640 static vlc_v4l2_ctrl_t *ControlAddIntMenu (vlc_object_t *obj, int fd,
641                                            const struct v4l2_queryctrl *query)
642 {
643     msg_Dbg (obj, " int menu %s (%08"PRIX32")", query->name, query->id);
644     if (query->flags & CTRL_FLAGS_IGNORE)
645         return NULL;
646
647     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
648     if (unlikely(c == NULL))
649         return NULL;
650
651     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_HASCHOICE
652                                                   | VLC_VAR_ISCOMMAND))
653     {
654         free (c);
655         return NULL;
656     }
657
658     vlc_value_t val;
659     struct v4l2_control ctrl = { .id = query->id };
660
661     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
662     {
663         msg_Dbg (obj, "  current: %"PRId32", default: %"PRId32,
664                  ctrl.value, query->default_value);
665         val.i_int = ctrl.value;
666         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
667     }
668     val.i_int = query->minimum;
669     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
670     val.i_int = query->maximum;
671     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
672     val.i_int = query->default_value;
673     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
674
675     /* Import menu choices */
676     for (uint_fast32_t idx = query->minimum;
677          idx <= (uint_fast32_t)query->maximum;
678          idx++)
679     {
680         struct v4l2_querymenu menu = { .id = query->id, .index = idx };
681         char name[sizeof ("-9223372036854775808")];
682
683         if (v4l2_ioctl (fd, VIDIOC_QUERYMENU, &menu) < 0)
684             continue;
685         msg_Dbg (obj, "  choice %"PRIu32") %"PRId64, menu.index, menu.value);
686
687         vlc_value_t text;
688         val.i_int = menu.index;
689         sprintf (name, "%"PRId64, menu.value);
690         text.psz_string = name;
691         var_Change (obj, c->name, VLC_VAR_ADDCHOICE, &val, &text);
692     }
693     return c;
694 }
695
696 static vlc_v4l2_ctrl_t *ControlAddUnknown (vlc_object_t *obj, int fd,
697                                            const struct v4l2_queryctrl *query)
698 {
699     msg_Dbg (obj, " unknown %s (%08"PRIX32")", query->name, query->id);
700     msg_Warn (obj, "  unknown control type %u", (unsigned)query->type);
701     (void) fd;
702     return NULL;
703 }
704
705 typedef vlc_v4l2_ctrl_t *(*ctrl_type_cb) (vlc_object_t *, int,
706                                           const struct v4l2_queryctrl *);
707
708 /**
709  * Lists all user-class v4l2 controls, sets them to the user specified
710  * value and create the relevant variables to enable run-time changes.
711  */
712 vlc_v4l2_ctrl_t *ControlsInit (vlc_object_t *obj, int fd)
713 {
714     /* A list of controls that can be modified at run-time is stored in the
715      * "controls" variable. The V4L2 controls dialog can be built from this. */
716     var_Create (obj, "controls", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE);
717
718     static const ctrl_type_cb handlers[] =
719     {
720         [V4L2_CTRL_TYPE_INTEGER] = ControlAddInteger,
721         [V4L2_CTRL_TYPE_BOOLEAN] = ControlAddBoolean,
722         [V4L2_CTRL_TYPE_MENU] = ControlAddMenu,
723         [V4L2_CTRL_TYPE_BUTTON] = ControlAddButton,
724         [V4L2_CTRL_TYPE_INTEGER64] = ControlAddInteger64,
725         [V4L2_CTRL_TYPE_CTRL_CLASS] = ControlAddClass,
726         [V4L2_CTRL_TYPE_STRING] = ControlAddString,
727         [V4L2_CTRL_TYPE_BITMASK] = ControlAddBitMask,
728         [V4L2_CTRL_TYPE_INTEGER_MENU] = ControlAddIntMenu,
729     };
730
731     vlc_v4l2_ctrl_t *list = NULL;
732     struct v4l2_queryctrl query;
733
734     query.id = V4L2_CTRL_FLAG_NEXT_CTRL;
735     while (v4l2_ioctl (fd, VIDIOC_QUERYCTRL, &query) >= 0)
736     {
737         ctrl_type_cb handler = NULL;
738         if (query.type < (sizeof (handlers) / sizeof (handlers[0])))
739             handler = handlers[query.type];
740         if (handler == NULL)
741             handler = ControlAddUnknown;
742
743         vlc_v4l2_ctrl_t *c = handler (obj, fd, &query);
744         if (c != NULL)
745         {
746             vlc_value_t val, text;
747
748             var_AddCallback (obj, c->name, ControlSetCallback, c);
749             text.psz_string = (char *)query.name;
750             var_Change (obj, c->name, VLC_VAR_SETTEXT, &text, NULL);
751             val.i_int = query.id;
752             text.psz_string = (char *)c->name;
753             var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
754
755             c->next = list;
756             list = c;
757         }
758         query.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
759     }
760
761     /* Set well-known controls from VLC configuration */
762     for (vlc_v4l2_ctrl_t *ctrl = list; ctrl != NULL; ctrl = ctrl->next)
763     {
764         if (!CTRL_CID_KNOWN (ctrl->id))
765             continue;
766
767         char varname[sizeof (CFG_PREFIX) + sizeof (ctrl->name) - 1];
768         sprintf (varname, CFG_PREFIX"%s", ctrl->name);
769
770         int64_t val = var_InheritInteger (obj, varname);
771         if (val == -1)
772             continue; /* the VLC default value: "do not modify" */
773         ControlSet (ctrl, val); /* NOTE: all known are integers or booleans */
774     }
775
776     /* Set any control from the VLC configuration control string */
777     ControlsSetFromString (obj, list);
778
779     /* Add a control to reset all controls to their default values */
780     {
781         vlc_value_t val, text;
782
783         var_Create (obj, "reset", VLC_VAR_VOID | VLC_VAR_ISCOMMAND);
784         val.psz_string = _("Reset defaults");
785         var_Change (obj, "reset", VLC_VAR_SETTEXT, &val, NULL);
786         val.i_int = -1;
787
788         text.psz_string = (char *)"reset";
789         var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
790         var_AddCallback (obj, "reset", ControlsResetCallback, list);
791     }
792     if (var_InheritBool (obj, CFG_PREFIX"controls-reset"))
793         ControlsReset (obj, list);
794
795     return list;
796 }
797
798 void ControlsDeinit (vlc_object_t *obj, vlc_v4l2_ctrl_t *list)
799 {
800     var_DelCallback (obj, "reset", ControlsResetCallback, list);
801     var_Destroy (obj, "reset");
802
803     while (list != NULL)
804     {
805         vlc_v4l2_ctrl_t *next = list->next;
806
807         var_DelCallback (obj, list->name, ControlSetCallback, list);
808         var_Destroy (obj, list->name);
809         free (list);
810         list = next;
811     }
812
813     var_Destroy (obj, "controls");
814 }