Top.Mail.Ru

Сборка libcamera-apps из исходников и пример кастомизации.

В одной (и даже не в одной, а в нескольких) из предыдущих статей мы разбирали совместную работу камеры High Quality Camera с Raspberry Pi посредством стека libcamera. Так вот сегодня в некотором роде продолжим эту тему и рассмотрим, как собрать приложения libcamera из исходников, и, что не менее важно, разберем пример, чтобы понять, зачем это вообще может быть нужно.

Собирать будем именно libcamera-apps, так как это более востребовано на практике, к этому и переходим. Зафиксируем исходные данные – у меня плата Raspberry Pi 4 Model B 8 Gb и операционная система на данный момент следующая:

Raspberry Pi OS
Release date: September 22nd 2022
System: 32-bit
Kernel version: 5.15
Debian version: 11 (bullseye)

Это не критично, но чуть другой набор может потребовать незначительных изменений в осуществляемых процессах. А может и не потребовать. Если что, пишите в комментариях – с любыми нюансами разберемся, это не проблема. Все, приступаем.

Сборка libcamera-apps.

Начало традиционное – устанавливаем необходимые пакеты:

sudo apt install -y libcamera-dev libepoxy-dev libjpeg-dev libtiff5-dev
sudo apt install -y qtbase5-dev libqt5core5a libqt5gui5 libqt5widgets5
sudo apt install -y cmake libboost-program-options-dev libdrm-dev libexif-dev

Update

В определенный момент в libcamera-apps обновилась система сборки (где-то в районе мая 2023), поэтому добавляю обновленные же команды/инструкции. Соответственно, если используем обновленную версию, дальше по тексту нас интересуют секции с обновленными командами 👍 Итак необходимые пакеты:

sudo apt install -y cmake libboost-program-options-dev libdrm-dev libexif-dev
sudo pip3 install ninja meson

Далее клонируем исходники:

mkdir my-libcamera-apps
cd my-libcamera-apps
git clone https://github.com/raspberrypi/libcamera-apps.git
cd libcamera-apps

В результате получили каталог:

Building libcamera from source.

Создаем отдельный каталог для сборки, в который и переходим:

mkdir my-build
cd my-build

В общем, стандартные, не обременительные шаги 👍 Далее нужно запустить cmake, скормив ему необходимые флаги. Допустимые опции:

-DENABLE_COMPILE_FLAGS_FOR_TARGET=armv8-neon Пост-обработка может происходить быстрее на 32-битной ОС (для Raspberry Pi 3 и Raspberry Pi 4)
-DENABLE_DRM=1 / -DENABLE_DRM=0 DRM/KMS рендеринг превью
-DENABLE_X11=1 / -DENABLE_X11=0 Превью на базе X Windows
-DENABLE_QT=1 / -DENABLE_QT=0 Поддержка превью-окна на базе Qt. Обычно не рекомендуется включать
-DENABLE_OPENCV=1 / -DENABLE_OPENCV=0 Пост-обработка на базе OpenCV
-DENABLE_TFLITE=1 / -DENABLE_TFLITE=0 Включение / отключение TensorFlow Lite пост-обработки

Собственно, короткий путь – официальная документация предлагает два конкретных варианта, для Raspberry Pi OS:

cmake .. -DENABLE_DRM=1 -DENABLE_X11=1 -DENABLE_QT=1 -DENABLE_OPENCV=0 -DENABLE_TFLITE=0

Для Raspberry Pi OS Lite:

cmake .. -DENABLE_DRM=1 -DENABLE_X11=0 -DENABLE_QT=0 -DENABLE_OPENCV=0 -DENABLE_TFLITE=0

Update

И обновленные варианты, для Raspberry Pi OS:

meson setup build -Denable_libav=true -Denable_drm=true -Denable_egl=true -Denable_qt=true -Denable_opencv=false -Denable_tflite=false

Для Raspberry Pi OS Lite:

meson setup build -Denable_libav=false -Denable_drm=true -Denable_egl=false -Denable_qt=false -Denable_opencv=false -Denable_tflite=false

Первый случай мне отлично подходит, нет поводов им не воспользоваться, само собой, все что указано в таблице выше по-прежнему в силе. Я от случая к случаю использую разные ОС, так что, по итогу, выбираем опции в соответствии с таблицей и своим конкретным случаем, и все будет в порядке.

Приготовления завершены, непосредственно сборка:

make -j4

Update

meson compile -C build

Для предыдущих модификаций Raspberry Pi, среди которых в данном контексте Raspberry Pi 3 и более ранние, используем:

make -j1

Update

meson compile -j1 -C build

