Работа с usb видеокамерой в Linux. Часть 2

  • Tutorial
   Привествую, Хабр!

   Продолжаем цикл статей про программирование видеокамеры в Linux. В первой части [1], мы рассмотрели механизм открытия и считывания первичных параметров видеоустройства. Была написана простенькая утилита catvd. Сегодня расширим функционал нашей маленькой программы, но сначала надо написать обертку для функции ioctl.
Код метода xioctl
int videodevice::xioctl(int fd, int request, void *arg)
{
       int r;

       r = ioctl (fd, request, arg);

       if(r == -1)
       {
           if (errno == EAGAIN)
               return EAGAIN;

           stringstream ss;

           ss << "ioctl code " << request << " ";

           errno_exit(ss.str());
       }

       return r;
}


Эта обертка позволяет прервать программу если была ошибка и показать сообщение.

  Попробуем считать картинку с камеры и сохранить в файл.
Код метода getFrame
void videodevice::getFrame(string file_name)
{
    initMMAP();

    startCapturing();

    long int i = 0;

    for (;;)
    {
        if(readFrame(file_name))
           break;

        i++;
    }

    cout << "iter == " << i << endl;

    stopCapturing();

    freeMMAP();
}


метод readFrame — отвечает за чтение и обработку полученого изображения.
методы initMMAP(), freeMMAP() — создание/очистка буфера памяти устройства.
методы startCapturing(), stopCapturing() — включение/выключение режима streaming у видеоустройства. Наличие этих функций, у камеры, можно проверить флагом V4L2_CAP_STREAMING [*].

  Разберем метод initMMAP
Код метода initMMAP
void videodevice::initMMAP()
{
    struct v4l2_requestbuffers req;

    req.count = 1;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    xioctl(fd, VIDIOC_REQBUFS, &req);

    devbuffer = (buffer*) calloc(req.count, sizeof(*devbuffer));

    struct v4l2_buffer buf;

    memset(&buf, 0, sizeof(buf));

    buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory      = V4L2_MEMORY_MMAP;
    buf.index       = 0;

    xioctl(fd, VIDIOC_QUERYBUF, &buf);

    devbuffer->length = buf.length;
    devbuffer->start =
               mmap(NULL,
                    buf.length,
                    PROT_READ | PROT_WRITE,
                    MAP_SHARED,
                    fd,
                    buf.m.offset);

    if (devbuffer->start == MAP_FAILED)
        errno_exit("mmap");
}


функция VIDIOC_REQBUFS [↓] позволяет проинициализировать буфер памяти внутри устройства. Структура v4l2_requestbuffers задает параметры инициализации
struct v4l2_requestbuffers {
	__u32			count;		//количество буферов
	__u32			type;		//тип или цель использования
	__u32			memory;		//режим работы с памятью.
	__u32			reserved[2];	//всегда в ноль
};

  После того, как буфер был проинициализирован, его надо отобразить на область памяти (mapping).
Функция VIDIOC_QUERYBUF [↓] позволяет считать параметры буфера, которые будут использоваться для создания memory-mapping области. Структура v4l2_buffer большая, опишу необходимые поля:
struct v4l2_buffer {
//до выполнения VIDIOC_QUERYBUF  устанавливаем следующие поля
__u32   index;         // ноль или номер буфера (если v4l2_requestbuffers.cout > 1)
__u32   type;	       // тип  (совпадает со значением v4l2_requestbuffers.type)


//после выполнения VIDIOC_QUERYBUF  используем эти поля в качестве параметров для memory-mapping
union {
__u32   offset;		// смещение буфера относительно начала памяти устройства
	} m;
__u32   length;		// размер  буфера
};

системная функция mmap() [3] позволяет отображать файл или область памяти устройств в оперативную память. Для использования mmap() необходимо подключить
<sys/mman.h>

  Далее необходимо переключить камеру в режим захвата.
