From 7013b75daf98022a6f7df96d5092c9ee7129a25e Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:23:44 +0200 Subject: [PATCH] rpicamera: add rpiCameraMode parameter (#1111) --- internal/conf/path.go | 1 + internal/core/source_static.go | 1 + internal/rpicamera/exe/Makefile | 4 +- internal/rpicamera/exe/camera.cpp | 84 ++++++++++++++++++---------- internal/rpicamera/exe/camera.h | 9 ++- internal/rpicamera/exe/encoder.c | 12 ++-- internal/rpicamera/exe/encoder.h | 5 ++ internal/rpicamera/exe/main.c | 12 ++-- internal/rpicamera/exe/parameters.c | 41 +++++++++++++- internal/rpicamera/exe/parameters.h | 16 +++++- internal/rpicamera/exe/roi.c | 30 ++++++++++ internal/rpicamera/exe/roi.h | 15 +++++ internal/rpicamera/exe/sensor_mode.c | 30 ++++++++++ internal/rpicamera/exe/sensor_mode.h | 15 +++++ internal/rpicamera/params.go | 1 + internal/rpicamera/rpicamera.go | 1 + rtsp-simple-server.yml | 5 +- 17 files changed, 235 insertions(+), 47 deletions(-) create mode 100644 internal/rpicamera/exe/roi.c create mode 100644 internal/rpicamera/exe/roi.h create mode 100644 internal/rpicamera/exe/sensor_mode.c create mode 100644 internal/rpicamera/exe/sensor_mode.h diff --git a/internal/conf/path.go b/internal/conf/path.go index 402756c6..8c41850b 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -67,6 +67,7 @@ type PathConf struct { RPICameraEV float64 `json:"rpiCameraEV"` RPICameraROI string `json:"rpiCameraROI"` RPICameraTuningFile string `json:"rpiCameraTuningFile"` + RPICameraMode string `json:"rpiCameraMode"` RPICameraFPS int `json:"rpiCameraFPS"` RPICameraIDRPeriod int `json:"rpiCameraIDRPeriod"` RPICameraBitrate int `json:"rpiCameraBitrate"` diff --git a/internal/core/source_static.go b/internal/core/source_static.go index e1879352..6536d1bf 100644 --- a/internal/core/source_static.go +++ b/internal/core/source_static.go @@ -104,6 +104,7 @@ func newSourceStatic( EV: conf.RPICameraEV, ROI: conf.RPICameraROI, TuningFile: conf.RPICameraTuningFile, + Mode: conf.RPICameraMode, FPS: conf.RPICameraFPS, IDRPeriod: conf.RPICameraIDRPeriod, Bitrate: conf.RPICameraBitrate, diff --git a/internal/rpicamera/exe/Makefile b/internal/rpicamera/exe/Makefile index f7d33c0e..d9ff7def 100644 --- a/internal/rpicamera/exe/Makefile +++ b/internal/rpicamera/exe/Makefile @@ -26,7 +26,9 @@ OBJS = \ camera.o \ encoder.o \ main.o \ - parameters.o + parameters.o \ + roi.o \ + sensor_mode.o all: exe diff --git a/internal/rpicamera/exe/camera.cpp b/internal/rpicamera/exe/camera.cpp index 9f5e55bc..89060bf5 100644 --- a/internal/rpicamera/exe/camera.cpp +++ b/internal/rpicamera/exe/camera.cpp @@ -46,11 +46,32 @@ const char *camera_get_error() { return errbuf; } +// https://github.com/raspberrypi/libcamera-apps/blob/dd97618a25523c2c4aa58f87af5f23e49aa6069c/core/libcamera_app.cpp#L42 +static libcamera::PixelFormat mode_to_pixel_format(sensor_mode_t *mode) { + static std::vector, libcamera::PixelFormat>> table = { + { {8, false}, libcamera::formats::SBGGR8 }, + { {8, true}, libcamera::formats::SBGGR8 }, + { {10, false}, libcamera::formats::SBGGR10 }, + { {10, true}, libcamera::formats::SBGGR10_CSI2P }, + { {12, false}, libcamera::formats::SBGGR12 }, + { {12, true}, libcamera::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 libcamera::formats::SBGGR12_CSI2P; +} + struct CameraPriv { parameters_t *params; camera_frame_cb frame_cb; std::unique_ptr camera_manager; std::shared_ptr camera; + Stream *video_stream; std::unique_ptr allocator; std::vector> requests; }; @@ -87,6 +108,8 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca return false; } + setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1); + ret = camp->camera->acquire(); if (ret != 0) { set_error("Camera.acquire() failed"); @@ -94,21 +117,32 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca } StreamRoles stream_roles = { StreamRole::VideoRecording }; + if (params->mode != NULL) { + stream_roles.push_back(StreamRole::Raw); + } + std::unique_ptr conf = camp->camera->generateConfiguration(stream_roles); if (conf == NULL) { set_error("Camera.generateConfiguration() failed"); return false; } - StreamConfiguration &stream_conf = conf->at(0); - stream_conf.pixelFormat = formats::YUV420; - stream_conf.bufferCount = params->buffer_count; - stream_conf.size.width = params->width; - stream_conf.size.height = params->height; + StreamConfiguration &video_stream_conf = conf->at(0); + video_stream_conf.pixelFormat = formats::YUV420; + video_stream_conf.bufferCount = params->buffer_count; + video_stream_conf.size.width = params->width; + video_stream_conf.size.height = params->height; if (params->width >= 1280 || params->height >= 720) { - stream_conf.colorSpace = libcamera::ColorSpace::Rec709; + video_stream_conf.colorSpace = libcamera::ColorSpace::Rec709; } else { - stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m; + video_stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m; + } + + if (params->mode != NULL) { + StreamConfiguration &raw_stream_conf = conf->at(1); + raw_stream_conf.size = libcamera::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; @@ -119,8 +153,6 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca conf->transform = Transform::VFlip * conf->transform; } - setenv("LIBCAMERA_RPI_TUNING_FILE", params->tuning_file, 1); - CameraConfiguration::Status vstatus = conf->validate(); if (vstatus == CameraConfiguration::Invalid) { set_error("StreamConfiguration.validate() failed"); @@ -133,23 +165,23 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca return false; } - Stream *stream = stream_conf.stream(); + camp->video_stream = video_stream_conf.stream(); camp->allocator = std::make_unique(camp->camera); - res = camp->allocator->allocate(stream); + res = camp->allocator->allocate(camp->video_stream); if (res < 0) { set_error("allocate() failed"); return false; } - for (const std::unique_ptr &buffer : camp->allocator->buffers(stream)) { + for (const std::unique_ptr &buffer : camp->allocator->buffers(camp->video_stream)) { std::unique_ptr request = camp->camera->createRequest((uint64_t)camp.get()); if (request == NULL) { set_error("createRequest() failed"); return false; } - int res = request->addBuffer(stream, buffer.get()); + int res = request->addBuffer(camp->video_stream, buffer.get()); if (res != 0) { set_error("addBuffer() failed"); return false; @@ -185,14 +217,14 @@ static void on_request_complete(Request *request) { camp->camera->queueRequest(request); } -int camera_get_stride(camera_t *cam) { +int camera_get_mode_stride(camera_t *cam) { CameraPriv *camp = (CameraPriv *)cam; - return (*camp->camera->streams().begin())->configuration().stride; + return camp->video_stream->configuration().stride; } -int camera_get_colorspace(camera_t *cam) { +int camera_get_mode_colorspace(camera_t *cam) { CameraPriv *camp = (CameraPriv *)cam; - return get_v4l2_colorspace((*camp->camera->streams().begin())->configuration().colorSpace); + return get_v4l2_colorspace(camp->video_stream->configuration().colorSpace); } bool camera_start(camera_t *cam) { @@ -271,21 +303,13 @@ bool camera_start(camera_t *cam) { ctrls.set(controls::ExposureValue, camp->params->ev); - if (strlen(camp->params->roi) != 0) { - float vals[4]; - int i = 0; - char *token = strtok((char *)camp->params->roi, ","); - while (token != NULL) { - vals[i++] = atof(token); - token = strtok(NULL, ","); - } - + if (camp->params->roi != NULL) { Rectangle sensor_area = camp->camera->properties().get(libcamera::properties::ScalerCropMaximum); Rectangle crop( - vals[0] * sensor_area.width, - vals[1] * sensor_area.height, - vals[2] * sensor_area.width, - vals[3] * sensor_area.height); + 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()); ctrls.set(controls::ScalerCrop, crop); } diff --git a/internal/rpicamera/exe/camera.h b/internal/rpicamera/exe/camera.h index 61d7c956..42191b07 100644 --- a/internal/rpicamera/exe/camera.h +++ b/internal/rpicamera/exe/camera.h @@ -1,3 +1,6 @@ +#ifndef __CAMERA_H__ +#define __CAMERA_H__ + typedef void camera_t; typedef void (*camera_frame_cb)(int buffer_fd, uint64_t size, uint64_t timestamp); @@ -8,10 +11,12 @@ extern "C" { const char *camera_get_error(); bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **cam); -int camera_get_stride(camera_t *cam); -int camera_get_colorspace(camera_t *cam); +int camera_get_mode_stride(camera_t *cam); +int camera_get_mode_colorspace(camera_t *cam); bool camera_start(camera_t *cam); #ifdef __cplusplus } #endif + +#endif diff --git a/internal/rpicamera/exe/encoder.c b/internal/rpicamera/exe/encoder.c index 52ffe87c..15eb72ca 100644 --- a/internal/rpicamera/exe/encoder.c +++ b/internal/rpicamera/exe/encoder.c @@ -17,6 +17,8 @@ #include "parameters.h" #include "encoder.h" +#define DEVICE "/dev/video11" + char errbuf[256]; static void set_error(const char *format, ...) { @@ -58,7 +60,7 @@ static void *output_thread(void *userdata) { buf.m.planes = planes; int res = ioctl(encp->fd, VIDIOC_DQBUF, &buf); if (res != 0) { - fprintf(stderr, "output_thread(): ioctl() failed\n"); + fprintf(stderr, "output_thread(): ioctl(VIDIOC_DQBUF) failed\n"); exit(1); } @@ -88,7 +90,7 @@ static void *output_thread(void *userdata) { buf.m.planes[0].length = length; int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); if (res < 0) { - fprintf(stderr, "output_thread(): ioctl() failed\n"); + fprintf(stderr, "output_thread(): ioctl(VIDIOC_QBUF) failed\n"); exit(1); } } @@ -102,7 +104,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou *enc = malloc(sizeof(encoder_priv_t)); encoder_priv_t *encp = (encoder_priv_t *)(*enc); - encp->fd = open("/dev/video11", O_RDWR, 0); + encp->fd = open(DEVICE, O_RDWR, 0); if (encp->fd < 0) { set_error("unable to open device"); return false; @@ -254,7 +256,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou res = ioctl(encp->fd, VIDIOC_QBUF, &buffer); if (res != 0) { - set_error("ioctl() failed"); + set_error("ioctl(VIDIOC_QBUF) failed"); free(encp->capture_buffers); close(encp->fd); return false; @@ -309,7 +311,7 @@ void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestam buf.m.planes[0].length = size; int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); if (res != 0) { - fprintf(stderr, "encoder_encode(): ioctl() failed\n"); + fprintf(stderr, "encoder_encode(): ioctl(VIDIOC_QBUF) failed\n"); exit(1); } } diff --git a/internal/rpicamera/exe/encoder.h b/internal/rpicamera/exe/encoder.h index 6ea9438c..d5a71010 100644 --- a/internal/rpicamera/exe/encoder.h +++ b/internal/rpicamera/exe/encoder.h @@ -1,3 +1,6 @@ +#ifndef __ENCODER_H__ +#define __ENCODER_H__ + typedef void encoder_t; typedef void (*encoder_output_cb)(const uint8_t *buf, uint64_t size); @@ -5,3 +8,5 @@ typedef void (*encoder_output_cb)(const uint8_t *buf, uint64_t size); const char *encoder_get_error(); bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_output_cb output_cb, encoder_t **enc); void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestamp_us); + +#endif diff --git a/internal/rpicamera/exe/main.c b/internal/rpicamera/exe/main.c index cf4c2437..953f09ee 100644 --- a/internal/rpicamera/exe/main.c +++ b/internal/rpicamera/exe/main.c @@ -72,9 +72,13 @@ int main() { pthread_mutex_init(&pipe_mutex, NULL); pthread_mutex_lock(&pipe_mutex); - parameters_load(¶ms); + bool ok = parameters_load(¶ms); + if (!ok) { + pipe_write_error(pipe_fd, "parameters_load(): %s", parameters_get_error()); + return 5; + } - bool ok = camera_create( + ok = camera_create( ¶ms, on_frame, &cam); @@ -85,8 +89,8 @@ int main() { ok = encoder_create( ¶ms, - camera_get_stride(cam), - camera_get_colorspace(cam), + camera_get_mode_stride(cam), + camera_get_mode_colorspace(cam), on_encoder_output, &enc); if (!ok) { diff --git a/internal/rpicamera/exe/parameters.c b/internal/rpicamera/exe/parameters.c index a0d8175c..621294d9 100644 --- a/internal/rpicamera/exe/parameters.c +++ b/internal/rpicamera/exe/parameters.c @@ -1,12 +1,26 @@ #include #include #include +#include +#include #include #include "parameters.h" -void parameters_load(parameters_t *params) { +char errbuf[256]; + +static void set_error(const char *format, ...) { + va_list args; + va_start(args, format); + vsnprintf(errbuf, 256, format, args); +} + +const char *parameters_get_error() { + return errbuf; +} + +bool parameters_load(parameters_t *params) { params->camera_id = atoi(getenv("CAMERA_ID")); params->width = atoi(getenv("WIDTH")); params->height = atoi(getenv("HEIGHT")); @@ -23,8 +37,29 @@ void parameters_load(parameters_t *params) { params->metering = getenv("METERING"); params->gain = atof(getenv("GAIN")); params->ev = atof(getenv("EV")); - params->roi = getenv("ROI"); + + if (strlen(getenv("ROI")) != 0) { + bool ok = roi_load(getenv("ROI"), ¶ms->roi); + if (!ok) { + set_error("invalid ROI"); + return false; + } + } else { + params->roi = NULL; + } + params->tuning_file = getenv("TUNING_FILE"); + + if (strlen(getenv("MODE")) != 0) { + bool ok = sensor_mode_load(getenv("MODE"), ¶ms->mode); + if (!ok) { + set_error("invalid sensor mode"); + return false; + } + } else { + params->mode = NULL; + } + params->fps = atoi(getenv("FPS")); params->idr_period = atoi(getenv("IDR_PERIOD")); params->bitrate = atoi(getenv("BITRATE")); @@ -49,4 +84,6 @@ void parameters_load(parameters_t *params) { params->buffer_count = 3; params->capture_buffer_count = params->buffer_count * 2; + + return true; } diff --git a/internal/rpicamera/exe/parameters.h b/internal/rpicamera/exe/parameters.h index 1d94c059..f96fc9cd 100644 --- a/internal/rpicamera/exe/parameters.h +++ b/internal/rpicamera/exe/parameters.h @@ -1,3 +1,11 @@ +#ifndef __PARAMETERS_H__ +#define __PARAMETERS_H__ + +#include + +#include "roi.h" +#include "sensor_mode.h" + typedef struct { unsigned int camera_id; unsigned int width; @@ -15,8 +23,9 @@ typedef struct { const char *metering; float gain; float ev; - const char *roi; + roi_t *roi; const char *tuning_file; + sensor_mode_t *mode; unsigned int fps; unsigned int idr_period; unsigned int bitrate; @@ -32,8 +41,11 @@ typedef struct { extern "C" { #endif -void parameters_load(parameters_t *params); +const char *parameters_get_error(); +bool parameters_load(parameters_t *params); #ifdef __cplusplus } #endif + +#endif diff --git a/internal/rpicamera/exe/roi.c b/internal/rpicamera/exe/roi.c new file mode 100644 index 00000000..f82c271f --- /dev/null +++ b/internal/rpicamera/exe/roi.c @@ -0,0 +1,30 @@ +#include +#include + +#include "roi.h" + +bool roi_load(const char *encoded, roi_t **mode) { + float vals[4]; + int i = 0; + char *token = strtok((char *)encoded, ","); + while (token != NULL) { + vals[i] = atof(token); + if (vals[i] < 0 || vals[i] > 1) { + return false; + } + + i++; + token = strtok(NULL, ","); + } + + if (i != 4) { + return false; + } + + *mode = malloc(sizeof(roi_t)); + (*mode)->x = vals[0]; + (*mode)->y = vals[1]; + (*mode)->width = vals[2]; + (*mode)->height = vals[3]; + return true; +} diff --git a/internal/rpicamera/exe/roi.h b/internal/rpicamera/exe/roi.h new file mode 100644 index 00000000..4634d925 --- /dev/null +++ b/internal/rpicamera/exe/roi.h @@ -0,0 +1,15 @@ +#ifndef __ROI_H__ +#define __ROI_H__ + +#include + +typedef struct { + float x; + float y; + float width; + float height; +} roi_t; + +bool roi_load(const char *encoded, roi_t **mode); + +#endif diff --git a/internal/rpicamera/exe/sensor_mode.c b/internal/rpicamera/exe/sensor_mode.c new file mode 100644 index 00000000..b41663cb --- /dev/null +++ b/internal/rpicamera/exe/sensor_mode.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "sensor_mode.h" + +bool sensor_mode_load(const char *encoded, sensor_mode_t **mode) { + *mode = malloc(sizeof(sensor_mode_t)); + + char p; + int n = sscanf(encoded, "%u:%u:%u:%c", &((*mode)->width), &((*mode)->height), &((*mode)->bit_depth), &p); + if (n < 2) { + free(*mode); + return false; + } + + if (n < 4) { + (*mode)->packed = true; + } else if (toupper(p) == 'P') { + (*mode)->packed = true; + } else if (toupper(p) == 'U') { + (*mode)->packed = false; + } + + if (n < 3) { + (*mode)->bit_depth = 12; + } + + return true; +} diff --git a/internal/rpicamera/exe/sensor_mode.h b/internal/rpicamera/exe/sensor_mode.h new file mode 100644 index 00000000..ab91839c --- /dev/null +++ b/internal/rpicamera/exe/sensor_mode.h @@ -0,0 +1,15 @@ +#ifndef __SENSOR_MODE_H__ +#define __SENSOR_MODE_H__ + +#include + +typedef struct { + int width; + int height; + int bit_depth; + bool packed; +} sensor_mode_t; + +bool sensor_mode_load(const char *encoded, sensor_mode_t **mode); + +#endif diff --git a/internal/rpicamera/params.go b/internal/rpicamera/params.go index b5ccf53d..5e3761cb 100644 --- a/internal/rpicamera/params.go +++ b/internal/rpicamera/params.go @@ -20,6 +20,7 @@ type Params struct { EV float64 ROI string TuningFile string + Mode string FPS int IDRPeriod int Bitrate int diff --git a/internal/rpicamera/rpicamera.go b/internal/rpicamera/rpicamera.go index f8afe876..bd489b09 100644 --- a/internal/rpicamera/rpicamera.go +++ b/internal/rpicamera/rpicamera.go @@ -60,6 +60,7 @@ func New( "EV=" + strconv.FormatFloat(params.EV, 'f', -1, 64), "ROI=" + params.ROI, "TUNING_FILE=" + params.TuningFile, + "MODE=" + params.Mode, "FPS=" + strconv.FormatInt(int64(params.FPS), 10), "IDR_PERIOD=" + strconv.FormatInt(int64(params.IDRPeriod), 10), "BITRATE=" + strconv.FormatInt(int64(params.Bitrate), 10), diff --git a/rtsp-simple-server.yml b/rtsp-simple-server.yml index a6d280ea..b2423254 100644 --- a/rtsp-simple-server.yml +++ b/rtsp-simple-server.yml @@ -264,10 +264,13 @@ paths: rpiCameraGain: 0 # EV compensation of the image [-10, 10] rpiCameraEV: 0 - # Region of interst in format x,y,width,height + # Region of interest, in format x,y,width,height rpiCameraROI: # tuning file rpiCameraTuningFile: + # sensor mode, in format [width]:[height]:[bit-depth]:[packing] + # bit-depth and packing are optional. + rpiCameraMode: # frames per second rpiCameraFPS: 30 # period between IDR frames