[RFC] V4L2 Controls State Store/Restore File Format v.0.0.2 and test app

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Ok so here is a new version for the controls state store/restore
functionality and a test app implementing the functionality.
I've removed the need for control order inside the file since this can
be done inside the application.
Simply  ordering by id is enough for most auto/absolute control pairs
to work since auto controls will almost in all cases have a lower id
than the absolute counterpart, the exception being
V4L2_CID_FOCUS_AUTO, in the included test app I don't take any special
attention for this case since it would increase complexity.
I also could not test string and 64 bit integer controls since i don't
have any hardware that supports them.

Best Regards,
Paulo
[RFC] V4L2 Controls State Store/Restore File Format

AUTHORS: Paulo Assis <pj.assis@xxxxxxxxx>

VERSION: 0.0.2

ABSTRACT

This document proposes a standard for the file format used by v4l2 applications to store/restore the controls state.
This unified file format allows sharing control profiles between applications, making it much easier on both developers and users.

INTRODUCTION

V4l2 controls can be divided by classes and types.
Controls in different classes are not dependent between themselves, on the other end if two controls belong to the same class they may or may not be dependent.
A good example are automatic controls and their absolute counterparts, e.g.: V4L2_CID_AUTOGAIN and V4L2_CID_GAIN.
Controls must be set following the dependency order, automatic controls must be set first or else setting the absolute value may fail, when that was not the intended behavior (auto disabled).
After a quick analyses of the v4l2 controls, we are left to conclude that auto controls have in almost all cases a lower ID order than the corresponding absolute counterparts, the exception being V4L2_CID_FOCUS_AUTO.
So by ordering controls by id, with the above exception, should be enough for things to work without problems.
Ordering by id will also automatically order the controls by class allowing the usage of the extension control API. 

Button controls are stateless so they can't be stored.
Relative controls are also in effect stateless, since they will always depend on their current state.
In fact only controls with Read/Write permissions can be stored and restored so only these belong in the scope of this document.

The proposed file format takes all of this into account and tries to make implementation of both store and restore functionality as easy as possible.

FILE FORMAT

The proposed file format is a regular text file with lines terminating with the newline character '\n'. 
Comments can be inserted by adding '#' at the beginning of the line, and can safely be ignored when parsing the file.

FILE EXTENSION

Although not much relevant, the file extension makes it easy to visually identify the file type and  also for applications to list relevant files, so we propose that v4l2 control state files be terminated by the suffix: ".v4l"
 
FILE HEADER

The file must always start with a commented line containing the file type identification and the version of this document on which it is based:

#V4L2/CTRL/0.0.2
 
Additionally it may contain extra information like the application name that generated the file and for usb devices the vid and pid of the device to whom the controls relate in hexadecimal notation:

APP_NAME{"application name"}
VID{0x00}
PID{0x00}

CONTROLS DATA

The controls do not require any special ordering in the file since this can be achieved in the application implementing the functionality.
In any case ordering by ID is natural as this is the usual way VIDIOC_QUERYCTRL is done.

Each control must have is data set in a single line nad it will depend on the control type:
 
1- V4L2_CTRL_TYPE_STRING:
ID{0x00000000};CHK{min:max:step:0}=STR{"string_value"}

2- V4L2_CTRL_TYPE_INTEGER64:
ID{0x00000000};CHK{0:0:0:0}=VAL64{value64}

