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