Здесь мы не собираем libcamera вручную, нам нужны только libcamera-apps, поэтому существует вероятность несовместимости текущей версии пакета libcamera-dev и самой актуальной версии libcamera-apps, которую мы взяли с github'а. В данном случае самым правильным будет собрать libcamera также руками. Если будет необходимость, добавлю аналогичные инструкции для сборки отдельным постом, пишите в комментарии. В случае ошибок также пишите туда же )

Вновь собранные приложения получаем в libcamera-apps/my-build, как и ожидалось:

Каталог сборки.

Далее официальный манускрипт рекомендует выполнить установку:

sudo make install
sudo ldconfig # this is only necessary on the first build

Update

sudo meson install -C build
sudo ldconfig # this is only necessary on the first build

Но это такое себе дело, особенно в современных дистрибутивах, в общем, данная операция требует как минимум вдумчивости и осознанности, так что смотрите сами, надо вам это или нет. Моя рекомендация – с этим не спешить, так что этот этап пропускаем.

И на данный момент мы имеем базовую версию libcamera-vid, которая чаще всего по умолчанию уже установлена в ОС и нашу кастомную версию, собранную руками. Внесем простейшие тестовые изменения в:

my-libcamera-apps/libcamera-apps/apps/libcamera_vid.cpp

Кинем в лог:

LOG(1, "This is my custom libcamera-vid version");
try
{
	LOG(1, "This is my custom libcamera-vid version");
	
	LibcameraEncoder app;
	VideoOptions *options = app.GetOptions();
	if (options->Parse(argc, argv))
	{
		if (options->verbose >= 2)
			options->Print();

		event_loop(app);
	}
}
catch (std::exception const &e)
{
	LOG_ERROR("ERROR: *** " << e.what() << " ***");
	return -1;
}

Пересобираем:

make -j4

Update

meson compile -C build

Переходим в my-build и запускаем кастомную версию:

./libcamera-vid -t 2000

В итоге в консоли видим вывод:

Пример запуска libcamera-vid.

Отлично, проверим работоспособность базовой версии, сохранившей свою первозданность:

libcamera-vid -t 2000

В данном случае вывод в консоль будет иметь первоначальный вид. Все, работоспособность не нарушена, процессы осуществлены успешно. Переходим к парочке конкретных случаев-примеров, как и зачем это может использоваться.

Практический пример использования.

Собственно, даже нет особой необходимости заниматься придумыванием искусственных задач – реальный пример из недавних. Мне нужно разветвить поток с камеры и вывести в два отдельных окна. При всем при этом в одном окне должно быть live-preview, то есть изображение с камеры в реальном времени. Во втором же необходимо выводить то же самое, но с задержкой.

Непосредственно для формирования задержки один из вариантов – использовать команду delay.   Представляет она из себя следующее:

delay introduces a constant delay between its standard input and its standard output. 
The data from its stdin will be stored until it has been written to stdout.

Лучше и не скажешь, устанавливаем:

sudo apt -y install delay

Ах да, я забыл упомянуть, что параллельно необходимо записывать видео в файл. Ключевой нюанс 🙂 Если бы не это, то можно было бы просто перенаправить поток в stdout и воспроизвести хоть тем же mpv:

./libcamera-vid -t 0 -o /dev/stdout | delay 2s | mpv /dev/stdin

Аналогичный эффект дает упрощенно-сокращенная запись:

./libcamera-vid -t 0 -o - | delay 2s | mpv -

Величина на практике будет иной, то есть ровно 2 секунды мы таким образом не получим, здесь в случае необходимости нужно провести дополнительную настройку/калибровку. Суть в том, что работает все корректно:

Libcamera, пример использования.

Но(!) Как мы выяснили, параллельно нужно скинуть видео в файл, то есть будем использовать:

./libcamera-vid -t 0 -o test_video.h264

А для решения полной задачи  отредактируем libcamera-vid так, чтобы поток выводился в stdout всегда, вне зависимости от аргументов. Досконально описывать не буду, в случае чего, пишите в комментарии или на форуме, по мере возможности я всегда стараюсь помочь. В область нашего интереса, в первую очередь, попадает класс FileOutput:

  • my-libcamera-apps\libcamera-apps\output\file_output.cpp
  • my-libcamera-apps\libcamera-apps\output\file_output.hpp

По умолчанию поток отправляется в файл:

FILE *fp_;

Добавляем свой:

FILE *fp_stdout;

В конструкторе:

fp_stdout = stdout;

Все максимально просто и логично 👍 Основную работу произведем в функции:

void FileOutput::outputBuffer(void *mem, size_t size, int64_t timestamp_us, uint32_t flags)

Она вызывается после каждого фрейма – то что надо:

void FileOutput::outputBuffer(void *mem, size_t size, int64_t timestamp_us, uint32_t flags)
{
	if (fp_stdout && size)
	{
		if (fwrite(mem, size, 1, fp_stdout) != 1)
			throw std::runtime_error("failed to write output bytes to stdout");
		if (options_->flush)
			fflush(fp_stdout);
	}
	
	// We need to open a new file if we're in "segment" mode and our segment is full
	// (though we have to wait for the next I frame), or if we're in "split" mode
	// and recording is being restarted (this is necessarily an I-frame already).
	if (fp_ == nullptr ||
		(options_->segment && (flags & FLAG_KEYFRAME) &&
		 timestamp_us / 1000 - file_start_time_ms_ > options_->segment) ||
		(options_->split && (flags & FLAG_RESTART)))
	{
		closeFile();
		openFile(timestamp_us);
	}

	LOG(2, "FileOutput: output buffer " << mem << " size " << size);
	if (fp_ && size)
	{
		if (fwrite(mem, size, 1, fp_) != 1)
			throw std::runtime_error("failed to write output bytes");
		if (options_->flush)
			fflush(fp_);
	}
}

Ничего лишнего, только необходимое. Пробуем собрать и запустить, сборка по-прежнему командой:

make -j4

И запуск:

./libcamera-vid -t 0 -o test_video.h264 | delay 2s | mpv -

Результат:

Иначе как успехом не назовешь, все в соответствии с ТЗ:

  • Превью в реальном времени
  • Отложенное видео в отдельном окне
  • Параллельно все пишется в файл

Таким образом, внеся незначительные изменения и проведя модификации, получили решение конкретной имеющейся задачи. Еще один пример, пожалуй, приведу в отдельной статье, чтобы не перегружать объемом эту, так что оставайтесь на связи!

/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
 *
 * file_output.cpp - Write output to file.
 */

#include "file_output.hpp"

FileOutput::FileOutput(VideoOptions const *options)
	: Output(options), fp_(nullptr), count_(0), file_start_time_ms_(0)
{
	fp_stdout = stdout;
}

FileOutput::~FileOutput()
{
	closeFile();
}

void FileOutput::outputBuffer(void *mem, size_t size, int64_t timestamp_us, uint32_t flags)
{
	if (fp_stdout && size)
	{
		if (fwrite(mem, size, 1, fp_stdout) != 1)
			throw std::runtime_error("failed to write output bytes to stdout");
		if (options_->flush)
			fflush(fp_stdout);
	}
	
	// We need to open a new file if we're in "segment" mode and our segment is full
	// (though we have to wait for the next I frame), or if we're in "split" mode
	// and recording is being restarted (this is necessarily an I-frame already).
	if (fp_ == nullptr ||
		(options_->segment && (flags & FLAG_KEYFRAME) &&
		 timestamp_us / 1000 - file_start_time_ms_ > options_->segment) ||
		(options_->split && (flags & FLAG_RESTART)))
	{
		closeFile();
		openFile(timestamp_us);
	}

	LOG(2, "FileOutput: output buffer " << mem << " size " << size);
	if (fp_ && size)
	{
		if (fwrite(mem, size, 1, fp_) != 1)
			throw std::runtime_error("failed to write output bytes");
		if (options_->flush)
			fflush(fp_);
	}
}

void FileOutput::openFile(int64_t timestamp_us)
{
	if (options_->output == "-")
		fp_ = stdout;
	else if (!options_->output.empty())
	{
		// Generate the next output file name.
		char filename[256];
		int n = snprintf(filename, sizeof(filename), options_->output.c_str(), count_);
		count_++;
		if (options_->wrap)
			count_ = count_ % options_->wrap;
		if (n < 0)
			throw std::runtime_error("failed to generate filename");

		fp_ = fopen(filename, "w");
		if (!fp_)
			throw std::runtime_error("failed to open output file " + std::string(filename));
		LOG(2, "FileOutput: opened output file " << filename);

		file_start_time_ms_ = timestamp_us / 1000;
	}
}

void FileOutput::closeFile()
{
	if (fp_ && fp_ != stdout)
		fclose(fp_);
	fp_ = nullptr;
}
/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
 *
 * file_output.hpp - Write output to file.
 */

#pragma once

#include "output.hpp"

class FileOutput : public Output
{
public:
	FileOutput(VideoOptions const *options);
	~FileOutput();

protected:
	void outputBuffer(void *mem, size_t size, int64_t timestamp_us, uint32_t flags) override;

private:
	void openFile(int64_t timestamp_us);
	void closeFile();
	FILE *fp_;
	FILE *fp_stdout;
	unsigned int count_;
	int64_t file_start_time_ms_;
};
Подписаться
Уведомить о
guest