3- V4L2_CTRL_TYPE_INTEGER; V4L2_CTRL_TYPE_BOOLEAN; V4L2_CTRL_TYPE_MENU:
ID{0x0000};CHK{min:max:step:def};EXT{[0|>0}=VAL{value}

The ID key is the control v4l2 id in hex notation.

The CHK key is used to match the control stored in file to the one we are trying to set on the device.
Controls on different devices may have identical ID's but is unlikely that the correspondent values remain the same. All values are in decimal notation and correspond to the controls reported values.
min- minimum
max- maximum
step: step
def: default

These are specially important for integer types but are not relevant for 64 bit controls.
For string controls max can be used for the string allocation (max+1).
 
The STR key contains the string control value.

The VAL64 key contains a 64 bit signed integer value.

The VAL key contains a 32 bit signed integer value.

This format should cover all current control types, and is still flexible enough to allow new types in the future. 



/* Store/Restore V4L2 Controls File Format example
 * 
 *   Paulo Assis <pj.assis@xxxxxxxxx>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <errno.h>
#include <getopt.h>

static char device_name[20] = "/dev/video0";
static char filename[100];
static int store = 0;

/* 
 * Control structure where we old the control values.
 */
typedef struct _Control
{
    struct v4l2_queryctrl control;
    int32_t class;
    int32_t value; //also used for string max size
    int64_t value64;
    char *string;
    struct _Control *next;
} Control;

/*
 * returns a Control structure NULL terminated linked list
 * with all of the device controls with Read/Write permissions.
 * These are the only ones that we can store/restore.
 * Also sets num_ctrls with the controls count.
 */
Control *get_control_list(int hdevice, int *num_ctrls)
{
    int ret=0;
    Control *first   = NULL;
    Control *current = NULL;
    Control *control = NULL;
    
    int n = 0;
    struct v4l2_queryctrl queryctrl={0}; 

    queryctrl.id = 0 | V4L2_CTRL_FLAG_NEXT_CTRL;
    
    if ((ret=ioctl (hdevice, VIDIOC_QUERYCTRL, &queryctrl)) == 0)
    {
        // The driver supports the V4L2_CTRL_FLAG_NEXT_CTRL flag
        queryctrl.id = 0;
        int currentctrl= queryctrl.id;
        queryctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;

        while((ret = ioctl(hdevice, VIDIOC_QUERYCTRL, &queryctrl)), ret ? errno != EINVAL : 1) 
        {
            // Prevent infinite loop for buggy NEXT_CTRL implementations
            if(ret && queryctrl.id <= currentctrl) 
            {
                currentctrl++;
                goto next_control;
            }
            currentctrl = queryctrl.id;
    
            // skip if control is disabled, read only, write only or failed
            if (ret || 
                (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)  || 
                (queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) ||
                (queryctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY))
                goto next_control;
    
            // Add the control to the linked list
            control = calloc (1, sizeof(Control));
            memcpy(&(control->control), &queryctrl, sizeof(struct v4l2_queryctrl));
            control->class = (control->control.id & 0xFFFF0000);
            //allocate a string with max size if needed
            if(control->control.type == V4L2_CTRL_TYPE_STRING)
                control->string = calloc(control->control.maximum + 1, sizeof(char));
            else
                control->string = NULL;
                
            if(first != NULL)
            {
                current->next = control;
                current = control;
            }
            else
            {
                first = control;
                current = first;
            }
            
            n++;
    
next_control:
            queryctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
        }
    }
    else
    {
        printf("NEXT_CTRL flag not supported\n");
        int currentctrl;
        for(currentctrl = V4L2_CID_BASE; currentctrl < V4L2_CID_LASTP1; currentctrl++) 
        {
            queryctrl.id = currentctrl;
            ret = ioctl(hdevice, VIDIOC_QUERYCTRL, &queryctrl);
    
            if (ret || 
                (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)  || 
                (queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) ||
                (queryctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY))
                continue;
            // Add the control to the linked list
            control = calloc (1, sizeof(Control));
            memcpy(&(control->control), &queryctrl, sizeof(struct v4l2_queryctrl));
            control->class = 0x00980000;
            
            if(first != NULL)
            {
                current->next = control;
                current = control;
            }
            else
            {
                first = control;
                current = first;
            }
            
            n++;
        }
        
        for (currentctrl = V4L2_CID_PRIVATE_BASE;;queryctrl.id++) 
        {
            ret = ioctl(hdevice, VIDIOC_QUERYCTRL, &queryctrl);
            if(ret)
                break;
            else if ((queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)  || 
                     (queryctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) ||
                     (queryctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY))
                continue;
            // Add the control to the linked list
            control = calloc (1, sizeof(Control));
            memcpy(&(control->control), &queryctrl, sizeof(struct v4l2_queryctrl));
            control->class = 0x00980000;
            
            if(first != NULL)
            {
                current->next = control;
                current = control;
            }
            else
            {
                first = control;
                current = first;
            }

            n++;
        }
    }
    
    *num_ctrls = n;
    return first;
}

/*
 * Returns the Control structure corresponding to control id,
 * from the control list.
 */
static Control *get_ctrl_by_id(Control *control_list, int id)
{
    Control *current = control_list;
    Control *next = current->next;
    while (next != NULL)
    {
        if(current->control.id == id)
            return (current);
        
        current = next;
        next = current->next;
    }
    if(current->control.id == id)
        return (current);
    else//no id match
        return(NULL);
}

/*
 * Goes through the control list and gets the controls current values
 */
static void get_ctrl_values (int hdevice, Control *controls, int num_controls)
{
    int ret = 0;
    struct v4l2_ext_control clist[num_controls];
    Control *current = controls;
    Control *next = current->next;
    int count = 0;
    int i = 0;
    int done = 0;
    
    while(!done)
    {
        clist[count].id = current->control.id;
        clist[count].size = 0;
        if(current->control.type == V4L2_CTRL_TYPE_STRING)
        {
            clist[count].size = current->control.maximum + 1;
            clist[count].string = current->string; 
        }
        count++;
        
        if((next == NULL) || (next->class != current->class))
        {
            struct v4l2_ext_controls ctrls = {0};
            ctrls.ctrl_class = current->class;
            ctrls.count = count;
            ctrls.controls = clist;
            ret = ioctl(hdevice, VIDIOC_G_EXT_CTRLS, &ctrls);
            if(ret)
            {
                printf("VIDIOC_G_EXT_CTRLS failed\n");
                struct v4l2_control ctrl;
                //get the controls one by one
                if( current->class == V4L2_CTRL_CLASS_USER)
                {
                    printf("   using VIDIOC_G_CTRL for user class controls\n");
                    for(i=0; i < count; i++)
                    {
                        ctrl.id = clist[i].id;
                        ctrl.value = 0;
                        ret = ioctl(hdevice, VIDIOC_G_CTRL, &ctrl);
                        if(ret)
                            continue;
                        clist[i].value = ctrl.value;
                    }
                }
                else
                {
                    printf("   using VIDIOC_G_EXT_CTRLS on single controls for class: 0x%08x\n", 
                        current->class);
                    for(i=0;i < count; i++)
                    {
                        ctrls.count = 1;
                        ctrls.controls = &clist[i];
                        ret = ioctl(hdevice, VIDIOC_S_EXT_CTRLS, &ctrls);
                        if(ret)
                            printf("control id: 0x%08x failed to set (error %i)\n",
                                clist[i].id, ret);
                    }
                }
            }
            
            //fill in the values on the control list
            for(i=0; i<count; i++)
            {
                Control *ctrl = get_ctrl_by_id(controls, clist[i].id);
                if(!ctrl)
                {
                    printf("couldn't get control for id: %i\n", clist[i].id);
                    continue;
                }
                switch(ctrl->control.type)
                {
                    case V4L2_CTRL_TYPE_STRING:
                        //string gets set on VIDIOC_G_EXT_CTRLS
                        //add the maximum size to value
                        ctrl->value = clist[i].size;
                        break;
                    case V4L2_CTRL_TYPE_INTEGER64:
                        ctrl->value64 = clist[i].value64;
                        break;
                    default:
                        ctrl->value = clist[i].value;
                        //printf("control %i [0x%08x] = %i\n", 
                        //    i, clist[i].id, clist[i].value);
                        break;
                }
            }
            
            count = 0;
            
            if(next == NULL)
                done = 1;
        }
        
        if(!done)
        {
            current = next;
            next = current->next;
        }
    }
    
}

/*
 * Goes through the control list and tries to set the controls values 
 */
static void set_ctrl_values (int hdevice, Control *controls, int num_controls)
{
    int ret = 0;
    struct v4l2_ext_control clist[num_controls];
    Control *current = controls;
    Control *next = current->next;
    int count = 0;
    int i = 0;
    int done = 0;
    
    while(!done)
    {
        clist[count].id = current->control.id;
        switch (current->control.type)
        {
            case V4L2_CTRL_TYPE_STRING:
                clist[count].size = current->value;
                clist[count].string = current->string;
                break;
            case V4L2_CTRL_TYPE_INTEGER64:
                clist[count].size = 0;
                clist[count].value64 = current->value64;
                break;
            default:
                clist[count].size = 0;
                clist[count].value = current->value;
                break;
        }
        count++;
        
        if((next == NULL) || (next->class != current->class))
        {
            struct v4l2_ext_controls ctrls = {0};
            ctrls.ctrl_class = current->class;
            ctrls.count = count;
            ctrls.controls = clist;
            ret = ioctl(hdevice, VIDIOC_S_EXT_CTRLS, &ctrls);
            if(ret)
            {
                printf("VIDIOC_S_EXT_CTRLS for multiple controls failed (error %i)\n", ret);
                struct v4l2_control ctrl;
                //set the controls one by one
                if( current->class == V4L2_CTRL_CLASS_USER)
                {
                    printf("   using VIDIOC_S_CTRL for user class controls\n");
                    for(i=0;i < count; i++)
                    {
                        ctrl.id = clist[i].id;
                        ctrl.value = clist[i].value;
                        ret = ioctl(hdevice, VIDIOC_S_CTRL, &ctrl);
                        if(ret)
                            printf("control id: 0x%08x failed to set (error %i)\n",
                                clist[i].id, ret);
                    }
                }
                else
                {
                    printf("   using VIDIOC_S_EXT_CTRLS on single controls for class: 0x%08x\n", 
                        current->class);
                    for(i=0;i < count; i++)
                    {
                        ctrls.count = 1;
                        ctrls.controls = &clist[i];
                        ret = ioctl(hdevice, VIDIOC_S_EXT_CTRLS, &ctrls);
                        if(ret)
                            printf("control id: 0x%08x failed to set (error %i)\n", 
                                clist[i].id, ret);
                    }
                }
            }
            
            count = 0;
            
            if(next == NULL)
                done = 1;
        }
       
        if(!done)
        {
            current = next;
            next = current->next;   
        }
    }
}

/*
 * frees the control list allocations
 */
static void free_control_list (Control *controls)
{
    Control *first = controls;
    Control *next = first->next;
    while (next != NULL)
    {
        if(first->string) free(first->string);
        free(first);
        first = next;
        next = first->next;
    }
    //clean the last one
    if(first->string) free(first->string);
    if(first) free(first);
    controls = NULL;
}

/*
 * Getopt option parser
 */
static void parse_options(int argc, char *argv[])
{
    int next_option = 0;
    int option_index = 0;
    const char short_options[]= "hd:s:r:";
    static struct option long_options[] =
    {
        {"help",    no_argument,       0, 'h'},
        {"device",  required_argument, 0, 'd'},
        {"store",   required_argument, 0, 's'},
        {"restore", required_argument, 0, 'r'},
        {0, 0, 0, 0}
    };
    
    while (next_option != -1) 
    {
        next_option = getopt_long (argc, argv, short_options,
                               long_options, &option_index);
        switch (next_option)
        {
            case 'h':   /* -h or --help */
                printf("USAGE: \n");
                printf("%s options\n", argv[0]);
                printf("OPTIONS: \n");
                printf("-h, --help                : prints this message\n");
                printf("-d, --device  devicename  : sets the device [default: /dev/video0]\n");
                printf("-s, --store   filename    : stores the controls state to filename\n");
                printf("-r, --restore filename    : restores the controls state from filename\n");
                exit(0);
                break;
            case 'd':
                strncpy(device_name, optarg, 20*sizeof(char)); 
                break;
            case 's':
                store = 1;
            case 'r':
                strncpy(filename, optarg, 100*sizeof(char)); 
                break;
            case '?':
                break;
            default:
                break;
        }
    }
}


int main(int argc, char *argv[])
{
    int hdevice = 0;
    int num_ctrls = 0;
    int i = 0;
    int major = 0, minor = 0, rev = 0;
    Control *control_list = NULL;
    Control *current = NULL;
    Control *next = NULL;
    FILE *fp = NULL;
    
    parse_options(argc, argv);
    
    if ((hdevice = open(device_name, O_RDWR | O_NONBLOCK, 0)) < 0) 
    {
        printf("ERROR opening V4L interface\n");
        return (-1);
    }
    
    control_list = get_control_list(hdevice, &num_ctrls);
    printf ("listed %i controles\n", num_ctrls);
    
    get_ctrl_values(hdevice, control_list, num_ctrls);
    
    current = control_list;
    next = current->next;
    
    if(store)
    {   
        printf ("storing controles to %s\n", filename);
        //save to file
        fp = fopen(filename, "w");
        if(!fp)
        {
            printf("Couldn't open %s for write\n", filename);
            goto finish;
        }
        
        //write header
        fprintf(fp, "#V4L2/CTRL/0.0.2\n");
        fprintf(fp, "APP{\"v4l2-re-store-ctrls example app\"}\n");
        //write control data
        fprintf(fp, "# control data\n");
        for(i=0; i<num_ctrls; i++)
        {
            fprintf(fp, "#%s\n", current->control.name);
            switch(current->control.type)
            {
                case V4L2_CTRL_TYPE_STRING :
                    fprintf(fp, "ID{0x%08x};CHK{%i:%i:%i:0}=STR{\"%s\"}\n",
                        current->control.id, 
                        current->control.minimum, 
                        current->control.maximum,
                        current->control.step,
                        current->string);
                    break;
                case V4L2_CTRL_TYPE_INTEGER64 :
                    fprintf(fp, "ID{0x%08x};CHK{0:0:0:0}=VAL64{%" PRId64 "}\n",
                        current->control.id, 
                        current->value64);
                    break;
                default :
                    fprintf(fp, "ID{0x%08x};CHK{%i:%i:%i:%i}=VAL{%i}\n",
                        current->control.id, 
                        current->control.minimum, 
                        current->control.maximum,
                        current->control.step,
                        current->control.default_value,
                        current->value);
                    break;
            }
            
            if(next == NULL)
                break;
            else 
            {
                current = next;
                next = current->next;
            }    
        }
    }
    else
    {
        printf ("restoring controles from %s\n", filename);
        //read from file
        //save to file
        fp = fopen(filename, "r");
        if(!fp)
        {
            printf("Couldn't open %s for read\n", filename);
            goto finish;
        }
        
        char line[200];
        if(fgets(line, sizeof(line), fp) != NULL)
        if(sscanf(line,"#V4L2/CTRL/%i.%i.%i", &major, &minor, &rev) == 3) {
            //check standard version if needed
        }
        else
        {
            printf("no valid header found\n");
            goto finish;
        }
            
        while (fgets(line, sizeof(line), fp) != NULL) 
        {
            int id = 0; 
            int min = 0, max = 0, step = 0, def = 0;
            int32_t val = 0;
            int64_t val64 = 0;
            
            if ((line[0]!='#') && (line[0]!='\n')) 
            {
                if(sscanf(line,"ID{0x%08x};CHK{%i:%i:%i:%i}=VAL{%i}",
                    &id, &min, &max, &step, &def, &val) == 6)
                {
                    current = get_ctrl_by_id(control_list, id);
                    if(current)
                    {
                        //check values
                        if(current->control.minimum == min &&
                           current->control.maximum == max &&
                           current->control.step == step &&
                           current->control.default_value == def)
                        {
                            current->value = val;
                            //printf("setting %s to %i\n",
                            //    current->control.name, val);
                        }
                    }
                }
                else if(sscanf(line,"ID{0x%08x};CHK{0:0:0:0}=VAL64{%" PRId64 "}",
                    &id, &val64) == 2)
                {
                    current = get_ctrl_by_id(control_list, id);
                    if(current)
                    {
                        current->value64 = val64;
                    }
                }
                else if(sscanf(line,"ID{0x%08x};CHK{%i:%i:%i:0}=STR{\"%*s\"}",
                    &id, &min, &max, &step) == 5)
                {
                    current = get_ctrl_by_id(control_list, id);
                    if(current)
                    {
                         //check values
                        if(current->control.minimum == min &&
                           current->control.maximum == max &&
                           current->control.step == step)
                        {
                            char str[max+1];
                            sscanf(line, "ID{0x%*x};CHK{%*i:%*i:%*i:0}==STR{\"%s\"}", str);
                            if(strlen(str) > max) //FIXME: should also check (minimum +N*step)
                            {
                                printf("string bigger than maximum buffer size");
                            }
                            else
                            {
                                //string size including terminating null character
                                current->value = strlen(str) + 1;
                                strcpy(current->string, str);
                            }
                        }
                    }
                }       
            }
        }
        
        set_ctrl_values(hdevice, control_list, num_ctrls);
    }

finish:
    if(fp)
    {
        fflush(fp);
        fsync(fileno(fp));
        fclose(fp);
    }
    free_control_list(control_list);
    close(hdevice);
}


[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux