Привествую!
Продолжаем цикл статей про программирование видеокамеры в Linux. В первой части [1], мы рассмотрели механизм открытия и считывания первичных параметров видеоустройства. Была написана простенькая утилита catvd. Сегодня расширим функционал нашей маленькой программы, но сначала надо написать обертку для функции ioctl.
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;
}
Эта обертка позволяет прервать программу если была ошибка и показать сообщение.
  Попробуем считать картинку с камеры и сохранить в файл.
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
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>
  Далее необходимо переключить камеру в режим захвата.
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[↓] включает камеру в режим захвата.
  Теперь камера включена и захватывает изображения. Но картинку еще надо получить.
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), затронуты некоторые форматы изображения и настройки камеры.
Ресурсы используемые в статье:
Ссылки на используемые функции:
Автор: svistkovr