]> git.sesse.net Git - vlc/blob - modules/access/v4l2/controls.c
Rewrite V4L2 controls to keep a list of them (fix #5269)
[vlc] / modules / access / v4l2 / controls.c
1 /*****************************************************************************
2  * controls.c : Video4Linux2 device controls for vlc
3  *****************************************************************************
4  * Copyright (C) 2002-2011 the VideoLAN team
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
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 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 General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, 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 "v4l2.h"
31 #include <ctype.h>
32 #include <sys/ioctl.h>
33
34 typedef struct vlc_v4l2_ctrl_name
35 {
36     const char name[20];
37     uint32_t cid;
38 } vlc_v4l2_ctrl_name_t;
39
40 /* NOTE: must be sorted by ID */
41 static const vlc_v4l2_ctrl_name_t controls[] =
42 {
43     { "brightness", V4L2_CID_BRIGHTNESS },
44     { "contrast", V4L2_CID_CONTRAST },
45     { "saturation", V4L2_CID_SATURATION },
46     { "hue", V4L2_CID_HUE },
47     { "audio-volume", V4L2_CID_AUDIO_VOLUME },
48     { "audio-balance", V4L2_CID_AUDIO_BALANCE },
49     { "audio-bass", V4L2_CID_AUDIO_BASS },
50     { "audio-treble", V4L2_CID_AUDIO_TREBLE },
51     { "audio-mute", V4L2_CID_AUDIO_MUTE },
52     { "audio-loudness", V4L2_CID_AUDIO_LOUDNESS },
53     { "black-level", V4L2_CID_BLACK_LEVEL },
54     { "auto-white-balance", V4L2_CID_AUTO_WHITE_BALANCE },
55     { "do-white-balance", V4L2_CID_DO_WHITE_BALANCE },
56     { "red-balance", V4L2_CID_RED_BALANCE },
57     { "blue-balance", V4L2_CID_BLUE_BALANCE },
58     { "gamma", V4L2_CID_GAMMA },
59     { "exposure", V4L2_CID_EXPOSURE },
60     { "autogain", V4L2_CID_AUTOGAIN },
61     { "gain", V4L2_CID_GAIN },
62     { "hflip", V4L2_CID_HFLIP },
63     { "vflip", V4L2_CID_VFLIP },
64     { "hcenter", V4L2_CID_HCENTER },
65     { "vcenter", V4L2_CID_VCENTER },
66     /* TODO: add more standardized controls */
67 #define CTRL_CID_KNOWN(cid) \
68     ((((uint32_t)cid) - V4L2_CID_BRIGHTNESS) \
69         <= (V4L2_CID_VCENTER - V4L2_CID_BRIGHTNESS))
70 };
71
72 typedef struct vlc_v4l2_ctrl
73 {
74     int                   fd;
75     uint32_t              id;
76     enum v4l2_ctrl_type   type;
77     char                  name[32];
78     int32_t               default_value;
79     struct vlc_v4l2_ctrl *next;
80 } vlc_v4l2_ctrl_t;
81
82 static int ControlSet (const vlc_v4l2_ctrl_t *c, int_fast32_t value)
83 {
84     struct v4l2_control ctrl = {
85         .id = c->id,
86         .value = value,
87     };
88     if (v4l2_ioctl (c->fd, VIDIOC_S_CTRL, &ctrl) < 0)
89         return -1;
90     return 0;
91 }
92
93 static int ControlSetCallback (vlc_object_t *obj, const char *var,
94                                vlc_value_t old, vlc_value_t cur, void *data)
95 {
96     const vlc_v4l2_ctrl_t *ctrl = data;
97
98     if (ControlSet (ctrl, cur.i_int))
99     {
100         msg_Err (obj, "cannot set control %s: %m", var);
101         return VLC_EGENERIC;
102     }
103     (void) old;
104     return VLC_SUCCESS;
105 }
106
107 static void ControlsReset (vlc_v4l2_ctrl_t *list)
108 {
109     while (list != NULL)
110     {
111         if (list->type != V4L2_CTRL_TYPE_BUTTON)
112             ControlSet (list, list->default_value);
113         list = list->next;
114     }
115 }
116
117 static int ControlsResetCallback (vlc_object_t *obj, const char *var,
118                                   vlc_value_t old, vlc_value_t cur, void *data)
119 {
120     ControlsReset (data);
121     (void) obj; (void) var; (void) old; (void) cur;
122     return VLC_SUCCESS;
123 }
124
125 static void ControlsSetFromString (vlc_object_t *obj,
126                                    const vlc_v4l2_ctrl_t *list)
127 {
128     char *buf = var_InheritString (obj, CFG_PREFIX"set-ctrls");
129     if (buf == NULL)
130         return;
131
132     char *p = buf;
133     if (*p == '{')
134         p++;
135
136     char *end = strchr (p, '}');
137     if (end != NULL)
138         *end = '\0';
139 next:
140     while (p != NULL && *p)
141     {
142         const char *name, *value;
143
144         p += strspn (p, ", ");
145         name = p;
146         end = strchr (p, ',');
147         if (end != NULL)
148             *(end++) = '\0';
149         p = end; /* next name/value pair */
150
151         end = strchr (name, '=');
152         if (end == NULL)
153         {
154             /* TODO? support button controls that way? */
155             msg_Err (obj, "syntax error in \"%s\": missing '='", name);
156             continue;
157         }
158         *(end++) = '\0';
159         value = end;
160
161         long val = strtol (value, &end, 0);
162         if (*end)
163         {
164             msg_Err (obj, "syntax error in \"%s\": not an integer", value);
165             continue;
166         }
167
168         for (const vlc_v4l2_ctrl_t *c = list; c != NULL; c = c->next)
169             if (!strcasecmp (name, c->name))
170             {
171                 ControlSet (c, val);
172                 goto next;
173             }
174
175         msg_Err (obj, "control \"%s\" not available", name);
176     }
177     free (buf);
178 }
179
180 static int cidcmp (const void *a, const void *b)
181 {
182     const uint32_t *id = a;
183     const vlc_v4l2_ctrl_name_t *name = b;
184
185     return (int32_t)(*id - name->cid);
186 }
187
188 /**
189  * Creates a VLC-V4L2 control structure:
190  * In particular, determines a name suitable for a VLC object variable.
191  * \param query V4L2 control query structure [IN]
192  * \return NULL on error
193  */
194 static vlc_v4l2_ctrl_t *ControlCreate (int fd,
195                                        const struct v4l2_queryctrl *query)
196 {
197     vlc_v4l2_ctrl_t *ctrl = malloc (sizeof (*ctrl));
198     if (unlikely(ctrl == NULL))
199         return NULL;
200
201     ctrl->fd = fd;
202     ctrl->id = query->id;
203     ctrl->type = query->type;
204
205     /* Search for a well-known control */
206     const vlc_v4l2_ctrl_name_t *known;
207     known = bsearch (&query->id, controls, sizeof (controls) / sizeof (*known),
208                      sizeof (*known), cidcmp);
209     if (known != NULL)
210         strcpy (ctrl->name, known->name);
211     else
212     /* Fallback to automatically-generated control name */
213     {
214         size_t i;
215         for (i = 0; query->name[i]; i++)
216         {
217             unsigned char c = query->name[i];
218             if (c == ' ')
219                 c = '_';
220             if (c < 128)
221                 c = tolower (c);
222             ctrl->name[i] = c;
223         }
224         ctrl->name[i] = '\0';
225     }
226
227     ctrl->default_value = query->default_value;
228     return ctrl;
229 }
230
231
232 #ifndef V4L2_CTRL_FLAG_VOLATILE
233 # define V4L2_CTRL_FLAG_VOLATILE 0x0080
234 # warning Please update V4L2 kernel headers!
235 #endif
236
237 #define CTRL_FLAGS_IGNORE \
238     (V4L2_CTRL_FLAG_DISABLED /* not implemented at all */ \
239     |V4L2_CTRL_FLAG_READ_ONLY /* value is constant */ \
240     |V4L2_CTRL_FLAG_VOLATILE /* value is (variable but) read-only */)
241
242 static vlc_v4l2_ctrl_t *ControlAddInteger (vlc_object_t *obj, int fd,
243                                            const struct v4l2_queryctrl *query)
244 {
245     msg_Dbg (obj, " integer  %s (%08"PRIX32")", query->name, query->id);
246     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
247         return NULL;
248
249     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
250     if (unlikely(c == NULL))
251         return NULL;
252
253     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
254     {
255         free (c);
256         return NULL;
257     }
258
259     vlc_value_t val;
260     struct v4l2_control ctrl = { .id = query->id };
261
262     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
263     {
264         msg_Dbg (obj, "  current: %3"PRId32", default: %3"PRId32,
265                  ctrl.value, query->default_value);
266         val.i_int = ctrl.value;
267         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
268     }
269     val.i_int = query->minimum;
270     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
271     val.i_int = query->maximum;
272     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
273     if (query->step != 1)
274     {
275         val.i_int = query->step;
276         var_Change (obj, c->name, VLC_VAR_SETSTEP, &val, NULL);
277     }
278     val.i_int = query->default_value;
279     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
280     return c;
281 }
282
283 static vlc_v4l2_ctrl_t *ControlAddBoolean (vlc_object_t *obj, int fd,
284                                            const struct v4l2_queryctrl *query)
285 {
286     msg_Dbg (obj, " boolean  %s (%08"PRIX32")", query->name, query->id);
287     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
288         return NULL;
289
290     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
291     if (unlikely(c == NULL))
292         return NULL;
293
294     if (var_Create (obj, c->name, VLC_VAR_BOOL | VLC_VAR_ISCOMMAND))
295     {
296         free (c);
297         return NULL;
298     }
299
300     vlc_value_t val;
301     struct v4l2_control ctrl = { .id = query->id };
302
303     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
304     {
305         msg_Dbg (obj, "  current: %s, default: %s",
306                  ctrl.value ? " true" : "false",
307                  query->default_value ? " true" : "false");
308         val.b_bool = ctrl.value;
309         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
310     }
311     val.b_bool = query->default_value;
312     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
313     return c;
314 }
315
316 static vlc_v4l2_ctrl_t *ControlAddMenu (vlc_object_t *obj, int fd,
317                                         const struct v4l2_queryctrl *query)
318 {
319     msg_Dbg (obj, " menu     %s (%08"PRIX32")", query->name, query->id);
320     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
321         return NULL;
322
323     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
324     if (unlikely(c == NULL))
325         return NULL;
326
327     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_HASCHOICE
328                                                   | VLC_VAR_ISCOMMAND))
329     {
330         free (c);
331         return NULL;
332     }
333
334     vlc_value_t val;
335     struct v4l2_control ctrl = { .id = query->id };
336
337     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
338     {
339         msg_Dbg (obj, "  current: %"PRId32", default: %"PRId32,
340                  ctrl.value, query->default_value);
341         val.i_int = ctrl.value;
342         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
343     }
344     val.b_bool = query->default_value;
345     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
346     val.i_int = query->minimum;
347     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
348     val.i_int = query->maximum;
349     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
350
351     /* Import menu choices */
352     for (uint_fast32_t idx = query->minimum;
353          idx <= (uint_fast32_t)query->maximum;
354          idx++)
355     {
356         struct v4l2_querymenu menu = { .id = query->id, .index = idx };
357
358         if (v4l2_ioctl (fd, VIDIOC_QUERYMENU, &menu) < 0)
359             continue;
360         msg_Dbg (obj, "  choice %"PRIu32") %s", menu.index, menu.name);
361
362         vlc_value_t text;
363         val.i_int = menu.index;
364         text.psz_string = (char *)menu.name;
365         var_Change (obj, c->name, VLC_VAR_ADDCHOICE, &val, &text);
366     }
367     return c;
368 }
369
370 static vlc_v4l2_ctrl_t *ControlAddButton (vlc_object_t *obj, int fd,
371                                           const struct v4l2_queryctrl *query)
372 {
373     msg_Dbg (obj, " button   %s (%08"PRIX32")", query->name, query->id);
374     if (query->flags & CTRL_FLAGS_IGNORE)
375         return NULL;
376
377     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
378     if (unlikely(c == NULL))
379         return NULL;
380
381     if (var_Create (obj, c->name, VLC_VAR_VOID | VLC_VAR_ISCOMMAND))
382         return NULL;
383     (void) fd;
384     return c;
385 }
386
387 static vlc_v4l2_ctrl_t *ControlAddClass (vlc_object_t *obj, int fd,
388                                          const struct v4l2_queryctrl *query)
389 {
390     msg_Dbg (obj, "control class %s:", query->name);
391     (void) fd;
392     return NULL;
393 }
394
395 static vlc_v4l2_ctrl_t *ControlAddUnknown (vlc_object_t *obj, int fd,
396                                            const struct v4l2_queryctrl *query)
397 {
398     msg_Dbg (obj, " unknown %s (%08"PRIX32")", query->name, query->id);
399     msg_Warn (obj, "  unknown control type %u", (unsigned)query->type);
400     (void) fd;
401     return NULL;
402 }
403
404 typedef vlc_v4l2_ctrl_t *(*ctrl_type_cb) (vlc_object_t *, int,
405                                           const struct v4l2_queryctrl *);
406
407 /**
408  * Lists all user-class v4l2 controls, sets them to the user specified
409  * value and create the relevant variables to enable run-time changes.
410  */
411 vlc_v4l2_ctrl_t *ControlsInit (vlc_object_t *obj, int fd)
412 {
413     /* A list of controls that can be modified at run-time is stored in the
414      * "controls" variable. The V4L2 controls dialog can be built from this. */
415     var_Create (obj, "controls", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE);
416
417     static const ctrl_type_cb handlers[] =
418     {
419         [V4L2_CTRL_TYPE_INTEGER] = ControlAddInteger,
420         [V4L2_CTRL_TYPE_BOOLEAN] = ControlAddBoolean,
421         [V4L2_CTRL_TYPE_MENU] = ControlAddMenu,
422         [V4L2_CTRL_TYPE_BUTTON] = ControlAddButton,
423         [V4L2_CTRL_TYPE_CTRL_CLASS] = ControlAddClass,
424     };
425
426     vlc_v4l2_ctrl_t *list = NULL;
427     struct v4l2_queryctrl query;
428
429     query.id = V4L2_CTRL_FLAG_NEXT_CTRL;
430     while (v4l2_ioctl (fd, VIDIOC_QUERYCTRL, &query) >= 0)
431     {
432         ctrl_type_cb handler = NULL;
433         if (query.type < (sizeof (handlers) / sizeof (handlers[0])))
434             handler = handlers[query.type];
435         if (handler == NULL)
436             handler = ControlAddUnknown;
437
438         vlc_v4l2_ctrl_t *c = handler (obj, fd, &query);
439         if (c != NULL)
440         {
441             vlc_value_t val, text;
442
443             var_AddCallback (obj, c->name, ControlSetCallback, c);
444             text.psz_string = (char *)query.name;
445             var_Change (obj, c->name, VLC_VAR_SETTEXT, &text, NULL);
446             val.i_int = query.id;
447             text.psz_string = (char *)c->name;
448             var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
449
450             c->next = list;
451             list = c;
452         }
453         query.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
454     }
455
456     /* Set well-known controls from VLC configuration */
457     for (vlc_v4l2_ctrl_t *ctrl = list; ctrl != NULL; ctrl = ctrl->next)
458     {
459         if (!CTRL_CID_KNOWN (ctrl->id))
460             continue;
461
462         char varname[sizeof (CFG_PREFIX) + sizeof (ctrl->name) - 1];
463         sprintf (varname, CFG_PREFIX"%s", ctrl->name);
464
465         int64_t val = var_InheritInteger (obj, varname);
466         if (val == -1)
467             continue; /* the VLC default value: "do not modify" */
468         ControlSet (ctrl, val);
469     }
470
471     /* Set any control from the VLC configuration control string */
472     ControlsSetFromString (obj, list);
473
474     /* Add a control to reset all controls to their default values */
475     {
476         vlc_value_t val, text;
477
478         var_Create (obj, "reset", VLC_VAR_VOID | VLC_VAR_ISCOMMAND);
479         val.psz_string = _("Reset defaults");
480         var_Change (obj, "reset", VLC_VAR_SETTEXT, &val, NULL);
481         val.i_int = -1;
482
483         text.psz_string = (char *)"reset";
484         var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
485         var_AddCallback (obj, "reset", ControlsResetCallback, list);
486     }
487     if (var_InheritBool (obj, CFG_PREFIX"controls-reset"))
488         ControlsReset (list);
489
490     return list;
491 }
492
493 void ControlsDeinit (vlc_object_t *obj, vlc_v4l2_ctrl_t *list)
494 {
495     var_DelCallback (obj, "reset", ControlsResetCallback, list);
496     var_Destroy (obj, "reset");
497
498     while (list != NULL)
499     {
500         vlc_v4l2_ctrl_t *next = list->next;
501
502         var_DelCallback (obj, list->name, ControlSetCallback, list);
503         var_Destroy (obj, list->name);
504         free (list);
505         list = next;
506     }
507
508     var_Destroy (obj, "controls");
509 }