//Merci à https://stackoverflow.com/questions/278112/webcam-library-for-c-on-linux
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/types.h>
#include <libv4l2.h>
#include <linux/videodev2.h>

#define COMMON_V4L2_CLEAR(x) memset(&(x), 0, sizeof(x))

#define WRITE_PPM_ERR     12

typedef struct {
    void *start;
    size_t length;
} CommonV4l2_Buffer;

typedef struct {
    int fd;
    CommonV4l2_Buffer *buffers;
    struct v4l2_buffer buf;
    unsigned int n_buffers;
} CommonV4l2;
/*============================================================================*/
typedef struct
{
    s16 width;        // largeur de l'image en pixels
     s16 height;
    u8 nchannels;    // nombre de channels
    u8* pixels;     // tableau de pixels
    u32 len;        // nombre d'octets en tout
} PIX;
PIX lastframe;
/*============================================================================*/
CommonV4l2 common_v4l2;
struct buffer *buffers;
/*============================================================================*/
void print_error(const int exit_status, const int err)
{
    char* msg = strerror(err);
    printf("%s\n", msg);
    _exit(exit_status);
}
/*============================================================================*/
void CommonV4l2_xioctl(int fh, unsigned long int request, void *arg)
{
    int r;
    do {
        r = v4l2_ioctl(fh, request, arg);
    } while (r == -1 && ((errno == EINTR) || (errno == EAGAIN)));
    if (r == -1) {
        fprintf(stderr, "error %d, %s\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
}
/*============================================================================*/
void CommonV4l2_init(CommonV4l2 *this, char *dev_name, unsigned int x_res, unsigned int y_res) {
    enum v4l2_buf_type type;
    struct v4l2_format fmt;
    struct v4l2_requestbuffers req;
    unsigned int i;

    this->fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
    if (this->fd < 0) {perror("Cannot open device");exit(EXIT_FAILURE);}
    COMMON_V4L2_CLEAR(fmt);
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width        = x_res;
    fmt.fmt.pix.height        = y_res;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
    fmt.fmt.pix.field        = V4L2_FIELD_INTERLACED;
    CommonV4l2_xioctl(this->fd, VIDIOC_S_FMT, &fmt);
    if ((fmt.fmt.pix.width != x_res) || (fmt.fmt.pix.height != y_res))
        printf("Warning: driver is sending image at %dx%d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    COMMON_V4L2_CLEAR(req);
    req.count = 2;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    CommonV4l2_xioctl(this->fd, VIDIOC_REQBUFS, &req);
    this->buffers = calloc(req.count, sizeof(*this->buffers));
    for (this->n_buffers = 0; this->n_buffers < req.count; ++this->n_buffers) {
        COMMON_V4L2_CLEAR(this->buf);
        this->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        this->buf.memory = V4L2_MEMORY_MMAP;
        this->buf.index = this->n_buffers;
        CommonV4l2_xioctl(this->fd, VIDIOC_QUERYBUF, &this->buf);
        this->buffers[this->n_buffers].length = this->buf.length;
        this->buffers[this->n_buffers].start = v4l2_mmap(NULL, this->buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, this->buf.m.offset);
        if (MAP_FAILED == this->buffers[this->n_buffers].start) {perror("mmap");exit(EXIT_FAILURE);}
    }
    for (i = 0; i < this->n_buffers; ++i) {
        COMMON_V4L2_CLEAR(this->buf);
        this->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        this->buf.memory = V4L2_MEMORY_MMAP;
        this->buf.index = i;
        CommonV4l2_xioctl(this->fd, VIDIOC_QBUF, &this->buf);
    }
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    CommonV4l2_xioctl(this->fd, VIDIOC_STREAMON, &type);
}
/*============================================================================*/
void CommonV4l2_update_image(CommonV4l2 *this) {
    fd_set fds;
    int r;
    struct timeval tv;

    do {
        FD_ZERO(&fds);
        FD_SET(this->fd, &fds);

        tv.tv_sec = 2; // Timeout
        tv.tv_usec = 0;

        r = select(this->fd + 1, &fds, NULL, NULL, &tv);
    } while ((r == -1 && (errno == EINTR)));
    if (r == -1) {perror("select");exit(EXIT_FAILURE);}
    COMMON_V4L2_CLEAR(this->buf);
    this->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    this->buf.memory = V4L2_MEMORY_MMAP;
    CommonV4l2_xioctl(this->fd, VIDIOC_DQBUF, &this->buf);
    CommonV4l2_xioctl(this->fd, VIDIOC_QBUF, &this->buf);
}
/*============================================================================*/
char* CommonV4l2_get_image(CommonV4l2 *this) {
    return ((char *)this->buffers[this->buf.index].start);
}
/*============================================================================*/
size_t CommonV4l2_get_image_size(CommonV4l2 *this) {
    return this->buffers[this->buf.index].length;
}
/*============================================================================*/
void CommonV4l2_deinit(CommonV4l2 *this) {
    unsigned int i;
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    CommonV4l2_xioctl(this->fd, VIDIOC_STREAMOFF, &type);
    for (i = 0; i < this->n_buffers; ++i){
        //printf("buffer %d\n",i);
        v4l2_munmap(this->buffers[i].start, this->buffers[i].length);
    }

    v4l2_close(this->fd);
    free(this->buffers);
}
/*image couleur 3 channels ===================================================*/
static void save_ppm(u32 i, u32 x_res, u32 y_res, char *data, size_t data_length) {
    FILE *fout;
    char out_name[9];

    sprintf(out_name, "%03d.ppm", i);
    fout = fopen(out_name, "w");
    if (!fout) print_error(WRITE_PPM_ERR, errno);
    fprintf(fout, "P6\n%d %d 255\n", x_res, y_res);
    fwrite(data, data_length, 1, fout);
    fclose(fout);
}
/*image gris 1 channel P5:binaire P2:ASCII ===================================*/
// 228Ko ppm -> 76Ko pgm intéressant pour le réseau, ça permettrait d'avoir une résolution plus grande ou plus de FPS
static void save_PGM(u32 i, u32 x_res, u32 y_res, u8 *data, size_t data_length) {
    FILE *fout;
    char out_name[9];

    sprintf(out_name, "%03d.pgm", i);
    fout = fopen(out_name, "w");
    if (!fout) print_error(WRITE_PPM_ERR, errno);
    fprintf(fout, "P5\n%d %d 255\n", x_res, y_res);
    fwrite(data, data_length, 1, fout);
    fclose(fout);
}
/*image monochrome 1 channel 0:noir 1:blanc P4 ==================================*/
//static void save_PBM(u32 i, u32 x_res, u32 y_res, char *data, size_t data_length) {}
/*============================================================================*/
// https://www.linuxtv.org/downloads/v4l-dvb-apis-old/v4l2grab-example.html
void grabFrameFromCamera(){

    CommonV4l2_update_image(&common_v4l2);

    // je voudrais bien que le serveur n'envoie qu'un seul channel, en noir et blanc. 1 pixel = 1 octet de 0 à 255.
    // commence par essayer de générer cette image et l'écrire en local, après on verra comment l'envoyer.
    // ne garde que le canal rouge pour 3X moins de bande passante 228Ko ppm -> 76Ko pgm
    lastframe.len = CommonV4l2_get_image_size(&common_v4l2);
    lastframe.pixels = CommonV4l2_get_image(&common_v4l2);

    int nbredpixels = lastframe.len / 3;
    u8* redpixels = (char*)malloc(nbredpixels+1);

    int n = 0;
    while (n < nbredpixels){
        redpixels[n] = lastframe.pixels[3*n+1];//(u8)((lastframe.pixels[3*n]+lastframe.pixels[3*n+1]+lastframe.pixels[3*n+2]) / 3);//lastframe.pixels[3*n];
        n++;
    }
    redpixels[n]=0;

    static int i=0;
    save_PGM (i++, x_res, y_res, redpixels, nbredpixels);
    free(redpixels);
    //save_ppm(i++, x_res, y_res, CommonV4l2_get_image(&common_v4l2), CommonV4l2_get_image_size(&common_v4l2));
    //save_ppm(i++, x_res, y_res, lastframe.pixels, lastframe.len);
}


camera.h 208 lignes, 7586 octets