Merge remote-tracking branch 'upstream/master'
* upstream/master:
ipa: rpi: common: Handle AEC/AGC flicker controls
libcamera: controls: Add controls for AEC/AGC flicker avoidance
libcamera: rpi: pipeline_base: Cache sensor format
libcamera: rpi: pipeline_base: Move findBestFormat to CameraData
libcamera: rpi: pipeline_base: Remove populateSensorFormats()
utils: checkstyle.py: Check trailers for Amendment commits
utils: checkstyle.py: Derive Amendment from Commit
utils: checkstyle.py: Initialise staged trailers
utils: checkstyle.py: Treat Malformed trailers as a CommitIssue
libcamera v0.1.0
tests: gstreamer: Fix compiler error with gcc 8.4.0
libcamera: CameraManager: Remove ::get(dev_t)
v4l2: Use SystemDevices properties to identify cameras
v4l2: v4l2_camera_proxy: Prevent ioctl sign-extensions
ipa: rpi: imx296_mono: Disable all colour shading
ipa: rpi: imx708: Fix mode switch drop frame count
Change-Id: Ib5f840fde37f6e99d80769ec77c53e094b9966fb
diff --git a/include/libcamera/camera_manager.h b/include/libcamera/camera_manager.h
index 9767acc..1a891ca 100644
--- a/include/libcamera/camera_manager.h
+++ b/include/libcamera/camera_manager.h
@@ -32,7 +32,6 @@
std::vector<std::shared_ptr<Camera>> cameras() const;
std::shared_ptr<Camera> get(const std::string &id);
- std::shared_ptr<Camera> get(dev_t devnum);
static const std::string &version() { return version_; }
diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
index e4f5aaf..33ebe06 100644
--- a/include/libcamera/internal/camera_manager.h
+++ b/include/libcamera/internal/camera_manager.h
@@ -50,11 +50,10 @@
* This mutex protects
*
* - initialized_ and status_ during initialization
- * - cameras_ and camerasByDevnum_ after initialization
+ * - cameras_ after initialization
*/
mutable Mutex mutex_;
std::vector<std::shared_ptr<Camera>> cameras_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
- std::map<dev_t, std::weak_ptr<Camera>> camerasByDevnum_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
ConditionVariable cv_;
bool initialized_ LIBCAMERA_TSA_GUARDED_BY(mutex_);
diff --git a/meson.build b/meson.build
index 363de45..031e1ed 100644
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,7 @@
project('libcamera', 'c', 'cpp',
meson_version : '>= 0.57',
- version : '0.0.5',
+ version : '0.1.0',
default_options : [
'werror=true',
'warning_level=2',
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp
index 641ba18..dce39cd 100644
--- a/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx708.cpp
@@ -21,6 +21,8 @@
using namespace libcamera;
using libcamera::utils::Duration;
+using namespace std::literals::chrono_literals;
+
namespace libcamera {
LOG_DECLARE_CATEGORY(IPARPI)
}
@@ -56,7 +58,8 @@
int &vblankDelay, int &hblankDelay) const override;
bool sensorEmbeddedDataPresent() const override;
double getModeSensitivity(const CameraMode &mode) const override;
- unsigned int hideFramesModeSwitch() const override { return 1; } // seems to be required for HDR
+ unsigned int hideFramesModeSwitch() const override;
+ unsigned int hideFramesStartup() const override;
private:
/*
@@ -225,6 +228,26 @@
return (mode.width > 2304) ? 1.0 : 2.0;
}
+unsigned int CamHelperImx708::hideFramesModeSwitch() const
+{
+ /*
+ * We need to drop the first startup frame in HDR mode.
+ * Unfortunately the only way to currently determine if the sensor is in
+ * the HDR mode is to match with the resolution and framerate - the HDR
+ * mode only runs upto 30fps.
+ */
+ if (mode_.width == 2304 && mode_.height == 1296 &&
+ mode_.minFrameDuration > 1.0s / 32)
+ return 1;
+ else
+ return 0;
+}
+
+unsigned int CamHelperImx708::hideFramesStartup() const
+{
+ return hideFramesModeSwitch();
+}
+
void CamHelperImx708::populateMetadata(const MdParser::RegisterMap ®isters,
Metadata &metadata) const
{
diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp
index f40f2e7..4438ecd 100644
--- a/src/ipa/rpi/common/ipa_base.cpp
+++ b/src/ipa/rpi/common/ipa_base.cpp
@@ -61,6 +61,10 @@
{ &controls::AeConstraintMode, ControlInfo(controls::AeConstraintModeValues) },
{ &controls::AeExposureMode, ControlInfo(controls::AeExposureModeValues) },
{ &controls::ExposureValue, ControlInfo(-8.0f, 8.0f, 0.0f) },
+ { &controls::AeFlickerMode, ControlInfo(static_cast<int>(controls::FlickerOff),
+ static_cast<int>(controls::FlickerManual),
+ static_cast<int>(controls::FlickerOff)) },
+ { &controls::AeFlickerPeriod, ControlInfo(100, 1000000) },
{ &controls::Brightness, ControlInfo(-1.0f, 1.0f, 0.0f) },
{ &controls::Contrast, ControlInfo(0.0f, 32.0f, 1.0f) },
{ &controls::Sharpness, ControlInfo(0.0f, 16.0f, 1.0f) },
@@ -97,7 +101,7 @@
IpaBase::IpaBase()
: controller_(), frameCount_(0), mistrustCount_(0), lastRunTimestamp_(0),
- firstStart_(true)
+ firstStart_(true), flickerState_({ 0, 0s })
{
}
@@ -812,6 +816,64 @@
break;
}
+ case controls::AE_FLICKER_MODE: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AeFlickerMode - no AGC algorithm";
+ break;
+ }
+
+ int32_t mode = ctrl.second.get<int32_t>();
+ bool modeValid = true;
+
+ switch (mode) {
+ case controls::FlickerOff:
+ agc->setFlickerPeriod(0us);
+
+ break;
+
+ case controls::FlickerManual:
+ agc->setFlickerPeriod(flickerState_.manualPeriod);
+
+ break;
+
+ default:
+ LOG(IPARPI, Error) << "Flicker mode " << mode << " is not supported";
+ modeValid = false;
+
+ break;
+ }
+
+ if (modeValid)
+ flickerState_.mode = mode;
+
+ break;
+ }
+
+ case controls::AE_FLICKER_PERIOD: {
+ RPiController::AgcAlgorithm *agc = dynamic_cast<RPiController::AgcAlgorithm *>(
+ controller_.getAlgorithm("agc"));
+ if (!agc) {
+ LOG(IPARPI, Warning)
+ << "Could not set AeFlickerPeriod - no AGC algorithm";
+ break;
+ }
+
+ uint32_t manualPeriod = ctrl.second.get<int32_t>();
+ flickerState_.manualPeriod = manualPeriod * 1.0us;
+
+ /*
+ * We note that it makes no difference if the mode gets set to "manual"
+ * first, and the period updated after, or vice versa.
+ */
+ if (flickerState_.mode == controls::FlickerManual)
+ agc->setFlickerPeriod(flickerState_.manualPeriod);
+
+ break;
+ }
+
case controls::AWB_ENABLE: {
/* Silently ignore this control for a mono sensor. */
if (monoSensor_)
diff --git a/src/ipa/rpi/common/ipa_base.h b/src/ipa/rpi/common/ipa_base.h
index 39d0076..097f436 100644
--- a/src/ipa/rpi/common/ipa_base.h
+++ b/src/ipa/rpi/common/ipa_base.h
@@ -116,6 +116,12 @@
/* Frame duration (1/fps) limits. */
utils::Duration minFrameDuration_;
utils::Duration maxFrameDuration_;
+
+ /* The current state of flicker avoidance. */
+ struct FlickerState {
+ int32_t mode;
+ utils::Duration manualPeriod;
+ } flickerState_;
};
} /* namespace ipa::RPi */
diff --git a/src/ipa/rpi/vc4/data/imx296_mono.json b/src/ipa/rpi/vc4/data/imx296_mono.json
index 955af43..d4140c8 100644
--- a/src/ipa/rpi/vc4/data/imx296_mono.json
+++ b/src/ipa/rpi/vc4/data/imx296_mono.json
@@ -123,18 +123,18 @@
"ct": 4000,
"table":
[
- 2.554, 2.554, 2.541, 2.534, 2.495, 2.506, 2.516, 2.517, 2.518, 2.515, 2.513, 2.495, 2.481, 2.533, 2.533, 2.521,
- 2.522, 2.534, 2.539, 2.531, 2.531, 2.506, 2.506, 2.513, 2.513, 2.509, 2.498, 2.496, 2.508, 2.517, 2.521, 2.521,
- 2.509, 2.517, 2.534, 2.529, 2.531, 2.521, 2.517, 2.517, 2.515, 2.514, 2.506, 2.499, 2.508, 2.508, 2.521, 2.537,
- 2.507, 2.508, 2.517, 2.516, 2.495, 2.487, 2.519, 2.534, 2.535, 2.531, 2.499, 2.494, 2.501, 2.511, 2.526, 2.526,
- 2.509, 2.517, 2.507, 2.501, 2.494, 2.519, 2.539, 2.539, 2.537, 2.537, 2.533, 2.499, 2.503, 2.511, 2.529, 2.525,
- 2.521, 2.522, 2.476, 2.501, 2.501, 2.539, 2.546, 2.538, 2.531, 2.538, 2.541, 2.531, 2.529, 2.526, 2.529, 2.525,
- 2.516, 2.519, 2.469, 2.499, 2.499, 2.543, 2.543, 2.531, 2.528, 2.534, 2.541, 2.535, 2.531, 2.526, 2.531, 2.528,
- 2.509, 2.515, 2.465, 2.487, 2.487, 2.539, 2.543, 2.539, 2.533, 2.549, 2.542, 2.531, 2.529, 2.524, 2.532, 2.533,
- 2.499, 2.499, 2.475, 2.482, 2.471, 2.509, 2.539, 2.544, 2.543, 2.545, 2.533, 2.498, 2.521, 2.521, 2.537, 2.536,
- 2.499, 2.488, 2.488, 2.488, 2.471, 2.462, 2.509, 2.539, 2.539, 2.532, 2.498, 2.498, 2.518, 2.518, 2.539, 2.539,
- 2.483, 2.484, 2.488, 2.488, 2.502, 2.496, 2.508, 2.514, 2.518, 2.517, 2.521, 2.518, 2.518, 2.518, 2.525, 2.539,
- 2.483, 2.487, 2.478, 2.478, 2.507, 2.509, 2.514, 2.513, 2.514, 2.517, 2.536, 2.559, 2.501, 2.501, 2.503, 2.525
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]
}
],
@@ -143,18 +143,18 @@
"ct": 4000,
"table":
[
- 2.619, 2.603, 2.599, 2.597, 2.595, 2.594, 2.589, 2.587, 2.586, 2.589, 2.592, 2.597, 2.601, 2.608, 2.621, 2.621,
- 2.619, 2.615, 2.603, 2.601, 2.596, 2.595, 2.591, 2.589, 2.589, 2.592, 2.599, 2.593, 2.601, 2.613, 2.622, 2.631,
- 2.617, 2.617, 2.612, 2.611, 2.604, 2.598, 2.593, 2.591, 2.592, 2.591, 2.593, 2.595, 2.599, 2.614, 2.623, 2.631,
- 2.624, 2.619, 2.615, 2.612, 2.605, 2.602, 2.597, 2.596, 2.592, 2.592, 2.595, 2.599, 2.602, 2.606, 2.619, 2.624,
- 2.629, 2.627, 2.627, 2.617, 2.609, 2.598, 2.612, 2.623, 2.615, 2.604, 2.589, 2.595, 2.599, 2.608, 2.611, 2.614,
- 2.629, 2.632, 2.637, 2.627, 2.612, 2.612, 2.629, 2.631, 2.628, 2.621, 2.604, 2.597, 2.598, 2.604, 2.609, 2.609,
- 2.635, 2.636, 2.642, 2.628, 2.623, 2.623, 2.636, 2.636, 2.634, 2.628, 2.616, 2.599, 2.597, 2.601, 2.603, 2.601,
- 2.641, 2.639, 2.646, 2.632, 2.627, 2.625, 2.632, 2.635, 2.634, 2.627, 2.614, 2.596, 2.595, 2.599, 2.599, 2.598,
- 2.643, 2.644, 2.651, 2.649, 2.629, 2.617, 2.624, 2.629, 2.625, 2.614, 2.586, 2.599, 2.595, 2.597, 2.592, 2.595,
- 2.645, 2.646, 2.649, 2.649, 2.638, 2.624, 2.616, 2.617, 2.609, 2.604, 2.603, 2.603, 2.595, 2.589, 2.587, 2.592,
- 2.641, 2.643, 2.649, 2.647, 2.638, 2.618, 2.615, 2.608, 2.602, 2.595, 2.596, 2.595, 2.593, 2.584, 2.581, 2.583,
- 2.638, 2.637, 2.647, 2.634, 2.634, 2.618, 2.621, 2.621, 2.611, 2.602, 2.596, 2.583, 2.581, 2.581, 2.576, 2.574
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
]
}
],
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 7c65bb9..355f3ad 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -178,15 +178,9 @@
}
}
- auto devnums = camera->properties()
- .get(properties::SystemDevices)
- .value_or(Span<int64_t>{});
-
cameras_.push_back(std::move(camera));
unsigned int index = cameras_.size() - 1;
- for (dev_t devnum : devnums)
- camerasByDevnum_[devnum] = cameras_[index];
/* Report the addition to the public signal */
CameraManager *const o = LIBCAMERA_O_PTR();
@@ -219,13 +213,6 @@
LOG(Camera, Debug)
<< "Unregistering camera '" << camera->id() << "'";
- auto iter_d = std::find_if(camerasByDevnum_.begin(), camerasByDevnum_.end(),
- [camera](const std::pair<dev_t, std::weak_ptr<Camera>> &p) {
- return p.second.lock().get() == camera.get();
- });
- if (iter_d != camerasByDevnum_.end())
- camerasByDevnum_.erase(iter_d);
-
cameras_.erase(iter);
/* Report the removal to the public signal */
@@ -367,35 +354,6 @@
}
/**
- * \brief Retrieve a camera based on device number
- * \param[in] devnum Device number of camera to get
- *
- * This function is meant solely for the use of the V4L2 compatibility
- * layer, to map device nodes to Camera instances. Applications shall
- * not use it and shall instead retrieve cameras by name.
- *
- * Before calling this function the caller is responsible for ensuring that
- * the camera manager is running.
- *
- * \context This function is \threadsafe.
- *
- * \return Shared pointer to Camera object, which is empty if the camera is
- * not found
- */
-std::shared_ptr<Camera> CameraManager::get(dev_t devnum)
-{
- Private *const d = _d();
-
- MutexLocker locker(d->mutex_);
-
- auto iter = d->camerasByDevnum_.find(devnum);
- if (iter == d->camerasByDevnum_.end())
- return nullptr;
-
- return iter->second.lock();
-}
-
-/**
* \var CameraManager::cameraAdded
* \brief Notify of a new camera added to the system
*
diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml
index 056886e..f2e542f 100644
--- a/src/libcamera/control_ids.yaml
+++ b/src/libcamera/control_ids.yaml
@@ -156,6 +156,79 @@
control of which features should be automatically adjusted shouldn't
better be handled through a separate AE mode control.
+ - AeFlickerMode:
+ type: int32_t
+ description: |
+ Set the flicker mode, which determines whether, and how, the AGC/AEC
+ algorithm attempts to hide flicker effects caused by the duty cycle of
+ artificial lighting.
+
+ Although implementation dependent, many algorithms for "flicker
+ avoidance" work by restricting this exposure time to integer multiples
+ of the cycle period, wherever possible.
+
+ Implementations may not support all of the flicker modes listed below.
+
+ By default the system will start in FlickerAuto mode if this is
+ supported, otherwise the flicker mode will be set to FlickerOff.
+
+ enum:
+ - name: FlickerOff
+ value: 0
+ description: No flicker avoidance is performed.
+ - name: FlickerManual
+ value: 1
+ description: Manual flicker avoidance.
+ Suppress flicker effects caused by lighting running with a period
+ specified by the AeFlickerPeriod control.
+ \sa AeFlickerPeriod
+ - name: FlickerAuto
+ value: 2
+ description: Automatic flicker period detection and avoidance.
+ The system will automatically determine the most likely value of
+ flicker period, and avoid flicker of this frequency. Once flicker
+ is being corrected, it is implementation dependent whether the
+ system is still able to detect a change in the flicker period.
+ \sa AeFlickerDetected
+
+ - AeFlickerPeriod:
+ type: int32_t
+ description: Manual flicker period in microseconds.
+ This value sets the current flicker period to avoid. It is used when
+ AeFlickerMode is set to FlickerManual.
+
+ To cancel 50Hz mains flicker, this should be set to 10000 (corresponding
+ to 100Hz), or 8333 (120Hz) for 60Hz mains.
+
+ Setting the mode to FlickerManual when no AeFlickerPeriod has ever been
+ set means that no flicker cancellation occurs (until the value of this
+ control is updated).
+
+ Switching to modes other than FlickerManual has no effect on the
+ value of the AeFlickerPeriod control.
+
+ \sa AeFlickerMode
+
+ - AeFlickerDetected:
+ type: int32_t
+ description: Flicker period detected in microseconds.
+ The value reported here indicates the currently detected flicker
+ period, or zero if no flicker at all is detected.
+
+ When AeFlickerMode is set to FlickerAuto, there may be a period during
+ which the value reported here remains zero. Once a non-zero value is
+ reported, then this is the flicker period that has been detected and is
+ now being cancelled.
+
+ In the case of 50Hz mains flicker, the value would be 10000
+ (corresponding to 100Hz), or 8333 (120Hz) for 60Hz mains flicker.
+
+ It is implementation dependent whether the system can continue to detect
+ flicker of different periods when another frequency is already being
+ cancelled.
+
+ \sa AeFlickerMode
+
- Brightness:
type: float
description: |
@@ -850,23 +923,6 @@
value: 1
description: The lens shading map mode is available.
- - SceneFlicker:
- type: int32_t
- draft: true
- description: |
- Control to report the detected scene light frequency. Currently
- identical to ANDROID_STATISTICS_SCENE_FLICKER.
- enum:
- - name: SceneFickerOff
- value: 0
- description: No flickering detected.
- - name: SceneFicker50Hz
- value: 1
- description: 50Hz flickering detected.
- - name: SceneFicker60Hz
- value: 2
- description: 60Hz flickering detected.
-
- PipelineDepth:
type: int32_t
draft: true
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
index 179a5b8..e0fbeec 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.cpp
@@ -66,16 +66,6 @@
return pix;
}
-SensorFormats populateSensorFormats(std::unique_ptr<CameraSensor> &sensor)
-{
- SensorFormats formats;
-
- for (auto const mbusCode : sensor->mbusCodes())
- formats.emplace(mbusCode, sensor->sizes(mbusCode));
-
- return formats;
-}
-
bool isMonoSensor(std::unique_ptr<CameraSensor> &sensor)
{
unsigned int mbusCode = sensor->mbusCodes()[0];
@@ -84,63 +74,6 @@
return bayer.order == BayerFormat::Order::MONO;
}
-double scoreFormat(double desired, double actual)
-{
- double score = desired - actual;
- /* Smaller desired dimensions are preferred. */
- if (score < 0.0)
- score = (-score) / 8;
- /* Penalise non-exact matches. */
- if (actual != desired)
- score *= 2;
-
- return score;
-}
-
-V4L2SubdeviceFormat findBestFormat(const SensorFormats &formatsMap, const Size &req, unsigned int bitDepth)
-{
- double bestScore = std::numeric_limits<double>::max(), score;
- V4L2SubdeviceFormat bestFormat;
- bestFormat.colorSpace = ColorSpace::Raw;
-
- constexpr float penaltyAr = 1500.0;
- constexpr float penaltyBitDepth = 500.0;
-
- /* Calculate the closest/best mode from the user requested size. */
- for (const auto &iter : formatsMap) {
- const unsigned int mbusCode = iter.first;
- const PixelFormat format = mbusCodeToPixelFormat(mbusCode,
- BayerFormat::Packing::None);
- const PixelFormatInfo &info = PixelFormatInfo::info(format);
-
- for (const Size &size : iter.second) {
- double reqAr = static_cast<double>(req.width) / req.height;
- double fmtAr = static_cast<double>(size.width) / size.height;
-
- /* Score the dimensions for closeness. */
- score = scoreFormat(req.width, size.width);
- score += scoreFormat(req.height, size.height);
- score += penaltyAr * scoreFormat(reqAr, fmtAr);
-
- /* Add any penalties... this is not an exact science! */
- score += utils::abs_diff(info.bitsPerPixel, bitDepth) * penaltyBitDepth;
-
- if (score <= bestScore) {
- bestScore = score;
- bestFormat.mbus_code = mbusCode;
- bestFormat.size = size;
- }
-
- LOG(RPI, Debug) << "Format: " << size
- << " fmt " << format
- << " Score: " << score
- << " (best " << bestScore << ")";
- }
- }
-
- return bestFormat;
-}
-
const std::vector<ColorSpace> validColorSpaces = {
ColorSpace::Sycc,
ColorSpace::Smpte170m,
@@ -274,6 +207,17 @@
std::sort(outStreams.begin(), outStreams.end(),
[](auto &l, auto &r) { return l.cfg->size > r.cfg->size; });
+ /* Compute the sensor configuration. */
+ unsigned int bitDepth = defaultRawBitDepth;
+ if (!rawStreams.empty()) {
+ BayerFormat bayerFormat = BayerFormat::fromPixelFormat(rawStreams[0].cfg->pixelFormat);
+ bitDepth = bayerFormat.bitDepth;
+ }
+
+ sensorFormat_ = data_->findBestFormat(rawStreams.empty() ? outStreams[0].cfg->size
+ : rawStreams[0].cfg->size,
+ bitDepth);
+
/* Do any platform specific fixups. */
status = data_->platformValidate(rawStreams, outStreams);
if (status == Invalid)
@@ -284,12 +228,8 @@
StreamConfiguration &cfg = config_.at(raw.index);
V4L2DeviceFormat rawFormat;
- const PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);
- unsigned int bitDepth = info.isValid() ? info.bitsPerPixel : defaultRawBitDepth;
- V4L2SubdeviceFormat sensorFormat = findBestFormat(data_->sensorFormats_, cfg.size, bitDepth);
-
BayerFormat::Packing packing = BayerFormat::fromPixelFormat(cfg.pixelFormat).packing;
- rawFormat = PipelineHandlerBase::toV4L2DeviceFormat(raw.dev, sensorFormat, packing);
+ rawFormat = PipelineHandlerBase::toV4L2DeviceFormat(raw.dev, sensorFormat_, packing);
int ret = raw.dev->tryFormat(&rawFormat);
if (ret)
@@ -401,7 +341,7 @@
switch (role) {
case StreamRole::Raw:
size = sensorSize;
- sensorFormat = findBestFormat(data->sensorFormats_, size, defaultRawBitDepth);
+ sensorFormat = data->findBestFormat(size, defaultRawBitDepth);
pixelFormat = mbusCodeToPixelFormat(sensorFormat.mbus_code,
BayerFormat::Packing::CSI2);
ASSERT(pixelFormat.isValid());
@@ -509,8 +449,6 @@
stream->clearFlags(StreamFlag::External);
std::vector<CameraData::StreamParams> rawStreams, ispStreams;
- std::optional<BayerFormat::Packing> packing;
- unsigned int bitDepth = defaultRawBitDepth;
for (unsigned i = 0; i < config->size(); i++) {
StreamConfiguration *cfg = &config->at(i);
@@ -528,31 +466,23 @@
std::sort(ispStreams.begin(), ispStreams.end(),
[](auto &l, auto &r) { return l.cfg->size > r.cfg->size; });
- /*
- * Calculate the best sensor mode we can use based on the user's request,
- * and apply it to the sensor with the cached tranform, if any.
- *
- * If we have been given a RAW stream, use that size for setting up the sensor.
- */
- if (!rawStreams.empty()) {
- BayerFormat bayerFormat = BayerFormat::fromPixelFormat(rawStreams[0].cfg->pixelFormat);
- /* Replace the user requested packing/bit-depth. */
- packing = bayerFormat.packing;
- bitDepth = bayerFormat.bitDepth;
- }
+ /* Apply the format on the sensor with any cached transform. */
+ const RPiCameraConfiguration *rpiConfig =
+ static_cast<const RPiCameraConfiguration *>(config);
+ V4L2SubdeviceFormat sensorFormat = rpiConfig->sensorFormat_;
- V4L2SubdeviceFormat sensorFormat = findBestFormat(data->sensorFormats_,
- rawStreams.empty() ? ispStreams[0].cfg->size
- : rawStreams[0].cfg->size,
- bitDepth);
- /* Apply any cached transform. */
- const RPiCameraConfiguration *rpiConfig = static_cast<const RPiCameraConfiguration *>(config);
-
- /* Then apply the format on the sensor. */
ret = data->sensor_->setFormat(&sensorFormat, rpiConfig->combinedTransform_);
if (ret)
return ret;
+ /* Use the user requested packing/bit-depth. */
+ std::optional<BayerFormat::Packing> packing;
+ if (!rawStreams.empty()) {
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(rawStreams[0].cfg->pixelFormat);
+ packing = bayerFormat.packing;
+ }
+
/*
* Platform specific internal stream configuration. This also assigns
* external streams which get configured below.
@@ -804,7 +734,10 @@
if (data->sensor_->init())
return -EINVAL;
- data->sensorFormats_ = populateSensorFormats(data->sensor_);
+ /* Populate the map of sensor supported formats and sizes. */
+ for (auto const mbusCode : data->sensor_->mbusCodes())
+ data->sensorFormats_.emplace(mbusCode,
+ data->sensor_->sizes(mbusCode));
/*
* Enumerate all the Video Mux/Bridge devices across the sensor -> Fr
@@ -961,6 +894,63 @@
return 0;
}
+double CameraData::scoreFormat(double desired, double actual) const
+{
+ double score = desired - actual;
+ /* Smaller desired dimensions are preferred. */
+ if (score < 0.0)
+ score = (-score) / 8;
+ /* Penalise non-exact matches. */
+ if (actual != desired)
+ score *= 2;
+
+ return score;
+}
+
+V4L2SubdeviceFormat CameraData::findBestFormat(const Size &req, unsigned int bitDepth) const
+{
+ double bestScore = std::numeric_limits<double>::max(), score;
+ V4L2SubdeviceFormat bestFormat;
+ bestFormat.colorSpace = ColorSpace::Raw;
+
+ constexpr float penaltyAr = 1500.0;
+ constexpr float penaltyBitDepth = 500.0;
+
+ /* Calculate the closest/best mode from the user requested size. */
+ for (const auto &iter : sensorFormats_) {
+ const unsigned int mbusCode = iter.first;
+ const PixelFormat format = mbusCodeToPixelFormat(mbusCode,
+ BayerFormat::Packing::None);
+ const PixelFormatInfo &info = PixelFormatInfo::info(format);
+
+ for (const Size &size : iter.second) {
+ double reqAr = static_cast<double>(req.width) / req.height;
+ double fmtAr = static_cast<double>(size.width) / size.height;
+
+ /* Score the dimensions for closeness. */
+ score = scoreFormat(req.width, size.width);
+ score += scoreFormat(req.height, size.height);
+ score += penaltyAr * scoreFormat(reqAr, fmtAr);
+
+ /* Add any penalties... this is not an exact science! */
+ score += utils::abs_diff(info.bitsPerPixel, bitDepth) * penaltyBitDepth;
+
+ if (score <= bestScore) {
+ bestScore = score;
+ bestFormat.mbus_code = mbusCode;
+ bestFormat.size = size;
+ }
+
+ LOG(RPI, Debug) << "Format: " << size
+ << " fmt " << format
+ << " Score: " << score
+ << " (best " << bestScore << ")";
+ }
+ }
+
+ return bestFormat;
+}
+
void CameraData::freeBuffers()
{
if (ipa_) {
diff --git a/src/libcamera/pipeline/rpi/common/pipeline_base.h b/src/libcamera/pipeline/rpi/common/pipeline_base.h
index f648e81..a139c98 100644
--- a/src/libcamera/pipeline/rpi/common/pipeline_base.h
+++ b/src/libcamera/pipeline/rpi/common/pipeline_base.h
@@ -81,6 +81,9 @@
virtual void platformStart() = 0;
virtual void platformStop() = 0;
+ double scoreFormat(double desired, double actual) const;
+ V4L2SubdeviceFormat findBestFormat(const Size &req, unsigned int bitDepth) const;
+
void freeBuffers();
virtual void platformFreeBuffers() = 0;
@@ -259,6 +262,8 @@
/* Cache the combinedTransform_ that will be applied to the sensor */
Transform combinedTransform_;
+ /* The sensor format computed in validate() */
+ V4L2SubdeviceFormat sensorFormat_;
private:
const CameraData *data_;
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 55ff62c..341f790 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -778,10 +778,20 @@
VIDIOC_STREAMOFF,
};
-int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long request, void *arg)
+int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg)
{
MutexLocker locker(proxyMutex_);
+ /*
+ * The Linux Kernel only processes 32 bits of an IOCTL.
+ *
+ * Prevent unexpected sign-extensions that could occur if applications
+ * use a signed int for the ioctl request, which would sign-extend to
+ * an incorrect value for unsigned longs on 64 bit architectures by
+ * explicitly casting as an unsigned int here.
+ */
+ unsigned int request = longRequest;
+
if (!arg && (_IOC_DIR(request) & _IOC_WRITE)) {
errno = EFAULT;
return -1;
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 0f7575c..5e8cdb4 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -24,6 +24,7 @@
#include <libcamera/camera.h>
#include <libcamera/camera_manager.h>
+#include <libcamera/property_ids.h>
#include "v4l2_camera_file.h"
@@ -113,14 +114,35 @@
if (ret < 0)
return -1;
- std::shared_ptr<Camera> target = cm_->get(statbuf.st_rdev);
- if (!target)
- return -1;
+ const dev_t devnum = statbuf.st_rdev;
+ /*
+ * Iterate each known camera and identify if it reports this nodes
+ * device number in its list of SystemDevices.
+ */
auto cameras = cm_->cameras();
for (auto [index, camera] : utils::enumerate(cameras)) {
- if (camera == target)
- return index;
+ Span<const int64_t> devices = camera->properties()
+ .get(properties::SystemDevices)
+ .value_or(Span<int64_t>{});
+
+ /*
+ * While there may be multiple cameras that could reference the
+ * same device node, we take a first match as a best effort for
+ * now.
+ *
+ * \todo Each camera can be accessed through any of the video
+ * device nodes that it uses. This may confuse applications.
+ * Consider reworking the V4L2 adaptation layer to instead
+ * expose each Camera instance through a single video device
+ * node (with a consistent and stable mapping). The other
+ * device nodes could possibly be hidden from the application
+ * by intercepting additional calls to the C library.
+ */
+ for (const int64_t dev : devices) {
+ if (dev == static_cast<int64_t>(devnum))
+ return index;
+ }
}
return -1;
diff --git a/test/gstreamer/gstreamer_device_provider_test.cpp b/test/gstreamer/gstreamer_device_provider_test.cpp
index c8606b9..237af8c 100644
--- a/test/gstreamer/gstreamer_device_provider_test.cpp
+++ b/test/gstreamer/gstreamer_device_provider_test.cpp
@@ -34,7 +34,7 @@
int run() override
{
- g_autoptr(GstDeviceProvider) provider;
+ g_autoptr(GstDeviceProvider) provider = NULL;
GList *devices, *l;
std::vector<std::string> cameraNames;
std::unique_ptr<libcamera::CameraManager> cm;
diff --git a/utils/checkstyle.py b/utils/checkstyle.py
index 3558740..214509b 100755
--- a/utils/checkstyle.py
+++ b/utils/checkstyle.py
@@ -208,6 +208,17 @@
self.commit = commit
self._parse()
+ def _parse_trailers(self, lines):
+ self._trailers = []
+ for index in range(1, len(lines)):
+ line = lines[index]
+ if not line:
+ break
+
+ self._trailers.append(line)
+
+ return index
+
def _parse(self):
# Get the commit title and list of files.
ret = subprocess.run(['git', 'show', '--format=%s%n%(trailers:only,unfold)', '--name-status',
@@ -217,14 +228,7 @@
self._title = lines[0]
- self._trailers = []
- for index in range(1, len(lines)):
- line = lines[index]
- if not line:
- break
-
- self._trailers.append(line)
-
+ index = self._parse_trailers(lines)
self._files = [CommitFile(f) for f in lines[index:] if f]
def files(self, filter='AMR'):
@@ -253,6 +257,9 @@
def __init__(self):
Commit.__init__(self, '')
+ # There are no trailers to parse on a Staged Change.
+ self._trailers = []
+
def _parse(self):
ret = subprocess.run(['git', 'diff', '--staged', '--name-status'],
stdout=subprocess.PIPE).stdout.decode('utf-8')
@@ -266,9 +273,9 @@
return parse_diff(diff.splitlines(True))
-class Amendment(StagedChanges):
+class Amendment(Commit):
def __init__(self):
- StagedChanges.__init__(self)
+ Commit.__init__(self, '')
def _parse(self):
# Create a title using HEAD commit
@@ -280,6 +287,12 @@
stdout=subprocess.PIPE).stdout.decode('utf-8')
self._files = [CommitFile(f) for f in ret.splitlines()]
+ # Parse trailers from the existing commit only.
+ ret = subprocess.run(['git', 'show', '--format=%n%(trailers:only,unfold)',
+ '--no-patch'],
+ stdout=subprocess.PIPE).stdout.decode('utf-8')
+ self._parse_trailers(ret.splitlines())
+
def get_diff(self, top_level, filename):
diff = subprocess.run(['git', 'diff', '--staged', 'HEAD~', '--',
'%s/%s' % (top_level, filename)],
@@ -479,7 +492,8 @@
for trailer in commit.trailers:
match = TrailersChecker.trailer_regex.fullmatch(trailer)
if not match:
- raise RuntimeError(f"Malformed commit trailer '{trailer}'")
+ issues.append(CommitIssue(f"Malformed commit trailer '{trailer}'"))
+ continue
key, value = match.groups()