[Linux] Lecture d’une webcam en langage C (Video4Linux2)

Nous allons voir dans cet article comment accéder à la webcam sous Linux depuis un programme en langage C.

Les webcams sont détectées et gérées sous Linux par un sous-système du noyau appelé Video4Linux2 (v4l2). Il s’agit d’une couche d’abstraction offerte au programmeur lui permettant d’accéder à la webcam par une API indépendante de la webcam installée.

Video4Linux2 dispose de capacité de détection et de reconnaissance des webcams très performantes, comme c’est souvent le cas sous Linux. L’API à utiliser n’est cependant pas triviale à utiliser, c’est pourquoi le programmeur trouvera utile d’avoir dans sa boite à outils un exemple de code « qui marche ».

Ce bout de code qui marche est présenté ci-dessous. Il est très simple en termes de fonctions mais difficile à écrire from scratch. En gros, il initialise l’interface video4linux2, ouvre une fenêtre à l’écran, et entre dans une boucle infinie qui lit l’image courante sur la webcam et rafraîchit l’image dans la fenêtre au fur et à mesure. Pour tuer le programme, il faudra donc faire un Ctrl-C dans la fenêtre dans laquelle on l’a lancé.

Voici ce que donne le programme à l’exécution.

Pour afficher l’image dans une fenêtre, j’ai choisi la librairie graphique SDL, qui est puissante et simple à utiliser (en plus d’être portable sous Windows et Mac, mais ce détail a peu d’importance dans le cas présent).

Ces routines peuvent être utilement ré-utilisées dans tout projet de plus grande ampleur devant lire les images capturées par la webcam. Vous en aurez besoin si vous programmez votre propre programme de visio-conférence par exemple.

A l’utilisation de ce programme, vous constaterez que la LED de la webcam s’allume. Pour ceux qui seraient intéressés à construire un programme malveillant à partir de ce code (genre un programme d’espionnage qui filme à l’insu de l’utilisateur), sachez que ce code ne permet pas d’éteindre la LED, donc on ne peut pas filmer discrètement.

Eteindre cette LED ne parait pas trivial, si j’en crois Google qui est mon ami. Beaucoup de programmeurs se posent la question mais peu d’entre eux partagent leur découverte. Il semblerait que l’utilitaire uvcdynctrl puisse réussir cette prouesse mais a priori il a besoin qu’on lui passe en paramètre un fichier de mappage xml propre au modèle de webcam dont on veut éteindre la LED, ce qui limite l’intérêt de tenter de programmer un espion.

Quoi qu’il en soit, voici le code source ma_cam.c du programme qui lit les images envoyées par la webcam sous Linux.

// Pour compiler ce programme : gcc ma_cam.c -o ma_cam -lSDL2 -lSDL2_image

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/mman.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_surface.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>

// Variables nécessaires au fonctionnement de Video4Linux2
struct v4l2_capability cap;
struct v4l2_format format;
struct v4l2_requestbuffers bufrequest;
struct v4l2_buffer bufferinfo;

char* buffer_start;
int jpgfile, type;

// Variables nécessaires au fonctionnement de SDL2
SDL_Window* screen;
SDL_Surface *picture;
SDL_Renderer *renderer;
SDL_Texture *tpicture;
SDL_Rect dest;

SDL_Renderer *getrenderer(void)
{
return renderer;
}

