From 07d0e232b2de7b07d0d3c0605e82944abcf346f0 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Thu, 25 Jun 2026 22:07:36 +0200 Subject: [PATCH] rpi: add unified rpiCameraH264Profile, rpiCameraH264Level params (#5894) These replace rpiCameraHardwareH264Profile, rpiCameraHardwareH264Level, rpiCameraSoftwareH264Profile, rpiCameraSoftwareH264Level. --- api/openapi.yaml | 12 ++ internal/conf/conf_test.go | 80 ++++++----- internal/conf/path.go | 86 ++++++++---- .../rpicamera/camera_params_arm_.go | 124 +++++++++++------- .../HASH_MTXRPICAM_32_TAR_GZ | 2 +- .../HASH_MTXRPICAM_64_TAR_GZ | 2 +- .../rpicamera/mtxrpicamdownloader/VERSION | 2 +- .../rpicamera/supports_hardware_h264_arm_.go | 44 +++++++ mediamtx.yml | 23 ++-- 9 files changed, 249 insertions(+), 126 deletions(-) create mode 100644 internal/staticsources/rpicamera/supports_hardware_h264_arm_.go diff --git a/api/openapi.yaml b/api/openapi.yaml index f043c15a..dbd68c21 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -926,12 +926,24 @@ components: deprecated: true rpiCameraHardwareH264Profile: type: string + nullable: true + deprecated: true rpiCameraHardwareH264Level: type: string + nullable: true + deprecated: true rpiCameraSoftwareH264Profile: type: string + nullable: true + deprecated: true rpiCameraSoftwareH264Level: type: string + nullable: true + deprecated: true + rpiCameraH264Profile: + type: string + rpiCameraH264Level: + type: string rpiCameraJPEGQuality: type: integer format: uint64 diff --git a/internal/conf/conf_test.go b/internal/conf/conf_test.go index 09f6e57d..53881cc3 100644 --- a/internal/conf/conf_test.go +++ b/internal/conf/conf_test.go @@ -93,47 +93,45 @@ func TestConfFromFile(t *testing.T) { pa, ok := conf.Paths["cam1"] require.Equal(t, true, ok) require.Equal(t, &Path{ - Name: "cam1", - Source: "publisher", - SourceOnDemandStartTimeout: 10 * Duration(time.Second), - SourceOnDemandCloseAfter: 10 * Duration(time.Second), - OverridePublisher: true, - AlwaysAvailableTracks: []AlwaysAvailableTrack{}, - RecordPath: "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f", - RecordFormat: RecordFormatFMP4, - RecordPartDuration: Duration(1 * time.Second), - RecordMaxPartSize: 50 * 1024 * 1024, - RecordSegmentDuration: 3600000000000, - RecordDeleteAfter: 86400000000000, - RTSPUDPSourcePortRange: []uint{10000, 65535}, - WHEPSTUNGatherTimeout: 5 * Duration(time.Second), - WHEPHandshakeTimeout: 10 * Duration(time.Second), - WHEPTrackGatherTimeout: 2 * Duration(time.Second), - RPICameraWidth: 1920, - RPICameraHeight: 1080, - RPICameraContrast: 1, - RPICameraSaturation: 1, - RPICameraSharpness: 1, - RPICameraExposure: "normal", - RPICameraAWB: "auto", - RPICameraAWBGains: []float64{0, 0}, - RPICameraDenoise: "off", - RPICameraMetering: "centre", - RPICameraFPS: 30, - RPICameraAfMode: "continuous", - RPICameraAfRange: "normal", - RPICameraAfSpeed: "normal", - RPICameraTextOverlay: "%Y-%m-%d %H:%M:%S - MediaMTX", - RPICameraCodec: "auto", - RPICameraIDRPeriod: 60, - RPICameraBitrate: 5000000, - RPICameraHardwareH264Profile: "main", - RPICameraHardwareH264Level: "4.1", - RPICameraSoftwareH264Profile: "baseline", - RPICameraSoftwareH264Level: "4.1", - RPICameraMJPEGQuality: 60, - RunOnDemandStartTimeout: 5 * Duration(time.Second), - RunOnDemandCloseAfter: 10 * Duration(time.Second), + Name: "cam1", + Source: "publisher", + SourceOnDemandStartTimeout: 10 * Duration(time.Second), + SourceOnDemandCloseAfter: 10 * Duration(time.Second), + OverridePublisher: true, + AlwaysAvailableTracks: []AlwaysAvailableTrack{}, + RecordPath: "./recordings/%path/%Y-%m-%d_%H-%M-%S-%f", + RecordFormat: RecordFormatFMP4, + RecordPartDuration: Duration(1 * time.Second), + RecordMaxPartSize: 50 * 1024 * 1024, + RecordSegmentDuration: 3600000000000, + RecordDeleteAfter: 86400000000000, + RTSPUDPSourcePortRange: []uint{10000, 65535}, + WHEPSTUNGatherTimeout: 5 * Duration(time.Second), + WHEPHandshakeTimeout: 10 * Duration(time.Second), + WHEPTrackGatherTimeout: 2 * Duration(time.Second), + RPICameraWidth: 1920, + RPICameraHeight: 1080, + RPICameraContrast: 1, + RPICameraSaturation: 1, + RPICameraSharpness: 1, + RPICameraExposure: "normal", + RPICameraAWB: "auto", + RPICameraAWBGains: []float64{0, 0}, + RPICameraDenoise: "off", + RPICameraMetering: "centre", + RPICameraFPS: 30, + RPICameraAfMode: "continuous", + RPICameraAfRange: "normal", + RPICameraAfSpeed: "normal", + RPICameraTextOverlay: "%Y-%m-%d %H:%M:%S - MediaMTX", + RPICameraCodec: "auto", + RPICameraIDRPeriod: 60, + RPICameraBitrate: 5000000, + RPICameraH264Profile: "auto", + RPICameraH264Level: "4.1", + RPICameraMJPEGQuality: 60, + RunOnDemandStartTimeout: 5 * Duration(time.Second), + RunOnDemandCloseAfter: 10 * Duration(time.Second), }, pa) }() diff --git a/internal/conf/path.go b/internal/conf/path.go index 9cccd1ef..7ace95fd 100644 --- a/internal/conf/path.go +++ b/internal/conf/path.go @@ -314,10 +314,12 @@ type Path struct { RPICameraBitrate uint `json:"rpiCameraBitrate"` RPICameraProfile *string `json:"rpiCameraProfile,omitempty" deprecated:"true"` RPICameraLevel *string `json:"rpiCameraLevel,omitempty" deprecated:"true"` - RPICameraHardwareH264Profile string `json:"rpiCameraHardwareH264Profile"` - RPICameraHardwareH264Level string `json:"rpiCameraHardwareH264Level"` - RPICameraSoftwareH264Profile string `json:"rpiCameraSoftwareH264Profile"` - RPICameraSoftwareH264Level string `json:"rpiCameraSoftwareH264Level"` + RPICameraHardwareH264Profile *string `json:"rpiCameraHardwareH264Profile,omitempty" deprecated:"true"` + RPICameraHardwareH264Level *string `json:"rpiCameraHardwareH264Level,omitempty" deprecated:"true"` + RPICameraSoftwareH264Profile *string `json:"rpiCameraSoftwareH264Profile,omitempty" deprecated:"true"` + RPICameraSoftwareH264Level *string `json:"rpiCameraSoftwareH264Level,omitempty" deprecated:"true"` + RPICameraH264Profile string `json:"rpiCameraH264Profile"` + RPICameraH264Level string `json:"rpiCameraH264Level"` RPICameraJPEGQuality *uint `json:"rpiCameraJPEGQuality,omitempty" deprecated:"true"` RPICameraMJPEGQuality uint `json:"rpiCameraMJPEGQuality"` RPICameraPrimaryName string `json:"-"` // filled by Validate() @@ -388,10 +390,8 @@ func (pconf *Path) setDefaults() { pconf.RPICameraCodec = "auto" pconf.RPICameraIDRPeriod = 60 pconf.RPICameraBitrate = 5000000 - pconf.RPICameraHardwareH264Profile = "main" - pconf.RPICameraHardwareH264Level = "4.1" - pconf.RPICameraSoftwareH264Profile = "baseline" - pconf.RPICameraSoftwareH264Level = "4.1" + pconf.RPICameraH264Profile = "auto" + pconf.RPICameraH264Level = "4.1" pconf.RPICameraMJPEGQuality = 60 // Hooks @@ -628,37 +628,69 @@ func (pconf *Path) validate( if pconf.RPICameraProfile != nil { l.Log(logger.Warn, "parameter 'rpiCameraProfile' is deprecated"+ " and has been replaced with 'rpiCameraHardwareH264Profile'") - pconf.RPICameraHardwareH264Profile = *pconf.RPICameraProfile + pconf.RPICameraHardwareH264Profile = pconf.RPICameraProfile } if pconf.RPICameraLevel != nil { l.Log(logger.Warn, "parameter 'rpiCameraLevel' is deprecated"+ " and has been replaced with 'rpiCameraHardwareH264Level'") - pconf.RPICameraHardwareH264Level = *pconf.RPICameraLevel + pconf.RPICameraHardwareH264Level = pconf.RPICameraLevel } - switch pconf.RPICameraHardwareH264Profile { - case "baseline", "main", "high": + if pconf.RPICameraHardwareH264Profile != nil { + l.Log(logger.Warn, "parameter 'rpiCameraHardwareH264Profile' is deprecated"+ + " and has been replaced with 'rpiCameraH264Profile'") + + switch *pconf.RPICameraHardwareH264Profile { + case "baseline", "main", "high": + default: + return fmt.Errorf("invalid 'rpiCameraHardwareH264Profile' value") + } + } + + if pconf.RPICameraHardwareH264Level != nil { + l.Log(logger.Warn, "parameter 'rpiCameraHardwareH264Level' is deprecated"+ + " and has been replaced with 'rpiCameraH264Level'") + + switch *pconf.RPICameraHardwareH264Level { + case "4.0", "4.1", "4.2": + default: + return fmt.Errorf("invalid 'rpiCameraHardwareH264Level' value") + } + } + + if pconf.RPICameraSoftwareH264Profile != nil { + l.Log(logger.Warn, "parameter 'rpiCameraSoftwareH264Profile' is deprecated"+ + " and has been replaced with 'rpiCameraH264Profile'") + + switch *pconf.RPICameraSoftwareH264Profile { + case "baseline", "main", "high": + default: + return fmt.Errorf("invalid 'rpiCameraSoftwareH264Profile' value") + } + } + + if pconf.RPICameraSoftwareH264Level != nil { + l.Log(logger.Warn, "parameter 'rpiCameraSoftwareH264Level' is deprecated"+ + " and has been replaced with 'rpiCameraH264Level'") + + switch *pconf.RPICameraSoftwareH264Level { + case "4.0", "4.1", "4.2": + default: + return fmt.Errorf("invalid 'rpiCameraSoftwareH264Level' value") + } + } + + switch pconf.RPICameraH264Profile { + case "auto", "baseline", "main", "high": default: - return fmt.Errorf("invalid 'rpiCameraHardwareH264Profile' value") + return fmt.Errorf("invalid 'rpiCameraH264Profile' value") } - switch pconf.RPICameraHardwareH264Level { + switch pconf.RPICameraH264Level { case "4.0", "4.1", "4.2": default: - return fmt.Errorf("invalid 'rpiCameraHardwareH264Level' value") - } - - switch pconf.RPICameraSoftwareH264Profile { - case "baseline", "main", "high": - default: - return fmt.Errorf("invalid 'rpiCameraSoftwareH264Profile' value") - } - - switch pconf.RPICameraSoftwareH264Level { - case "4.0", "4.1", "4.2": - default: - return fmt.Errorf("invalid 'rpiCameraSoftwareH264Level' value") + return fmt.Errorf("invalid 'rpiCameraH264Level' value") } if pconf.RPICameraJPEGQuality != nil { diff --git a/internal/staticsources/rpicamera/camera_params_arm_.go b/internal/staticsources/rpicamera/camera_params_arm_.go index 850719bc..a786ebbc 100644 --- a/internal/staticsources/rpicamera/camera_params_arm_.go +++ b/internal/staticsources/rpicamera/camera_params_arm_.go @@ -13,45 +13,43 @@ import ( ) type cameraParams struct { - LogLevel string - CameraID uint32 - Width uint32 - Height uint32 - HFlip bool - VFlip bool - Brightness float32 - Contrast float32 - Saturation float32 - Sharpness float32 - Exposure string - AWB string - AWBGainRed float32 - AWBGainBlue float32 - Denoise string - Shutter uint32 - Metering string - Gain float32 - EV float32 - ROI string - HDR bool - TuningFile string - Mode string - FPS float32 - AfMode string - AfRange string - AfSpeed string - LensPosition float32 - AfWindow string - FlickerPeriod uint32 - TextOverlayEnable bool - TextOverlay string - Codec string - IDRPeriod uint32 - Bitrate uint32 - HardwareH264Profile string - HardwareH264Level string - SoftwareH264Profile string - SoftwareH264Level string + LogLevel string + CameraID uint32 + Width uint32 + Height uint32 + HFlip bool + VFlip bool + Brightness float32 + Contrast float32 + Saturation float32 + Sharpness float32 + Exposure string + AWB string + AWBGainRed float32 + AWBGainBlue float32 + Denoise string + Shutter uint32 + Metering string + Gain float32 + EV float32 + ROI string + HDR bool + TuningFile string + Mode string + FPS float32 + AfMode string + AfRange string + AfSpeed string + LensPosition float32 + AfWindow string + FlickerPeriod uint32 + TextOverlayEnable bool + TextOverlay string + Codec string + IDRPeriod uint32 + Bitrate uint32 + H264Profile string + H264Level string SecondaryWidth uint32 SecondaryHeight uint32 @@ -102,13 +100,51 @@ func (p *cameraParams) fromConf(logLevel conf.LogLevel, cnf *conf.Path) { p.FlickerPeriod = uint32(cnf.RPICameraFlickerPeriod) p.TextOverlayEnable = cnf.RPICameraTextOverlayEnable p.TextOverlay = cnf.RPICameraTextOverlay - p.Codec = cnf.RPICameraCodec + + p.Codec = func() string { + if cnf.RPICameraCodec == "auto" { + if !cnf.RPICameraSecondary { + if supportsHardwareH264() { + return "hardwareH264" + } + return "softwareH264" + } + return "mjpeg" + } + return cnf.RPICameraCodec + }() + p.IDRPeriod = uint32(cnf.RPICameraIDRPeriod) p.Bitrate = uint32(cnf.RPICameraBitrate) - p.HardwareH264Profile = cnf.RPICameraHardwareH264Profile - p.HardwareH264Level = cnf.RPICameraHardwareH264Level - p.SoftwareH264Profile = cnf.RPICameraSoftwareH264Profile - p.SoftwareH264Level = cnf.RPICameraSoftwareH264Level + + p.H264Profile = func() string { + if p.Codec == "hardwareH264" && cnf.RPICameraHardwareH264Profile != nil { + return *cnf.RPICameraHardwareH264Profile + } + if p.Codec == "softwareH264" && cnf.RPICameraSoftwareH264Profile != nil { + return *cnf.RPICameraSoftwareH264Profile + } + + if cnf.RPICameraH264Profile == "auto" { + if p.Codec == "hardwareH264" { + return "main" + } + return "baseline" + } + + return cnf.RPICameraH264Profile + }() + + p.H264Level = func() string { + if p.Codec == "hardwareH264" && cnf.RPICameraHardwareH264Level != nil { + return *cnf.RPICameraHardwareH264Level + } + if p.Codec == "softwareH264" && cnf.RPICameraSoftwareH264Level != nil { + return *cnf.RPICameraSoftwareH264Level + } + + return cnf.RPICameraH264Level + }() p.SecondaryWidth = uint32(cnf.RPICameraSecondaryWidth) p.SecondaryHeight = uint32(cnf.RPICameraSecondaryHeight) diff --git a/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_32_TAR_GZ b/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_32_TAR_GZ index b3f3ee28..e2f05406 100644 --- a/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_32_TAR_GZ +++ b/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_32_TAR_GZ @@ -1 +1 @@ -4d9f7785e225af4c84d54dc36a5e44fc255188e3a2db291ccf7fa3093adc549d +63f305e54d124427d39f3f428cbc22ca004bc5f0f5ea550ed2998299f11c4b4a diff --git a/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_64_TAR_GZ b/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_64_TAR_GZ index da1080d7..a2e08297 100644 --- a/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_64_TAR_GZ +++ b/internal/staticsources/rpicamera/mtxrpicamdownloader/HASH_MTXRPICAM_64_TAR_GZ @@ -1 +1 @@ -8448f91064c9d659aa28dd22141ab4b82918d5990a8c3939cebcb2013a58f346 +5873f9fc54cb80714bf7ca577481de3a6ff9b928325b4ec87dde99c3bfd5b3f3 diff --git a/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION b/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION index 8a965c11..06646d7c 100644 --- a/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION +++ b/internal/staticsources/rpicamera/mtxrpicamdownloader/VERSION @@ -1 +1 @@ -v2.6.0 +v2.6.1 diff --git a/internal/staticsources/rpicamera/supports_hardware_h264_arm_.go b/internal/staticsources/rpicamera/supports_hardware_h264_arm_.go new file mode 100644 index 00000000..fec230df --- /dev/null +++ b/internal/staticsources/rpicamera/supports_hardware_h264_arm_.go @@ -0,0 +1,44 @@ +//go:build (linux && arm) || (linux && arm64) + +package rpicamera + +import ( + "bytes" + "os" + "syscall" + "unsafe" +) + +type v4l2Capability struct { + Driver [16]byte + Card [32]byte + BusInfo [32]byte + Version uint32 + Capabilities uint32 + DeviceCaps uint32 + Reserved [3]uint32 +} + +const VIDIOC_QUERYCAP = 0x80685600 + +func supportsHardwareH264() bool { + file, err := os.OpenFile("/dev/video11", os.O_RDWR, 0) + if err != nil { + return false + } + defer file.Close() + + var caps v4l2Capability + + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + file.Fd(), + uintptr(VIDIOC_QUERYCAP), + uintptr(unsafe.Pointer(&caps)), + ) + if errno != 0 { + return false + } + + return bytes.HasPrefix(caps.Card[:], []byte("bcm2835-codec")) +} diff --git a/mediamtx.yml b/mediamtx.yml index 92886086..78b3faba 100644 --- a/mediamtx.yml +++ b/mediamtx.yml @@ -701,22 +701,23 @@ pathDefaults: # format is the one of the strftime() function. rpiCameraTextOverlay: "%Y-%m-%d %H:%M:%S - MediaMTX" # Codec (auto, hardwareH264, softwareH264 or mjpeg). - # When is "auto" and stream is primary, it defaults to hardwareH264 (if available) or softwareH264. - # When is "auto" and stream is secondary, it defaults to mjpeg. + # When is "auto", it defaults to: + # * hardwareH264 when stream is primary and hardware encoder is available + # * softwareH264 when stream is primary and hardware encoder is not available + # * mjpeg when stream is secondary rpiCameraCodec: auto # Period between IDR frames (when codec is hardwareH264 or softwareH264). rpiCameraIDRPeriod: 60 # Bitrate (when codec is hardwareH264 or softwareH264). rpiCameraBitrate: 5000000 - # Hardware H264 profile (baseline, main or high) (when codec is hardwareH264). - rpiCameraHardwareH264Profile: main - # Hardware H264 level (4.0, 4.1 or 4.2) (when codec is hardwareH264). - rpiCameraHardwareH264Level: "4.1" - # Software H264 profile (baseline, main or high) (when codec is softwareH264). - rpiCameraSoftwareH264Profile: baseline - # Software H264 level (4.0, 4.1 or 4.2) (when codec is softwareH264). - rpiCameraSoftwareH264Level: "4.1" - # M-JPEG JPEG quality (when codec is mjpeg). + # H264 profile (auto, baseline, main or high). + # When is "auto", it defaults to: + # * main when codec is hardwareH264 + # * baseline when codec is softwareH264 + rpiCameraH264Profile: auto + # H264 level (4.0, 4.1 or 4.2). + rpiCameraH264Level: "4.1" + # M-JPEG quality (0-100). rpiCameraMJPEGQuality: 60 ###############################################