Научно-исследовательский институт встраиваемых приложений |
Источник материала |CSDN
vl42 — это аббревиатура видео для Linux 2. Это среда драйверов для видеоустройств ядра Linux. Структура драйверов обеспечивает унифицированный рабочий интерфейс (серия ioctl) для уровня приложений.
V4L2 предназначен для поддержки широкого спектра устройств. Лишь некоторые из них являются настоящими видеоустройствами и могут поддерживать различные устройства. Он может иметь следующие интерфейсы.
интерфейс захвата видео: интерфейс захвата видео, этот интерфейс используется в камерах, для этой функции использовалась версия v4l2 при ее первоначальной разработке.
интерфейс вывода видео: интерфейс вывода видео, который кодирует неподвижные изображения или последовательности изображений в аналоговые видеосигналы. С помощью этого интерфейса приложение может управлять процессом кодирования и перемещать изображения из пространства пользователя в драйвер.
Интерфейс наложения видео: интерфейс прямой передачи видео, который может напрямую передавать собранные видеоданные на устройство отображения без участия процессора. Этот метод отображения изображений намного более эффективен, чем другие методы.
Другие интерфейсы здесь представлены не будут. Давайте посмотрим на API v4l2.
Программирование устройства V4L2 включает в себя следующие шаги.
Большинство операций реализуется путем вызова ioctl на уровне приложения. Эти ioctl можно разделить на следующие категории:
Поскольку V4L2 охватывает множество устройств, не все аспекты API применимы ко всем типам устройств. При использовании устройства v4l2 необходимо вызывать этот API, чтобы получить функции, поддерживаемые устройством (захват, вывод, наложение...). )
Примечание. Вы можете нажать на имя, чтобы просмотреть объяснение API.
Когда несколько приложений совместно используют одно устройство, им может потребоваться назначить разные приоритеты. Приложения для записи видео могут, например, запрещать другим приложениям изменять элементы управления видео или переключать текущий телеканал. Другая цель — разрешить приложениям с низким приоритетом работать в фоновом режиме, которые могут быть вытеснены приложениями, управляемыми пользователем, и позже автоматически восстановить контроль над устройством.
Изображения состоят из нескольких форматов, YUV, RGB, форматов сжатия и т. д. Каждый формат делится на несколько форматов, например RGB: RGB565, RGB888... Поэтому при использовании устройства необходимо задать формат.
Ядро использует очереди кэша для управления данными изображений. Существует два способа получения данных изображения из пользовательского пространства: один — чтение кэша пространства ядра посредством чтения и записи, а другой — сопоставление кэша пространства ядра с пользовательским пространством. . При работе с устройством v4l2 узнайте, какой метод поддерживает устройство, с помощью VIDIOC_QUERYCAP.
Сначала здесь представлен API ioctl. Существует множество интерфейсов, которые я не буду описывать здесь один за другим. Для получения подробной информации вы можете просмотреть Справочник по функциям V4L2. Давайте поговорим о том, как использовать эти интерфейсы.
V4L2 поддерживает несколько интерфейсов: захват (захват), вывод (вывод), наложение (предварительный просмотр) и т. д. Здесь мы объясним, как использовать функцию захвата, а процесс работы описан ниже.
int fd = open(name, flag);
if(fd < 0)
{
printf("ERR(%s):failed to open %s\n", __func__, name);
return -1;
}
return fd;
if (ioctl(fd, VIDIOC_QUERYCAP, cap) < 0)
{
printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
return -1;
}
Взгляните на v4l2_capability:
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
Самым важным из них является поле возможностей, которое отмечает функции устройства v4l2. Возможности имеют следующие биты маркировки:
О работе устройства можно судить по следующему:
Конечно, не всем устройствам необходимо настраивать входы. Например: камеры UVC обычно имеют только один вход, который выбран по умолчанию и не требует настройки.
Вот как настроить устройство ввода
struct v4l2_input input;
input.index = 0;
while (!ioctl(fd, VIDIOC_ENUMINPUT, &input))
{
printf("input:%s\n", input.name);
++input.index;
}
struct v4l2_input input;
input.index = index; //Указываем устройство ввода
if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0)
{
printf("ERR(%s):VIDIOC_S_INPUT failed\n", __func__);
return -1;
}
struct v4l2_fmtdesc fmtdesc;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
{
printf("fmt:%s\n", fmtdesc.description);
fmtdesc.index++;
}
struct v4l2_format v4l2_fmt;
memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_fmt.fmt.pix.width = width; //ширина
v4l2_fmt.fmt.pix.height = height; //высокий
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //Формат пикселей
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
{
printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);
return -1;
}
Метод чтения прост для понимания, то есть чтение через функцию чтения. Так что же означает потоковая передача?
Потоковая передача поддерживает очередь кэша в пространстве ядра, а затем отображает память в пространство пользователя. Чтение данных изображения приложением представляет собой процесс непрерывного удаления из очереди и постановки в очередь, как показано на следующем рисунке.
Вот как подать заявку и сопоставить кэш:
struct v4l2_requestbuffers req;
req.count = nr_bufs; //кэш Количество
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
return -1;
}
Потому что, если вы используете метод чтения для чтения, данные изображения копируются из пространства ядра и будет использоваться пространство приложения, а данные изображения обычно относительно велики, поэтому эффективность будет относительно низкой. Если вы используете метод сопоставления и применяете память из пространства ядра к пространству пользователя, то пространство пользователя считывает данные так же, как и при работе с памятью. Нет необходимости копировать пространство ядра в пространство пользователя, что значительно улучшает работу. эффективность.
Для сопоставления кэша необходимо сначала запросить информацию о кэше, а затем использовать ее для сопоставления. Вот пример:
struct v4l2_buffer v4l2_buffer;
void* addr;
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //Кэш, который вы хотите запросить
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
/* Запрос информации о кэше */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to query buffer.\n");
return -1;
}
/* картографирование */
addr = mmap(NULL /* start anywhere */ ,
v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer.m.offset);
Примечание. Все применяемые кэши необходимо сопоставить с использованием описанного выше метода.
struct v4l2_buffer v4l2_buffer;
for(i = 0; i < nr_bufs; i++)
{
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //Кэш, который вы хотите поставить в очереди
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to queue buffer.\n");
return -1;
}
}
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);
return -1;
}
struct pollfd poll_fds[1];
poll_fds[0].fd = fd;
poll_fds[0].events = POLLIN; //ждём, пока его можно будет прочитать
poll(poll_fds, 1, 10000);
struct v4l2_buffer buffer;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
{
printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);
return -1;
}
После удаления из очереди получается индекс кэша buffer.index, а затем находится соответствующий кэш и данные считываются через сопоставленный адрес.
struct v4l2_buffer v4l2_buf;
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
v4l2_buf.index = i; //указанный буфер
if (ioctl(fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);
return -1;
}
Чтение данных заключается в непрерывном циклическом выполнении трех вышеуказанных шагов.
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);
return -1;
}
for(i = 0; i < nr_bufs; ++i)
munmap(buf[i].addr, buf[i]->length);
close(fd);
В комплекте идет пример example_cature, который запускает программу через capture /dev/video0 для сбора картинки в формате YUYV. После сбора получается pic.yuv, который можно просмотреть через ffplay ffplay -pixel_format yuyv422 -f rawvideo -video_size. 640x480 pic.yuv, рендеры следующие
Как отобразить собранное изображение на баффе кадра?
Конкретный процесс реализации здесь подробно описываться не будет. Ниже приведен пример для получения ссылки:
https://github.com/ImSjt/libv4l2
После выполнения команды make для компиляции вы можете получить video2lcd, выполните video2lcd /dev/video0
Эффект от бега следующий:
Как использовать qt display, принцип тот же, что и при отображении на фрейме. Все они представляют собой сбор, формат преобразования и отображение. Вот пример для получения ссылки:
https://github.com/ImSjt/libv4l2
Эффект от операции следующий.