int main(int argc, char **argv)
{
int fd;

// Initialisation de la vidéo sous SDL2
SDL_Init(SDL_INIT_VIDEO);
IMG_Init(IMG_INIT_JPG);
// Ouverture du fichier device représentant la webcam
if((fd = open(“/dev/video0”, O_RDWR)) < 0)
{
fprintf(stderr, “No webcam device found\n”);
}
else
{
// Lecture des fonctionalités assurées par la webcam
if(ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
{
fprintf(stderr, “Unable to retrieve device capabilities.\n”);
close(fd);
}
else
{
// Test de la fonctionalité de capture de la webcam
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
{
fprintf(stderr, “The device does not handle video capture.\n”);
close(fd);
}
else
{
// Test de la fonctionalité de streaming de la webcam
if(!(cap.capabilities & V4L2_CAP_STREAMING))
{
fprintf(stderr, “The device does not handle video capture streaming.\n”);
close(fd);
}
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
// On décide arbitrairement de prévoir une image de 640×480 qui est une taille standard
format.fmt.pix.width = 640;
format.fmt.pix.height = 480;
if(ioctl(fd, VIDIOC_S_FMT, &format) < 0)
{
// Si la webcam ne sait pas faire du 640×480, on a un message d’erreur ici
fprintf(stderr, “Unable to retrieve video formats\n”);
close(fd);
}
else
{
// Ouverture d’une fenêtre de 640×480 à l’écran
screen = SDL_CreateWindow(“Webcam”,SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,format.fmt.pix.width, format.fmt.pix.height,SDL_WINDOW_SHOWN);
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_PRESENTVSYNC);
// Initialisation de la webcam en vue de la capture
bufrequest.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
bufrequest.memory = V4L2_MEMORY_MMAP;
bufrequest.count = 1;
if(ioctl(fd, VIDIOC_REQBUFS, &bufrequest) < 0)
{
perror(“VIDIOC_REQBUFS”);
exit(1);
}
memset(&bufferinfo, 0, sizeof(bufferinfo));
bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
bufferinfo.memory = V4L2_MEMORY_MMAP;
bufferinfo.index = 0;
if(ioctl(fd, VIDIOC_QUERYBUF, &bufferinfo) < 0)
{
perror(“VIDIOC_QUERYBUF”);
exit(1);
}
buffer_start = mmap(NULL, bufferinfo.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bufferinfo.m.offset);
if (buffer_start == MAP_FAILED)
{
perror(“mmap”);
exit(1);
}
memset(buffer_start, 0, bufferinfo.length);
memset(&bufferinfo, 0, sizeof(bufferinfo));
bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
bufferinfo.memory = V4L2_MEMORY_MMAP;
bufferinfo.index = 0;
type = bufferinfo.type;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
perror(“VIDIOC_STREAMON”);
exit(1);
}
}
}
}
}
// Maintenant que la fenêtre SDL est ouverte à l’écran, et que la webcam est initialisée
// on crée une boucle infinie pour la capture et l’affichage image par image
for(;;)
{
// Les 2 appels ioctl ci-dessous capturent une image de la webcam au format jpg
if(ioctl(fd, VIDIOC_QBUF, &bufferinfo) < 0)
{
perror(“VIDIOC_QBUF”);
exit(1);
}
if (ioctl(fd, VIDIOC_DQBUF, &bufferinfo) < 0)
{
perror(“VIDIOC_DQBUF”);
exit(1);
}
// Cette image est ensuite mise sur le disque dur dans /tmp/myimage.jpeg
if((jpgfile = open(“/tmp/myimage.jpeg”, O_WRONLY | O_CREAT, 0660)) < 0)
{
perror(“open”);
exit(1);
}
write(jpgfile, buffer_start, bufferinfo.length);
close(jpgfile);
// Puis le fichier /tmp/myimage.jpeg est chargé dans SDL pour être affiché à l’écran
picture = IMG_Load(“/tmp/myimage.jpeg”);
tpicture=SDL_CreateTextureFromSurface(getrenderer(), picture);
dest.x = 0;
dest.y = 0;
// L’image est envoyé vers le renderer de travail de SDL2
SDL_QueryTexture(tpicture, NULL, NULL, &dest.w, &dest.h);
SDL_RenderCopy(getrenderer(), tpicture, NULL, &dest);
// Le renderer de travail de SDL2 est envoyé vers la fenêtre visible à l’écran
SDL_RenderPresent(getrenderer());
// Enfin, le renderer de travail de SDL2 est purgé en vue de la prochaine image capturée
SDL_RenderClear(getrenderer());
}
}

Tagués avec : , , , , , ,

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Résoudre : *
11 − 3 =