Ready-to-use SRT / WebRTC / RTSP / RTMP / LL-HLS media server and media proxy that allows to read, publish, proxy, record and playback video and audio streams.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

499 lines
17 KiB

#include <stdio.h>
#include <stdarg.h>
#include <cstring>
#include <sys/mman.h>
#include <iostream>
#include <mutex>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <libcamera/camera_manager.h>
#include <libcamera/camera.h>
#include <libcamera/formats.h>
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/framebuffer_allocator.h>
#include <libcamera/property_ids.h>
#include <linux/videodev2.h>
#include "camera.h"
using libcamera::CameraManager;
using libcamera::CameraConfiguration;
using libcamera::Camera;
using libcamera::ColorSpace;
using libcamera::ControlList;
using libcamera::FrameBufferAllocator;
using libcamera::FrameBuffer;
using libcamera::PixelFormat;
using libcamera::Rectangle;
using libcamera::Request;
using libcamera::Size;
using libcamera::Span;
using libcamera::Stream;
using libcamera::StreamRole;
using libcamera::StreamConfiguration;
using libcamera::Transform;
namespace controls = libcamera::controls;
namespace formats = libcamera::formats;
namespace properties = libcamera::properties;
static char errbuf[256];
static void set_error(const char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(errbuf, 256, format, args);
}
const char *camera_get_error() {
return errbuf;
}
// https://github.com/raspberrypi/libcamera-apps/blob/dd97618a25523c2c4aa58f87af5f23e49aa6069c/core/libcamera_app.cpp#L42
static PixelFormat mode_to_pixel_format(sensor_mode_t *mode) {
static std::vector<std::pair<std::pair<int, bool>, PixelFormat>> table = {
{ {8, false}, formats::SBGGR8 },
{ {8, true}, formats::SBGGR8 },
{ {10, false}, formats::SBGGR10 },
{ {10, true}, formats::SBGGR10_CSI2P },
{ {12, false}, formats::SBGGR12 },
{ {12, true}, formats::SBGGR12_CSI2P },
};
auto it = std::find_if(table.begin(), table.end(), [&mode] (auto &m) {
return mode->bit_depth == m.first.first && mode->packed == m.first.second; });
if (it != table.end()) {
return it->second;
}
return formats::SBGGR12_CSI2P;
}
struct CameraPriv {
const parameters_t *params;
camera_frame_cb frame_cb;
std::unique_ptr<CameraManager> camera_manager;
std::shared_ptr<Camera> camera;
Stream *video_stream;
std::unique_ptr<FrameBufferAllocator> allocator;
std::vector<std::unique_ptr<Request>> requests;
std::mutex ctrls_mutex;
std::unique_ptr<ControlList> ctrls;
std::map<FrameBuffer *, uint8_t *> mapped_buffers;
};
static int get_v4l2_colorspace(std::optional<ColorSpace> const &cs) {
if (cs == ColorSpace::Rec709) {
return V4L2_COLORSPACE_REC709;
}
return V4L2_COLORSPACE_SMPTE170M;
}
// https://github.com/raspberrypi/libcamera-apps/blob/a5b5506a132056ac48ba22bc581cc394456da339/core/libcamera_app.cpp#L824
static uint8_t *map_buffer(FrameBuffer *buffer) {
size_t buffer_size = 0;
for (unsigned i = 0; i < buffer->planes().size(); i++) {
const FrameBuffer::Plane &plane = buffer->planes()[i];
buffer_size += plane.length;
if (i == buffer->planes().size() - 1 || plane.fd.get() != buffer->planes()[i + 1].fd.get()) {
return (uint8_t *)mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, plane.fd.get(), 0);
}
}
return NULL;
}
// https://github.com/raspberrypi/libcamera-apps/blob/a6267d51949d0602eedf60f3ddf8c6685f652812/core/options.cpp#L101
static void set_hdr(bool hdr) {
bool ok = false;
for (int i = 0; i < 4 && !ok; i++)
{
std::string dev("/dev/v4l-subdev");
dev += (char)('0' + i);
int fd = open(dev.c_str(), O_RDWR, 0);
if (fd < 0)
continue;
v4l2_control ctrl { V4L2_CID_WIDE_DYNAMIC_RANGE, hdr };
ok = !ioctl(fd, VIDIOC_S_CTRL, &ctrl);
close(fd);
}
}
bool camera_create(const parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) {
std::unique_ptr<CameraPriv> camp = std::make_unique<CameraPriv>();
set_hdr(params->hdr);
if (strcmp(params->log_level, "debug") == 0) {
setenv("LIBCAMERA_LOG_LEVELS", "*:DEBUG", 1);
} else if (strcmp(params->log_level, "info") == 0) {
setenv("LIBCAMERA_LOG_LEVELS", "*:INFO", 1);
} else if (strcmp(params->log_level, "warn") == 0) {
setenv("LIBCAMERA_LOG_LEVELS", "*:WARN", 1);
} else { // error
setenv("LIBCAMERA_LOG_LEVELS", "*:ERROR", 1);
}
// We make sure to set the environment variable before libcamera init
setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1);
camp->camera_manager = std::make_unique<CameraManager>();
int ret = camp->camera_manager->start();
if (ret != 0) {
set_error("CameraManager.start() failed");
return false;
}
std::vector<std::shared_ptr<Camera>> cameras = camp->camera_manager->cameras();
auto rem = std::remove_if(cameras.begin(), cameras.end(),
[](auto &cam) { return cam->id().find("/usb") != std::string::npos; });
cameras.erase(rem, cameras.end());
if (params->camera_id >= cameras.size()){
set_error("selected camera is not available");
return false;
}
camp->camera = camp->camera_manager->get(cameras[params->camera_id]->id());
if (camp->camera == NULL) {
set_error("CameraManager.get() failed");
return false;
}
ret = camp->camera->acquire();
if (ret != 0) {
set_error("Camera.acquire() failed");
return false;
}
std::vector<libcamera::StreamRole> stream_roles = { StreamRole::VideoRecording };
if (params->mode != NULL) {
stream_roles.push_back(StreamRole::Raw);
}
std::unique_ptr<CameraConfiguration> conf = camp->camera->generateConfiguration(stream_roles);
if (conf == NULL) {
set_error("Camera.generateConfiguration() failed");
return false;
}
StreamConfiguration &video_stream_conf = conf->at(0);
video_stream_conf.size = libcamera::Size(params->width, params->height);
video_stream_conf.pixelFormat = formats::YUV420;
video_stream_conf.bufferCount = params->buffer_count;
if (params->width >= 1280 || params->height >= 720) {
video_stream_conf.colorSpace = ColorSpace::Rec709;
} else {
video_stream_conf.colorSpace = ColorSpace::Smpte170m;
}
if (params->mode != NULL) {
StreamConfiguration &raw_stream_conf = conf->at(1);
raw_stream_conf.size = Size(params->mode->width, params->mode->height);
raw_stream_conf.pixelFormat = mode_to_pixel_format(params->mode);
raw_stream_conf.bufferCount = video_stream_conf.bufferCount;
}
conf->transform = Transform::Identity;
if (params->h_flip) {
conf->transform = Transform::HFlip * conf->transform;
}
if (params->v_flip) {
conf->transform = Transform::VFlip * conf->transform;
}
CameraConfiguration::Status vstatus = conf->validate();
if (vstatus == CameraConfiguration::Invalid) {
set_error("StreamConfiguration.validate() failed");
return false;
}
int res = camp->camera->configure(conf.get());
if (res != 0) {
set_error("Camera.configure() failed");
return false;
}
camp->video_stream = video_stream_conf.stream();
for (unsigned int i = 0; i < params->buffer_count; i++) {
std::unique_ptr<Request> request = camp->camera->createRequest((uint64_t)camp.get());
if (request == NULL) {
set_error("createRequest() failed");
return false;
}
camp->requests.push_back(std::move(request));
}
camp->allocator = std::make_unique<FrameBufferAllocator>(camp->camera);
for (StreamConfiguration &stream_conf : *conf) {
Stream *stream = stream_conf.stream();
res = camp->allocator->allocate(stream);
if (res < 0) {
set_error("allocate() failed");
return false;
}
int i = 0;
for (const std::unique_ptr<FrameBuffer> &buffer : camp->allocator->buffers(stream)) {
// map buffer of the video stream only
if (stream == video_stream_conf.stream()) {
camp->mapped_buffers[buffer.get()] = map_buffer(buffer.get());
}
res = camp->requests.at(i++)->addBuffer(stream, buffer.get());
if (res != 0) {
set_error("addBuffer() failed");
return false;
}
}
}
camp->params = params;
camp->frame_cb = frame_cb;
*cam = camp.release();
return true;
}
static int buffer_size(const std::vector<FrameBuffer::Plane> &planes) {
int size = 0;
for (const FrameBuffer::Plane &plane : planes) {
size += plane.length;
}
return size;
}
static void on_request_complete(Request *request) {
if (request->status() == Request::RequestCancelled) {
return;
}
CameraPriv *camp = (CameraPriv *)request->cookie();
FrameBuffer *buffer = request->buffers().at(camp->video_stream);
camp->frame_cb(
camp->mapped_buffers.at(buffer),
camp->video_stream->configuration().stride,
camp->video_stream->configuration().size.height,
buffer->planes()[0].fd.get(),
buffer_size(buffer->planes()),
buffer->metadata().timestamp / 1000);
request->reuse(Request::ReuseFlag::ReuseBuffers);
{
std::lock_guard<std::mutex> lock(camp->ctrls_mutex);
request->controls() = *camp->ctrls;
camp->ctrls->clear();
}
camp->camera->queueRequest(request);
}
int camera_get_mode_stride(camera_t *cam) {
CameraPriv *camp = (CameraPriv *)cam;
return camp->video_stream->configuration().stride;
}
int camera_get_mode_colorspace(camera_t *cam) {
CameraPriv *camp = (CameraPriv *)cam;
return get_v4l2_colorspace(camp->video_stream->configuration().colorSpace);
}
static void fill_dynamic_controls(ControlList *ctrls, const parameters_t *params) {
ctrls->set(controls::Brightness, params->brightness);
ctrls->set(controls::Contrast, params->contrast);
ctrls->set(controls::Saturation, params->saturation);
ctrls->set(controls::Sharpness, params->sharpness);
int exposure_mode;
if (strcmp(params->exposure, "short") == 0) {
exposure_mode = controls::ExposureShort;
} else if (strcmp(params->exposure, "long") == 0) {
exposure_mode = controls::ExposureLong;
} else if (strcmp(params->exposure, "custom") == 0) {
exposure_mode = controls::ExposureCustom;
} else {
exposure_mode = controls::ExposureNormal;
}
ctrls->set(controls::AeExposureMode, exposure_mode);
int awb_mode;
if (strcmp(params->awb, "incandescent") == 0) {
awb_mode = controls::AwbIncandescent;
} else if (strcmp(params->awb, "tungsten") == 0) {
awb_mode = controls::AwbTungsten;
} else if (strcmp(params->awb, "fluorescent") == 0) {
awb_mode = controls::AwbFluorescent;
} else if (strcmp(params->awb, "indoor") == 0) {
awb_mode = controls::AwbIndoor;
} else if (strcmp(params->awb, "daylight") == 0) {
awb_mode = controls::AwbDaylight;
} else if (strcmp(params->awb, "cloudy") == 0) {
awb_mode = controls::AwbCloudy;
} else if (strcmp(params->awb, "custom") == 0) {
awb_mode = controls::AwbCustom;
} else {
awb_mode = controls::AwbAuto;
}
ctrls->set(controls::AwbMode, awb_mode);
if (params->awb_gain_red > 0 && params->awb_gain_blue > 0) {
ctrls->set(controls::ColourGains,
Span<const float, 2>({params->awb_gain_red, params->awb_gain_blue}));
}
int denoise_mode;
if (strcmp(params->denoise, "cdn_off") == 0) {
denoise_mode = controls::draft::NoiseReductionModeMinimal;
} else if (strcmp(params->denoise, "cdn_hq") == 0) {
denoise_mode = controls::draft::NoiseReductionModeHighQuality;
} else if (strcmp(params->denoise, "cdn_fast") == 0) {
denoise_mode = controls::draft::NoiseReductionModeFast;
} else {
denoise_mode = controls::draft::NoiseReductionModeOff;
}
ctrls->set(controls::draft::NoiseReductionMode, denoise_mode);
ctrls->set(controls::ExposureTime, params->shutter);
int metering_mode;
if (strcmp(params->metering, "spot") == 0) {
metering_mode = controls::MeteringSpot;
} else if (strcmp(params->metering, "matrix") == 0) {
metering_mode = controls::MeteringMatrix;
} else if (strcmp(params->metering, "custom") == 0) {
metering_mode = controls::MeteringCustom;
} else {
metering_mode = controls::MeteringCentreWeighted;
}
ctrls->set(controls::AeMeteringMode, metering_mode);
ctrls->set(controls::AnalogueGain, params->gain);
ctrls->set(controls::ExposureValue, params->ev);
int64_t frame_time = (int64_t)(((float)1000000) / params->fps);
ctrls->set(controls::FrameDurationLimits, Span<const int64_t, 2>({ frame_time, frame_time }));
}
bool camera_start(camera_t *cam) {
CameraPriv *camp = (CameraPriv *)cam;
camp->ctrls = std::make_unique<ControlList>(controls::controls);
fill_dynamic_controls(camp->ctrls.get(), camp->params);
if (camp->camera->controls().count(&controls::AfMode) > 0) {
if (camp->params->af_window != NULL) {
std::optional<Rectangle> opt = camp->camera->properties().get(properties::ScalerCropMaximum);
Rectangle sensor_area;
try {
sensor_area = opt.value();
} catch(const std::bad_optional_access& exc) {
set_error("get(ScalerCropMaximum) failed");
return false;
}
Rectangle afwindows_rectangle[1];
afwindows_rectangle[0] = Rectangle(
camp->params->af_window->x * sensor_area.width,
camp->params->af_window->y * sensor_area.height,
camp->params->af_window->width * sensor_area.width,
camp->params->af_window->height * sensor_area.height);
afwindows_rectangle[0].translateBy(sensor_area.topLeft());
camp->ctrls->set(controls::AfMetering, controls::AfMeteringWindows);
camp->ctrls->set(controls::AfWindows, afwindows_rectangle);
}
int af_mode;
if (strcmp(camp->params->af_mode, "manual") == 0) {
af_mode = controls::AfModeManual;
} else if (strcmp(camp->params->af_mode, "continuous") == 0) {
af_mode = controls::AfModeContinuous;
} else {
af_mode = controls::AfModeAuto;
}
camp->ctrls->set(controls::AfMode, af_mode);
int af_range;
if (strcmp(camp->params->af_range, "macro") == 0) {
af_range = controls::AfRangeMacro;
} else if (strcmp(camp->params->af_range, "full") == 0) {
af_range = controls::AfRangeFull;
} else {
af_range = controls::AfRangeNormal;
}
camp->ctrls->set(controls::AfRange, af_range);
int af_speed;
if (strcmp(camp->params->af_range, "fast") == 0) {
af_speed = controls::AfSpeedFast;
} else {
af_speed = controls::AfSpeedNormal;
}
camp->ctrls->set(controls::AfSpeed, af_speed);
if (strcmp(camp->params->af_mode, "auto") == 0) {
camp->ctrls->set(controls::AfTrigger, controls::AfTriggerStart);
} else if (strcmp(camp->params->af_mode, "manual") == 0) {
camp->ctrls->set(controls::LensPosition, camp->params->lens_position);
}
}
if (camp->params->roi != NULL) {
std::optional<Rectangle> opt = camp->camera->properties().get(properties::ScalerCropMaximum);
Rectangle sensor_area;
try {
sensor_area = opt.value();
} catch(const std::bad_optional_access& exc) {
set_error("get(ScalerCropMaximum) failed");
return false;
}
Rectangle crop(
camp->params->roi->x * sensor_area.width,
camp->params->roi->y * sensor_area.height,
camp->params->roi->width * sensor_area.width,
camp->params->roi->height * sensor_area.height);
crop.translateBy(sensor_area.topLeft());
camp->ctrls->set(controls::ScalerCrop, crop);
}
int res = camp->camera->start(camp->ctrls.get());
if (res != 0) {
set_error("Camera.start() failed");
return false;
}
camp->ctrls->clear();
camp->camera->requestCompleted.connect(on_request_complete);
for (std::unique_ptr<Request> &request : camp->requests) {
int res = camp->camera->queueRequest(request.get());
if (res != 0) {
set_error("Camera.queueRequest() failed");
return false;
}
}
return true;
}
void camera_reload_params(camera_t *cam, const parameters_t *params) {
CameraPriv *camp = (CameraPriv *)cam;
std::lock_guard<std::mutex> lock(camp->ctrls_mutex);
fill_dynamic_controls(camp->ctrls.get(), params);
}