]> git.sesse.net Git - vlc/blob - modules/access/v4l2/controls.c
V4L2: mark black-level obsolete (also kernel 2.6.26)
[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 <assert.h>
33 #include <sys/ioctl.h>
34
35 typedef struct vlc_v4l2_ctrl_name
36 {
37     const char name[20];
38     uint32_t cid;
39 } vlc_v4l2_ctrl_name_t;
40
41 /* NOTE: must be sorted by ID */
42 static const vlc_v4l2_ctrl_name_t controls[] =
43 {
44     { "brightness", V4L2_CID_BRIGHTNESS },
45     { "contrast", V4L2_CID_CONTRAST },
46     { "saturation", V4L2_CID_SATURATION },
47     { "hue", V4L2_CID_HUE },
48     { "audio-volume", V4L2_CID_AUDIO_VOLUME },
49     { "audio-balance", V4L2_CID_AUDIO_BALANCE },
50     { "audio-bass", V4L2_CID_AUDIO_BASS },
51     { "audio-treble", V4L2_CID_AUDIO_TREBLE },
52     { "audio-mute", V4L2_CID_AUDIO_MUTE },
53     { "audio-loudness", V4L2_CID_AUDIO_LOUDNESS },
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     /* TODO: add more standardized controls */
65 #define CTRL_CID_KNOWN(cid) \
66     ((((uint32_t)cid) - V4L2_CID_BRIGHTNESS) \
67         <= (V4L2_CID_VCENTER - V4L2_CID_BRIGHTNESS))
68 };
69
70 typedef struct vlc_v4l2_ctrl
71 {
72     int                   fd;
73     uint32_t              id;
74     enum v4l2_ctrl_type   type;
75     char                  name[32];
76     int32_t               default_value;
77     struct vlc_v4l2_ctrl *next;
78 } vlc_v4l2_ctrl_t;
79
80 static int ControlSet (const vlc_v4l2_ctrl_t *c, int_fast32_t value)
81 {
82     struct v4l2_control ctrl = {
83         .id = c->id,
84         .value = value,
85     };
86     if (v4l2_ioctl (c->fd, VIDIOC_S_CTRL, &ctrl) < 0)
87         return -1;
88     return 0;
89 }
90
91 static int ControlSet64 (const vlc_v4l2_ctrl_t *c, int64_t value)
92 {
93     struct v4l2_ext_control ext_ctrl = {
94         .id = c->id,
95         .size = 0,
96         .value64 = value,
97     };
98     struct v4l2_ext_controls ext_ctrls = {
99         .ctrl_class = V4L2_CTRL_ID2CLASS(c->id),
100         .count = 1,
101         .error_idx = 0,
102         .controls = &ext_ctrl,
103     };
104
105     if (v4l2_ioctl (c->fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls) < 0)
106         return -1;
107     return 0;
108 }
109
110 static int ControlSetCallback (vlc_object_t *obj, const char *var,
111                                vlc_value_t old, vlc_value_t cur, void *data)
112 {
113     const vlc_v4l2_ctrl_t *ctrl = data;
114     int ret;
115
116     switch (ctrl->type)
117     {
118         case V4L2_CTRL_TYPE_INTEGER:
119         case V4L2_CTRL_TYPE_MENU:
120         case V4L2_CTRL_TYPE_BITMASK:
121             ret = ControlSet (ctrl, cur.i_int);
122             break;
123         case V4L2_CTRL_TYPE_BOOLEAN:
124             ret = ControlSet (ctrl, cur.b_bool);
125             break;
126         case V4L2_CTRL_TYPE_BUTTON:
127             ret = ControlSet (ctrl, 0);
128             break;
129         case V4L2_CTRL_TYPE_INTEGER64:
130             ret = ControlSet64 (ctrl, cur.i_int);
131             break;
132         default:
133             assert (0);
134     }
135
136     if (ret)
137     {
138         msg_Err (obj, "cannot set control %s: %m", var);
139         return VLC_EGENERIC;
140     }
141     (void) old;
142     return VLC_SUCCESS;
143 }
144
145 static void ControlsReset (vlc_object_t *obj, vlc_v4l2_ctrl_t *list)
146 {
147     while (list != NULL)
148     {
149         switch (list->type)
150         {
151             case V4L2_CTRL_TYPE_INTEGER:
152             case V4L2_CTRL_TYPE_MENU:
153                 var_SetInteger (obj, list->name, list->default_value);
154                 break;
155             case V4L2_CTRL_TYPE_BOOLEAN:
156                 var_SetBool (obj, list->name, list->default_value);
157                 break;
158             default:;
159         }
160         list = list->next;
161     }
162 }
163
164 static int ControlsResetCallback (vlc_object_t *obj, const char *var,
165                                   vlc_value_t old, vlc_value_t cur, void *data)
166 {
167     ControlsReset (obj, data);
168     (void) var; (void) old; (void) cur;
169     return VLC_SUCCESS;
170 }
171
172 static void ControlsSetFromString (vlc_object_t *obj,
173                                    const vlc_v4l2_ctrl_t *list)
174 {
175     char *buf = var_InheritString (obj, CFG_PREFIX"set-ctrls");
176     if (buf == NULL)
177         return;
178
179     char *p = buf;
180     if (*p == '{')
181         p++;
182
183     char *end = strchr (p, '}');
184     if (end != NULL)
185         *end = '\0';
186 next:
187     while (p != NULL && *p)
188     {
189         const char *name, *value;
190
191         p += strspn (p, ", ");
192         name = p;
193         end = strchr (p, ',');
194         if (end != NULL)
195             *(end++) = '\0';
196         p = end; /* next name/value pair */
197
198         end = strchr (name, '=');
199         if (end == NULL)
200         {
201             /* TODO? support button controls that way? */
202             msg_Err (obj, "syntax error in \"%s\": missing '='", name);
203             continue;
204         }
205         *(end++) = '\0';
206         value = end;
207
208         for (const vlc_v4l2_ctrl_t *c = list; c != NULL; c = c->next)
209             if (!strcasecmp (name, c->name))
210                 switch (c->type)
211                 {
212                     case V4L2_CTRL_TYPE_INTEGER:
213                     case V4L2_CTRL_TYPE_BOOLEAN:
214                     case V4L2_CTRL_TYPE_MENU:
215                     {
216                         long val = strtol (value, &end, 0);
217                         if (*end)
218                         {
219                             msg_Err (obj, "syntax error in \"%s\": "
220                                      " not an integer", value);
221                             goto next;
222                         }
223                         ControlSet (c, val);
224                         break;
225                     }
226
227                     case V4L2_CTRL_TYPE_INTEGER64:
228                     {
229                         long long val = strtoll (value, &end, 0);
230                         if (*end)
231                         {
232                             msg_Err (obj, "syntax error in \"%s\": "
233                                      " not an integer", value);
234                             goto next;
235                         }
236                         ControlSet64 (c, val);
237                         break;
238                     }
239
240                     case V4L2_CTRL_TYPE_BITMASK:
241                     {
242                         unsigned long val = strtoul (value, &end, 0);
243                         if (*end)
244                         {
245                             msg_Err (obj, "syntax error in \"%s\": "
246                                      " not an integer", value);
247                             goto next;
248                         }
249                         ControlSet (c, val);
250                         break;
251                     }
252
253                     default:
254                         msg_Err (obj, "setting \"%s\" not supported", name);
255                         goto next;
256                 }
257
258         msg_Err (obj, "control \"%s\" not available", name);
259     }
260     free (buf);
261 }
262
263 static int cidcmp (const void *a, const void *b)
264 {
265     const uint32_t *id = a;
266     const vlc_v4l2_ctrl_name_t *name = b;
267
268     return (int32_t)(*id - name->cid);
269 }
270
271 /**
272  * Creates a VLC-V4L2 control structure:
273  * In particular, determines a name suitable for a VLC object variable.
274  * \param query V4L2 control query structure [IN]
275  * \return NULL on error
276  */
277 static vlc_v4l2_ctrl_t *ControlCreate (int fd,
278                                        const struct v4l2_queryctrl *query)
279 {
280     vlc_v4l2_ctrl_t *ctrl = malloc (sizeof (*ctrl));
281     if (unlikely(ctrl == NULL))
282         return NULL;
283
284     ctrl->fd = fd;
285     ctrl->id = query->id;
286     ctrl->type = query->type;
287
288     /* Search for a well-known control */
289     const vlc_v4l2_ctrl_name_t *known;
290     known = bsearch (&query->id, controls, sizeof (controls) / sizeof (*known),
291                      sizeof (*known), cidcmp);
292     if (known != NULL)
293         strcpy (ctrl->name, known->name);
294     else
295     /* Fallback to automatically-generated control name */
296     {
297         size_t i;
298         for (i = 0; query->name[i]; i++)
299         {
300             unsigned char c = query->name[i];
301             if (c == ' ')
302                 c = '_';
303             if (c < 128)
304                 c = tolower (c);
305             ctrl->name[i] = c;
306         }
307         ctrl->name[i] = '\0';
308     }
309
310     ctrl->default_value = query->default_value;
311     return ctrl;
312 }
313
314
315 #define CTRL_FLAGS_IGNORE \
316     (V4L2_CTRL_FLAG_DISABLED /* not implemented at all */ \
317     |V4L2_CTRL_FLAG_READ_ONLY /* value is constant */ \
318     |V4L2_CTRL_FLAG_VOLATILE /* value is (variable but) read-only */)
319
320 static vlc_v4l2_ctrl_t *ControlAddInteger (vlc_object_t *obj, int fd,
321                                            const struct v4l2_queryctrl *query)
322 {
323     msg_Dbg (obj, " integer  %s (%08"PRIX32")", query->name, query->id);
324     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
325         return NULL;
326
327     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
328     if (unlikely(c == NULL))
329         return NULL;
330
331     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
332     {
333         free (c);
334         return NULL;
335     }
336
337     vlc_value_t val;
338     struct v4l2_control ctrl = { .id = query->id };
339
340     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
341     {
342         msg_Dbg (obj, "  current: %3"PRId32", default: %3"PRId32,
343                  ctrl.value, query->default_value);
344         val.i_int = ctrl.value;
345         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
346     }
347     val.i_int = query->minimum;
348     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
349     val.i_int = query->maximum;
350     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
351     if (query->step != 1)
352     {
353         val.i_int = query->step;
354         var_Change (obj, c->name, VLC_VAR_SETSTEP, &val, NULL);
355     }
356     val.i_int = query->default_value;
357     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
358     return c;
359 }
360
361 static vlc_v4l2_ctrl_t *ControlAddBoolean (vlc_object_t *obj, int fd,
362                                            const struct v4l2_queryctrl *query)
363 {
364     msg_Dbg (obj, " boolean  %s (%08"PRIX32")", query->name, query->id);
365     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
366         return NULL;
367
368     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
369     if (unlikely(c == NULL))
370         return NULL;
371
372     if (var_Create (obj, c->name, VLC_VAR_BOOL | VLC_VAR_ISCOMMAND))
373     {
374         free (c);
375         return NULL;
376     }
377
378     vlc_value_t val;
379     struct v4l2_control ctrl = { .id = query->id };
380
381     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
382     {
383         msg_Dbg (obj, "  current: %s, default: %s",
384                  ctrl.value ? " true" : "false",
385                  query->default_value ? " true" : "false");
386         val.b_bool = ctrl.value;
387         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
388     }
389     val.b_bool = query->default_value;
390     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
391     return c;
392 }
393
394 static vlc_v4l2_ctrl_t *ControlAddMenu (vlc_object_t *obj, int fd,
395                                         const struct v4l2_queryctrl *query)
396 {
397     msg_Dbg (obj, " menu     %s (%08"PRIX32")", query->name, query->id);
398     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
399         return NULL;
400
401     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
402     if (unlikely(c == NULL))
403         return NULL;
404
405     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_HASCHOICE
406                                                   | VLC_VAR_ISCOMMAND))
407     {
408         free (c);
409         return NULL;
410     }
411
412     vlc_value_t val;
413     struct v4l2_control ctrl = { .id = query->id };
414
415     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
416     {
417         msg_Dbg (obj, "  current: %"PRId32", default: %"PRId32,
418                  ctrl.value, query->default_value);
419         val.i_int = ctrl.value;
420         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
421     }
422     val.b_bool = query->default_value;
423     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
424     val.i_int = query->minimum;
425     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
426     val.i_int = query->maximum;
427     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
428
429     /* Import menu choices */
430     for (uint_fast32_t idx = query->minimum;
431          idx <= (uint_fast32_t)query->maximum;
432          idx++)
433     {
434         struct v4l2_querymenu menu = { .id = query->id, .index = idx };
435
436         if (v4l2_ioctl (fd, VIDIOC_QUERYMENU, &menu) < 0)
437             continue;
438         msg_Dbg (obj, "  choice %"PRIu32") %s", menu.index, menu.name);
439
440         vlc_value_t text;
441         val.i_int = menu.index;
442         text.psz_string = (char *)menu.name;
443         var_Change (obj, c->name, VLC_VAR_ADDCHOICE, &val, &text);
444     }
445     return c;
446 }
447
448 static vlc_v4l2_ctrl_t *ControlAddButton (vlc_object_t *obj, int fd,
449                                           const struct v4l2_queryctrl *query)
450 {
451     msg_Dbg (obj, " button   %s (%08"PRIX32")", query->name, query->id);
452     if (query->flags & CTRL_FLAGS_IGNORE)
453         return NULL;
454
455     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
456     if (unlikely(c == NULL))
457         return NULL;
458
459     if (var_Create (obj, c->name, VLC_VAR_VOID | VLC_VAR_ISCOMMAND))
460         return NULL;
461     (void) fd;
462     return c;
463 }
464
465 static vlc_v4l2_ctrl_t *ControlAddInteger64 (vlc_object_t *obj, int fd,
466                                             const struct v4l2_queryctrl *query)
467 {
468     msg_Dbg (obj, " 64-bits  %s (%08"PRIX32")", query->name, query->id);
469     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
470         return NULL;
471
472     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
473     if (unlikely(c == NULL))
474         return NULL;
475
476     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
477     {
478         free (c);
479         return NULL;
480     }
481
482     return c;
483 }
484
485 static vlc_v4l2_ctrl_t *ControlAddClass (vlc_object_t *obj, int fd,
486                                          const struct v4l2_queryctrl *query)
487 {
488     msg_Dbg (obj, "control class %s:", query->name);
489     (void) fd;
490     return NULL;
491 }
492
493 static vlc_v4l2_ctrl_t *ControlAddBitMask (vlc_object_t *obj, int fd,
494                                            const struct v4l2_queryctrl *query)
495 {
496     msg_Dbg (obj, " bit mask %s (%08"PRIX32")", query->name, query->id);
497     if (query->flags & (CTRL_FLAGS_IGNORE | V4L2_CTRL_FLAG_WRITE_ONLY))
498         return NULL;
499
500     vlc_v4l2_ctrl_t *c = ControlCreate (fd, query);
501     if (unlikely(c == NULL))
502         return NULL;
503
504     if (var_Create (obj, c->name, VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND))
505     {
506         free (c);
507         return NULL;
508     }
509
510     vlc_value_t val;
511     struct v4l2_control ctrl = { .id = query->id };
512
513     if (v4l2_ioctl (fd, VIDIOC_G_CTRL, &ctrl) >= 0)
514     {
515         msg_Dbg (obj, "  current: 0x%08"PRIX32", default: 0x%08"PRIX32,
516                  ctrl.value, query->default_value);
517         val.i_int = ctrl.value;
518         var_Change (obj, c->name, VLC_VAR_SETVALUE, &val, NULL);
519     }
520     val.i_int = 0;
521     var_Change (obj, c->name, VLC_VAR_SETMIN, &val, NULL);
522     val.i_int = (uint32_t)query->maximum;
523     var_Change (obj, c->name, VLC_VAR_SETMAX, &val, NULL);
524     val.i_int = query->default_value;
525     var_Change (obj, c->name, VLC_VAR_SETDEFAULT, &val, NULL);
526     return c;
527 }
528
529 static vlc_v4l2_ctrl_t *ControlAddUnknown (vlc_object_t *obj, int fd,
530                                            const struct v4l2_queryctrl *query)
531 {
532     msg_Dbg (obj, " unknown %s (%08"PRIX32")", query->name, query->id);
533     msg_Warn (obj, "  unknown control type %u", (unsigned)query->type);
534     (void) fd;
535     return NULL;
536 }
537
538 typedef vlc_v4l2_ctrl_t *(*ctrl_type_cb) (vlc_object_t *, int,
539                                           const struct v4l2_queryctrl *);
540
541 /**
542  * Lists all user-class v4l2 controls, sets them to the user specified
543  * value and create the relevant variables to enable run-time changes.
544  */
545 vlc_v4l2_ctrl_t *ControlsInit (vlc_object_t *obj, int fd)
546 {
547     /* A list of controls that can be modified at run-time is stored in the
548      * "controls" variable. The V4L2 controls dialog can be built from this. */
549     var_Create (obj, "controls", VLC_VAR_INTEGER | VLC_VAR_HASCHOICE);
550
551     static const ctrl_type_cb handlers[] =
552     {
553         [V4L2_CTRL_TYPE_INTEGER] = ControlAddInteger,
554         [V4L2_CTRL_TYPE_BOOLEAN] = ControlAddBoolean,
555         [V4L2_CTRL_TYPE_MENU] = ControlAddMenu,
556         [V4L2_CTRL_TYPE_BUTTON] = ControlAddButton,
557         [V4L2_CTRL_TYPE_INTEGER64] = ControlAddInteger64,
558         [V4L2_CTRL_TYPE_CTRL_CLASS] = ControlAddClass,
559         [V4L2_CTRL_TYPE_BITMASK] = ControlAddBitMask,
560     };
561
562     vlc_v4l2_ctrl_t *list = NULL;
563     struct v4l2_queryctrl query;
564
565     query.id = V4L2_CTRL_FLAG_NEXT_CTRL;
566     while (v4l2_ioctl (fd, VIDIOC_QUERYCTRL, &query) >= 0)
567     {
568         ctrl_type_cb handler = NULL;
569         if (query.type < (sizeof (handlers) / sizeof (handlers[0])))
570             handler = handlers[query.type];
571         if (handler == NULL)
572             handler = ControlAddUnknown;
573
574         vlc_v4l2_ctrl_t *c = handler (obj, fd, &query);
575         if (c != NULL)
576         {
577             vlc_value_t val, text;
578
579             var_AddCallback (obj, c->name, ControlSetCallback, c);
580             text.psz_string = (char *)query.name;
581             var_Change (obj, c->name, VLC_VAR_SETTEXT, &text, NULL);
582             val.i_int = query.id;
583             text.psz_string = (char *)c->name;
584             var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
585
586             c->next = list;
587             list = c;
588         }
589         query.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
590     }
591
592     /* Set well-known controls from VLC configuration */
593     for (vlc_v4l2_ctrl_t *ctrl = list; ctrl != NULL; ctrl = ctrl->next)
594     {
595         if (!CTRL_CID_KNOWN (ctrl->id))
596             continue;
597
598         char varname[sizeof (CFG_PREFIX) + sizeof (ctrl->name) - 1];
599         sprintf (varname, CFG_PREFIX"%s", ctrl->name);
600
601         int64_t val = var_InheritInteger (obj, varname);
602         if (val == -1)
603             continue; /* the VLC default value: "do not modify" */
604         ControlSet (ctrl, val); /* NOTE: all known are integers or booleans */
605     }
606
607     /* Set any control from the VLC configuration control string */
608     ControlsSetFromString (obj, list);
609
610     /* Add a control to reset all controls to their default values */
611     {
612         vlc_value_t val, text;
613
614         var_Create (obj, "reset", VLC_VAR_VOID | VLC_VAR_ISCOMMAND);
615         val.psz_string = _("Reset defaults");
616         var_Change (obj, "reset", VLC_VAR_SETTEXT, &val, NULL);
617         val.i_int = -1;
618
619         text.psz_string = (char *)"reset";
620         var_Change (obj, "controls", VLC_VAR_ADDCHOICE, &val, &text);
621         var_AddCallback (obj, "reset", ControlsResetCallback, list);
622     }
623     if (var_InheritBool (obj, CFG_PREFIX"controls-reset"))
624         ControlsReset (obj, list);
625
626     return list;
627 }
628
629 void ControlsDeinit (vlc_object_t *obj, vlc_v4l2_ctrl_t *list)
630 {
631     var_DelCallback (obj, "reset", ControlsResetCallback, list);
632     var_Destroy (obj, "reset");
633
634     while (list != NULL)
635     {
636         vlc_v4l2_ctrl_t *next = list->next;
637
638         var_DelCallback (obj, list->name, ControlSetCallback, list);
639         var_Destroy (obj, list->name);
640         free (list);
641         list = next;
642     }
643
644     var_Destroy (obj, "controls");
645 }