Привествую, Хабр!
Продолжаем цикл статей про программирование видеокамеры в Linux. В первой части [1], мы рассмотрели механизм открытия и считывания первичных параметров видеоустройства. Была написана простенькая утилита catvd. Сегодня расширим функционал нашей маленькой программы, но сначала надо написать обертку для функции ioctl.
Эта обертка позволяет прервать программу если была ошибка и показать сообщение.
  Попробуем считать картинку с камеры и сохранить в файл.
метод readFrame — отвечает за чтение и обработку полученого изображения.
методы initMMAP(), freeMMAP() — создание/очистка буфера памяти устройства.
методы startCapturing(), stopCapturing() — включение/выключение режима streaming у видеоустройства. Наличие этих функций, у камеры, можно проверить флагом V4L2_CAP_STREAMING [*].
  Разберем метод initMMAP
функция VIDIOC_REQBUFS [↓] позволяет проинициализировать буфер памяти внутри устройства. Структура v4l2_requestbuffers задает параметры инициализации
  После того, как буфер был проинициализирован, его надо отобразить на область памяти (mapping).
Функция VIDIOC_QUERYBUF [↓] позволяет считать параметры буфера, которые будут использоваться для создания memory-mapping области. Структура v4l2_buffer большая, опишу необходимые поля:
системная функция mmap() [3] позволяет отображать файл или область памяти устройств в оперативную память. Для использования mmap() необходимо подключить
  Далее необходимо переключить камеру в режим захвата.
Функция VIDIOC_QBUF [↓] ставит буфер в очередь обработки драйвером устройства. Поля используются такие же, как и для VIDIOC_REQBUFS или VIDIOC_QUERYBUF.
Функция VIDIOC_STREAMON[↓] включает камеру в режим захвата.
  Теперь камера включена и захватывает изображения. Но картинку еще надо получить.
Функция VIDIOC_DQBUF[↓] освобождает буфер из очереди обработки драйвера. В результате можем получить ошибку EAGAIN. Ничего опасного в этом нет, надо еще раз вызвать VIDIOC_DQBUF. Это происходит потому, что драйвер еще обрабатывает запрос и не может освободить буфер из очереди. При успешном выполнении этой функции, мы получаем в «руки» нашу картинку. В самом начале статьи, в коде был добавлен итератор. Итератор позволяет проследить сколько итераций вхолостую проходит цикл до успешного выполнения VIDIOC_DQBUF.
Компиляция
Вывод программы следующий
Из «iter == 831013» видно — картинка скидывается в буфер довольно долго. Для ускорения можно использовать несколько буферов и вытаскивать картинку с первого свободного и т.д.
  Сегодня была рассмотрена инициализация буфера памяти и чтения из него картинки. Изображение сохраняется в raw формате. Можно открыть программой Shotwell. В следующей статье будет рассмотрен вывод изображению в текстуру (через SDL2), затронуты некоторые форматы изображения и настройки камеры.
Ресурсы используемые в статье:
Ссылки на используемые функции:
Исходный код
Продолжаем цикл статей про программирование видеокамеры в 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), затронуты некоторые форматы изображения и настройки камеры.
Ресурсы используемые в статье:
Ссылки на используемые функции:
Исходный код