Код метода startCapturing
void videodevice::startCapturing()
{
    struct v4l2_buffer buf;

    memset(&buf, 0, sizeof(buf));

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = 0;

    xioctl(fd, VIDIOC_QBUF, &buf);

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    xioctl(fd, VIDIOC_STREAMON, &type);
}


Функция VIDIOC_QBUF [↓] ставит буфер в очередь обработки драйвером устройства. Поля используются такие же, как и для VIDIOC_REQBUFS или VIDIOC_QUERYBUF.
Функция VIDIOC_STREAMON[↓] включает камеру в режим захвата.

  Теперь камера включена и захватывает изображения. Но картинку еще надо получить.
Код метода readFrame
int videodevice::readFrame(string file_name)
{
    struct v4l2_buffer buf;

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (xioctl(fd, VIDIOC_DQBUF, &buf) == EAGAIN)
            return 0;

    buffer *temp = devbuffer;

    FILE *out_file = fopen(file_name.c_str(),"w");

    fwrite(temp->start,temp->length,1,out_file);

    fclose(out_file);

    return 1;
}


Функция VIDIOC_DQBUF[↓] освобождает буфер из очереди обработки драйвера. В результате можем получить ошибку EAGAIN. Ничего опасного в этом нет, надо еще раз вызвать VIDIOC_DQBUF. Это происходит потому, что драйвер еще обрабатывает запрос и не может освободить буфер из очереди. При успешном выполнении этой функции, мы получаем в «руки» нашу картинку. В самом начале статьи, в коде был добавлен итератор. Итератор позволяет проследить сколько итераций вхолостую проходит цикл до успешного выполнения VIDIOC_DQBUF.

Компиляция
$ cmake .
$ make


Вывод программы следующий
$./getimage
Open device /dev/video0
Init mmap
Start capturing
read frame from buffer and write to file
iter == 831013
stop Capturing
free mmap
Close device /dev/video0

Из «iter == 831013» видно — картинка скидывается в буфер довольно долго. Для ускорения можно использовать несколько буферов и вытаскивать картинку с первого свободного и т.д.

  Сегодня была рассмотрена инициализация буфера памяти и чтения из него картинки. Изображение сохраняется в raw формате. Можно открыть программой Shotwell. В следующей статье будет рассмотрен вывод изображению в текстуру (через SDL2), затронуты некоторые форматы изображения и настройки камеры.

Ресурсы используемые в статье:
  1. Работа с usb видеокамерой в Linux. Часть 1
  2. video for Linux API
  3. подробнее про mmap()

Ссылки на используемые функции:

Исходный код
  • +22
  • 19.9k
  • 7
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 7

    0
    Спасибо, полезный код. Вопрос в том, можно ли вебкамеру использовать для измерения яркости? У меня есть идея одного эксперимента, как раз с вебкамерами. Или поборот автоподстройку уже нельзя?
      +1
      Можно управлять автоподстройкой (экспозиции, фокуса, масштабирования). Я планирую написать об этом в будущих статьях. Если интересно, то полный список возможностей можно глянуть в v4l2-controls.h, документации на эту тему мало — приходится изучать по исходникам ядра.
        0
        Я планирую написать об этом в будущих статьях


        Будем ждать! Спасибо.
      0
      Скажите, пожалуйста, данным образом можно работать с любой USB-камерой или только с той, чьи драйвера есть в системе?
        0
        Отвечу за автора.

        Автор работает с файлом устройства, который создаёт драйвер и предоставляет таким образом API пространству пользователя в виде open/close/read/write/ioctl/pull и др. Если драйвера нет, то файл устройства в системе создавать будет не кому, и предоставлять API так же не кому.

        Отсюда вывод, что поскольку автор работает в пространстве пользователя, то только с дровами такое возможно…
          0
          Да, драйвер должен быть подгружен в ядро. API video 4 linux позволяет работать не только с usb-камерами, но и с tv-tuners, audio/video i/o устройствами.
          0
          Хотелось бы уточнить что этот код будет работать не только с USB web камерой а с любым v4l устройством, для которого установлен драйвер, напримет TV тюнером.

          Only users with full accounts can post comments. Log in, please.