Browse Source

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
pull/1219/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
8825fddd89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 10
      internal/conf/env.go
  3. 18
      internal/conf/env_test.go
  4. 24
      internal/conf/path.go
  5. 15
      internal/core/source_static.go
  6. 112
      internal/rpicamera/exe/camera.cpp
  7. 16
      internal/rpicamera/exe/parameters.c
  8. 17
      internal/rpicamera/exe/parameters.h
  9. 15
      internal/rpicamera/params.go
  10. 22
      internal/rpicamera/rpicamera.go
  11. 44
      rtsp-simple-server.yml
  12. 4
      scripts/binaries.mk

2
README.md

@ -557,7 +557,7 @@ paths: @@ -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

10
internal/conf/env.go

@ -52,6 +52,16 @@ func loadEnvInternal(env map[string]string, prefix string, rv reflect.Value) err @@ -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) {

18
internal/conf/env_test.go

@ -9,32 +9,20 @@ import ( @@ -9,32 +9,20 @@ import (
)
type subStruct struct {
// int
MyParam int
}
type mapEntry struct {
// string
MyValue string
// struct
MyStruct subStruct
}
type testStruct struct {
// string
MyString string
// int
MyInt int
// bool
MyFloat float64
MyBool bool
// duration
MyDuration StringDuration
// map
MyMap map[string]*mapEntry
}
@ -45,6 +33,9 @@ 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) { @@ -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)

24
internal/conf/path.go

@ -52,6 +52,21 @@ type PathConf struct { @@ -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 { @@ -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
}

15
internal/core/source_static.go

@ -89,6 +89,21 @@ func newSourceStatic( @@ -89,6 +89,21 @@ func newSourceStatic(
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,

112
internal/rpicamera/exe/camera.cpp

@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/framebuffer_allocator.h>
#include <libcamera/property_ids.h>
#include <linux/videodev2.h>
#include "parameters.h"
@ -18,15 +19,17 @@ @@ -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;
@ -108,6 +111,16 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca @@ -108,6 +111,16 @@ bool camera_create(parameters_t *params, camera_frame_cb frame_cb, camera_t **ca
stream_conf.colorSpace = libcamera::ColorSpace::Smpte170m;
}
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");
@ -186,6 +199,97 @@ bool camera_start(camera_t *cam) { @@ -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<const int64_t, 2>({ frame_time, frame_time }));

16
internal/rpicamera/exe/parameters.c

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <linux/videodev2.h>
@ -9,6 +10,21 @@ void parameters_load(parameters_t *params) { @@ -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"));

17
internal/rpicamera/exe/parameters.h

@ -2,11 +2,28 @@ typedef struct { @@ -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;

15
internal/rpicamera/params.go

@ -5,6 +5,21 @@ type Params struct { @@ -5,6 +5,21 @@ type Params struct {
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

22
internal/rpicamera/rpicamera.go

@ -14,6 +14,13 @@ import ( @@ -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( @@ -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),

44
rtsp-simple-server.yml

@ -227,14 +227,56 @@ paths: @@ -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.

4
scripts/binaries.mk

@ -4,14 +4,14 @@ RUN ["cross-build-start"] @@ -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

Loading…
Cancel
Save