17 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
fedor_alex
5 месяцев назад

Добрый день! В статье видимо пропущены некоторые шаги. При попытке сделать "cmake .. ". Пишет что нет CMakeList.txt и папка сборки действительна пуста. И даже если make делать в папке ~/my-libcamerf-apps в которой файлы есть сборка не начинается.

Подскажите что сделать?

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад

Очередной день Сурка... Ничего не изменилось, ошибка та же. Под папки build нет совсем, есть my-build, она пуста. Есть папка apps, там какие то файлы. Но сборка по прежнему не происходит

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад
gromx@raspberrypi:~ $ ls
Bookshelf camera_test.py Desktop Documents Downloads Music my-libcamera-apps Pictures Public Templates Videos
gromx@raspberrypi:~ $ cd my-libcamera-apps/
gromx@raspberrypi:~/my-libcamera-apps $ ls
libcamera-apps
gromx@raspberrypi:~/my-libcamera-apps $ cd libcamera-apps/
gromx@raspberrypi:~/my-libcamera-apps/libcamera-apps $ ls
apps  core   image    meson.build    my-build post_processing_stages README.md
assets encoder license.txt meson_options.txt output  preview         utils
gromx@raspberrypi:~/my-libcamera-apps/libcamera-apps $ 

Вот что там в наличии

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад

Не сильно помогло, сборка началась, но посыпалось множество ошибок и сборка завершилась крахом.

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад
gromx@raspberrypi:~/my-libcamera-apps/libcamera-apps $ meson compile -C build
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: /usr/bin/ninja -C /home/gromx/my-libcamera-apps/libcamera-apps/build
ninja: Entering directory `/home/gromx/my-libcamera-apps/libcamera-apps/build'
[6/43] Compiling C++ object libcamera_app.so.1.2.2.p/core_options.cpp.o
FAILED: libcamera_app.so.1.2.2.p/core_options.cpp.o 
c++ -Ilibcamera_app.so.1.2.2.p -I. -I.. -Icore -I/usr/include/libcamera -I/usr/include -I/usr/include/aarch64-linux-gnu -I/usr/include/libpng16 -I/usr/include/libdrm -I/usr/include/aarch64-linux-gnu/qt5/QtCore -I/usr/include/aarch64-linux-gnu/qt5 -I/usr/include/aarch64-linux-gnu/qt5/QtWidgets -I/usr/include/aarch64-linux-gnu/qt5/QtGui -fdiagnostics-color=always -Wall -Winvalid-pch -Wextra -Wpedantic -Werror -std=c++17 -O3 -pedantic -Wno-unused-parameter -faligned-new -D_FILE_OFFSET_BITS=64 -Wno-psabi -ftree-vectorize -DLIBDRM_PRESENT=1 -DLIBEGL_PRESENT=1 -DQT_PRESENT=1 -fPIC -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -pthread -DBOOST_ALL_NO_LIB -MD -MQ libcamera_app.so.1.2.2.p/core_options.cpp.o -MF libcamera_app.so.1.2.2.p/core_options.cpp.o.d -o libcamera_app.so.1.2.2.p/core_options.cpp.o -c ../core/options.cpp
../core/options.cpp: In member function 'virtual bool Options::Parse(int, char**)':
../core/options.cpp:267:15: error: 'class libcamera::CameraConfiguration' has no member named 'sensorConfig'
 267 |    config->sensorConfig = libcamera::SensorConfiguration();
   |        ^~~~~~~~~~~~
../core/options.cpp:267:41: error: 'SensorConfiguration' is not a member of 'libcamera'; did you mean 'StreamConfiguration'?
 267 |    config->sensorConfig = libcamera::SensorConfiguration();
   |                     ^~~~~~~~~~~~~~~~~~~
   |                     StreamConfiguration
../core/options.cpp:268:15: error: 'class libcamera::CameraConfiguration' has no member named 'sensorConfig'
 268 |    config->sensorConfig->outputSize = size;

И так далее ещё на страницу

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад

Нет не изменило. Пакеты эти я раньше ставил, согласно данной статье (и другим урокам). Было установлено ноль пакетов и ноль обновлено.

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад

Спасибо за помощь. Победил! Просто снёс 64 битную версию, поставил на 32 бита - и сразу из коробки камера заработала! Просто в raspbery-config включил. И дал команду покажи картинку! 🙂

fedor_alex
Ответ на комментарий  Aveal
5 месяцев назад

Да, пакеты поставил и да всё работает, осталось только на Qt прикрутить свой интерфейс, это уже проще 🙂

17
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x