Работа с usb видеокамерой в Linux. Часть 2
Продолжаем цикл статей про программирование видеокамеры в 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 canon as usb cam
For a while now, I’ve been interested in using my Canon EOS 70D DSLR as a USB camera so that I can record (and potentially live stream) better quality tutorial videos. The quality of video from the 70D is super and it has a USB connector, so I thought this should be possible. However, although I think Canon provides some kind of software utility to support this kind of thing in Windows and Mac OS, it wasn’t immediately obvious how to do it in Linux. Anyway, I spent a while digging around to figure it, but the solution which finally worked for me (shown below) is basically straight from this post.
One drawback to using the 70D this way is that it doesn’t seem to be possible to power it via the USB connector, so you either need to periodically recharge the battery or buy some kind of AC adapter that plugs into the battery compartment (I think devices like this are available, but I haven’t tried any of them).
The solution is basically:
- Connect the Canon 70D to the laptop via USB cable.
- Use gphoto2 to initiate video capture on the camera and receive the data in real time from USB and pipe it onwards via stdout.
- The output of gphoto2 is piped into gstreamer which does some data format conversion
- The output of gstreamer is sent to a V4L2 loopback device (e.g. /dev/video2), so that other applications can access it the same way as a normal USB cam.
- I’ve been using OBS to capture videos from the 70D (including audio), which seems to work extremely well, but in principle any V4L2 compatible software should now be able to access the camera.
First, I had to install a couple of packages:
Then, the v4l2loopback kernel module needs to be inserted, since that will be used to make the video stream appear as a camera device (e.g. /dev/video2).
Edit (25-7-2019): For some reason, to make the loopback video capture device visible to the Chrome browser, I need to insert the v4l2loopback module with the following additional option specified:
To begin streaming video data:
Incidentally, the following command can be used to capture a still image to a file on the laptop without storing it on the camera’s memory card):
Захват видео с USB камер на устройствах под управлением Linux
Предыстория
- Видео в разрешении FullHD (1920Х1080) или HD (1280х720) и нормальная частота кадров (чтобы можно было играть).
- Игрушку я планировал отдать детям, поэтому нужен был автостарт и поддержка подключения/отключения камеры.
В общем хотелось что-то вроде этого:
Ограничения
Я не собирался искать решение, которое работает всегда и везде. Следующие ограничения меня вполне устраивали:
- Хороший WiFi сигнал.
- Ограниченное число подключений, приоритет отдавался случаю, когда есть всего один клиент.
- Камера поддерживает режим MJPG.
HW и SW
Предварительный анализ
Код UVC драйвера оказался готов к добавлению различного рода “специальных” решений, и я легко нашел место, где надо скорректировать размер буфера (функция uvc_fixup_video_ctrl()). Более того, драйвер поддерживает набор quirks, которые позволяют поддерживать камеры с разного рода отклонениями от стандарта UVC. В общем, разработчики драйвера сделали лучшее, что возможно для поддержки зоопарка камер.
Добавив коррекцию размера буфера, я получил стабильную работу в режиме 1280х720 и даже в режиме 1920х1080. Ура! Половина задачи решена!
В поисках новых приключений
Немного порадовавшись первой удаче, я вспомнил, что mjpg-streamer далек от совершенства. Наверняка можно сделать что-то простое, не такое универсальное как mjpg-streamer, но более подходящее для моих условий. Так я решил сделать uvc2http.
В mjpg-streamer мне не понравилось использование нескольких потоков и копирование буферов. Это определило архитектуру решения: 1 поток и никакого копирования. Используя non-blocking IO, это делается достаточно просто: захватываем кадр и без копирования отсылаем его клиенту. Есть небольшая проблема: пока мы отсылаем данные из буфера, мы не можем вернуть буфер обратно в очередь. А пока буфер не в очереди, драйвер не может положить в него новый кадр. Но если размер очереди > 1, то это становится возможным. Число буферов определяет максимальное количество подключений, которое можно гарантированно обслуживать. Т.е., если я хочу гарантированно поддерживать 1 клиента, то 3-х буферов достаточно (в один буфер пишет драйвер, из второго отсылаем данные, третий в запасе, чтобы избежать конкуренции с драйвером за буфер при попытке получить новый кадр).
Uvc2http
Uvc2http состоит из двух компонентов: UvcGrabber и HttpStreamer. Первый отвечает за получение буферов (кадров) из очереди и возврат их обратно в очередь. Второй отвечает за обслуживание клиентов по HTTP. Есть еще немного кода, который связывает эти компоненты. Подробности можно посмотреть в исходниках.
Неожиданная проблема
Все было замечательно: приложение работало и в разрешении 1280х720 выдавало 20+ кадров/сек. Я делал косметические изменения в коде. После очередной порции изменений я замерил частоту кадров. Результат был удручающий — меньше 15 кадров. Я бросился искать, что же привело к деградации. Я потратил, наверное, 2 часа в течение которых частота уменьшалась с каждым замером до значения 7 кадров/сек. В голову лезли разные мысли о деградации из-за долгой работы роутера, из-за его перегрева. Это было что-то непонятное. В какой-то момент я отключил стримминг и увидел, что просто один захват (без стримминга) давал те же 7 кадров. Я даже начал подозревать проблемы с камерой. В общем какая-то чушь. Дело было вечером и камера, повернутая в окно, показывала что-то серое. Дабы сменить мрачное изображение я повернул камеру внутрь комнаты. И, о чудо! Частота кадров увеличилась до 15 и я все понял. Камера автоматически подстраивала время экспозиции и в какой-то момент это время стало больше длительности кадра при заданной частоте. За эти два часа случилось следующее: сначала плавно темнело (это был вечер), а потом я повернул камеру внутрь освещенной комнаты. Направив камеру на люстру я получил 20+ кадров/сек. Ура.
Другие проблемы и нюансы использования
Результаты
Ниже табличка с результатами сравнения mjpg-streamer и uvc2http. Если коротко — есть значительный выигрыш в потреблении памяти и небольшой выигрыш в частоте кадров и загрузке CPU.
1280×720 | 1920×1080 | |||||||||||
VSZ, KB, 1 client | VSZ, KB, 2 clients | CPU, %, 1 client | CPU, %, 2 clients | FPS, f/s, 1 client | FPS, f/s, 2 clients | VSZ, KB, 1 client | VSZ, KB, 2 clients | CPU, %, 1 client | CPU, %, 2 clients | FPS, f/s, 1 client | FPS, f/s, 2 clients | |
Mjpg-streamer | 16860 | 19040 | 26 | 43 | 17.6 | 15 | 25456 | 25812 | 28 | 50 | 13.8 | 10 |
uvc2http | 3960 | 3960 | 26 | 43 | 22 | 19.6 | 7576 | 7576 | 28 | 43 | 15.5 | 12.2 |
Ну и конечно же видео, которое я сделал вместе с детьми:
Фото получившегося танка (получилось что-то вроде цыганской телеги):
Использование
Исходники находятся здесь. Для использования на PC Linux надо всего лишь собрать (при условии что вы не хотите патчить драйвер UVC). Утилита собирается с помощью CMake стандартным способом. Если же надо использовать в OpenWRT, то надо сделать дополнительные шаги:
- Скопировать содержимое директории OpenWrt-15.05 в корень репозитория OpenWRT. Эти файлы только для OpenWRT 15.05. Они описывают новый пакет для OpenWRT и патч для драйвера UVC.
- Если ваша камера также возвращает завышенный размер необходимого буфера, то надо добавить использование quirk UVC_QUIRK_COMPRESSION_RATE для вашей камеры в файле uvc_driver.c. Для этого надо сделать собственный патч для драйвера UVC. Как это сделать, описано здесь wiki.openwrt.org/doc/devel/patches. Вам необходимо добавить описание вашей камеры в массив uvc_ids. В качестве примера можно посмотреть на описание моей камеры:
Что дальше
Решение состоит из двух частей: патч драйвера и другой алгоритм стримминга. Патч драйвера можно было бы включить в новую версию ядра линукса, но это спорное решение, так как оно основано на предположении о минимальном коэффициенте сжатия. Утилита же, на мой взгляд, хорошо подходит для использования на слабых системах (игрушках, домашних системах видеонаблюдения), и ее можно немного улучшить, добавив возможность задавать настройки камеры через параметры.
Алгоритм стримминга можно улучшить так как есть запас по загрузке CPU и по ширине канала (я легко получал с роутера 50+ MBit подключая десяток клиентов). Также можно добавить поддержку звука.