From 0f733bab262f6fc5e034d15498dd1efa55665af4 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Sun, 28 Jan 2024 21:48:21 +0100 Subject: [PATCH] add rpiCameraAWBGains (#2858) (#2954) --- apidocs/openapi.yaml | 6 ++ internal/conf/conf_test.go | 1 + internal/conf/env/env.go | 25 +++++++ internal/conf/env/env_test.go | 3 + internal/conf/path.go | 69 ++++++++++--------- internal/core/path_manager.go | 1 + internal/protocols/rpicamera/exe/camera.cpp | 5 ++ internal/protocols/rpicamera/exe/parameters.c | 4 ++ internal/protocols/rpicamera/exe/parameters.h | 2 + internal/protocols/rpicamera/params.go | 2 + internal/staticsources/rpicamera/source.go | 2 + mediamtx.yml | 3 + 12 files changed, 91 insertions(+), 32 deletions(-) diff --git a/apidocs/openapi.yaml b/apidocs/openapi.yaml index ad82a07b..e44e0400 100644 --- a/apidocs/openapi.yaml +++ b/apidocs/openapi.yaml @@ -298,6 +298,12 @@ components: type: string rpiCameraAWB: type: string + rpiCameraAWBGains: + type: array + minItems: 2 + maxItems: 2 + items: + type: number rpiCameraDenoise: type: string rpiCameraShutter: diff --git a/internal/conf/conf_test.go b/internal/conf/conf_test.go index 6575c545..3f5a25d0 100644 --- a/internal/conf/conf_test.go +++ b/internal/conf/conf_test.go @@ -66,6 +66,7 @@ func TestConfFromFile(t *testing.T) { RPICameraSharpness: 1, RPICameraExposure: "normal", RPICameraAWB: "auto", + RPICameraAWBGains: []float64{0, 0}, RPICameraDenoise: "off", RPICameraMetering: "centre", RPICameraFPS: 30, diff --git a/internal/conf/env/env.go b/internal/conf/env/env.go index 7c60ca78..bb735224 100644 --- a/internal/conf/env/env.go +++ b/internal/conf/env/env.go @@ -188,6 +188,31 @@ func loadEnvInternal(env map[string]string, prefix string, prv reflect.Value) er } return nil + case rt.Elem() == reflect.TypeOf(float64(0)): + if ev, ok := env[prefix]; ok { + if ev == "" { + prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) + } else { + if prv.IsNil() { + prv.Set(reflect.New(rt)) + } + + raw := strings.Split(ev, ",") + vals := make([]float64, len(raw)) + + for i, v := range raw { + tmp, err := strconv.ParseFloat(v, 64) + if err != nil { + return err + } + vals[i] = tmp + } + + prv.Elem().Set(reflect.ValueOf(vals)) + } + } + return nil + case rt.Elem().Kind() == reflect.Struct: if ev, ok := env[prefix]; ok && ev == "" { // special case: empty list prv.Elem().Set(reflect.MakeSlice(prv.Elem().Type(), 0, 0)) diff --git a/internal/conf/env/env_test.go b/internal/conf/env/env_test.go index 6fc057de..3de2759a 100644 --- a/internal/conf/env/env_test.go +++ b/internal/conf/env/env_test.go @@ -85,6 +85,7 @@ type testStruct struct { MyDurationOpt *myDuration `json:"myDurationOpt"` MyDurationOptUnset *myDuration `json:"myDurationOptUnset"` MyMap map[string]*mapEntry `json:"myMap"` + MySliceFloat []float64 `json:"mySliceFloat"` MySliceString []string `json:"mySliceString"` MySliceStringEmpty []string `json:"mySliceStringEmpty"` MySliceStringOpt *[]string `json:"mySliceStringOpt"` @@ -113,6 +114,7 @@ func TestLoad(t *testing.T) { "MYPREFIX_MYMAP_MYKEY": "", "MYPREFIX_MYMAP_MYKEY2_MYVALUE": "asd", "MYPREFIX_MYMAP_MYKEY2_MYSTRUCT_MYPARAM": "456", + "MYPREFIX_MYSLICEFLOAT": "0.5,0.5", "MYPREFIX_MYSLICESTRING": "val1,val2", "MYPREFIX_MYSLICESTRINGEMPTY": "", "MYPREFIX_MYSLICESTRINGOPT": "aa", @@ -160,6 +162,7 @@ func TestLoad(t *testing.T) { }, }, }, + MySliceFloat: []float64{0.5, 0.5}, MySliceString: []string{ "val1", "val2", diff --git a/internal/conf/path.go b/internal/conf/path.go index a6b840a0..9e641f3d 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -130,38 +130,39 @@ type Path struct { SourceRedirect string `json:"sourceRedirect"` // Raspberry Pi Camera source - 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"` - RPICameraHDR bool `json:"rpiCameraHDR"` - RPICameraTuningFile string `json:"rpiCameraTuningFile"` - RPICameraMode string `json:"rpiCameraMode"` - RPICameraFPS float64 `json:"rpiCameraFPS"` - RPICameraIDRPeriod int `json:"rpiCameraIDRPeriod"` - RPICameraBitrate int `json:"rpiCameraBitrate"` - RPICameraProfile string `json:"rpiCameraProfile"` - RPICameraLevel string `json:"rpiCameraLevel"` - RPICameraAfMode string `json:"rpiCameraAfMode"` - RPICameraAfRange string `json:"rpiCameraAfRange"` - RPICameraAfSpeed string `json:"rpiCameraAfSpeed"` - RPICameraLensPosition float64 `json:"rpiCameraLensPosition"` - RPICameraAfWindow string `json:"rpiCameraAfWindow"` - RPICameraTextOverlayEnable bool `json:"rpiCameraTextOverlayEnable"` - RPICameraTextOverlay string `json:"rpiCameraTextOverlay"` + 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"` + RPICameraAWBGains []float64 `json:"rpiCameraAWBGains"` + RPICameraDenoise string `json:"rpiCameraDenoise"` + RPICameraShutter int `json:"rpiCameraShutter"` + RPICameraMetering string `json:"rpiCameraMetering"` + RPICameraGain float64 `json:"rpiCameraGain"` + RPICameraEV float64 `json:"rpiCameraEV"` + RPICameraROI string `json:"rpiCameraROI"` + RPICameraHDR bool `json:"rpiCameraHDR"` + RPICameraTuningFile string `json:"rpiCameraTuningFile"` + RPICameraMode string `json:"rpiCameraMode"` + RPICameraFPS float64 `json:"rpiCameraFPS"` + RPICameraIDRPeriod int `json:"rpiCameraIDRPeriod"` + RPICameraBitrate int `json:"rpiCameraBitrate"` + RPICameraProfile string `json:"rpiCameraProfile"` + RPICameraLevel string `json:"rpiCameraLevel"` + RPICameraAfMode string `json:"rpiCameraAfMode"` + RPICameraAfRange string `json:"rpiCameraAfRange"` + RPICameraAfSpeed string `json:"rpiCameraAfSpeed"` + RPICameraLensPosition float64 `json:"rpiCameraLensPosition"` + RPICameraAfWindow string `json:"rpiCameraAfWindow"` + RPICameraTextOverlayEnable bool `json:"rpiCameraTextOverlayEnable"` + RPICameraTextOverlay string `json:"rpiCameraTextOverlay"` // Hooks RunOnInit string `json:"runOnInit"` @@ -206,6 +207,7 @@ func (pconf *Path) setDefaults() { pconf.RPICameraSharpness = 1 pconf.RPICameraExposure = "normal" pconf.RPICameraAWB = "auto" + pconf.RPICameraAWBGains = []float64{0, 0} pconf.RPICameraDenoise = "off" pconf.RPICameraMetering = "centre" pconf.RPICameraFPS = 30 @@ -467,6 +469,9 @@ func (pconf *Path) validate(conf *Conf, name string) error { default: return fmt.Errorf("invalid 'rpiCameraAWB' value") } + if len(pconf.RPICameraAWBGains) != 2 { + return fmt.Errorf("invalid 'rpiCameraAWBGains' value") + } switch pconf.RPICameraDenoise { case "off", "cdn_off", "cdn_fast", "cdn_hq": default: diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go index c4a0ff48..ae38c47c 100644 --- a/internal/core/path_manager.go +++ b/internal/core/path_manager.go @@ -23,6 +23,7 @@ func pathConfCanBeUpdated(oldPathConf *conf.Path, newPathConf *conf.Path) bool { clone.RPICameraSharpness = newPathConf.RPICameraSharpness clone.RPICameraExposure = newPathConf.RPICameraExposure clone.RPICameraAWB = newPathConf.RPICameraAWB + clone.RPICameraAWBGains = newPathConf.RPICameraAWBGains clone.RPICameraDenoise = newPathConf.RPICameraDenoise clone.RPICameraShutter = newPathConf.RPICameraShutter clone.RPICameraMetering = newPathConf.RPICameraMetering diff --git a/internal/protocols/rpicamera/exe/camera.cpp b/internal/protocols/rpicamera/exe/camera.cpp index 3ef87960..73701a61 100644 --- a/internal/protocols/rpicamera/exe/camera.cpp +++ b/internal/protocols/rpicamera/exe/camera.cpp @@ -346,6 +346,11 @@ static void fill_dynamic_controls(ControlList *ctrls, const parameters_t *params } ctrls->set(controls::AwbMode, awb_mode); + if (params->awb_gain_red > 0 && params->awb_gain_blue > 0) { + ctrls->set(controls::ColourGains, + Span({params->awb_gain_red, params->awb_gain_blue})); + } + int denoise_mode; if (strcmp(params->denoise, "cdn_off") == 0) { denoise_mode = controls::draft::NoiseReductionModeMinimal; diff --git a/internal/protocols/rpicamera/exe/parameters.c b/internal/protocols/rpicamera/exe/parameters.c index 5a8905ef..9805d2f3 100644 --- a/internal/protocols/rpicamera/exe/parameters.c +++ b/internal/protocols/rpicamera/exe/parameters.c @@ -61,6 +61,10 @@ bool parameters_unserialize(parameters_t *params, const uint8_t *buf, size_t buf params->exposure = base64_decode(val); } else if (strcmp(key, "AWB") == 0) { params->awb = base64_decode(val); + } else if (strcmp(key, "AWBGainRed") == 0) { + params->awb_gain_red = atof(val); + } else if (strcmp(key, "AWBGainBlue") == 0) { + params->awb_gain_blue = atof(val); } else if (strcmp(key, "Denoise") == 0) { params->denoise = base64_decode(val); } else if (strcmp(key, "Shutter") == 0) { diff --git a/internal/protocols/rpicamera/exe/parameters.h b/internal/protocols/rpicamera/exe/parameters.h index abfcfa20..36ced18b 100644 --- a/internal/protocols/rpicamera/exe/parameters.h +++ b/internal/protocols/rpicamera/exe/parameters.h @@ -20,6 +20,8 @@ typedef struct { float sharpness; char *exposure; char *awb; + float awb_gain_red; + float awb_gain_blue; char *denoise; unsigned int shutter; char *metering; diff --git a/internal/protocols/rpicamera/params.go b/internal/protocols/rpicamera/params.go index bf30d0a5..5262d36f 100644 --- a/internal/protocols/rpicamera/params.go +++ b/internal/protocols/rpicamera/params.go @@ -21,6 +21,8 @@ type Params struct { Sharpness float64 Exposure string AWB string + AWBGainRed float64 + AWBGainBlue float64 Denoise string Shutter int Metering string diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go index 9e5c8313..924a9ab3 100644 --- a/internal/staticsources/rpicamera/source.go +++ b/internal/staticsources/rpicamera/source.go @@ -39,6 +39,8 @@ func paramsFromConf(logLevel conf.LogLevel, cnf *conf.Path) rpicamera.Params { Sharpness: cnf.RPICameraSharpness, Exposure: cnf.RPICameraExposure, AWB: cnf.RPICameraAWB, + AWBGainRed: cnf.RPICameraAWBGains[0], + AWBGainBlue: cnf.RPICameraAWBGains[1], Denoise: cnf.RPICameraDenoise, Shutter: cnf.RPICameraShutter, Metering: cnf.RPICameraMetering, diff --git a/mediamtx.yml b/mediamtx.yml index d0d63d18..670a65c0 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -409,6 +409,9 @@ pathDefaults: # auto-white-balance mode. # values: auto, incandescent, tungsten, fluorescent, indoor, daylight, cloudy, custom rpiCameraAWB: auto + # auto-white-balance fixed gains. This can be used in place of rpiCameraAWB. + # format: [red,blue] + rpiCameraAWBGains: [0, 0] # denoise operating mode. # values: off, cdn_off, cdn_fast, cdn_hq rpiCameraDenoise: "off"