From 8825fddd89f38b1cd88c8c28e52aa9a55e8c7982 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Mon, 24 Oct 2022 15:08:30 +0200 Subject: [PATCH] Add additional Raspberry Pi Camera parameters (#1198) * rpicamera: add rpiCameraHFlip and rpiCameraVFlip parameters * rpicamera: add rpiCameraBrightness, rpiCameraContrast, rpiCameraSaturation, rpiCameraSharpness, rpiCameraExposure, rpiCameraAWB, rpiCameraDenoise, rpiCameraShutter, rpiCameraMetering, rpiCameraGain, rpiCameraEV, rpiCameraROI, rpiCameraTuningFile * support float values in config file --- README.md | 2 +- internal/conf/env.go | 10 ++ internal/conf/env_test.go | 28 ++---- internal/conf/path.go | 24 +++++ internal/core/source_static.go | 31 ++++-- internal/rpicamera/exe/camera.cpp | 148 +++++++++++++++++++++++----- internal/rpicamera/exe/encoder.c | 132 ++++++++++++------------- internal/rpicamera/exe/parameters.c | 16 +++ internal/rpicamera/exe/parameters.h | 17 ++++ internal/rpicamera/params.go | 31 ++++-- internal/rpicamera/rpicamera.go | 22 +++++ rtsp-simple-server.yml | 44 ++++++++- scripts/binaries.mk | 4 +- 13 files changed, 383 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index fc2b355a..56492288 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ paths: rpiCameraHeight: 1080 ``` -All available parameters are listed in the [sample configuration file](https://github.com/aler9/rtsp-simple-server/blob/1e788f81fd46c7e8f5314bd4ae04989debfff52c/rtsp-simple-server.yml#L230). +All available parameters are listed in the [sample configuration file](https://github.com/aler9/rtsp-simple-server/blob/master/rtsp-simple-server.yml#L230). ### From OBS Studio diff --git a/internal/conf/env.go b/internal/conf/env.go index 1d019100..599ca43c 100644 --- a/internal/conf/env.go +++ b/internal/conf/env.go @@ -52,6 +52,16 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err } return nil + case reflect.TypeOf(float64(0)): + if ev, ok := env[prefix]; ok { + iv, err := strconv.ParseFloat(ev, 64) + if err != nil { + return fmt.Errorf("%s: %s", prefix, err) + } + rv.SetFloat(iv) + } + return nil + case reflect.TypeOf(bool(false)): if ev, ok := env[prefix]; ok { switch strings.ToLower(ev) { diff --git a/internal/conf/env_test.go b/internal/conf/env_test.go index 2e33157b..92915920 100644 --- a/internal/conf/env_test.go +++ b/internal/conf/env_test.go @@ -9,33 +9,21 @@ import ( ) type subStruct struct { - // int MyParam int } type mapEntry struct { - // string - MyValue string - - // struct + MyValue string MyStruct subStruct } type testStruct struct { - // string - MyString string - - // int - MyInt int - - // bool - MyBool bool - - // duration + MyString string + MyInt int + MyFloat float64 + MyBool bool MyDuration StringDuration - - // map - MyMap map[string]*mapEntry + MyMap map[string]*mapEntry } func TestEnvironment(t *testing.T) { @@ -45,6 +33,9 @@ func TestEnvironment(t *testing.T) { os.Setenv("MYPREFIX_MYINT", "123") defer os.Unsetenv("MYPREFIX_MYINT") + os.Setenv("MYPREFIX_MYFLOAT", "15.2") + defer os.Unsetenv("MYPREFIX_MYFLOAT") + os.Setenv("MYPREFIX_MYBOOL", "yes") defer os.Unsetenv("MYPREFIX_MYBOOL") @@ -66,6 +57,7 @@ func TestEnvironment(t *testing.T) { require.Equal(t, "testcontent", s.MyString) require.Equal(t, 123, s.MyInt) + require.Equal(t, 15.2, s.MyFloat) require.Equal(t, true, s.MyBool) require.Equal(t, 22*StringDuration(time.Second), s.MyDuration) diff --git a/internal/conf/path.go b/internal/conf/path.go index 77cdabdf..402756c6 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -52,6 +52,21 @@ type PathConf struct { RPICameraCamID int `json:"rpiCameraCamID"` RPICameraWidth int `json:"rpiCameraWidth"` RPICameraHeight int `json:"rpiCameraHeight"` + RPICameraHFlip bool `json:"rpiCameraHFlip"` + RPICameraVFlip bool `json:"rpiCameraVFlip"` + RPICameraBrightness float64 `json:"rpiCameraBrightness"` + RPICameraContrast float64 `json:"rpiCameraContrast"` + RPICameraSaturation float64 `json:"rpiCameraSaturation"` + RPICameraSharpness float64 `json:"rpiCameraSharpness"` + RPICameraExposure string `json:"rpiCameraExposure"` + RPICameraAWB string `json:"rpiCameraAWB"` + RPICameraDenoise string `json:"rpiCameraDenoise"` + RPICameraShutter int `json:"rpiCameraShutter"` + RPICameraMetering string `json:"rpiCameraMetering"` + RPICameraGain float64 `json:"rpiCameraGain"` + RPICameraEV float64 `json:"rpiCameraEV"` + RPICameraROI string `json:"rpiCameraROI"` + RPICameraTuningFile string `json:"rpiCameraTuningFile"` RPICameraFPS int `json:"rpiCameraFPS"` RPICameraIDRPeriod int `json:"rpiCameraIDRPeriod"` RPICameraBitrate int `json:"rpiCameraBitrate"` @@ -183,6 +198,15 @@ func (pconf *PathConf) checkAndFillMissing(conf *Conf, name string) error { if pconf.RPICameraHeight == 0 { pconf.RPICameraHeight = 720 } + if pconf.RPICameraContrast == 0 { + pconf.RPICameraContrast = 1 + } + if pconf.RPICameraSaturation == 0 { + pconf.RPICameraSaturation = 1 + } + if pconf.RPICameraSharpness == 0 { + pconf.RPICameraSharpness = 1 + } if pconf.RPICameraFPS == 0 { pconf.RPICameraFPS = 30 } diff --git a/internal/core/source_static.go b/internal/core/source_static.go index a586ac39..e1879352 100644 --- a/internal/core/source_static.go +++ b/internal/core/source_static.go @@ -86,14 +86,29 @@ func newSourceStatic( case conf.Source == "rpiCamera": s.impl = newRPICameraSource( rpicamera.Params{ - CameraID: conf.RPICameraCamID, - Width: conf.RPICameraWidth, - Height: conf.RPICameraHeight, - FPS: conf.RPICameraFPS, - IDRPeriod: conf.RPICameraIDRPeriod, - Bitrate: conf.RPICameraBitrate, - Profile: conf.RPICameraProfile, - Level: conf.RPICameraLevel, + CameraID: conf.RPICameraCamID, + Width: conf.RPICameraWidth, + Height: conf.RPICameraHeight, + HFlip: conf.RPICameraHFlip, + VFlip: conf.RPICameraVFlip, + Brightness: conf.RPICameraBrightness, + Contrast: conf.RPICameraContrast, + Saturation: conf.RPICameraSaturation, + Sharpness: conf.RPICameraSharpness, + Exposure: conf.RPICameraExposure, + AWB: conf.RPICameraAWB, + Denoise: conf.RPICameraDenoise, + Shutter: conf.RPICameraShutter, + Metering: conf.RPICameraMetering, + Gain: conf.RPICameraGain, + EV: conf.RPICameraEV, + ROI: conf.RPICameraROI, + TuningFile: conf.RPICameraTuningFile, + FPS: conf.RPICameraFPS, + IDRPeriod: conf.RPICameraIDRPeriod, + Bitrate: conf.RPICameraBitrate, + Profile: conf.RPICameraProfile, + Level: conf.RPICameraLevel, }, s) } diff --git a/internal/rpicamera/exe/camera.cpp b/internal/rpicamera/exe/camera.cpp index abffe352..9f5e55bc 100644 --- a/internal/rpicamera/exe/camera.cpp +++ b/internal/rpicamera/exe/camera.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "parameters.h" @@ -18,15 +19,17 @@ using libcamera::CameraManager; using libcamera::CameraConfiguration; using libcamera::Camera; -using libcamera::StreamRoles; -using libcamera::StreamRole; -using libcamera::StreamConfiguration; -using libcamera::Stream; using libcamera::ControlList; using libcamera::FrameBufferAllocator; using libcamera::FrameBuffer; +using libcamera::Rectangle; using libcamera::Request; using libcamera::Span; +using libcamera::Stream; +using libcamera::StreamRoles; +using libcamera::StreamRole; +using libcamera::StreamConfiguration; +using libcamera::Transform; namespace controls = libcamera::controls; namespace formats = libcamera::formats; @@ -53,10 +56,10 @@ struct CameraPriv { }; static int get_v4l2_colorspace(std::optional const &cs) { - if (cs == libcamera::ColorSpace::Rec709) { - return V4L2_COLORSPACE_REC709; + if (cs == libcamera::ColorSpace::Rec709) { + return V4L2_COLORSPACE_REC709; } - return V4L2_COLORSPACE_SMPTE170M; + return V4L2_COLORSPACE_SMPTE170M; } bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **cam) { @@ -70,9 +73,9 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca } std::vector> cameras = camp->camera_manager->cameras(); - auto rem = std::remove_if(cameras.begin(), cameras.end(), + auto rem = std::remove_if(cameras.begin(), cameras.end(), [](auto &cam) { return cam->id().find("/usb") != std::string::npos; }); - cameras.erase(rem, cameras.end()); + cameras.erase(rem, cameras.end()); if (params->camera_id >= cameras.size()){ set_error("selected camera is not available"); return false; @@ -98,18 +101,28 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca } StreamConfiguration &stream_conf = conf->at(0); - stream_conf.pixelFormat = formats::YUV420; - stream_conf.bufferCount = params->buffer_count; + stream_conf.pixelFormat = formats::YUV420; + stream_conf.bufferCount = params->buffer_count; stream_conf.size.width = params->width; stream_conf.size.height = params->height; if (params->width >= 1280 || params->height >= 720) { - stream_conf.colorSpace = libcamera::ColorSpace::Rec709; + stream_conf.colorSpace = libcamera::ColorSpace::Rec709; } else { - stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m; + stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m; } - CameraConfiguration::Status vstatus = conf->validate(); - if (vstatus == CameraConfiguration::Invalid) { + conf->transform = Transform::Identity; + if (params->h_flip) { + conf->transform = Transform::HFlip * conf->transform; + } + if (params->v_flip) { + 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"); return false; } @@ -122,7 +135,7 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca Stream *stream = stream_conf.stream(); - camp->allocator = std::make_unique(camp->camera); + camp->allocator = std::make_unique(camp->camera); res = camp->allocator->allocate(stream); if (res < 0) { set_error("allocate() failed"); @@ -137,7 +150,7 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca } int res = request->addBuffer(stream, buffer.get()); - if (res != 0) { + if (res != 0) { set_error("addBuffer() failed"); return false; } @@ -154,7 +167,7 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca static void on_request_complete(Request *request) { if (request->status() == Request::RequestCancelled) { - return; + return; } CameraPriv *camp = (CameraPriv *)request->cookie(); @@ -186,6 +199,97 @@ bool camera_start(camera_t *cam) { CameraPriv *camp = (CameraPriv *)cam; ControlList ctrls = ControlList(controls::controls); + + ctrls.set(controls::Brightness, camp->params->brightness); + ctrls.set(controls::Contrast, camp->params->contrast); + ctrls.set(controls::Saturation, camp->params->saturation); + ctrls.set(controls::Sharpness, camp->params->sharpness); + + int exposure_mode; + if (strcmp(camp->params->exposure, "short") == 0) { + exposure_mode = controls::ExposureShort; + } else if (strcmp(camp->params->exposure, "long") == 0) { + exposure_mode = controls::ExposureLong; + } else if (strcmp(camp->params->exposure, "custom") == 0) { + exposure_mode = controls::ExposureCustom; + } else { + exposure_mode = controls::ExposureNormal; + } + ctrls.set(controls::AeExposureMode, exposure_mode); + + int awb_mode; + if (strcmp(camp->params->awb, "incandescent") == 0) { + awb_mode = controls::AwbIncandescent; + } else if (strcmp(camp->params->awb, "tungsten") == 0) { + awb_mode = controls::AwbTungsten; + } else if (strcmp(camp->params->awb, "fluorescent") == 0) { + awb_mode = controls::AwbFluorescent; + } else if (strcmp(camp->params->awb, "indoor") == 0) { + awb_mode = controls::AwbIndoor; + } else if (strcmp(camp->params->awb, "daylight") == 0) { + awb_mode = controls::AwbDaylight; + } else if (strcmp(camp->params->awb, "cloudy") == 0) { + awb_mode = controls::AwbCloudy; + } else if (strcmp(camp->params->awb, "custom") == 0) { + awb_mode = controls::AwbCustom; + } else { + awb_mode = controls::AwbAuto; + } + ctrls.set(controls::AwbMode, awb_mode); + + int denoise_mode; + if (strcmp(camp->params->denoise, "off") == 0) { + denoise_mode = controls::draft::NoiseReductionModeOff; + } else if (strcmp(camp->params->denoise, "cdn_off") == 0) { + denoise_mode = controls::draft::NoiseReductionModeMinimal; + } if (strcmp(camp->params->denoise, "cdn_hq") == 0) { + denoise_mode = controls::draft::NoiseReductionModeHighQuality; + } else { + denoise_mode = controls::draft::NoiseReductionModeFast; + } + ctrls.set(controls::draft::NoiseReductionMode, denoise_mode); + + if (camp->params->shutter != 0) { + ctrls.set(controls::ExposureTime, camp->params->shutter); + } + + int metering_mode; + if (strcmp(camp->params->metering, "spot") == 0) { + metering_mode = controls::MeteringSpot; + } else if (strcmp(camp->params->metering, "matrix") == 0) { + metering_mode = controls::MeteringMatrix; + } else if (strcmp(camp->params->metering, "custom") == 0) { + metering_mode = controls::MeteringCustom; + } else { + metering_mode = controls::MeteringCentreWeighted; + } + ctrls.set(controls::AeMeteringMode, metering_mode); + + if (camp->params->gain > 0) { + ctrls.set(controls::AnalogueGain, camp->params->gain); + } + + 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, ","); + } + + 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); + crop.translateBy(sensor_area.topLeft()); + ctrls.set(controls::ScalerCrop, crop); + } + int64_t frame_time = 1000000 / camp->params->fps; ctrls.set(controls::FrameDurationLimits, Span({ frame_time, frame_time })); @@ -197,13 +301,13 @@ bool camera_start(camera_t *cam) { camp->camera->requestCompleted.connect(on_request_complete); - for (std::unique_ptr &request : camp->requests) { + for (std::unique_ptr &request : camp->requests) { int res = camp->camera->queueRequest(request.get()); - if (res != 0) { - set_error("Camera.queueRequest() failed"); + if (res != 0) { + set_error("Camera.queueRequest() failed"); return false; } - } + } return true; } diff --git a/internal/rpicamera/exe/encoder.c b/internal/rpicamera/exe/encoder.c index 1c0a430a..52ffe87c 100644 --- a/internal/rpicamera/exe/encoder.c +++ b/internal/rpicamera/exe/encoder.c @@ -102,15 +102,15 @@ 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); - if (encp->fd < 0) { + encp->fd = open("/dev/video11", O_RDWR, 0); + if (encp->fd < 0) { set_error("unable to open device"); return false; } struct v4l2_control ctrl = {0}; ctrl.id = V4L2_CID_MPEG_VIDEO_BITRATE; - ctrl.value = params->bitrate; + ctrl.value = params->bitrate; int res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); if (res != 0) { set_error("unable to set bitrate"); @@ -119,7 +119,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou } ctrl.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE; - ctrl.value = params->profile; + ctrl.value = params->profile; res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); if (res != 0) { set_error("unable to set profile"); @@ -128,7 +128,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou } ctrl.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL; - ctrl.value = params->level; + ctrl.value = params->level; res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); if (res != 0) { set_error("unable to set level"); @@ -137,7 +137,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou } ctrl.id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD; - ctrl.value = params->idr_period; + ctrl.value = params->idr_period; res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); if (res != 0) { set_error("unable to set IDR period"); @@ -146,7 +146,7 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou } ctrl.id = V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER; - ctrl.value = 0; + ctrl.value = 0; res = ioctl(encp->fd, VIDIOC_S_CTRL, &ctrl); if (res != 0) { set_error("unable to set REPEAT_SEQ_HEADER"); @@ -154,15 +154,15 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou return false; } - struct v4l2_format fmt = {0}; - fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - fmt.fmt.pix_mp.width = params->width; - fmt.fmt.pix_mp.height = params->height; - fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420; - fmt.fmt.pix_mp.plane_fmt[0].bytesperline = stride; - fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; - fmt.fmt.pix_mp.colorspace = colorspace; - fmt.fmt.pix_mp.num_planes = 1; + struct v4l2_format fmt = {0}; + fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + fmt.fmt.pix_mp.width = params->width; + fmt.fmt.pix_mp.height = params->height; + fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420; + fmt.fmt.pix_mp.plane_fmt[0].bytesperline = stride; + fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; + fmt.fmt.pix_mp.colorspace = colorspace; + fmt.fmt.pix_mp.num_planes = 1; res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); if (res != 0) { set_error("unable to set output format"); @@ -170,16 +170,16 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou return false; } - memset(&fmt, 0, sizeof(fmt)); - fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - fmt.fmt.pix_mp.width = params->width; - fmt.fmt.pix_mp.height = params->height; - fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; - fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; - fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; - fmt.fmt.pix_mp.num_planes = 1; - fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; - fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + fmt.fmt.pix_mp.width = params->width; + fmt.fmt.pix_mp.height = params->height; + fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + fmt.fmt.pix_mp.field = V4L2_FIELD_ANY; + fmt.fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; + fmt.fmt.pix_mp.num_planes = 1; + fmt.fmt.pix_mp.plane_fmt[0].bytesperline = 0; + fmt.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10; res = ioctl(encp->fd, VIDIOC_S_FMT, &fmt); if (res != 0) { set_error("unable to set capture format"); @@ -187,10 +187,10 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou return false; } - struct v4l2_streamparm parm = {0}; - parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - parm.parm.output.timeperframe.numerator = 1; - parm.parm.output.timeperframe.denominator = params->fps; + struct v4l2_streamparm parm = {0}; + parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + parm.parm.output.timeperframe.numerator = 1; + parm.parm.output.timeperframe.denominator = params->fps; res = ioctl(encp->fd, VIDIOC_S_PARM, &parm); if (res != 0) { set_error("unable to set fps"); @@ -198,10 +198,10 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou return false; } - struct v4l2_requestbuffers reqbufs = {0}; - reqbufs.count = params->buffer_count; - reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - reqbufs.memory = V4L2_MEMORY_DMABUF; + struct v4l2_requestbuffers reqbufs = {0}; + reqbufs.count = params->buffer_count; + reqbufs.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + reqbufs.memory = V4L2_MEMORY_DMABUF; res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); if (res != 0) { set_error("unable to set output buffers"); @@ -209,10 +209,10 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou return false; } - memset(&reqbufs, 0, sizeof(reqbufs)); - reqbufs.count = params->capture_buffer_count; - reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - reqbufs.memory = V4L2_MEMORY_MMAP; + memset(&reqbufs, 0, sizeof(reqbufs)); + reqbufs.count = params->capture_buffer_count; + reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + reqbufs.memory = V4L2_MEMORY_MMAP; res = ioctl(encp->fd, VIDIOC_REQBUFS, &reqbufs); if (res != 0) { set_error("unable to set capture buffers"); @@ -222,30 +222,30 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou encp->capture_buffers = malloc(sizeof(void *) * reqbufs.count); - for (unsigned int i = 0; i < reqbufs.count; i++) { - struct v4l2_plane planes[VIDEO_MAX_PLANES]; + for (unsigned int i = 0; i < reqbufs.count; i++) { + struct v4l2_plane planes[VIDEO_MAX_PLANES]; - struct v4l2_buffer buffer = {0}; - buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; - buffer.memory = V4L2_MEMORY_MMAP; - buffer.index = i; - buffer.length = 1; - buffer.m.planes = planes; + struct v4l2_buffer buffer = {0}; + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = i; + buffer.length = 1; + buffer.m.planes = planes; int res = ioctl(encp->fd, VIDIOC_QUERYBUF, &buffer); - if (res != 0) { + if (res != 0) { set_error("unable to query buffer"); free(encp->capture_buffers); close(encp->fd); return false; } - encp->capture_buffers[i] = mmap( + encp->capture_buffers[i] = mmap( 0, buffer.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED, encp->fd, buffer.m.planes[0].m.mem_offset); - if (encp->capture_buffers[i] == MAP_FAILED) { + if (encp->capture_buffers[i] == MAP_FAILED) { set_error("mmap() failed"); free(encp->capture_buffers); close(encp->fd); @@ -253,15 +253,15 @@ bool encoder_create(parameters_t *params, int stride, int colorspace, encoder_ou } res = ioctl(encp->fd, VIDIOC_QBUF, &buffer); - if (res != 0) { + if (res != 0) { set_error("ioctl() failed"); free(encp->capture_buffers); close(encp->fd); return false; } - } + } - enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; res = ioctl(encp->fd, VIDIOC_STREAMON, &type); if (res != 0) { set_error("unable to activate output stream"); @@ -294,20 +294,20 @@ void encoder_encode(encoder_t *enc, int buffer_fd, size_t size, int64_t timestam int index = encp->cur_buffer++; encp->cur_buffer %= encp->params->buffer_count; - struct v4l2_buffer buf = {0}; - struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; - buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - buf.index = index; - buf.field = V4L2_FIELD_NONE; - buf.memory = V4L2_MEMORY_DMABUF; - buf.length = 1; - buf.timestamp.tv_sec = timestamp_us / 1000000; - buf.timestamp.tv_usec = timestamp_us % 1000000; - buf.m.planes = planes; - buf.m.planes[0].m.fd = buffer_fd; - buf.m.planes[0].bytesused = size; - buf.m.planes[0].length = size; - int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); + struct v4l2_buffer buf = {0}; + struct v4l2_plane planes[VIDEO_MAX_PLANES] = {0}; + buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + buf.index = index; + buf.field = V4L2_FIELD_NONE; + buf.memory = V4L2_MEMORY_DMABUF; + buf.length = 1; + buf.timestamp.tv_sec = timestamp_us / 1000000; + buf.timestamp.tv_usec = timestamp_us % 1000000; + buf.m.planes = planes; + buf.m.planes[0].m.fd = buffer_fd; + buf.m.planes[0].bytesused = size; + buf.m.planes[0].length = size; + int res = ioctl(encp->fd, VIDIOC_QBUF, &buf); if (res != 0) { fprintf(stderr, "encoder_encode(): ioctl() failed\n"); exit(1); diff --git a/internal/rpicamera/exe/parameters.c b/internal/rpicamera/exe/parameters.c index e83653e0..a0d8175c 100644 --- a/internal/rpicamera/exe/parameters.c +++ b/internal/rpicamera/exe/parameters.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -9,6 +10,21 @@ void parameters_load(parameters_t *params) { params->camera_id = atoi(getenv("CAMERA_ID")); params->width = atoi(getenv("WIDTH")); params->height = atoi(getenv("HEIGHT")); + params->h_flip = (strcmp(getenv("H_FLIP"), "1") == 0); + params->v_flip = (strcmp(getenv("V_FLIP"), "1") == 0); + params->brightness = atof(getenv("BRIGHTNESS")); + params->contrast = atof(getenv("CONTRAST")); + params->saturation = atof(getenv("SATURATION")); + params->sharpness = atof(getenv("SHARPNESS")); + params->exposure = getenv("EXPOSURE"); + params->awb = getenv("AWB"); + params->denoise = getenv("DENOISE"); + params->shutter = atoi(getenv("SHUTTER")); + params->metering = getenv("METERING"); + params->gain = atof(getenv("GAIN")); + params->ev = atof(getenv("EV")); + params->roi = getenv("ROI"); + params->tuning_file = getenv("TUNING_FILE"); params->fps = atoi(getenv("FPS")); params->idr_period = atoi(getenv("IDR_PERIOD")); params->bitrate = atoi(getenv("BITRATE")); diff --git a/internal/rpicamera/exe/parameters.h b/internal/rpicamera/exe/parameters.h index 2bdb9972..1d94c059 100644 --- a/internal/rpicamera/exe/parameters.h +++ b/internal/rpicamera/exe/parameters.h @@ -2,11 +2,28 @@ typedef struct { unsigned int camera_id; unsigned int width; unsigned int height; + bool h_flip; + bool v_flip; + float brightness; + float contrast; + float saturation; + float sharpness; + const char *exposure; + const char *awb; + const char *denoise; + unsigned int shutter; + const char *metering; + float gain; + float ev; + const char *roi; + const char *tuning_file; unsigned int fps; unsigned int idr_period; unsigned int bitrate; unsigned int profile; unsigned int level; + + // private unsigned int buffer_count; unsigned int capture_buffer_count; } parameters_t; diff --git a/internal/rpicamera/params.go b/internal/rpicamera/params.go index aecc711f..b5ccf53d 100644 --- a/internal/rpicamera/params.go +++ b/internal/rpicamera/params.go @@ -2,12 +2,27 @@ package rpicamera // Params is a set of camera parameters. type Params struct { - CameraID int - Width int - Height int - FPS int - IDRPeriod int - Bitrate int - Profile string - Level string + CameraID int + Width int + Height int + HFlip bool + VFlip bool + Brightness float64 + Contrast float64 + Saturation float64 + Sharpness float64 + Exposure string + AWB string + Denoise string + Shutter int + Metering string + Gain float64 + EV float64 + ROI string + TuningFile string + FPS int + IDRPeriod int + Bitrate int + Profile string + Level string } diff --git a/internal/rpicamera/rpicamera.go b/internal/rpicamera/rpicamera.go index d52eec93..f8afe876 100644 --- a/internal/rpicamera/rpicamera.go +++ b/internal/rpicamera/rpicamera.go @@ -14,6 +14,13 @@ import ( //go:embed exe/exe var exeContent []byte +func bool2env(v bool) string { + if v { + return "1" + } + return "0" +} + type RPICamera struct { onData func([][]byte) @@ -38,6 +45,21 @@ func New( "CAMERA_ID=" + strconv.FormatInt(int64(params.CameraID), 10), "WIDTH=" + strconv.FormatInt(int64(params.Width), 10), "HEIGHT=" + strconv.FormatInt(int64(params.Height), 10), + "H_FLIP=" + bool2env(params.HFlip), + "V_FLIP=" + bool2env(params.VFlip), + "BRIGHTNESS=" + strconv.FormatFloat(params.Brightness, 'f', -1, 64), + "CONTRAST=" + strconv.FormatFloat(params.Contrast, 'f', -1, 64), + "SATURATION=" + strconv.FormatFloat(params.Saturation, 'f', -1, 64), + "SHARPNESS=" + strconv.FormatFloat(params.Sharpness, 'f', -1, 64), + "EXPOSURE=" + params.Exposure, + "AWB=" + params.AWB, + "DENOISE=" + params.Denoise, + "SHUTTER=" + strconv.FormatInt(int64(params.Shutter), 10), + "METERING=" + params.Metering, + "GAIN=" + strconv.FormatFloat(params.Gain, 'f', -1, 64), + "EV=" + strconv.FormatFloat(params.EV, 'f', -1, 64), + "ROI=" + params.ROI, + "TUNING_FILE=" + params.TuningFile, "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 46e4fe37..a6d280ea 100644 --- a/rtsp-simple-server.yml +++ b/rtsp-simple-server.yml @@ -227,14 +227,56 @@ paths: # path. It can be can be a relative path (i.e. /otherstream) or an absolute RTSP URL. fallback: - # If the source is "rpiCamera", these are the Raspberry Pi Camera parameters + # If the source is "rpiCamera", these are the Raspberry Pi Camera parameters. + # ID of the camera rpiCameraCamID: 0 + # width of frames rpiCameraWidth: 1280 + # height of frames rpiCameraHeight: 720 + # flip horizontally + rpiCameraHFlip: false + # flip vertically + rpiCameraVFlip: false + # brightness [-1, 1] + rpiCameraBrightness: 0 + # contrast [0, 16] + rpiCameraContrast: 1 + # saturation [0, 16] + rpiCameraSaturation: 1 + # sharpness [0, 16] + rpiCameraSharpness: 1 + # exposure mode. + # values: normal, short, long, custom + rpiCameraExposure: normal + # auto-white-balance mode. + # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom + rpiCameraAWB: auto + # denoise operating mode. + # values: auto, off, cdn_off, cdn_fast, cdn_hq + rpiCameraDenoise: auto + # fixed shutter speed, in microseconds. + rpiCameraShutter: 0 + # metering mode of the AEC/AGC algorithm. + # values: centre, spot, matrix, custom + rpiCameraMetering: centre + # fixed gain + rpiCameraGain: 0 + # EV compensation of the image [-10, 10] + rpiCameraEV: 0 + # Region of interst in format x,y,width,height + rpiCameraROI: + # tuning file + rpiCameraTuningFile: + # frames per second rpiCameraFPS: 30 + # period between IDR frames rpiCameraIDRPeriod: 60 + # bitrate rpiCameraBitrate: 1000000 + # H264 profile rpiCameraProfile: main + # H264 level rpiCameraLevel: '4.1' # Username required to publish. diff --git a/scripts/binaries.mk b/scripts/binaries.mk index 3d735004..13aea7dc 100644 --- a/scripts/binaries.mk +++ b/scripts/binaries.mk @@ -4,14 +4,14 @@ RUN ["cross-build-start"] RUN apt update && apt install -y g++ pkg-config make libcamera-dev WORKDIR /s/internal/rpicamera COPY internal/rpicamera . -RUN cd exe && make +RUN cd exe && make -j$$(nproc) FROM $(RPI64_IMAGE) AS rpicamera64 RUN ["cross-build-start"] RUN apt update && apt install -y g++ pkg-config make libcamera-dev WORKDIR /s/internal/rpicamera COPY internal/rpicamera . -RUN cd exe && make +RUN cd exe && make -j$$(nproc) FROM $(BASE_IMAGE) RUN apk add --no-cache zip make git tar