summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format4
-rw-r--r--Documentation/Doxyfile-common.in (renamed from Documentation/Doxyfile.in)23
-rw-r--r--Documentation/Doxyfile-internal.in31
-rw-r--r--Documentation/Doxyfile-public.in20
-rw-r--r--Documentation/api-html/index.rst4
-rw-r--r--Documentation/camera-sensor-model.rst2
-rw-r--r--Documentation/code-of-conduct.rst2
-rw-r--r--Documentation/coding-style.rst4
-rw-r--r--Documentation/conf.py10
-rw-r--r--Documentation/docs.rst400
-rw-r--r--Documentation/documentation-contents.rst35
-rw-r--r--Documentation/environment_variables.rst12
-rw-r--r--Documentation/feature_requirements.rst150
-rwxr-xr-xDocumentation/gen-doxyfile.py46
-rw-r--r--Documentation/getting-started.rst1
-rw-r--r--Documentation/guides/application-developer.rst2
-rw-r--r--Documentation/guides/introduction.rst319
-rw-r--r--Documentation/guides/ipa.rst2
-rw-r--r--Documentation/guides/pipeline-handler.rst7
-rw-r--r--Documentation/guides/tracing.rst2
-rw-r--r--Documentation/index.rst24
-rw-r--r--Documentation/internal-api-html/index.rst8
-rw-r--r--Documentation/introduction.rst224
-rw-r--r--Documentation/lens_driver_requirements.rst2
-rw-r--r--Documentation/libcamera_architecture.rst168
-rw-r--r--Documentation/mainpage.dox33
-rw-r--r--Documentation/mali-c55.dot25
-rw-r--r--Documentation/meson.build89
-rw-r--r--Documentation/python-bindings.rst2
-rw-r--r--Documentation/sensor_driver_requirements.rst2
-rw-r--r--Documentation/software-isp-benchmarking.rst2
-rw-r--r--Documentation/theme/static/css/theme.css6
-rw-r--r--Documentation/thread-safety.dox44
-rw-r--r--README.rst21
-rw-r--r--include/libcamera/base/event_dispatcher.h2
-rw-r--r--include/libcamera/base/file.h4
-rw-r--r--include/libcamera/base/log.h2
-rw-r--r--include/libcamera/base/memfd.h32
-rw-r--r--include/libcamera/base/meson.build1
-rw-r--r--include/libcamera/base/signal.h1
-rw-r--r--include/libcamera/base/span.h1
-rw-r--r--include/libcamera/base/timer.h1
-rw-r--r--include/libcamera/base/utils.h40
-rw-r--r--include/libcamera/control_ids.h.in42
-rw-r--r--include/libcamera/controls.h29
-rw-r--r--include/libcamera/framebuffer.h1
-rw-r--r--include/libcamera/internal/camera_manager.h8
-rw-r--r--include/libcamera/internal/camera_sensor.h1
-rw-r--r--include/libcamera/internal/converter.h5
-rw-r--r--include/libcamera/internal/converter/converter_v4l2_m2m.h13
-rw-r--r--include/libcamera/internal/device_enumerator_sysfs.h1
-rw-r--r--include/libcamera/internal/dma_buf_allocator.h2
-rw-r--r--include/libcamera/internal/formats.h1
-rw-r--r--include/libcamera/internal/ipa_data_serializer.h11
-rw-r--r--include/libcamera/internal/ipa_manager.h9
-rw-r--r--include/libcamera/internal/ipa_proxy.h5
-rw-r--r--include/libcamera/internal/ipc_pipe_unixsocket.h1
-rw-r--r--include/libcamera/internal/media_device.h1
-rw-r--r--include/libcamera/internal/media_object.h8
-rw-r--r--include/libcamera/internal/meson.build18
-rw-r--r--include/libcamera/internal/pipeline_handler.h13
-rw-r--r--include/libcamera/internal/request.h1
-rw-r--r--include/libcamera/internal/shared_mem_object.h1
-rw-r--r--include/libcamera/internal/software_isp/software_isp.h5
-rw-r--r--include/libcamera/internal/yaml_parser.h10
-rw-r--r--include/libcamera/ipa/ipa_interface.h17
-rw-r--r--include/libcamera/ipa/meson.build29
-rw-r--r--include/libcamera/ipa/rkisp1.mojom3
-rw-r--r--include/libcamera/logging.h2
-rw-r--r--include/libcamera/meson.build38
-rw-r--r--include/libcamera/pixel_format.h1
-rw-r--r--include/libcamera/property_ids.h.in34
-rw-r--r--include/libcamera/request.h1
-rw-r--r--include/libcamera/stream.h1
-rw-r--r--include/libcamera/transform.h2
-rw-r--r--include/linux/README2
-rw-r--r--include/linux/dma-heap.h2
-rw-r--r--include/linux/drm_fourcc.h18
-rw-r--r--include/linux/rkisp1-config.h578
-rw-r--r--include/linux/v4l2-controls.h2
-rw-r--r--include/linux/videodev2.h5
-rw-r--r--meson.build30
-rw-r--r--src/android/jpeg/encoder_libjpeg.cpp6
-rw-r--r--src/apps/cam/camera_session.cpp30
-rw-r--r--src/apps/cam/main.cpp4
-rw-r--r--src/apps/common/dng_writer.cpp223
-rw-r--r--src/apps/common/options.cpp8
-rw-r--r--src/apps/qcam/assets/shader/bayer_8.vert4
-rw-r--r--src/apps/qcam/assets/shader/identity.vert3
-rw-r--r--src/apps/qcam/cam_select_dialog.cpp10
-rw-r--r--src/apps/qcam/main.cpp4
-rw-r--r--src/apps/qcam/main_window.cpp10
-rw-r--r--src/apps/qcam/meson.build49
-rw-r--r--src/apps/qcam/viewfinder_gl.cpp52
-rw-r--r--src/apps/qcam/viewfinder_gl.h2
-rw-r--r--src/apps/qcam/viewfinder_qt.cpp51
-rw-r--r--src/apps/qcam/viewfinder_qt.h2
-rw-r--r--src/gstreamer/gstlibcamera-utils.cpp18
-rw-r--r--src/gstreamer/gstlibcameraallocator.cpp16
-rw-r--r--src/gstreamer/gstlibcamerapool.cpp41
-rw-r--r--src/gstreamer/gstlibcamerasrc.cpp47
-rw-r--r--src/ipa/ipu3/algorithms/af.cpp3
-rw-r--r--src/ipa/ipu3/algorithms/agc.cpp3
-rw-r--r--src/ipa/ipu3/algorithms/blc.cpp6
-rw-r--r--src/ipa/ipu3/ipu3.cpp20
-rw-r--r--src/ipa/ipu3/meson.build3
-rw-r--r--src/ipa/libipa/camera_sensor_helper.cpp155
-rw-r--r--src/ipa/libipa/camera_sensor_helper.h6
-rw-r--r--src/ipa/libipa/exposure_mode_helper.cpp9
-rw-r--r--src/ipa/libipa/histogram.h1
-rw-r--r--src/ipa/libipa/interpolator.cpp157
-rw-r--r--src/ipa/libipa/interpolator.h131
-rw-r--r--src/ipa/libipa/lsc_polynomial.cpp81
-rw-r--r--src/ipa/libipa/lsc_polynomial.h105
-rw-r--r--src/ipa/libipa/matrix.h1
-rw-r--r--src/ipa/libipa/matrix_interpolator.cpp110
-rw-r--r--src/ipa/libipa/matrix_interpolator.h122
-rw-r--r--src/ipa/libipa/meson.build6
-rw-r--r--src/ipa/libipa/pwl.cpp2
-rw-r--r--src/ipa/libipa/pwl.h3
-rw-r--r--src/ipa/libipa/vector.h4
-rw-r--r--src/ipa/rkisp1/algorithms/agc.cpp48
-rw-r--r--src/ipa/rkisp1/algorithms/agc.h3
-rw-r--r--src/ipa/rkisp1/algorithms/awb.cpp87
-rw-r--r--src/ipa/rkisp1/algorithms/awb.h2
-rw-r--r--src/ipa/rkisp1/algorithms/blc.cpp135
-rw-r--r--src/ipa/rkisp1/algorithms/blc.h11
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.cpp46
-rw-r--r--src/ipa/rkisp1/algorithms/ccm.h10
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.cpp14
-rw-r--r--src/ipa/rkisp1/algorithms/cproc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.cpp10
-rw-r--r--src/ipa/rkisp1/algorithms/dpcc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.cpp31
-rw-r--r--src/ipa/rkisp1/algorithms/dpf.h2
-rw-r--r--src/ipa/rkisp1/algorithms/filter.cpp56
-rw-r--r--src/ipa/rkisp1/algorithms/filter.h2
-rw-r--r--src/ipa/rkisp1/algorithms/goc.cpp17
-rw-r--r--src/ipa/rkisp1/algorithms/goc.h2
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.cpp20
-rw-r--r--src/ipa/rkisp1/algorithms/gsl.h2
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.cpp436
-rw-r--r--src/ipa/rkisp1/algorithms/lsc.h17
-rw-r--r--src/ipa/rkisp1/data/imx219.yaml4
-rw-r--r--src/ipa/rkisp1/data/imx258.yaml1
-rw-r--r--src/ipa/rkisp1/data/meson.build4
-rw-r--r--src/ipa/rkisp1/data/ov4689.yaml4
-rw-r--r--src/ipa/rkisp1/data/ov5640.yaml4
-rw-r--r--src/ipa/rkisp1/data/uncalibrated.yaml1
-rw-r--r--src/ipa/rkisp1/ipa_context.cpp8
-rw-r--r--src/ipa/rkisp1/ipa_context.h15
-rw-r--r--src/ipa/rkisp1/meson.build4
-rw-r--r--src/ipa/rkisp1/module.h3
-rw-r--r--src/ipa/rkisp1/params.cpp222
-rw-r--r--src/ipa/rkisp1/params.h163
-rw-r--r--src/ipa/rkisp1/rkisp1.cpp69
-rw-r--r--src/ipa/rkisp1/utils.h1
-rw-r--r--src/ipa/rpi/cam_helper/cam_helper_imx283.cpp73
-rw-r--r--src/ipa/rpi/cam_helper/meson.build1
-rw-r--r--src/ipa/rpi/controller/rpi/agc_channel.cpp7
-rw-r--r--src/ipa/rpi/controller/rpi/alsc.cpp6
-rw-r--r--src/ipa/rpi/controller/rpi/awb.cpp2
-rw-r--r--src/ipa/rpi/controller/rpi/ccm.cpp6
-rw-r--r--src/ipa/rpi/controller/rpi/contrast.cpp4
-rw-r--r--src/ipa/rpi/vc4/data/imx283.json313
-rw-r--r--src/ipa/rpi/vc4/data/meson.build1
-rw-r--r--src/ipa/rpi/vc4/meson.build3
-rw-r--r--src/ipa/simple/meson.build3
-rw-r--r--src/ipa/vimc/meson.build3
-rw-r--r--src/ipa/vimc/vimc.cpp1
-rw-r--r--src/libcamera/base/event_dispatcher_poll.cpp3
-rw-r--r--src/libcamera/base/memfd.cpp123
-rw-r--r--src/libcamera/base/meson.build26
-rw-r--r--src/libcamera/base/thread.cpp36
-rw-r--r--src/libcamera/base/utils.cpp132
-rw-r--r--src/libcamera/bayer_format.cpp2
-rw-r--r--src/libcamera/camera.cpp22
-rw-r--r--src/libcamera/camera_manager.cpp12
-rw-r--r--src/libcamera/control_ids.cpp.in103
-rw-r--r--src/libcamera/control_ids_core.yaml472
-rw-r--r--src/libcamera/control_ids_rpi.yaml16
-rw-r--r--src/libcamera/controls.cpp32
-rw-r--r--src/libcamera/converter.cpp8
-rw-r--r--src/libcamera/converter/converter_v4l2_m2m.cpp82
-rw-r--r--src/libcamera/converter/meson.build2
-rw-r--r--src/libcamera/device_enumerator_sysfs.cpp2
-rw-r--r--src/libcamera/device_enumerator_udev.cpp8
-rw-r--r--src/libcamera/dma_buf_allocator.cpp46
-rw-r--r--src/libcamera/fence.cpp2
-rw-r--r--src/libcamera/formats.cpp15
-rw-r--r--src/libcamera/formats.yaml3
-rw-r--r--src/libcamera/framebuffer.cpp7
-rw-r--r--src/libcamera/ipa_data_serializer.cpp5
-rw-r--r--src/libcamera/ipa_manager.cpp10
-rw-r--r--src/libcamera/ipa_module.cpp5
-rw-r--r--src/libcamera/ipa_proxy.cpp23
-rw-r--r--src/libcamera/ipc_unixsocket.cpp11
-rw-r--r--src/libcamera/mapped_framebuffer.cpp2
-rw-r--r--src/libcamera/media_device.cpp12
-rw-r--r--src/libcamera/media_object.cpp50
-rw-r--r--src/libcamera/meson.build86
-rw-r--r--src/libcamera/orientation.cpp15
-rw-r--r--src/libcamera/pipeline/imx8-isi/meson.build2
-rw-r--r--src/libcamera/pipeline/ipu3/frames.cpp3
-rw-r--r--src/libcamera/pipeline/ipu3/ipu3.cpp23
-rw-r--r--src/libcamera/pipeline/ipu3/meson.build2
-rw-r--r--src/libcamera/pipeline/mali-c55/meson.build2
-rw-r--r--src/libcamera/pipeline/rkisp1/meson.build2
-rw-r--r--src/libcamera/pipeline/rkisp1/rkisp1.cpp53
-rw-r--r--src/libcamera/pipeline/rpi/common/meson.build2
-rw-r--r--src/libcamera/pipeline/rpi/vc4/meson.build2
-rw-r--r--src/libcamera/pipeline/rpi/vc4/vc4.cpp2
-rw-r--r--src/libcamera/pipeline/simple/meson.build2
-rw-r--r--src/libcamera/pipeline/simple/simple.cpp51
-rw-r--r--src/libcamera/pipeline/uvcvideo/meson.build2
-rw-r--r--src/libcamera/pipeline/uvcvideo/uvcvideo.cpp60
-rw-r--r--src/libcamera/pipeline/vimc/meson.build2
-rw-r--r--src/libcamera/pipeline/vimc/vimc.cpp3
-rw-r--r--src/libcamera/pipeline_handler.cpp73
-rw-r--r--src/libcamera/process.cpp6
-rw-r--r--src/libcamera/property_ids.cpp.in48
-rw-r--r--src/libcamera/proxy/meson.build5
-rw-r--r--src/libcamera/proxy/worker/meson.build6
-rw-r--r--src/libcamera/request.cpp8
-rw-r--r--src/libcamera/sensor/camera_sensor.cpp8
-rw-r--r--src/libcamera/sensor/camera_sensor_properties.cpp19
-rw-r--r--src/libcamera/sensor/meson.build2
-rw-r--r--src/libcamera/shared_mem_object.cpp23
-rw-r--r--src/libcamera/software_isp/TODO19
-rw-r--r--src/libcamera/software_isp/debayer_cpu.cpp63
-rw-r--r--src/libcamera/software_isp/debayer_cpu.h2
-rw-r--r--src/libcamera/software_isp/meson.build2
-rw-r--r--src/libcamera/software_isp/software_isp.cpp31
-rw-r--r--src/libcamera/stream.cpp8
-rw-r--r--src/libcamera/v4l2_device.cpp2
-rw-r--r--src/libcamera/v4l2_pixelformat.cpp2
-rw-r--r--src/libcamera/v4l2_subdevice.cpp5
-rw-r--r--src/libcamera/v4l2_videodevice.cpp61
-rw-r--r--src/libcamera/yaml_parser.cpp29
-rw-r--r--src/py/cam/cam_qt.py6
-rw-r--r--src/py/cam/cam_qtgl.py12
-rwxr-xr-xsrc/py/libcamera/gen-py-controls.py126
-rw-r--r--src/py/libcamera/meson.build27
-rw-r--r--src/py/libcamera/py_color_space.cpp2
-rw-r--r--src/py/libcamera/py_controls_generated.cpp.in35
-rw-r--r--src/py/libcamera/py_enums.cpp2
-rw-r--r--src/py/libcamera/py_formats_generated.cpp.in2
-rw-r--r--src/py/libcamera/py_geometry.cpp2
-rw-r--r--src/py/libcamera/py_main.cpp11
-rw-r--r--src/py/libcamera/py_main.h10
-rw-r--r--src/py/libcamera/py_properties_generated.cpp.in28
-rw-r--r--src/py/libcamera/py_transform.cpp2
-rw-r--r--src/v4l2/v4l2_camera.cpp12
-rw-r--r--src/v4l2/v4l2_camera.h8
-rw-r--r--src/v4l2/v4l2_camera_proxy.cpp27
-rw-r--r--src/v4l2/v4l2_camera_proxy.h1
-rw-r--r--src/v4l2/v4l2_compat.cpp76
-rw-r--r--src/v4l2/v4l2_compat_manager.cpp1
-rw-r--r--test/gstreamer/gstreamer_memory_lifetime_test.cpp90
-rw-r--r--test/gstreamer/gstreamer_test.cpp6
-rw-r--r--test/gstreamer/meson.build14
-rw-r--r--test/ipa/ipa_interface_test.cpp12
-rw-r--r--test/ipa/libipa/interpolator.cpp54
-rw-r--r--test/ipa/libipa/meson.build15
-rw-r--r--test/ipa/meson.build3
-rw-r--r--test/ipa/rkisp1/meson.build2
-rw-r--r--test/ipc/unixsocket.cpp11
-rw-r--r--test/serialization/generated_serializer/generated_serializer_test.cpp1
-rw-r--r--test/serialization/generated_serializer/include/libcamera/ipa/meson.build9
-rw-r--r--test/utils.cpp17
-rw-r--r--test/yaml-parser.cpp52
-rwxr-xr-xutils/abi-compat.sh13
-rwxr-xr-xutils/checkstyle.py153
-rw-r--r--utils/codegen/controls.py112
-rwxr-xr-xutils/codegen/gen-controls.py109
-rwxr-xr-xutils/codegen/gen-formats.py (renamed from utils/gen-formats.py)0
-rwxr-xr-xutils/codegen/gen-header.sh (renamed from utils/gen-header.sh)7
-rwxr-xr-xutils/codegen/gen-ipa-pub-key.py (renamed from utils/gen-ipa-pub-key.py)0
-rwxr-xr-xutils/codegen/gen-tp-header.py (renamed from utils/tracepoints/gen-tp-header.py)0
-rwxr-xr-xutils/codegen/ipc/extract-docs.py (renamed from utils/ipc/extract-docs.py)0
-rwxr-xr-xutils/codegen/ipc/generate.py (renamed from utils/ipc/generate.py)3
-rw-r--r--utils/codegen/ipc/generators/__init__.py (renamed from utils/ipc/generators/__init__.py)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl (renamed from utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl)5
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl (renamed from utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/definition_functions.tmpl (renamed from utils/ipc/generators/libcamera_templates/definition_functions.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/meson.build (renamed from utils/ipc/generators/libcamera_templates/meson.build)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl (renamed from utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl)14
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl (renamed from utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl (renamed from utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl (renamed from utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl (renamed from utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/proxy_functions.tmpl (renamed from utils/ipc/generators/libcamera_templates/proxy_functions.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/libcamera_templates/serializer.tmpl (renamed from utils/ipc/generators/libcamera_templates/serializer.tmpl)0
-rw-r--r--utils/codegen/ipc/generators/meson.build (renamed from utils/ipc/generators/meson.build)0
-rw-r--r--utils/codegen/ipc/generators/mojom_libcamera_generator.py (renamed from utils/ipc/generators/mojom_libcamera_generator.py)2
-rw-r--r--utils/codegen/ipc/meson.build (renamed from utils/ipc/meson.build)3
-rw-r--r--utils/codegen/ipc/mojo/README (renamed from utils/ipc/mojo/README)0
-rw-r--r--utils/codegen/ipc/mojo/public/LICENSE (renamed from utils/ipc/mojo/public/LICENSE)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/.style.yapf (renamed from utils/ipc/mojo/public/tools/.style.yapf)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/BUILD.gn (renamed from utils/ipc/mojo/public/tools/BUILD.gn)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn (renamed from utils/ipc/mojo/public/tools/bindings/BUILD.gn)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/README.md (renamed from utils/ipc/mojo/public/tools/bindings/README.md)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/__init__.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py (renamed from utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py (renamed from utils/ipc/mojo/public/tools/bindings/concatenate-files.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py (renamed from utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py (renamed from utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py (renamed from utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py (renamed from utils/ipc/mojo/public/tools/bindings/minify_with_terser.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni (renamed from utils/ipc/mojo/public/tools/bindings/mojom.gni)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py (renamed from utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py (renamed from utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py (renamed from utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn (renamed from utils/ipc/mojo/public/tools/mojom/BUILD.gn)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/README.md (renamed from utils/ipc/mojo/public/tools/mojom/README.md)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py (renamed from utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/const_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/const_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/enum_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/enum_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/feature_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/BUILD.gn (renamed from utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/__init__.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/__init__.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/error.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/error.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/mojom/mojom_parser.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom_parser.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/union_unittest.py)0
-rw-r--r--utils/codegen/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py (renamed from utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py)0
-rwxr-xr-xutils/codegen/ipc/mojo/public/tools/run_all_python_unittests.py (renamed from utils/ipc/mojo/public/tools/run_all_python_unittests.py)0
-rwxr-xr-xutils/codegen/ipc/parser.py (renamed from utils/ipc/parser.py)3
-rw-r--r--utils/codegen/ipc/tools/README (renamed from utils/ipc/tools/README)0
-rw-r--r--utils/codegen/ipc/tools/diagnosis/crbug_1001171.py (renamed from utils/ipc/tools/diagnosis/crbug_1001171.py)0
-rw-r--r--utils/codegen/meson.build18
-rwxr-xr-xutils/gen-controls.py389
-rwxr-xr-xutils/gen-version.sh2
-rw-r--r--utils/meson.build10
-rw-r--r--utils/tracepoints/meson.build5
-rw-r--r--utils/tuning/README.rst23
-rw-r--r--utils/tuning/config-example.yaml12
-rw-r--r--utils/tuning/libtuning/ctt_awb.py378
-rw-r--r--utils/tuning/libtuning/ctt_ccm.py408
-rw-r--r--utils/tuning/libtuning/ctt_colors.py30
-rw-r--r--utils/tuning/libtuning/ctt_ransac.py71
-rw-r--r--utils/tuning/libtuning/generators/yaml_output.py8
-rw-r--r--utils/tuning/libtuning/image.py10
-rw-r--r--utils/tuning/libtuning/libtuning.py28
-rw-r--r--utils/tuning/libtuning/macbeth.py65
-rw-r--r--utils/tuning/libtuning/macbeth_ref.pgm2
-rw-r--r--utils/tuning/libtuning/modules/agc/rkisp1.py2
-rw-r--r--utils/tuning/libtuning/modules/ccm/__init__.py6
-rw-r--r--utils/tuning/libtuning/modules/ccm/ccm.py41
-rw-r--r--utils/tuning/libtuning/modules/ccm/rkisp1.py28
-rw-r--r--utils/tuning/libtuning/modules/lsc/lsc.py5
-rw-r--r--utils/tuning/libtuning/modules/lsc/raspberrypi.py12
-rw-r--r--utils/tuning/libtuning/modules/lsc/rkisp1.py20
-rw-r--r--utils/tuning/libtuning/modules/static.py24
-rw-r--r--utils/tuning/libtuning/parsers/yaml_parser.py9
-rw-r--r--utils/tuning/libtuning/utils.py97
-rw-r--r--utils/tuning/requirements.txt9
-rwxr-xr-xutils/tuning/rkisp1.py55
-rwxr-xr-xutils/update-kernel-headers.sh2
390 files changed, 7711 insertions, 3316 deletions
diff --git a/.clang-format b/.clang-format
index f2b44e87..7fc30f61 100644
--- a/.clang-format
+++ b/.clang-format
@@ -80,7 +80,7 @@ IncludeCategories:
# Headers in <> with an extension. (+system libraries)
- Regex: '<([A-Za-z0-9\-_])+\.h>'
Priority: 2
- # System headers
+ # System headers
- Regex: '<sys/.*>'
Priority: 2
# C++ standard library includes (no extension)
@@ -100,7 +100,7 @@ IncludeCategories:
# IPA Interfaces
- Regex: '<libcamera/ipa/.*\.h>'
Priority: 7
- # libcamera Internal headers in ""
+ # libcamera Internal headers in ""
- Regex: '"libcamera/internal/.*\.h"'
Priority: 8
# Other libraries headers with one group per library (.h or .hpp)
diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile-common.in
index abafcf6c..a70aee43 100644
--- a/Documentation/Doxyfile.in
+++ b/Documentation/Doxyfile-common.in
@@ -22,32 +22,13 @@ CASE_SENSE_NAMES = YES
QUIET = YES
WARN_AS_ERROR = @WARN_AS_ERROR@
-INPUT = "@TOP_SRCDIR@/include/libcamera" \
- "@TOP_SRCDIR@/src/ipa/ipu3" \
- "@TOP_SRCDIR@/src/ipa/libipa" \
- "@TOP_SRCDIR@/src/libcamera" \
- "@TOP_BUILDDIR@/include/libcamera" \
- "@TOP_BUILDDIR@/src/libcamera"
-
FILE_PATTERNS = *.c \
*.cpp \
+ *.dox \
*.h
RECURSIVE = YES
-EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
- @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \
- @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \
- @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \
- @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
- @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
- @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \
- @TOP_SRCDIR@/src/libcamera/pipeline/ \
- @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
- @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
- @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
- @TOP_BUILDDIR@/src/libcamera/proxy/
-
EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
@TOP_BUILDDIR@/include/libcamera/ipa/*_proxy.h \
@TOP_BUILDDIR@/include/libcamera/ipa/ipu3_*.h \
@@ -70,8 +51,6 @@ EXCLUDE_SYMBOLS = libcamera::BoundMethodArgs \
EXCLUDE_SYMLINKS = YES
-HTML_OUTPUT = api-html
-
GENERATE_LATEX = NO
MACRO_EXPANSION = YES
diff --git a/Documentation/Doxyfile-internal.in b/Documentation/Doxyfile-internal.in
new file mode 100644
index 00000000..cf982553
--- /dev/null
+++ b/Documentation/Doxyfile-internal.in
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: CC-BY-SA-4.0
+
+@INCLUDE_PATH = @TOP_BUILDDIR@/Documentation
+@INCLUDE = Doxyfile-common
+
+HIDE_UNDOC_CLASSES = NO
+HIDE_UNDOC_MEMBERS = NO
+HTML_OUTPUT = internal-api-html
+INTERNAL_DOCS = YES
+ENABLED_SECTIONS = internal
+
+INPUT = "@TOP_SRCDIR@/Documentation" \
+ "@TOP_SRCDIR@/include/libcamera" \
+ "@TOP_SRCDIR@/src/ipa/ipu3" \
+ "@TOP_SRCDIR@/src/ipa/libipa" \
+ "@TOP_SRCDIR@/src/libcamera" \
+ "@TOP_BUILDDIR@/include/libcamera" \
+ "@TOP_BUILDDIR@/src/libcamera"
+
+EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
+ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_sysfs.h \
+ @TOP_SRCDIR@/include/libcamera/internal/device_enumerator_udev.h \
+ @TOP_SRCDIR@/include/libcamera/internal/ipc_pipe_unixsocket.h \
+ @TOP_SRCDIR@/src/libcamera/device_enumerator_sysfs.cpp \
+ @TOP_SRCDIR@/src/libcamera/device_enumerator_udev.cpp \
+ @TOP_SRCDIR@/src/libcamera/ipc_pipe_unixsocket.cpp \
+ @TOP_SRCDIR@/src/libcamera/pipeline/ \
+ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
+ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
+ @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
+ @TOP_BUILDDIR@/src/libcamera/proxy/
diff --git a/Documentation/Doxyfile-public.in b/Documentation/Doxyfile-public.in
new file mode 100644
index 00000000..36bb5758
--- /dev/null
+++ b/Documentation/Doxyfile-public.in
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: CC-BY-SA-4.0
+
+@INCLUDE_PATH = @TOP_BUILDDIR@/Documentation
+@INCLUDE = Doxyfile-common
+
+HIDE_UNDOC_CLASSES = YES
+HIDE_UNDOC_MEMBERS = YES
+HTML_OUTPUT = api-html
+INTERNAL_DOCS = NO
+
+INPUT = "@TOP_SRCDIR@/Documentation" \
+ ${inputs}
+
+EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/class.h \
+ @TOP_SRCDIR@/include/libcamera/base/object.h \
+ @TOP_SRCDIR@/include/libcamera/base/span.h \
+ @TOP_SRCDIR@/src/libcamera/base/class.cpp \
+ @TOP_SRCDIR@/src/libcamera/base/object.cpp
+
+PREDEFINED += __DOXYGEN_PUBLIC__
diff --git a/Documentation/api-html/index.rst b/Documentation/api-html/index.rst
index 9e630fc0..2f09833d 100644
--- a/Documentation/api-html/index.rst
+++ b/Documentation/api-html/index.rst
@@ -2,7 +2,7 @@
.. _api:
-API
-===
+API Reference
+=============
:: Placeholder for Doxygen documentation
diff --git a/Documentation/camera-sensor-model.rst b/Documentation/camera-sensor-model.rst
index b66c880a..87a25bf4 100644
--- a/Documentation/camera-sensor-model.rst
+++ b/Documentation/camera-sensor-model.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _camera-sensor-model:
.. todo: Move to Doxygen-generated documentation
diff --git a/Documentation/code-of-conduct.rst b/Documentation/code-of-conduct.rst
index 38b7d7ad..0edd1e99 100644
--- a/Documentation/code-of-conduct.rst
+++ b/Documentation/code-of-conduct.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-4.0
+.. include:: documentation-contents.rst
+
.. _code-of-conduct:
Contributor Covenant Code of Conduct
diff --git a/Documentation/coding-style.rst b/Documentation/coding-style.rst
index 72cb28d2..6ac3a4a0 100644
--- a/Documentation/coding-style.rst
+++ b/Documentation/coding-style.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _coding-style-guidelines:
Coding Style Guidelines
@@ -215,7 +217,7 @@ shall be avoided when possible, but are allowed when required (for instance to
implement factories with auto-registration). They shall not depend on any other
global variable, should run a minimal amount of code in the constructor and
destructor, and code that contains dependencies should be moved to a later
-point in time.
+point in time.
Error Handling
~~~~~~~~~~~~~~
diff --git a/Documentation/conf.py b/Documentation/conf.py
index 7eeea7f3..089f114c 100644
--- a/Documentation/conf.py
+++ b/Documentation/conf.py
@@ -37,8 +37,11 @@ author = u'Kieran Bingham, Jacopo Mondi, Laurent Pinchart, Niklas Söderlund'
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
+ 'sphinx.ext.graphviz'
]
+graphviz_output_format = 'svg'
+
# Add any paths that contain templates here, relative to this directory.
templates_path = []
@@ -61,7 +64,12 @@ language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = [
+ '_build',
+ 'Thumbs.db',
+ '.DS_Store',
+ 'documentation-contents.rst',
+]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
diff --git a/Documentation/docs.rst b/Documentation/docs.rst
deleted file mode 100644
index a6e8a59a..00000000
--- a/Documentation/docs.rst
+++ /dev/null
@@ -1,400 +0,0 @@
-.. SPDX-License-Identifier: CC-BY-SA-4.0
-
-.. contents::
- :local:
-
-*************
-Documentation
-*************
-
-.. toctree::
- :hidden:
-
- API <api-html/index>
-
-API
-===
-
-The libcamera API is extensively documented using Doxygen. The :ref:`API
-nightly build <api>` contains the most up-to-date API documentation, built from
-the latest master branch.
-
-Feature Requirements
-====================
-
-Device enumeration
-------------------
-
-The library shall support enumerating all camera devices available in the
-system, including both fixed cameras and hotpluggable cameras. It shall
-support cameras plugged and unplugged after the initialization of the
-library, and shall offer a mechanism to notify applications of camera plug
-and unplug.
-
-The following types of cameras shall be supported:
-
-* Internal cameras designed for point-and-shoot still image and video
- capture usage, either controlled directly by the CPU, or exposed through
- an internal USB bus as a UVC device.
-
-* External UVC cameras designed for video conferencing usage.
-
-Other types of camera, including analog cameras, depth cameras, thermal
-cameras, external digital picture or movie cameras, are out of scope for
-this project.
-
-A hardware device that includes independent camera sensors, such as front
-and back sensors in a phone, shall be considered as multiple camera devices
-for the purpose of this library.
-
-Independent Camera Devices
---------------------------
-
-When multiple cameras are present in the system and are able to operate
-independently from each other, the library shall expose them as multiple
-camera devices and support parallel operation without any additional usage
-restriction apart from the limitations inherent to the hardware (such as
-memory bandwidth, CPU usage or number of CSI-2 receivers for instance).
-
-Independent processes shall be able to use independent cameras devices
-without interfering with each other. A single camera device shall be
-usable by a single process at a time.
-
-Multiple streams support
-------------------------
-
-The library shall support multiple video streams running in parallel
-for each camera device, within the limits imposed by the system.
-
-Per frame controls
-------------------
-
-The library shall support controlling capture parameters for each stream
-on a per-frame basis, on a best effort basis based on the capabilities of the
-hardware and underlying software stack (including kernel drivers and
-firmware). It shall apply capture parameters to the frame they target, and
-report the value of the parameters that have effectively been used for each
-captured frame.
-
-When a camera device supports multiple streams, the library shall allow both
-control of each stream independently, and control of multiple streams
-together. Streams that are controlled together shall be synchronized. No
-synchronization is required for streams controlled independently.
-
-Capability Enumeration
-----------------------
-
-The library shall expose capabilities of each camera device in a way that
-allows applications to discover those capabilities dynamically. Applications
-shall be allowed to cache capabilities for as long as they are using the
-library. If capabilities can change at runtime, the library shall offer a
-mechanism to notify applications of such changes. Applications shall not
-cache capabilities in long term storage between runs.
-
-Capabilities shall be discovered dynamically at runtime from the device when
-possible, and may come, in part or in full, from platform configuration
-data.
-
-Device Profiles
----------------
-
-The library may define different camera device profiles, each with a minimum
-set of required capabilities. Applications may use those profiles to quickly
-determine the level of features exposed by a device without parsing the full
-list of capabilities. Camera devices may implement additional capabilities
-on top of the minimum required set for the profile they expose.
-
-3A and Image Enhancement Algorithms
------------------------------------
-
-The camera devices shall implement auto exposure, auto gain and auto white
-balance. Camera devices that include a focus lens shall implement auto
-focus. Additional image enhancement algorithms, such as noise reduction or
-video stabilization, may be implemented.
-
-All algorithms may be implemented in hardware or firmware outside of the
-library, or in software in the library. They shall all be controllable by
-applications.
-
-The library shall be architectured to isolate the 3A and image enhancement
-algorithms in a component with a documented API, respectively called the 3A
-component and the 3A API. The 3A API shall be stable, and shall allow both
-open-source and closed-source implementations of the 3A component.
-
-The library may include statically-linked open-source 3A components, and
-shall support dynamically-linked open-source and closed-source 3A
-components.
-
-Closed-source 3A Component Sandboxing
--------------------------------------
-
-For security purposes, it may be desired to run closed-source 3A components
-in a separate process. The 3A API would in such a case be transported over
-IPC. The 3A API shall make it possible to use any IPC mechanism that
-supports passing file descriptors.
-
-The library may implement an IPC mechanism, and shall support third-party
-platform-specific IPC mechanisms through the implementation of a
-platform-specific 3A API wrapper. No modification to the library shall be
-needed to use such third-party IPC mechanisms.
-
-The 3A component shall not directly access any device node on the system.
-Such accesses shall instead be performed through the 3A API. The library
-shall validate all accesses and restrict them to what is absolutely required
-by 3A components.
-
-V4L2 Compatibility Layer
-------------------------
-
-The project shall support traditional V4L2 application through an additional
-libcamera wrapper library. The wrapper library shall trap all accesses to
-camera devices through `LD_PRELOAD`, and route them through libcamera to
-emulate a high-level V4L2 camera device. It shall expose camera device
-features on a best-effort basis, and aim for the level of features
-traditionally available from a UVC camera designed for video conferencing.
-
-Android Camera HAL v3 Compatibility
------------------------------------
-
-The library API shall expose all the features required to implement an
-Android Camera HAL v3 on top of libcamera. Some features of the HAL may be
-omitted as long as they can be implemented separately in the HAL, such as
-JPEG encoding, or YUV reprocessing.
-
-
-Camera Stack
-============
-
-::
-
- a c / +-------------+ +-------------+ +-------------+ +-------------+
- p a | | Native | | Framework | | Native | | Android |
- p t | | V4L2 | | Application | | libcamera | | Camera |
- l i | | Application | | (gstreamer) | | Application | | Framework |
- i o \ +-------------+ +-------------+ +-------------+ +-------------+
- n ^ ^ ^ ^
- | | | |
- l a | | | |
- i d v v | v
- b a / +-------------+ +-------------+ | +-------------+
- c p | | V4L2 | | Camera | | | Android |
- a t | | Compat. | | Framework | | | Camera |
- m a | | | | (gstreamer) | | | HAL |
- e t \ +-------------+ +-------------+ | +-------------+
- r i ^ ^ | ^
- a o | | | |
- n | | | |
- / | ,................................................
- | | ! : Language : !
- l f | | ! : Bindings : !
- i r | | ! : (optional) : !
- b a | | \...............................................'
- c m | | | | |
- a e | | | | |
- m w | v v v v
- e o | +----------------------------------------------------------------+
- r r | | |
- a k | | libcamera |
- | | |
- \ +----------------------------------------------------------------+
- ^ ^ ^
- Userspace | | |
- ------------------------ | ---------------- | ---------------- | ---------------
- Kernel | | |
- v v v
- +-----------+ +-----------+ +-----------+
- | Media | <--> | Video | <--> | V4L2 |
- | Device | | Device | | Subdev |
- +-----------+ +-----------+ +-----------+
-
-The camera stack comprises four software layers. From bottom to top:
-
-* The kernel drivers control the camera hardware and expose a
- low-level interface to userspace through the Linux kernel V4L2
- family of APIs (Media Controller API, V4L2 Video Device API and
- V4L2 Subdev API).
-
-* The libcamera framework is the core part of the stack. It
- handles all control of the camera devices in its core component,
- libcamera, and exposes a native C++ API to upper layers. Optional
- language bindings allow interfacing to libcamera from other
- programming languages.
-
- Those components live in the same source code repository and
- all together constitute the libcamera framework.
-
-* The libcamera adaptation is an umbrella term designating the
- components that interface to libcamera in other frameworks.
- Notable examples are a V4L2 compatibility layer, a gstreamer
- libcamera element, and an Android camera HAL implementation based
- on libcamera.
-
- Those components can live in the libcamera project source code
- in separate repositories, or move to their respective project's
- repository (for instance the gstreamer libcamera element).
-
-* The applications and upper level frameworks are based on the
- libcamera framework or libcamera adaptation, and are outside of
- the scope of the libcamera project.
-
-
-libcamera Architecture
-======================
-
-::
-
- ---------------------------< libcamera Public API >---------------------------
- ^ ^
- | |
- v v
- +-------------+ +-------------------------------------------------+
- | Camera | | Camera Device |
- | Devices | | +---------------------------------------------+ |
- | Manager | | | Device-Agnostic | |
- +-------------+ | | | |
- ^ | | +------------------------+ |
- | | | | ~~~~~~~~~~~~~~~~~~~~~ |
- | | | | { +---------------+ } |
- | | | | } | ////Image//// | { |
- | | | | <-> | /Processing// | } |
- | | | | } | /Algorithms// | { |
- | | | | { +---------------+ } |
- | | | | ~~~~~~~~~~~~~~~~~~~~~ |
- | | | | ======================== |
- | | | | +---------------+ |
- | | | | | //Pipeline/// | |
- | | | | <-> | ///Handler/// | |
- | | | | | ///////////// | |
- | | +--------------------+ +---------------+ |
- | | Device-Specific |
- | +-------------------------------------------------+
- | ^ ^
- | | |
- v v v
- +--------------------------------------------------------------------+
- | Helpers and Support Classes |
- | +-------------+ +-------------+ +-------------+ +-------------+ |
- | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | |
- | | Support | | Allocator | | IPC | | Manager | |
- | +-------------+ +-------------+ +-------------+ +-------------+ |
- | +-------------+ +-------------+ |
- | | Pipeline | | ... | |
- | | Runner | | | |
- | +-------------+ +-------------+ |
- +--------------------------------------------------------------------+
-
- /// Device-Specific Components
- ~~~ Sandboxing
-
-While offering a unified API towards upper layers, and presenting
-itself as a single library, libcamera isn't monolithic. It exposes
-multiple components through its public API, is built around a set of
-separate helpers internally, uses device-specific components and can
-load dynamic plugins.
-
-Camera Devices Manager
- The Camera Devices Manager provides a view of available cameras
- in the system. It performs cold enumeration and runtime camera
- management, and supports a hotplug notification mechanism in its
- public API.
-
- To avoid the cost associated with cold enumeration of all devices
- at application start, and to arbitrate concurrent access to camera
- devices, the Camera Devices Manager could later be split to a
- separate service, possibly with integration in platform-specific
- device management.
-
-Camera Device
- The Camera Device represents a camera device to upper layers. It
- exposes full control of the device through the public API, and is
- thus the highest level object exposed by libcamera.
-
- Camera Device instances are created by the Camera Devices
- Manager. An optional function to create new instances could be exposed
- through the public API to speed up initialization when the upper
- layer knows how to directly address camera devices present in the
- system.
-
-Pipeline Handler
- The Pipeline Handler manages complex pipelines exposed by the kernel drivers
- through the Media Controller and V4L2 APIs. It abstracts pipeline handling to
- hide device-specific details to the rest of the library, and implements both
- pipeline configuration based on stream configuration, and pipeline runtime
- execution and scheduling when needed by the device.
-
- This component is device-specific and is part of the libcamera code base. As
- such it is covered by the same free software license as the rest of libcamera
- and needs to be contributed upstream by device vendors. The Pipeline Handler
- lives in the same process as the rest of the library, and has access to all
- helpers and kernel camera-related devices.
-
-Image Processing Algorithms
- Together with the hardware image processing and hardware statistics
- collection, the Image Processing Algorithms implement 3A (Auto-Exposure,
- Auto-White Balance and Auto-Focus) and other algorithms. They run on the CPU
- and interact with the kernel camera devices to control hardware image
- processing based on the parameters supplied by upper layers, closing the
- control loop of the ISP.
-
- This component is device-specific and is loaded as an external plugin. It can
- be part of the libcamera code base, in which case it is covered by the same
- license, or provided externally as an open-source or closed-source component.
-
- The component is sandboxed and can only interact with libcamera through
- internal APIs specifically marked as such. In particular it will have no
- direct access to kernel camera devices, and all its accesses to image and
- metadata will be mediated by dmabuf instances explicitly passed to the
- component. The component must be prepared to run in a process separate from
- the main libcamera process, and to have a very restricted view of the system,
- including no access to networking APIs and limited access to file systems.
-
- The sandboxing mechanism isn't defined by libcamera. One example
- implementation will be provided as part of the project, and platforms vendors
- will be able to provide their own sandboxing mechanism as a plugin.
-
- libcamera should provide a basic implementation of Image Processing
- Algorithms, to serve as a reference for the internal API. Device vendors are
- expected to provide a full-fledged implementation compatible with their
- Pipeline Handler. One goal of the libcamera project is to create an
- environment in which the community will be able to compete with the
- closed-source vendor binaries and develop a high quality open source
- implementation.
-
-Helpers and Support Classes
- While Pipeline Handlers are device-specific, implementations are expected to
- share code due to usage of identical APIs towards the kernel camera drivers
- and the Image Processing Algorithms. This includes without limitation handling
- of the MC and V4L2 APIs, buffer management through dmabuf, and pipeline
- discovery, configuration and scheduling. Such code will be factored out to
- helpers when applicable.
-
- Other parts of libcamera will also benefit from factoring code out to
- self-contained support classes, even if such code is present only once in the
- code base, in order to keep the source code clean and easy to read. This
- should be the case for instance for plugin management.
-
-
-V4L2 Compatibility Layer
-------------------------
-
-V4L2 compatibility is achieved through a shared library that traps all
-accesses to camera devices and routes them to libcamera to emulate high-level
-V4L2 camera devices. It is injected in a process address space through
-`LD_PRELOAD` and is completely transparent for applications.
-
-The compatibility layer exposes camera device features on a best-effort basis,
-and aims for the level of features traditionally available from a UVC camera
-designed for video conferencing.
-
-
-Android Camera HAL
-------------------
-
-Camera support for Android is achieved through a generic Android
-camera HAL implementation on top of libcamera. The HAL will implement internally
-features required by Android and missing from libcamera, such as JPEG encoding
-support.
-
-The Android camera HAL implementation will initially target the
-LIMITED hardware level, with support for the FULL level then being gradually
-implemented.
diff --git a/Documentation/documentation-contents.rst b/Documentation/documentation-contents.rst
new file mode 100644
index 00000000..5c111849
--- /dev/null
+++ b/Documentation/documentation-contents.rst
@@ -0,0 +1,35 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. container:: documentation-nav
+
+ * **Documentation for Users**
+ * :doc:`Introduction </introduction>`
+ * :doc:`/feature_requirements`
+ * :doc:`/guides/application-developer`
+ * :doc:`/python-bindings`
+ * :doc:`/environment_variables`
+ * :doc:`/api-html/index`
+ * :doc:`/code-of-conduct`
+ * |
+ * **Documentation for Developers**
+ * :doc:`/libcamera_architecture`
+ * :doc:`/guides/pipeline-handler`
+ * :doc:`/guides/ipa`
+ * :doc:`/camera-sensor-model`
+ * :doc:`/guides/tracing`
+ * :doc:`/software-isp-benchmarking`
+ * :doc:`/coding-style`
+ * :doc:`/internal-api-html/index`
+ * |
+ * **Documentation for System Integrators**
+ * :doc:`/lens_driver_requirements`
+ * :doc:`/sensor_driver_requirements`
+
+..
+ The following directive adds the "documentation" class to all of the pages
+ generated by sphinx. This is not relevant in libcamera nor addressed in the
+ theme's CSS, since all of the pages here are documentation. It **is** used
+ to properly format the documentation pages on libcamera.org and so should not
+ be removed.
+
+.. rst-class:: documentation
diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst
index 4e9fbb27..7da9883a 100644
--- a/Documentation/environment_variables.rst
+++ b/Documentation/environment_variables.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
Environment variables
=====================
@@ -37,6 +39,11 @@ LIBCAMERA_IPA_MODULE_PATH
Example value: ``${HOME}/.libcamera/lib:/opt/libcamera/vendor/lib``
+LIBCAMERA_IPA_PROXY_PATH
+ Define custom full path for a proxy worker for a given executable name.
+
+ Example value: ``${HOME}/.libcamera/proxy/worker:/opt/libcamera/vendor/proxy/worker``
+
LIBCAMERA_PIPELINES_MATCH_LIST
Define an ordered list of pipeline names to be used to match the media
devices in the system. The pipeline handler names used to populate the
@@ -50,6 +57,11 @@ LIBCAMERA_RPI_CONFIG_FILE
Example value: ``/usr/local/share/libcamera/pipeline/rpi/vc4/minimal_mem.yaml``
+LIBCAMERA_RPI_TUNING_FILE
+ Define a custom JSON tuning file to use in the Raspberry Pi.
+
+ Example value: ``/usr/local/share/libcamera/ipa/rpi/vc4/custom_sensor.json``
+
Further details
---------------
diff --git a/Documentation/feature_requirements.rst b/Documentation/feature_requirements.rst
new file mode 100644
index 00000000..e6b74a62
--- /dev/null
+++ b/Documentation/feature_requirements.rst
@@ -0,0 +1,150 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. include:: documentation-contents.rst
+
+Feature Requirements
+====================
+
+Device enumeration
+------------------
+
+The library shall support enumerating all camera devices available in the
+system, including both fixed cameras and hotpluggable cameras. It shall
+support cameras plugged and unplugged after the initialization of the
+library, and shall offer a mechanism to notify applications of camera plug
+and unplug.
+
+The following types of cameras shall be supported:
+
+* Internal cameras designed for point-and-shoot still image and video
+ capture usage, either controlled directly by the CPU, or exposed through
+ an internal USB bus as a UVC device.
+
+* External UVC cameras designed for video conferencing usage.
+
+Other types of camera, including analog cameras, depth cameras, thermal
+cameras, external digital picture or movie cameras, are out of scope for
+this project.
+
+A hardware device that includes independent camera sensors, such as front
+and back sensors in a phone, shall be considered as multiple camera devices
+for the purpose of this library.
+
+Independent Camera Devices
+--------------------------
+
+When multiple cameras are present in the system and are able to operate
+independently from each other, the library shall expose them as multiple
+camera devices and support parallel operation without any additional usage
+restriction apart from the limitations inherent to the hardware (such as
+memory bandwidth, CPU usage or number of CSI-2 receivers for instance).
+
+Independent processes shall be able to use independent cameras devices
+without interfering with each other. A single camera device shall be
+usable by a single process at a time.
+
+Multiple streams support
+------------------------
+
+The library shall support multiple video streams running in parallel
+for each camera device, within the limits imposed by the system.
+
+Per frame controls
+------------------
+
+The library shall support controlling capture parameters for each stream
+on a per-frame basis, on a best effort basis based on the capabilities of the
+hardware and underlying software stack (including kernel drivers and
+firmware). It shall apply capture parameters to the frame they target, and
+report the value of the parameters that have effectively been used for each
+captured frame.
+
+When a camera device supports multiple streams, the library shall allow both
+control of each stream independently, and control of multiple streams
+together. Streams that are controlled together shall be synchronized. No
+synchronization is required for streams controlled independently.
+
+Capability Enumeration
+----------------------
+
+The library shall expose capabilities of each camera device in a way that
+allows applications to discover those capabilities dynamically. Applications
+shall be allowed to cache capabilities for as long as they are using the
+library. If capabilities can change at runtime, the library shall offer a
+mechanism to notify applications of such changes. Applications shall not
+cache capabilities in long term storage between runs.
+
+Capabilities shall be discovered dynamically at runtime from the device when
+possible, and may come, in part or in full, from platform configuration
+data.
+
+Device Profiles
+---------------
+
+The library may define different camera device profiles, each with a minimum
+set of required capabilities. Applications may use those profiles to quickly
+determine the level of features exposed by a device without parsing the full
+list of capabilities. Camera devices may implement additional capabilities
+on top of the minimum required set for the profile they expose.
+
+3A and Image Enhancement Algorithms
+-----------------------------------
+
+The library shall provide a basic implementation of Image Processing Algorithms
+to serve as a reference for the internal API. This shall including auto exposure
+and gain and auto white balance. Camera devices that include a focus lens shall
+implement auto focus. Additional image enhancement algorithms, such as noise
+reduction or video stabilization, may be implemented. Device vendors are
+expected to provide a fully-fledged implementation compatible with their
+Pipeline Handler. One goal of the libcamera project is to create an environment
+in which the community will be able to compete with the closed-source vendor
+biaries and develop a high quality open source implementation.
+
+All algorithms may be implemented in hardware or firmware outside of the
+library, or in software in the library. They shall all be controllable by
+applications.
+
+The library shall be architectured to isolate the 3A and image enhancement
+algorithms in a component with a documented API, respectively called the 3A
+component and the 3A API. The 3A API shall be stable, and shall allow both
+open-source and closed-source implementations of the 3A component.
+
+The library may include statically-linked open-source 3A components, and
+shall support dynamically-linked open-source and closed-source 3A
+components.
+
+Closed-source 3A Component Sandboxing
+-------------------------------------
+
+For security purposes, it may be desired to run closed-source 3A components
+in a separate process. The 3A API would in such a case be transported over
+IPC. The 3A API shall make it possible to use any IPC mechanism that
+supports passing file descriptors.
+
+The library may implement an IPC mechanism, and shall support third-party
+platform-specific IPC mechanisms through the implementation of a
+platform-specific 3A API wrapper. No modification to the library shall be
+needed to use such third-party IPC mechanisms.
+
+The 3A component shall not directly access any device node on the system.
+Such accesses shall instead be performed through the 3A API. The library
+shall validate all accesses and restrict them to what is absolutely required
+by 3A components.
+
+V4L2 Compatibility Layer
+------------------------
+
+The project shall support traditional V4L2 application through an additional
+libcamera wrapper library. The wrapper library shall trap all accesses to
+camera devices through `LD_PRELOAD`, and route them through libcamera to
+emulate a high-level V4L2 camera device. It shall expose camera device
+features on a best-effort basis, and aim for the level of features
+traditionally available from a UVC camera designed for video conferencing.
+
+Android Camera HAL v3 Compatibility
+-----------------------------------
+
+The library API shall expose all the features required to implement an
+Android Camera HAL v3 on top of libcamera. Some features of the HAL may be
+omitted as long as they can be implemented separately in the HAL, such as
+JPEG encoding, or YUV reprocessing.
diff --git a/Documentation/gen-doxyfile.py b/Documentation/gen-doxyfile.py
new file mode 100755
index 00000000..c265bc2f
--- /dev/null
+++ b/Documentation/gen-doxyfile.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2024, Google Inc.
+#
+# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+#
+# Generate Doxyfile from a template
+
+import argparse
+import os
+import string
+import sys
+
+
+def fill_template(template, data):
+
+ template = open(template, 'rb').read()
+ template = template.decode('utf-8')
+ template = string.Template(template)
+
+ return template.substitute(data)
+
+
+def main(argv):
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-o', dest='output', metavar='file',
+ type=argparse.FileType('w', encoding='utf-8'),
+ default=sys.stdout,
+ help='Output file name (default: standard output)')
+ parser.add_argument('template', metavar='doxyfile.tmpl', type=str,
+ help='Doxyfile template')
+ parser.add_argument('inputs', type=str, nargs='*',
+ help='Input files')
+
+ args = parser.parse_args(argv[1:])
+
+ inputs = [f'"{os.path.realpath(input)}"' for input in args.inputs]
+ data = fill_template(args.template, {'inputs': (' \\\n' + ' ' * 25).join(inputs)})
+ args.output.write(data)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/Documentation/getting-started.rst b/Documentation/getting-started.rst
index 987f43f7..63b050eb 100644
--- a/Documentation/getting-started.rst
+++ b/Documentation/getting-started.rst
@@ -1,4 +1,5 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+
.. Getting started information is defined in the project README file.
.. include:: ../README.rst
:start-after: .. section-begin-getting-started
diff --git a/Documentation/guides/application-developer.rst b/Documentation/guides/application-developer.rst
index 92e2a373..1ea8c40f 100644
--- a/Documentation/guides/application-developer.rst
+++ b/Documentation/guides/application-developer.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: ../documentation-contents.rst
+
Using libcamera in a C++ application
====================================
diff --git a/Documentation/guides/introduction.rst b/Documentation/guides/introduction.rst
deleted file mode 100644
index 700ec2d3..00000000
--- a/Documentation/guides/introduction.rst
+++ /dev/null
@@ -1,319 +0,0 @@
-.. SPDX-License-Identifier: CC-BY-SA-4.0
-
-Developers guide to libcamera
-=============================
-
-The Linux kernel handles multimedia devices through the 'Linux media' subsystem
-and provides a set of APIs (application programming interfaces) known
-collectively as V4L2 (`Video for Linux 2`_) and the `Media Controller`_ API
-which provide an interface to interact and control media devices.
-
-Included in this subsystem are drivers for camera sensors, CSI2 (Camera
-Serial Interface) receivers, and ISPs (Image Signal Processors)
-
-The usage of these drivers to provide a functioning camera stack is a
-responsibility that lies in userspace which is commonly implemented separately
-by vendors without a common architecture or API for application developers.
-
-libcamera provides a complete camera stack for Linux based systems to abstract
-functionality desired by camera application developers and process the
-configuration of hardware and image control algorithms required to obtain
-desirable results from the camera.
-
-.. _Video for Linux 2: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/v4l2.html
-.. _Media Controller: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/mediactl/media-controller.html
-
-
-In this developers guide, we will explore the `Camera Stack`_ and how it is
-can be visualised at a high level, and explore the internal `Architecture`_ of
-the libcamera library with its components. The current `Platform Support`_ is
-detailed, as well as an overview of the `Licensing`_ requirements of the
-project.
-
-This introduction is followed by a walkthrough tutorial to newcomers wishing to
-support a new platform with the `Pipeline Handler Writers Guide`_ and for those
-looking to make use of the libcamera native API an `Application Writers Guide`_
-provides a tutorial of the key APIs exposed by libcamera.
-
-.. _Pipeline Handler Writers Guide: pipeline-handler.html
-.. _Application Writers Guide: application-developer.html
-
-.. TODO: Correctly link to the other articles of the guide
-
-Camera Stack
-------------
-
-The libcamera library is implemented in userspace, and makes use of underlying
-kernel drivers that directly interact with hardware.
-
-Applications can make use of libcamera through the native `libcamera API`_'s or
-through an adaptation layer integrating libcamera into a larger framework.
-
-.. _libcamera API: https://www.libcamera.org/api-html/index.html
-
-::
-
- Application Layer
- / +--------------+ +--------------+ +--------------+ +--------------+
- | | Native | | Framework | | Native | | Android |
- | | V4L2 | | Application | | libcamera | | Camera |
- | | Application | | (gstreamer) | | Application | | Framework |
- \ +--------------+ +--------------+ +--------------+ +--------------+
-
- ^ ^ ^ ^
- | | | |
- | | | |
- v v | v
- Adaptation Layer |
- / +--------------+ +--------------+ | +--------------+
- | | V4L2 | | gstreamer | | | Android |
- | | Compatibility| | element | | | Camera |
- | | (preload) | |(libcamerasrc)| | | HAL |
- \ +--------------+ +--------------+ | +--------------+
- |
- ^ ^ | ^
- | | | |
- | | | |
- v v v v
- libcamera Framework
- / +--------------------------------------------------------------------+
- | | |
- | | libcamera |
- | | |
- \ +--------------------------------------------------------------------+
-
- ^ ^ ^
- Userspace | | |
- --------------------- | ---------------- | ---------------- | ---------------
- Kernel | | |
- v v v
-
- +-----------+ +-----------+ +-----------+
- | Media | <--> | Video | <--> | V4L2 |
- | Device | | Device | | Subdev |
- +-----------+ +-----------+ +-----------+
-
-The camera stack comprises of four software layers. From bottom to top:
-
-* The kernel drivers control the camera hardware and expose a low-level
- interface to userspace through the Linux kernel V4L2 family of APIs
- (Media Controller API, V4L2 Video Device API and V4L2 Subdev API).
-
-* The libcamera framework is the core part of the stack. It handles all control
- of the camera devices in its core component, libcamera, and exposes a native
- C++ API to upper layers.
-
-* The libcamera adaptation layer is an umbrella term designating the components
- that interface to libcamera in other frameworks. Notable examples are the V4L2
- compatibility layer, the gstreamer libcamera element, and the Android camera
- HAL implementation based on libcamera which are provided as a part of the
- libcamera project.
-
-* The applications and upper level frameworks are based on the libcamera
- framework or libcamera adaptation, and are outside of the scope of the
- libcamera project, however example native applications (cam, qcam) are
- provided for testing.
-
-
-V4L2 Compatibility Layer
- V4L2 compatibility is achieved through a shared library that traps all
- accesses to camera devices and routes them to libcamera to emulate high-level
- V4L2 camera devices. It is injected in a process address space through
- ``LD_PRELOAD`` and is completely transparent for applications.
-
- The compatibility layer exposes camera device features on a best-effort basis,
- and aims for the level of features traditionally available from a UVC camera
- designed for video conferencing.
-
-Android Camera HAL
- Camera support for Android is achieved through a generic Android camera HAL
- implementation on top of libcamera. The HAL implements features required by
- Android and out of scope from libcamera, such as JPEG encoding support.
-
- This component is used to provide support for ChromeOS platforms
-
-GStreamer element (gstlibcamerasrc)
- A `GStreamer element`_ is provided to allow capture from libcamera supported
- devices through GStreamer pipelines, and connect to other elements for further
- processing.
-
- Development of this element is ongoing and is limited to a single stream.
-
-Native libcamera API
- Applications can make use of the libcamera API directly using the C++
- API. An example application and walkthrough using the libcamera API can be
- followed in the `Application Writers Guide`_
-
-.. _GStreamer element: https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html
-
-Architecture
-------------
-
-While offering a unified API towards upper layers, and presenting itself as a
-single library, libcamera isn't monolithic. It exposes multiple components
-through its public API and is built around a set of separate helpers internally.
-Hardware abstractions are handled through the use of device-specific components
-where required and dynamically loadable plugins are used to separate image
-processing algorithms from the core libcamera codebase.
-
-::
-
- --------------------------< libcamera Public API >---------------------------
- ^ ^
- | |
- v v
- +-------------+ +---------------------------------------------------+
- | Camera | | Camera Device |
- | Manager | | +-----------------------------------------------+ |
- +-------------+ | | Device-Agnostic | |
- ^ | | | |
- | | | +--------------------------+ |
- | | | | ~~~~~~~~~~~~~~~~~~~~~~~ |
- | | | | { +-----------------+ } |
- | | | | } | //// Image //// | { |
- | | | | <-> | / Processing // | } |
- | | | | } | / Algorithms // | { |
- | | | | { +-----------------+ } |
- | | | | ~~~~~~~~~~~~~~~~~~~~~~~ |
- | | | | ========================== |
- | | | | +-----------------+ |
- | | | | | // Pipeline /// | |
- | | | | <-> | /// Handler /// | |
- | | | | | /////////////// | |
- | | +--------------------+ +-----------------+ |
- | | Device-Specific |
- | +---------------------------------------------------+
- | ^ ^
- | | |
- v v v
- +--------------------------------------------------------------------+
- | Helpers and Support Classes |
- | +-------------+ +-------------+ +-------------+ +-------------+ |
- | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | |
- | | Support | | Allocator | | IPC | | Manager | |
- | +-------------+ +-------------+ +-------------+ +-------------+ |
- | +-------------+ +-------------+ |
- | | Pipeline | | ... | |
- | | Runner | | | |
- | +-------------+ +-------------+ |
- +--------------------------------------------------------------------+
-
- /// Device-Specific Components
- ~~~ Sandboxing
-
-
-Camera Manager
- The Camera Manager enumerates cameras and instantiates Pipeline Handlers to
- manage each Camera that libcamera supports. The Camera Manager supports
- hotplug detection and notification events when supported by the underlying
- kernel devices.
-
- There is only ever one instance of the Camera Manager running per application.
- Each application's instance of the Camera Manager ensures that only a single
- application can take control of a camera device at once.
-
- Read the `Camera Manager API`_ documentation for more details.
-
-.. _Camera Manager API: https://libcamera.org/api-html/classlibcamera_1_1CameraManager.html
-
-Camera Device
- The Camera class represents a single item of camera hardware that is capable
- of producing one or more image streams, and provides the API to interact with
- the underlying device.
-
- If a system has multiple instances of the same hardware attached, each has its
- own instance of the camera class.
-
- The API exposes full control of the device to upper layers of libcamera through
- the public API, making it the highest level object libcamera exposes, and the
- object that all other API operations interact with from configuration to
- capture.
-
- Read the `Camera API`_ documentation for more details.
-
-.. _Camera API: https://libcamera.org/api-html/classlibcamera_1_1Camera.html
-
-Pipeline Handler
- The Pipeline Handler manages the complex pipelines exposed by the kernel
- drivers through the Media Controller and V4L2 APIs. It abstracts pipeline
- handling to hide device-specific details from the rest of the library, and
- implements both pipeline configuration based on stream configuration, and
- pipeline runtime execution and scheduling when needed by the device.
-
- The Pipeline Handler lives in the same process as the rest of the library, and
- has access to all helpers and kernel camera-related devices.
-
- Hardware abstraction is handled by device specific Pipeline Handlers which are
- derived from the Pipeline Handler base class allowing commonality to be shared
- among the implementations.
-
- Derived pipeline handlers create Camera device instances based on the devices
- they detect and support on the running system, and are responsible for
- managing the interactions with a camera device.
-
- More details can be found in the `PipelineHandler API`_ documentation, and the
- `Pipeline Handler Writers Guide`_.
-
-.. _PipelineHandler API: https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html
-
-Image Processing Algorithms
- An image processing algorithm (IPA) component is a loadable plugin that
- implements 3A (Auto-Exposure, Auto-White Balance, and Auto-Focus) and other
- algorithms.
-
- The algorithms run on the CPU and interact with the camera devices through the
- Pipeline Handler to control hardware image processing based on the parameters
- supplied by upper layers, maintaining state and closing the control loop
- of the ISP.
-
- The component is sandboxed and can only interact with libcamera through the
- API provided by the Pipeline Handler and an IPA has no direct access to kernel
- camera devices.
-
- Open source IPA modules built with libcamera can be run in the same process
- space as libcamera, however external IPA modules are run in a separate process
- from the main libcamera process. IPA modules have a restricted view of the
- system, including no access to networking APIs and limited access to file
- systems.
-
- IPA modules are only required for platforms and devices with an ISP controlled
- by the host CPU. Camera sensors which have an integrated ISP are not
- controlled through the IPA module.
-
-Platform Support
-----------------
-
-The library currently supports the following hardware platforms specifically
-with dedicated pipeline handlers:
-
- - Intel IPU3 (ipu3)
- - Rockchip RK3399 (rkisp1)
- - RaspberryPi 3 and 4 (rpi/vc4)
-
-Furthermore, generic platform support is provided for the following:
-
- - USB video device class cameras (uvcvideo)
- - iMX7, Allwinner Sun6i (simple)
- - Virtual media controller driver for test use cases (vimc)
-
-Licensing
----------
-
-The libcamera core, is covered by the `LGPL-2.1-or-later`_ license. Pipeline
-Handlers are a part of the libcamera code base and need to be contributed
-upstream by device vendors. IPA modules included in libcamera are covered by a
-free software license, however third-parties may develop IPA modules outside of
-libcamera and distribute them under a closed-source license, provided they do
-not include source code from the libcamera project.
-
-The libcamera project itself contains multiple libraries, applications and
-utilities. Licenses are expressed through SPDX tags in text-based files that
-support comments, and through the .reuse/dep5 file otherwise. A copy of all
-licenses are stored in the LICENSES directory, and a full summary of the
-licensing used throughout the project can be found in the COPYING.rst document.
-
-Applications which link dynamically against libcamera and use only the public
-API are an independent work of the authors and have no license restrictions
-imposed upon them from libcamera.
-
-.. _LGPL-2.1-or-later: https://spdx.org/licenses/LGPL-2.1-or-later.html
diff --git a/Documentation/guides/ipa.rst b/Documentation/guides/ipa.rst
index 25deadef..cd640563 100644
--- a/Documentation/guides/ipa.rst
+++ b/Documentation/guides/ipa.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: ../documentation-contents.rst
+
IPA Writer's Guide
==================
diff --git a/Documentation/guides/pipeline-handler.rst b/Documentation/guides/pipeline-handler.rst
index 7e45cdb8..26aea433 100644
--- a/Documentation/guides/pipeline-handler.rst
+++ b/Documentation/guides/pipeline-handler.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: ../documentation-contents.rst
+
Pipeline Handler Writers Guide
==============================
@@ -151,13 +153,14 @@ integrates with the libcamera build system, and a *vivid.cpp* file that matches
the name of the pipeline.
In the *meson.build* file, add the *vivid.cpp* file as a build source for
-libcamera by adding it to the global meson ``libcamera_sources`` variable:
+libcamera by adding it to the global meson ``libcamera_internal_sources``
+variable:
.. code-block:: none
# SPDX-License-Identifier: CC0-1.0
- libcamera_sources += files([
+ libcamera_internal_sources += files([
'vivid.cpp',
])
diff --git a/Documentation/guides/tracing.rst b/Documentation/guides/tracing.rst
index ae960d85..537dce50 100644
--- a/Documentation/guides/tracing.rst
+++ b/Documentation/guides/tracing.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: ../documentation-contents.rst
+
Tracing Guide
=============
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 5442ae75..bea40660 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -1,27 +1,29 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
-.. Front page matter is defined in the project README file.
-.. include:: ../README.rst
- :start-after: .. section-begin-libcamera
- :end-before: .. section-end-libcamera
+.. include:: introduction.rst
.. toctree::
:maxdepth: 1
:caption: Contents:
Home <self>
- Docs <docs>
Contribute <contributing>
Getting Started <getting-started>
- Developer Guide <guides/introduction>
Application Writer's Guide <guides/application-developer>
- Pipeline Handler Writer's Guide <guides/pipeline-handler>
- IPA Writer's guide <guides/ipa>
- Tracing guide <guides/tracing>
+ Camera Sensor Model <camera-sensor-model>
Environment variables <environment_variables>
- Sensor driver requirements <sensor_driver_requirements>
+ Feature Requirements <feature_requirements>
+ IPA Writer's guide <guides/ipa>
Lens driver requirements <lens_driver_requirements>
+ libcamera Architecture <libcamera_architecture>
+ Pipeline Handler Writer's Guide <guides/pipeline-handler>
Python Bindings <python-bindings>
- Camera Sensor Model <camera-sensor-model>
+ Sensor driver requirements <sensor_driver_requirements>
SoftwareISP Benchmarking <software-isp-benchmarking>
+ Tracing guide <guides/tracing>
+
+.. toctree::
+ :hidden:
+
+ introduction \ No newline at end of file
diff --git a/Documentation/internal-api-html/index.rst b/Documentation/internal-api-html/index.rst
new file mode 100644
index 00000000..43768648
--- /dev/null
+++ b/Documentation/internal-api-html/index.rst
@@ -0,0 +1,8 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. _internal-api:
+
+Internal API Reference
+======================
+
+:: Placeholder for Doxygen documentation
diff --git a/Documentation/introduction.rst b/Documentation/introduction.rst
new file mode 100644
index 00000000..82aa11a3
--- /dev/null
+++ b/Documentation/introduction.rst
@@ -0,0 +1,224 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. include:: documentation-contents.rst
+
+************
+Introduction
+************
+
+.. toctree::
+ :hidden:
+
+ API <api-html/index>
+ Internal API <internal-api-html/index>
+
+What is libcamera?
+==================
+
+libcamera is an open source complex camera support library for Linux, Android
+and ChromeOS. The library interfaces with Linux kernel device drivers and
+provides an intuitive API to developers in order to simplify the complexity
+involved in capturing images from complex cameras on Linux systems.
+
+What is a "complex camera"?
+===========================
+
+A modern "camera" tends to infact be several different pieces of hardware which
+must all be controlled together in order to produce and capture images of
+appropriate quality. A hardware pipeline typically consists of a camera sensor
+that captures raw frames and transmits them on a bus, a receiver that decodes
+the bus signals, and an image signal processor that processes raw frames to
+produce usable images in a standard format. The Linux kernel handles these
+multimedia devices through the 'Linux media' subsystem and provides a set of
+application programming interfaces known collectively as the
+V4L2 (`Video for Linux 2`_) and the `Media Controller`_ APIs, which provide an
+interface to interact and control media devices.
+
+.. _Video for Linux 2: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/v4l/v4l2.html
+.. _Media Controller: https://www.linuxtv.org/downloads/v4l-dvb-apis-new/userspace-api/mediactl/media-controller.html
+
+Included in this subsystem are drivers for camera sensors, CSI2 (Camera
+Serial Interface) receivers, and ISPs (Image Signal Processors).
+
+The usage of these drivers to provide a functioning camera stack is a
+responsibility that lies in userspace, and is commonly implemented separately
+by vendors without a common architecture or API for application developers. This
+adds a lot of complexity to the task, particularly when considering that the
+differences in hardware pipelines and their representation in the kernel's APIs
+often necessitate bespoke handling.
+
+What is libcamera for?
+======================
+
+libcamera provides a complete camera stack for Linux-based systems to abstract
+the configuration of hardware and image control algorithms required to obtain
+desirable results from the camera through the kernel's APIs, reducing those
+operations to a simple and consistent method for developers. In short instead of
+having to deal with this:
+
+.. graphviz:: mali-c55.dot
+
+you can instead simply deal with:
+
+.. code-block:: python
+
+ >>> import libcamera as lc
+ >>> camera_manager = lc.CameraManager.singleton()
+ [0:15:59.582029920] [504] INFO Camera camera_manager.cpp:313 libcamera v0.3.0+182-01e57380
+ >>> for camera in camera_manager.cameras:
+ ... print(f' - {camera.id}')
+ ...
+ - mali-c55 tpg
+ - imx415 1-001a
+
+The library handles the rest for you. These documentary pages give more
+information on the internal workings of libcamera (and the kernel camera stack
+that lies behind it) as well as guidance on using libcamera in an application or
+extending the library with support for your hardware (through the pipeline
+handler and IPA module writer's guides).
+
+How should I use it?
+====================
+
+There are a few ways you might want to use libcamera, depending on your
+application. It's always possible to use the library directly, and you can find
+detailed information on how to do so in the
+:doc:`application writer's guide <guides/application-developer>`.
+
+It is often more appropriate to use one of the frameworks with libcamera
+support. For example an application powering an embedded media device
+incorporating capture, encoding and streaming of both video and audio would
+benefit from using `GStreamer`_, for which libcamera provides a plugin.
+Similarly an application for user-facing devices like a laptop would likely
+benefit accessing cameras through the XDG camera portal and `pipewire`_, which
+brings the advantages of resource sharing (multiple applications accessing the
+stream at the same time) and access control.
+
+.. _GStreamer: https://gstreamer.freedesktop.org/
+.. _pipewire: https://pipewire.org/
+
+Camera Stack
+============
+
+::
+
+ a c / +-------------+ +-------------+ +-------------+ +-------------+
+ p a | | Native | | Framework | | Native | | Android |
+ p t | | V4L2 | | Application | | libcamera | | Camera |
+ l i | | Application | | (gstreamer) | | Application | | Framework |
+ i o \ +-------------+ +-------------+ +-------------+ +-------------+
+ n ^ ^ ^ ^
+ | | | |
+ l a | | | |
+ i d v v | v
+ b a / +-------------+ +-------------+ | +-------------+
+ c p | | V4L2 | | Camera | | | Android |
+ a t | | Compat. | | Framework | | | Camera |
+ m a | | | | (gstreamer) | | | HAL |
+ e t \ +-------------+ +-------------+ | +-------------+
+ r i ^ ^ | ^
+ a o | | | |
+ n | | | |
+ / | ,................................................
+ | | ! : Language : !
+ l f | | ! : Bindings : !
+ i r | | ! : (optional) : !
+ b a | | \...............................................'
+ c m | | | | |
+ a e | | | | |
+ m w | v v v v
+ e o | +----------------------------------------------------------------+
+ r r | | |
+ a k | | libcamera |
+ | | |
+ \ +----------------------------------------------------------------+
+ ^ ^ ^
+ Userspace | | |
+ ------------------------ | ---------------- | ---------------- | ---------------
+ Kernel | | |
+ v v v
+ +-----------+ +-----------+ +-----------+
+ | Media | <--> | Video | <--> | V4L2 |
+ | Device | | Device | | Subdev |
+ +-----------+ +-----------+ +-----------+
+
+The camera stack comprises four software layers. From bottom to top:
+
+* The kernel drivers control the camera hardware and expose a
+ low-level interface to userspace through the Linux kernel V4L2
+ family of APIs (Media Controller API, V4L2 Video Device API and
+ V4L2 Subdev API).
+
+* The libcamera framework is the core part of the stack. It
+ handles all control of the camera devices in its core component,
+ libcamera, and exposes a native C++ API to upper layers. Optional
+ language bindings allow interfacing to libcamera from other
+ programming languages.
+
+ Those components live in the same source code repository and
+ all together constitute the libcamera framework.
+
+* The libcamera adaptation is an umbrella term designating the
+ components that interface to libcamera in other frameworks.
+ Notable examples are a V4L2 compatibility layer, a gstreamer
+ libcamera element, and an Android camera HAL implementation based
+ on libcamera.
+
+ Those components can live in the libcamera project source code
+ in separate repositories, or move to their respective project's
+ repository (for instance the gstreamer libcamera element).
+
+* The applications and upper level frameworks are based on the
+ libcamera framework or libcamera adaptation, and are outside of
+ the scope of the libcamera project.
+
+V4L2 Compatibility Layer
+ V4L2 compatibility is achieved through a shared library that traps all
+ accesses to camera devices and routes them to libcamera to emulate high-level
+ V4L2 camera devices. It is injected in a process address space through
+ ``LD_PRELOAD`` and is completely transparent for applications.
+
+ The compatibility layer exposes camera device features on a best-effort basis,
+ and aims for the level of features traditionally available from a UVC camera
+ designed for video conferencing.
+
+Android Camera HAL
+ Camera support for Android is achieved through a generic Android camera HAL
+ implementation on top of libcamera. The HAL implements features required by
+ Android and out of scope from libcamera, such as JPEG encoding support.
+
+ This component is used to provide support for ChromeOS platforms.
+
+GStreamer element (gstlibcamerasrc)
+ A `GStreamer element`_ is provided to allow capture from libcamera supported
+ devices through GStreamer pipelines, and connect to other elements for further
+ processing.
+
+Native libcamera API
+ Applications can make use of the libcamera API directly using the C++
+ API. An example application and walkthrough using the libcamera API can be
+ followed in the :doc:`Application writer's guide </guides/application-developer>`
+
+.. _GStreamer element: https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html
+
+Licensing
+=========
+
+The libcamera core is covered by the `LGPL-2.1-or-later`_ license. Pipeline
+Handlers are a part of the libcamera code base and need to be contributed
+upstream by device vendors. IPA modules included in libcamera are covered by a
+free software license, however third-parties may develop IPA modules outside of
+libcamera and distribute them under a closed-source license, provided they do
+not include source code from the libcamera project.
+
+The libcamera project itself contains multiple libraries, applications and
+utilities. Licenses are expressed through SPDX tags in text-based files that
+support comments, and through the .reuse/dep5 file otherwise. A copy of all
+licenses are stored in the LICENSES directory, and a full summary of the
+licensing used throughout the project can be found in the COPYING.rst document.
+
+Applications which link dynamically against libcamera and use only the public
+API are an independent work of the authors and have no license restrictions
+imposed upon them from libcamera.
+
+.. _LGPL-2.1-or-later: https://spdx.org/licenses/LGPL-2.1-or-later.html
diff --git a/Documentation/lens_driver_requirements.rst b/Documentation/lens_driver_requirements.rst
index b96e502d..85fef76f 100644
--- a/Documentation/lens_driver_requirements.rst
+++ b/Documentation/lens_driver_requirements.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _lens-driver-requirements:
Lens Driver Requirements
diff --git a/Documentation/libcamera_architecture.rst b/Documentation/libcamera_architecture.rst
new file mode 100644
index 00000000..abbb0d17
--- /dev/null
+++ b/Documentation/libcamera_architecture.rst
@@ -0,0 +1,168 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. include:: documentation-contents.rst
+
+libcamera Architecture
+======================
+
+While offering a unified API towards upper layers, and presenting itself as a
+single library, libcamera isn't monolithic. It exposes multiple components
+through its public API and is built around a set of separate helpers internally.
+Hardware abstractions are handled through the use of device-specific components
+where required and dynamically loadable plugins are used to separate image
+processing algorithms from the core libcamera codebase.
+
+::
+
+ --------------------------< libcamera Public API >---------------------------
+ ^ ^
+ | |
+ v v
+ +-------------+ +---------------------------------------------------+
+ | Camera | | Camera Device |
+ | Manager | | +-----------------------------------------------+ |
+ +-------------+ | | Device-Agnostic | |
+ ^ | | | |
+ | | | +--------------------------+ |
+ | | | | ~~~~~~~~~~~~~~~~~~~~~~~ |
+ | | | | { +-----------------+ } |
+ | | | | } | //// Image //// | { |
+ | | | | <-> | / Processing // | } |
+ | | | | } | / Algorithms // | { |
+ | | | | { +-----------------+ } |
+ | | | | ~~~~~~~~~~~~~~~~~~~~~~~ |
+ | | | | ========================== |
+ | | | | +-----------------+ |
+ | | | | | // Pipeline /// | |
+ | | | | <-> | /// Handler /// | |
+ | | | | | /////////////// | |
+ | | +--------------------+ +-----------------+ |
+ | | Device-Specific |
+ | +---------------------------------------------------+
+ | ^ ^
+ | | |
+ v v v
+ +--------------------------------------------------------------------+
+ | Helpers and Support Classes |
+ | +-------------+ +-------------+ +-------------+ +-------------+ |
+ | | MC & V4L2 | | Buffers | | Sandboxing | | Plugins | |
+ | | Support | | Allocator | | IPC | | Manager | |
+ | +-------------+ +-------------+ +-------------+ +-------------+ |
+ | +-------------+ +-------------+ |
+ | | Pipeline | | ... | |
+ | | Runner | | | |
+ | +-------------+ +-------------+ |
+ +--------------------------------------------------------------------+
+
+ /// Device-Specific Components
+ ~~~ Sandboxing
+
+
+Camera Manager
+ The Camera Manager enumerates cameras and instantiates Pipeline Handlers to
+ manage each Camera that libcamera supports. The Camera Manager supports
+ hotplug detection and notification events when supported by the underlying
+ kernel devices.
+
+ There is only ever one instance of the Camera Manager running per application.
+ Each application's instance of the Camera Manager ensures that only a single
+ application can take control of a camera device at once.
+
+ Read the `Camera Manager API`_ documentation for more details.
+
+.. _Camera Manager API: https://libcamera.org/api-html/classlibcamera_1_1CameraManager.html
+
+Camera Device
+ The Camera class represents a single item of camera hardware that is capable
+ of producing one or more image streams, and provides the API to interact with
+ the underlying device.
+
+ If a system has multiple instances of the same hardware attached, each has its
+ own instance of the camera class.
+
+ The API exposes full control of the device to upper layers of libcamera through
+ the public API, making it the highest level object libcamera exposes, and the
+ object that all other API operations interact with from configuration to
+ capture.
+
+ Read the `Camera API`_ documentation for more details.
+
+.. _Camera API: https://libcamera.org/api-html/classlibcamera_1_1Camera.html
+
+Pipeline Handler
+ The Pipeline Handler manages the complex pipelines exposed by the kernel
+ drivers through the Media Controller and V4L2 APIs. It abstracts pipeline
+ handling to hide device-specific details from the rest of the library, and
+ implements both pipeline configuration based on stream configuration, and
+ pipeline runtime execution and scheduling when needed by the device.
+
+ The Pipeline Handler lives in the same process as the rest of the library, and
+ has access to all helpers and kernel camera-related devices.
+
+ Hardware abstraction is handled by device specific Pipeline Handlers which are
+ derived from the Pipeline Handler base class allowing commonality to be shared
+ among the implementations.
+
+ Derived pipeline handlers create Camera device instances based on the devices
+ they detect and support on the running system, and are responsible for
+ managing the interactions with a camera device.
+
+ More details can be found in the `PipelineHandler API`_ documentation, and the
+ :doc:`Pipeline Handler Writers Guide <guides/pipeline-handler>`.
+
+.. _PipelineHandler API: https://libcamera.org/api-html/classlibcamera_1_1PipelineHandler.html
+
+Image Processing Algorithms
+ Together with the hardware image processing and hardware statistics
+ collection, the Image Processing Algorithms (IPA) implement 3A (Auto-Exposure,
+ Auto-White Balance and Auto-Focus) and other algorithms. They run on the CPU
+ and control hardware image processing based on the parameters supplied by
+ upper layers, closing the control loop of the ISP.
+
+ IPAs are loaded as external plugins named IPA Modules. IPA Modules can be part
+ of the libcamera code base or provided externally by camera vendors as
+ open-source or closed-source components.
+
+ Open source IPA Modules built with libcamera are run in the same process space
+ as libcamera. External IPA Modules are run in a separate sandboxed process. In
+ either case, they can only interact with libcamera through the API provided by
+ the Pipeline Handler. They have a restricted view of the system, with no direct
+ access to kernel camera devices, no access to networking APIs, and limited
+ access to file systems. All their accesses to image and metadata are mediated
+ by dmabuf instances explicitly passed by the Pipeline Handler to the IPA
+ Module.
+
+ IPA Modules are only required for platforms and devices with an ISP controlled
+ by the host CPU. Camera sensors which have an integrated ISP are not
+ controlled through the IPA Module.
+
+Helpers and Support Classes
+ While Pipeline Handlers are device-specific, implementations are expected to
+ share code due to usage of identical APIs towards the kernel camera drivers
+ and the Image Processing Algorithms. This includes without limitation handling
+ of the MC and V4L2 APIs, buffer management through dmabuf, and pipeline
+ discovery, configuration and scheduling. Such code will be factored out to
+ helpers when applicable.
+
+ Other parts of libcamera will also benefit from factoring code out to
+ self-contained support classes, even if such code is present only once in the
+ code base, in order to keep the source code clean and easy to read. This
+ should be the case for instance for plugin management.
+
+Platform Support
+----------------
+
+The library currently supports the following hardware platforms specifically
+with dedicated pipeline handlers:
+
+ - Arm Mali-C55
+ - Intel IPU3 (ipu3)
+ - NXP i.MX8MP (imx8-isi and rkisp1)
+ - RaspberryPi 3, 4 and zero (rpi/vc4)
+ - Rockchip RK3399 (rkisp1)
+
+Furthermore, generic platform support is provided for the following:
+
+ - USB video device class cameras (uvcvideo)
+ - iMX7, IPU6, Allwinner Sun6i (simple)
+ - Virtual media controller driver for test use cases (vimc)
diff --git a/Documentation/mainpage.dox b/Documentation/mainpage.dox
new file mode 100644
index 00000000..cbee9bab
--- /dev/null
+++ b/Documentation/mainpage.dox
@@ -0,0 +1,33 @@
+/**
+\mainpage libcamera API reference
+
+Welcome to the API reference for <a href="https://libcamera.org/">libcamera</a>,
+a complex camera support library for Linux, Android and ChromeOS. These pages
+are automatically generated from the libcamera source code and describe the API
+in detail - if this is your first interaction with libcamera then you may find
+it useful to visit the [documentation](../introduction.html) in
+the first instance, which can provide a more generic introduction to the
+library's concepts.
+
+\if internal
+
+As a follow-on to the developer's guide, to assist you in adding support for
+your platform the [pipeline handler writer's guide](../guides/pipeline-handler.html)
+and the [ipa module writer's guide](../guides/ipa.html) should be helpful.
+
+The full libcamera API is documented here. If you wish to see only the public
+part of the API you can use [these pages](../api-html/index.html) instead.
+
+\else
+
+As a follow-on to the developer's guide, to assist you in using libcamera within
+your project the [application developer's guide](../guides/application-developer.html)
+gives an overview on how to achieve that.
+
+Only the public part of the libcamera API is documented here; if you are a
+developer seeking to add support for your hardware to the library or make other
+improvements, you should switch to the internal API
+[reference pages](../internal-api-html/index.html) instead.
+
+\endif
+*/
diff --git a/Documentation/mali-c55.dot b/Documentation/mali-c55.dot
new file mode 100644
index 00000000..7bfc44c0
--- /dev/null
+++ b/Documentation/mali-c55.dot
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: CC-BY-SA-4.0 */
+
+digraph board {
+ rankdir=TB
+ n00000001 [label="{{} | mali-c55 tpg\n/dev/v4l-subdev0 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
+ n00000001:port0 -> n00000003:port0 [style=dashed]
+ n00000003 [label="{{<port0> 0 | <port4> 4} | mali-c55 isp\n/dev/v4l-subdev1 | {<port1> 1 | <port2> 2 | <port3> 3}}", shape=Mrecord, style=filled, fillcolor=green]
+ n00000003:port1 -> n00000009:port0 [style=bold]
+ n00000003:port2 -> n00000009:port2 [style=bold]
+ n00000003:port1 -> n0000000d:port0 [style=bold]
+ n00000003:port3 -> n0000001c
+ n00000009 [label="{{<port0> 0 | <port2> 2} | mali-c55 resizer fr\n/dev/v4l-subdev2 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+ n00000009:port1 -> n00000010
+ n0000000d [label="{{<port0> 0} | mali-c55 resizer ds\n/dev/v4l-subdev3 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+ n0000000d:port1 -> n00000014
+ n00000010 [label="mali-c55 fr\n/dev/video0", shape=box, style=filled, fillcolor=yellow]
+ n00000014 [label="mali-c55 ds\n/dev/video1", shape=box, style=filled, fillcolor=yellow]
+ n00000018 [label="mali-c55 3a params\n/dev/video2", shape=box, style=filled, fillcolor=yellow]
+ n00000018 -> n00000003:port4
+ n0000001c [label="mali-c55 3a stats\n/dev/video3", shape=box, style=filled, fillcolor=yellow]
+ n00000030 [label="{{<port0> 0} | lte-csi2-rx\n/dev/v4l-subdev4 | {<port1> 1}}", shape=Mrecord, style=filled, fillcolor=green]
+ n00000030:port1 -> n00000003:port0
+ n00000035 [label="{{} | imx415 1-001a\n/dev/v4l-subdev5 | {<port0> 0}}", shape=Mrecord, style=filled, fillcolor=green]
+ n00000035:port0 -> n00000030:port0 [style=bold]
+}
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 30d39523..36ffae23 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -24,34 +24,92 @@ if doxygen.found() and dot.found()
cdata.set('PREDEFINED', ' \\\n\t\t\t '.join(doxygen_predefined))
- doxyfile = configure_file(input : 'Doxyfile.in',
- output : 'Doxyfile',
- configuration : cdata)
+ doxyfile_common = configure_file(input : 'Doxyfile-common.in',
+ output : 'Doxyfile-common',
+ configuration : cdata)
+
+ doxygen_public_input = [
+ libcamera_base_public_headers,
+ libcamera_base_public_sources,
+ libcamera_public_headers,
+ libcamera_public_sources,
+ ]
- doxygen_input = [
- doxyfile,
- libcamera_base_headers,
- libcamera_base_sources,
+ doxygen_internal_input = [
+ libcamera_base_private_headers,
+ libcamera_base_internal_sources,
libcamera_internal_headers,
+ libcamera_internal_sources,
libcamera_ipa_headers,
libcamera_ipa_interfaces,
- libcamera_public_headers,
- libcamera_sources,
libipa_headers,
libipa_sources,
]
if is_variable('ipu3_ipa_sources')
- doxygen_input += [ipu3_ipa_sources]
+ doxygen_internal_input += [ipu3_ipa_sources]
endif
- custom_target('doxygen',
- input : doxygen_input,
+ # We run doxygen twice - the first run excludes internal API objects as it
+ # is intended to document the public API only. A second run covers all of
+ # the library's objects for libcamera developers. Common configuration is
+ # set in an initially generated Doxyfile, which is then included by the two
+ # final Doxyfiles.
+
+ # This is the "public" run of doxygen generating an abridged version of the
+ # API's documentation.
+
+ doxyfile_tmpl = configure_file(input : 'Doxyfile-public.in',
+ output : 'Doxyfile-public.tmpl',
+ configuration : cdata)
+
+ # The set of public input files stored in the doxygen_public_input array
+ # needs to be set in Doxyfile public. We can't pass them through cdata
+ # cdata, as some of the array members are custom_tgt instances, which
+ # configuration_data.set() doesn't support. Using a separate script invoked
+ # through custom_target(), which supports custom_tgt instances as inputs.
+
+ doxyfile = custom_target('doxyfile-public',
+ input : [
+ doxygen_public_input,
+ ],
+ output : 'Doxyfile-public',
+ command : [
+ 'gen-doxyfile.py',
+ '-o', '@OUTPUT@',
+ doxyfile_tmpl,
+ '@INPUT@',
+ ])
+
+ custom_target('doxygen-public',
+ input : [
+ doxyfile,
+ doxyfile_common,
+ ],
output : 'api-html',
command : [doxygen, doxyfile],
install : true,
install_dir : doc_install_dir,
install_tag : 'doc')
+
+ # This is the internal documentation, which hard-codes a list of directories
+ # to parse in its doxyfile.
+
+ doxyfile = configure_file(input : 'Doxyfile-internal.in',
+ output : 'Doxyfile-internal',
+ configuration : cdata)
+
+ custom_target('doxygen-internal',
+ input : [
+ doxyfile,
+ doxyfile_common,
+ doxygen_internal_input,
+ ],
+ output : 'internal-api-html',
+ command : [doxygen, doxyfile],
+ install : true,
+ install_dir : doc_install_dir,
+ install_tag : 'doc-internal')
endif
#
@@ -70,15 +128,18 @@ if sphinx.found()
'coding-style.rst',
'conf.py',
'contributing.rst',
- 'docs.rst',
+ 'documentation-contents.rst',
'environment_variables.rst',
+ 'feature_requirements.rst',
'guides/application-developer.rst',
- 'guides/introduction.rst',
'guides/ipa.rst',
'guides/pipeline-handler.rst',
'guides/tracing.rst',
'index.rst',
+ 'introduction.rst',
'lens_driver_requirements.rst',
+ 'libcamera_architecture.rst',
+ 'mali-c55.dot',
'python-bindings.rst',
'sensor_driver_requirements.rst',
'software-isp-benchmarking.rst',
diff --git a/Documentation/python-bindings.rst b/Documentation/python-bindings.rst
index ed9f686b..94712238 100644
--- a/Documentation/python-bindings.rst
+++ b/Documentation/python-bindings.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _python-bindings:
Python Bindings for libcamera
diff --git a/Documentation/sensor_driver_requirements.rst b/Documentation/sensor_driver_requirements.rst
index 0e516b34..fb4269d0 100644
--- a/Documentation/sensor_driver_requirements.rst
+++ b/Documentation/sensor_driver_requirements.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _sensor-driver-requirements:
Sensor Driver Requirements
diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst
index b3033132..9c2a409b 100644
--- a/Documentation/software-isp-benchmarking.rst
+++ b/Documentation/software-isp-benchmarking.rst
@@ -1,5 +1,7 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
+.. include:: documentation-contents.rst
+
.. _software-isp-benchmarking:
Software ISP benchmarking
diff --git a/Documentation/theme/static/css/theme.css b/Documentation/theme/static/css/theme.css
index d4274ea6..a6d43195 100644
--- a/Documentation/theme/static/css/theme.css
+++ b/Documentation/theme/static/css/theme.css
@@ -283,9 +283,13 @@ div#signature {
font-size: 12px;
}
-#libcamera div.toctree-wrapper {
+#licensing div.toctree-wrapper {
height: 0px;
margin: 0px;
padding: 0px;
visibility: hidden;
}
+
+.documentation-nav {
+ display: none;
+}
diff --git a/Documentation/thread-safety.dox b/Documentation/thread-safety.dox
new file mode 100644
index 00000000..df4c457c
--- /dev/null
+++ b/Documentation/thread-safety.dox
@@ -0,0 +1,44 @@
+/**
+ * \page thread-safety Reentrancy and Thread-Safety
+ *
+ * Through the documentation, several terms are used to define how classes and
+ * their member functions can be used from multiple threads.
+ *
+ * - A **reentrant** function may be called simultaneously from multiple
+ * threads if and only if each invocation uses a different instance of the
+ * class. This is the default for all member functions not explictly marked
+ * otherwise.
+ *
+ * - \anchor thread-safe A **thread-safe** function may be called
+ * simultaneously from multiple threads on the same instance of a class. A
+ * thread-safe function is thus reentrant. Thread-safe functions may also be
+ * called simultaneously with any other reentrant function of the same class
+ * on the same instance.
+ *
+ * \internal
+ * - \anchor thread-bound A **thread-bound** function may be called only from
+ * the thread that the class instances lives in (see section \ref
+ * thread-objects). For instances of classes that do not derive from the
+ * Object class, this is the thread in which the instance was created. A
+ * thread-bound function is not thread-safe, and may or may not be reentrant.
+ * \endinternal
+ *
+ * Neither reentrancy nor thread-safety, in this context, mean that a function
+ * may be called simultaneously from the same thread, for instance from a
+ * callback invoked by the function. This may deadlock and isn't allowed unless
+ * separately documented.
+ *
+ * \if internal
+ * A class is defined as reentrant, thread-safe or thread-bound if all its
+ * member functions are reentrant, thread-safe or thread-bound respectively.
+ * \else
+ * A class is defined as reentrant or thread-safe if all its member functions
+ * are reentrant or thread-safe respectively.
+ * \endif
+ * Some member functions may additionally be documented as having additional
+ * thread-related attributes.
+ *
+ * Most classes are reentrant but not thread-safe, as making them fully
+ * thread-safe would incur locking costs considered prohibitive for the
+ * expected use cases.
+ */
diff --git a/README.rst b/README.rst
index 1da7a3d6..4068c6cc 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,5 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
-.. section-begin-libcamera
-
===========
libcamera
===========
@@ -22,7 +20,6 @@ open-source-friendly while still protecting vendor core IP. libcamera was born
out of that collaboration and will offer modern camera support to Linux-based
systems, including traditional Linux distributions, ChromeOS and Android.
-.. section-end-libcamera
.. section-begin-getting-started
Getting Started
@@ -88,7 +85,7 @@ for cam: [optional]
- libsdl2-dev: Enables the SDL sink
for qcam: [optional]
- libtiff-dev qtbase5-dev qttools5-dev-tools
+ libtiff-dev qt6-base-dev qt6-tools-dev-tools
for tracing with lttng: [optional]
liblttng-ust-dev python3-jinja2 lttng-tools
@@ -178,6 +175,22 @@ Which can be received on another device over the network with:
gst-launch-1.0 tcpclientsrc host=$DEVICE_IP port=5000 ! \
multipartdemux ! jpegdec ! autovideosink
+The GStreamer element also supports multiple streams. This is achieved by
+requesting additional source pads. Downstream caps filters can be used
+to choose specific parameters like resolution and pixel format. The pad
+property ``stream-role`` can be used to select a role.
+
+The following example displays a 640x480 view finder while streaming JPEG
+encoded 800x600 video. You can use the receiver pipeline above to view the
+remote stream from another device.
+
+.. code::
+
+ gst-launch-1.0 libcamerasrc name=cs src::stream-role=view-finder src_0::stream-role=video-recording \
+ cs.src ! queue ! video/x-raw,width=640,height=480 ! videoconvert ! autovideosink \
+ cs.src_0 ! queue ! video/x-raw,width=800,height=600 ! videoconvert ! \
+ jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=5000
+
.. section-end-getting-started
Troubleshooting
diff --git a/include/libcamera/base/event_dispatcher.h b/include/libcamera/base/event_dispatcher.h
index e9a09c6e..408f8da6 100644
--- a/include/libcamera/base/event_dispatcher.h
+++ b/include/libcamera/base/event_dispatcher.h
@@ -7,8 +7,6 @@
#pragma once
-#include <vector>
-
#include <libcamera/base/private.h>
namespace libcamera {
diff --git a/include/libcamera/base/file.h b/include/libcamera/base/file.h
index 5637934c..6d3f106d 100644
--- a/include/libcamera/base/file.h
+++ b/include/libcamera/base/file.h
@@ -7,10 +7,10 @@
#pragma once
-#include <sys/types.h>
-
#include <map>
+#include <stdint.h>
#include <string>
+#include <sys/types.h>
#include <libcamera/base/private.h>
diff --git a/include/libcamera/base/log.h b/include/libcamera/base/log.h
index b5775e49..62093012 100644
--- a/include/libcamera/base/log.h
+++ b/include/libcamera/base/log.h
@@ -7,7 +7,6 @@
#pragma once
-#include <chrono>
#include <sstream>
#include <libcamera/base/private.h>
@@ -48,6 +47,7 @@ private:
extern const LogCategory &_LOG_CATEGORY(name)();
#define LOG_DEFINE_CATEGORY(name) \
+LOG_DECLARE_CATEGORY(name) \
const LogCategory &_LOG_CATEGORY(name)() \
{ \
/* The instance will be deleted by the Logger destructor. */ \
diff --git a/include/libcamera/base/memfd.h b/include/libcamera/base/memfd.h
new file mode 100644
index 00000000..705d9929
--- /dev/null
+++ b/include/libcamera/base/memfd.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Anonymous file creation
+ */
+
+#pragma once
+
+#include <libcamera/base/flags.h>
+#include <libcamera/base/unique_fd.h>
+
+namespace libcamera {
+
+class MemFd
+{
+public:
+ enum class Seal {
+ None = 0,
+ Shrink = (1 << 0),
+ Grow = (1 << 1),
+ };
+
+ using Seals = Flags<Seal>;
+
+ static UniqueFD create(const char *name, std::size_t size,
+ Seals seals = Seal::None);
+};
+
+LIBCAMERA_FLAGS_ENABLE_OPERATORS(MemFd::Seal)
+
+} /* namespace libcamera */
diff --git a/include/libcamera/base/meson.build b/include/libcamera/base/meson.build
index bace25d5..2a0cee31 100644
--- a/include/libcamera/base/meson.build
+++ b/include/libcamera/base/meson.build
@@ -21,6 +21,7 @@ libcamera_base_private_headers = files([
'event_notifier.h',
'file.h',
'log.h',
+ 'memfd.h',
'message.h',
'mutex.h',
'private.h',
diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h
index 849fbbda..bbff1495 100644
--- a/include/libcamera/base/signal.h
+++ b/include/libcamera/base/signal.h
@@ -10,7 +10,6 @@
#include <functional>
#include <list>
#include <type_traits>
-#include <vector>
#include <libcamera/base/bound_method.h>
diff --git a/include/libcamera/base/span.h b/include/libcamera/base/span.h
index c3e63f69..92cce4f0 100644
--- a/include/libcamera/base/span.h
+++ b/include/libcamera/base/span.h
@@ -10,7 +10,6 @@
#include <array>
#include <iterator>
#include <limits>
-#include <stddef.h>
#include <type_traits>
namespace libcamera {
diff --git a/include/libcamera/base/timer.h b/include/libcamera/base/timer.h
index 5ef45959..9646a0fe 100644
--- a/include/libcamera/base/timer.h
+++ b/include/libcamera/base/timer.h
@@ -8,7 +8,6 @@
#pragma once
#include <chrono>
-#include <stdint.h>
#include <libcamera/base/private.h>
diff --git a/include/libcamera/base/utils.h b/include/libcamera/base/utils.h
index 4ae02dc9..957150cb 100644
--- a/include/libcamera/base/utils.h
+++ b/include/libcamera/base/utils.h
@@ -9,12 +9,12 @@
#include <algorithm>
#include <chrono>
+#include <functional>
#include <iterator>
-#include <memory>
#include <ostream>
#include <sstream>
-#include <string>
#include <string.h>
+#include <string>
#include <sys/time.h>
#include <type_traits>
#include <utility>
@@ -91,6 +91,30 @@ _hex hex(T value, unsigned int width = 0);
#ifndef __DOXYGEN__
template<>
+inline _hex hex<int8_t>(int8_t value, unsigned int width)
+{
+ return { static_cast<uint64_t>(value), width ? width : 2 };
+}
+
+template<>
+inline _hex hex<uint8_t>(uint8_t value, unsigned int width)
+{
+ return { static_cast<uint64_t>(value), width ? width : 2 };
+}
+
+template<>
+inline _hex hex<int16_t>(int16_t value, unsigned int width)
+{
+ return { static_cast<uint64_t>(value), width ? width : 4 };
+}
+
+template<>
+inline _hex hex<uint16_t>(uint16_t value, unsigned int width)
+{
+ return { static_cast<uint64_t>(value), width ? width : 4 };
+}
+
+template<>
inline _hex hex<int32_t>(int32_t value, unsigned int width)
{
return { static_cast<uint64_t>(value), width ? width : 8 };
@@ -375,6 +399,18 @@ constexpr std::underlying_type_t<Enum> to_underlying(Enum e) noexcept
return static_cast<std::underlying_type_t<Enum>>(e);
}
+class ScopeExitActions
+{
+public:
+ ~ScopeExitActions();
+
+ void operator+=(std::function<void()> &&action);
+ void release();
+
+private:
+ std::vector<std::function<void()>> actions_;
+};
+
} /* namespace utils */
#ifndef __DOXYGEN__
diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in
index 293ba966..858ef872 100644
--- a/include/libcamera/control_ids.h.in
+++ b/include/libcamera/control_ids.h.in
@@ -2,7 +2,7 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * Control ID list
+ * {{mode|capitalize}} ID list
*
* This file is auto-generated. Do not edit.
*/
@@ -18,18 +18,42 @@
namespace libcamera {
-namespace controls {
+namespace {{mode}} {
-enum {
-${ids}
-};
+extern const ControlIdMap {{mode}};
+
+{%- for vendor, ctrls in controls -%}
-${controls}
+{% if vendor != 'libcamera' %}
+namespace {{vendor}} {
-extern const ControlIdMap controls;
+#define LIBCAMERA_HAS_{{vendor|upper}}_VENDOR_{{mode|upper}}
+{%- endif %}
-${vendor_controls}
+enum {
+{%- for ctrl in ctrls %}
+ {{ctrl.name|snake_case|upper}} = {{ctrl.id}},
+{%- endfor %}
+};
-} /* namespace controls */
+{% for ctrl in ctrls -%}
+{% if ctrl.is_enum -%}
+enum {{ctrl.name}}Enum {
+{%- for enum in ctrl.enum_values %}
+ {{enum.name}} = {{enum.value}},
+{%- endfor %}
+};
+extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values;
+extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap;
+{% endif -%}
+extern const Control<{{ctrl.type}}> {{ctrl.name}};
+{% endfor -%}
+
+{% if vendor != 'libcamera' %}
+} /* namespace {{vendor}} */
+{% endif -%}
+
+{% endfor %}
+} /* namespace {{mode}} */
} /* namespace libcamera */
diff --git a/include/libcamera/controls.h b/include/libcamera/controls.h
index 7c2bb287..25f67ed9 100644
--- a/include/libcamera/controls.h
+++ b/include/libcamera/controls.h
@@ -8,6 +8,7 @@
#pragma once
#include <assert.h>
+#include <map>
#include <optional>
#include <set>
#include <stdint.h>
@@ -45,50 +46,60 @@ struct control_type {
template<>
struct control_type<void> {
static constexpr ControlType value = ControlTypeNone;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<bool> {
static constexpr ControlType value = ControlTypeBool;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<uint8_t> {
static constexpr ControlType value = ControlTypeByte;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<int32_t> {
static constexpr ControlType value = ControlTypeInteger32;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<int64_t> {
static constexpr ControlType value = ControlTypeInteger64;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<float> {
static constexpr ControlType value = ControlTypeFloat;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<std::string> {
static constexpr ControlType value = ControlTypeString;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<Rectangle> {
static constexpr ControlType value = ControlTypeRectangle;
+ static constexpr std::size_t size = 0;
};
template<>
struct control_type<Size> {
static constexpr ControlType value = ControlTypeSize;
+ static constexpr std::size_t size = 0;
};
template<typename T, std::size_t N>
struct control_type<Span<T, N>> : public control_type<std::remove_cv_t<T>> {
+ static constexpr std::size_t size = N;
};
} /* namespace details */
@@ -213,14 +224,16 @@ private:
class ControlId
{
public:
- ControlId(unsigned int id, const std::string &name, ControlType type)
- : id_(id), name_(name), type_(type)
- {
- }
+ ControlId(unsigned int id, const std::string &name, ControlType type,
+ std::size_t size = 0,
+ const std::map<std::string, int32_t> &enumStrMap = {});
unsigned int id() const { return id_; }
const std::string &name() const { return name_; }
ControlType type() const { return type_; }
+ bool isArray() const { return size_ > 0; }
+ std::size_t size() const { return size_; }
+ const std::map<int32_t, std::string> &enumerators() const { return reverseMap_; }
private:
LIBCAMERA_DISABLE_COPY_AND_MOVE(ControlId)
@@ -228,6 +241,9 @@ private:
unsigned int id_;
std::string name_;
ControlType type_;
+ std::size_t size_;
+ std::map<std::string, int32_t> enumStrMap_;
+ std::map<int32_t, std::string> reverseMap_;
};
static inline bool operator==(unsigned int lhs, const ControlId &rhs)
@@ -256,8 +272,9 @@ class Control : public ControlId
public:
using type = T;
- Control(unsigned int id, const char *name)
- : ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value)
+ Control(unsigned int id, const char *name, const std::map<std::string, int32_t> &enumStrMap = {})
+ : ControlId(id, name, details::control_type<std::remove_cv_t<T>>::value,
+ details::control_type<std::remove_cv_t<T>>::size, enumStrMap)
{
}
diff --git a/include/libcamera/framebuffer.h b/include/libcamera/framebuffer.h
index 5ae2270b..ff839243 100644
--- a/include/libcamera/framebuffer.h
+++ b/include/libcamera/framebuffer.h
@@ -7,7 +7,6 @@
#pragma once
-#include <assert.h>
#include <limits>
#include <memory>
#include <stdint.h>
diff --git a/include/libcamera/internal/camera_manager.h b/include/libcamera/internal/camera_manager.h
index af9ed60a..0150ca61 100644
--- a/include/libcamera/internal/camera_manager.h
+++ b/include/libcamera/internal/camera_manager.h
@@ -9,7 +9,6 @@
#include <libcamera/camera_manager.h>
-#include <map>
#include <memory>
#include <sys/types.h>
#include <vector>
@@ -19,13 +18,14 @@
#include <libcamera/base/thread.h>
#include <libcamera/base/thread_annotations.h>
-#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/process.h"
namespace libcamera {
class Camera;
class DeviceEnumerator;
+class IPAManager;
+class PipelineHandlerFactoryBase;
class CameraManager::Private : public Extensible::Private, public Thread
{
@@ -38,6 +38,8 @@ public:
void addCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_);
void removeCamera(std::shared_ptr<Camera> camera) LIBCAMERA_TSA_EXCLUDES(mutex_);
+ IPAManager *ipaManager() const { return ipaManager_.get(); }
+
protected:
void run() override;
@@ -62,7 +64,7 @@ private:
std::unique_ptr<DeviceEnumerator> enumerator_;
- IPAManager ipaManager_;
+ std::unique_ptr<IPAManager> ipaManager_;
ProcessManager processManager_;
};
diff --git a/include/libcamera/internal/camera_sensor.h b/include/libcamera/internal/camera_sensor.h
index fc44ab98..a42c15fa 100644
--- a/include/libcamera/internal/camera_sensor.h
+++ b/include/libcamera/internal/camera_sensor.h
@@ -23,7 +23,6 @@
#include <libcamera/ipa/core_ipa_interface.h>
#include "libcamera/internal/bayer_format.h"
-#include "libcamera/internal/formats.h"
#include "libcamera/internal/v4l2_subdevice.h"
namespace libcamera {
diff --git a/include/libcamera/internal/converter.h b/include/libcamera/internal/converter.h
index 5d74db6b..b51563d7 100644
--- a/include/libcamera/internal/converter.h
+++ b/include/libcamera/internal/converter.h
@@ -26,6 +26,7 @@ namespace libcamera {
class FrameBuffer;
class MediaDevice;
class PixelFormat;
+class Stream;
struct StreamConfiguration;
class Converter
@@ -46,14 +47,14 @@ public:
virtual int configure(const StreamConfiguration &inputCfg,
const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0;
- virtual int exportBuffers(unsigned int output, unsigned int count,
+ virtual int exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0;
virtual int start() = 0;
virtual void stop() = 0;
virtual int queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs) = 0;
+ const std::map<const Stream *, FrameBuffer *> &outputs) = 0;
Signal<FrameBuffer *> inputBufferReady;
Signal<FrameBuffer *> outputBufferReady;
diff --git a/include/libcamera/internal/converter/converter_v4l2_m2m.h b/include/libcamera/internal/converter/converter_v4l2_m2m.h
index 1126050c..b9e59899 100644
--- a/include/libcamera/internal/converter/converter_v4l2_m2m.h
+++ b/include/libcamera/internal/converter/converter_v4l2_m2m.h
@@ -28,6 +28,7 @@ class FrameBuffer;
class MediaDevice;
class Size;
class SizeRange;
+class Stream;
struct StreamConfiguration;
class V4L2M2MDevice;
@@ -47,20 +48,20 @@ public:
int configure(const StreamConfiguration &inputCfg,
const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfg);
- int exportBuffers(unsigned int output, unsigned int count,
+ int exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers);
int start();
void stop();
int queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs);
+ const std::map<const Stream *, FrameBuffer *> &outputs);
private:
- class Stream : protected Loggable
+ class V4L2M2MStream : protected Loggable
{
public:
- Stream(V4L2M2MConverter *converter, unsigned int index);
+ V4L2M2MStream(V4L2M2MConverter *converter, const Stream *stream);
bool isValid() const { return m2m_ != nullptr; }
@@ -82,7 +83,7 @@ private:
void outputBufferReady(FrameBuffer *buffer);
V4L2M2MConverter *converter_;
- unsigned int index_;
+ const Stream *stream_;
std::unique_ptr<V4L2M2MDevice> m2m_;
unsigned int inputBufferCount_;
@@ -91,7 +92,7 @@ private:
std::unique_ptr<V4L2M2MDevice> m2m_;
- std::vector<Stream> streams_;
+ std::map<const Stream *, std::unique_ptr<V4L2M2MStream>> streams_;
std::map<FrameBuffer *, unsigned int> queue_;
};
diff --git a/include/libcamera/internal/device_enumerator_sysfs.h b/include/libcamera/internal/device_enumerator_sysfs.h
index a5bfc711..4ccc9845 100644
--- a/include/libcamera/internal/device_enumerator_sysfs.h
+++ b/include/libcamera/internal/device_enumerator_sysfs.h
@@ -7,7 +7,6 @@
#pragma once
-#include <memory>
#include <string>
#include "libcamera/internal/device_enumerator.h"
diff --git a/include/libcamera/internal/dma_buf_allocator.h b/include/libcamera/internal/dma_buf_allocator.h
index 36ec1696..d2a0a0d1 100644
--- a/include/libcamera/internal/dma_buf_allocator.h
+++ b/include/libcamera/internal/dma_buf_allocator.h
@@ -7,8 +7,6 @@
#pragma once
-#include <stddef.h>
-
#include <libcamera/base/flags.h>
#include <libcamera/base/unique_fd.h>
diff --git a/include/libcamera/internal/formats.h b/include/libcamera/internal/formats.h
index 71895cd8..6a3e9c16 100644
--- a/include/libcamera/internal/formats.h
+++ b/include/libcamera/internal/formats.h
@@ -8,7 +8,6 @@
#pragma once
#include <array>
-#include <map>
#include <vector>
#include <libcamera/geometry.h>
diff --git a/include/libcamera/internal/ipa_data_serializer.h b/include/libcamera/internal/ipa_data_serializer.h
index 337c948c..66d9a19f 100644
--- a/include/libcamera/internal/ipa_data_serializer.h
+++ b/include/libcamera/internal/ipa_data_serializer.h
@@ -7,8 +7,6 @@
#pragma once
-#include <deque>
-#include <iostream>
#include <string.h>
#include <tuple>
#include <type_traits>
@@ -20,10 +18,9 @@
#include <libcamera/control_ids.h>
#include <libcamera/framebuffer.h>
#include <libcamera/geometry.h>
+
#include <libcamera/ipa/ipa_interface.h>
-#include "libcamera/internal/byte_stream_buffer.h"
-#include "libcamera/internal/camera_sensor.h"
#include "libcamera/internal/control_serializer.h"
namespace libcamera {
@@ -165,7 +162,7 @@ public:
std::vector<SharedFD>::const_iterator fdIter = fdsBegin;
for (uint32_t i = 0; i < vecLen; i++) {
uint32_t sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd);
- uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
+ uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
dataIter += 8;
ret[i] = IPADataSerializer<V>::deserialize(dataIter,
@@ -272,7 +269,7 @@ public:
std::vector<SharedFD>::const_iterator fdIter = fdsBegin;
for (uint32_t i = 0; i < mapLen; i++) {
uint32_t sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd);
- uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
+ uint32_t sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
dataIter += 8;
K key = IPADataSerializer<K>::deserialize(dataIter,
@@ -284,7 +281,7 @@ public:
dataIter += sizeofData;
fdIter += sizeofFds;
sizeofData = readPOD<uint32_t>(dataIter, 0, dataEnd);
- sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
+ sizeofFds = readPOD<uint32_t>(dataIter, 4, dataEnd);
dataIter += 8;
const V value = IPADataSerializer<V>::deserialize(dataIter,
diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index c6f74e11..16dede0c 100644
--- a/include/libcamera/internal/ipa_manager.h
+++ b/include/libcamera/internal/ipa_manager.h
@@ -15,6 +15,7 @@
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
+#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/ipa_module.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/pub_key.h"
@@ -34,11 +35,13 @@ public:
uint32_t minVersion,
uint32_t maxVersion)
{
- IPAModule *m = self_->module(pipe, minVersion, maxVersion);
+ CameraManager *cm = pipe->cameraManager();
+ IPAManager *self = cm->_d()->ipaManager();
+ IPAModule *m = self->module(pipe, minVersion, maxVersion);
if (!m)
return nullptr;
- std::unique_ptr<T> proxy = std::make_unique<T>(m, !self_->isSignatureValid(m));
+ std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m));
if (!proxy->isValid()) {
LOG(IPAManager, Error) << "Failed to load proxy";
return nullptr;
@@ -55,8 +58,6 @@ public:
#endif
private:
- static IPAManager *self_;
-
void parseDir(const char *libDir, unsigned int maxDepth,
std::vector<std::string> &files);
unsigned int addDir(const char *libDir, unsigned int maxDepth = 0);
diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
index ed6a5bcf..983bcc5f 100644
--- a/include/libcamera/internal/ipa_proxy.h
+++ b/include/libcamera/internal/ipa_proxy.h
@@ -7,9 +7,7 @@
#pragma once
-#include <memory>
#include <string>
-#include <vector>
#include <libcamera/ipa/ipa_interface.h>
@@ -31,7 +29,8 @@ public:
bool isValid() const { return valid_; }
- std::string configurationFile(const std::string &file) const;
+ std::string configurationFile(const std::string &name,
+ const std::string &fallbackName = std::string()) const;
protected:
std::string resolvePath(const std::string &file) const;
diff --git a/include/libcamera/internal/ipc_pipe_unixsocket.h b/include/libcamera/internal/ipc_pipe_unixsocket.h
index 4a0f6d57..8c972613 100644
--- a/include/libcamera/internal/ipc_pipe_unixsocket.h
+++ b/include/libcamera/internal/ipc_pipe_unixsocket.h
@@ -9,7 +9,6 @@
#include <map>
#include <memory>
-#include <vector>
#include "libcamera/internal/ipc_pipe.h"
#include "libcamera/internal/ipc_unixsocket.h"
diff --git a/include/libcamera/internal/media_device.h b/include/libcamera/internal/media_device.h
index bf2e475d..e412d3a0 100644
--- a/include/libcamera/internal/media_device.h
+++ b/include/libcamera/internal/media_device.h
@@ -8,7 +8,6 @@
#pragma once
#include <map>
-#include <sstream>
#include <string>
#include <vector>
diff --git a/include/libcamera/internal/media_object.h b/include/libcamera/internal/media_object.h
index c9d77511..9356d204 100644
--- a/include/libcamera/internal/media_object.h
+++ b/include/libcamera/internal/media_object.h
@@ -48,6 +48,8 @@ public:
unsigned int flags() const { return flags_; }
int setEnabled(bool enable);
+ std::string toString() const;
+
private:
LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaLink)
@@ -61,6 +63,8 @@ private:
unsigned int flags_;
};
+std::ostream &operator<<(std::ostream &out, const MediaLink &link);
+
class MediaPad : public MediaObject
{
public:
@@ -71,6 +75,8 @@ public:
void addLink(MediaLink *link);
+ std::string toString() const;
+
private:
LIBCAMERA_DISABLE_COPY_AND_MOVE(MediaPad)
@@ -85,6 +91,8 @@ private:
std::vector<MediaLink *> links_;
};
+std::ostream &operator<<(std::ostream &out, const MediaPad &pad);
+
class MediaEntity : public MediaObject
{
public:
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index 9713ea1c..1c5eef9c 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -2,13 +2,6 @@
subdir('tracepoints')
-libcamera_tracepoint_header = custom_target(
- 'tp_header',
- input : ['tracepoints.h.in', tracepoint_files],
- output : 'tracepoints.h',
- command : [gen_tracepoints_header, include_build_dir, '@OUTPUT@', '@INPUT@'],
-)
-
libcamera_internal_headers = files([
'bayer_format.h',
'byte_stream_buffer.h',
@@ -28,9 +21,11 @@ libcamera_internal_headers = files([
'dma_buf_allocator.h',
'formats.h',
'framebuffer.h',
+ 'ipa_data_serializer.h',
'ipa_manager.h',
'ipa_module.h',
'ipa_proxy.h',
+ 'ipc_pipe.h',
'ipc_unixsocket.h',
'mapped_framebuffer.h',
'media_device.h',
@@ -49,5 +44,14 @@ libcamera_internal_headers = files([
'yaml_parser.h',
])
+tracepoints_h = custom_target(
+ 'tp_header',
+ input : ['tracepoints.h.in', tracepoint_files],
+ output : 'tracepoints.h',
+ command : [gen_tracepoints, include_build_dir, '@OUTPUT@', '@INPUT@'],
+)
+
+libcamera_internal_headers += tracepoints_h
+
subdir('converter')
subdir('software_isp')
diff --git a/include/libcamera/internal/pipeline_handler.h b/include/libcamera/internal/pipeline_handler.h
index 746a34f8..0d380803 100644
--- a/include/libcamera/internal/pipeline_handler.h
+++ b/include/libcamera/internal/pipeline_handler.h
@@ -9,19 +9,15 @@
#include <memory>
#include <queue>
-#include <set>
#include <string>
#include <sys/types.h>
#include <vector>
-#include <libcamera/base/mutex.h>
#include <libcamera/base/object.h>
#include <libcamera/controls.h>
#include <libcamera/stream.h>
-#include "libcamera/internal/ipa_proxy.h"
-
namespace libcamera {
class Camera;
@@ -45,7 +41,7 @@ public:
MediaDevice *acquireMediaDevice(DeviceEnumerator *enumerator,
const DeviceMatch &dm);
- bool acquire();
+ bool acquire(Camera *camera);
void release(Camera *camera);
virtual std::unique_ptr<CameraConfiguration> generateConfiguration(Camera *camera,
@@ -70,6 +66,8 @@ public:
const char *name() const { return name_; }
+ CameraManager *cameraManager() const { return manager_; }
+
protected:
void registerCamera(std::shared_ptr<Camera> camera);
void hotplugMediaDevice(MediaDevice *media);
@@ -77,6 +75,7 @@ protected:
virtual int queueRequestDevice(Camera *camera, Request *request) = 0;
virtual void stopDevice(Camera *camera) = 0;
+ virtual bool acquireDevice(Camera *camera);
virtual void releaseDevice(Camera *camera);
CameraManager *manager_;
@@ -96,9 +95,7 @@ private:
std::queue<Request *> waitingRequests_;
const char *name_;
-
- Mutex lock_;
- unsigned int useCount_ LIBCAMERA_TSA_GUARDED_BY(lock_);
+ unsigned int useCount_;
friend class PipelineHandlerFactoryBase;
};
diff --git a/include/libcamera/internal/request.h b/include/libcamera/internal/request.h
index f5d98069..4e7d05b1 100644
--- a/include/libcamera/internal/request.h
+++ b/include/libcamera/internal/request.h
@@ -10,6 +10,7 @@
#include <chrono>
#include <map>
#include <memory>
+#include <unordered_set>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/timer.h>
diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
index 2ab0189f..e9f1dacd 100644
--- a/include/libcamera/internal/shared_mem_object.h
+++ b/include/libcamera/internal/shared_mem_object.h
@@ -8,7 +8,6 @@
*/
#pragma once
-#include <stddef.h>
#include <stdint.h>
#include <string>
#include <sys/mman.h>
diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
index c5338c05..f8e00003 100644
--- a/include/libcamera/internal/software_isp/software_isp.h
+++ b/include/libcamera/internal/software_isp/software_isp.h
@@ -37,6 +37,7 @@ namespace libcamera {
class DebayerCpu;
class FrameBuffer;
class PixelFormat;
+class Stream;
struct StreamConfiguration;
LOG_DECLARE_CATEGORY(SoftwareIsp)
@@ -62,7 +63,7 @@ public:
const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
const ControlInfoMap &sensorControls);
- int exportBuffers(unsigned int output, unsigned int count,
+ int exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers);
void processStats(const ControlList &sensorControls);
@@ -71,7 +72,7 @@ public:
void stop();
int queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs);
+ const std::map<const Stream *, FrameBuffer *> &outputs);
void process(FrameBuffer *input, FrameBuffer *output);
diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
index e38a2df9..6211ff4a 100644
--- a/include/libcamera/internal/yaml_parser.h
+++ b/include/libcamera/internal/yaml_parser.h
@@ -10,6 +10,7 @@
#include <iterator>
#include <map>
#include <optional>
+#include <stdint.h>
#include <string>
#include <vector>
@@ -158,6 +159,14 @@ public:
{
return type_ == Type::Dictionary;
}
+ bool isEmpty() const
+ {
+ return type_ == Type::Empty;
+ }
+ explicit operator bool() const
+ {
+ return type_ != Type::Empty;
+ }
std::size_t size() const;
@@ -211,6 +220,7 @@ private:
Dictionary,
List,
Value,
+ Empty,
};
template<typename T>
diff --git a/include/libcamera/ipa/ipa_interface.h b/include/libcamera/ipa/ipa_interface.h
index b93f1a15..dce9637a 100644
--- a/include/libcamera/ipa/ipa_interface.h
+++ b/include/libcamera/ipa/ipa_interface.h
@@ -7,19 +7,6 @@
#pragma once
-#include <stddef.h>
-#include <stdint.h>
-
-#include <map>
-#include <vector>
-
-#include <libcamera/base/flags.h>
-#include <libcamera/base/signal.h>
-
-#include <libcamera/controls.h>
-#include <libcamera/framebuffer.h>
-#include <libcamera/geometry.h>
-
namespace libcamera {
/*
@@ -33,8 +20,8 @@ public:
virtual ~IPAInterface() = default;
};
-} /* namespace libcamera */
-
extern "C" {
libcamera::IPAInterface *ipaCreate();
}
+
+} /* namespace libcamera */
diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
index 3352d08f..bf55e124 100644
--- a/include/libcamera/ipa/meson.build
+++ b/include/libcamera/ipa/meson.build
@@ -11,8 +11,6 @@ libcamera_ipa_headers = files([
install_headers(libcamera_ipa_headers,
subdir : libcamera_ipa_include_dir)
-libcamera_generated_ipa_headers = []
-
ipa_headers_install_dir = get_option('includedir') / libcamera_ipa_include_dir
#
@@ -28,10 +26,11 @@ ipa_mojom_core = custom_target(core_mojom_file.split('.')[0] + '_mojom_module',
'--output-root', meson.project_build_root(),
'--input-root', meson.project_source_root(),
'--mojoms', '@INPUT@'
- ])
+ ],
+ env : py_build_env)
# core_ipa_interface.h
-libcamera_generated_ipa_headers += custom_target('core_ipa_interface_h',
+libcamera_ipa_headers += custom_target('core_ipa_interface_h',
input : ipa_mojom_core,
output : 'core_ipa_interface.h',
depends : mojom_templates,
@@ -44,10 +43,11 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_interface_h',
'--libcamera_generate_core_header',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
# core_ipa_serializer.h
-libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h',
+libcamera_ipa_headers += custom_target('core_ipa_serializer_h',
input : ipa_mojom_core,
output : 'core_ipa_serializer.h',
depends : mojom_templates,
@@ -58,7 +58,8 @@ libcamera_generated_ipa_headers += custom_target('core_ipa_serializer_h',
'--libcamera_generate_core_serializer',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
# Mapping from pipeline handler name to mojom file
pipeline_ipa_mojom_mapping = {
@@ -101,7 +102,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping
'--output-root', meson.project_build_root(),
'--input-root', meson.project_source_root(),
'--mojoms', '@INPUT@'
- ])
+ ],
+ env : py_build_env)
# {interface}_ipa_interface.h
header = custom_target(name + '_ipa_interface_h',
@@ -117,7 +119,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping
'--libcamera_generate_header',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
# {interface}_ipa_serializer.h
serializer = custom_target(name + '_ipa_serializer_h',
@@ -131,7 +134,8 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping
'--libcamera_generate_serializer',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
# {interface}_ipa_proxy.h
proxy_header = custom_target(name + '_proxy_h',
@@ -145,14 +149,15 @@ foreach pipeline, file : pipeline_ipa_mojom_mapping
'--libcamera_generate_proxy_h',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
ipa_mojoms += {
'name': name,
'mojom': mojom,
}
- libcamera_generated_ipa_headers += [header, serializer, proxy_header]
+ libcamera_ipa_headers += [header, serializer, proxy_header]
endforeach
ipa_mojom_files = []
diff --git a/include/libcamera/ipa/rkisp1.mojom b/include/libcamera/ipa/rkisp1.mojom
index 1009e970..80d54a03 100644
--- a/include/libcamera/ipa/rkisp1.mojom
+++ b/include/libcamera/ipa/rkisp1.mojom
@@ -11,6 +11,7 @@ import "include/libcamera/ipa/core.mojom";
struct IPAConfigInfo {
libcamera.IPACameraSensorInfo sensorInfo;
libcamera.ControlInfoMap sensorControls;
+ uint32 paramFormat;
};
interface IPARkISP1Interface {
@@ -36,7 +37,7 @@ interface IPARkISP1Interface {
};
interface IPARkISP1EventInterface {
- paramsBufferReady(uint32 frame);
+ paramsBufferReady(uint32 frame, uint32 bytesused);
setSensorControls(uint32 frame, libcamera.ControlList sensorControls);
metadataReady(uint32 frame, libcamera.ControlList metadata);
};
diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h
index e334d87b..e1c6341c 100644
--- a/include/libcamera/logging.h
+++ b/include/libcamera/logging.h
@@ -7,6 +7,8 @@
#pragma once
+#include <ostream>
+
namespace libcamera {
enum LoggingTarget {
diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
index 84c6c4cb..a969a95d 100644
--- a/include/libcamera/meson.build
+++ b/include/libcamera/meson.build
@@ -47,7 +47,9 @@ controls_map = {
control_headers = []
controls_files = []
+controls_files_names = []
properties_files = []
+properties_files_names = []
foreach mode, entry : controls_map
files_list = []
@@ -70,13 +72,15 @@ foreach mode, entry : controls_map
outfile = ''
if mode == 'controls'
outfile = 'control_ids.h'
- controls_files += files_list
+ controls_files += input_files
+ controls_files_names += files_list
else
outfile = 'property_ids.h'
- properties_files += files_list
+ properties_files += input_files
+ properties_files_names += files_list
endif
- template_file = files(outfile + '.in')
+ template_file = files('control_ids.h.in')
ranges_file = files('../../src/libcamera/control_ranges.yaml')
control_headers += custom_target(header + '_h',
input : input_files,
@@ -84,6 +88,7 @@ foreach mode, entry : controls_map
command : [gen_controls, '-o', '@OUTPUT@',
'--mode', mode, '-t', template_file,
'-r', ranges_file, '@INPUT@'],
+ env : py_build_env,
install : true,
install_dir : libcamera_headers_install_dir)
endforeach
@@ -103,24 +108,25 @@ formats_h = custom_target('formats_h',
install_dir : libcamera_headers_install_dir)
libcamera_public_headers += formats_h
+# version.h
+version = libcamera_version.split('.')
+libcamera_version_config = configuration_data()
+libcamera_version_config.set('LIBCAMERA_VERSION_MAJOR', version[0])
+libcamera_version_config.set('LIBCAMERA_VERSION_MINOR', version[1])
+libcamera_version_config.set('LIBCAMERA_VERSION_PATCH', version[2])
+
+version_h = configure_file(input : 'version.h.in',
+ output : 'version.h',
+ configuration : libcamera_version_config,
+ install_dir : libcamera_headers_install_dir)
+libcamera_public_headers += version_h
+
# libcamera.h
libcamera_h = custom_target('gen-header',
input : 'meson.build',
output : 'libcamera.h',
- command : [gen_header, meson.current_source_dir(), '@OUTPUT@'],
+ command : [gen_header, '@OUTPUT@', libcamera_public_headers],
install : true,
install_dir : libcamera_headers_install_dir)
libcamera_public_headers += libcamera_h
-
-# version.h
-version = libcamera_version.split('.')
-libcamera_version_config = configuration_data()
-libcamera_version_config.set('LIBCAMERA_VERSION_MAJOR', version[0])
-libcamera_version_config.set('LIBCAMERA_VERSION_MINOR', version[1])
-libcamera_version_config.set('LIBCAMERA_VERSION_PATCH', version[2])
-
-configure_file(input : 'version.h.in',
- output : 'version.h',
- configuration : libcamera_version_config,
- install_dir : libcamera_headers_install_dir)
diff --git a/include/libcamera/pixel_format.h b/include/libcamera/pixel_format.h
index ea60fe72..1b4d8c7c 100644
--- a/include/libcamera/pixel_format.h
+++ b/include/libcamera/pixel_format.h
@@ -8,7 +8,6 @@
#pragma once
#include <ostream>
-#include <set>
#include <stdint.h>
#include <string>
diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in
deleted file mode 100644
index e6edabca..00000000
--- a/include/libcamera/property_ids.h.in
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * Property ID list
- *
- * This file is auto-generated. Do not edit.
- */
-
-#pragma once
-
-#include <map>
-#include <stdint.h>
-#include <string>
-
-#include <libcamera/controls.h>
-
-namespace libcamera {
-
-namespace properties {
-
-enum {
-${ids}
-};
-
-${controls}
-
-extern const ControlIdMap properties;
-
-${vendor_controls}
-
-} /* namespace properties */
-
-} /* namespace libcamera */
diff --git a/include/libcamera/request.h b/include/libcamera/request.h
index 2c78d9bb..e214a9d1 100644
--- a/include/libcamera/request.h
+++ b/include/libcamera/request.h
@@ -12,7 +12,6 @@
#include <ostream>
#include <stdint.h>
#include <string>
-#include <unordered_set>
#include <libcamera/base/class.h>
#include <libcamera/base/signal.h>
diff --git a/include/libcamera/stream.h b/include/libcamera/stream.h
index d510238a..071b7169 100644
--- a/include/libcamera/stream.h
+++ b/include/libcamera/stream.h
@@ -8,7 +8,6 @@
#pragma once
#include <map>
-#include <memory>
#include <ostream>
#include <string>
#include <vector>
diff --git a/include/libcamera/transform.h b/include/libcamera/transform.h
index a88f809e..4517412a 100644
--- a/include/libcamera/transform.h
+++ b/include/libcamera/transform.h
@@ -7,8 +7,6 @@
#pragma once
-#include <string>
-
namespace libcamera {
enum class Orientation;
diff --git a/include/linux/README b/include/linux/README
index b7795309..ef178681 100644
--- a/include/linux/README
+++ b/include/linux/README
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: CC0-1.0
-Files in this directory are imported from v6.10-rc1 of the Linux kernel. Do not
+Files in this directory are imported from next-media-rkisp1-20240814-14-ga043ea54bbb9 of the Linux kernel. Do not
modify them manually.
diff --git a/include/linux/dma-heap.h b/include/linux/dma-heap.h
index 96b90cf0..63b1e9ed 100644
--- a/include/linux/dma-heap.h
+++ b/include/linux/dma-heap.h
@@ -19,7 +19,7 @@
#define DMA_HEAP_VALID_FD_FLAGS (O_CLOEXEC | O_ACCMODE)
/* Currently no heap flags */
-#define DMA_HEAP_VALID_HEAP_FLAGS (0)
+#define DMA_HEAP_VALID_HEAP_FLAGS (0ULL)
/**
* struct dma_heap_allocation_data - metadata passed from userspace for
diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h
index d8e99940..db679877 100644
--- a/include/linux/drm_fourcc.h
+++ b/include/linux/drm_fourcc.h
@@ -1556,6 +1556,7 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
#define AMD_FMT_MOD_TILE_VER_GFX10 2
#define AMD_FMT_MOD_TILE_VER_GFX10_RBPLUS 3
#define AMD_FMT_MOD_TILE_VER_GFX11 4
+#define AMD_FMT_MOD_TILE_VER_GFX12 5
/*
* 64K_S is the same for GFX9/GFX10/GFX10_RBPLUS and hence has GFX9 as canonical
@@ -1566,6 +1567,8 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
/*
* 64K_D for non-32 bpp is the same for GFX9/GFX10/GFX10_RBPLUS and hence has
* GFX9 as canonical version.
+ *
+ * 64K_D_2D on GFX12 is identical to 64K_D on GFX11.
*/
#define AMD_FMT_MOD_TILE_GFX9_64K_D 10
#define AMD_FMT_MOD_TILE_GFX9_64K_S_X 25
@@ -1573,6 +1576,21 @@ drm_fourcc_canonicalize_nvidia_format_mod(__u64 modifier)
#define AMD_FMT_MOD_TILE_GFX9_64K_R_X 27
#define AMD_FMT_MOD_TILE_GFX11_256K_R_X 31
+/* Gfx12 swizzle modes:
+ * 0 - LINEAR
+ * 1 - 256B_2D - 2D block dimensions
+ * 2 - 4KB_2D
+ * 3 - 64KB_2D
+ * 4 - 256KB_2D
+ * 5 - 4KB_3D - 3D block dimensions
+ * 6 - 64KB_3D
+ * 7 - 256KB_3D
+ */
+#define AMD_FMT_MOD_TILE_GFX12_256B_2D 1
+#define AMD_FMT_MOD_TILE_GFX12_4K_2D 2
+#define AMD_FMT_MOD_TILE_GFX12_64K_2D 3
+#define AMD_FMT_MOD_TILE_GFX12_256K_2D 4
+
#define AMD_FMT_MOD_DCC_BLOCK_64B 0
#define AMD_FMT_MOD_DCC_BLOCK_128B 1
#define AMD_FMT_MOD_DCC_BLOCK_256B 2
diff --git a/include/linux/rkisp1-config.h b/include/linux/rkisp1-config.h
index f87c6bd4..edbc6cb6 100644
--- a/include/linux/rkisp1-config.h
+++ b/include/linux/rkisp1-config.h
@@ -165,6 +165,11 @@
#define RKISP1_CIF_ISP_DPF_MAX_SPATIAL_COEFFS 6
/*
+ * Compand
+ */
+#define RKISP1_CIF_ISP_COMPAND_NUM_POINTS 64
+
+/*
* Measurement types
*/
#define RKISP1_CIF_ISP_STAT_AWB (1U << 0)
@@ -851,6 +856,39 @@ struct rkisp1_params_cfg {
struct rkisp1_cif_isp_isp_other_cfg others;
};
+/**
+ * struct rkisp1_cif_isp_compand_bls_config - Rockchip ISP1 Companding parameters (BLS)
+ * @r: Fixed subtraction value for Bayer pattern R
+ * @gr: Fixed subtraction value for Bayer pattern Gr
+ * @gb: Fixed subtraction value for Bayer pattern Gb
+ * @b: Fixed subtraction value for Bayer pattern B
+ *
+ * The values will be subtracted from the sensor values. Note that unlike the
+ * dedicated BLS block, the BLS values in the compander are 20-bit unsigned.
+ */
+struct rkisp1_cif_isp_compand_bls_config {
+ __u32 r;
+ __u32 gr;
+ __u32 gb;
+ __u32 b;
+};
+
+/**
+ * struct rkisp1_cif_isp_compand_curve_config - Rockchip ISP1 Companding
+ * parameters (expand and compression curves)
+ * @px: Compand curve x-values. Each value stores the distance from the
+ * previous x-value, expressed as log2 of the distance on 5 bits.
+ * @x: Compand curve x-values. The functionality of these parameters are
+ * unknown due to do a lack of hardware documentation, but these are left
+ * here for future compatibility purposes.
+ * @y: Compand curve y-values
+ */
+struct rkisp1_cif_isp_compand_curve_config {
+ __u8 px[RKISP1_CIF_ISP_COMPAND_NUM_POINTS];
+ __u32 x[RKISP1_CIF_ISP_COMPAND_NUM_POINTS];
+ __u32 y[RKISP1_CIF_ISP_COMPAND_NUM_POINTS];
+};
+
/*---------- PART2: Measurement Statistics ------------*/
/**
@@ -996,4 +1034,544 @@ struct rkisp1_stat_buffer {
struct rkisp1_cif_isp_stat params;
};
+/*---------- PART3: Extensible Configuration Parameters ------------*/
+
+/**
+ * enum rkisp1_ext_params_block_type - RkISP1 extensible params block type
+ *
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS: Black level subtraction
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC: Defect pixel cluster correction
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG: Sensor de-gamma
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN: Auto white balance gains
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT: ISP filtering
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM: Bayer de-mosaic
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK: Cross-talk correction
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC: Gamma out correction
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF: De-noise pre-filter
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH: De-noise pre-filter strength
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC: Color processing
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_IE: Image effects
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC: Lens shading correction
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS: Auto white balance statistics
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS: Histogram statistics
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS: Auto exposure statistics
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS: Auto-focus statistics
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS: BLS in the compand block
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND: Companding expand curve
+ * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS: Companding compress curve
+ */
+enum rkisp1_ext_params_block_type {
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_IE,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND,
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS,
+};
+
+#define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0)
+#define RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE (1U << 1)
+
+/**
+ * struct rkisp1_ext_params_block_header - RkISP1 extensible parameters block
+ * header
+ *
+ * This structure represents the common part of all the ISP configuration
+ * blocks. Each parameters block shall embed an instance of this structure type
+ * as its first member, followed by the block-specific configuration data. The
+ * driver inspects this common header to discern the block type and its size and
+ * properly handle the block content by casting it to the correct block-specific
+ * type.
+ *
+ * The @type field is one of the values enumerated by
+ * :c:type:`rkisp1_ext_params_block_type` and specifies how the data should be
+ * interpreted by the driver. The @size field specifies the size of the
+ * parameters block and is used by the driver for validation purposes.
+ *
+ * The @flags field is a bitmask of per-block flags RKISP1_EXT_PARAMS_FL_*.
+ *
+ * When userspace wants to configure and enable an ISP block it shall fully
+ * populate the block configuration and set the
+ * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE bit in the @flags field.
+ *
+ * When userspace simply wants to disable an ISP block the
+ * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bit should be set in @flags field. The
+ * driver ignores the rest of the block configuration structure in this case.
+ *
+ * If a new configuration of an ISP block has to be applied userspace shall
+ * fully populate the ISP block configuration and omit setting the
+ * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits
+ * in the @flags field.
+ *
+ * Setting both the RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and
+ * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits in the @flags field is not allowed
+ * and not accepted by the driver.
+ *
+ * Userspace is responsible for correctly populating the parameters block header
+ * fields (@type, @flags and @size) and the block-specific parameters.
+ *
+ * For example:
+ *
+ * .. code-block:: c
+ *
+ * void populate_bls(struct rkisp1_ext_params_block_header *block) {
+ * struct rkisp1_ext_params_bls_config *bls =
+ * (struct rkisp1_ext_params_bls_config *)block;
+ *
+ * bls->header.type = RKISP1_EXT_PARAMS_BLOCK_ID_BLS;
+ * bls->header.flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE;
+ * bls->header.size = sizeof(*bls);
+ *
+ * bls->config.enable_auto = 0;
+ * bls->config.fixed_val.r = blackLevelRed_;
+ * bls->config.fixed_val.gr = blackLevelGreenR_;
+ * bls->config.fixed_val.gb = blackLevelGreenB_;
+ * bls->config.fixed_val.b = blackLevelBlue_;
+ * }
+ *
+ * @type: The parameters block type, see
+ * :c:type:`rkisp1_ext_params_block_type`
+ * @flags: A bitmask of block flags
+ * @size: Size (in bytes) of the parameters block, including this header
+ */
+struct rkisp1_ext_params_block_header {
+ __u16 type;
+ __u16 flags;
+ __u32 size;
+};
+
+/**
+ * struct rkisp1_ext_params_bls_config - RkISP1 extensible params BLS config
+ *
+ * RkISP1 extensible parameters Black Level Subtraction configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Black Level Subtraction configuration, see
+ * :c:type:`rkisp1_cif_isp_bls_config`
+ */
+struct rkisp1_ext_params_bls_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_bls_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_dpcc_config - RkISP1 extensible params DPCC config
+ *
+ * RkISP1 extensible parameters Defective Pixel Cluster Correction configuration
+ * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Defective Pixel Cluster Correction configuration, see
+ * :c:type:`rkisp1_cif_isp_dpcc_config`
+ */
+struct rkisp1_ext_params_dpcc_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_dpcc_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_sdg_config - RkISP1 extensible params SDG config
+ *
+ * RkISP1 extensible parameters Sensor Degamma configuration block. Identified
+ * by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_SDG`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Sensor Degamma configuration, see
+ * :c:type:`rkisp1_cif_isp_sdg_config`
+ */
+struct rkisp1_ext_params_sdg_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_sdg_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_lsc_config - RkISP1 extensible params LSC config
+ *
+ * RkISP1 extensible parameters Lens Shading Correction configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_LSC`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Lens Shading Correction configuration, see
+ * :c:type:`rkisp1_cif_isp_lsc_config`
+ */
+struct rkisp1_ext_params_lsc_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_lsc_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_awb_gain_config - RkISP1 extensible params AWB
+ * gain config
+ *
+ * RkISP1 extensible parameters Auto-White Balance Gains configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_GAIN`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Auto-White Balance Gains configuration, see
+ * :c:type:`rkisp1_cif_isp_awb_gain_config`
+ */
+struct rkisp1_ext_params_awb_gain_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_awb_gain_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_flt_config - RkISP1 extensible params FLT config
+ *
+ * RkISP1 extensible parameters Filter configuration block. Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_FLT`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Filter configuration, see :c:type:`rkisp1_cif_isp_flt_config`
+ */
+struct rkisp1_ext_params_flt_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_flt_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_bdm_config - RkISP1 extensible params BDM config
+ *
+ * RkISP1 extensible parameters Demosaicing configuration block. Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_BDM`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Demosaicing configuration, see :c:type:`rkisp1_cif_isp_bdm_config`
+ */
+struct rkisp1_ext_params_bdm_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_bdm_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_ctk_config - RkISP1 extensible params CTK config
+ *
+ * RkISP1 extensible parameters Cross-Talk configuration block. Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_CTK`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Cross-Talk configuration, see :c:type:`rkisp1_cif_isp_ctk_config`
+ */
+struct rkisp1_ext_params_ctk_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_ctk_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_goc_config - RkISP1 extensible params GOC config
+ *
+ * RkISP1 extensible parameters Gamma-Out configuration block. Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_GOC`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Gamma-Out configuration, see :c:type:`rkisp1_cif_isp_goc_config`
+ */
+struct rkisp1_ext_params_goc_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_goc_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_dpf_config - RkISP1 extensible params DPF config
+ *
+ * RkISP1 extensible parameters De-noise Pre-Filter configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: De-noise Pre-Filter configuration, see
+ * :c:type:`rkisp1_cif_isp_dpf_config`
+ */
+struct rkisp1_ext_params_dpf_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_dpf_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_dpf_strength_config - RkISP1 extensible params DPF
+ * strength config
+ *
+ * RkISP1 extensible parameters De-noise Pre-Filter strength configuration
+ * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_DPF_STRENGTH`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: De-noise Pre-Filter strength configuration, see
+ * :c:type:`rkisp1_cif_isp_dpf_strength_config`
+ */
+struct rkisp1_ext_params_dpf_strength_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_dpf_strength_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_cproc_config - RkISP1 extensible params CPROC config
+ *
+ * RkISP1 extensible parameters Color Processing configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_CPROC`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Color processing configuration, see
+ * :c:type:`rkisp1_cif_isp_cproc_config`
+ */
+struct rkisp1_ext_params_cproc_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_cproc_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_ie_config - RkISP1 extensible params IE config
+ *
+ * RkISP1 extensible parameters Image Effect configuration block. Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_IE`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Image Effect configuration, see :c:type:`rkisp1_cif_isp_ie_config`
+ */
+struct rkisp1_ext_params_ie_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_ie_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_awb_meas_config - RkISP1 extensible params AWB
+ * Meas config
+ *
+ * RkISP1 extensible parameters Auto-White Balance Measurement configuration
+ * block. Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AWB_MEAS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Auto-White Balance measure configuration, see
+ * :c:type:`rkisp1_cif_isp_awb_meas_config`
+ */
+struct rkisp1_ext_params_awb_meas_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_awb_meas_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_hst_config - RkISP1 extensible params Histogram config
+ *
+ * RkISP1 extensible parameters Histogram statistics configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_HST_MEAS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Histogram statistics configuration, see
+ * :c:type:`rkisp1_cif_isp_hst_config`
+ */
+struct rkisp1_ext_params_hst_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_hst_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_aec_config - RkISP1 extensible params AEC config
+ *
+ * RkISP1 extensible parameters Auto-Exposure statistics configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AEC_MEAS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Auto-Exposure statistics configuration, see
+ * :c:type:`rkisp1_cif_isp_aec_config`
+ */
+struct rkisp1_ext_params_aec_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_aec_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_afc_config - RkISP1 extensible params AFC config
+ *
+ * RkISP1 extensible parameters Auto-Focus statistics configuration block.
+ * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_AFC_MEAS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Auto-Focus statistics configuration, see
+ * :c:type:`rkisp1_cif_isp_afc_config`
+ */
+struct rkisp1_ext_params_afc_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_afc_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_compand_bls_config - RkISP1 extensible params
+ * Compand BLS config
+ *
+ * RkISP1 extensible parameters Companding configuration block (black level
+ * subtraction). Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Companding BLS configuration, see
+ * :c:type:`rkisp1_cif_isp_compand_bls_config`
+ */
+struct rkisp1_ext_params_compand_bls_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_compand_bls_config config;
+} __attribute__((aligned(8)));
+
+/**
+ * struct rkisp1_ext_params_compand_curve_config - RkISP1 extensible params
+ * Compand curve config
+ *
+ * RkISP1 extensible parameters Companding configuration block (expand and
+ * compression curves). Identified by
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND` or
+ * :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS`.
+ *
+ * @header: The RkISP1 extensible parameters header, see
+ * :c:type:`rkisp1_ext_params_block_header`
+ * @config: Companding curve configuration, see
+ * :c:type:`rkisp1_cif_isp_compand_curve_config`
+ */
+struct rkisp1_ext_params_compand_curve_config {
+ struct rkisp1_ext_params_block_header header;
+ struct rkisp1_cif_isp_compand_curve_config config;
+} __attribute__((aligned(8)));
+
+/*
+ * The rkisp1_ext_params_compand_curve_config structure is counted twice as it
+ * is used for both the COMPAND_EXPAND and COMPAND_COMPRESS block types.
+ */
+#define RKISP1_EXT_PARAMS_MAX_SIZE \
+ (sizeof(struct rkisp1_ext_params_bls_config) +\
+ sizeof(struct rkisp1_ext_params_dpcc_config) +\
+ sizeof(struct rkisp1_ext_params_sdg_config) +\
+ sizeof(struct rkisp1_ext_params_lsc_config) +\
+ sizeof(struct rkisp1_ext_params_awb_gain_config) +\
+ sizeof(struct rkisp1_ext_params_flt_config) +\
+ sizeof(struct rkisp1_ext_params_bdm_config) +\
+ sizeof(struct rkisp1_ext_params_ctk_config) +\
+ sizeof(struct rkisp1_ext_params_goc_config) +\
+ sizeof(struct rkisp1_ext_params_dpf_config) +\
+ sizeof(struct rkisp1_ext_params_dpf_strength_config) +\
+ sizeof(struct rkisp1_ext_params_cproc_config) +\
+ sizeof(struct rkisp1_ext_params_ie_config) +\
+ sizeof(struct rkisp1_ext_params_awb_meas_config) +\
+ sizeof(struct rkisp1_ext_params_hst_config) +\
+ sizeof(struct rkisp1_ext_params_aec_config) +\
+ sizeof(struct rkisp1_ext_params_afc_config) +\
+ sizeof(struct rkisp1_ext_params_compand_bls_config) +\
+ sizeof(struct rkisp1_ext_params_compand_curve_config) +\
+ sizeof(struct rkisp1_ext_params_compand_curve_config))
+
+/**
+ * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version
+ *
+ * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters
+ */
+enum rksip1_ext_param_buffer_version {
+ RKISP1_EXT_PARAM_BUFFER_V1 = 1,
+};
+
+/**
+ * struct rkisp1_ext_params_cfg - RkISP1 extensible parameters configuration
+ *
+ * This struct contains the configuration parameters of the RkISP1 ISP
+ * algorithms, serialized by userspace into a data buffer. Each configuration
+ * parameter block is represented by a block-specific structure which contains a
+ * :c:type:`rkisp1_ext_params_block_header` entry as first member. Userspace
+ * populates the @data buffer with configuration parameters for the blocks that
+ * it intends to configure. As a consequence, the data buffer effective size
+ * changes according to the number of ISP blocks that userspace intends to
+ * configure and is set by userspace in the @data_size field.
+ *
+ * The parameters buffer is versioned by the @version field to allow modifying
+ * and extending its definition. Userspace shall populate the @version field to
+ * inform the driver about the version it intends to use. The driver will parse
+ * and handle the @data buffer according to the data layout specific to the
+ * indicated version and return an error if the desired version is not
+ * supported.
+ *
+ * Currently the single RKISP1_EXT_PARAM_BUFFER_V1 version is supported.
+ * When a new format version will be added, a mechanism for userspace to query
+ * the supported format versions will be implemented in the form of a read-only
+ * V4L2 control. If such control is not available, userspace should assume only
+ * RKISP1_EXT_PARAM_BUFFER_V1 is supported by the driver.
+ *
+ * For each ISP block that userspace wants to configure, a block-specific
+ * structure is appended to the @data buffer, one after the other without gaps
+ * in between nor overlaps. Userspace shall populate the @data_size field with
+ * the effective size, in bytes, of the @data buffer.
+ *
+ * The expected memory layout of the parameters buffer is::
+ *
+ * +-------------------- struct rkisp1_ext_params_cfg -------------------+
+ * | version = RKISP_EXT_PARAMS_BUFFER_V1; |
+ * | data_size = sizeof(struct rkisp1_ext_params_bls_config) |
+ * | + sizeof(struct rkisp1_ext_params_dpcc_config); |
+ * | +------------------------- data ---------------------------------+ |
+ * | | +------------- struct rkisp1_ext_params_bls_config -----------+ | |
+ * | | | +-------- struct rkisp1_ext_params_block_header ---------+ | | |
+ * | | | | type = RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS; | | | |
+ * | | | | flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; | | | |
+ * | | | | size = sizeof(struct rkisp1_ext_params_bls_config); | | | |
+ * | | | +---------------------------------------------------------+ | | |
+ * | | | +---------- struct rkisp1_cif_isp_bls_config -------------+ | | |
+ * | | | | enable_auto = 0; | | | |
+ * | | | | fixed_val.r = 256; | | | |
+ * | | | | fixed_val.gr = 256; | | | |
+ * | | | | fixed_val.gb = 256; | | | |
+ * | | | | fixed_val.b = 256; | | | |
+ * | | | +---------------------------------------------------------+ | | |
+ * | | +------------ struct rkisp1_ext_params_dpcc_config -----------+ | |
+ * | | | +-------- struct rkisp1_ext_params_block_header ---------+ | | |
+ * | | | | type = RKISP1_EXT_PARAMS_BLOCK_TYPE_DPCC; | | | |
+ * | | | | flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; | | | |
+ * | | | | size = sizeof(struct rkisp1_ext_params_dpcc_config); | | | |
+ * | | | +---------------------------------------------------------+ | | |
+ * | | | +---------- struct rkisp1_cif_isp_dpcc_config ------------+ | | |
+ * | | | | mode = RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE; | | | |
+ * | | | | output_mode = | | | |
+ * | | | | RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_STAGE1_INCL_G_CENTER; | | | |
+ * | | | | set_use = ... ; | | | |
+ * | | | | ... = ... ; | | | |
+ * | | | +---------------------------------------------------------+ | | |
+ * | | +-------------------------------------------------------------+ | |
+ * | +-----------------------------------------------------------------+ |
+ * +---------------------------------------------------------------------+
+ *
+ * @version: The RkISP1 extensible parameters buffer version, see
+ * :c:type:`rksip1_ext_param_buffer_version`
+ * @data_size: The RkISP1 configuration data effective size, excluding this
+ * header
+ * @data: The RkISP1 extensible configuration data blocks
+ */
+struct rkisp1_ext_params_cfg {
+ __u32 version;
+ __u32 data_size;
+ __u8 data[RKISP1_EXT_PARAMS_MAX_SIZE];
+};
+
#endif /* _RKISP1_CONFIG_H */
diff --git a/include/linux/v4l2-controls.h b/include/linux/v4l2-controls.h
index 1e6e816b..882a8180 100644
--- a/include/linux/v4l2-controls.h
+++ b/include/linux/v4l2-controls.h
@@ -898,6 +898,8 @@ enum v4l2_mpeg_video_av1_level {
V4L2_MPEG_VIDEO_AV1_LEVEL_7_3 = 23
};
+#define V4L2_CID_MPEG_VIDEO_AVERAGE_QP (V4L2_CID_CODEC_BASE + 657)
+
/* MPEG-class control IDs specific to the CX2341x driver as defined by V4L2 */
#define V4L2_CID_CODEC_CX2341X_BASE (V4L2_CTRL_CLASS_CODEC | 0x1000)
#define V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE (V4L2_CID_CODEC_CX2341X_BASE+0)
diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index 7fe522e8..3829c0b6 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -829,9 +829,10 @@ struct v4l2_pix_format {
/* Vendor specific - used for RK_ISP1 camera sub-system */
#define V4L2_META_FMT_RK_ISP1_PARAMS v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */
#define V4L2_META_FMT_RK_ISP1_STAT_3A v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */
+#define V4L2_META_FMT_RK_ISP1_EXT_PARAMS v4l2_fourcc('R', 'K', '1', 'E') /* Rockchip ISP1 3a Extensible Parameters */
-/* The metadata format identifier for BE configuration buffers. */
-#define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C')
+/* Vendor specific - used for RaspberryPi PiSP */
+#define V4L2_META_FMT_RPI_BE_CFG v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */
/* The metadata format identifier for FE configuration buffers. */
#define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C')
diff --git a/meson.build b/meson.build
index 0ef4cdaa..63e45465 100644
--- a/meson.build
+++ b/meson.build
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: CC0-1.0
project('libcamera', 'c', 'cpp',
- meson_version : '>= 0.60',
- version : '0.3.0',
+ meson_version : '>= 0.63',
+ version : '0.3.2',
default_options : [
'werror=true',
'warning_level=2',
@@ -90,11 +90,21 @@ if cc.has_header_symbol('sys/mman.h', 'memfd_create', prefix : '#define _GNU_SOU
config_h.set('HAVE_MEMFD_CREATE', 1)
endif
+ioctl_posix_test = '''
+#include <sys/ioctl.h>
+int ioctl (int, int, ...);
+'''
+
+if cc.compiles(ioctl_posix_test)
+ config_h.set('HAVE_POSIX_IOCTL', 1)
+endif
+
if cc.has_header_symbol('stdlib.h', 'secure_getenv', prefix : '#define _GNU_SOURCE')
config_h.set('HAVE_SECURE_GETENV', 1)
endif
common_arguments = [
+ '-Wmissing-declarations',
'-Wshadow',
'-include', meson.current_build_dir() / 'config.h',
]
@@ -136,16 +146,8 @@ if cc.get_id() == 'clang'
endif
if cc.get_id() == 'gcc'
- if cc.version().version_compare('<8')
- error('gcc version is too old, libcamera requires 8.0 or newer')
- endif
-
- # On gcc 8, the file system library is provided in a separate static
- # library.
if cc.version().version_compare('<9')
- cpp_arguments += [
- '-lstdc++fs',
- ]
+ error('gcc version is too old, libcamera requires 9.0 or newer')
endif
# gcc 13 implements the C++23 version of automatic move from local
@@ -209,7 +211,7 @@ pipelines_support = {
'mali-c55': arch_arm,
'rkisp1': arch_arm,
'rpi/vc4': arch_arm,
- 'simple': arch_arm,
+ 'simple': ['any'],
'uvcvideo': ['any'],
'vimc': ['test'],
}
@@ -276,8 +278,8 @@ py_mod.find_installation('python3', modules : py_modules)
summary({
'Enabled pipelines': pipelines,
'Enabled IPA modules': enabled_ipa_names,
- 'Controls files': controls_files,
- 'Properties files': properties_files,
+ 'Controls files': controls_files_names,
+ 'Properties files': properties_files_names,
'Hotplug support': libudev.found(),
'Tracing support': tracing_enabled,
'Android support': android_enabled,
diff --git a/src/android/jpeg/encoder_libjpeg.cpp b/src/android/jpeg/encoder_libjpeg.cpp
index 7fc6287e..cb242b5e 100644
--- a/src/android/jpeg/encoder_libjpeg.cpp
+++ b/src/android/jpeg/encoder_libjpeg.cpp
@@ -125,7 +125,7 @@ void EncoderLibJpeg::compressRGB(const std::vector<Span<uint8_t>> &planes)
*/
void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
{
- uint8_t tmprowbuf[compress_.image_width * 3];
+ std::vector<uint8_t> tmprowbuf(compress_.image_width * 3);
/*
* \todo Use the raw api, and only unpack the cb/cr samples to new line
@@ -149,10 +149,10 @@ void EncoderLibJpeg::compressNV(const std::vector<Span<uint8_t>> &planes)
const unsigned char *src_c = planes[1].data();
JSAMPROW row_pointer[1];
- row_pointer[0] = &tmprowbuf[0];
+ row_pointer[0] = tmprowbuf.data();
for (unsigned int y = 0; y < compress_.image_height; y++) {
- unsigned char *dst = &tmprowbuf[0];
+ unsigned char *dst = tmprowbuf.data();
const unsigned char *src_y = src + y * y_stride;
const unsigned char *src_cb = src_c + (y / vertSubSample) * c_stride + cb_pos;
diff --git a/src/apps/cam/camera_session.cpp b/src/apps/cam/camera_session.cpp
index 097dc479..d24ecbb3 100644
--- a/src/apps/cam/camera_session.cpp
+++ b/src/apps/cam/camera_session.cpp
@@ -159,8 +159,34 @@ CameraSession::~CameraSession()
void CameraSession::listControls() const
{
for (const auto &[id, info] : camera_->controls()) {
- std::cout << "Control: " << id->name() << ": "
- << info.toString() << std::endl;
+ if (info.values().empty()) {
+ std::cout << "Control: " << id->name() << ": "
+ << info.toString() << std::endl;
+ } else {
+ std::cout << "Control: " << id->name() << ":" << std::endl;
+ for (const auto &value : info.values()) {
+ int32_t val = value.get<int32_t>();
+ const auto &it = id->enumerators().find(val);
+
+ std::cout << " - ";
+ if (it == id->enumerators().end())
+ std::cout << "UNKNOWN";
+ else
+ std::cout << it->second;
+ std::cout << " (" << val << ")" << std::endl;
+ }
+ }
+
+ if (id->isArray()) {
+ std::size_t size = id->size();
+
+ std::cout << " Size: ";
+ if (size == std::numeric_limits<std::size_t>::max())
+ std::cout << "n";
+ else
+ std::cout << std::to_string(size);
+ std::cout << std::endl;
+ }
}
}
diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp
index 4f87f200..460dbc81 100644
--- a/src/apps/cam/main.cpp
+++ b/src/apps/cam/main.cpp
@@ -344,12 +344,16 @@ std::string CamApp::cameraName(const Camera *camera)
return name;
}
+namespace {
+
void signalHandler([[maybe_unused]] int signal)
{
std::cout << "Exiting" << std::endl;
CamApp::instance()->quit();
}
+} /* namespace */
+
int main(int argc, char **argv)
{
CamApp app;
diff --git a/src/apps/common/dng_writer.cpp b/src/apps/common/dng_writer.cpp
index 59f1fa23..ac461951 100644
--- a/src/apps/common/dng_writer.cpp
+++ b/src/apps/common/dng_writer.cpp
@@ -8,8 +8,10 @@
#include "dng_writer.h"
#include <algorithm>
+#include <endian.h>
#include <iostream>
#include <map>
+#include <vector>
#include <tiffio.h>
@@ -126,7 +128,9 @@ struct Matrix3d {
float m[9];
};
-void packScanlineSBGGR8(void *output, const void *input, unsigned int width)
+namespace {
+
+void packScanlineRaw8(void *output, const void *input, unsigned int width)
{
const uint8_t *in = static_cast<const uint8_t *>(input);
uint8_t *out = static_cast<uint8_t *>(output);
@@ -134,7 +138,67 @@ void packScanlineSBGGR8(void *output, const void *input, unsigned int width)
std::copy(in, in + width, out);
}
-void packScanlineSBGGR10P(void *output, const void *input, unsigned int width)
+void packScanlineRaw10(void *output, const void *input, unsigned int width)
+{
+ const uint8_t *in = static_cast<const uint8_t *>(input);
+ uint8_t *out = static_cast<uint8_t *>(output);
+
+ for (unsigned int i = 0; i < width; i += 4) {
+ *out++ = in[1] << 6 | in[0] >> 2;
+ *out++ = in[0] << 6 | (in[3] & 0x03) << 4 | in[2] >> 4;
+ *out++ = in[2] << 4 | (in[5] & 0x03) << 2 | in[4] >> 6;
+ *out++ = in[4] << 2 | (in[7] & 0x03) << 0;
+ *out++ = in[6];
+ in += 8;
+ }
+}
+
+void packScanlineRaw12(void *output, const void *input, unsigned int width)
+{
+ const uint8_t *in = static_cast<const uint8_t *>(input);
+ uint8_t *out = static_cast<uint8_t *>(output);
+
+ for (unsigned int i = 0; i < width; i += 2) {
+ *out++ = in[1] << 4 | in[0] >> 4;
+ *out++ = in[0] << 4 | (in[3] & 0x0f);
+ *out++ = in[2];
+ in += 4;
+ }
+}
+
+void packScanlineRaw16(void *output, const void *input, unsigned int width)
+{
+ const uint16_t *in = static_cast<const uint16_t *>(input);
+ uint16_t *out = static_cast<uint16_t *>(output);
+
+ std::copy(in, in + width, out);
+}
+
+/* Thumbnail function for raw data with each pixel aligned to 16bit. */
+void thumbScanlineRaw(const FormatInfo &info, void *output, const void *input,
+ unsigned int width, unsigned int stride)
+{
+ const uint16_t *in = static_cast<const uint16_t *>(input);
+ const uint16_t *in2 = static_cast<const uint16_t *>(input) + stride / 2;
+ uint8_t *out = static_cast<uint8_t *>(output);
+
+ /* Shift down to 8. */
+ unsigned int shift = info.bitsPerSample - 8;
+
+ /* Simple averaging that produces greyscale RGB values. */
+ for (unsigned int x = 0; x < width; x++) {
+ uint16_t value = (le16toh(in[0]) + le16toh(in[1]) +
+ le16toh(in2[0]) + le16toh(in2[1])) >> 2;
+ value = value >> shift;
+ *out++ = value;
+ *out++ = value;
+ *out++ = value;
+ in += 16;
+ in2 += 16;
+ }
+}
+
+void packScanlineRaw10_CSI2P(void *output, const void *input, unsigned int width)
{
const uint8_t *in = static_cast<const uint8_t *>(input);
uint8_t *out = static_cast<uint8_t *>(output);
@@ -150,7 +214,7 @@ void packScanlineSBGGR10P(void *output, const void *input, unsigned int width)
}
}
-void packScanlineSBGGR12P(void *output, const void *input, unsigned int width)
+void packScanlineRaw12_CSI2P(void *output, const void *input, unsigned int width)
{
const uint8_t *in = static_cast<const uint8_t *>(input);
uint8_t *out = static_cast<uint8_t *>(output);
@@ -164,7 +228,7 @@ void packScanlineSBGGR12P(void *output, const void *input, unsigned int width)
}
}
-void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output,
+void thumbScanlineRaw_CSI2P(const FormatInfo &info, void *output,
const void *input, unsigned int width,
unsigned int stride)
{
@@ -282,78 +346,150 @@ void thumbScanlineIPU3([[maybe_unused]] const FormatInfo &info, void *output,
}
}
-static const std::map<PixelFormat, FormatInfo> formatInfo = {
+const std::map<PixelFormat, FormatInfo> formatInfo = {
{ formats::SBGGR8, {
.bitsPerSample = 8,
.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw8,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGBRG8, {
.bitsPerSample = 8,
.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw8,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGRBG8, {
.bitsPerSample = 8,
.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw8,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SRGGB8, {
.bitsPerSample = 8,
.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR8,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw8,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
+ } },
+ { formats::SBGGR10, {
+ .bitsPerSample = 10,
+ .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
+ .packScanline = packScanlineRaw10,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGBRG10, {
+ .bitsPerSample = 10,
+ .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
+ .packScanline = packScanlineRaw10,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGRBG10, {
+ .bitsPerSample = 10,
+ .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
+ .packScanline = packScanlineRaw10,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SRGGB10, {
+ .bitsPerSample = 10,
+ .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
+ .packScanline = packScanlineRaw10,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SBGGR12, {
+ .bitsPerSample = 12,
+ .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
+ .packScanline = packScanlineRaw12,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGBRG12, {
+ .bitsPerSample = 12,
+ .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
+ .packScanline = packScanlineRaw12,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGRBG12, {
+ .bitsPerSample = 12,
+ .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
+ .packScanline = packScanlineRaw12,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SRGGB12, {
+ .bitsPerSample = 12,
+ .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
+ .packScanline = packScanlineRaw12,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SBGGR16, {
+ .bitsPerSample = 16,
+ .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
+ .packScanline = packScanlineRaw16,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGBRG16, {
+ .bitsPerSample = 16,
+ .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
+ .packScanline = packScanlineRaw16,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SGRBG16, {
+ .bitsPerSample = 16,
+ .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
+ .packScanline = packScanlineRaw16,
+ .thumbScanline = thumbScanlineRaw,
+ } },
+ { formats::SRGGB16, {
+ .bitsPerSample = 16,
+ .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
+ .packScanline = packScanlineRaw16,
+ .thumbScanline = thumbScanlineRaw,
} },
{ formats::SBGGR10_CSI2P, {
.bitsPerSample = 10,
.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw10_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGBRG10_CSI2P, {
.bitsPerSample = 10,
.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw10_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGRBG10_CSI2P, {
.bitsPerSample = 10,
.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw10_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SRGGB10_CSI2P, {
.bitsPerSample = 10,
.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR10P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw10_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SBGGR12_CSI2P, {
.bitsPerSample = 12,
.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw12_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGBRG12_CSI2P, {
.bitsPerSample = 12,
.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw12_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SGRBG12_CSI2P, {
.bitsPerSample = 12,
.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw12_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SRGGB12_CSI2P, {
.bitsPerSample = 12,
.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },
- .packScanline = packScanlineSBGGR12P,
- .thumbScanline = thumbScanlineSBGGRxxP,
+ .packScanline = packScanlineRaw12_CSI2P,
+ .thumbScanline = thumbScanlineRaw_CSI2P,
} },
{ formats::SBGGR10_IPU3, {
.bitsPerSample = 16,
@@ -381,6 +517,8 @@ static const std::map<PixelFormat, FormatInfo> formatInfo = {
} },
};
+} /* namespace */
+
int DNGWriter::write(const char *filename, const Camera *camera,
const StreamConfiguration &config,
const ControlList &metadata,
@@ -407,7 +545,7 @@ int DNGWriter::write(const char *filename, const Camera *camera,
* or a thumbnail scanline. The latter will always be much smaller than
* the former as we downscale by 16 in both directions.
*/
- uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];
+ std::vector<uint8_t> scanline((config.size.width * info->bitsPerSample + 7) / 8);
toff_t rawIFDOffset = 0;
toff_t exifIFDOffset = 0;
@@ -507,10 +645,10 @@ int DNGWriter::write(const char *filename, const Camera *camera,
/* Write the thumbnail. */
const uint8_t *row = static_cast<const uint8_t *>(data);
for (unsigned int y = 0; y < config.size.height / 16; y++) {
- info->thumbScanline(*info, &scanline, row,
+ info->thumbScanline(*info, scanline.data(), row,
config.size.width / 16, config.stride);
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
+ if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) {
std::cerr << "Failed to write thumbnail scanline"
<< std::endl;
TIFFClose(tif);
@@ -522,6 +660,23 @@ int DNGWriter::write(const char *filename, const Camera *camera,
TIFFWriteDirectory(tif);
+ /*
+ * Workaround for a bug introduced in libtiff version 4.5.1 and no fix
+ * released. In these versions the CFA* tags were missing in the field
+ * info.
+ * Introduced by: https://gitlab.com/libtiff/libtiff/-/commit/738e04099b13192bb1f654e74e9b5829313f3161
+ * Fixed by: https://gitlab.com/libtiff/libtiff/-/commit/49856998c3d82e65444b47bb4fb11b7830a0c2be
+ */
+ if (!TIFFFindField(tif, TIFFTAG_CFAREPEATPATTERNDIM, TIFF_ANY)) {
+ static const TIFFFieldInfo infos[] = {
+ { TIFFTAG_CFAREPEATPATTERNDIM, 2, 2, TIFF_SHORT, FIELD_CUSTOM,
+ 1, 0, const_cast<char *>("CFARepeatPatternDim") },
+ { TIFFTAG_CFAPATTERN, -1, -1, TIFF_BYTE, FIELD_CUSTOM,
+ 1, 1, const_cast<char *>("CFAPattern") },
+ };
+ TIFFMergeFieldInfo(tif, infos, 2);
+ }
+
/* Create a new IFD for the RAW image. */
const uint16_t cfaRepeatPatternDim[] = { 2, 2 };
const uint8_t cfaPlaneColor[] = {
@@ -593,9 +748,9 @@ int DNGWriter::write(const char *filename, const Camera *camera,
/* Write RAW content. */
row = static_cast<const uint8_t *>(data);
for (unsigned int y = 0; y < config.size.height; y++) {
- info->packScanline(&scanline, row, config.size.width);
+ info->packScanline(scanline.data(), row, config.size.width);
- if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {
+ if (TIFFWriteScanline(tif, scanline.data(), y, 0) != 1) {
std::cerr << "Failed to write RAW scanline"
<< std::endl;
TIFFClose(tif);
diff --git a/src/apps/common/options.cpp b/src/apps/common/options.cpp
index ab19aa3d..ece268d0 100644
--- a/src/apps/common/options.cpp
+++ b/src/apps/common/options.cpp
@@ -10,6 +10,7 @@
#include <iomanip>
#include <iostream>
#include <string.h>
+#include <vector>
#include "options.h"
@@ -879,8 +880,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
* Allocate short and long options arrays large enough to contain all
* options.
*/
- char shortOptions[optionsMap_.size() * 3 + 2];
- struct option longOptions[optionsMap_.size() + 1];
+ std::vector<char> shortOptions(optionsMap_.size() * 3 + 2);
+ std::vector<struct option> longOptions(optionsMap_.size() + 1);
unsigned int ids = 0;
unsigned int idl = 0;
@@ -922,7 +923,8 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
opterr = 0;
while (true) {
- int c = getopt_long(argc, argv, shortOptions, longOptions, nullptr);
+ int c = getopt_long(argc, argv, shortOptions.data(),
+ longOptions.data(), nullptr);
if (c == -1)
break;
diff --git a/src/apps/qcam/assets/shader/bayer_8.vert b/src/apps/qcam/assets/shader/bayer_8.vert
index 3695a5e9..fb5109ee 100644
--- a/src/apps/qcam/assets/shader/bayer_8.vert
+++ b/src/apps/qcam/assets/shader/bayer_8.vert
@@ -19,6 +19,8 @@ Copyright (C) 2021, Linaro
attribute vec4 vertexIn;
attribute vec2 textureIn;
+uniform mat4 proj_matrix;
+
uniform vec2 tex_size; /* The texture size in pixels */
uniform vec2 tex_step;
@@ -47,5 +49,5 @@ void main(void) {
yCoord = center.y + vec4(-2.0 * tex_step.y,
-tex_step.y, tex_step.y, 2.0 * tex_step.y);
- gl_Position = vertexIn;
+ gl_Position = proj_matrix * vertexIn;
}
diff --git a/src/apps/qcam/assets/shader/identity.vert b/src/apps/qcam/assets/shader/identity.vert
index 12c41377..907e8741 100644
--- a/src/apps/qcam/assets/shader/identity.vert
+++ b/src/apps/qcam/assets/shader/identity.vert
@@ -9,10 +9,11 @@ attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
+uniform mat4 proj_matrix;
uniform float stride_factor;
void main(void)
{
- gl_Position = vertexIn;
+ gl_Position = proj_matrix * vertexIn;
textureOut = vec2(textureIn.x * stride_factor, textureIn.y);
}
diff --git a/src/apps/qcam/cam_select_dialog.cpp b/src/apps/qcam/cam_select_dialog.cpp
index c51f5974..6b6d0713 100644
--- a/src/apps/qcam/cam_select_dialog.cpp
+++ b/src/apps/qcam/cam_select_dialog.cpp
@@ -15,7 +15,9 @@
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
+#include <QGuiApplication>
#include <QLabel>
+#include <QScreen>
#include <QString>
CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManager,
@@ -53,6 +55,14 @@ CameraSelectorDialog::CameraSelectorDialog(libcamera::CameraManager *cameraManag
layout->addRow("Location:", cameraLocation_);
layout->addRow("Model:", cameraModel_);
layout->addWidget(buttonBox);
+
+ /*
+ * Decrease the minimum width of dialog to fit on narrow screens, with a
+ * 20 pixels margin.
+ */
+ QRect screenGeometry = qGuiApp->primaryScreen()->availableGeometry();
+ if (screenGeometry.width() < minimumWidth())
+ setMinimumWidth(screenGeometry.width() - 20);
}
CameraSelectorDialog::~CameraSelectorDialog() = default;
diff --git a/src/apps/qcam/main.cpp b/src/apps/qcam/main.cpp
index 9846fba5..d0bde141 100644
--- a/src/apps/qcam/main.cpp
+++ b/src/apps/qcam/main.cpp
@@ -21,6 +21,8 @@
using namespace libcamera;
+namespace {
+
void signalHandler([[maybe_unused]] int signal)
{
qInfo() << "Exiting";
@@ -52,6 +54,8 @@ OptionsParser::Options parseOptions(int argc, char *argv[])
return options;
}
+} /* namespace */
+
int main(int argc, char **argv)
{
QApplication app(argc, argv);
diff --git a/src/apps/qcam/main_window.cpp b/src/apps/qcam/main_window.cpp
index d515beed..dd2aa196 100644
--- a/src/apps/qcam/main_window.cpp
+++ b/src/apps/qcam/main_window.cpp
@@ -37,16 +37,6 @@
using namespace libcamera;
-#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
-/*
- * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow
- * usage of Qt::fixed unconditionally.
- */
-namespace Qt {
-constexpr auto fixed = ::fixed;
-} /* namespace Qt */
-#endif
-
/**
* \brief Custom QEvent to signal capture completion
*/
diff --git a/src/apps/qcam/meson.build b/src/apps/qcam/meson.build
index 6cf4c171..f7c14064 100644
--- a/src/apps/qcam/meson.build
+++ b/src/apps/qcam/meson.build
@@ -1,13 +1,13 @@
# SPDX-License-Identifier: CC0-1.0
-qt5 = import('qt5')
-qt5_dep = dependency('qt5',
+qt6 = import('qt6')
+qt6_dep = dependency('qt6',
method : 'pkg-config',
- modules : ['Core', 'Gui', 'Widgets'],
+ modules : ['Core', 'Gui', 'OpenGL', 'OpenGLWidgets', 'Widgets'],
required : get_option('qcam'),
- version : '>=5.4')
+ version : '>=6.2')
-if not qt5_dep.found()
+if not qt6_dep.found()
qcam_enabled = false
subdir_done()
endif
@@ -20,46 +20,31 @@ qcam_sources = files([
'main.cpp',
'main_window.cpp',
'message_handler.cpp',
+ 'viewfinder_gl.cpp',
'viewfinder_qt.cpp',
])
qcam_moc_headers = files([
'cam_select_dialog.h',
'main_window.h',
+ 'viewfinder_gl.h',
'viewfinder_qt.h',
])
qcam_resources = files([
'assets/feathericons/feathericons.qrc',
+ 'assets/shader/shaders.qrc',
])
-qt5_cpp_args = [apps_cpp_args, '-DQT_NO_KEYWORDS']
+qt6_cpp_args = [
+ apps_cpp_args,
+ '-DQT_NO_KEYWORDS',
+ '-Wno-extra-semi',
+]
-if cxx.has_header_symbol('QOpenGLWidget', 'QOpenGLWidget',
- dependencies : qt5_dep, args : '-fPIC')
- qcam_sources += files([
- 'viewfinder_gl.cpp',
- ])
- qcam_moc_headers += files([
- 'viewfinder_gl.h',
- ])
- qcam_resources += files([
- 'assets/shader/shaders.qrc'
- ])
-endif
-
-# gcc 9 introduced a deprecated-copy warning that is triggered by Qt until
-# Qt 5.13. clang 10 introduced the same warning, but detects more issues
-# that are not fixed in Qt yet. Disable the warning manually in both cases.
-if ((cc.get_id() == 'gcc' and cc.version().version_compare('>=9.0') and
- qt5_dep.version().version_compare('<5.13')) or
- (cc.get_id() == 'clang' and cc.version().version_compare('>=10.0')))
- qt5_cpp_args += ['-Wno-deprecated-copy']
-endif
-
-resources = qt5.preprocess(moc_headers : qcam_moc_headers,
+resources = qt6.preprocess(moc_headers : qcam_moc_headers,
qresources : qcam_resources,
- dependencies : qt5_dep)
+ dependencies : qt6_dep)
qcam = executable('qcam', qcam_sources, resources,
install : true,
@@ -69,6 +54,6 @@ qcam = executable('qcam', qcam_sources, resources,
libatomic,
libcamera_public,
libtiff,
- qt5_dep,
+ qt6_dep,
],
- cpp_args : qt5_cpp_args)
+ cpp_args : qt6_cpp_args)
diff --git a/src/apps/qcam/viewfinder_gl.cpp b/src/apps/qcam/viewfinder_gl.cpp
index 9d2a6960..f31956ff 100644
--- a/src/apps/qcam/viewfinder_gl.cpp
+++ b/src/apps/qcam/viewfinder_gl.cpp
@@ -12,6 +12,7 @@
#include <QByteArray>
#include <QFile>
#include <QImage>
+#include <QMatrix4x4>
#include <QStringList>
#include <libcamera/formats.h>
@@ -443,15 +444,11 @@ bool ViewFinderGL::createFragmentShader()
close();
}
- /* Bind shader pipeline for use */
- if (!shaderProgram_.bind()) {
- qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
- close();
- }
-
attributeVertex = shaderProgram_.attributeLocation("vertexIn");
attributeTexture = shaderProgram_.attributeLocation("textureIn");
+ vertexBuffer_.bind();
+
shaderProgram_.enableAttributeArray(attributeVertex);
shaderProgram_.setAttributeBuffer(attributeVertex,
GL_FLOAT,
@@ -466,6 +463,9 @@ bool ViewFinderGL::createFragmentShader()
2,
2 * sizeof(GLfloat));
+ vertexBuffer_.release();
+
+ projMatrixUniform_ = shaderProgram_.uniformLocation("proj_matrix");
textureUniformY_ = shaderProgram_.uniformLocation("tex_y");
textureUniformU_ = shaderProgram_.uniformLocation("tex_u");
textureUniformV_ = shaderProgram_.uniformLocation("tex_v");
@@ -511,7 +511,7 @@ void ViewFinderGL::initializeGL()
glEnable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
- static const GLfloat coordinates[2][4][2]{
+ const GLfloat coordinates[2][4][2]{
{
/* Vertex coordinates */
{ -1.0f, -1.0f },
@@ -535,8 +535,6 @@ void ViewFinderGL::initializeGL()
/* Create Vertex Shader */
if (!createVertexShader())
qWarning() << "[ViewFinderGL]: create vertex shader failed.";
-
- glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
}
void ViewFinderGL::doRender()
@@ -805,28 +803,42 @@ void ViewFinderGL::doRender()
shaderProgram_.setUniformValue(textureUniformStrideFactor_,
static_cast<float>(size_.width() - 1) /
(stridePixels - 1));
+
+ /*
+ * Place the viewfinder in the centre of the widget, preserving the
+ * aspect ratio of the image.
+ */
+ QMatrix4x4 projMatrix;
+ QSizeF scaledSize = size_.scaled(size(), Qt::KeepAspectRatio);
+ projMatrix.scale(scaledSize.width() / size().width(),
+ scaledSize.height() / size().height());
+
+ shaderProgram_.setUniformValue(projMatrixUniform_, projMatrix);
}
void ViewFinderGL::paintGL()
{
- if (!fragmentShader_)
+ if (!fragmentShader_) {
if (!createFragmentShader()) {
qWarning() << "[ViewFinderGL]:"
<< "create fragment shader failed.";
}
+ }
- if (image_) {
- glClearColor(0.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- doRender();
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ /* Bind shader pipeline for use. */
+ if (!shaderProgram_.bind()) {
+ qWarning() << "[ViewFinderGL]:" << shaderProgram_.log();
+ close();
}
-}
-void ViewFinderGL::resizeGL(int w, int h)
-{
- glViewport(0, 0, w, h);
+ if (!image_)
+ return;
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ doRender();
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
QSize ViewFinderGL::sizeHint() const
diff --git a/src/apps/qcam/viewfinder_gl.h b/src/apps/qcam/viewfinder_gl.h
index 23744b41..23c657bc 100644
--- a/src/apps/qcam/viewfinder_gl.h
+++ b/src/apps/qcam/viewfinder_gl.h
@@ -51,7 +51,6 @@ Q_SIGNALS:
protected:
void initializeGL() override;
void paintGL() override;
- void resizeGL(int w, int h) override;
QSize sizeHint() const override;
private:
@@ -88,6 +87,7 @@ private:
/* Common texture parameters */
GLuint textureMinMagFilters_;
+ GLuint projMatrixUniform_;
/* YUV texture parameters */
GLuint textureUniformU_;
diff --git a/src/apps/qcam/viewfinder_qt.cpp b/src/apps/qcam/viewfinder_qt.cpp
index 4821c27d..1a238922 100644
--- a/src/apps/qcam/viewfinder_qt.cpp
+++ b/src/apps/qcam/viewfinder_qt.cpp
@@ -18,6 +18,7 @@
#include <QMap>
#include <QMutexLocker>
#include <QPainter>
+#include <QResizeEvent>
#include <QtDebug>
#include "../common/image.h"
@@ -26,23 +27,23 @@
static const QMap<libcamera::PixelFormat, QImage::Format> nativeFormats
{
-#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
{ libcamera::formats::ABGR8888, QImage::Format_RGBX8888 },
{ libcamera::formats::XBGR8888, QImage::Format_RGBX8888 },
-#endif
{ libcamera::formats::ARGB8888, QImage::Format_RGB32 },
{ libcamera::formats::XRGB8888, QImage::Format_RGB32 },
-#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
{ libcamera::formats::RGB888, QImage::Format_BGR888 },
-#endif
{ libcamera::formats::BGR888, QImage::Format_RGB888 },
{ libcamera::formats::RGB565, QImage::Format_RGB16 },
};
ViewFinderQt::ViewFinderQt(QWidget *parent)
- : QWidget(parent), buffer_(nullptr)
+ : QWidget(parent), place_(rect()), buffer_(nullptr)
{
icon_ = QIcon(":camera-off.svg");
+
+ QPalette pal = palette();
+ pal.setColor(QPalette::Window, Qt::black);
+ setPalette(pal);
}
ViewFinderQt::~ViewFinderQt()
@@ -117,6 +118,11 @@ void ViewFinderQt::render(libcamera::FrameBuffer *buffer, Image *image)
}
}
+ /*
+ * Indicate the widget paints all its pixels, to optimize rendering by
+ * skipping erasing the widget before painting.
+ */
+ setAttribute(Qt::WA_OpaquePaintEvent, true);
update();
if (buffer)
@@ -132,6 +138,11 @@ void ViewFinderQt::stop()
buffer_ = nullptr;
}
+ /*
+ * The logo has a transparent background, reenable erasing the widget
+ * before painting.
+ */
+ setAttribute(Qt::WA_OpaquePaintEvent, false);
update();
}
@@ -146,9 +157,23 @@ void ViewFinderQt::paintEvent(QPaintEvent *)
{
QPainter painter(this);
- /* If we have an image, draw it. */
+ painter.setBrush(palette().window());
+
+ /* If we have an image, draw it, with black letterbox rectangles. */
if (!image_.isNull()) {
- painter.drawImage(rect(), image_, image_.rect());
+ if (place_.width() < width()) {
+ QRect rect{ 0, 0, (width() - place_.width()) / 2, height() };
+ painter.drawRect(rect);
+ rect.moveLeft(place_.right());
+ painter.drawRect(rect);
+ } else {
+ QRect rect{ 0, 0, width(), (height() - place_.height()) / 2 };
+ painter.drawRect(rect);
+ rect.moveTop(place_.bottom());
+ painter.drawRect(rect);
+ }
+
+ painter.drawImage(place_, image_, image_.rect());
return;
}
@@ -173,7 +198,6 @@ void ViewFinderQt::paintEvent(QPaintEvent *)
else
point.setY((height() - pixmap_.height()) / 2);
- painter.setBackgroundMode(Qt::OpaqueMode);
painter.drawPixmap(point, pixmap_);
}
@@ -181,3 +205,14 @@ QSize ViewFinderQt::sizeHint() const
{
return size_.isValid() ? size_ : QSize(640, 480);
}
+
+void ViewFinderQt::resizeEvent(QResizeEvent *event)
+{
+ if (!size_.isValid())
+ return;
+
+ place_.setSize(size_.scaled(event->size(), Qt::KeepAspectRatio));
+ place_.moveCenter(rect().center());
+
+ QWidget::resizeEvent(event);
+}
diff --git a/src/apps/qcam/viewfinder_qt.h b/src/apps/qcam/viewfinder_qt.h
index 4f4b9f11..50fde88e 100644
--- a/src/apps/qcam/viewfinder_qt.h
+++ b/src/apps/qcam/viewfinder_qt.h
@@ -44,6 +44,7 @@ Q_SIGNALS:
protected:
void paintEvent(QPaintEvent *) override;
+ void resizeEvent(QResizeEvent *) override;
QSize sizeHint() const override;
private:
@@ -51,6 +52,7 @@ private:
libcamera::PixelFormat format_;
QSize size_;
+ QRect place_;
/* Camera stopped icon */
QSize vfSize_;
diff --git a/src/gstreamer/gstlibcamera-utils.cpp b/src/gstreamer/gstlibcamera-utils.cpp
index ec4da435..79f71246 100644
--- a/src/gstreamer/gstlibcamera-utils.cpp
+++ b/src/gstreamer/gstlibcamera-utils.cpp
@@ -359,13 +359,21 @@ gst_libcamera_stream_formats_to_caps(const StreamFormats &formats)
GValue val = G_VALUE_INIT;
g_value_init(&val, GST_TYPE_INT_RANGE);
- gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
- gst_structure_set_value(s, "width", &val);
- gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
- gst_structure_set_value(s, "height", &val);
+ if (range.min.width == range.max.width) {
+ gst_structure_set(s, "width", G_TYPE_INT, range.min.width, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.width, range.max.width, range.hStep);
+ gst_structure_set_value(s, "width", &val);
+ }
+ if (range.min.height == range.max.height) {
+ gst_structure_set(s, "height", G_TYPE_INT, range.min.height, nullptr);
+ } else {
+ gst_value_set_int_range_step(&val, range.min.height, range.max.height, range.vStep);
+ gst_structure_set_value(s, "height", &val);
+ }
g_value_unset(&val);
- gst_caps_append_structure(caps, s);
+ caps = gst_caps_merge_structure(caps, s);
}
}
diff --git a/src/gstreamer/gstlibcameraallocator.cpp b/src/gstreamer/gstlibcameraallocator.cpp
index 741ed592..7e4c904d 100644
--- a/src/gstreamer/gstlibcameraallocator.cpp
+++ b/src/gstreamer/gstlibcameraallocator.cpp
@@ -100,6 +100,11 @@ struct _GstLibcameraAllocator {
* FrameWrap.
*/
GHashTable *pools;
+ /*
+ * The camera manager represents the library, which needs to be kept
+ * alive until all the memory has been released.
+ */
+ std::shared_ptr<CameraManager> *cm_ptr;
};
G_DEFINE_TYPE(GstLibcameraAllocator, gst_libcamera_allocator,
@@ -173,6 +178,9 @@ gst_libcamera_allocator_finalize(GObject *object)
delete self->fb_allocator;
+ /* Keep last. */
+ delete self->cm_ptr;
+
G_OBJECT_CLASS(gst_libcamera_allocator_parent_class)->finalize(object);
}
@@ -193,11 +201,17 @@ gst_libcamera_allocator_new(std::shared_ptr<Camera> camera,
{
auto *self = GST_LIBCAMERA_ALLOCATOR(g_object_new(GST_TYPE_LIBCAMERA_ALLOCATOR,
nullptr));
+ gint ret;
+
+ self->cm_ptr = new std::shared_ptr<CameraManager>(gst_libcamera_get_camera_manager(ret));
+ if (ret) {
+ g_object_unref(self);
+ return nullptr;
+ }
self->fb_allocator = new FrameBufferAllocator(camera);
for (StreamConfiguration &streamCfg : *config_) {
Stream *stream = streamCfg.stream();
- gint ret;
ret = self->fb_allocator->allocate(stream);
if (ret == 0)
diff --git a/src/gstreamer/gstlibcamerapool.cpp b/src/gstreamer/gstlibcamerapool.cpp
index 9661c67a..9cd7eccb 100644
--- a/src/gstreamer/gstlibcamerapool.cpp
+++ b/src/gstreamer/gstlibcamerapool.cpp
@@ -8,6 +8,8 @@
#include "gstlibcamerapool.h"
+#include <deque>
+
#include <libcamera/stream.h>
#include "gstlibcamera-utils.h"
@@ -24,24 +26,41 @@ static guint signals[N_SIGNALS];
struct _GstLibcameraPool {
GstBufferPool parent;
- GstAtomicQueue *queue;
+ std::deque<GstBuffer *> *queue;
GstLibcameraAllocator *allocator;
Stream *stream;
};
G_DEFINE_TYPE(GstLibcameraPool, gst_libcamera_pool, GST_TYPE_BUFFER_POOL)
+static GstBuffer *
+gst_libcamera_pool_pop_buffer(GstLibcameraPool *self)
+{
+ GLibLocker lock(GST_OBJECT(self));
+ GstBuffer *buf;
+
+ if (self->queue->empty())
+ return nullptr;
+
+ buf = self->queue->front();
+ self->queue->pop_front();
+
+ return buf;
+}
+
static GstFlowReturn
gst_libcamera_pool_acquire_buffer(GstBufferPool *pool, GstBuffer **buffer,
[[maybe_unused]] GstBufferPoolAcquireParams *params)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- GstBuffer *buf = GST_BUFFER(gst_atomic_queue_pop(self->queue));
+ GstBuffer *buf = gst_libcamera_pool_pop_buffer(self);
+
if (!buf)
return GST_FLOW_ERROR;
if (!gst_libcamera_allocator_prepare_buffer(self->allocator, self->stream, buf)) {
- gst_atomic_queue_push(self->queue, buf);
+ GLibLocker lock(GST_OBJECT(self));
+ self->queue->push_back(buf);
return GST_FLOW_ERROR;
}
@@ -64,9 +83,13 @@ static void
gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
{
GstLibcameraPool *self = GST_LIBCAMERA_POOL(pool);
- bool do_notify = gst_atomic_queue_length(self->queue) == 0;
+ bool do_notify;
- gst_atomic_queue_push(self->queue, buffer);
+ {
+ GLibLocker lock(GST_OBJECT(self));
+ do_notify = self->queue->empty();
+ self->queue->push_back(buffer);
+ }
if (do_notify)
g_signal_emit(self, signals[SIGNAL_BUFFER_NOTIFY], 0);
@@ -75,7 +98,7 @@ gst_libcamera_pool_release_buffer(GstBufferPool *pool, GstBuffer *buffer)
static void
gst_libcamera_pool_init(GstLibcameraPool *self)
{
- self->queue = gst_atomic_queue_new(4);
+ self->queue = new std::deque<GstBuffer *>();
}
static void
@@ -84,10 +107,10 @@ gst_libcamera_pool_finalize(GObject *object)
GstLibcameraPool *self = GST_LIBCAMERA_POOL(object);
GstBuffer *buf;
- while ((buf = GST_BUFFER(gst_atomic_queue_pop(self->queue))))
+ while ((buf = gst_libcamera_pool_pop_buffer(self)))
gst_buffer_unref(buf);
- gst_atomic_queue_unref(self->queue);
+ delete self->queue;
g_object_unref(self->allocator);
G_OBJECT_CLASS(gst_libcamera_pool_parent_class)->finalize(object);
@@ -122,7 +145,7 @@ gst_libcamera_pool_new(GstLibcameraAllocator *allocator, Stream *stream)
gsize pool_size = gst_libcamera_allocator_get_pool_size(allocator, stream);
for (gsize i = 0; i < pool_size; i++) {
GstBuffer *buffer = gst_buffer_new();
- gst_atomic_queue_push(pool->queue, buffer);
+ pool->queue->push_back(buffer);
}
return pool;
diff --git a/src/gstreamer/gstlibcamerasrc.cpp b/src/gstreamer/gstlibcamerasrc.cpp
index 9680d809..912a8d55 100644
--- a/src/gstreamer/gstlibcamerasrc.cpp
+++ b/src/gstreamer/gstlibcamerasrc.cpp
@@ -157,7 +157,12 @@ enum {
PROP_AUTO_FOCUS_MODE,
};
+static void gst_libcamera_src_child_proxy_init(gpointer g_iface,
+ gpointer iface_data);
+
G_DEFINE_TYPE_WITH_CODE(GstLibcameraSrc, gst_libcamera_src, GST_TYPE_ELEMENT,
+ G_IMPLEMENT_INTERFACE(GST_TYPE_CHILD_PROXY,
+ gst_libcamera_src_child_proxy_init)
GST_DEBUG_CATEGORY_INIT(source_debug, "libcamerasrc", 0,
"libcamera Source"))
@@ -865,8 +870,10 @@ gst_libcamera_src_init(GstLibcameraSrc *self)
g_mutex_init(&state->lock_);
- state->srcpads_.push_back(gst_pad_new_from_template(templ, "src"));
- gst_element_add_pad(GST_ELEMENT(self), state->srcpads_.back());
+ GstPad *pad = gst_pad_new_from_template(templ, "src");
+ state->srcpads_.push_back(pad);
+ gst_element_add_pad(GST_ELEMENT(self), pad);
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
GST_OBJECT_FLAG_SET(self, GST_ELEMENT_FLAG_SOURCE);
@@ -897,6 +904,8 @@ gst_libcamera_src_request_new_pad(GstElement *element, GstPadTemplate *templ,
return NULL;
}
+ gst_child_proxy_child_added(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
return reinterpret_cast<GstPad *>(g_steal_pointer(&pad));
}
@@ -905,6 +914,8 @@ gst_libcamera_src_release_pad(GstElement *element, GstPad *pad)
{
GstLibcameraSrc *self = GST_LIBCAMERA_SRC(element);
+ gst_child_proxy_child_removed(GST_CHILD_PROXY(self), G_OBJECT(pad), GST_OBJECT_NAME(pad));
+
GST_DEBUG_OBJECT(self, "Pad %" GST_PTR_FORMAT " being released", pad);
{
@@ -940,7 +951,7 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
gst_element_class_set_metadata(element_class,
"libcamera Source", "Source/Video",
"Linux Camera source using libcamera",
- "Nicolas Dufresne <nicolas.dufresne@collabora.com");
+ "Nicolas Dufresne <nicolas.dufresne@collabora.com>");
gst_element_class_add_static_pad_template_with_gtype(element_class,
&src_template,
GST_TYPE_LIBCAMERA_PAD);
@@ -965,3 +976,33 @@ gst_libcamera_src_class_init(GstLibcameraSrcClass *klass)
G_PARAM_WRITABLE);
g_object_class_install_property(object_class, PROP_AUTO_FOCUS_MODE, spec);
}
+
+/* GstChildProxy implementation */
+static GObject *
+gst_libcamera_src_child_proxy_get_child_by_index(GstChildProxy *child_proxy,
+ guint index)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ GObject *obj = nullptr;
+
+ obj = reinterpret_cast<GObject *>(g_list_nth_data(GST_ELEMENT(child_proxy)->srcpads, index));
+ if (obj)
+ gst_object_ref(obj);
+
+ return obj;
+}
+
+static guint
+gst_libcamera_src_child_proxy_get_children_count(GstChildProxy *child_proxy)
+{
+ GLibLocker lock(GST_OBJECT(child_proxy));
+ return GST_ELEMENT_CAST(child_proxy)->numsrcpads;
+}
+
+static void
+gst_libcamera_src_child_proxy_init(gpointer g_iface, [[maybe_unused]] gpointer iface_data)
+{
+ GstChildProxyInterface *iface = reinterpret_cast<GstChildProxyInterface *>(g_iface);
+ iface->get_child_by_index = gst_libcamera_src_child_proxy_get_child_by_index;
+ iface->get_children_count = gst_libcamera_src_child_proxy_get_children_count;
+}
diff --git a/src/ipa/ipu3/algorithms/af.cpp b/src/ipa/ipu3/algorithms/af.cpp
index 29eb7355..cf68fb59 100644
--- a/src/ipa/ipu3/algorithms/af.cpp
+++ b/src/ipa/ipu3/algorithms/af.cpp
@@ -11,7 +11,6 @@
#include <chrono>
#include <cmath>
#include <fcntl.h>
-#include <numeric>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -23,8 +22,6 @@
#include <libcamera/ipa/core_ipa_interface.h>
-#include "libipa/histogram.h"
-
/**
* \file af.h
*/
diff --git a/src/ipa/ipu3/algorithms/agc.cpp b/src/ipa/ipu3/algorithms/agc.cpp
index 0e0114f6..c5f3d8f0 100644
--- a/src/ipa/ipu3/algorithms/agc.cpp
+++ b/src/ipa/ipu3/algorithms/agc.cpp
@@ -9,12 +9,12 @@
#include <algorithm>
#include <chrono>
-#include <cmath>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
#include <libcamera/control_ids.h>
+
#include <libcamera/ipa/core_ipa_interface.h>
#include "libipa/histogram.h"
@@ -247,7 +247,6 @@ void Agc::process(IPAContext &context, [[maybe_unused]] const uint32_t frame,
utils::Duration frameDuration = context.configuration.sensor.lineDuration
* vTotal;
metadata.set(controls::FrameDuration, frameDuration.get<std::micro>());
-
}
REGISTER_IPA_ALGORITHM(Agc, "Agc")
diff --git a/src/ipa/ipu3/algorithms/blc.cpp b/src/ipa/ipu3/algorithms/blc.cpp
index 257f40e2..35748fb2 100644
--- a/src/ipa/ipu3/algorithms/blc.cpp
+++ b/src/ipa/ipu3/algorithms/blc.cpp
@@ -7,8 +7,6 @@
#include "blc.h"
-#include <string.h>
-
/**
* \file blc.h
* \brief IPU3 Black Level Correction control
@@ -57,8 +55,8 @@ void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
* tuning processes. This is a first rough approximation.
*/
params->obgrid_param.gr = 64;
- params->obgrid_param.r = 64;
- params->obgrid_param.b = 64;
+ params->obgrid_param.r = 64;
+ params->obgrid_param.b = 64;
params->obgrid_param.gb = 64;
/* Enable the custom black level correction processing */
diff --git a/src/ipa/ipu3/ipu3.cpp b/src/ipa/ipu3/ipu3.cpp
index cdcdf1fb..10a8c86d 100644
--- a/src/ipa/ipu3/ipu3.cpp
+++ b/src/ipa/ipu3/ipu3.cpp
@@ -23,24 +23,22 @@
#include <libcamera/base/utils.h>
#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+#include <libcamera/request.h>
+
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
#include <libcamera/ipa/ipu3_ipa_interface.h>
-#include <libcamera/request.h>
#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/yaml_parser.h"
-#include "algorithms/af.h"
-#include "algorithms/agc.h"
-#include "algorithms/algorithm.h"
-#include "algorithms/awb.h"
-#include "algorithms/blc.h"
-#include "algorithms/tone_mapping.h"
#include "libipa/camera_sensor_helper.h"
#include "ipa_context.h"
+#include "module.h"
/* Minimum grid width, expressed as a number of cells */
static constexpr uint32_t kMinGridWidth = 16;
@@ -313,8 +311,8 @@ int IPAIPU3::init(const IPASettings &settings,
/* Clean context */
context_.configuration = {};
- context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
- * 1.0s / sensorInfo.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate;
/* Load the tuning data file. */
File file(settings.configurationFile);
@@ -477,8 +475,8 @@ int IPAIPU3::configure(const IPAConfigInfo &configInfo,
context_.frameContexts.clear();
/* Initialise the sensor configuration. */
- context_.configuration.sensor.lineDuration = sensorInfo_.minLineLength
- * 1.0s / sensorInfo_.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo_.minLineLength * 1.0s / sensorInfo_.pixelRate;
context_.configuration.sensor.size = sensorInfo_.outputSize;
/*
diff --git a/src/ipa/ipu3/meson.build b/src/ipa/ipu3/meson.build
index e76f97c0..34de6213 100644
--- a/src/ipa/ipu3/meson.build
+++ b/src/ipa/ipu3/meson.build
@@ -12,8 +12,7 @@ ipu3_ipa_sources = files([
ipu3_ipa_sources += ipu3_ipa_algorithms
-mod = shared_module(ipa_name,
- [ipu3_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, ipu3_ipa_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/libipa/camera_sensor_helper.cpp b/src/ipa/libipa/camera_sensor_helper.cpp
index 782ff990..c6169bdc 100644
--- a/src/ipa/libipa/camera_sensor_helper.cpp
+++ b/src/ipa/libipa/camera_sensor_helper.cpp
@@ -8,6 +8,7 @@
#include "camera_sensor_helper.h"
#include <cmath>
+#include <limits>
#include <libcamera/base/log.h>
@@ -40,6 +41,7 @@ namespace ipa {
*/
/**
+ * \fn CameraSensorHelper::CameraSensorHelper()
* \brief Construct a CameraSensorHelper instance
*
* CameraSensorHelper derived class instances shall never be constructed
@@ -48,6 +50,33 @@ namespace ipa {
*/
/**
+ * \fn CameraSensorHelper::blackLevel()
+ * \brief Fetch the black level of the sensor
+ *
+ * This function returns the black level of the sensor scaled to a 16bit pixel
+ * width. If it is unknown an empty optional is returned.
+ *
+ * \todo Fill the blanks and add pedestal values for all supported sensors. Once
+ * done, drop the std::optional<>.
+ *
+ * Black levels are typically the result of the following phenomena:
+ * - Pedestal added by the sensor to pixel values. They are typically fixed,
+ * sometimes programmable and should be reported in datasheets (but
+ * documentation is not always available).
+ * - Dark currents and other physical effects that add charge to pixels in the
+ * absence of light. Those can depend on the integration time and the sensor
+ * die temperature, and their contribution to pixel values depend on the
+ * sensor gains.
+ *
+ * The pedestal is usually the value with the biggest contribution to the
+ * overall black level. In most cases it is either known before or in rare cases
+ * (there is not a single driver with such a control in the linux kernel) can be
+ * queried from the sensor. This function provides that fixed, known value.
+ *
+ * \return The black level of the sensor, or std::nullopt if not known
+ */
+
+/**
* \brief Compute gain code from the analogue gain absolute value
* \param[in] gain The real gain to pass
*
@@ -205,6 +234,12 @@ double CameraSensorHelper::gain(uint32_t gainCode) const
*/
/**
+ * \var CameraSensorHelper::blackLevel_
+ * \brief The black level of the sensor
+ * \sa CameraSensorHelper::blackLevel()
+ */
+
+/**
* \var CameraSensorHelper::gainType_
* \brief The analogue gain model type
*/
@@ -366,6 +401,99 @@ static constexpr double expGainDb(double step)
return log2_10 * step / 20;
}
+class CameraSensorHelperAr0144 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperAr0144()
+ {
+ /* Power-on default value: 168 at 12bits. */
+ blackLevel_ = 2688;
+ }
+
+ uint32_t gainCode(double gain) const override
+ {
+ /* The recommended minimum gain is 1.6842 to avoid artifacts. */
+ gain = std::clamp(gain, 1.0 / (1.0 - 13.0 / 32.0), 18.45);
+
+ /*
+ * The analogue gain is made of a coarse exponential gain in
+ * the range [2^0, 2^4] and a fine inversely linear gain in the
+ * range [1.0, 2.0[. There is an additional fixed 1.153125
+ * multiplier when the coarse gain reaches 2^2.
+ */
+
+ if (gain > 4.0)
+ gain /= 1.153125;
+
+ unsigned int coarse = std::log2(gain);
+ unsigned int fine = (1 - (1 << coarse) / gain) * 32;
+
+ /* The fine gain rounding depends on the coarse gain. */
+ if (coarse == 1 || coarse == 3)
+ fine &= ~1;
+ else if (coarse == 4)
+ fine &= ~3;
+
+ return (coarse << 4) | (fine & 0xf);
+ }
+
+ double gain(uint32_t gainCode) const override
+ {
+ unsigned int coarse = gainCode >> 4;
+ unsigned int fine = gainCode & 0xf;
+ unsigned int d1;
+ double d2, m;
+
+ switch (coarse) {
+ default:
+ case 0:
+ d1 = 1;
+ d2 = 32.0;
+ m = 1.0;
+ break;
+ case 1:
+ d1 = 2;
+ d2 = 16.0;
+ m = 1.0;
+ break;
+ case 2:
+ d1 = 1;
+ d2 = 32.0;
+ m = 1.153125;
+ break;
+ case 3:
+ d1 = 2;
+ d2 = 16.0;
+ m = 1.153125;
+ break;
+ case 4:
+ d1 = 4;
+ d2 = 8.0;
+ m = 1.153125;
+ break;
+ }
+
+ /*
+ * With infinite precision, the calculated gain would be exact,
+ * and the reverse conversion with gainCode() would produce the
+ * same gain code. In the real world, rounding errors may cause
+ * the calculated gain to be lower by an amount negligible for
+ * all purposes, except for the reverse conversion. Converting
+ * the gain to a gain code could then return the quantized value
+ * just lower than the original gain code. To avoid this, tests
+ * showed that adding the machine epsilon to the multiplier m is
+ * sufficient.
+ */
+ m += std::numeric_limits<decltype(m)>::epsilon();
+
+ return m * (1 << coarse) / (1.0 - (fine / d1) / d2);
+ }
+
+private:
+ static constexpr double kStep_ = 16;
+};
+REGISTER_CAMERA_SENSOR_HELPER("ar0144", CameraSensorHelperAr0144)
+
class CameraSensorHelperAr0521 : public CameraSensorHelper
{
public:
@@ -391,11 +519,26 @@ private:
};
REGISTER_CAMERA_SENSOR_HELPER("ar0521", CameraSensorHelperAr0521)
+class CameraSensorHelperImx214 : public CameraSensorHelper
+{
+public:
+ CameraSensorHelperImx214()
+ {
+ /* From datasheet: 64 at 10bits. */
+ blackLevel_ = 4096;
+ gainType_ = AnalogueGainLinear;
+ gainConstants_.linear = { 0, 512, -1, 512 };
+ }
+};
+REGISTER_CAMERA_SENSOR_HELPER("imx214", CameraSensorHelperImx214)
+
class CameraSensorHelperImx219 : public CameraSensorHelper
{
public:
CameraSensorHelperImx219()
{
+ /* From datasheet: 64 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 256, -1, 256 };
}
@@ -407,6 +550,8 @@ class CameraSensorHelperImx258 : public CameraSensorHelper
public:
CameraSensorHelperImx258()
{
+ /* From datasheet: 0x40 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 512, -1, 512 };
}
@@ -418,6 +563,8 @@ class CameraSensorHelperImx283 : public CameraSensorHelper
public:
CameraSensorHelperImx283()
{
+ /* From datasheet: 0x32 at 10bits. */
+ blackLevel_ = 3200;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 0, 2048, -1, 2048 };
}
@@ -456,6 +603,8 @@ class CameraSensorHelperImx335 : public CameraSensorHelper
public:
CameraSensorHelperImx335()
{
+ /* From datasheet: 0x32 at 10bits. */
+ blackLevel_ = 3200;
gainType_ = AnalogueGainExponential;
gainConstants_.exp = { 1.0, expGainDb(0.3) };
}
@@ -515,6 +664,8 @@ class CameraSensorHelperOv4689 : public CameraSensorHelper
public:
CameraSensorHelperOv4689()
{
+ /* From datasheet: 0x40 at 12bits. */
+ blackLevel_ = 1024;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 128 };
}
@@ -526,6 +677,8 @@ class CameraSensorHelperOv5640 : public CameraSensorHelper
public:
CameraSensorHelperOv5640()
{
+ /* From datasheet: 0x10 at 10bits. */
+ blackLevel_ = 1024;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 16 };
}
@@ -559,6 +712,8 @@ class CameraSensorHelperOv5675 : public CameraSensorHelper
public:
CameraSensorHelperOv5675()
{
+ /* From Linux kernel driver: 0x40 at 10bits. */
+ blackLevel_ = 4096;
gainType_ = AnalogueGainLinear;
gainConstants_.linear = { 1, 0, 0, 128 };
}
diff --git a/src/ipa/libipa/camera_sensor_helper.h b/src/ipa/libipa/camera_sensor_helper.h
index 0d99073b..75868205 100644
--- a/src/ipa/libipa/camera_sensor_helper.h
+++ b/src/ipa/libipa/camera_sensor_helper.h
@@ -7,9 +7,9 @@
#pragma once
-#include <stdint.h>
-
#include <memory>
+#include <optional>
+#include <stdint.h>
#include <string>
#include <vector>
@@ -25,6 +25,7 @@ public:
CameraSensorHelper() = default;
virtual ~CameraSensorHelper() = default;
+ std::optional<int16_t> blackLevel() const { return blackLevel_; }
virtual uint32_t gainCode(double gain) const;
virtual double gain(uint32_t gainCode) const;
@@ -51,6 +52,7 @@ protected:
AnalogueGainExpConstants exp;
};
+ std::optional<int16_t> blackLevel_;
AnalogueGainType gainType_;
AnalogueGainConstants gainConstants_;
diff --git a/src/ipa/libipa/exposure_mode_helper.cpp b/src/ipa/libipa/exposure_mode_helper.cpp
index 683a564a..7703becc 100644
--- a/src/ipa/libipa/exposure_mode_helper.cpp
+++ b/src/ipa/libipa/exposure_mode_helper.cpp
@@ -166,7 +166,7 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
return { minShutter_, minGain_, exposure / (minShutter_ * minGain_) };
utils::Duration shutter;
- double stageGain;
+ double stageGain = 1.0;
double gain;
for (unsigned int stage = 0; stage < gains_.size(); stage++) {
@@ -201,12 +201,9 @@ ExposureModeHelper::splitExposure(utils::Duration exposure) const
* From here on all we can do is max out the shutter time, followed by
* the analogue gain. If we still haven't achieved the target we send
* the rest of the exposure time to digital gain. If we were given no
- * stages to use then set stageGain to 1.0 so that shutter time is maxed
- * before gain touched at all.
+ * stages to use then the default stageGain of 1.0 is used so that
+ * shutter time is maxed before gain is touched at all.
*/
- if (gains_.empty())
- stageGain = 1.0;
-
shutter = clampShutter(exposure / clampGain(stageGain));
gain = clampGain(exposure / shutter);
diff --git a/src/ipa/libipa/histogram.h b/src/ipa/libipa/histogram.h
index 032adca0..6fd64168 100644
--- a/src/ipa/libipa/histogram.h
+++ b/src/ipa/libipa/histogram.h
@@ -7,7 +7,6 @@
#pragma once
-#include <assert.h>
#include <limits.h>
#include <stdint.h>
#include <type_traits>
diff --git a/src/ipa/libipa/interpolator.cpp b/src/ipa/libipa/interpolator.cpp
new file mode 100644
index 00000000..73e8d3b7
--- /dev/null
+++ b/src/ipa/libipa/interpolator.cpp
@@ -0,0 +1,157 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating objects
+ */
+#include "interpolator.h"
+
+#include <algorithm>
+#include <string>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include "interpolator.h"
+
+/**
+ * \file interpolator.h
+ * \brief Helper class for linear interpolating a set of objects
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(Interpolator)
+
+namespace ipa {
+
+/**
+ * \class Interpolator
+ * \brief Class for storing, retrieving, and interpolating objects
+ * \tparam T Type of objects stored in the interpolator
+ *
+ * The main use case is to pass a map from color temperatures to corresponding
+ * objects (eg. matrices for color correction), and then requesting a
+ * interpolated object for a specific color temperature. This class will
+ * abstract away the interpolation portion.
+ */
+
+/**
+ * \fn Interpolator::Interpolator()
+ * \brief Construct an empty interpolator
+ */
+
+/**
+ * \fn Interpolator::Interpolator(const std::map<unsigned int, T> &data)
+ * \brief Construct an interpolator from a map of objects
+ * \param data Map from which to construct the interpolator
+ */
+
+/**
+ * \fn Interpolator::Interpolator(std::map<unsigned int, T> &&data)
+ * \brief Construct an interpolator from a map of objects
+ * \param data Map from which to construct the interpolator
+ */
+
+/**
+ * \fn int Interpolator<T>::readYaml(const libcamera::YamlObject &yaml,
+ const std::string &key_name,
+ const std::string &value_name)
+ * \brief Initialize an Interpolator instance from yaml
+ * \tparam T Type of data stored in the interpolator
+ * \param[in] yaml The yaml object that contains the map of unsigned integers to
+ * objects
+ * \param[in] key_name The name of the key in the yaml object
+ * \param[in] value_name The name of the value in the yaml object
+ *
+ * The yaml object is expected to be a list of maps. Each map has two or more
+ * pairs: one of \a key_name to the key value (usually color temperature), and
+ * one or more of \a value_name to the object. This is a bit difficult to
+ * explain, so here is an example (in python, as it is easier to parse than
+ * yaml):
+ * [
+ * {
+ * 'ct': 2860,
+ * 'ccm': [ 2.12089, -0.52461, -0.59629,
+ * -0.85342, 2.80445, -0.95103,
+ * -0.26897, -1.14788, 2.41685 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 2960,
+ * 'ccm': [ 2.26962, -0.54174, -0.72789,
+ * -0.77008, 2.60271, -0.83262,
+ * -0.26036, -1.51254, 2.77289 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ *
+ * {
+ * 'ct': 3603,
+ * 'ccm': [ 2.18644, -0.66148, -0.52496,
+ * -0.77828, 2.69474, -0.91645,
+ * -0.25239, -0.83059, 2.08298 ],
+ * 'offsets': [ 0, 0, 0 ]
+ * },
+ * ]
+ *
+ * In this case, \a key_name would be 'ct', and \a value_name can be either
+ * 'ccm' or 'offsets'. This way multiple interpolators can be defined in
+ * one set of color temperature ranges in the tuning file, and they can be
+ * retrieved separately with the \a value_name parameter.
+ *
+ * \return Zero on success, negative error code otherwise
+ */
+
+/**
+ * \fn void Interpolator<T>::setQuantization(const unsigned int q)
+ * \brief Set the quantization value
+ * \param[in] q The quantization value
+ *
+ * Sets the quantization value. When this is set, 'key' gets quantized to this
+ * size, before doing the interpolation. This can help in reducing the number of
+ * updates pushed to the hardware.
+ *
+ * Note that normally a threshold needs to be combined with quantization.
+ * Otherwise a value that swings around the edge of the quantization step will
+ * lead to constant updates.
+ */
+
+/**
+ * \fn void Interpolator<T>::setData(std::map<unsigned int, T> &&data)
+ * \brief Set the internal map
+ *
+ * Overwrites the internal map using move semantics.
+ */
+
+/**
+ * \fn const T& Interpolator<T>::getInterpolated()
+ * \brief Retrieve an interpolated value for the given key
+ * \param[in] key The unsigned integer key of the object to retrieve
+ * \param[out] quantizedKey If provided, the key value after quantization
+ * \return The object corresponding to the key. The object is cached internally,
+ * so on successive calls with the same key (after quantization) interpolation
+ * is not recalculated.
+ */
+
+/**
+ * \fn void Interpolator<T>::interpolate(const T &a, const T &b, T &dest, double
+ * lambda)
+ * \brief Interpolate between two instances of T
+ * \param a The first value to interpolate
+ * \param b The second value to interpolate
+ * \param dest The destination for the interpolated value
+ * \param lambda The interpolation factor (0..1)
+ *
+ * Interpolates between \a a and \a b according to \a lambda. It calculates
+ * dest = a * (1.0 - lambda) + b * lambda;
+ *
+ * If T supports multiplication with double and addition, this function can be
+ * used as is. For other types this function can be overwritten using partial
+ * template specialization.
+ */
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/interpolator.h b/src/ipa/libipa/interpolator.h
new file mode 100644
index 00000000..fffce214
--- /dev/null
+++ b/src/ipa/libipa/interpolator.h
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+ *
+ * Helper class for interpolating maps of objects
+ */
+
+#pragma once
+
+#include <algorithm>
+#include <cmath>
+#include <map>
+#include <string>
+#include <tuple>
+
+#include <libcamera/base/log.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(Interpolator)
+
+namespace ipa {
+
+template<typename T>
+class Interpolator
+{
+public:
+ Interpolator() = default;
+ Interpolator(const std::map<unsigned int, T> &data)
+ : data_(data)
+ {
+ }
+ Interpolator(std::map<unsigned int, T> &&data)
+ : data_(std::move(data))
+ {
+ }
+
+ ~Interpolator() = default;
+
+ int readYaml(const libcamera::YamlObject &yaml,
+ const std::string &key_name,
+ const std::string &value_name)
+ {
+ data_.clear();
+ lastInterpolatedKey_.reset();
+
+ if (!yaml.isList()) {
+ LOG(Interpolator, Error) << "yaml object must be a list";
+ return -EINVAL;
+ }
+
+ for (const auto &value : yaml.asList()) {
+ unsigned int ct = std::stoul(value[key_name].get<std::string>(""));
+ std::optional<T> data =
+ value[value_name].get<T>();
+ if (!data) {
+ return -EINVAL;
+ }
+
+ data_[ct] = *data;
+ }
+
+ if (data_.size() < 1) {
+ LOG(Interpolator, Error) << "Need at least one element";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ void setQuantization(const unsigned int q)
+ {
+ quantization_ = q;
+ }
+
+ void setData(std::map<unsigned int, T> &&data)
+ {
+ data_ = std::move(data);
+ lastInterpolatedKey_.reset();
+ }
+
+ const T &getInterpolated(unsigned int key, unsigned int *quantizedKey = nullptr)
+ {
+ ASSERT(data_.size() > 0);
+
+ if (quantization_ > 0)
+ key = std::lround(key / static_cast<double>(quantization_)) * quantization_;
+
+ if (quantizedKey)
+ *quantizedKey = key;
+
+ if (lastInterpolatedKey_.has_value() &&
+ *lastInterpolatedKey_ == key)
+ return lastInterpolatedValue_;
+
+ auto it = data_.lower_bound(key);
+
+ if (it == data_.begin())
+ return it->second;
+
+ if (it == data_.end())
+ return std::prev(it)->second;
+
+ if (it->first == key)
+ return it->second;
+
+ auto it2 = std::prev(it);
+ double lambda = (key - it2->first) / static_cast<double>(it->first - it2->first);
+ interpolate(it2->second, it->second, lastInterpolatedValue_, lambda);
+ lastInterpolatedKey_ = key;
+
+ return lastInterpolatedValue_;
+ }
+
+ void interpolate(const T &a, const T &b, T &dest, double lambda)
+ {
+ dest = a * (1.0 - lambda) + b * lambda;
+ }
+
+private:
+ std::map<unsigned int, T> data_;
+ T lastInterpolatedValue_;
+ std::optional<unsigned int> lastInterpolatedKey_;
+ unsigned int quantization_ = 0;
+};
+
+} /* namespace ipa */
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/lsc_polynomial.cpp b/src/ipa/libipa/lsc_polynomial.cpp
new file mode 100644
index 00000000..f607d86c
--- /dev/null
+++ b/src/ipa/libipa/lsc_polynomial.cpp
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * Polynomial class to represent lens shading correction
+ */
+
+#include "lsc_polynomial.h"
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file lsc_polynomial.h
+ * \brief LscPolynomial class
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(LscPolynomial)
+
+namespace ipa {
+
+/**
+ * \class LscPolynomial
+ * \brief Class for handling even polynomials used in lens shading correction
+ *
+ * Shading artifacts of camera lenses can be modeled using even radial
+ * polynomials. This class implements a polynomial with 5 coefficients which
+ * follows the definition of the FixVignetteRadial opcode in the Adobe DNG
+ * specification.
+ */
+
+/**
+ * \fn LscPolynomial::LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0,
+ double k1 = 0.0, double k2 = 0.0, double k3 = 0.0,
+ double k4 = 0.0)
+ * \brief Construct a polynomial using the given coefficients
+ * \param cx Center-x relative to the image in normalized coordinates (0..1)
+ * \param cy Center-y relative to the image in normalized coordinates (0..1)
+ * \param k0 Coefficient of the polynomial
+ * \param k1 Coefficient of the polynomial
+ * \param k2 Coefficient of the polynomial
+ * \param k3 Coefficient of the polynomial
+ * \param k4 Coefficient of the polynomial
+ */
+
+/**
+ * \fn LscPolynomial::sampleAtNormalizedPixelPos(double x, double y)
+ * \brief Sample the polynomial at the given normalized pixel position
+ *
+ * This functions samples the polynomial at the given pixel position divided by
+ * the value returned by getM().
+ *
+ * \param x x position in normalized coordinates
+ * \param y y position in normalized coordinates
+ * \return The sampled value
+ */
+
+/**
+ * \fn LscPolynomial::getM()
+ * \brief Get the value m as described in the dng specification
+ *
+ * Returns m according to dng spec. m represents the Euclidean distance
+ * (in pixels) from the optical center to the farthest pixel in the
+ * image.
+ *
+ * \return The sampled value
+ */
+
+/**
+ * \fn LscPolynomial::setReferenceImageSize(const Size &size)
+ * \brief Set the reference image size
+ *
+ * Set the reference image size that is used for subsequent calls to getM() and
+ * sampleAtNormalizedPixelPos()
+ *
+ * \param size The size of the reference image
+ */
+
+} // namespace ipa
+} // namespace libcamera
diff --git a/src/ipa/libipa/lsc_polynomial.h b/src/ipa/libipa/lsc_polynomial.h
new file mode 100644
index 00000000..c898faeb
--- /dev/null
+++ b/src/ipa/libipa/lsc_polynomial.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * Helper for radial polynomial used in lens shading correction.
+ */
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <assert.h>
+#include <cmath>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/span.h>
+
+#include "libcamera/internal/yaml_parser.h"
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(LscPolynomial)
+
+namespace ipa {
+
+class LscPolynomial
+{
+public:
+ LscPolynomial(double cx = 0.0, double cy = 0.0, double k0 = 0.0,
+ double k1 = 0.0, double k2 = 0.0, double k3 = 0.0,
+ double k4 = 0.0)
+ : cx_(cx), cy_(cy), cnx_(0), cny_(0),
+ coefficients_({ k0, k1, k2, k3, k4 })
+ {
+ }
+
+ double sampleAtNormalizedPixelPos(double x, double y) const
+ {
+ double dx = x - cnx_;
+ double dy = y - cny_;
+ double r = sqrt(dx * dx + dy * dy);
+ double res = 1.0;
+ for (unsigned int i = 0; i < coefficients_.size(); i++) {
+ res += coefficients_[i] * std::pow(r, (i + 1) * 2);
+ }
+ return res;
+ }
+
+ double getM() const
+ {
+ double cpx = imageSize_.width * cx_;
+ double cpy = imageSize_.height * cy_;
+ double mx = std::max(cpx, std::fabs(imageSize_.width - cpx));
+ double my = std::max(cpy, std::fabs(imageSize_.height - cpy));
+
+ return sqrt(mx * mx + my * my);
+ }
+
+ void setReferenceImageSize(const Size &size)
+ {
+ assert(!size.isNull());
+ imageSize_ = size;
+
+ /* Calculate normalized centers */
+ double m = getM();
+ cnx_ = (size.width * cx_) / m;
+ cny_ = (size.height * cy_) / m;
+ }
+
+private:
+ double cx_;
+ double cy_;
+ double cnx_;
+ double cny_;
+ std::array<double, 5> coefficients_;
+
+ Size imageSize_;
+};
+
+} /* namespace ipa */
+
+#ifndef __DOXYGEN__
+
+template<>
+struct YamlObject::Getter<ipa::LscPolynomial> {
+ std::optional<ipa::LscPolynomial> get(const YamlObject &obj) const
+ {
+ std::optional<double> cx = obj["cx"].get<double>();
+ std::optional<double> cy = obj["cy"].get<double>();
+ std::optional<double> k0 = obj["k0"].get<double>();
+ std::optional<double> k1 = obj["k1"].get<double>();
+ std::optional<double> k2 = obj["k2"].get<double>();
+ std::optional<double> k3 = obj["k3"].get<double>();
+ std::optional<double> k4 = obj["k4"].get<double>();
+
+ if (!(cx && cy && k0 && k1 && k2 && k3 && k4))
+ LOG(LscPolynomial, Error)
+ << "Polynomial is missing a parameter";
+
+ return ipa::LscPolynomial(*cx, *cy, *k0, *k1, *k2, *k3, *k4);
+ }
+};
+
+#endif
+
+} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix.h b/src/ipa/libipa/matrix.h
index 8aa8f343..5471e697 100644
--- a/src/ipa/libipa/matrix.h
+++ b/src/ipa/libipa/matrix.h
@@ -7,7 +7,6 @@
#pragma once
#include <algorithm>
-#include <cmath>
#include <sstream>
#include <vector>
diff --git a/src/ipa/libipa/matrix_interpolator.cpp b/src/ipa/libipa/matrix_interpolator.cpp
deleted file mode 100644
index 04ca177f..00000000
--- a/src/ipa/libipa/matrix_interpolator.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
- *
- * Helper class for interpolating maps of matrices
- */
-#include "matrix_interpolator.h"
-
-#include <algorithm>
-#include <string>
-
-#include <libcamera/base/log.h>
-
-#include "libcamera/internal/yaml_parser.h"
-
-#include "matrix.h"
-
-/**
- * \file matrix_interpolator.h
- * \brief Helper class for interpolating maps of matrices
- */
-
-namespace libcamera {
-
-LOG_DEFINE_CATEGORY(MatrixInterpolator)
-
-namespace ipa {
-
-/**
- * \class MatrixInterpolator
- * \brief Class for storing, retrieving, and interpolating matrices
- * \tparam T Type of numerical values to be stored in the matrices
- * \tparam R Number of rows in the matrices
- * \tparam C Number of columns in the matrices
- *
- * The main use case is to pass a map from color temperatures to corresponding
- * matrices (eg. color correction), and then requesting a matrix for a specific
- * color temperature. This class will abstract away the interpolation portion.
- */
-
-/**
- * \fn MatrixInterpolator::MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
- * \brief Construct a matrix interpolator from a map of matrices
- * \param matrices Map from which to construct the matrix interpolator
- */
-
-/**
- * \fn MatrixInterpolator::reset()
- * \brief Reset the matrix interpolator content to a single identity matrix
- */
-
-/**
- * \fn int MatrixInterpolator<T, R, C>::readYaml()
- * \brief Initialize an MatrixInterpolator instance from yaml
- * \tparam T Type of data stored in the matrices
- * \tparam R Number of rows of the matrices
- * \tparam C Number of columns of the matrices
- * \param[in] yaml The yaml object that contains the map of unsigned integers to matrices
- * \param[in] key_name The name of the key in the yaml object
- * \param[in] matrix_name The name of the matrix in the yaml object
- *
- * The yaml object is expected to be a list of maps. Each map has two or more
- * pairs: one of \a key_name to the key value (usually color temperature), and
- * one or more of \a matrix_name to the matrix. This is a bit difficult to
- * explain, so here is an example (in python, as it is easier to parse than
- * yaml):
- * [
- * {
- * 'ct': 2860,
- * 'ccm': [ 2.12089, -0.52461, -0.59629,
- * -0.85342, 2.80445, -0.95103,
- * -0.26897, -1.14788, 2.41685 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- *
- * {
- * 'ct': 2960,
- * 'ccm': [ 2.26962, -0.54174, -0.72789,
- * -0.77008, 2.60271, -0.83262,
- * -0.26036, -1.51254, 2.77289 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- *
- * {
- * 'ct': 3603,
- * 'ccm': [ 2.18644, -0.66148, -0.52496,
- * -0.77828, 2.69474, -0.91645,
- * -0.25239, -0.83059, 2.08298 ],
- * 'offsets': [ 0, 0, 0 ]
- * },
- * ]
- *
- * In this case, \a key_name would be 'ct', and \a matrix_name can be either
- * 'ccm' or 'offsets'. This way multiple matrix interpolators can be defined in
- * one set of color temperature ranges in the tuning file, and they can be
- * retrieved separately with the \a matrix_name parameter.
- *
- * \return Zero on success, negative error code otherwise
- */
-
-/**
- * \fn Matrix<T, R, C> MatrixInterpolator<T, R, C>::get(unsigned int key)
- * \brief Retrieve a matrix from the list of matrices, interpolating if necessary
- * \param[in] key The unsigned integer key of the matrix to retrieve
- * \return The matrix corresponding to the color temperature
- */
-
-} /* namespace ipa */
-
-} /* namespace libcamera */
diff --git a/src/ipa/libipa/matrix_interpolator.h b/src/ipa/libipa/matrix_interpolator.h
deleted file mode 100644
index 087c4fd1..00000000
--- a/src/ipa/libipa/matrix_interpolator.h
+++ /dev/null
@@ -1,122 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
- *
- * Helper class for interpolating maps of matrices
- */
-
-#pragma once
-
-#include <algorithm>
-#include <map>
-#include <string>
-#include <tuple>
-
-#include <libcamera/base/log.h>
-
-#include "libcamera/internal/yaml_parser.h"
-
-#include "matrix.h"
-
-namespace libcamera {
-
-LOG_DECLARE_CATEGORY(MatrixInterpolator)
-
-namespace ipa {
-
-#ifndef __DOXYGEN__
-template<typename T, unsigned int R, unsigned int C,
- std::enable_if_t<std::is_arithmetic_v<T>> * = nullptr>
-#else
-template<typename T, unsigned int R, unsigned int C>
-#endif /* __DOXYGEN__ */
-class MatrixInterpolator
-{
-public:
- MatrixInterpolator()
- {
- reset();
- }
-
- MatrixInterpolator(const std::map<unsigned int, Matrix<T, R, C>> &matrices)
- {
- for (const auto &pair : matrices)
- matrices_[pair.first] = pair.second;
- }
-
- ~MatrixInterpolator() {}
-
- void reset()
- {
- matrices_.clear();
- matrices_[0] = Matrix<T, R, C>::identity();
- }
-
- int readYaml(const libcamera::YamlObject &yaml,
- const std::string &key_name,
- const std::string &matrix_name)
- {
- matrices_.clear();
-
- if (!yaml.isList()) {
- LOG(MatrixInterpolator, Error) << "yaml object must be a list";
- return -EINVAL;
- }
-
- for (const auto &value : yaml.asList()) {
- unsigned int ct = std::stoul(value[key_name].get<std::string>(""));
- std::optional<Matrix<T, R, C>> matrix =
- value[matrix_name].get<Matrix<T, R, C>>();
- if (!matrix) {
- LOG(MatrixInterpolator, Error) << "Failed to read matrix";
- return -EINVAL;
- }
-
- matrices_[ct] = *matrix;
-
- LOG(MatrixInterpolator, Debug)
- << "Read matrix '" << matrix_name << "' for key '"
- << key_name << "' " << ct << ": "
- << matrices_[ct].toString();
- }
-
- if (matrices_.size() < 1) {
- LOG(MatrixInterpolator, Error) << "Need at least one matrix";
- return -EINVAL;
- }
-
- return 0;
- }
-
- Matrix<T, R, C> get(unsigned int ct)
- {
- ASSERT(matrices_.size() > 0);
-
- if (matrices_.size() == 1 ||
- ct <= matrices_.begin()->first)
- return matrices_.begin()->second;
-
- if (ct >= matrices_.rbegin()->first)
- return matrices_.rbegin()->second;
-
- if (matrices_.find(ct) != matrices_.end())
- return matrices_[ct];
-
- /* The above four guarantee that this will succeed */
- auto iter = matrices_.upper_bound(ct);
- unsigned int ctUpper = iter->first;
- unsigned int ctLower = (--iter)->first;
-
- double lambda = (ct - ctLower) / static_cast<double>(ctUpper - ctLower);
- Matrix<T, R, C> ret =
- lambda * matrices_[ctUpper] + (1.0 - lambda) * matrices_[ctLower];
- return ret;
- }
-
-private:
- std::map<unsigned int, Matrix<T, R, C>> matrices_;
-};
-
-} /* namespace ipa */
-
-} /* namespace libcamera */
diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build
index eff8ce26..e78cbcd6 100644
--- a/src/ipa/libipa/meson.build
+++ b/src/ipa/libipa/meson.build
@@ -7,8 +7,9 @@ libipa_headers = files([
'exposure_mode_helper.h',
'fc_queue.h',
'histogram.h',
+ 'interpolator.h',
+ 'lsc_polynomial.h',
'matrix.h',
- 'matrix_interpolator.h',
'module.h',
'pwl.h',
'vector.h',
@@ -21,8 +22,9 @@ libipa_sources = files([
'exposure_mode_helper.cpp',
'fc_queue.cpp',
'histogram.cpp',
+ 'interpolator.cpp',
+ 'lsc_polynomial.cpp',
'matrix.cpp',
- 'matrix_interpolator.cpp',
'module.cpp',
'pwl.cpp',
'vector.cpp',
diff --git a/src/ipa/libipa/pwl.cpp b/src/ipa/libipa/pwl.cpp
index 9b213754..88fe2022 100644
--- a/src/ipa/libipa/pwl.cpp
+++ b/src/ipa/libipa/pwl.cpp
@@ -8,10 +8,8 @@
#include "pwl.h"
-#include <assert.h>
#include <cmath>
#include <sstream>
-#include <stdexcept>
/**
* \file pwl.h
diff --git a/src/ipa/libipa/pwl.h b/src/ipa/libipa/pwl.h
index b6f93494..d4ec9f4f 100644
--- a/src/ipa/libipa/pwl.h
+++ b/src/ipa/libipa/pwl.h
@@ -7,14 +7,11 @@
#pragma once
#include <algorithm>
-#include <cmath>
#include <functional>
#include <string>
#include <utility>
#include <vector>
-#include "libcamera/internal/yaml_parser.h"
-
#include "vector.h"
namespace libcamera {
diff --git a/src/ipa/libipa/vector.h b/src/ipa/libipa/vector.h
index 556e0967..8612a06a 100644
--- a/src/ipa/libipa/vector.h
+++ b/src/ipa/libipa/vector.h
@@ -6,10 +6,10 @@
*/
#pragma once
-#include <algorithm>
#include <array>
#include <cmath>
-#include <sstream>
+#include <optional>
+#include <ostream>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
diff --git a/src/ipa/rkisp1/algorithms/agc.cpp b/src/ipa/rkisp1/algorithms/agc.cpp
index f12f8b60..17d074d9 100644
--- a/src/ipa/rkisp1/algorithms/agc.cpp
+++ b/src/ipa/rkisp1/algorithms/agc.cpp
@@ -281,7 +281,7 @@ void Agc::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Agc::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
if (frameContext.agc.autoEnabled) {
frameContext.agc.exposure = context.activeState.agc.automatic.exposure;
@@ -291,41 +291,39 @@ void Agc::prepare(IPAContext &context, const uint32_t frame,
if (frame > 0 && !frameContext.agc.updateMetering)
return;
- /* Configure the measurement window. */
- params->meas.aec_config.meas_window = context.configuration.agc.measureWindow;
- /* Use a continuous method for measure. */
- params->meas.aec_config.autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
- /* Estimate Y as (R + G + B) x (85/256). */
- params->meas.aec_config.mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
+ /*
+ * Configure the AEC measurements. Set the window, measure
+ * continuously, and estimate Y as (R + G + B) x (85/256).
+ */
+ auto aecConfig = params->block<BlockType::Aec>();
+ aecConfig.setEnabled(true);
+
+ aecConfig->meas_window = context.configuration.agc.measureWindow;
+ aecConfig->autostop = RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0;
+ aecConfig->mode = RKISP1_CIF_ISP_EXP_MEASURING_MODE_1;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AEC;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AEC;
+ /*
+ * Configure the histogram measurement. Set the window, produce a
+ * luminance histogram, and set the weights and predivider.
+ */
+ auto hstConfig = params->block<BlockType::Hst>();
+ hstConfig.setEnabled(true);
- /* Configure histogram. */
- params->meas.hst_config.meas_window = context.configuration.agc.measureWindow;
- /* Produce the luminance histogram. */
- params->meas.hst_config.mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
+ hstConfig->meas_window = context.configuration.agc.measureWindow;
+ hstConfig->mode = RKISP1_CIF_ISP_HISTOGRAM_MODE_Y_HISTOGRAM;
- /* Set an average weighted histogram. */
Span<uint8_t> weights{
- params->meas.hst_config.hist_weight,
+ hstConfig->hist_weight,
context.hw->numHistogramWeights
};
std::vector<uint8_t> &modeWeights = meteringModes_.at(frameContext.agc.meteringMode);
std::copy(modeWeights.begin(), modeWeights.end(), weights.begin());
- struct rkisp1_cif_isp_window window = params->meas.hst_config.meas_window;
+ struct rkisp1_cif_isp_window window = hstConfig->meas_window;
Size windowSize = { window.h_size, window.v_size };
- params->meas.hst_config.histogram_predivider =
+ hstConfig->histogram_predivider =
computeHistogramPredivider(windowSize,
- static_cast<rkisp1_cif_isp_histogram_mode>(params->meas.hst_config.mode));
-
- /* Update the configuration for histogram. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_HST;
- /* Enable the histogram measure unit. */
- params->module_ens |= RKISP1_CIF_ISP_MODULE_HST;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_HST;
+ static_cast<rkisp1_cif_isp_histogram_mode>(hstConfig->mode));
}
void Agc::fillMetadata(IPAContext &context, IPAFrameContext &frameContext,
diff --git a/src/ipa/rkisp1/algorithms/agc.h b/src/ipa/rkisp1/algorithms/agc.h
index 9ceaa82b..aa86f2c5 100644
--- a/src/ipa/rkisp1/algorithms/agc.h
+++ b/src/ipa/rkisp1/algorithms/agc.h
@@ -15,7 +15,6 @@
#include <libcamera/geometry.h>
#include "libipa/agc_mean_luminance.h"
-#include "libipa/histogram.h"
#include "algorithm.h"
@@ -37,7 +36,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/awb.cpp b/src/ipa/rkisp1/algorithms/awb.cpp
index a01fe5d9..955a9ff4 100644
--- a/src/ipa/rkisp1/algorithms/awb.cpp
+++ b/src/ipa/rkisp1/algorithms/awb.cpp
@@ -8,12 +8,12 @@
#include "awb.h"
#include <algorithm>
-#include <cmath>
-#include <iomanip>
+#include <ios>
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
+
#include <libcamera/ipa/core_ipa_interface.h>
/**
@@ -108,7 +108,7 @@ void Awb::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Awb::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
/*
* This is the latest time we can read the active state. This is the
@@ -120,29 +120,30 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
frameContext.awb.gains.blue = context.activeState.awb.gains.automatic.blue;
}
- params->others.awb_gain_config.gain_green_b = 256 * frameContext.awb.gains.green;
- params->others.awb_gain_config.gain_blue = 256 * frameContext.awb.gains.blue;
- params->others.awb_gain_config.gain_red = 256 * frameContext.awb.gains.red;
- params->others.awb_gain_config.gain_green_r = 256 * frameContext.awb.gains.green;
+ auto gainConfig = params->block<BlockType::AwbGain>();
+ gainConfig.setEnabled(true);
- /* Update the gains. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
+ gainConfig->gain_green_b = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff);
+ gainConfig->gain_blue = std::clamp<int>(256 * frameContext.awb.gains.blue, 0, 0x3ff);
+ gainConfig->gain_red = std::clamp<int>(256 * frameContext.awb.gains.red, 0, 0x3ff);
+ gainConfig->gain_green_r = std::clamp<int>(256 * frameContext.awb.gains.green, 0, 0x3ff);
/* If we have already set the AWB measurement parameters, return. */
if (frame > 0)
return;
- rkisp1_cif_isp_awb_meas_config &awb_config = params->meas.awb_meas_config;
+ auto awbConfig = params->block<BlockType::Awb>();
+ awbConfig.setEnabled(true);
/* Configure the measure window for AWB. */
- awb_config.awb_wnd = context.configuration.awb.measureWindow;
+ awbConfig->awb_wnd = context.configuration.awb.measureWindow;
/* Number of frames to use to estimate the means (0 means 1 frame). */
- awb_config.frames = 0;
+ awbConfig->frames = 0;
/* Select RGB or YCbCr means measurement. */
if (rgbMode_) {
- awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB;
+ awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_RGB;
/*
* For RGB-based measurements, pixels are selected with maximum
@@ -150,19 +151,19 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
* awb_ref_cr, awb_min_y and awb_ref_cb respectively. The other
* values are not used, set them to 0.
*/
- awb_config.awb_ref_cr = 250;
- awb_config.min_y = 250;
- awb_config.awb_ref_cb = 250;
+ awbConfig->awb_ref_cr = 250;
+ awbConfig->min_y = 250;
+ awbConfig->awb_ref_cb = 250;
- awb_config.max_y = 0;
- awb_config.min_c = 0;
- awb_config.max_csum = 0;
+ awbConfig->max_y = 0;
+ awbConfig->min_c = 0;
+ awbConfig->max_csum = 0;
} else {
- awb_config.awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
+ awbConfig->awb_mode = RKISP1_CIF_ISP_AWB_MODE_YCBCR;
/* Set the reference Cr and Cb (AWB target) to white. */
- awb_config.awb_ref_cb = 128;
- awb_config.awb_ref_cr = 128;
+ awbConfig->awb_ref_cb = 128;
+ awbConfig->awb_ref_cr = 128;
/*
* Filter out pixels based on luminance and chrominance values.
@@ -170,20 +171,11 @@ void Awb::prepare(IPAContext &context, const uint32_t frame,
* range, while the acceptable chroma values are specified with
* a minimum of 16 and a maximum Cb+Cr sum of 250.
*/
- awb_config.min_y = 16;
- awb_config.max_y = 250;
- awb_config.min_c = 16;
- awb_config.max_csum = 250;
+ awbConfig->min_y = 16;
+ awbConfig->max_y = 250;
+ awbConfig->min_c = 16;
+ awbConfig->max_csum = 250;
}
-
- /* Enable the AWB gains. */
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB_GAIN;
-
- /* Update the AWB measurement parameters and enable the AWB module. */
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_AWB;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_AWB;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_AWB;
}
uint32_t Awb::estimateCCT(double red, double green, double blue)
@@ -218,6 +210,12 @@ void Awb::process(IPAContext &context,
double redMean;
double blueMean;
+ metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
+ metadata.set(controls::ColourGains, {
+ static_cast<float>(frameContext.awb.gains.red),
+ static_cast<float>(frameContext.awb.gains.blue)
+ });
+
if (rgbMode_) {
greenMean = awb->awb_mean[0].mean_y_or_g;
redMean = awb->awb_mean[0].mean_cr_or_r;
@@ -273,12 +271,15 @@ void Awb::process(IPAContext &context,
*/
if (redMean < kMeanMinThreshold && greenMean < kMeanMinThreshold &&
blueMean < kMeanMinThreshold) {
- frameContext.awb.temperatureK = activeState.awb.temperatureK;
+ metadata.set(controls::ColourTemperature, activeState.awb.temperatureK);
return;
}
activeState.awb.temperatureK = estimateCCT(redMean, greenMean, blueMean);
+ /* Metadata shall contain the up to date measurement */
+ metadata.set(controls::ColourTemperature, activeState.awb.temperatureK);
+
/*
* Estimate the red and blue gains to apply in a grey world. The green
* gain is hardcoded to 1.0. Avoid divisions by zero by clamping the
@@ -305,21 +306,13 @@ void Awb::process(IPAContext &context,
activeState.awb.gains.automatic.blue = blueGain;
activeState.awb.gains.automatic.green = 1.0;
- frameContext.awb.temperatureK = activeState.awb.temperatureK;
-
- metadata.set(controls::AwbEnable, frameContext.awb.autoEnabled);
- metadata.set(controls::ColourGains, {
- static_cast<float>(frameContext.awb.gains.red),
- static_cast<float>(frameContext.awb.gains.blue)
- });
- metadata.set(controls::ColourTemperature, frameContext.awb.temperatureK);
-
- LOG(RkISP1Awb, Debug) << std::showpoint
+ LOG(RkISP1Awb, Debug)
+ << std::showpoint
<< "Means [" << redMean << ", " << greenMean << ", " << blueMean
<< "], gains [" << activeState.awb.gains.automatic.red << ", "
<< activeState.awb.gains.automatic.green << ", "
<< activeState.awb.gains.automatic.blue << "], temp "
- << frameContext.awb.temperatureK << "K";
+ << activeState.awb.temperatureK << "K";
}
REGISTER_IPA_ALGORITHM(Awb, "Awb")
diff --git a/src/ipa/rkisp1/algorithms/awb.h b/src/ipa/rkisp1/algorithms/awb.h
index 06c92896..b3b2c0bb 100644
--- a/src/ipa/rkisp1/algorithms/awb.h
+++ b/src/ipa/rkisp1/algorithms/awb.h
@@ -25,7 +25,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index d2e74354..98cb7145 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -7,8 +7,12 @@
#include "blc.h"
+#include <linux/videodev2.h>
+
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
#include "libcamera/internal/yaml_parser.h"
/**
@@ -36,22 +40,62 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Blc)
BlackLevelCorrection::BlackLevelCorrection()
- : tuningParameters_(false)
{
+ /*
+ * This is a bit of a hack. In raw mode no black level correction
+ * happens. This flag is used to ensure the metadata gets populated with
+ * the black level which is needed to capture proper raw images for
+ * tuning.
+ */
+ supportsRaw_ = true;
}
/**
* \copydoc libcamera::ipa::Algorithm::init
*/
-int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
- const YamlObject &tuningData)
+int BlackLevelCorrection::init(IPAContext &context, const YamlObject &tuningData)
{
- blackLevelRed_ = tuningData["R"].get<int16_t>(256);
- blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
- blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
- blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
-
- tuningParameters_ = true;
+ std::optional<int16_t> levelRed = tuningData["R"].get<int16_t>();
+ std::optional<int16_t> levelGreenR = tuningData["Gr"].get<int16_t>();
+ std::optional<int16_t> levelGreenB = tuningData["Gb"].get<int16_t>();
+ std::optional<int16_t> levelBlue = tuningData["B"].get<int16_t>();
+ bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
+
+ auto blackLevel = context.camHelper->blackLevel();
+ if (!blackLevel) {
+ /*
+ * Not all camera sensor helpers have been updated with black
+ * levels. Print a warning and fall back to the levels from the
+ * tuning data to preserve backward compatibility. This should
+ * be removed once all helpers provide the data.
+ */
+ LOG(RkISP1Blc, Warning)
+ << "No black levels provided by camera sensor helper"
+ << ", please fix";
+
+ blackLevelRed_ = levelRed.value_or(4096);
+ blackLevelGreenR_ = levelGreenR.value_or(4096);
+ blackLevelGreenB_ = levelGreenB.value_or(4096);
+ blackLevelBlue_ = levelBlue.value_or(4096);
+ } else if (tuningHasLevels) {
+ /*
+ * If black levels are provided in the tuning file, use them to
+ * avoid breaking existing camera tuning. This is deprecated and
+ * will be removed.
+ */
+ LOG(RkISP1Blc, Warning)
+ << "Deprecated: black levels overwritten by tuning file";
+
+ blackLevelRed_ = *levelRed;
+ blackLevelGreenR_ = *levelGreenR;
+ blackLevelGreenB_ = *levelGreenB;
+ blackLevelBlue_ = *levelBlue;
+ } else {
+ blackLevelRed_ = *blackLevel;
+ blackLevelGreenR_ = *blackLevel;
+ blackLevelGreenB_ = *blackLevel;
+ blackLevelBlue_ = *blackLevel;
+ }
LOG(RkISP1Blc, Debug)
<< "Black levels: red " << blackLevelRed_
@@ -62,29 +106,80 @@ int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
return 0;
}
+int BlackLevelCorrection::configure(IPAContext &context,
+ [[maybe_unused]] const IPACameraSensorInfo &configInfo)
+{
+ /*
+ * BLC on ISP versions that include the companding block requires usage
+ * of the extensible parameters format.
+ */
+ supported_ = context.configuration.paramFormat == V4L2_META_FMT_RK_ISP1_EXT_PARAMS ||
+ !context.hw->compand;
+
+ if (!supported_)
+ LOG(RkISP1Blc, Warning)
+ << "BLC in companding block requires extensible parameters";
+
+ return 0;
+}
+
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
-void BlackLevelCorrection::prepare([[maybe_unused]] IPAContext &context,
+void BlackLevelCorrection::prepare(IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
+ if (context.configuration.raw)
+ return;
+
if (frame > 0)
return;
- if (!tuningParameters_)
+ if (!supported_)
return;
- params->others.bls_config.enable_auto = 0;
- params->others.bls_config.fixed_val.r = blackLevelRed_;
- params->others.bls_config.fixed_val.gr = blackLevelGreenR_;
- params->others.bls_config.fixed_val.gb = blackLevelGreenB_;
- params->others.bls_config.fixed_val.b = blackLevelBlue_;
+ if (context.hw->compand) {
+ auto config = params->block<BlockType::CompandBls>();
+ config.setEnabled(true);
+
+ /*
+ * Scale up to the 20-bit black levels used by the companding
+ * block.
+ */
+ config->r = blackLevelRed_ << 4;
+ config->gr = blackLevelGreenR_ << 4;
+ config->gb = blackLevelGreenB_ << 4;
+ config->b = blackLevelBlue_ << 4;
+ } else {
+ auto config = params->block<BlockType::Bls>();
+ config.setEnabled(true);
+
+ config->enable_auto = 0;
+
+ /* Scale down to the 12-bit black levels used by the BLS block. */
+ config->fixed_val.r = blackLevelRed_ >> 4;
+ config->fixed_val.gr = blackLevelGreenR_ >> 4;
+ config->fixed_val.gb = blackLevelGreenB_ >> 4;
+ config->fixed_val.b = blackLevelBlue_ >> 4;
+ }
+}
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_BLS;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_BLS;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_BLS;
+/**
+ * \copydoc libcamera::ipa::Algorithm::process
+ */
+void BlackLevelCorrection::process([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext,
+ [[maybe_unused]] const rkisp1_stat_buffer *stats,
+ ControlList &metadata)
+{
+ metadata.set(controls::SensorBlackLevels,
+ { static_cast<int32_t>(blackLevelRed_),
+ static_cast<int32_t>(blackLevelGreenR_),
+ static_cast<int32_t>(blackLevelGreenB_),
+ static_cast<int32_t>(blackLevelBlue_) });
}
REGISTER_IPA_ALGORITHM(BlackLevelCorrection, "BlackLevelCorrection")
diff --git a/src/ipa/rkisp1/algorithms/blc.h b/src/ipa/rkisp1/algorithms/blc.h
index 460ebcc1..f797ae44 100644
--- a/src/ipa/rkisp1/algorithms/blc.h
+++ b/src/ipa/rkisp1/algorithms/blc.h
@@ -20,12 +20,19 @@ public:
~BlackLevelCorrection() = default;
int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context,
+ const IPACameraSensorInfo &configInfo) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
+ void process(IPAContext &context, const uint32_t frame,
+ IPAFrameContext &frameContext,
+ const rkisp1_stat_buffer *stats,
+ ControlList &metadata) override;
private:
- bool tuningParameters_;
+ bool supported_;
+
int16_t blackLevelRed_;
int16_t blackLevelGreenR_;
int16_t blackLevelGreenB_;
diff --git a/src/ipa/rkisp1/algorithms/ccm.cpp b/src/ipa/rkisp1/algorithms/ccm.cpp
index c1f5403a..6b7d2e2c 100644
--- a/src/ipa/rkisp1/algorithms/ccm.cpp
+++ b/src/ipa/rkisp1/algorithms/ccm.cpp
@@ -7,11 +7,7 @@
#include "ccm.h"
-#include <algorithm>
-#include <chrono>
-#include <cmath>
-#include <tuple>
-#include <vector>
+#include <map>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -23,7 +19,7 @@
#include "libcamera/internal/yaml_parser.h"
#include "../utils.h"
-#include "libipa/matrix_interpolator.h"
+#include "libipa/interpolator.h"
/**
* \file ccm.h
@@ -50,7 +46,7 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
LOG(RkISP1Ccm, Warning)
<< "Failed to parse 'ccm' "
<< "parameter from tuning file; falling back to unit matrix";
- ccm_.reset();
+ ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } });
}
ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets");
@@ -58,25 +54,17 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
LOG(RkISP1Ccm, Warning)
<< "Failed to parse 'offsets' "
<< "parameter from tuning file; falling back to zero offsets";
- /*
- * MatrixInterpolator::reset() resets to identity matrices
- * while here we need zero matrices so we need to construct it
- * ourselves.
- */
- Matrix<int16_t, 3, 1> m({ 0, 0, 0 });
- std::map<unsigned int, Matrix<int16_t, 3, 1>> matrices = { { 0, m } };
- offsets_ = MatrixInterpolator<int16_t, 3, 1>(matrices);
+
+ offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } });
}
return 0;
}
-void Ccm::setParameters(rkisp1_params_cfg *params,
+void Ccm::setParameters(struct rkisp1_cif_isp_ctk_config &config,
const Matrix<float, 3, 3> &matrix,
const Matrix<int16_t, 3, 1> &offsets)
{
- struct rkisp1_cif_isp_ctk_config &config = params->others.ctk_config;
-
/*
* 4 bit integer and 7 bit fractional, ranging from -8 (0x400) to
* +7.992 (0x3ff)
@@ -92,18 +80,13 @@ void Ccm::setParameters(rkisp1_params_cfg *params,
LOG(RkISP1Ccm, Debug) << "Setting matrix " << matrix;
LOG(RkISP1Ccm, Debug) << "Setting offsets " << offsets;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_CTK;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_CTK;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CTK;
}
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Ccm::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
uint32_t ct = context.activeState.awb.temperatureK;
@@ -111,16 +94,21 @@ void Ccm::prepare(IPAContext &context, const uint32_t frame,
* \todo The colour temperature will likely be noisy, add filtering to
* avoid updating the CCM matrix all the time.
*/
- if (frame > 0 && ct == ct_)
+ if (frame > 0 && ct == ct_) {
+ frameContext.ccm.ccm = context.activeState.ccm.ccm;
return;
+ }
ct_ = ct;
- Matrix<float, 3, 3> ccm = ccm_.get(ct);
- Matrix<int16_t, 3, 1> offsets = offsets_.get(ct);
+ Matrix<float, 3, 3> ccm = ccm_.getInterpolated(ct);
+ Matrix<int16_t, 3, 1> offsets = offsets_.getInterpolated(ct);
+ context.activeState.ccm.ccm = ccm;
frameContext.ccm.ccm = ccm;
- setParameters(params, ccm, offsets);
+ auto config = params->block<BlockType::Ctk>();
+ config.setEnabled(true);
+ setParameters(*config, ccm, offsets);
}
/**
@@ -135,7 +123,7 @@ void Ccm::process([[maybe_unused]] IPAContext &context,
float m[9];
for (unsigned int i = 0; i < 3; i++) {
for (unsigned int j = 0; j < 3; j++)
- m[i] = frameContext.ccm.ccm[i][j];
+ m[i * 3 + j] = frameContext.ccm.ccm[i][j];
}
metadata.set(controls::ColourCorrectionMatrix, m);
}
diff --git a/src/ipa/rkisp1/algorithms/ccm.h b/src/ipa/rkisp1/algorithms/ccm.h
index 30cb8821..46a1416e 100644
--- a/src/ipa/rkisp1/algorithms/ccm.h
+++ b/src/ipa/rkisp1/algorithms/ccm.h
@@ -9,8 +9,8 @@
#include <linux/rkisp1-config.h>
+#include "libipa/interpolator.h"
#include "libipa/matrix.h"
-#include "libipa/matrix_interpolator.h"
#include "algorithm.h"
@@ -27,7 +27,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
@@ -35,13 +35,13 @@ public:
private:
void parseYaml(const YamlObject &tuningData);
- void setParameters(rkisp1_params_cfg *params,
+ void setParameters(struct rkisp1_cif_isp_ctk_config &config,
const Matrix<float, 3, 3> &matrix,
const Matrix<int16_t, 3, 1> &offsets);
unsigned int ct_;
- MatrixInterpolator<float, 3, 3> ccm_;
- MatrixInterpolator<int16_t, 3, 1> offsets_;
+ Interpolator<Matrix<float, 3, 3>> ccm_;
+ Interpolator<Matrix<int16_t, 3, 1>> offsets_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/cproc.cpp b/src/ipa/rkisp1/algorithms/cproc.cpp
index ef0931b2..d1fff699 100644
--- a/src/ipa/rkisp1/algorithms/cproc.cpp
+++ b/src/ipa/rkisp1/algorithms/cproc.cpp
@@ -140,19 +140,17 @@ void ColorProcessing::queueRequest(IPAContext &context,
void ColorProcessing::prepare([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
/* Check if the algorithm configuration has been updated. */
if (!frameContext.cproc.update)
return;
- params->others.cproc_config.brightness = frameContext.cproc.brightness;
- params->others.cproc_config.contrast = frameContext.cproc.contrast;
- params->others.cproc_config.sat = frameContext.cproc.saturation;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_CPROC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_CPROC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_CPROC;
+ auto config = params->block<BlockType::Cproc>();
+ config.setEnabled(true);
+ config->brightness = frameContext.cproc.brightness;
+ config->contrast = frameContext.cproc.contrast;
+ config->sat = frameContext.cproc.saturation;
}
REGISTER_IPA_ALGORITHM(ColorProcessing, "ColorProcessing")
diff --git a/src/ipa/rkisp1/algorithms/cproc.h b/src/ipa/rkisp1/algorithms/cproc.h
index e50e7200..fd38fd17 100644
--- a/src/ipa/rkisp1/algorithms/cproc.h
+++ b/src/ipa/rkisp1/algorithms/cproc.h
@@ -29,7 +29,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/dpcc.cpp b/src/ipa/rkisp1/algorithms/dpcc.cpp
index b5a339e9..78946281 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.cpp
+++ b/src/ipa/rkisp1/algorithms/dpcc.cpp
@@ -232,16 +232,14 @@ int DefectPixelClusterCorrection::init([[maybe_unused]] IPAContext &context,
void DefectPixelClusterCorrection::prepare([[maybe_unused]] IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
if (frame > 0)
return;
- params->others.dpcc_config = config_;
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPCC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_DPCC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPCC;
+ auto config = params->block<BlockType::Dpcc>();
+ config.setEnabled(true);
+ *config = config_;
}
REGISTER_IPA_ALGORITHM(DefectPixelClusterCorrection, "DefectPixelClusterCorrection")
diff --git a/src/ipa/rkisp1/algorithms/dpcc.h b/src/ipa/rkisp1/algorithms/dpcc.h
index d39b7bed..b77766c3 100644
--- a/src/ipa/rkisp1/algorithms/dpcc.h
+++ b/src/ipa/rkisp1/algorithms/dpcc.h
@@ -22,7 +22,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
rkisp1_cif_isp_dpcc_config config_;
diff --git a/src/ipa/rkisp1/algorithms/dpf.cpp b/src/ipa/rkisp1/algorithms/dpf.cpp
index abf95728..cb6095da 100644
--- a/src/ipa/rkisp1/algorithms/dpf.cpp
+++ b/src/ipa/rkisp1/algorithms/dpf.cpp
@@ -7,7 +7,9 @@
#include "dpf.h"
-#include <cmath>
+#include <algorithm>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
@@ -215,15 +217,21 @@ void Dpf::queueRequest(IPAContext &context,
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void Dpf::prepare(IPAContext &context, const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
- if (frame == 0) {
- params->others.dpf_config = config_;
- params->others.dpf_strength_config = strengthConfig_;
+ if (!frameContext.dpf.update && frame > 0)
+ return;
+
+ auto config = params->block<BlockType::Dpf>();
+ config.setEnabled(frameContext.dpf.denoise);
+
+ if (frameContext.dpf.denoise) {
+ *config = config_;
const auto &awb = context.configuration.awb;
const auto &lsc = context.configuration.lsc;
- auto &mode = params->others.dpf_config.gain.mode;
+
+ auto &mode = config->gain.mode;
/*
* The DPF needs to take into account the total amount of
@@ -241,15 +249,12 @@ void Dpf::prepare(IPAContext &context, const uint32_t frame,
mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS;
else
mode = RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED;
-
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_DPF |
- RKISP1_CIF_ISP_MODULE_DPF_STRENGTH;
}
- if (frameContext.dpf.update) {
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_DPF;
- if (frameContext.dpf.denoise)
- params->module_ens |= RKISP1_CIF_ISP_MODULE_DPF;
+ if (frame == 0) {
+ auto strengthConfig = params->block<BlockType::DpfStrength>();
+ strengthConfig.setEnabled(true);
+ *strengthConfig = strengthConfig_;
}
}
diff --git a/src/ipa/rkisp1/algorithms/dpf.h b/src/ipa/rkisp1/algorithms/dpf.h
index da0115ba..2dd8cd36 100644
--- a/src/ipa/rkisp1/algorithms/dpf.h
+++ b/src/ipa/rkisp1/algorithms/dpf.h
@@ -27,7 +27,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
struct rkisp1_cif_isp_dpf_config config_;
diff --git a/src/ipa/rkisp1/algorithms/filter.cpp b/src/ipa/rkisp1/algorithms/filter.cpp
index 9752248a..7598ef8a 100644
--- a/src/ipa/rkisp1/algorithms/filter.cpp
+++ b/src/ipa/rkisp1/algorithms/filter.cpp
@@ -104,7 +104,7 @@ void Filter::queueRequest(IPAContext &context,
*/
void Filter::prepare([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const uint32_t frame,
- IPAFrameContext &frameContext, rkisp1_params_cfg *params)
+ IPAFrameContext &frameContext, RkISP1Params *params)
{
/* Check if the algorithm configuration has been updated. */
if (!frameContext.filter.update)
@@ -160,23 +160,25 @@ void Filter::prepare([[maybe_unused]] IPAContext &context,
uint8_t denoise = frameContext.filter.denoise;
uint8_t sharpness = frameContext.filter.sharpness;
- auto &flt_config = params->others.flt_config;
-
- flt_config.fac_sh0 = filt_fac_sh0[sharpness];
- flt_config.fac_sh1 = filt_fac_sh1[sharpness];
- flt_config.fac_mid = filt_fac_mid[sharpness];
- flt_config.fac_bl0 = filt_fac_bl0[sharpness];
- flt_config.fac_bl1 = filt_fac_bl1[sharpness];
-
- flt_config.lum_weight = kFiltLumWeightDefault;
- flt_config.mode = kFiltModeDefault;
- flt_config.thresh_sh0 = filt_thresh_sh0[denoise];
- flt_config.thresh_sh1 = filt_thresh_sh1[denoise];
- flt_config.thresh_bl0 = filt_thresh_bl0[denoise];
- flt_config.thresh_bl1 = filt_thresh_bl1[denoise];
- flt_config.grn_stage1 = stage1_select[denoise];
- flt_config.chr_v_mode = filt_chr_v_mode[denoise];
- flt_config.chr_h_mode = filt_chr_h_mode[denoise];
+
+ auto config = params->block<BlockType::Flt>();
+ config.setEnabled(true);
+
+ config->fac_sh0 = filt_fac_sh0[sharpness];
+ config->fac_sh1 = filt_fac_sh1[sharpness];
+ config->fac_mid = filt_fac_mid[sharpness];
+ config->fac_bl0 = filt_fac_bl0[sharpness];
+ config->fac_bl1 = filt_fac_bl1[sharpness];
+
+ config->lum_weight = kFiltLumWeightDefault;
+ config->mode = kFiltModeDefault;
+ config->thresh_sh0 = filt_thresh_sh0[denoise];
+ config->thresh_sh1 = filt_thresh_sh1[denoise];
+ config->thresh_bl0 = filt_thresh_bl0[denoise];
+ config->thresh_bl1 = filt_thresh_bl1[denoise];
+ config->grn_stage1 = stage1_select[denoise];
+ config->chr_v_mode = filt_chr_v_mode[denoise];
+ config->chr_h_mode = filt_chr_h_mode[denoise];
/*
* Combined high denoising and high sharpening requires some
@@ -186,27 +188,23 @@ void Filter::prepare([[maybe_unused]] IPAContext &context,
*/
if (denoise == 9) {
if (sharpness > 3)
- flt_config.grn_stage1 = 2;
+ config->grn_stage1 = 2;
} else if (denoise == 10) {
if (sharpness > 5)
- flt_config.grn_stage1 = 2;
+ config->grn_stage1 = 2;
else if (sharpness > 3)
- flt_config.grn_stage1 = 1;
+ config->grn_stage1 = 1;
}
if (denoise > 7) {
if (sharpness > 7) {
- flt_config.fac_bl0 /= 2;
- flt_config.fac_bl1 /= 4;
+ config->fac_bl0 /= 2;
+ config->fac_bl1 /= 4;
} else if (sharpness > 4) {
- flt_config.fac_bl0 = flt_config.fac_bl0 * 3 / 4;
- flt_config.fac_bl1 /= 2;
+ config->fac_bl0 = config->fac_bl0 * 3 / 4;
+ config->fac_bl1 /= 2;
}
}
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_FLT;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_FLT;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_FLT;
}
REGISTER_IPA_ALGORITHM(Filter, "Filter")
diff --git a/src/ipa/rkisp1/algorithms/filter.h b/src/ipa/rkisp1/algorithms/filter.h
index d595811d..8f858e57 100644
--- a/src/ipa/rkisp1/algorithms/filter.h
+++ b/src/ipa/rkisp1/algorithms/filter.h
@@ -26,7 +26,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/algorithms/goc.cpp b/src/ipa/rkisp1/algorithms/goc.cpp
index a82cee3b..a9493678 100644
--- a/src/ipa/rkisp1/algorithms/goc.cpp
+++ b/src/ipa/rkisp1/algorithms/goc.cpp
@@ -99,11 +99,14 @@ void GammaOutCorrection::queueRequest(IPAContext &context, const uint32_t frame,
void GammaOutCorrection::prepare(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
ASSERT(context.hw->numGammaOutSamples ==
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10);
+ if (!frameContext.goc.update)
+ return;
+
/*
* The logarithmic segments as specified in the reference.
* Plus an additional 0 to make the loop easier
@@ -112,10 +115,11 @@ void GammaOutCorrection::prepare(IPAContext &context,
64, 64, 64, 64, 128, 128, 128, 128, 256,
256, 256, 512, 512, 512, 512, 512, 0
};
- __u16 *gamma_y = params->others.goc_config.gamma_y;
- if (!frameContext.goc.update)
- return;
+ auto config = params->block<BlockType::Goc>();
+ config.setEnabled(true);
+
+ __u16 *gamma_y = config->gamma_y;
unsigned x = 0;
for (const auto [i, size] : utils::enumerate(segments)) {
@@ -123,10 +127,7 @@ void GammaOutCorrection::prepare(IPAContext &context,
x += size;
}
- params->others.goc_config.mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_GOC;
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_GOC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_GOC;
+ config->mode = RKISP1_CIF_ISP_GOC_MODE_LOGARITHMIC;
}
/**
diff --git a/src/ipa/rkisp1/algorithms/goc.h b/src/ipa/rkisp1/algorithms/goc.h
index 0e05d7ce..bb2ddfc9 100644
--- a/src/ipa/rkisp1/algorithms/goc.h
+++ b/src/ipa/rkisp1/algorithms/goc.h
@@ -28,7 +28,7 @@ public:
const ControlList &controls) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
void process(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
const rkisp1_stat_buffer *stats,
diff --git a/src/ipa/rkisp1/algorithms/gsl.cpp b/src/ipa/rkisp1/algorithms/gsl.cpp
index 9b056c6e..9604c0ac 100644
--- a/src/ipa/rkisp1/algorithms/gsl.cpp
+++ b/src/ipa/rkisp1/algorithms/gsl.cpp
@@ -119,24 +119,20 @@ int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context,
void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context,
const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
if (frame > 0)
return;
- params->others.sdg_config.xa_pnts.gamma_dx0 = gammaDx_[0];
- params->others.sdg_config.xa_pnts.gamma_dx1 = gammaDx_[1];
+ auto config = params->block<BlockType::Sdg>();
+ config.setEnabled(true);
- std::copy(curveYr_.begin(), curveYr_.end(),
- params->others.sdg_config.curve_r.gamma_y);
- std::copy(curveYg_.begin(), curveYg_.end(),
- params->others.sdg_config.curve_g.gamma_y);
- std::copy(curveYb_.begin(), curveYb_.end(),
- params->others.sdg_config.curve_b.gamma_y);
+ config->xa_pnts.gamma_dx0 = gammaDx_[0];
+ config->xa_pnts.gamma_dx1 = gammaDx_[1];
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_SDG;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_SDG;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_SDG;
+ std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r.gamma_y);
+ std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g.gamma_y);
+ std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b.gamma_y);
}
REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization")
diff --git a/src/ipa/rkisp1/algorithms/gsl.h b/src/ipa/rkisp1/algorithms/gsl.h
index c404105e..91cf6efa 100644
--- a/src/ipa/rkisp1/algorithms/gsl.h
+++ b/src/ipa/rkisp1/algorithms/gsl.h
@@ -22,7 +22,7 @@ public:
int init(IPAContext &context, const YamlObject &tuningData) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
private:
uint32_t gammaDx_[2];
diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 161183fc..e47aa2f0 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -16,6 +16,7 @@
#include "libcamera/internal/yaml_parser.h"
+#include "libipa/lsc_polynomial.h"
#include "linux/rkisp1-config.h"
/**
@@ -24,6 +25,36 @@
namespace libcamera {
+namespace ipa {
+
+constexpr int kColourTemperatureChangeThreshhold = 10;
+
+template<typename T>
+void interpolateVector(const std::vector<T> &a, const std::vector<T> &b,
+ std::vector<T> &dest, double lambda)
+{
+ assert(a.size() == b.size());
+ dest.resize(a.size());
+ for (size_t i = 0; i < a.size(); i++) {
+ dest[i] = a[i] * (1.0 - lambda) + b[i] * lambda;
+ }
+}
+
+template<>
+void Interpolator<rkisp1::algorithms::LensShadingCorrection::Components>::
+ interpolate(const rkisp1::algorithms::LensShadingCorrection::Components &a,
+ const rkisp1::algorithms::LensShadingCorrection::Components &b,
+ rkisp1::algorithms::LensShadingCorrection::Components &dest,
+ double lambda)
+{
+ interpolateVector(a.r, b.r, dest.r, lambda);
+ interpolateVector(a.gr, b.gr, dest.gr, lambda);
+ interpolateVector(a.gb, b.gb, dest.gb, lambda);
+ interpolateVector(a.b, b.b, dest.b, lambda);
+}
+
+} /* namespace ipa */
+
namespace ipa::rkisp1::algorithms {
/**
@@ -40,6 +71,200 @@ namespace ipa::rkisp1::algorithms {
LOG_DEFINE_CATEGORY(RkISP1Lsc)
+class LscPolynomialLoader
+{
+public:
+ LscPolynomialLoader(const Size &sensorSize,
+ const Rectangle &cropRectangle,
+ const std::vector<double> &xSizes,
+ const std::vector<double> &ySizes)
+ : sensorSize_(sensorSize),
+ cropRectangle_(cropRectangle),
+ xSizes_(xSizes),
+ ySizes_(ySizes)
+ {
+ }
+
+ int parseLscData(const YamlObject &yamlSets,
+ std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+ {
+ const auto &sets = yamlSets.asList();
+ for (const auto &yamlSet : sets) {
+ std::optional<LscPolynomial> pr, pgr, pgb, pb;
+ uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+
+ if (lscData.count(ct)) {
+ LOG(RkISP1Lsc, Error)
+ << "Multiple sets found for "
+ << "color temperature " << ct;
+ return -EINVAL;
+ }
+
+ LensShadingCorrection::Components &set = lscData[ct];
+ pr = yamlSet["r"].get<LscPolynomial>();
+ pgr = yamlSet["gr"].get<LscPolynomial>();
+ pgb = yamlSet["gb"].get<LscPolynomial>();
+ pb = yamlSet["b"].get<LscPolynomial>();
+
+ if (!(pr || pgr || pgb || pb)) {
+ LOG(RkISP1Lsc, Error)
+ << "Failed to parse polynomial for "
+ << "colour temperature " << ct;
+ return -EINVAL;
+ }
+
+ set.ct = ct;
+ pr->setReferenceImageSize(sensorSize_);
+ pgr->setReferenceImageSize(sensorSize_);
+ pgb->setReferenceImageSize(sensorSize_);
+ pb->setReferenceImageSize(sensorSize_);
+ set.r = samplePolynomial(*pr);
+ set.gr = samplePolynomial(*pgr);
+ set.gb = samplePolynomial(*pgb);
+ set.b = samplePolynomial(*pb);
+ }
+
+ if (lscData.empty()) {
+ LOG(RkISP1Lsc, Error) << "Failed to load any sets";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+private:
+ /*
+ * The lsc grid has custom spacing defined on half the range (see
+ * parseSizes() for details). For easier handling this function converts
+ * the spaces vector to positions and mirrors them. E.g.:
+ *
+ * input: | 0.2 | 0.3 |
+ * output: 0.0 0.2 0.5 0.8 1.0
+ */
+ std::vector<double> sizesListToPositions(const std::vector<double> &sizes)
+ {
+ const int half = sizes.size();
+ std::vector<double> res(half * 2 + 1);
+ double x = 0.0;
+
+ res[half] = 0.5;
+ for (int i = 1; i <= half; i++) {
+ x += sizes[half - i];
+ res[half - i] = 0.5 - x;
+ res[half + i] = 0.5 + x;
+ }
+
+ return res;
+ }
+
+ std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly)
+ {
+ constexpr int k = RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+
+ double m = poly.getM();
+ double x0 = cropRectangle_.x / m;
+ double y0 = cropRectangle_.y / m;
+ double w = cropRectangle_.width / m;
+ double h = cropRectangle_.height / m;
+ std::vector<uint16_t> res;
+
+ assert(xSizes_.size() * 2 + 1 == k);
+ assert(ySizes_.size() * 2 + 1 == k);
+
+ res.reserve(k * k);
+
+ std::vector<double> xPos(sizesListToPositions(xSizes_));
+ std::vector<double> yPos(sizesListToPositions(ySizes_));
+
+ for (int y = 0; y < k; y++) {
+ for (int x = 0; x < k; x++) {
+ double xp = x0 + xPos[x] * w;
+ double yp = y0 + yPos[y] * h;
+ /*
+ * The hardware uses 2.10 fixed point format and
+ * limits the legal values to [1..3.999]. Scale
+ * and clamp the sampled value accordingly.
+ */
+ int v = static_cast<int>(
+ poly.sampleAtNormalizedPixelPos(xp, yp) *
+ 1024);
+ v = std::min(std::max(v, 1024), 4095);
+ res.push_back(v);
+ }
+ }
+ return res;
+ }
+
+ Size sensorSize_;
+ Rectangle cropRectangle_;
+ const std::vector<double> &xSizes_;
+ const std::vector<double> &ySizes_;
+};
+
+class LscTableLoader
+{
+public:
+ int parseLscData(const YamlObject &yamlSets,
+ std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+ {
+ const auto &sets = yamlSets.asList();
+
+ for (const auto &yamlSet : sets) {
+ uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
+
+ if (lscData.count(ct)) {
+ LOG(RkISP1Lsc, Error)
+ << "Multiple sets found for color temperature "
+ << ct;
+ return -EINVAL;
+ }
+
+ LensShadingCorrection::Components &set = lscData[ct];
+
+ set.ct = ct;
+ set.r = parseTable(yamlSet, "r");
+ set.gr = parseTable(yamlSet, "gr");
+ set.gb = parseTable(yamlSet, "gb");
+ set.b = parseTable(yamlSet, "b");
+
+ if (set.r.empty() || set.gr.empty() ||
+ set.gb.empty() || set.b.empty()) {
+ LOG(RkISP1Lsc, Error)
+ << "Set for color temperature " << ct
+ << " is missing tables";
+ return -EINVAL;
+ }
+ }
+
+ if (lscData.empty()) {
+ LOG(RkISP1Lsc, Error) << "Failed to load any sets";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+private:
+ std::vector<uint16_t> parseTable(const YamlObject &tuningData,
+ const char *prop)
+ {
+ static constexpr unsigned int kLscNumSamples =
+ RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
+
+ std::vector<uint16_t> table =
+ tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{});
+ if (table.size() != kLscNumSamples) {
+ LOG(RkISP1Lsc, Error)
+ << "Invalid '" << prop << "' values: expected "
+ << kLscNumSamples
+ << " elements, got " << table.size();
+ return {};
+ }
+
+ return table;
+ }
+};
+
static std::vector<double> parseSizes(const YamlObject &tuningData,
const char *prop)
{
@@ -70,28 +295,10 @@ static std::vector<double> parseSizes(const YamlObject &tuningData,
return sizes;
}
-static std::vector<uint16_t> parseTable(const YamlObject &tuningData,
- const char *prop)
-{
- static constexpr unsigned int kLscNumSamples =
- RKISP1_CIF_ISP_LSC_SAMPLES_MAX * RKISP1_CIF_ISP_LSC_SAMPLES_MAX;
-
- std::vector<uint16_t> table =
- tuningData[prop].getList<uint16_t>().value_or(std::vector<uint16_t>{});
- if (table.size() != kLscNumSamples) {
- LOG(RkISP1Lsc, Error)
- << "Invalid '" << prop << "' values: expected "
- << kLscNumSamples
- << " elements, got " << table.size();
- return {};
- }
-
- return table;
-}
-
LensShadingCorrection::LensShadingCorrection()
- : lastCt_({ 0, 0 })
+ : lastAppliedCt_(0), lastAppliedQuantizedCt_(0)
{
+ sets_.setQuantization(kColourTemperatureChangeThreshhold);
}
/**
@@ -114,38 +321,30 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
return -EINVAL;
}
- const auto &sets = yamlSets.asList();
- for (const auto &yamlSet : sets) {
- uint32_t ct = yamlSet["ct"].get<uint32_t>(0);
-
- if (sets_.count(ct)) {
- LOG(RkISP1Lsc, Error)
- << "Multiple sets found for color temperature "
- << ct;
- return -EINVAL;
- }
-
- Components &set = sets_[ct];
-
- set.ct = ct;
- set.r = parseTable(yamlSet, "r");
- set.gr = parseTable(yamlSet, "gr");
- set.gb = parseTable(yamlSet, "gb");
- set.b = parseTable(yamlSet, "b");
-
- if (set.r.empty() || set.gr.empty() ||
- set.gb.empty() || set.b.empty()) {
- LOG(RkISP1Lsc, Error)
- << "Set for color temperature " << ct
- << " is missing tables";
- return -EINVAL;
- }
+ std::map<unsigned int, Components> lscData;
+ int res = 0;
+ std::string type = tuningData["type"].get<std::string>("table");
+ if (type == "table") {
+ LOG(RkISP1Lsc, Debug) << "Loading tabular LSC data.";
+ auto loader = LscTableLoader();
+ res = loader.parseLscData(yamlSets, lscData);
+ } else if (type == "polynomial") {
+ LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data.";
+ auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,
+ context.sensorInfo.analogCrop,
+ xSize_,
+ ySize_);
+ res = loader.parseLscData(yamlSets, lscData);
+ } else {
+ LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '"
+ << type << "'";
+ res = -EINVAL;
}
- if (sets_.empty()) {
- LOG(RkISP1Lsc, Error) << "Failed to load any sets";
- return -EINVAL;
- }
+ if (res)
+ return res;
+
+ sets_.setData(std::move(lscData));
return 0;
}
@@ -185,18 +384,12 @@ int LensShadingCorrection::configure(IPAContext &context,
return 0;
}
-void LensShadingCorrection::setParameters(rkisp1_params_cfg *params)
+void LensShadingCorrection::setParameters(rkisp1_cif_isp_lsc_config &config)
{
- struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
-
memcpy(config.x_grad_tbl, xGrad_, sizeof(config.x_grad_tbl));
memcpy(config.y_grad_tbl, yGrad_, sizeof(config.y_grad_tbl));
memcpy(config.x_size_tbl, xSizes_, sizeof(config.x_size_tbl));
memcpy(config.y_size_tbl, ySizes_, sizeof(config.y_size_tbl));
-
- params->module_en_update |= RKISP1_CIF_ISP_MODULE_LSC;
- params->module_ens |= RKISP1_CIF_ISP_MODULE_LSC;
- params->module_cfg_update |= RKISP1_CIF_ISP_MODULE_LSC;
}
void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
@@ -208,131 +401,34 @@ void LensShadingCorrection::copyTable(rkisp1_cif_isp_lsc_config &config,
std::copy(set.b.begin(), set.b.end(), &config.b_data_tbl[0][0]);
}
-/*
- * Interpolate LSC parameters based on color temperature value.
- */
-void LensShadingCorrection::interpolateTable(rkisp1_cif_isp_lsc_config &config,
- const Components &set0,
- const Components &set1,
- const uint32_t ct)
-{
- double coeff0 = (set1.ct - ct) / static_cast<double>(set1.ct - set0.ct);
- double coeff1 = (ct - set0.ct) / static_cast<double>(set1.ct - set0.ct);
-
- for (unsigned int i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++i) {
- for (unsigned int j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; ++j) {
- unsigned int sample = i * RKISP1_CIF_ISP_LSC_SAMPLES_MAX + j;
-
- config.r_data_tbl[i][j] =
- set0.r[sample] * coeff0 +
- set1.r[sample] * coeff1;
-
- config.gr_data_tbl[i][j] =
- set0.gr[sample] * coeff0 +
- set1.gr[sample] * coeff1;
-
- config.gb_data_tbl[i][j] =
- set0.gb[sample] * coeff0 +
- set1.gb[sample] * coeff1;
-
- config.b_data_tbl[i][j] =
- set0.b[sample] * coeff0 +
- set1.b[sample] * coeff1;
- }
- }
-}
-
/**
* \copydoc libcamera::ipa::Algorithm::prepare
*/
void LensShadingCorrection::prepare(IPAContext &context,
- const uint32_t frame,
+ [[maybe_unused]] const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
- rkisp1_params_cfg *params)
+ RkISP1Params *params)
{
- struct rkisp1_cif_isp_lsc_config &config = params->others.lsc_config;
-
- /*
- * If there is only one set, the configuration has already been done
- * for first frame.
- */
- if (sets_.size() == 1 && frame > 0)
- return;
-
- /*
- * If there is only one set, pick it. We can ignore lastCt_, as it will
- * never be relevant.
- */
- if (sets_.size() == 1) {
- setParameters(params);
- copyTable(config, sets_.cbegin()->second);
- return;
- }
-
uint32_t ct = context.activeState.awb.temperatureK;
- ct = std::clamp(ct, sets_.cbegin()->first, sets_.crbegin()->first);
-
- /*
- * If the original is the same, then it means the same adjustment would
- * be made. If the adjusted is the same, then it means that it's the
- * same as what was actually applied. Thus in these cases we can skip
- * reprogramming the LSC.
- *
- * original == adjusted can only happen if an interpolation
- * happened, or if original has an exact entry in sets_. This means
- * that if original != adjusted, then original was adjusted to
- * the nearest available entry in sets_, resulting in adjusted.
- * Clearly, any ct value that is in between original and adjusted
- * will be adjusted to the same adjusted value, so we can skip
- * reprogramming the LSC table.
- *
- * We also skip updating the original value, as the last one had a
- * larger bound and thus a larger range of ct values that will be
- * adjusted to the same adjusted.
- */
- if ((lastCt_.original <= ct && ct <= lastCt_.adjusted) ||
- (lastCt_.adjusted <= ct && ct <= lastCt_.original))
+ if (std::abs(static_cast<int>(ct) - static_cast<int>(lastAppliedCt_)) <
+ kColourTemperatureChangeThreshhold)
return;
-
- setParameters(params);
-
- /*
- * The color temperature matches exactly one of the available LSC tables.
- */
- if (sets_.count(ct)) {
- copyTable(config, sets_[ct]);
- lastCt_ = { ct, ct };
+ unsigned int quantizedCt;
+ const Components &set = sets_.getInterpolated(ct, &quantizedCt);
+ if (lastAppliedQuantizedCt_ == quantizedCt)
return;
- }
- /* No shortcuts left; we need to round or interpolate */
- auto iter = sets_.upper_bound(ct);
- const Components &set1 = iter->second;
- const Components &set0 = (--iter)->second;
- uint32_t ct0 = set0.ct;
- uint32_t ct1 = set1.ct;
- uint32_t diff0 = ct - ct0;
- uint32_t diff1 = ct1 - ct;
- static constexpr double kThreshold = 0.1;
- float threshold = kThreshold * (ct1 - ct0);
-
- if (diff0 < threshold || diff1 < threshold) {
- const Components &set = diff0 < diff1 ? set0 : set1;
- LOG(RkISP1Lsc, Debug) << "using LSC table for " << set.ct;
- copyTable(config, set);
- lastCt_ = { ct, set.ct };
- return;
- }
+ auto config = params->block<BlockType::Lsc>();
+ config.setEnabled(true);
+ setParameters(*config);
+ copyTable(*config, set);
+
+ lastAppliedCt_ = ct;
+ lastAppliedQuantizedCt_ = quantizedCt;
- /*
- * ct is not within 10% of the difference between the neighbouring
- * color temperatures, so we need to interpolate.
- */
LOG(RkISP1Lsc, Debug)
- << "ct is " << ct << ", interpolating between "
- << ct0 << " and " << ct1;
- interpolateTable(config, set0, set1, ct);
- lastCt_ = { ct, ct };
+ << "ct is " << ct << ", quantized to "
+ << quantizedCt;
}
REGISTER_IPA_ALGORITHM(LensShadingCorrection, "LensShadingCorrection")
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index 5baf5927..5a0824e3 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -9,6 +9,8 @@
#include <map>
+#include "libipa/interpolator.h"
+
#include "algorithm.h"
namespace libcamera {
@@ -25,9 +27,8 @@ public:
int configure(IPAContext &context, const IPACameraSensorInfo &configInfo) override;
void prepare(IPAContext &context, const uint32_t frame,
IPAFrameContext &frameContext,
- rkisp1_params_cfg *params) override;
+ RkISP1Params *params) override;
-private:
struct Components {
uint32_t ct;
std::vector<uint16_t> r;
@@ -36,23 +37,23 @@ private:
std::vector<uint16_t> b;
};
- void setParameters(rkisp1_params_cfg *params);
+private:
+ void setParameters(rkisp1_cif_isp_lsc_config &config);
void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
void interpolateTable(rkisp1_cif_isp_lsc_config &config,
const Components &set0, const Components &set1,
const uint32_t ct);
- std::map<uint32_t, Components> sets_;
+ ipa::Interpolator<Components> sets_;
std::vector<double> xSize_;
std::vector<double> ySize_;
uint16_t xGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t yGrad_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t xSizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
uint16_t ySizes_[RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE];
- struct {
- uint32_t original;
- uint32_t adjusted;
- } lastCt_;
+
+ unsigned int lastAppliedCt_;
+ unsigned int lastAppliedQuantizedCt_;
};
} /* namespace ipa::rkisp1::algorithms */
diff --git a/src/ipa/rkisp1/data/imx219.yaml b/src/ipa/rkisp1/data/imx219.yaml
index cbcc43b8..0d99cb52 100644
--- a/src/ipa/rkisp1/data/imx219.yaml
+++ b/src/ipa/rkisp1/data/imx219.yaml
@@ -6,10 +6,6 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 256
- Gr: 256
- Gb: 256
- B: 256
- LensShadingCorrection:
x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
diff --git a/src/ipa/rkisp1/data/imx258.yaml b/src/ipa/rkisp1/data/imx258.yaml
index 43dddf20..202af36a 100644
--- a/src/ipa/rkisp1/data/imx258.yaml
+++ b/src/ipa/rkisp1/data/imx258.yaml
@@ -5,6 +5,7 @@ version: 1
algorithms:
- Agc:
- Awb:
+ - BlackLevelCorrection:
- LensShadingCorrection:
x-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
y-size: [ 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625, 0.0625 ]
diff --git a/src/ipa/rkisp1/data/meson.build b/src/ipa/rkisp1/data/meson.build
index 7150e155..1e3522b2 100644
--- a/src/ipa/rkisp1/data/meson.build
+++ b/src/ipa/rkisp1/data/meson.build
@@ -2,8 +2,12 @@
conf_files = files([
'imx219.yaml',
+ 'imx258.yaml',
+ 'ov2685.yaml',
'ov4689.yaml',
'ov5640.yaml',
+ 'ov5695.yaml',
+ 'ov8858.yaml',
'uncalibrated.yaml',
])
diff --git a/src/ipa/rkisp1/data/ov4689.yaml b/src/ipa/rkisp1/data/ov4689.yaml
index 2068684c..60901296 100644
--- a/src/ipa/rkisp1/data/ov4689.yaml
+++ b/src/ipa/rkisp1/data/ov4689.yaml
@@ -6,8 +6,4 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 66
- Gr: 66
- Gb: 66
- B: 66
...
diff --git a/src/ipa/rkisp1/data/ov5640.yaml b/src/ipa/rkisp1/data/ov5640.yaml
index 897b83cb..4b21d412 100644
--- a/src/ipa/rkisp1/data/ov5640.yaml
+++ b/src/ipa/rkisp1/data/ov5640.yaml
@@ -6,10 +6,6 @@ algorithms:
- Agc:
- Awb:
- BlackLevelCorrection:
- R: 256
- Gr: 256
- Gb: 256
- B: 256
- ColorProcessing:
- GammaSensorLinearization:
x-intervals: [ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ]
diff --git a/src/ipa/rkisp1/data/uncalibrated.yaml b/src/ipa/rkisp1/data/uncalibrated.yaml
index a7bbd8d8..60901296 100644
--- a/src/ipa/rkisp1/data/uncalibrated.yaml
+++ b/src/ipa/rkisp1/data/uncalibrated.yaml
@@ -5,4 +5,5 @@ version: 1
algorithms:
- Agc:
- Awb:
+ - BlackLevelCorrection:
...
diff --git a/src/ipa/rkisp1/ipa_context.cpp b/src/ipa/rkisp1/ipa_context.cpp
index 9f3f576a..14d0c02a 100644
--- a/src/ipa/rkisp1/ipa_context.cpp
+++ b/src/ipa/rkisp1/ipa_context.cpp
@@ -106,6 +106,11 @@ namespace libcamera::ipa::rkisp1 {
*/
/**
+ * \var IPASessionConfiguration::paramFormat
+ * \brief The fourcc of the parameters buffers format
+ */
+
+/**
* \struct IPAActiveState
* \brief Active state for algorithms
*
@@ -419,6 +424,9 @@ namespace libcamera::ipa::rkisp1 {
* \var IPAContext::hw
* \brief RkISP1 version-specific hardware parameters
*
+ * \var IPAContext::sensorInfo
+ * \brief The IPA session sensorInfo, immutable during the session
+ *
* \var IPAContext::configuration
* \brief The IPA session configuration, immutable during the session
*
diff --git a/src/ipa/rkisp1/ipa_context.h b/src/ipa/rkisp1/ipa_context.h
index 8602b408..e274d9b0 100644
--- a/src/ipa/rkisp1/ipa_context.h
+++ b/src/ipa/rkisp1/ipa_context.h
@@ -8,6 +8,8 @@
#pragma once
+#include <memory>
+
#include <linux/rkisp1-config.h>
#include <libcamera/base/utils.h>
@@ -15,7 +17,9 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/geometry.h>
+#include <libcamera/ipa/core_ipa_interface.h>
+#include <libipa/camera_sensor_helper.h>
#include <libipa/fc_queue.h>
#include <libipa/matrix.h>
@@ -28,6 +32,7 @@ struct IPAHwSettings {
unsigned int numHistogramBins;
unsigned int numHistogramWeights;
unsigned int numGammaOutSamples;
+ bool compand;
};
struct IPASessionConfiguration {
@@ -56,6 +61,7 @@ struct IPASessionConfiguration {
} sensor;
bool raw;
+ uint32_t paramFormat;
};
struct IPAActiveState {
@@ -95,6 +101,10 @@ struct IPAActiveState {
} awb;
struct {
+ Matrix<float, 3, 3> ccm;
+ } ccm;
+
+ struct {
int8_t brightness;
uint8_t contrast;
uint8_t saturation;
@@ -133,7 +143,6 @@ struct IPAFrameContext : public FrameContext {
double blue;
} gains;
- unsigned int temperatureK;
bool autoEnabled;
} awb;
@@ -172,12 +181,16 @@ struct IPAFrameContext : public FrameContext {
struct IPAContext {
const IPAHwSettings *hw;
+ IPACameraSensorInfo sensorInfo;
IPASessionConfiguration configuration;
IPAActiveState activeState;
FCQueue<IPAFrameContext> frameContexts;
ControlInfoMap::Map ctrlMap;
+
+ /* Interface to the Camera Helper */
+ std::unique_ptr<CameraSensorHelper> camHelper;
};
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/meson.build b/src/ipa/rkisp1/meson.build
index e8b266f1..34844f14 100644
--- a/src/ipa/rkisp1/meson.build
+++ b/src/ipa/rkisp1/meson.build
@@ -7,14 +7,14 @@ ipa_name = 'ipa_rkisp1'
rkisp1_ipa_sources = files([
'ipa_context.cpp',
+ 'params.cpp',
'rkisp1.cpp',
'utils.cpp',
])
rkisp1_ipa_sources += rkisp1_ipa_algorithms
-mod = shared_module(ipa_name,
- [rkisp1_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, rkisp1_ipa_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/rkisp1/module.h b/src/ipa/rkisp1/module.h
index 16c3e43e..69e9bc82 100644
--- a/src/ipa/rkisp1/module.h
+++ b/src/ipa/rkisp1/module.h
@@ -14,13 +14,14 @@
#include <libipa/module.h>
#include "ipa_context.h"
+#include "params.h"
namespace libcamera {
namespace ipa::rkisp1 {
using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
- rkisp1_params_cfg, rkisp1_stat_buffer>;
+ RkISP1Params, rkisp1_stat_buffer>;
} /* namespace ipa::rkisp1 */
diff --git a/src/ipa/rkisp1/params.cpp b/src/ipa/rkisp1/params.cpp
new file mode 100644
index 00000000..4c0b051c
--- /dev/null
+++ b/src/ipa/rkisp1/params.cpp
@@ -0,0 +1,222 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 ISP Parameters
+ */
+
+#include "params.h"
+
+#include <map>
+#include <stddef.h>
+#include <string.h>
+
+#include <linux/rkisp1-config.h>
+#include <linux/videodev2.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(RkISP1Params)
+
+namespace ipa::rkisp1 {
+
+namespace {
+
+struct BlockTypeInfo {
+ enum rkisp1_ext_params_block_type type;
+ size_t size;
+ size_t offset;
+ uint32_t enableBit;
+};
+
+#define RKISP1_BLOCK_TYPE_ENTRY(block, id, type, category, bit) \
+ { BlockType::block, { \
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \
+ sizeof(struct rkisp1_cif_isp_##type##_config), \
+ offsetof(struct rkisp1_params_cfg, category.type##_config), \
+ RKISP1_CIF_ISP_MODULE_##bit, \
+ } }
+
+#define RKISP1_BLOCK_TYPE_ENTRY_MEAS(block, id, type) \
+ RKISP1_BLOCK_TYPE_ENTRY(block, id##_MEAS, type, meas, id)
+
+#define RKISP1_BLOCK_TYPE_ENTRY_OTHERS(block, id, type) \
+ RKISP1_BLOCK_TYPE_ENTRY(block, id, type, others, id)
+
+#define RKISP1_BLOCK_TYPE_ENTRY_EXT(block, id, type) \
+ { BlockType::block, { \
+ RKISP1_EXT_PARAMS_BLOCK_TYPE_##id, \
+ sizeof(struct rkisp1_cif_isp_##type##_config), \
+ 0, 0, \
+ } }
+
+const std::map<BlockType, BlockTypeInfo> kBlockTypeInfo = {
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bls, BLS, bls),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpcc, DPCC, dpcc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Sdg, SDG, sdg),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(AwbGain, AWB_GAIN, awb_gain),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Flt, FLT, flt),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Bdm, BDM, bdm),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ctk, CTK, ctk),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Goc, GOC, goc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Dpf, DPF, dpf),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(DpfStrength, DPF_STRENGTH, dpf_strength),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Cproc, CPROC, cproc),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Ie, IE, ie),
+ RKISP1_BLOCK_TYPE_ENTRY_OTHERS(Lsc, LSC, lsc),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Awb, AWB, awb_meas),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Hst, HST, hst),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Aec, AEC, aec),
+ RKISP1_BLOCK_TYPE_ENTRY_MEAS(Afc, AFC, afc),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandBls, COMPAND_BLS, compand_bls),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandExpand, COMPAND_EXPAND, compand_curve),
+ RKISP1_BLOCK_TYPE_ENTRY_EXT(CompandCompress, COMPAND_COMPRESS, compand_curve),
+};
+
+} /* namespace */
+
+RkISP1ParamsBlockBase::RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type,
+ const Span<uint8_t> &data)
+ : params_(params), type_(type)
+{
+ if (params_->format() == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) {
+ header_ = data.subspan(0, sizeof(rkisp1_ext_params_block_header));
+ data_ = data.subspan(sizeof(rkisp1_ext_params_block_header));
+ } else {
+ data_ = data;
+ }
+}
+
+void RkISP1ParamsBlockBase::setEnabled(bool enabled)
+{
+ /*
+ * For the legacy fixed format, blocks are enabled in the top-level
+ * header. Delegate to the RkISP1Params class.
+ */
+ if (params_->format() == V4L2_META_FMT_RK_ISP1_PARAMS)
+ return params_->setBlockEnabled(type_, enabled);
+
+ /*
+ * For the extensible format, set the enable and disable flags in the
+ * block header directly.
+ */
+ struct rkisp1_ext_params_block_header *header =
+ reinterpret_cast<struct rkisp1_ext_params_block_header *>(header_.data());
+ header->flags &= ~(RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE |
+ RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE);
+ header->flags |= enabled ? RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE
+ : RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE;
+}
+
+RkISP1Params::RkISP1Params(uint32_t format, Span<uint8_t> data)
+ : format_(format), data_(data), used_(0)
+{
+ if (format_ == V4L2_META_FMT_RK_ISP1_EXT_PARAMS) {
+ struct rkisp1_ext_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_ext_params_cfg *>(data.data());
+
+ cfg->version = RKISP1_EXT_PARAM_BUFFER_V1;
+ cfg->data_size = 0;
+
+ used_ += offsetof(struct rkisp1_ext_params_cfg, data);
+ } else {
+ memset(data.data(), 0, data.size());
+ used_ = sizeof(struct rkisp1_params_cfg);
+ }
+}
+
+void RkISP1Params::setBlockEnabled(BlockType type, bool enabled)
+{
+ const BlockTypeInfo &info = kBlockTypeInfo.at(type);
+
+ struct rkisp1_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_params_cfg *>(data_.data());
+ if (enabled)
+ cfg->module_ens |= info.enableBit;
+ else
+ cfg->module_ens &= ~info.enableBit;
+}
+
+Span<uint8_t> RkISP1Params::block(BlockType type)
+{
+ auto infoIt = kBlockTypeInfo.find(type);
+ if (infoIt == kBlockTypeInfo.end()) {
+ LOG(RkISP1Params, Error)
+ << "Invalid parameters block type "
+ << utils::to_underlying(type);
+ return {};
+ }
+
+ const BlockTypeInfo &info = infoIt->second;
+
+ /*
+ * For the legacy format, return a block referencing the fixed location
+ * of the data.
+ */
+ if (format_ == V4L2_META_FMT_RK_ISP1_PARAMS) {
+ /*
+ * Blocks available only in extended parameters have an offset
+ * of 0. Return nullptr in that case.
+ */
+ if (info.offset == 0) {
+ LOG(RkISP1Params, Error)
+ << "Block type " << utils::to_underlying(type)
+ << " unavailable in fixed parameters format";
+ return {};
+ }
+
+ struct rkisp1_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_params_cfg *>(data_.data());
+
+ cfg->module_cfg_update |= info.enableBit;
+ cfg->module_en_update |= info.enableBit;
+
+ return data_.subspan(info.offset, info.size);
+ }
+
+ /*
+ * For the extensible format, allocate memory for the block, including
+ * the header. Look up the block in the cache first. If an algorithm
+ * requests the same block type twice, it should get the same block.
+ */
+ auto cacheIt = blocks_.find(type);
+ if (cacheIt != blocks_.end())
+ return cacheIt->second;
+
+ /* Make sure we don't run out of space. */
+ size_t size = sizeof(struct rkisp1_ext_params_block_header)
+ + ((info.size + 7) & ~7);
+ if (size > data_.size() - used_) {
+ LOG(RkISP1Params, Error)
+ << "Out of memory to allocate block type "
+ << utils::to_underlying(type);
+ return {};
+ }
+
+ /* Allocate a new block, clear its memory, and initialize its header. */
+ Span<uint8_t> block = data_.subspan(used_, size);
+ used_ += size;
+
+ struct rkisp1_ext_params_cfg *cfg =
+ reinterpret_cast<struct rkisp1_ext_params_cfg *>(data_.data());
+ cfg->data_size += size;
+
+ memset(block.data(), 0, block.size());
+
+ struct rkisp1_ext_params_block_header *header =
+ reinterpret_cast<struct rkisp1_ext_params_block_header *>(block.data());
+ header->type = info.type;
+ header->size = block.size();
+
+ /* Update the cache. */
+ blocks_[type] = block;
+
+ return block;
+}
+
+} /* namespace ipa::rkisp1 */
+
+} /* namespace libcamera */
diff --git a/src/ipa/rkisp1/params.h b/src/ipa/rkisp1/params.h
new file mode 100644
index 00000000..40450e34
--- /dev/null
+++ b/src/ipa/rkisp1/params.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * RkISP1 ISP Parameters
+ */
+
+#pragma once
+
+#include <map>
+#include <stdint.h>
+
+#include <linux/rkisp1-config.h>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/span.h>
+
+namespace libcamera {
+
+namespace ipa::rkisp1 {
+
+enum class BlockType {
+ Bls,
+ Dpcc,
+ Sdg,
+ AwbGain,
+ Flt,
+ Bdm,
+ Ctk,
+ Goc,
+ Dpf,
+ DpfStrength,
+ Cproc,
+ Ie,
+ Lsc,
+ Awb,
+ Hst,
+ Aec,
+ Afc,
+ CompandBls,
+ CompandExpand,
+ CompandCompress,
+};
+
+namespace details {
+
+template<BlockType B>
+struct block_type {
+};
+
+#define RKISP1_DEFINE_BLOCK_TYPE(blockType, blockStruct) \
+template<> \
+struct block_type<BlockType::blockType> { \
+ using type = struct rkisp1_cif_isp_##blockStruct##_config; \
+};
+
+RKISP1_DEFINE_BLOCK_TYPE(Bls, bls)
+RKISP1_DEFINE_BLOCK_TYPE(Dpcc, dpcc)
+RKISP1_DEFINE_BLOCK_TYPE(Sdg, sdg)
+RKISP1_DEFINE_BLOCK_TYPE(AwbGain, awb_gain)
+RKISP1_DEFINE_BLOCK_TYPE(Flt, flt)
+RKISP1_DEFINE_BLOCK_TYPE(Bdm, bdm)
+RKISP1_DEFINE_BLOCK_TYPE(Ctk, ctk)
+RKISP1_DEFINE_BLOCK_TYPE(Goc, goc)
+RKISP1_DEFINE_BLOCK_TYPE(Dpf, dpf)
+RKISP1_DEFINE_BLOCK_TYPE(DpfStrength, dpf_strength)
+RKISP1_DEFINE_BLOCK_TYPE(Cproc, cproc)
+RKISP1_DEFINE_BLOCK_TYPE(Ie, ie)
+RKISP1_DEFINE_BLOCK_TYPE(Lsc, lsc)
+RKISP1_DEFINE_BLOCK_TYPE(Awb, awb_meas)
+RKISP1_DEFINE_BLOCK_TYPE(Hst, hst)
+RKISP1_DEFINE_BLOCK_TYPE(Aec, aec)
+RKISP1_DEFINE_BLOCK_TYPE(Afc, afc)
+RKISP1_DEFINE_BLOCK_TYPE(CompandBls, compand_bls)
+RKISP1_DEFINE_BLOCK_TYPE(CompandExpand, compand_curve)
+RKISP1_DEFINE_BLOCK_TYPE(CompandCompress, compand_curve)
+
+} /* namespace details */
+
+class RkISP1Params;
+
+class RkISP1ParamsBlockBase
+{
+public:
+ RkISP1ParamsBlockBase(RkISP1Params *params, BlockType type,
+ const Span<uint8_t> &data);
+
+ Span<uint8_t> data() const { return data_; }
+
+ void setEnabled(bool enabled);
+
+private:
+ LIBCAMERA_DISABLE_COPY(RkISP1ParamsBlockBase)
+
+ RkISP1Params *params_;
+ BlockType type_;
+ Span<uint8_t> header_;
+ Span<uint8_t> data_;
+};
+
+template<BlockType B>
+class RkISP1ParamsBlock : public RkISP1ParamsBlockBase
+{
+public:
+ using Type = typename details::block_type<B>::type;
+
+ RkISP1ParamsBlock(RkISP1Params *params, const Span<uint8_t> &data)
+ : RkISP1ParamsBlockBase(params, B, data)
+ {
+ }
+
+ const Type *operator->() const
+ {
+ return reinterpret_cast<const Type *>(data().data());
+ }
+
+ Type *operator->()
+ {
+ return reinterpret_cast<Type *>(data().data());
+ }
+
+ const Type &operator*() const &
+ {
+ return *reinterpret_cast<const Type *>(data().data());
+ }
+
+ Type &operator*() &
+ {
+ return *reinterpret_cast<Type *>(data().data());
+ }
+};
+
+class RkISP1Params
+{
+public:
+ RkISP1Params(uint32_t format, Span<uint8_t> data);
+
+ template<BlockType B>
+ RkISP1ParamsBlock<B> block()
+ {
+ return RkISP1ParamsBlock<B>(this, block(B));
+ }
+
+ uint32_t format() const { return format_; }
+ size_t size() const { return used_; }
+
+private:
+ friend class RkISP1ParamsBlockBase;
+
+ Span<uint8_t> block(BlockType type);
+ void setBlockEnabled(BlockType type, bool enabled);
+
+ uint32_t format_;
+
+ Span<uint8_t> data_;
+ size_t used_;
+
+ std::map<BlockType, Span<uint8_t>> blocks_;
+};
+
+} /* namespace ipa::rkisp1 */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/rkisp1/rkisp1.cpp b/src/ipa/rkisp1/rkisp1.cpp
index d31cdbab..9e161cab 100644
--- a/src/ipa/rkisp1/rkisp1.cpp
+++ b/src/ipa/rkisp1/rkisp1.cpp
@@ -6,8 +6,8 @@
*/
#include <algorithm>
-#include <math.h>
-#include <queue>
+#include <array>
+#include <chrono>
#include <stdint.h>
#include <string.h>
@@ -18,20 +18,22 @@
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
+#include <libcamera/request.h>
+
#include <libcamera/ipa/ipa_interface.h>
#include <libcamera/ipa/ipa_module_info.h>
#include <libcamera/ipa/rkisp1_ipa_interface.h>
-#include <libcamera/request.h>
#include "libcamera/internal/formats.h"
#include "libcamera/internal/mapped_framebuffer.h"
#include "libcamera/internal/yaml_parser.h"
#include "algorithms/algorithm.h"
-#include "libipa/camera_sensor_helper.h"
#include "ipa_context.h"
+#include "params.h"
namespace libcamera {
@@ -81,9 +83,6 @@ private:
ControlInfoMap sensorControls_;
- /* Interface to the Camera Helper */
- std::unique_ptr<CameraSensorHelper> camHelper_;
-
/* Local parameter storage */
struct IPAContext context_;
};
@@ -95,6 +94,15 @@ const IPAHwSettings ipaHwSettingsV10{
RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10,
RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10,
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10,
+ false,
+};
+
+const IPAHwSettings ipaHwSettingsIMX8MP{
+ RKISP1_CIF_ISP_AE_MEAN_MAX_V10,
+ RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10,
+ RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V10,
+ RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10,
+ true,
};
const IPAHwSettings ipaHwSettingsV12{
@@ -102,6 +110,7 @@ const IPAHwSettings ipaHwSettingsV12{
RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12,
RKISP1_CIF_ISP_HISTOGRAM_WEIGHT_GRIDS_SIZE_V12,
RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12,
+ false,
};
/* List of controls handled by the RkISP1 IPA */
@@ -115,7 +124,7 @@ const ControlInfoMap::Map rkisp1Controls{
} /* namespace */
IPARkISP1::IPARkISP1()
- : context_({ {}, {}, {}, { kMaxFrameContexts }, {} })
+ : context_({ {}, {}, {}, {}, { kMaxFrameContexts }, {}, {} })
{
}
@@ -132,9 +141,11 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
/* \todo Add support for other revisions */
switch (hwRevision) {
case RKISP1_V10:
- case RKISP1_V_IMX8MP:
context_.hw = &ipaHwSettingsV10;
break;
+ case RKISP1_V_IMX8MP:
+ context_.hw = &ipaHwSettingsIMX8MP;
+ break;
case RKISP1_V12:
context_.hw = &ipaHwSettingsV12;
break;
@@ -147,16 +158,18 @@ int IPARkISP1::init(const IPASettings &settings, unsigned int hwRevision,
LOG(IPARkISP1, Debug) << "Hardware revision is " << hwRevision;
- camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
- if (!camHelper_) {
+ context_.sensorInfo = sensorInfo;
+
+ context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+ if (!context_.camHelper) {
LOG(IPARkISP1, Error)
<< "Failed to create camera sensor helper for "
<< settings.sensorModel;
return -ENODEV;
}
- context_.configuration.sensor.lineDuration = sensorInfo.minLineLength
- * 1.0s / sensorInfo.pixelRate;
+ context_.configuration.sensor.lineDuration =
+ sensorInfo.minLineLength * 1.0s / sensorInfo.pixelRate;
/* Load the tuning data file. */
File file(settings.configurationFile);
@@ -230,6 +243,8 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,
context_.activeState = {};
context_.frameContexts.clear();
+ context_.configuration.paramFormat = ipaConfig.paramFormat;
+
const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
const ControlInfo vBlank = sensorControls_.find(V4L2_CID_VBLANK)->second;
context_.configuration.sensor.defVBlank = vBlank.def().get<int32_t>();
@@ -250,8 +265,10 @@ int IPARkISP1::configure(const IPAConfigInfo &ipaConfig,
minExposure * context_.configuration.sensor.lineDuration;
context_.configuration.sensor.maxShutterSpeed =
maxExposure * context_.configuration.sensor.lineDuration;
- context_.configuration.sensor.minAnalogueGain = camHelper_->gain(minGain);
- context_.configuration.sensor.maxAnalogueGain = camHelper_->gain(maxGain);
+ context_.configuration.sensor.minAnalogueGain =
+ context_.camHelper->gain(minGain);
+ context_.configuration.sensor.maxAnalogueGain =
+ context_.camHelper->gain(maxGain);
context_.configuration.raw = std::any_of(streamConfig.begin(), streamConfig.end(),
[](auto &cfg) -> bool {
@@ -322,17 +339,13 @@ void IPARkISP1::fillParamsBuffer(const uint32_t frame, const uint32_t bufferId)
{
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
- rkisp1_params_cfg *params =
- reinterpret_cast<rkisp1_params_cfg *>(
- mappedBuffers_.at(bufferId).planes()[0].data());
-
- /* Prepare parameters buffer. */
- memset(params, 0, sizeof(*params));
+ RkISP1Params params(context_.configuration.paramFormat,
+ mappedBuffers_.at(bufferId).planes()[0]);
for (auto const &algo : algorithms())
- algo->prepare(context_, frame, frameContext, params);
+ algo->prepare(context_, frame, frameContext, &params);
- paramsBufferReady.emit(frame);
+ paramsBufferReady.emit(frame, params.size());
}
void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId,
@@ -352,7 +365,7 @@ void IPARkISP1::processStatsBuffer(const uint32_t frame, const uint32_t bufferId
frameContext.sensor.exposure =
sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
frameContext.sensor.gain =
- camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
+ context_.camHelper->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
ControlList metadata(controls::controls);
@@ -389,9 +402,9 @@ void IPARkISP1::updateControls(const IPACameraSensorInfo &sensorInfo,
/* Compute the analogue gain limits. */
const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
- float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
- float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
- float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
+ float minGain = context_.camHelper->gain(v4l2Gain.min().get<int32_t>());
+ float maxGain = context_.camHelper->gain(v4l2Gain.max().get<int32_t>());
+ float defGain = context_.camHelper->gain(v4l2Gain.def().get<int32_t>());
ctrlMap.emplace(std::piecewise_construct,
std::forward_as_tuple(&controls::AnalogueGain),
std::forward_as_tuple(minGain, maxGain, defGain));
@@ -436,7 +449,7 @@ void IPARkISP1::setControls(unsigned int frame)
IPAFrameContext &frameContext = context_.frameContexts.get(frame);
uint32_t exposure = frameContext.agc.exposure;
- uint32_t gain = camHelper_->gainCode(frameContext.agc.gain);
+ uint32_t gain = context_.camHelper->gainCode(frameContext.agc.gain);
ControlList ctrls(sensorControls_);
ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
diff --git a/src/ipa/rkisp1/utils.h b/src/ipa/rkisp1/utils.h
index 450f2244..5f38b50b 100644
--- a/src/ipa/rkisp1/utils.h
+++ b/src/ipa/rkisp1/utils.h
@@ -8,7 +8,6 @@
#pragma once
#include <cmath>
-#include <limits>
#include <type_traits>
namespace libcamera {
diff --git a/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp
new file mode 100644
index 00000000..1fd4d7f3
--- /dev/null
+++ b/src/ipa/rpi/cam_helper/cam_helper_imx283.cpp
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (C) 2024, Raspberry Pi Ltd
+ *
+ * cam_helper_Imx283.cpp - camera information for Imx283 sensor
+ */
+
+#include <assert.h>
+
+#include "cam_helper.h"
+#include "math.h"
+using namespace RPiController;
+
+class CamHelperImx283 : public CamHelper
+{
+public:
+ CamHelperImx283();
+ uint32_t gainCode(double gain) const override;
+ double gain(uint32_t gainCode) const override;
+ void getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const override;
+ unsigned int hideFramesModeSwitch() const override;
+
+private:
+ /*
+ * Smallest difference between the frame length and integration time,
+ * in units of lines.
+ */
+ static constexpr int frameIntegrationDiff = 4;
+};
+
+/*
+ * Imx283 doesn't output metadata, so we have to use the delayed controls which
+ * works by counting frames.
+ */
+
+CamHelperImx283::CamHelperImx283()
+ : CamHelper({}, frameIntegrationDiff)
+{
+}
+
+uint32_t CamHelperImx283::gainCode(double gain) const
+{
+ return static_cast<uint32_t>(2048.0 - 2048.0 / gain);
+}
+
+double CamHelperImx283::gain(uint32_t gainCode) const
+{
+ return static_cast<double>(2048.0 / (2048 - gainCode));
+}
+
+void CamHelperImx283::getDelays(int &exposureDelay, int &gainDelay,
+ int &vblankDelay, int &hblankDelay) const
+{
+ /* The driver appears to behave as follows: */
+ exposureDelay = 2;
+ gainDelay = 2;
+ vblankDelay = 2;
+ hblankDelay = 2;
+}
+
+unsigned int CamHelperImx283::hideFramesModeSwitch() const
+{
+ /* After a mode switch, we seem to get 1 bad frame. */
+ return 1;
+}
+
+static CamHelper *create()
+{
+ return new CamHelperImx283();
+}
+
+static RegisterCamHelper reg("imx283", &create);
diff --git a/src/ipa/rpi/cam_helper/meson.build b/src/ipa/rpi/cam_helper/meson.build
index 72625057..1e43f1da 100644
--- a/src/ipa/rpi/cam_helper/meson.build
+++ b/src/ipa/rpi/cam_helper/meson.build
@@ -4,6 +4,7 @@ rpi_ipa_cam_helper_sources = files([
'cam_helper.cpp',
'cam_helper_ov5647.cpp',
'cam_helper_imx219.cpp',
+ 'cam_helper_imx283.cpp',
'cam_helper_imx290.cpp',
'cam_helper_imx296.cpp',
'cam_helper_imx477.cpp',
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index cf2565a8..c9df9b5b 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -883,11 +883,14 @@ void AgcChannel::filterExposure()
/*
* AGC adapts instantly if both shutter and gain are directly specified
- * or we're in the startup phase.
+ * or we're in the startup phase. Also disable the stable region, because we want
+ * to reflect any user exposure/gain updates, however small.
*/
if ((status_.fixedShutter && status_.fixedAnalogueGain) ||
- frameCount_ <= config_.startupFrames)
+ frameCount_ <= config_.startupFrames) {
speed = 1.0;
+ stableRegion = 0.0;
+ }
if (!filtered_.totalExposure) {
filtered_.totalExposure = target_.totalExposure;
} else if (filtered_.totalExposure * (1.0 - stableRegion) < target_.totalExposure &&
diff --git a/src/ipa/rpi/controller/rpi/alsc.cpp b/src/ipa/rpi/controller/rpi/alsc.cpp
index 67029fc3..161fd455 100644
--- a/src/ipa/rpi/controller/rpi/alsc.cpp
+++ b/src/ipa/rpi/controller/rpi/alsc.cpp
@@ -9,6 +9,7 @@
#include <functional>
#include <math.h>
#include <numeric>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/base/span.h>
@@ -496,8 +497,9 @@ void resampleCalTable(const Array2D<double> &calTableIn,
* Precalculate and cache the x sampling locations and phases to save
* recomputing them on every row.
*/
- int xLo[X], xHi[X];
- double xf[X];
+ std::vector<int> xLo(X);
+ std::vector<int> xHi(X);
+ std::vector<double> xf(X);
double scaleX = cameraMode.sensorWidth /
(cameraMode.width * cameraMode.scaleX);
double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth;
diff --git a/src/ipa/rpi/controller/rpi/awb.cpp b/src/ipa/rpi/controller/rpi/awb.cpp
index 003c8fa1..f45525bc 100644
--- a/src/ipa/rpi/controller/rpi/awb.cpp
+++ b/src/ipa/rpi/controller/rpi/awb.cpp
@@ -122,7 +122,7 @@ int AwbConfig::read(const libcamera::YamlObject &params)
}
if (priors.empty()) {
LOG(RPiAwb, Error) << "AwbConfig: no AWB priors configured";
- return ret;
+ return -EINVAL;
}
}
if (params.contains("modes")) {
diff --git a/src/ipa/rpi/controller/rpi/ccm.cpp b/src/ipa/rpi/controller/rpi/ccm.cpp
index e673964c..aefa580c 100644
--- a/src/ipa/rpi/controller/rpi/ccm.cpp
+++ b/src/ipa/rpi/controller/rpi/ccm.cpp
@@ -113,8 +113,10 @@ void Ccm::initialise()
{
}
+namespace {
+
template<typename T>
-static bool getLocked(Metadata *metadata, std::string const &tag, T &value)
+bool getLocked(Metadata *metadata, std::string const &tag, T &value)
{
T *ptr = metadata->getLocked<T>(tag);
if (ptr == nullptr)
@@ -149,6 +151,8 @@ Matrix applySaturation(Matrix const &ccm, double saturation)
return Y2RGB * S * RGB2Y * ccm;
}
+} /* namespace */
+
void Ccm::prepare(Metadata *imageMetadata)
{
bool awbOk = false, luxOk = false;
diff --git a/src/ipa/rpi/controller/rpi/contrast.cpp b/src/ipa/rpi/controller/rpi/contrast.cpp
index 9b37943a..fe866a54 100644
--- a/src/ipa/rpi/controller/rpi/contrast.cpp
+++ b/src/ipa/rpi/controller/rpi/contrast.cpp
@@ -94,6 +94,8 @@ void Contrast::prepare(Metadata *imageMetadata)
imageMetadata->set("contrast.status", status_);
}
+namespace {
+
ipa::Pwl computeStretchCurve(Histogram const &histogram,
ContrastConfig const &config)
{
@@ -153,6 +155,8 @@ ipa::Pwl applyManualContrast(ipa::Pwl const &gammaCurve, double brightness,
return newGammaCurve;
}
+} /* namespace */
+
void Contrast::process(StatisticsPtr &stats,
[[maybe_unused]] Metadata *imageMetadata)
{
diff --git a/src/ipa/rpi/vc4/data/imx283.json b/src/ipa/rpi/vc4/data/imx283.json
new file mode 100644
index 00000000..bfacecc8
--- /dev/null
+++ b/src/ipa/rpi/vc4/data/imx283.json
@@ -0,0 +1,313 @@
+{
+ "version": 2.0,
+ "target": "bcm2835",
+ "algorithms": [
+ {
+ "rpi.black_level":
+ {
+ "black_level": 3200
+ }
+ },
+ {
+ "rpi.dpc": { }
+ },
+ {
+ "rpi.lux":
+ {
+ "reference_shutter_speed": 2461,
+ "reference_gain": 1.0,
+ "reference_aperture": 1.0,
+ "reference_lux": 1148,
+ "reference_Y": 13314
+ }
+ },
+ {
+ "rpi.noise":
+ {
+ "reference_constant": 0,
+ "reference_slope": 2.204
+ }
+ },
+ {
+ "rpi.geq":
+ {
+ "offset": 199,
+ "slope": 0.01947
+ }
+ },
+ {
+ "rpi.sdn": { }
+ },
+ {
+ "rpi.awb":
+ {
+ "priors": [
+ {
+ "lux": 0,
+ "prior":
+ [
+ 2000, 1.0,
+ 3000, 0.0,
+ 13000, 0.0
+ ]
+ },
+ {
+ "lux": 800,
+ "prior":
+ [
+ 2000, 0.0,
+ 6000, 2.0,
+ 13000, 2.0
+ ]
+ },
+ {
+ "lux": 1500,
+ "prior":
+ [
+ 2000, 0.0,
+ 4000, 1.0,
+ 6000, 6.0,
+ 6500, 7.0,
+ 7000, 1.0,
+ 13000, 1.0
+ ]
+ }
+ ],
+ "modes":
+ {
+ "auto":
+ {
+ "lo": 2500,
+ "hi": 8000
+ },
+ "incandescent":
+ {
+ "lo": 2500,
+ "hi": 3000
+ },
+ "tungsten":
+ {
+ "lo": 3000,
+ "hi": 3500
+ },
+ "fluorescent":
+ {
+ "lo": 4000,
+ "hi": 4700
+ },
+ "indoor":
+ {
+ "lo": 3000,
+ "hi": 5000
+ },
+ "daylight":
+ {
+ "lo": 5500,
+ "hi": 6500
+ }
+ },
+ "bayes": 1,
+ "ct_curve":
+ [
+ 2213.0, 0.9607, 0.2593,
+ 5313.0, 0.4822, 0.5909,
+ 6237.0, 0.4739, 0.6308
+ ],
+ "sensitivity_r": 1.0,
+ "sensitivity_b": 1.0,
+ "transverse_pos": 0.0144,
+ "transverse_neg": 0.01
+ }
+ },
+ {
+ "rpi.agc":
+ {
+ "metering_modes":
+ {
+ "centre-weighted":
+ {
+ "weights":
+ [
+ 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0
+ ]
+ },
+ "spot":
+ {
+ "weights":
+ [
+ 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ ]
+ },
+ "matrix":
+ {
+ "weights":
+ [
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ ]
+ }
+ },
+ "exposure_modes":
+ {
+ "normal":
+ {
+ "shutter": [ 100, 10000, 30000, 60000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ },
+ "short":
+ {
+ "shutter": [ 100, 5000, 10000, 20000, 120000 ],
+ "gain": [ 1.0, 2.0, 4.0, 6.0, 6.0 ]
+ }
+ },
+ "constraint_modes":
+ {
+ "normal": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ }
+ ],
+ "highlight": [
+ {
+ "bound": "LOWER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.5,
+ 1000, 0.5
+ ]
+ },
+ {
+ "bound": "UPPER",
+ "q_lo": 0.98,
+ "q_hi": 1.0,
+ "y_target":
+ [
+ 0, 0.8,
+ 1000, 0.8
+ ]
+ }
+ ]
+ },
+ "y_target":
+ [
+ 0, 0.16,
+ 1000, 0.165,
+ 10000, 0.17
+ ]
+ }
+ },
+ {
+ "rpi.alsc":
+ {
+ "omega": 1.3,
+ "n_iter": 100,
+ "luminance_strength": 0.7
+ }
+ },
+ {
+ "rpi.contrast":
+ {
+ "ce_enable": 1,
+ "gamma_curve":
+ [
+ 0, 0,
+ 1024, 5040,
+ 2048, 9338,
+ 3072, 12356,
+ 4096, 15312,
+ 5120, 18051,
+ 6144, 20790,
+ 7168, 23193,
+ 8192, 25744,
+ 9216, 27942,
+ 10240, 30035,
+ 11264, 32005,
+ 12288, 33975,
+ 13312, 35815,
+ 14336, 37600,
+ 15360, 39168,
+ 16384, 40642,
+ 18432, 43379,
+ 20480, 45749,
+ 22528, 47753,
+ 24576, 49621,
+ 26624, 51253,
+ 28672, 52698,
+ 30720, 53796,
+ 32768, 54876,
+ 36864, 57012,
+ 40960, 58656,
+ 45056, 59954,
+ 49152, 61183,
+ 53248, 62355,
+ 57344, 63419,
+ 61440, 64476,
+ 65535, 65535
+ ]
+ }
+ },
+ {
+ "rpi.ccm":
+ {
+ "ccms": [
+ {
+ "ct": 2213,
+ "ccm":
+ [
+ 1.91264, -0.27609, -0.63655,
+ -0.65708, 2.11718, -0.46009,
+ 0.03629, -1.38441, 2.34811
+ ]
+ },
+ {
+ "ct": 2255,
+ "ccm":
+ [
+ 1.90369, -0.29309, -0.61059,
+ -0.64693, 2.08169, -0.43476,
+ 0.04086, -1.29999, 2.25914
+ ]
+ },
+ {
+ "ct": 2259,
+ "ccm":
+ [
+ 1.92762, -0.35134, -0.57628,
+ -0.63523, 2.08481, -0.44958,
+ 0.06754, -1.32953, 2.26199
+ ]
+ },
+ {
+ "ct": 5313,
+ "ccm":
+ [
+ 1.75924, -0.54053, -0.21871,
+ -0.38159, 1.88671, -0.50511,
+ -0.00747, -0.53492, 1.54239
+ ]
+ },
+ {
+ "ct": 6237,
+ "ccm":
+ [
+ 2.19299, -0.74764, -0.44536,
+ -0.51678, 2.27651, -0.75972,
+ -0.06498, -0.74269, 1.80767
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "rpi.sharpen": { }
+ }
+ ]
+}
diff --git a/src/ipa/rpi/vc4/data/meson.build b/src/ipa/rpi/vc4/data/meson.build
index afbf875a..60477c11 100644
--- a/src/ipa/rpi/vc4/data/meson.build
+++ b/src/ipa/rpi/vc4/data/meson.build
@@ -3,6 +3,7 @@
conf_files = files([
'imx219.json',
'imx219_noir.json',
+ 'imx283.json',
'imx290.json',
'imx296.json',
'imx296_mono.json',
diff --git a/src/ipa/rpi/vc4/meson.build b/src/ipa/rpi/vc4/meson.build
index 63fc5925..c10fa17e 100644
--- a/src/ipa/rpi/vc4/meson.build
+++ b/src/ipa/rpi/vc4/meson.build
@@ -23,8 +23,7 @@ vc4_ipa_sources = files([
vc4_ipa_includes += include_directories('..')
-mod = shared_module(ipa_name,
- [vc4_ipa_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, vc4_ipa_sources,
name_prefix : '',
include_directories : vc4_ipa_includes,
dependencies : [vc4_ipa_deps, libipa_dep],
diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
index 33d1c96a..b297e1d2 100644
--- a/src/ipa/simple/meson.build
+++ b/src/ipa/simple/meson.build
@@ -7,8 +7,7 @@ soft_simple_sources = files([
'black_level.cpp',
])
-mod = shared_module(ipa_name,
- [soft_simple_sources, libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, soft_simple_sources,
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/vimc/meson.build b/src/ipa/vimc/meson.build
index d0b63edd..2cc5f80b 100644
--- a/src/ipa/vimc/meson.build
+++ b/src/ipa/vimc/meson.build
@@ -2,8 +2,7 @@
ipa_name = 'ipa_vimc'
-mod = shared_module(ipa_name,
- ['vimc.cpp', libcamera_generated_ipa_headers],
+mod = shared_module(ipa_name, 'vimc.cpp',
name_prefix : '',
include_directories : [ipa_includes],
dependencies : [libcamera_private, libipa_dep],
diff --git a/src/ipa/vimc/vimc.cpp b/src/ipa/vimc/vimc.cpp
index ebd63fa6..5495401f 100644
--- a/src/ipa/vimc/vimc.cpp
+++ b/src/ipa/vimc/vimc.cpp
@@ -14,6 +14,7 @@
#include <iostream>
#include <libcamera/base/file.h>
+#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/ipa/ipa_interface.h>
diff --git a/src/libcamera/base/event_dispatcher_poll.cpp b/src/libcamera/base/event_dispatcher_poll.cpp
index b737ca7a..52bfb34e 100644
--- a/src/libcamera/base/event_dispatcher_poll.cpp
+++ b/src/libcamera/base/event_dispatcher_poll.cpp
@@ -7,14 +7,13 @@
#include <libcamera/base/event_dispatcher_poll.h>
-#include <algorithm>
-#include <chrono>
#include <iomanip>
#include <poll.h>
#include <stdint.h>
#include <string.h>
#include <sys/eventfd.h>
#include <unistd.h>
+#include <vector>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/log.h>
diff --git a/src/libcamera/base/memfd.cpp b/src/libcamera/base/memfd.cpp
new file mode 100644
index 00000000..ed0b299b
--- /dev/null
+++ b/src/libcamera/base/memfd.cpp
@@ -0,0 +1,123 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Anonymous file creation
+ */
+
+#include <libcamera/base/memfd.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <libcamera/base/log.h>
+
+/**
+ * \file base/memfd.h
+ * \brief Anonymous file creation
+ */
+
+#ifndef __DOXYGEN__
+namespace {
+
+/* uClibc doesn't provide the file sealing API. */
+#if not HAVE_FILE_SEALS
+#define F_ADD_SEALS 1033
+#define F_SEAL_SHRINK 0x0002
+#define F_SEAL_GROW 0x0004
+#endif
+
+#if not HAVE_MEMFD_CREATE
+int memfd_create(const char *name, unsigned int flags)
+{
+ return syscall(SYS_memfd_create, name, flags);
+}
+#endif
+
+} /* namespace */
+#endif /* __DOXYGEN__ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(File)
+
+/**
+ * \class MemFd
+ * \brief Helper class to create anonymous files
+ *
+ * Anonymous files behave like regular files, and can be modified, truncated,
+ * memory-mapped and so on. Unlike regular files, they however live in RAM and
+ * don't have permanent backing storage.
+ */
+
+/**
+ * \enum MemFd::Seal
+ * \brief Seals for the MemFd::create() function
+ * \var MemFd::Seal::None
+ * \brief No seals (used as default value)
+ * \var MemFd::Seal::Shrink
+ * \brief Prevent the memfd from shrinking
+ * \var MemFd::Seal::Grow
+ * \brief Prevent the memfd from growing
+ */
+
+/**
+ * \typedef MemFd::Seals
+ * \brief A bitwise combination of MemFd::Seal values
+ */
+
+/**
+ * \brief Create an anonymous file
+ * \param[in] name The file name (displayed in symbolic links in /proc/self/fd/)
+ * \param[in] size The file size
+ * \param[in] seals The file seals
+ *
+ * This function is a helper that wraps anonymous file (memfd) creation and
+ * sets the file size and optional seals.
+ *
+ * \return The descriptor of the anonymous file if creation succeeded, or an
+ * invalid UniqueFD otherwise
+ */
+UniqueFD MemFd::create(const char *name, std::size_t size, Seals seals)
+{
+ int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to allocate memfd storage for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ UniqueFD memfd(ret);
+
+ ret = ftruncate(memfd.get(), size);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to set memfd size for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+
+ if (seals) {
+ int fileSeals = (seals & Seal::Shrink ? F_SEAL_SHRINK : 0)
+ | (seals & Seal::Grow ? F_SEAL_GROW : 0);
+
+ ret = fcntl(memfd.get(), F_ADD_SEALS, fileSeals);
+ if (ret < 0) {
+ ret = errno;
+ LOG(File, Error)
+ << "Failed to seal the memfd for " << name
+ << ": " << strerror(ret);
+ return {};
+ }
+ }
+
+ return memfd;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/base/meson.build b/src/libcamera/base/meson.build
index 7a7fd7e4..a742dfdf 100644
--- a/src/libcamera/base/meson.build
+++ b/src/libcamera/base/meson.build
@@ -1,24 +1,28 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_base_sources = files([
- 'backtrace.cpp',
- 'class.cpp',
+libcamera_base_public_sources = files([
'bound_method.cpp',
+ 'class.cpp',
+ 'flags.cpp',
+ 'object.cpp',
+ 'shared_fd.cpp',
+ 'signal.cpp',
+ 'unique_fd.cpp',
+])
+
+libcamera_base_internal_sources = files([
+ 'backtrace.cpp',
'event_dispatcher.cpp',
'event_dispatcher_poll.cpp',
'event_notifier.cpp',
'file.cpp',
- 'flags.cpp',
'log.cpp',
+ 'memfd.cpp',
'message.cpp',
'mutex.cpp',
- 'object.cpp',
'semaphore.cpp',
- 'shared_fd.cpp',
- 'signal.cpp',
'thread.cpp',
'timer.cpp',
- 'unique_fd.cpp',
'utils.cpp',
])
@@ -49,7 +53,11 @@ libcamera_base_deps = [
libcamera_base_args = [ '-DLIBCAMERA_BASE_PRIVATE' ]
libcamera_base_lib = shared_library('libcamera-base',
- [libcamera_base_sources, libcamera_base_headers],
+ [
+ libcamera_base_public_sources,
+ libcamera_base_internal_sources,
+ libcamera_base_headers,
+ ],
version : libcamera_version,
soversion : libcamera_soversion,
name_prefix : '',
diff --git a/src/libcamera/base/thread.cpp b/src/libcamera/base/thread.cpp
index 72733431..8735670b 100644
--- a/src/libcamera/base/thread.cpp
+++ b/src/libcamera/base/thread.cpp
@@ -64,42 +64,6 @@
* receiver's event loop, running in the receiver's thread. This mechanism can
* be overridden by selecting a different connection type when calling
* Signal::connect().
- *
- * \section thread-reentrancy Reentrancy and Thread-Safety
- *
- * Through the documentation, several terms are used to define how classes and
- * their member functions can be used from multiple threads.
- *
- * - A **reentrant** function may be called simultaneously from multiple
- * threads if and only if each invocation uses a different instance of the
- * class. This is the default for all member functions not explictly marked
- * otherwise.
- *
- * - \anchor thread-safe A **thread-safe** function may be called
- * simultaneously from multiple threads on the same instance of a class. A
- * thread-safe function is thus reentrant. Thread-safe functions may also be
- * called simultaneously with any other reentrant function of the same class
- * on the same instance.
- *
- * - \anchor thread-bound A **thread-bound** function may be called only from
- * the thread that the class instances lives in (see section \ref
- * thread-objects). For instances of classes that do not derive from the
- * Object class, this is the thread in which the instance was created. A
- * thread-bound function is not thread-safe, and may or may not be reentrant.
- *
- * Neither reentrancy nor thread-safety, in this context, mean that a function
- * may be called simultaneously from the same thread, for instance from a
- * callback invoked by the function. This may deadlock and isn't allowed unless
- * separately documented.
- *
- * A class is defined as reentrant, thread-safe or thread-bound if all its
- * member functions are reentrant, thread-safe or thread-bound respectively.
- * Some member functions may additionally be documented as having additional
- * thread-related attributes.
- *
- * Most classes are reentrant but not thread-safe, as making them fully
- * thread-safe would incur locking costs considered prohibitive for the
- * expected use cases.
*/
/**
diff --git a/src/libcamera/base/utils.cpp b/src/libcamera/base/utils.cpp
index ccb31063..67e5a896 100644
--- a/src/libcamera/base/utils.cpp
+++ b/src/libcamera/base/utils.cpp
@@ -531,6 +531,138 @@ double strtod(const char *__restrict nptr, char **__restrict endptr)
* \return The value of e converted to its underlying type
*/
+/**
+ * \class ScopeExitActions
+ * \brief An object that performs actions upon destruction
+ *
+ * The ScopeExitActions class is a simple object that performs user-provided
+ * actions upon destruction. It is meant to simplify cleanup tasks in error
+ * handling paths.
+ *
+ * When the code flow performs multiple sequential actions that each need a
+ * corresponding cleanup action, error handling quickly become tedious:
+ *
+ * \code{.cpp}
+ * {
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * ret = startProducer();
+ * if (ret) {
+ * freeMemory();
+ * return ret;
+ * }
+ *
+ * ret = startConsumer();
+ * if (ret) {
+ * stopProducer();
+ * freeMemory();
+ * return ret;
+ * }
+ *
+ * return 0;
+ * }
+ * \endcode
+ *
+ * This is prone to programming mistakes, as cleanup actions can easily be
+ * forgotten or ordered incorrectly. One strategy to simplify error handling is
+ * to use goto statements:
+ *
+ * \code{.cpp}
+ * {
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * ret = startProducer();
+ * if (ret)
+ * goto error_free;
+ *
+ * ret = startConsumer();
+ * if (ret)
+ * goto error_stop;
+ *
+ * return 0;
+ *
+ * error_stop:
+ * stopProducer();
+ * error_free:
+ * freeMemory();
+ * return ret;
+ * }
+ * \endcode
+ *
+ * While this may be considered better, this solution is still quite
+ * error-prone. Beside the risk of picking the wrong error label, the error
+ * handling logic is separated from the normal code flow, which increases the
+ * risk of error when refactoring the code. Additionally, C++ doesn't allow
+ * goto statements to jump over local variable declarations, which can make
+ * usage of this pattern more difficult.
+ *
+ * The ScopeExitActions class solves these issues by allowing code that
+ * requires cleanup actions to be grouped with its corresponding error handling
+ * code:
+ *
+ * \code{.cpp}
+ * {
+ * ScopeExitActions actions;
+ *
+ * int ret = allocateMemory();
+ * if (ret)
+ * return ret;
+ *
+ * actions += [&]() { freeMemory(); };
+ *
+ * ret = startProducer();
+ * if (ret)
+ * return ret;
+ *
+ * actions += [&]() { stopProducer(); };
+ *
+ * ret = startConsumer();
+ * if (ret)
+ * return ret;
+ *
+ * actions.release();
+ * return 0;
+ * }
+ * \endcode
+ *
+ * Error handlers are executed when the ScopeExitActions instance is destroyed,
+ * in the reverse order of their addition.
+ */
+
+ScopeExitActions::~ScopeExitActions()
+{
+ for (const auto &action : utils::reverse(actions_))
+ action();
+}
+
+/**
+ * \brief Add an exit action
+ * \param[in] action The action
+ *
+ * Add an exit action to the ScopeExitActions. Actions will be called upon
+ * destruction in the reverse order of their addition.
+ */
+void ScopeExitActions::operator+=(std::function<void()> &&action)
+{
+ actions_.push_back(std::move(action));
+}
+
+/**
+ * \brief Remove all exit actions
+ *
+ * This function should be called in scope exit paths that don't need the
+ * actions to be executed, such as success return paths from a function when
+ * the ScopeExitActions is used for error cleanup.
+ */
+void ScopeExitActions::release()
+{
+ actions_.clear();
+}
+
} /* namespace utils */
#ifndef __DOXYGEN__
diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp
index 014f716d..c2120d1c 100644
--- a/src/libcamera/bayer_format.cpp
+++ b/src/libcamera/bayer_format.cpp
@@ -184,6 +184,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
{ formats::R10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y10P) } },
{ { BayerFormat::MONO, 12, BayerFormat::Packing::None },
{ formats::R12, V4L2PixelFormat(V4L2_PIX_FMT_Y12) } },
+ { { BayerFormat::MONO, 12, BayerFormat::Packing::CSI2 },
+ { formats::R12_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_Y12P) } },
{ { BayerFormat::MONO, 16, BayerFormat::Packing::None },
{ formats::R16, V4L2PixelFormat(V4L2_PIX_FMT_Y16) } },
{ { BayerFormat::MONO, 16, BayerFormat::Packing::PISP1 },
diff --git a/src/libcamera/camera.cpp b/src/libcamera/camera.cpp
index 67f34901..a86f552a 100644
--- a/src/libcamera/camera.cpp
+++ b/src/libcamera/camera.cpp
@@ -9,7 +9,11 @@
#include <array>
#include <atomic>
-#include <iomanip>
+#include <ios>
+#include <memory>
+#include <optional>
+#include <set>
+#include <sstream>
#include <libcamera/base/log.h>
#include <libcamera/base/thread.h>
@@ -21,7 +25,6 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_controls.h"
-#include "libcamera/internal/formats.h"
#include "libcamera/internal/pipeline_handler.h"
#include "libcamera/internal/request.h"
@@ -117,6 +120,12 @@
* of view is affected by the pipeline.
*/
+/**
+ * \internal
+ * \file libcamera/internal/camera.h
+ * \brief Internal camera device handling
+ */
+
namespace libcamera {
LOG_DECLARE_CATEGORY(Camera)
@@ -559,6 +568,7 @@ CameraConfiguration::Status CameraConfiguration::validateColorSpaces(ColorSpaceF
* \brief The vector of stream configurations
*/
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class Camera::Private
* \brief Base class for camera private data
@@ -719,6 +729,7 @@ void Camera::Private::setState(State state)
{
state_.store(state, std::memory_order_release);
}
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \class Camera
@@ -813,6 +824,7 @@ void Camera::Private::setState(State state)
*/
/**
+ * \internal
* \brief Create a camera instance
* \param[in] d Camera private data
* \param[in] id The ID of the camera device
@@ -986,7 +998,8 @@ int Camera::acquire()
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
- if (!d->pipe_->acquire()) {
+ if (!d->pipe_->invokeMethod(&PipelineHandler::acquire,
+ ConnectionTypeBlocking, this)) {
LOG(Camera, Info)
<< "Pipeline handler in use by another process";
return -EBUSY;
@@ -1021,7 +1034,8 @@ int Camera::release()
return ret == -EACCES ? -EBUSY : ret;
if (d->isAcquired())
- d->pipe_->release(this);
+ d->pipe_->invokeMethod(&PipelineHandler::release,
+ ConnectionTypeBlocking, this);
d->setState(Private::CameraAvailable);
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index 95a9e326..09f58776 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -15,6 +15,7 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/device_enumerator.h"
+#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/pipeline_handler.h"
/**
@@ -23,6 +24,7 @@
*/
/**
+ * \internal
* \file libcamera/internal/camera_manager.h
* \brief Internal camera manager support
*/
@@ -34,9 +36,11 @@ namespace libcamera {
LOG_DEFINE_CATEGORY(Camera)
+#ifndef __DOXYGEN_PUBLIC__
CameraManager::Private::Private()
: initialized_(false)
{
+ ipaManager_ = std::make_unique<IPAManager>();
}
int CameraManager::Private::start()
@@ -250,6 +254,14 @@ void CameraManager::Private::removeCamera(std::shared_ptr<Camera> camera)
}
/**
+ * \fn CameraManager::Private::ipaManager() const
+ * \brief Retrieve the IPAManager
+ * \context This function is \threadsafe.
+ * \return The IPAManager for this CameraManager
+ */
+#endif /* __DOXYGEN_PUBLIC__ */
+
+/**
* \class CameraManager
* \brief Provide access and manage all cameras in the system
*
diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in
index d283c1c1..3a204931 100644
--- a/src/libcamera/control_ids.cpp.in
+++ b/src/libcamera/control_ids.cpp.in
@@ -2,51 +2,122 @@
/*
* Copyright (C) 2019, Google Inc.
*
- * control_ids.cpp : Control ID list
+ * {{mode}} ID list
*
* This file is auto-generated. Do not edit.
*/
-#include <libcamera/control_ids.h>
+#include <libcamera/{{filename}}.h>
#include <libcamera/controls.h>
/**
- * \file control_ids.h
- * \brief Camera control identifiers
+ * \file {{filename}}.h
+ * \brief Camera {{mode}} identifiers
*/
namespace libcamera {
/**
- * \brief Namespace for libcamera controls
+ * \brief Namespace for libcamera {{mode}}
*/
-namespace controls {
+namespace {{mode}} {
-${controls_doc}
+{%- for vendor, ctrls in controls -%}
-${vendor_controls_doc}
+{%- if vendor != 'libcamera' %}
+/**
+ * \brief Namespace for {{vendor}} {{mode}}
+ */
+namespace {{vendor}} {
+{%- endif -%}
+
+{% for ctrl in ctrls %}
+
+{% if ctrl.is_enum -%}
+/**
+ * \enum {{ctrl.name}}Enum
+ * \brief Supported {{ctrl.name}} values
+{%- for enum in ctrl.enum_values %}
+ *
+ * \var {{enum.name}}
+ * \brief {{enum.description|format_description}}
+{%- endfor %}
+ */
+
+/**
+ * \var {{ctrl.name}}Values
+ * \brief List of all {{ctrl.name}} supported values
+ */
+
+/**
+ * \var {{ctrl.name}}NameValueMap
+ * \brief Map of all {{ctrl.name}} supported value names (in std::string format) to value
+ */
+
+{% endif -%}
+/**
+ * \var {{ctrl.name}}
+ * \brief {{ctrl.description|format_description}}
+ */
+{%- endfor %}
+{% if vendor != 'libcamera' %}
+} /* namespace {{vendor}} */
+{% endif -%}
+
+{%- endfor %}
#ifndef __DOXYGEN__
/*
- * Keep the controls definitions hidden from doxygen as it incorrectly parses
+ * Keep the {{mode}} definitions hidden from doxygen as it incorrectly parses
* them as functions.
*/
-${controls_def}
+{% for vendor, ctrls in controls -%}
+
+{% if vendor != 'libcamera' %}
+namespace {{vendor}} {
+{% endif %}
+
+{%- for ctrl in ctrls %}
+{% if ctrl.is_enum -%}
+extern const std::array<const ControlValue, {{ctrl.enum_values_count}}> {{ctrl.name}}Values = {
+{%- for enum in ctrl.enum_values %}
+ static_cast<{{ctrl.type}}>({{enum.name}}),
+{%- endfor %}
+};
+extern const std::map<std::string, {{ctrl.type}}> {{ctrl.name}}NameValueMap = {
+{%- for enum in ctrl.enum_values %}
+ { "{{enum.name}}", {{enum.name}} },
+{%- endfor %}
+};
+extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}", {{ctrl.name}}NameValueMap);
+{% else -%}
+extern const Control<{{ctrl.type}}> {{ctrl.name}}({{ctrl.name|snake_case|upper}}, "{{ctrl.name}}");
+{% endif -%}
+{%- endfor %}
-${vendor_controls_def}
+{% if vendor != 'libcamera' %}
+} /* namespace {{vendor}} */
+{% endif -%}
-#endif
+{%- endfor %}
+#endif /* __DOXYGEN__ */
/**
- * \brief List of all supported libcamera controls
+ * \brief List of all supported libcamera {{mode}}
+{%- if mode == 'controls' %}
*
* Unless otherwise stated, all controls are bi-directional, i.e. they can be
* set through Request::controls() and returned out through Request::metadata().
+{%- endif %}
*/
-extern const ControlIdMap controls {
-${controls_map}
+extern const ControlIdMap {{mode}} {
+{%- for vendor, ctrls in controls -%}
+{%- for ctrl in ctrls %}
+ { {{ctrl.namespace}}{{ctrl.name|snake_case|upper}}, &{{ctrl.namespace}}{{ctrl.name}} },
+{%- endfor -%}
+{%- endfor %}
};
-} /* namespace controls */
+} /* namespace {{mode}} */
} /* namespace libcamera */
diff --git a/src/libcamera/control_ids_core.yaml b/src/libcamera/control_ids_core.yaml
index 9d413a94..1b1bd950 100644
--- a/src/libcamera/control_ids_core.yaml
+++ b/src/libcamera/control_ids_core.yaml
@@ -32,10 +32,11 @@ controls:
- AeMeteringMode:
type: int32_t
description: |
- Specify a metering mode for the AE algorithm to use. The metering
- modes determine which parts of the image are used to determine the
- scene brightness. Metering modes may be platform specific and not
- all metering modes may be supported.
+ Specify a metering mode for the AE algorithm to use.
+
+ The metering modes determine which parts of the image are used to
+ determine the scene brightness. Metering modes may be platform specific
+ and not all metering modes may be supported.
enum:
- name: MeteringCentreWeighted
value: 0
@@ -56,33 +57,41 @@ controls:
- AeConstraintMode:
type: int32_t
description: |
- Specify a constraint mode for the AE algorithm to use. These determine
- how the measured scene brightness is adjusted to reach the desired
- target exposure. Constraint modes may be platform specific, and not
- all constraint modes may be supported.
+ Specify a constraint mode for the AE algorithm to use.
+
+ The constraint modes determine how the measured scene brightness is
+ adjusted to reach the desired target exposure. Constraint modes may be
+ platform specific, and not all constraint modes may be supported.
enum:
- name: ConstraintNormal
value: 0
- description: Default constraint mode.
+ description: |
+ Default constraint mode.
+
This mode aims to balance the exposure of different parts of the
image so as to reach a reasonable average level. However, highlights
in the image may appear over-exposed and lowlights may appear
under-exposed.
- name: ConstraintHighlight
value: 1
- description: Highlight constraint mode.
+ description: |
+ Highlight constraint mode.
+
This mode adjusts the exposure levels in order to try and avoid
over-exposing the brightest parts (highlights) of an image.
Other non-highlight parts of the image may appear under-exposed.
- name: ConstraintShadows
value: 2
- description: Shadows constraint mode.
+ description: |
+ Shadows constraint mode.
+
This mode adjusts the exposure levels in order to try and avoid
under-exposing the dark parts (shadows) of an image. Other normally
exposed parts of the image may appear over-exposed.
- name: ConstraintCustom
value: 3
- description: Custom constraint mode.
+ description: |
+ Custom constraint mode.
# AeExposureMode needs further attention:
# - Auto-generate max enum value.
@@ -90,10 +99,11 @@ controls:
- AeExposureMode:
type: int32_t
description: |
- Specify an exposure mode for the AE algorithm to use. These specify
- how the desired total exposure is divided between the shutter time
- and the sensor's analogue gain. The exposure modes are platform
- specific, and not all exposure modes may be supported.
+ Specify an exposure mode for the AE algorithm to use.
+
+ The exposure modes specify how the desired total exposure is divided
+ between the shutter time and the sensor's analogue gain. They are
+ platform specific, and not all exposure modes may be supported.
enum:
- name: ExposureNormal
value: 0
@@ -111,11 +121,13 @@ controls:
- ExposureValue:
type: float
description: |
- Specify an Exposure Value (EV) parameter. The EV parameter will only be
- applied if the AE algorithm is currently enabled.
+ Specify an Exposure Value (EV) parameter.
+
+ The EV parameter will only be applied if the AE algorithm is currently
+ enabled.
By convention EV adjusts the exposure as log2. For example
- EV = [-2, -1, 0.5, 0, 0.5, 1, 2] results in an exposure adjustment
+ EV = [-2, -1, -0.5, 0, 0.5, 1, 2] results in an exposure adjustment
of [1/4x, 1/2x, 1/sqrt(2)x, 1x, sqrt(2)x, 2x, 4x].
\sa AeEnable
@@ -124,7 +136,9 @@ controls:
type: int32_t
description: |
Exposure time (shutter speed) for the frame applied in the sensor
- device. This value is specified in micro-seconds.
+ device.
+
+ This value is specified in micro-seconds.
Setting this value means that it is now fixed and the AE algorithm may
not change it. Setting it back to zero returns it to the control of the
@@ -142,6 +156,7 @@ controls:
type: float
description: |
Analogue gain value applied in the sensor device.
+
The value of the control specifies the gain multiplier applied to all
colour channels. This value cannot be lower than 1.0.
@@ -160,9 +175,11 @@ controls:
- 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.
+ Set the flicker avoidance mode for AGC/AEC.
+
+ The flicker mode 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
@@ -176,16 +193,21 @@ controls:
enum:
- name: FlickerOff
value: 0
- description: No flicker avoidance is performed.
+ description: |
+ No flicker avoidance is performed.
- name: FlickerManual
value: 1
- description: Manual flicker avoidance.
+ 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.
+ 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
@@ -194,7 +216,9 @@ controls:
- AeFlickerPeriod:
type: int32_t
- description: Manual flicker period in microseconds.
+ description: |
+ Manual flicker period in microseconds.
+
This value sets the current flicker period to avoid. It is used when
AeFlickerMode is set to FlickerManual.
@@ -212,7 +236,9 @@ controls:
- AeFlickerDetected:
type: int32_t
- description: Flicker period detected in microseconds.
+ 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.
@@ -233,21 +259,25 @@ controls:
- Brightness:
type: float
description: |
- Specify a fixed brightness parameter. Positive values (up to 1.0)
- produce brighter images; negative values (up to -1.0) produce darker
- images and 0.0 leaves pixels unchanged.
+ Specify a fixed brightness parameter.
+
+ Positive values (up to 1.0) produce brighter images; negative values
+ (up to -1.0) produce darker images and 0.0 leaves pixels unchanged.
- Contrast:
type: float
description: |
- Specify a fixed contrast parameter. Normal contrast is given by the
- value 1.0; larger values produce images with more contrast.
+ Specify a fixed contrast parameter.
+
+ Normal contrast is given by the value 1.0; larger values produce images
+ with more contrast.
- Lux:
type: float
description: |
- Report an estimate of the current illuminance level in lux. The Lux
- control can only be returned in metadata.
+ Report an estimate of the current illuminance level in lux.
+
+ The Lux control can only be returned in metadata.
- AwbEnable:
type: bool
@@ -262,8 +292,10 @@ controls:
- AwbMode:
type: int32_t
description: |
- Specify the range of illuminants to use for the AWB algorithm. The modes
- supported are platform specific, and not all modes may be supported.
+ Specify the range of illuminants to use for the AWB algorithm.
+
+ The modes supported are platform specific, and not all modes may be
+ supported.
enum:
- name: AwbAuto
value: 0
@@ -305,37 +337,43 @@ controls:
type: float
description: |
Pair of gain values for the Red and Blue colour channels, in that
- order. ColourGains can only be applied in a Request when the AWB is
- disabled.
+ order.
+
+ ColourGains can only be applied in a Request when the AWB is disabled.
\sa AwbEnable
size: [2]
- ColourTemperature:
type: int32_t
- description: Report the current estimate of the colour temperature, in
- kelvin, for this frame. The ColourTemperature control can only be
- returned in metadata.
+ description: |
+ Report the estimate of the colour temperature for the frame, in kelvin.
+
+ The ColourTemperature control can only be returned in metadata.
- Saturation:
type: float
description: |
- Specify a fixed saturation parameter. Normal saturation is given by
- the value 1.0; larger values produce more saturated colours; 0.0
- produces a greyscale image.
+ Specify a fixed saturation parameter.
+
+ Normal saturation is given by the value 1.0; larger values produce more
+ saturated colours; 0.0 produces a greyscale image.
- SensorBlackLevels:
type: int32_t
description: |
- Reports the sensor black levels used for processing a frame, in the
- order R, Gr, Gb, B. These values are returned as numbers out of a 16-bit
- pixel range (as if pixels ranged from 0 to 65535). The SensorBlackLevels
- control can only be returned in metadata.
+ Reports the sensor black levels used for processing a frame.
+
+ The values are in the order R, Gr, Gb, B. They are returned as numbers
+ out of a 16-bit pixel range (as if pixels ranged from 0 to 65535). The
+ SensorBlackLevels control can only be returned in metadata.
size: [4]
- Sharpness:
type: float
description: |
+ Intensity of the sharpening applied to the image.
+
A value of 0.0 means no sharpening. The minimum value means
minimal sharpening, and shall be 0.0 unless the camera can't
disable sharpening completely. The default value shall give a
@@ -349,6 +387,7 @@ controls:
type: int32_t
description: |
Reports a Figure of Merit (FoM) to indicate how in-focus the frame is.
+
A larger FocusFoM value indicates a more in-focus frame. This singular
value may be based on a combination of statistics gathered from
multiple focus regions within an image. The number of focus regions and
@@ -359,11 +398,13 @@ controls:
- ColourCorrectionMatrix:
type: float
description: |
- The 3x3 matrix that converts camera RGB to sRGB within the
- imaging pipeline. This should describe the matrix that is used
- after pixels have been white-balanced, but before any gamma
- transformation. The 3x3 matrix is stored in conventional reading
- order in an array of 9 floating point values.
+ The 3x3 matrix that converts camera RGB to sRGB within the imaging
+ pipeline.
+
+ This should describe the matrix that is used after pixels have been
+ white-balanced, but before any gamma transformation. The 3x3 matrix is
+ stored in conventional reading order in an array of 9 floating point
+ values.
size: [3,3]
@@ -371,10 +412,12 @@ controls:
type: Rectangle
description: |
Sets the image portion that will be scaled to form the whole of
- the final output image. The (x,y) location of this rectangle is
- relative to the PixelArrayActiveAreas that is being used. The units
- remain native sensor pixels, even if the sensor is being used in
- a binning or skipping mode.
+ the final output image.
+
+ The (x,y) location of this rectangle is relative to the
+ PixelArrayActiveAreas that is being used. The units remain native
+ sensor pixels, even if the sensor is being used in a binning or
+ skipping mode.
This control is only present when the pipeline supports scaling. Its
maximum valid value is given by the properties::ScalerCropMaximum
@@ -401,8 +444,9 @@ controls:
type: int64_t
description: |
The instantaneous frame duration from start of frame exposure to start
- of next exposure, expressed in microseconds. This control is meant to
- be returned in metadata.
+ of next exposure, expressed in microseconds.
+
+ This control is meant to be returned in metadata.
- FrameDurationLimits:
type: int64_t
@@ -444,9 +488,11 @@ controls:
- SensorTemperature:
type: float
description: |
- Temperature measure from the camera sensor in Celsius. This is typically
- obtained by a thermal sensor present on-die or in the camera module. The
- range of reported temperatures is device dependent.
+ Temperature measure from the camera sensor in Celsius.
+
+ This value is typically obtained by a thermal sensor present on-die or
+ in the camera module. The range of reported temperatures is device
+ dependent.
The SensorTemperature control will only be returned in metadata if a
thermal sensor is present.
@@ -468,7 +514,7 @@ controls:
- AfMode:
type: int32_t
description: |
- Control to set the mode of the AF (autofocus) algorithm.
+ The mode of the AF (autofocus) algorithm.
An implementation may choose not to implement all the modes.
@@ -476,12 +522,12 @@ controls:
- name: AfModeManual
value: 0
description: |
- The AF algorithm is in manual mode. In this mode it will never
- perform any action nor move the lens of its own accord, but an
- application can specify the desired lens position using the
- LensPosition control.
+ The AF algorithm is in manual mode.
- In this mode the AfState will always report AfStateIdle.
+ In this mode it will never perform any action nor move the lens of
+ its own accord, but an application can specify the desired lens
+ position using the LensPosition control. The AfState will always
+ report AfStateIdle.
If the camera is started in AfModeManual, it will move the focus
lens to the position specified by the LensPosition control.
@@ -492,31 +538,34 @@ controls:
- name: AfModeAuto
value: 1
description: |
- The AF algorithm is in auto mode. This means that the algorithm
- will never move the lens or change state unless the AfTrigger
- control is used. The AfTrigger control can be used to initiate a
- focus scan, the results of which will be reported by AfState.
-
- If the autofocus algorithm is moved from AfModeAuto to another
- mode while a scan is in progress, the scan is cancelled
- immediately, without waiting for the scan to finish.
-
- When first entering this mode the AfState will report
- AfStateIdle. When a trigger control is sent, AfState will
- report AfStateScanning for a period before spontaneously
- changing to AfStateFocused or AfStateFailed, depending on
- the outcome of the scan. It will remain in this state until
- another scan is initiated by the AfTrigger control. If a scan is
- cancelled (without changing to another mode), AfState will return
- to AfStateIdle.
+ The AF algorithm is in auto mode.
+
+ In this mode the algorithm will never move the lens or change state
+ unless the AfTrigger control is used. The AfTrigger control can be
+ used to initiate a focus scan, the results of which will be
+ reported by AfState.
+
+ If the autofocus algorithm is moved from AfModeAuto to another mode
+ while a scan is in progress, the scan is cancelled immediately,
+ without waiting for the scan to finish.
+
+ When first entering this mode the AfState will report AfStateIdle.
+ When a trigger control is sent, AfState will report AfStateScanning
+ for a period before spontaneously changing to AfStateFocused or
+ AfStateFailed, depending on the outcome of the scan. It will remain
+ in this state until another scan is initiated by the AfTrigger
+ control. If a scan is cancelled (without changing to another mode),
+ AfState will return to AfStateIdle.
- name: AfModeContinuous
value: 2
description: |
- The AF algorithm is in continuous mode. This means that the lens can
- re-start a scan spontaneously at any moment, without any user
- intervention. The AfState still reports whether the algorithm is
- currently scanning or not, though the application has no ability to
- initiate or cancel scans, nor to move the lens for itself.
+ The AF algorithm is in continuous mode.
+
+ In this mode the lens can re-start a scan spontaneously at any
+ moment, without any user intervention. The AfState still reports
+ whether the algorithm is currently scanning or not, though the
+ application has no ability to initiate or cancel scans, nor to move
+ the lens for itself.
However, applications can pause the AF algorithm from continuously
scanning by using the AfPause control. This allows video or still
@@ -529,34 +578,40 @@ controls:
- AfRange:
type: int32_t
description: |
- Control to set the range of focus distances that is scanned. An
- implementation may choose not to implement all the options here.
+ The range of focus distances that is scanned.
+
+ An implementation may choose not to implement all the options here.
enum:
- name: AfRangeNormal
value: 0
description: |
- A wide range of focus distances is scanned, all the way from
- infinity down to close distances, though depending on the
- implementation, possibly not including the very closest macro
- positions.
+ A wide range of focus distances is scanned.
+
+ Scanned distances cover all the way from infinity down to close
+ distances, though depending on the implementation, possibly not
+ including the very closest macro positions.
- name: AfRangeMacro
value: 1
- description: Only close distances are scanned.
+ description: |
+ Only close distances are scanned.
- name: AfRangeFull
value: 2
description: |
- The full range of focus distances is scanned just as with
- AfRangeNormal but this time including the very closest macro
- positions.
+ The full range of focus distances is scanned.
+
+ This range is similar to AfRangeNormal but includes the very
+ closest macro positions.
- AfSpeed:
type: int32_t
description: |
- Control that determines whether the AF algorithm is to move the lens
- as quickly as possible or more steadily. For example, during video
- recording it may be desirable not to move the lens too abruptly, but
- when in a preview mode (waiting for a still capture) it may be
- helpful to move the lens as quickly as is reasonably possible.
+ Determine whether the AF is to move the lens as quickly as possible or
+ more steadily.
+
+ For example, during video recording it may be desirable not to move the
+ lens too abruptly, but when in a preview mode (waiting for a still
+ capture) it may be helpful to move the lens as quickly as is reasonably
+ possible.
enum:
- name: AfSpeedNormal
value: 0
@@ -568,25 +623,27 @@ controls:
- AfMetering:
type: int32_t
description: |
- Instruct the AF algorithm how it should decide which parts of the image
- should be used to measure focus.
+ The parts of the image used by the AF algorithm to measure focus.
enum:
- name: AfMeteringAuto
value: 0
- description: The AF algorithm should decide for itself where it will
- measure focus.
+ description: |
+ Let the AF algorithm decide for itself where it will measure focus.
- name: AfMeteringWindows
value: 1
- description: The AF algorithm should use the rectangles defined by
- the AfWindows control to measure focus. If no windows are specified
- the behaviour is platform dependent.
+ description: |
+ Use the rectangles defined by the AfWindows control to measure focus.
+
+ If no windows are specified the behaviour is platform dependent.
- AfWindows:
type: Rectangle
description: |
- Sets the focus windows used by the AF algorithm when AfMetering is set
- to AfMeteringWindows. The units used are pixels within the rectangle
- returned by the ScalerCropMaximum property.
+ The focus windows used by the AF algorithm when AfMetering is set to
+ AfMeteringWindows.
+
+ The units used are pixels within the rectangle returned by the
+ ScalerCropMaximum property.
In order to be activated, a rectangle must be programmed with non-zero
width and height. Internally, these rectangles are intersected with the
@@ -611,23 +668,33 @@ controls:
- AfTrigger:
type: int32_t
description: |
- This control starts an autofocus scan when AfMode is set to AfModeAuto,
- and can also be used to terminate a scan early.
+ Start an autofocus scan.
- It is ignored if AfMode is set to AfModeManual or AfModeContinuous.
+ This control starts an autofocus scan when AfMode is set to AfModeAuto,
+ and is ignored if AfMode is set to AfModeManual or AfModeContinuous. It
+ can also be used to terminate a scan early.
enum:
- name: AfTriggerStart
value: 0
- description: Start an AF scan. Ignored if a scan is in progress.
+ description: |
+ Start an AF scan.
+
+ Setting the control to AfTriggerStart is ignored if a scan is in
+ progress.
- name: AfTriggerCancel
value: 1
- description: Cancel an AF scan. This does not cause the lens to move
- anywhere else. Ignored if no scan is in progress.
+ description: |
+ Cancel an AF scan.
+
+ This does not cause the lens to move anywhere else. Ignored if no
+ scan is in progress.
- AfPause:
type: int32_t
description: |
+ Pause lens movements when in continuous autofocus mode.
+
This control has no effect except when in continuous autofocus mode
(AfModeContinuous). It can be used to pause any lens movements while
(for example) images are captured. The algorithm remains inactive
@@ -637,17 +704,21 @@ controls:
- name: AfPauseImmediate
value: 0
description: |
- Pause the continuous autofocus algorithm immediately, whether or not
- any kind of scan is underway. AfPauseState will subsequently report
+ Pause the continuous autofocus algorithm immediately.
+
+ The autofocus algorithm is paused whether or not any kind of scan
+ is underway. AfPauseState will subsequently report
AfPauseStatePaused. AfState may report any of AfStateScanning,
AfStateFocused or AfStateFailed, depending on the algorithm's state
when it received this control.
- name: AfPauseDeferred
value: 1
description: |
- This is similar to AfPauseImmediate, and if the AfState is currently
- reporting AfStateFocused or AfStateFailed it will remain in that
- state and AfPauseState will report AfPauseStatePaused.
+ Pause the continuous autofocus algorithm at the end of the scan.
+
+ This is similar to AfPauseImmediate, and if the AfState is
+ currently reporting AfStateFocused or AfStateFailed it will remain
+ in that state and AfPauseState will report AfPauseStatePaused.
However, if the algorithm is scanning (AfStateScanning),
AfPauseState will report AfPauseStatePausing until the scan is
@@ -658,15 +729,18 @@ controls:
- name: AfPauseResume
value: 2
description: |
- Resume continuous autofocus operation. The algorithm starts again
- from exactly where it left off, and AfPauseState will report
- AfPauseStateRunning.
+ Resume continuous autofocus operation.
+
+ The algorithm starts again from exactly where it left off, and
+ AfPauseState will report AfPauseStateRunning.
- LensPosition:
type: float
description: |
- Acts as a control to instruct the lens to move to a particular position
- and also reports back the position of the lens for each frame.
+ Set and report the focus lens position.
+
+ This control instructs the lens to move to a particular position and
+ also reports back the position of the lens for each frame.
The LensPosition control is ignored unless the AfMode is set to
AfModeManual, though the value is reported back unconditionally in all
@@ -680,16 +754,16 @@ controls:
For example:
- 0 moves the lens to infinity.
- 0.5 moves the lens to focus on objects 2m away.
- 2 moves the lens to focus on objects 50cm away.
- And larger values will focus the lens closer.
+ - 0 moves the lens to infinity.
+ - 0.5 moves the lens to focus on objects 2m away.
+ - 2 moves the lens to focus on objects 50cm away.
+ - And larger values will focus the lens closer.
- The default value of the control should indicate a good general position
- for the lens, often corresponding to the hyperfocal distance (the
- closest position for which objects at infinity are still acceptably
- sharp). The minimum will often be zero (meaning infinity), and the
- maximum value defines the closest focus position.
+ The default value of the control should indicate a good general
+ position for the lens, often corresponding to the hyperfocal distance
+ (the closest position for which objects at infinity are still
+ acceptably sharp). The minimum will often be zero (meaning infinity),
+ and the maximum value defines the closest focus position.
\todo Define a property to report the Hyperfocal distance of calibrated
lenses.
@@ -697,22 +771,25 @@ controls:
- AfState:
type: int32_t
description: |
- Reports the current state of the AF algorithm in conjunction with the
- reported AfMode value and (in continuous AF mode) the AfPauseState
- value. The possible state changes are described below, though we note
- the following state transitions that occur when the AfMode is changed.
+ The current state of the AF algorithm.
+
+ This control reports the current state of the AF algorithm in
+ conjunction with the reported AfMode value and (in continuous AF mode)
+ the AfPauseState value. The possible state changes are described below,
+ though we note the following state transitions that occur when the
+ AfMode is changed.
If the AfMode is set to AfModeManual, then the AfState will always
- report AfStateIdle (even if the lens is subsequently moved). Changing to
- the AfModeManual state does not initiate any lens movement.
+ report AfStateIdle (even if the lens is subsequently moved). Changing
+ to the AfModeManual state does not initiate any lens movement.
If the AfMode is set to AfModeAuto then the AfState will report
- AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent together
- then AfState will omit AfStateIdle and move straight to AfStateScanning
- (and start a scan).
+ AfStateIdle. However, if AfModeAuto and AfTriggerStart are sent
+ together then AfState will omit AfStateIdle and move straight to
+ AfStateScanning (and start a scan).
- If the AfMode is set to AfModeContinuous then the AfState will initially
- report AfStateScanning.
+ If the AfMode is set to AfModeContinuous then the AfState will
+ initially report AfStateScanning.
enum:
- name: AfStateIdle
@@ -725,11 +802,12 @@ controls:
value: 1
description: |
The AF algorithm is in auto mode (AfModeAuto), and a scan has been
- started using the AfTrigger control. The scan can be cancelled by
- sending AfTriggerCancel at which point the algorithm will either
- move back to AfStateIdle or, if the scan actually completes before
- the cancel request is processed, to one of AfStateFocused or
- AfStateFailed.
+ started using the AfTrigger control.
+
+ The scan can be cancelled by sending AfTriggerCancel at which point
+ the algorithm will either move back to AfStateIdle or, if the scan
+ actually completes before the cancel request is processed, to one
+ of AfStateFocused or AfStateFailed.
Alternatively the AF algorithm could be in continuous mode
(AfModeContinuous) at which point it may enter this state
@@ -750,9 +828,12 @@ controls:
- AfPauseState:
type: int32_t
description: |
- Only applicable in continuous (AfModeContinuous) mode, this reports
- whether the algorithm is currently running, paused or pausing (that is,
- will pause as soon as any in-progress scan completes).
+ Report whether the autofocus is currently running, paused or pausing.
+
+ This control is only applicable in continuous (AfModeContinuous) mode,
+ and reports whether the algorithm is currently running, paused or
+ pausing (that is, will pause as soon as any in-progress scan
+ completes).
Any change to AfMode will cause AfPauseStateRunning to be reported.
@@ -766,22 +847,27 @@ controls:
value: 1
description: |
Continuous AF has been sent an AfPauseDeferred control, and will
- pause as soon as any in-progress scan completes (and then report
- AfPauseStatePaused). No new scans will be start spontaneously until
+ pause as soon as any in-progress scan completes.
+
+ When the scan completes, the AfPauseState control will report
+ AfPauseStatePaused. No new scans will be start spontaneously until
the AfPauseResume control is sent.
- name: AfPauseStatePaused
value: 2
description: |
- Continuous AF is paused. No further state changes or lens movements
- will occur until the AfPauseResume control is sent.
+ Continuous AF is paused.
+
+ No further state changes or lens movements will occur until the
+ AfPauseResume control is sent.
- HdrMode:
type: int32_t
description: |
- Control to set the mode to be used for High Dynamic Range (HDR)
- imaging. HDR techniques typically include multiple exposure, image
- fusion and tone mapping techniques to improve the dynamic range of the
- resulting images.
+ Set the mode to be used for High Dynamic Range (HDR) imaging.
+
+ HDR techniques typically include multiple exposure, image fusion and
+ tone mapping techniques to improve the dynamic range of the resulting
+ images.
When using an HDR mode, images are captured with different sets of AGC
settings called HDR channels. Channels indicate in particular the type
@@ -795,16 +881,18 @@ controls:
- name: HdrModeOff
value: 0
description: |
- HDR is disabled. Metadata for this frame will not include the
- HdrChannel control.
+ HDR is disabled.
+
+ Metadata for this frame will not include the HdrChannel control.
- name: HdrModeMultiExposureUnmerged
value: 1
description: |
Multiple exposures will be generated in an alternating fashion.
- However, they will not be merged together and will be returned to
- the application as they are. Each image will be tagged with the
- correct HDR channel, indicating what kind of exposure it is. The
- tag should be the same as in the HdrModeMultiExposure case.
+
+ The multiple exposures will not be merged together and will be
+ returned to the application as they are. Each image will be tagged
+ with the correct HDR channel, indicating what kind of exposure it
+ is. The tag should be the same as in the HdrModeMultiExposure case.
The expectation is that an application using this mode would merge
the frames to create HDR images for itself if it requires them.
@@ -812,8 +900,10 @@ controls:
value: 2
description: |
Multiple exposures will be generated and merged to create HDR
- images. Each image will be tagged with the HDR channel (long, medium
- or short) that arrived and which caused this image to be output.
+ images.
+
+ Each image will be tagged with the HDR channel (long, medium or
+ short) that arrived and which caused this image to be output.
Systems that use two channels for HDR will return images tagged
alternately as the short and long channel. Systems that use three
@@ -823,24 +913,29 @@ controls:
value: 3
description: |
Multiple frames all at a single exposure will be used to create HDR
- images. These images should be reported as all corresponding to the
- HDR short channel.
+ images.
+
+ These images should be reported as all corresponding to the HDR
+ short channel.
- name: HdrModeNight
value: 4
description: |
- Multiple frames will be combined to produce "night mode" images. It
- is up to the implementation exactly which HDR channels it uses, and
- the images will all be tagged accordingly with the correct HDR
+ Multiple frames will be combined to produce "night mode" images.
+
+ It is up to the implementation exactly which HDR channels it uses,
+ and the images will all be tagged accordingly with the correct HDR
channel information.
- HdrChannel:
type: int32_t
description: |
+ The HDR channel used to capture the frame.
+
This value is reported back to the application so that it can discover
- whether this capture corresponds to the short or long exposure image (or
- any other image used by the HDR procedure). An application can monitor
- the HDR channel to discover when the differently exposed images have
- arrived.
+ whether this capture corresponds to the short or long exposure image
+ (or any other image used by the HDR procedure). An application can
+ monitor the HDR channel to discover when the differently exposed images
+ have arrived.
This metadata is only available when an HDR mode has been enabled.
@@ -868,8 +963,9 @@ controls:
- Gamma:
type: float
description: |
- Specify a fixed gamma value. Default must be 2.2 which closely mimics
- sRGB gamma. Note that this is camera gamma, so it is applied as
- 1.0/gamma.
+ Specify a fixed gamma value.
+
+ The default gamma value must be 2.2 which closely mimics sRGB gamma.
+ Note that this is camera gamma, so it is applied as 1.0/gamma.
...
diff --git a/src/libcamera/control_ids_rpi.yaml b/src/libcamera/control_ids_rpi.yaml
index cb097f88..42c4bf5d 100644
--- a/src/libcamera/control_ids_rpi.yaml
+++ b/src/libcamera/control_ids_rpi.yaml
@@ -10,9 +10,11 @@ controls:
- StatsOutputEnable:
type: bool
description: |
- Toggles the Raspberry Pi IPA to output a binary dump of the hardware
- generated statistics through the Request metadata in the Bcm2835StatsOutput
- control.
+ Toggles the Raspberry Pi IPA to output the hardware generated statistics.
+
+ When this control is set to true, the IPA outputs a binary dump of the
+ hardware generated statistics through the Request metadata in the
+ Bcm2835StatsOutput control.
\sa Bcm2835StatsOutput
@@ -20,9 +22,11 @@ controls:
type: uint8_t
size: [n]
description: |
- Span of the BCM2835 ISP generated statistics for the current frame. This
- is sent in the Request metadata if the StatsOutputEnable is set to true.
- The statistics struct definition can be found in include/linux/bcm2835-isp.h.
+ Span of the BCM2835 ISP generated statistics for the current frame.
+
+ This is sent in the Request metadata if the StatsOutputEnable is set to
+ true. The statistics struct definition can be found in
+ include/linux/bcm2835-isp.h.
\sa StatsOutputEnable
diff --git a/src/libcamera/controls.cpp b/src/libcamera/controls.cpp
index 11d35321..ea439730 100644
--- a/src/libcamera/controls.cpp
+++ b/src/libcamera/controls.cpp
@@ -7,10 +7,9 @@
#include <libcamera/controls.h>
-#include <iomanip>
#include <sstream>
-#include <string>
#include <string.h>
+#include <string>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -390,7 +389,16 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
* \param[in] id The control numerical ID
* \param[in] name The control name
* \param[in] type The control data type
+ * \param[in] size The size of the array control, or 0 if scalar control
+ * \param[in] enumStrMap The map from enum names to values (optional)
*/
+ControlId::ControlId(unsigned int id, const std::string &name, ControlType type,
+ std::size_t size, const std::map<std::string, int32_t> &enumStrMap)
+ : id_(id), name_(name), type_(type), size_(size), enumStrMap_(enumStrMap)
+{
+ for (const auto &pair : enumStrMap_)
+ reverseMap_[pair.second] = pair.first;
+}
/**
* \fn unsigned int ControlId::id() const
@@ -411,6 +419,25 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
*/
/**
+ * \fn bool ControlId::isArray() const
+ * \brief Determine if the control is an array control
+ * \return True if the control is an array control, false otherwise
+ */
+
+/**
+ * \fn std::size_t ControlId::size() const
+ * \brief Retrieve the size of the control if it is an array control
+ * \return The size of the array control, size_t::max for dynamic extent, or 0
+ * for non-array
+ */
+
+/**
+ * \fn const std::map<int32_t, std::string> &ControlId::enumerators() const
+ * \brief Retrieve the map of enum values to enum names
+ * \return The map of enum values to enum names
+ */
+
+/**
* \fn bool operator==(unsigned int lhs, const ControlId &rhs)
* \brief Compare a ControlId with a control numerical ID
* \param[in] lhs Left-hand side numerical ID
@@ -460,6 +487,7 @@ void ControlValue::reserve(ControlType type, bool isArray, std::size_t numElemen
* \brief Construct a Control instance
* \param[in] id The control numerical ID
* \param[in] name The control name
+ * \param[in] enumStrMap The map from enum names to values (optional)
*
* The control data type is automatically deduced from the template type T.
*/
diff --git a/src/libcamera/converter.cpp b/src/libcamera/converter.cpp
index d3d38c1b..8237998f 100644
--- a/src/libcamera/converter.cpp
+++ b/src/libcamera/converter.cpp
@@ -14,7 +14,7 @@
#include "libcamera/internal/media_device.h"
/**
- * \file internal/converter.h
+ * \file converter.h
* \brief Abstract converter
*/
@@ -111,12 +111,12 @@ Converter::~Converter()
/**
* \fn Converter::exportBuffers()
* \brief Export buffers from the converter device
- * \param[in] output Output stream index exporting the buffers
+ * \param[in] stream Output stream pointer exporting the buffers
* \param[in] count Number of buffers to allocate
* \param[out] buffers Vector to store allocated buffers
*
* This function operates similarly to V4L2VideoDevice::exportBuffers() on the
- * output stream indicated by the \a output index.
+ * output stream indicated by the \a output.
*
* \return The number of allocated buffers on success or a negative error code
* otherwise
@@ -137,7 +137,7 @@ Converter::~Converter()
* \fn Converter::queueBuffers()
* \brief Queue buffers to converter device
* \param[in] input The frame buffer to apply the conversion
- * \param[out] outputs The container holding the output stream indexes and
+ * \param[out] outputs The container holding the output stream pointers and
* their respective frame buffer outputs.
*
* This function queues the \a input frame buffer on the output streams of the
diff --git a/src/libcamera/converter/converter_v4l2_m2m.cpp b/src/libcamera/converter/converter_v4l2_m2m.cpp
index d8929fc5..e4f656da 100644
--- a/src/libcamera/converter/converter_v4l2_m2m.cpp
+++ b/src/libcamera/converter/converter_v4l2_m2m.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/converter/converter_v4l2_m2m.h"
-#include <algorithm>
#include <limits.h>
#include <libcamera/base/log.h>
@@ -23,7 +22,7 @@
#include "libcamera/internal/v4l2_videodevice.h"
/**
- * \file internal/converter/converter_v4l2_m2m.h
+ * \file converter/converter_v4l2_m2m.h
* \brief V4L2 M2M based converter
*/
@@ -32,24 +31,24 @@ namespace libcamera {
LOG_DECLARE_CATEGORY(Converter)
/* -----------------------------------------------------------------------------
- * V4L2M2MConverter::Stream
+ * V4L2M2MConverter::V4L2M2MStream
*/
-V4L2M2MConverter::Stream::Stream(V4L2M2MConverter *converter, unsigned int index)
- : converter_(converter), index_(index)
+V4L2M2MConverter::V4L2M2MStream::V4L2M2MStream(V4L2M2MConverter *converter, const Stream *stream)
+ : converter_(converter), stream_(stream)
{
m2m_ = std::make_unique<V4L2M2MDevice>(converter->deviceNode());
- m2m_->output()->bufferReady.connect(this, &Stream::outputBufferReady);
- m2m_->capture()->bufferReady.connect(this, &Stream::captureBufferReady);
+ m2m_->output()->bufferReady.connect(this, &V4L2M2MStream::outputBufferReady);
+ m2m_->capture()->bufferReady.connect(this, &V4L2M2MStream::captureBufferReady);
int ret = m2m_->open();
if (ret < 0)
m2m_.reset();
}
-int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
- const StreamConfiguration &outputCfg)
+int V4L2M2MConverter::V4L2M2MStream::configure(const StreamConfiguration &inputCfg,
+ const StreamConfiguration &outputCfg)
{
V4L2PixelFormat videoFormat =
m2m_->output()->toV4L2PixelFormat(inputCfg.pixelFormat);
@@ -101,13 +100,13 @@ int V4L2M2MConverter::Stream::configure(const StreamConfiguration &inputCfg,
return 0;
}
-int V4L2M2MConverter::Stream::exportBuffers(unsigned int count,
- std::vector<std::unique_ptr<FrameBuffer>> *buffers)
+int V4L2M2MConverter::V4L2M2MStream::exportBuffers(unsigned int count,
+ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
return m2m_->capture()->exportBuffers(count, buffers);
}
-int V4L2M2MConverter::Stream::start()
+int V4L2M2MConverter::V4L2M2MStream::start()
{
int ret = m2m_->output()->importBuffers(inputBufferCount_);
if (ret < 0)
@@ -134,7 +133,7 @@ int V4L2M2MConverter::Stream::start()
return 0;
}
-void V4L2M2MConverter::Stream::stop()
+void V4L2M2MConverter::V4L2M2MStream::stop()
{
m2m_->capture()->streamOff();
m2m_->output()->streamOff();
@@ -142,7 +141,7 @@ void V4L2M2MConverter::Stream::stop()
m2m_->output()->releaseBuffers();
}
-int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
+int V4L2M2MConverter::V4L2M2MStream::queueBuffers(FrameBuffer *input, FrameBuffer *output)
{
int ret = m2m_->output()->queueBuffer(input);
if (ret < 0)
@@ -155,12 +154,12 @@ int V4L2M2MConverter::Stream::queueBuffers(FrameBuffer *input, FrameBuffer *outp
return 0;
}
-std::string V4L2M2MConverter::Stream::logPrefix() const
+std::string V4L2M2MConverter::V4L2M2MStream::logPrefix() const
{
- return "stream" + std::to_string(index_);
+ return stream_->configuration().toString();
}
-void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::V4L2M2MStream::outputBufferReady(FrameBuffer *buffer)
{
auto it = converter_->queue_.find(buffer);
if (it == converter_->queue_.end())
@@ -172,7 +171,7 @@ void V4L2M2MConverter::Stream::outputBufferReady(FrameBuffer *buffer)
}
}
-void V4L2M2MConverter::Stream::captureBufferReady(FrameBuffer *buffer)
+void V4L2M2MConverter::V4L2M2MStream::captureBufferReady(FrameBuffer *buffer)
{
converter_->outputBufferReady.emit(buffer);
}
@@ -333,21 +332,24 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
int ret = 0;
streams_.clear();
- streams_.reserve(outputCfgs.size());
for (unsigned int i = 0; i < outputCfgs.size(); ++i) {
- Stream &stream = streams_.emplace_back(this, i);
+ const StreamConfiguration &cfg = outputCfgs[i];
+ std::unique_ptr<V4L2M2MStream> stream =
+ std::make_unique<V4L2M2MStream>(this, cfg.stream());
- if (!stream.isValid()) {
+ if (!stream->isValid()) {
LOG(Converter, Error)
<< "Failed to create stream " << i;
ret = -EINVAL;
break;
}
- ret = stream.configure(inputCfg, outputCfgs[i]);
+ ret = stream->configure(inputCfg, cfg);
if (ret < 0)
break;
+
+ streams_.emplace(cfg.stream(), std::move(stream));
}
if (ret < 0) {
@@ -361,13 +363,14 @@ int V4L2M2MConverter::configure(const StreamConfiguration &inputCfg,
/**
* \copydoc libcamera::Converter::exportBuffers
*/
-int V4L2M2MConverter::exportBuffers(unsigned int output, unsigned int count,
+int V4L2M2MConverter::exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
- if (output >= streams_.size())
+ auto iter = streams_.find(stream);
+ if (iter == streams_.end())
return -EINVAL;
- return streams_[output].exportBuffers(count, buffers);
+ return iter->second->exportBuffers(count, buffers);
}
/**
@@ -377,8 +380,8 @@ int V4L2M2MConverter::start()
{
int ret;
- for (Stream &stream : streams_) {
- ret = stream.start();
+ for (auto &iter : streams_) {
+ ret = iter.second->start();
if (ret < 0) {
stop();
return ret;
@@ -393,41 +396,40 @@ int V4L2M2MConverter::start()
*/
void V4L2M2MConverter::stop()
{
- for (Stream &stream : utils::reverse(streams_))
- stream.stop();
+ for (auto &iter : streams_)
+ iter.second->stop();
}
/**
* \copydoc libcamera::Converter::queueBuffers
*/
int V4L2M2MConverter::queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs)
+ const std::map<const Stream *, FrameBuffer *> &outputs)
{
- unsigned int mask = 0;
+ std::set<FrameBuffer *> outputBufs;
int ret;
/*
* Validate the outputs as a sanity check: at least one output is
* required, all outputs must reference a valid stream and no two
- * outputs can reference the same stream.
+ * streams can reference same output framebuffers.
*/
if (outputs.empty())
return -EINVAL;
- for (auto [index, buffer] : outputs) {
+ for (auto [stream, buffer] : outputs) {
if (!buffer)
return -EINVAL;
- if (index >= streams_.size())
- return -EINVAL;
- if (mask & (1 << index))
- return -EINVAL;
- mask |= 1 << index;
+ outputBufs.insert(buffer);
}
+ if (outputBufs.size() != streams_.size())
+ return -EINVAL;
+
/* Queue the input and output buffers to all the streams. */
- for (auto [index, buffer] : outputs) {
- ret = streams_[index].queueBuffers(input, buffer);
+ for (auto [stream, buffer] : outputs) {
+ ret = streams_.at(stream)->queueBuffers(input, buffer);
if (ret < 0)
return ret;
}
diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build
index 2aa72fe4..af1a80fe 100644
--- a/src/libcamera/converter/meson.build
+++ b/src/libcamera/converter/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'converter_v4l2_m2m.cpp'
])
diff --git a/src/libcamera/device_enumerator_sysfs.cpp b/src/libcamera/device_enumerator_sysfs.cpp
index fc33ba52..7866885c 100644
--- a/src/libcamera/device_enumerator_sysfs.cpp
+++ b/src/libcamera/device_enumerator_sysfs.cpp
@@ -33,7 +33,7 @@ int DeviceEnumeratorSysfs::init()
int DeviceEnumeratorSysfs::enumerate()
{
struct dirent *ent;
- DIR *dir;
+ DIR *dir = nullptr;
static const char * const sysfs_dirs[] = {
"/sys/subsystem/media/devices",
diff --git a/src/libcamera/device_enumerator_udev.cpp b/src/libcamera/device_enumerator_udev.cpp
index 01c70b6d..4e20a3cc 100644
--- a/src/libcamera/device_enumerator_udev.cpp
+++ b/src/libcamera/device_enumerator_udev.cpp
@@ -332,6 +332,14 @@ int DeviceEnumeratorUdev::addV4L2Device(dev_t devnum)
void DeviceEnumeratorUdev::udevNotify()
{
struct udev_device *dev = udev_monitor_receive_device(monitor_);
+ if (!dev) {
+ int err = errno;
+ LOG(DeviceEnumerator, Warning)
+ << "Ignoring notfication received without a device: "
+ << strerror(err);
+ return;
+ }
+
std::string_view action(udev_device_get_action(dev));
std::string_view deviceNode(udev_device_get_devnode(dev));
diff --git a/src/libcamera/dma_buf_allocator.cpp b/src/libcamera/dma_buf_allocator.cpp
index c06eca7d..be6efb89 100644
--- a/src/libcamera/dma_buf_allocator.cpp
+++ b/src/libcamera/dma_buf_allocator.cpp
@@ -13,7 +13,6 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
-#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
@@ -22,6 +21,7 @@
#include <linux/udmabuf.h>
#include <libcamera/base/log.h>
+#include <libcamera/base/memfd.h>
/**
* \file dma_buf_allocator.cpp
@@ -126,54 +126,16 @@ DmaBufAllocator::~DmaBufAllocator() = default;
* \brief Check if the DmaBufAllocator instance is valid
* \return True if the DmaBufAllocator is valid, false otherwise
*/
-
-/* uClibc doesn't provide the file sealing API. */
-#ifndef __DOXYGEN__
-#if not HAVE_FILE_SEALS
-#define F_ADD_SEALS 1033
-#define F_SEAL_SHRINK 0x0002
-#endif
-#endif
-
UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size)
{
/* Size must be a multiple of the page size. Round it up. */
std::size_t pageMask = sysconf(_SC_PAGESIZE) - 1;
size = (size + pageMask) & ~pageMask;
-#if HAVE_MEMFD_CREATE
- int ret = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
-#else
- int ret = syscall(SYS_memfd_create, name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
-#endif
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to allocate memfd storage for " << name
- << ": " << strerror(ret);
- return {};
- }
-
- UniqueFD memfd(ret);
-
- ret = ftruncate(memfd.get(), size);
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to set memfd size for " << name
- << ": " << strerror(ret);
- return {};
- }
-
/* udmabuf dma-buffers *must* have the F_SEAL_SHRINK seal. */
- ret = fcntl(memfd.get(), F_ADD_SEALS, F_SEAL_SHRINK);
- if (ret < 0) {
- ret = errno;
- LOG(DmaBufAllocator, Error)
- << "Failed to seal the memfd for " << name
- << ": " << strerror(ret);
+ UniqueFD memfd = MemFd::create(name, size, MemFd::Seal::Shrink);
+ if (!memfd.isValid())
return {};
- }
struct udmabuf_create create;
@@ -182,7 +144,7 @@ UniqueFD DmaBufAllocator::allocFromUDmaBuf(const char *name, std::size_t size)
create.offset = 0;
create.size = size;
- ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create);
+ int ret = ::ioctl(providerHandle_.get(), UDMABUF_CREATE, &create);
if (ret < 0) {
ret = errno;
LOG(DmaBufAllocator, Error)
diff --git a/src/libcamera/fence.cpp b/src/libcamera/fence.cpp
index 634c74f8..73299b40 100644
--- a/src/libcamera/fence.cpp
+++ b/src/libcamera/fence.cpp
@@ -11,7 +11,7 @@ namespace libcamera {
/**
*
- * \file libcamera/fence.h
+ * \file fence.h
* \brief Definition of the Fence class
*/
diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp
index cf41f2c2..dbefb094 100644
--- a/src/libcamera/formats.cpp
+++ b/src/libcamera/formats.cpp
@@ -7,8 +7,7 @@
#include "libcamera/internal/formats.h"
-#include <algorithm>
-#include <errno.h>
+#include <map>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -16,7 +15,7 @@
#include <libcamera/formats.h>
/**
- * \file internal/formats.h
+ * \file libcamera/internal/formats.h
* \brief Types and helper functions to handle libcamera image formats
*/
@@ -527,6 +526,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
.pixelsPerGroup = 4,
.planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }},
} },
+ { formats::R12_CSI2P, {
+ .name = "R12_CSI2P",
+ .format = formats::R12_CSI2P,
+ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_Y12P), },
+ .bitsPerPixel = 12,
+ .colourEncoding = PixelFormatInfo::ColourEncodingYUV,
+ .packed = true,
+ .pixelsPerGroup = 2,
+ .planes = {{ { 3, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
{ formats::R12, {
.name = "R12",
.format = formats::R12,
diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml
index fe027a7c..2d54d391 100644
--- a/src/libcamera/formats.yaml
+++ b/src/libcamera/formats.yaml
@@ -138,6 +138,9 @@ formats:
- R10_CSI2P:
fourcc: DRM_FORMAT_R10
mod: MIPI_FORMAT_MOD_CSI2_PACKED
+ - R12_CSI2P:
+ fourcc: DRM_FORMAT_R12
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
- SRGGB10_CSI2P:
fourcc: DRM_FORMAT_SRGGB10
diff --git a/src/libcamera/framebuffer.cpp b/src/libcamera/framebuffer.cpp
index 63d679cb..826848f7 100644
--- a/src/libcamera/framebuffer.cpp
+++ b/src/libcamera/framebuffer.cpp
@@ -16,7 +16,10 @@
/**
* \file libcamera/framebuffer.h
* \brief Frame buffer handling
- *
+ */
+
+/**
+ * \internal
* \file libcamera/internal/framebuffer.h
* \brief Internal frame buffer handling support
*/
@@ -104,6 +107,7 @@ LOG_DEFINE_CATEGORY(Buffer)
* \return The array of per-plane metadata
*/
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class FrameBuffer::Private
* \brief Base class for FrameBuffer private data
@@ -206,6 +210,7 @@ FrameBuffer::Private::~Private()
* \brief Retrieve the dynamic metadata
* \return Dynamic metadata for the frame contained in the buffer
*/
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \class FrameBuffer
diff --git a/src/libcamera/ipa_data_serializer.cpp b/src/libcamera/ipa_data_serializer.cpp
index 3e9bef08..2189a246 100644
--- a/src/libcamera/ipa_data_serializer.cpp
+++ b/src/libcamera/ipa_data_serializer.cpp
@@ -11,6 +11,8 @@
#include <libcamera/base/log.h>
+#include "libcamera/internal/byte_stream_buffer.h"
+
/**
* \file ipa_data_serializer.h
* \brief IPA Data Serializer
@@ -537,7 +539,6 @@ IPADataSerializer<SharedFD>::serialize(const SharedFD &data,
if (data.isValid())
fdVec.push_back(data);
-
return { dataVec, fdVec };
}
@@ -604,7 +605,7 @@ IPADataSerializer<FrameBuffer::Plane>::deserialize(std::vector<uint8_t>::const_i
FrameBuffer::Plane ret;
ret.fd = IPADataSerializer<SharedFD>::deserialize(dataBegin, dataBegin + 4,
- fdsBegin, fdsBegin + 1);
+ fdsBegin, fdsBegin + 1);
ret.offset = readPOD<uint32_t>(dataBegin, 4, dataEnd);
ret.length = readPOD<uint32_t>(dataBegin, 8, dataEnd);
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index f4e0b633..cfc24d38 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -95,8 +95,6 @@ LOG_DEFINE_CATEGORY(IPAManager)
* IPC.
*/
-IPAManager *IPAManager::self_ = nullptr;
-
/**
* \brief Construct an IPAManager instance
*
@@ -105,10 +103,6 @@ IPAManager *IPAManager::self_ = nullptr;
*/
IPAManager::IPAManager()
{
- if (self_)
- LOG(IPAManager, Fatal)
- << "Multiple IPAManager objects are not allowed";
-
#if HAVE_IPA_PUBKEY
if (!pubKey_.isValid())
LOG(IPAManager, Warning) << "Public key not valid";
@@ -153,16 +147,12 @@ IPAManager::IPAManager()
if (!ipaCount)
LOG(IPAManager, Warning)
<< "No IPA found in '" IPA_MODULE_DIR "'";
-
- self_ = this;
}
IPAManager::~IPAManager()
{
for (IPAModule *module : modules_)
delete module;
-
- self_ = nullptr;
}
/**
diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp
index 0756b691..9ca74be6 100644
--- a/src/libcamera/ipa_module.cpp
+++ b/src/libcamera/ipa_module.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/ipa_module.h"
#include <algorithm>
-#include <array>
#include <ctype.h>
#include <dlfcn.h>
#include <elf.h>
@@ -51,8 +50,8 @@ typename std::remove_extent_t<T> *elfPointer(Span<const uint8_t> elf,
if (size > elf.size() || size < objSize)
return nullptr;
- return reinterpret_cast<typename std::remove_extent_t<T> *>
- (reinterpret_cast<const char *>(elf.data()) + offset);
+ return reinterpret_cast<typename std::remove_extent_t<T> *>(
+ reinterpret_cast<const char *>(elf.data()) + offset);
}
template<typename T>
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 6c17c456..85004737 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -7,7 +7,6 @@
#include "libcamera/internal/ipa_proxy.h"
-#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -72,6 +71,7 @@ IPAProxy::~IPAProxy()
/**
* \brief Retrieve the absolute path to an IPA configuration file
* \param[in] name The configuration file name
+ * \param[in] fallbackName The name of a fallback configuration file
*
* This function locates the configuration file for an IPA and returns its
* absolute path. It searches the following directories, in order:
@@ -89,10 +89,14 @@ IPAProxy::~IPAProxy()
* named after the IPA module name, as reported in IPAModuleInfo::name, and for
* a file named \a name within that directory. The \a name is IPA-specific.
*
+ * If the file named \a name is not found and \a fallbackName is non-empty then
+ * the whole search is repeated for \a fallbackName.
+ *
* \return The full path to the IPA configuration file, or an empty string if
* no configuration file can be found
*/
-std::string IPAProxy::configurationFile(const std::string &name) const
+std::string IPAProxy::configurationFile(const std::string &name,
+ const std::string &fallbackName) const
{
struct stat statbuf;
int ret;
@@ -146,11 +150,18 @@ std::string IPAProxy::configurationFile(const std::string &name) const
}
}
- LOG(IPAProxy, Error)
- << "Configuration file '" << name
- << "' not found for IPA module '" << ipaName << "'";
+ if (fallbackName.empty()) {
+ LOG(IPAProxy, Error)
+ << "Configuration file '" << name
+ << "' not found for IPA module '" << ipaName << "'";
+ return std::string();
+ }
- return std::string();
+ LOG(IPAProxy, Warning)
+ << "Configuration file '" << name
+ << "' not found for IPA module '" << ipaName
+ << "', falling back to '" << fallbackName << "'";
+ return configurationFile(fallbackName);
}
/**
diff --git a/src/libcamera/ipc_unixsocket.cpp b/src/libcamera/ipc_unixsocket.cpp
index 75285b67..002053e3 100644
--- a/src/libcamera/ipc_unixsocket.cpp
+++ b/src/libcamera/ipc_unixsocket.cpp
@@ -12,6 +12,7 @@
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
+#include <vector>
#include <libcamera/base/event_notifier.h>
#include <libcamera/base/log.h>
@@ -247,10 +248,9 @@ int IPCUnixSocket::sendData(const void *buffer, size_t length,
iov[0].iov_base = const_cast<void *>(buffer);
iov[0].iov_len = length;
- char buf[CMSG_SPACE(num * sizeof(uint32_t))];
- memset(buf, 0, sizeof(buf));
+ std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t)));
- struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data());
cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
@@ -283,10 +283,9 @@ int IPCUnixSocket::recvData(void *buffer, size_t length,
iov[0].iov_base = buffer;
iov[0].iov_len = length;
- char buf[CMSG_SPACE(num * sizeof(uint32_t))];
- memset(buf, 0, sizeof(buf));
+ std::vector<uint8_t> buf(CMSG_SPACE(num * sizeof(uint32_t)));
- struct cmsghdr *cmsg = (struct cmsghdr *)buf;
+ struct cmsghdr *cmsg = reinterpret_cast<struct cmsghdr *>(buf.data());
cmsg->cmsg_len = CMSG_LEN(num * sizeof(uint32_t));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
diff --git a/src/libcamera/mapped_framebuffer.cpp b/src/libcamera/mapped_framebuffer.cpp
index b3104e05..ad82e1f6 100644
--- a/src/libcamera/mapped_framebuffer.cpp
+++ b/src/libcamera/mapped_framebuffer.cpp
@@ -16,7 +16,7 @@
#include <libcamera/base/log.h>
/**
- * \file libcamera/internal/mapped_framebuffer.h
+ * \file mapped_framebuffer.h
* \brief Frame buffer memory mapping support
*/
diff --git a/src/libcamera/media_device.cpp b/src/libcamera/media_device.cpp
index bd054552..d71dad74 100644
--- a/src/libcamera/media_device.cpp
+++ b/src/libcamera/media_device.cpp
@@ -818,20 +818,12 @@ int MediaDevice::setupLink(const MediaLink *link, unsigned int flags)
if (ret) {
ret = -errno;
LOG(MediaDevice, Error)
- << "Failed to setup link "
- << source->entity()->name() << "["
- << source->index() << "] -> "
- << sink->entity()->name() << "["
- << sink->index() << "]: "
+ << "Failed to setup link " << *link << ": "
<< strerror(-ret);
return ret;
}
- LOG(MediaDevice, Debug)
- << source->entity()->name() << "["
- << source->index() << "] -> "
- << sink->entity()->name() << "["
- << sink->index() << "]: " << flags;
+ LOG(MediaDevice, Debug) << *link << ": " << flags;
return 0;
}
diff --git a/src/libcamera/media_object.cpp b/src/libcamera/media_object.cpp
index 1b191a1e..3e3772a6 100644
--- a/src/libcamera/media_object.cpp
+++ b/src/libcamera/media_object.cpp
@@ -147,6 +147,31 @@ MediaLink::MediaLink(const struct media_v2_link *link, MediaPad *source,
}
/**
+ * \brief Generate a string representation of the MediaLink
+ * \return A string representing the MediaLink
+ */
+std::string MediaLink::toString() const
+{
+ std::stringstream ss;
+ ss << *this;
+
+ return ss.str();
+}
+
+/**
+ * \brief Insert a text representation of a Link into an output stream
+ * \param[in] out The output stream
+ * \param[in] link The MediaLink
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const MediaLink &link)
+{
+ out << *link.source() << " -> " << *link.sink();
+
+ return out;
+}
+
+/**
* \fn MediaLink::source()
* \brief Retrieve the link's source pad
* \return The source pad at the origin of the link
@@ -236,6 +261,31 @@ void MediaPad::addLink(MediaLink *link)
}
/**
+ * \brief Generate a string representation of the MediaPad
+ * \return A string representing the MediaPad
+ */
+std::string MediaPad::toString() const
+{
+ std::stringstream ss;
+ ss << *this;
+
+ return ss.str();
+}
+
+/**
+ * \brief Insert a text representation of a MediaPad into an output stream
+ * \param[in] out The output stream
+ * \param[in] pad The MediaPad
+ * \return The output stream \a out
+ */
+std::ostream &operator<<(std::ostream &out, const MediaPad &pad)
+{
+ out << "'" << pad.entity()->name() << "'[" << pad.index() << "]";
+
+ return out;
+}
+
+/**
* \class MediaEntity
* \brief The MediaEntity represents an entity in the media graph
*
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 89504cee..aa9ab029 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -1,14 +1,26 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources = files([
- 'bayer_format.cpp',
- 'byte_stream_buffer.cpp',
+libcamera_public_sources = files([
'camera.cpp',
- 'camera_controls.cpp',
- 'camera_lens.cpp',
'camera_manager.cpp',
'color_space.cpp',
'controls.cpp',
+ 'fence.cpp',
+ 'framebuffer.cpp',
+ 'framebuffer_allocator.cpp',
+ 'geometry.cpp',
+ 'orientation.cpp',
+ 'pixel_format.cpp',
+ 'request.cpp',
+ 'stream.cpp',
+ 'transform.cpp',
+])
+
+libcamera_internal_sources = files([
+ 'bayer_format.cpp',
+ 'byte_stream_buffer.cpp',
+ 'camera_controls.cpp',
+ 'camera_lens.cpp',
'control_serializer.cpp',
'control_validator.cpp',
'converter.cpp',
@@ -16,11 +28,7 @@ libcamera_sources = files([
'device_enumerator.cpp',
'device_enumerator_sysfs.cpp',
'dma_buf_allocator.cpp',
- 'fence.cpp',
'formats.cpp',
- 'framebuffer.cpp',
- 'framebuffer_allocator.cpp',
- 'geometry.cpp',
'ipa_controls.cpp',
'ipa_data_serializer.cpp',
'ipa_interface.cpp',
@@ -33,17 +41,12 @@ libcamera_sources = files([
'mapped_framebuffer.cpp',
'media_device.cpp',
'media_object.cpp',
- 'orientation.cpp',
'pipeline_handler.cpp',
- 'pixel_format.cpp',
'process.cpp',
'pub_key.cpp',
- 'request.cpp',
'shared_mem_object.cpp',
'source_paths.cpp',
- 'stream.cpp',
'sysfs.cpp',
- 'transform.cpp',
'v4l2_device.cpp',
'v4l2_pixelformat.cpp',
'v4l2_subdevice.cpp',
@@ -51,10 +54,6 @@ libcamera_sources = files([
'yaml_parser.cpp',
])
-libcamera_sources += libcamera_public_headers
-libcamera_sources += libcamera_generated_ipa_headers
-libcamera_sources += libcamera_tracepoint_header
-
includes = [
libcamera_includes,
]
@@ -104,14 +103,14 @@ endif
if liblttng.found()
tracing_enabled = true
config_h.set('HAVE_TRACING', 1)
- libcamera_sources += files(['tracepoints.cpp'])
+ libcamera_internal_sources += files(['tracepoints.cpp'])
else
tracing_enabled = false
endif
if libudev.found()
config_h.set('HAVE_LIBUDEV', 1)
- libcamera_sources += files([
+ libcamera_internal_sources += files([
'device_enumerator_udev.cpp',
])
endif
@@ -130,29 +129,33 @@ endif
control_sources = []
controls_mode_files = {
- 'controls' : controls_files,
- 'properties' : properties_files,
+ 'controls': [
+ controls_files,
+ 'control_ids.cpp',
+ ],
+ 'properties': [
+ properties_files,
+ 'property_ids.cpp',
+ ],
}
-foreach mode, input_files : controls_mode_files
- input_files = files(input_files)
-
- if mode == 'controls'
- template_file = files('control_ids.cpp.in')
- else
- template_file = files('property_ids.cpp.in')
- endif
+foreach mode, inout_files : controls_mode_files
+ input_files = inout_files[0]
+ output_file = inout_files[1]
+ template_file = files('control_ids.cpp.in')
ranges_file = files('control_ranges.yaml')
- control_sources += custom_target(mode + '_cpp',
+
+ control_sources += custom_target(mode + '_ids_cpp',
input : input_files,
- output : mode + '_ids.cpp',
+ output : output_file,
command : [gen_controls, '-o', '@OUTPUT@',
'--mode', mode, '-t', template_file,
- '-r', ranges_file, '@INPUT@'])
+ '-r', ranges_file, '@INPUT@'],
+ env : py_build_env)
endforeach
-libcamera_sources += control_sources
+libcamera_public_sources += control_sources
gen_version = meson.project_source_root() / 'utils' / 'gen-version.sh'
@@ -163,7 +166,7 @@ version_cpp = vcs_tag(command : [gen_version, meson.project_build_root(), meson.
output : 'version.cpp',
fallback : meson.project_version())
-libcamera_sources += version_cpp
+libcamera_public_sources += version_cpp
if ipa_sign_module
ipa_pub_key_cpp = custom_target('ipa_pub_key_cpp',
@@ -171,7 +174,7 @@ if ipa_sign_module
output : 'ipa_pub_key.cpp',
command : [gen_ipa_pub_key, '@INPUT@', '@OUTPUT@'])
- libcamera_sources += ipa_pub_key_cpp
+ libcamera_internal_sources += ipa_pub_key_cpp
endif
libcamera_deps += [
@@ -191,7 +194,13 @@ libcamera_deps += [
# for the presence or abscence of the dynamic tag.
libcamera = shared_library('libcamera',
- libcamera_sources,
+ [
+ libcamera_public_headers,
+ libcamera_public_sources,
+ libcamera_ipa_headers,
+ libcamera_internal_headers,
+ libcamera_internal_sources,
+ ],
version : libcamera_version,
soversion : libcamera_soversion,
name_prefix : '',
@@ -201,7 +210,6 @@ libcamera = shared_library('libcamera',
dependencies : libcamera_deps)
libcamera_public = declare_dependency(sources : [
- libcamera_ipa_headers,
libcamera_public_headers,
],
include_directories : libcamera_includes,
@@ -210,7 +218,7 @@ libcamera_public = declare_dependency(sources : [
# Internal dependency for components and plugins which can use private APIs
libcamera_private = declare_dependency(sources : [
- libcamera_generated_ipa_headers,
+ libcamera_ipa_headers,
],
dependencies : [
libcamera_public,
diff --git a/src/libcamera/orientation.cpp b/src/libcamera/orientation.cpp
index 47fd6a32..7d7d21ae 100644
--- a/src/libcamera/orientation.cpp
+++ b/src/libcamera/orientation.cpp
@@ -8,10 +8,9 @@
#include <libcamera/orientation.h>
#include <array>
-#include <string>
/**
- * \file libcamera/orientation.h
+ * \file orientation.h
* \brief Image orientation definition
*/
@@ -102,10 +101,14 @@ std::ostream &operator<<(std::ostream &out, const Orientation &orientation)
{
constexpr std::array<const char *, 9> orientationNames = {
"", /* Orientation starts counting from 1. */
- "Rotate0", "Rotate0Mirror",
- "Rotate180", "Rotate180Mirror",
- "Rotate90Mirror", "Rotate270",
- "Rotate270Mirror", "Rotate90",
+ "Rotate0",
+ "Rotate0Mirror",
+ "Rotate180",
+ "Rotate180Mirror",
+ "Rotate90Mirror",
+ "Rotate270",
+ "Rotate270Mirror",
+ "Rotate90",
};
out << orientationNames[static_cast<unsigned int>(orientation)];
diff --git a/src/libcamera/pipeline/imx8-isi/meson.build b/src/libcamera/pipeline/imx8-isi/meson.build
index ffd0ce54..b369b031 100644
--- a/src/libcamera/pipeline/imx8-isi/meson.build
+++ b/src/libcamera/pipeline/imx8-isi/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'imx8-isi.cpp'
])
diff --git a/src/libcamera/pipeline/ipu3/frames.cpp b/src/libcamera/pipeline/ipu3/frames.cpp
index 88eb9d05..bc0526a7 100644
--- a/src/libcamera/pipeline/ipu3/frames.cpp
+++ b/src/libcamera/pipeline/ipu3/frames.cpp
@@ -7,12 +7,13 @@
#include "frames.h"
+#include <libcamera/base/log.h>
+
#include <libcamera/framebuffer.h>
#include <libcamera/request.h>
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/v4l2_videodevice.h"
namespace libcamera {
diff --git a/src/libcamera/pipeline/ipu3/ipu3.cpp b/src/libcamera/pipeline/ipu3/ipu3.cpp
index 066fd4a2..430aa902 100644
--- a/src/libcamera/pipeline/ipu3/ipu3.cpp
+++ b/src/libcamera/pipeline/ipu3/ipu3.cpp
@@ -6,7 +6,6 @@
*/
#include <algorithm>
-#include <iomanip>
#include <memory>
#include <queue>
#include <vector>
@@ -19,12 +18,13 @@
#include <libcamera/camera.h>
#include <libcamera/control_ids.h>
#include <libcamera/formats.h>
-#include <libcamera/ipa/ipu3_ipa_interface.h>
-#include <libcamera/ipa/ipu3_ipa_proxy.h>
#include <libcamera/property_ids.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
+#include <libcamera/ipa/ipu3_ipa_interface.h>
+#include <libcamera/ipa/ipu3_ipa_proxy.h>
+
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor.h"
@@ -1117,19 +1117,19 @@ int PipelineHandlerIPU3::registerCameras()
* returned through the ImgU main and secondary outputs.
*/
data->cio2_.bufferReady().connect(data.get(),
- &IPU3CameraData::cio2BufferReady);
+ &IPU3CameraData::cio2BufferReady);
data->cio2_.bufferAvailable.connect(
data.get(), &IPU3CameraData::queuePendingRequests);
data->imgu_->input_->bufferReady.connect(&data->cio2_,
- &CIO2Device::tryReturnBuffer);
+ &CIO2Device::tryReturnBuffer);
data->imgu_->output_->bufferReady.connect(data.get(),
- &IPU3CameraData::imguOutputBufferReady);
+ &IPU3CameraData::imguOutputBufferReady);
data->imgu_->viewfinder_->bufferReady.connect(data.get(),
- &IPU3CameraData::imguOutputBufferReady);
+ &IPU3CameraData::imguOutputBufferReady);
data->imgu_->param_->bufferReady.connect(data.get(),
- &IPU3CameraData::paramBufferReady);
+ &IPU3CameraData::paramBufferReady);
data->imgu_->stat_->bufferReady.connect(data.get(),
- &IPU3CameraData::statBufferReady);
+ &IPU3CameraData::statBufferReady);
/* Create and register the Camera instance. */
const std::string &cameraId = cio2->sensor()->id();
@@ -1186,9 +1186,8 @@ int IPU3CameraData::loadIPA()
* The API tuning file is made from the sensor name. If the tuning file
* isn't found, fall back to the 'uncalibrated' file.
*/
- std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ std::string ipaTuningFile =
+ ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
sensorInfo, sensor->controls(), &ipaControls_);
diff --git a/src/libcamera/pipeline/ipu3/meson.build b/src/libcamera/pipeline/ipu3/meson.build
index a1b0b31a..f2904b4a 100644
--- a/src/libcamera/pipeline/ipu3/meson.build
+++ b/src/libcamera/pipeline/ipu3/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'cio2.cpp',
'frames.cpp',
'imgu.cpp',
diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build
index 30fd29b9..eba8e5a3 100644
--- a/src/libcamera/pipeline/mali-c55/meson.build
+++ b/src/libcamera/pipeline/mali-c55/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'mali-c55.cpp'
])
diff --git a/src/libcamera/pipeline/rkisp1/meson.build b/src/libcamera/pipeline/rkisp1/meson.build
index cad66535..d21a6ef9 100644
--- a/src/libcamera/pipeline/rkisp1/meson.build
+++ b/src/libcamera/pipeline/rkisp1/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'rkisp1.cpp',
'rkisp1_path.cpp',
])
diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 4cbf105d..c02c7cf3 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -6,10 +6,9 @@
*/
#include <algorithm>
-#include <array>
-#include <iomanip>
#include <memory>
#include <numeric>
+#include <optional>
#include <queue>
#include <linux/media-bus-format.h>
@@ -110,7 +109,7 @@ public:
std::unique_ptr<ipa::rkisp1::IPAProxyRkISP1> ipa_;
private:
- void paramFilled(unsigned int frame);
+ void paramFilled(unsigned int frame, unsigned int bytesused);
void setSensorControls(unsigned int frame,
const ControlList &sensorControls);
@@ -351,13 +350,8 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
std::string ipaTuningFile;
char const *configFromEnv = utils::secure_getenv("LIBCAMERA_RKISP1_TUNING_FILE");
if (!configFromEnv || *configFromEnv == '\0') {
- ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml");
- /*
- * If the tuning file isn't found, fall back to the
- * 'uncalibrated' configuration file.
- */
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ ipaTuningFile =
+ ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml");
} else {
ipaTuningFile = std::string(configFromEnv);
}
@@ -379,15 +373,14 @@ int RkISP1CameraData::loadIPA(unsigned int hwRevision)
return 0;
}
-void RkISP1CameraData::paramFilled(unsigned int frame)
+void RkISP1CameraData::paramFilled(unsigned int frame, unsigned int bytesused)
{
PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe();
RkISP1FrameInfo *info = frameInfo_.find(frame);
if (!info)
return;
- info->paramBuffer->_d()->metadata().planes()[0].bytesused =
- sizeof(struct rkisp1_params_cfg);
+ info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused;
pipe->param_->queueBuffer(info->paramBuffer);
pipe->stat_->queueBuffer(info->statBuffer);
@@ -800,7 +793,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
}
V4L2DeviceFormat paramFormat;
- paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_PARAMS);
+ paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS);
ret = param_->setFormat(&paramFormat);
if (ret)
return ret;
@@ -819,6 +812,7 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
return ret;
ipaConfig.sensorControls = data->sensor_->controls();
+ ipaConfig.paramFormat = paramFormat.fourcc;
ret = data->ipa_->configure(ipaConfig, streamConfig, &data->controlInfo_);
if (ret) {
@@ -920,71 +914,62 @@ int PipelineHandlerRkISP1::freeBuffers(Camera *camera)
int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlList *controls)
{
RkISP1CameraData *data = cameraData(camera);
+ utils::ScopeExitActions actions;
int ret;
/* Allocate buffers for internal pipeline usage. */
ret = allocateBuffers(camera);
if (ret)
return ret;
+ actions += [&]() { freeBuffers(camera); };
ret = data->ipa_->start();
if (ret) {
- freeBuffers(camera);
LOG(RkISP1, Error)
<< "Failed to start IPA " << camera->id();
return ret;
}
+ actions += [&]() { data->ipa_->stop(); };
data->frame_ = 0;
if (!isRaw_) {
ret = param_->streamOn();
if (ret) {
- data->ipa_->stop();
- freeBuffers(camera);
LOG(RkISP1, Error)
<< "Failed to start parameters " << camera->id();
return ret;
}
+ actions += [&]() { param_->streamOff(); };
ret = stat_->streamOn();
if (ret) {
- param_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
LOG(RkISP1, Error)
<< "Failed to start statistics " << camera->id();
return ret;
}
+ actions += [&]() { stat_->streamOff(); };
}
if (data->mainPath_->isEnabled()) {
ret = mainPath_.start();
- if (ret) {
- param_->streamOff();
- stat_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
+ if (ret)
return ret;
- }
+ actions += [&]() { mainPath_.stop(); };
}
if (hasSelfPath_ && data->selfPath_->isEnabled()) {
ret = selfPath_.start();
- if (ret) {
- mainPath_.stop();
- param_->streamOff();
- stat_->streamOff();
- data->ipa_->stop();
- freeBuffers(camera);
+ if (ret)
return ret;
- }
}
isp_->setFrameStartEnabled(true);
activeCamera_ = camera;
- return ret;
+
+ actions.release();
+ return 0;
}
void PipelineHandlerRkISP1::stopDevice(Camera *camera)
diff --git a/src/libcamera/pipeline/rpi/common/meson.build b/src/libcamera/pipeline/rpi/common/meson.build
index 8fb7e823..b2b1a0a6 100644
--- a/src/libcamera/pipeline/rpi/common/meson.build
+++ b/src/libcamera/pipeline/rpi/common/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'delayed_controls.cpp',
'pipeline_base.cpp',
'rpi_stream.cpp',
diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build
index 386e2296..9b37c2f0 100644
--- a/src/libcamera/pipeline/rpi/vc4/meson.build
+++ b/src/libcamera/pipeline/rpi/vc4/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'vc4.cpp',
])
diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
index 4a89e35f..e5b6ef2b 100644
--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
@@ -802,7 +802,7 @@ void Vc4CameraData::ispInputDequeue(FrameBuffer *buffer)
void Vc4CameraData::ispOutputDequeue(FrameBuffer *buffer)
{
RPi::Stream *stream = nullptr;
- unsigned int index;
+ unsigned int index = 0;
if (!isRunning())
return;
diff --git a/src/libcamera/pipeline/simple/meson.build b/src/libcamera/pipeline/simple/meson.build
index 42b0896d..dda3de97 100644
--- a/src/libcamera/pipeline/simple/meson.build
+++ b/src/libcamera/pipeline/simple/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'simple.cpp',
])
diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
index eb36578e..81915573 100644
--- a/src/libcamera/pipeline/simple/simple.cpp
+++ b/src/libcamera/pipeline/simple/simple.cpp
@@ -163,7 +163,7 @@ LOG_DEFINE_CATEGORY(SimplePipeline)
* handler has no a priori knowledge of. The pipeline handler thus implements a
* heuristic to handle sharing of hardware resources in a generic fashion.
*
- * Two cameras are considered to be mutually exclusive if their share common
+ * Two cameras are considered to be mutually exclusive if they share common
* pads along the pipeline from the camera sensor to the video node. An entity
* can thus be used concurrently by multiple cameras, as long as pads are
* distinct.
@@ -198,7 +198,8 @@ namespace {
static const SimplePipelineInfo supportedDevices[] = {
{ "dcmipp", {}, false },
{ "imx7-csi", { { "pxp", 1 } }, false },
- { "j721e-csi2rx", {}, false },
+ { "intel-ipu6", {}, true },
+ { "j721e-csi2rx", {}, true },
{ "mtk-seninf", { { "mtk-mdp", 3 } }, false },
{ "mxc-isi", {}, false },
{ "qcom-camss", {}, true },
@@ -277,7 +278,7 @@ public:
std::map<PixelFormat, std::vector<const Configuration *>> formats_;
std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_;
- std::queue<std::map<unsigned int, FrameBuffer *>> conversionQueue_;
+ std::queue<std::map<const Stream *, FrameBuffer *>> conversionQueue_;
bool useConversion_;
std::unique_ptr<Converter> converter_;
@@ -362,13 +363,12 @@ private:
return static_cast<SimpleCameraData *>(camera->_d());
}
- std::vector<MediaEntity *> locateSensors();
+ std::vector<MediaEntity *> locateSensors(MediaDevice *media);
static int resetRoutingTable(V4L2Subdevice *subdev);
const MediaPad *acquirePipeline(SimpleCameraData *data);
void releasePipeline(SimpleCameraData *data);
- MediaDevice *media_;
std::map<const MediaEntity *, EntityData> entities_;
MediaDevice *converter_;
@@ -773,11 +773,8 @@ int SimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,
}
LOG(SimplePipeline, Debug)
- << "Link '" << source->entity()->name()
- << "':" << source->index()
- << " -> '" << sink->entity()->name()
- << "':" << sink->index()
- << " configured with format " << *format;
+ << "Link " << *link << ": configured with format "
+ << *format;
}
return 0;
@@ -836,7 +833,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
Request *request = buffer->request();
if (useConversion_ && !conversionQueue_.empty()) {
- const std::map<unsigned int, FrameBuffer *> &outputs =
+ const std::map<const Stream *, FrameBuffer *> &outputs =
conversionQueue_.front();
if (!outputs.empty()) {
FrameBuffer *outputBuffer = outputs.begin()->second;
@@ -1303,10 +1300,8 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
*/
if (data->useConversion_)
return data->converter_
- ? data->converter_->exportBuffers(data->streamIndex(stream),
- count, buffers)
- : data->swIsp_->exportBuffers(data->streamIndex(stream),
- count, buffers);
+ ? data->converter_->exportBuffers(stream, count, buffers)
+ : data->swIsp_->exportBuffers(stream, count, buffers);
else
return data->video_->exportBuffers(count, buffers);
}
@@ -1398,7 +1393,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
SimpleCameraData *data = cameraData(camera);
int ret;
- std::map<unsigned int, FrameBuffer *> buffers;
+ std::map<const Stream *, FrameBuffer *> buffers;
for (auto &[stream, buffer] : request->buffers()) {
/*
@@ -1407,7 +1402,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
* completion handler.
*/
if (data->useConversion_) {
- buffers.emplace(data->streamIndex(stream), buffer);
+ buffers.emplace(stream, buffer);
} else {
ret = data->video_->queueBuffer(buffer);
if (ret < 0)
@@ -1425,7 +1420,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
* Match and Setup
*/
-std::vector<MediaEntity *> SimplePipelineHandler::locateSensors()
+std::vector<MediaEntity *>
+SimplePipelineHandler::locateSensors(MediaDevice *media)
{
std::vector<MediaEntity *> entities;
@@ -1433,7 +1429,7 @@ std::vector<MediaEntity *> SimplePipelineHandler::locateSensors()
* Gather all the camera sensor entities based on the function they
* expose.
*/
- for (MediaEntity *entity : media_->entities()) {
+ for (MediaEntity *entity : media->entities()) {
if (entity->function() == MEDIA_ENT_F_CAM_SENSOR)
entities.push_back(entity);
}
@@ -1521,17 +1517,18 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
{
const SimplePipelineInfo *info = nullptr;
unsigned int numStreams = 1;
+ MediaDevice *media;
for (const SimplePipelineInfo &inf : supportedDevices) {
DeviceMatch dm(inf.driver);
- media_ = acquireMediaDevice(enumerator, dm);
- if (media_) {
+ media = acquireMediaDevice(enumerator, dm);
+ if (media) {
info = &inf;
break;
}
}
- if (!media_)
+ if (!media)
return false;
for (const auto &[name, streams] : info->converters) {
@@ -1546,12 +1543,14 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
swIspEnabled_ = info->swIspEnabled;
/* Locate the sensors. */
- std::vector<MediaEntity *> sensors = locateSensors();
+ std::vector<MediaEntity *> sensors = locateSensors(media);
if (sensors.empty()) {
- LOG(SimplePipeline, Error) << "No sensor found";
+ LOG(SimplePipeline, Info) << "No sensor found for " << media->deviceNode();
return false;
}
+ LOG(SimplePipeline, Debug) << "Sensor found for " << media->deviceNode();
+
/*
* Create one camera data instance for each sensor and gather all
* entities in all pipelines.
@@ -1615,8 +1614,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
if (subdev->caps().hasStreams()) {
/*
* Reset the routing table to its default state
- * to make sure entities are enumerate according
- * to the defaul routing configuration.
+ * to make sure entities are enumerated according
+ * to the default routing configuration.
*/
ret = resetRoutingTable(subdev.get());
if (ret) {
diff --git a/src/libcamera/pipeline/uvcvideo/meson.build b/src/libcamera/pipeline/uvcvideo/meson.build
index a3c2efd4..a3a91074 100644
--- a/src/libcamera/pipeline/uvcvideo/meson.build
+++ b/src/libcamera/pipeline/uvcvideo/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'uvcvideo.cpp',
])
diff --git a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
index 8a7409fc..6b32fa18 100644
--- a/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
+++ b/src/libcamera/pipeline/uvcvideo/uvcvideo.cpp
@@ -7,12 +7,15 @@
#include <algorithm>
#include <fstream>
-#include <iomanip>
+#include <map>
#include <math.h>
#include <memory>
-#include <tuple>
+#include <set>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
+#include <libcamera/base/mutex.h>
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
@@ -48,6 +51,7 @@ public:
const std::string &id() const { return id_; }
+ Mutex openLock_;
std::unique_ptr<V4L2VideoDevice> video_;
Stream stream_;
std::map<PixelFormat, std::vector<SizeRange>> formats_;
@@ -93,6 +97,9 @@ private:
const ControlValue &value);
int processControls(UVCCameraData *data, Request *request);
+ bool acquireDevice(Camera *camera) override;
+ void releaseDevice(Camera *camera) override;
+
UVCCameraData *cameraData(Camera *camera)
{
return static_cast<UVCCameraData *>(camera->_d());
@@ -158,9 +165,29 @@ CameraConfiguration::Status UVCCameraConfiguration::validate()
format.fourcc = data_->video_->toV4L2PixelFormat(cfg.pixelFormat);
format.size = cfg.size;
- int ret = data_->video_->tryFormat(&format);
- if (ret)
- return Invalid;
+ /*
+ * For power-consumption reasons video_ is closed when the camera is not
+ * acquired. Open it here if necessary.
+ */
+ {
+ bool opened = false;
+
+ MutexLocker locker(data_->openLock_);
+
+ if (!data_->video_->isOpen()) {
+ int ret = data_->video_->open();
+ if (ret)
+ return Invalid;
+
+ opened = true;
+ }
+
+ int ret = data_->video_->tryFormat(&format);
+ if (opened)
+ data_->video_->close();
+ if (ret)
+ return Invalid;
+ }
cfg.stride = format.planes[0].bpl;
cfg.frameSize = format.planes[0].size;
@@ -411,6 +438,23 @@ bool PipelineHandlerUVC::match(DeviceEnumerator *enumerator)
return true;
}
+bool PipelineHandlerUVC::acquireDevice(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+
+ MutexLocker locker(data->openLock_);
+
+ return data->video_->open() == 0;
+}
+
+void PipelineHandlerUVC::releaseDevice(Camera *camera)
+{
+ UVCCameraData *data = cameraData(camera);
+
+ MutexLocker locker(data->openLock_);
+ data->video_->close();
+}
+
int UVCCameraData::init(MediaDevice *media)
{
int ret;
@@ -512,6 +556,12 @@ int UVCCameraData::init(MediaDevice *media)
controlInfo_ = ControlInfoMap(std::move(ctrls), controls::controls);
+ /*
+ * Close to allow camera to go into runtime-suspend, video_ will be
+ * re-opened from acquireDevice() and validate().
+ */
+ video_->close();
+
return 0;
}
diff --git a/src/libcamera/pipeline/vimc/meson.build b/src/libcamera/pipeline/vimc/meson.build
index 290eefb5..868e2546 100644
--- a/src/libcamera/pipeline/vimc/meson.build
+++ b/src/libcamera/pipeline/vimc/meson.build
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'vimc.cpp',
])
diff --git a/src/libcamera/pipeline/vimc/vimc.cpp b/src/libcamera/pipeline/vimc/vimc.cpp
index 0ec9928e..325174b9 100644
--- a/src/libcamera/pipeline/vimc/vimc.cpp
+++ b/src/libcamera/pipeline/vimc/vimc.cpp
@@ -14,6 +14,7 @@
#include <linux/media-bus-format.h>
#include <linux/version.h>
+#include <libcamera/base/flags.h>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
@@ -21,6 +22,8 @@
#include <libcamera/control_ids.h>
#include <libcamera/controls.h>
#include <libcamera/formats.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
diff --git a/src/libcamera/pipeline_handler.cpp b/src/libcamera/pipeline_handler.cpp
index 5ea2ca78..e5940469 100644
--- a/src/libcamera/pipeline_handler.cpp
+++ b/src/libcamera/pipeline_handler.cpp
@@ -22,7 +22,6 @@
#include "libcamera/internal/camera.h"
#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/device_enumerator.h"
-#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/request.h"
#include "libcamera/internal/tracepoints.h"
@@ -157,26 +156,28 @@ MediaDevice *PipelineHandler::acquireMediaDevice(DeviceEnumerator *enumerator,
* Pipeline handlers shall not call this function directly as the Camera class
* handles access internally.
*
- * \context This function is \threadsafe.
+ * \context This function is called from the CameraManager thread.
*
* \return True if the pipeline handler was acquired, false if another process
* has already acquired it
* \sa release()
*/
-bool PipelineHandler::acquire()
+bool PipelineHandler::acquire(Camera *camera)
{
- MutexLocker locker(lock_);
-
- if (useCount_) {
- ++useCount_;
- return true;
+ if (useCount_ == 0) {
+ for (std::shared_ptr<MediaDevice> &media : mediaDevices_) {
+ if (!media->lock()) {
+ unlockMediaDevices();
+ return false;
+ }
+ }
}
- for (std::shared_ptr<MediaDevice> &media : mediaDevices_) {
- if (!media->lock()) {
+ if (!acquireDevice(camera)) {
+ if (useCount_ == 0)
unlockMediaDevices();
- return false;
- }
+
+ return false;
}
++useCount_;
@@ -195,29 +196,60 @@ bool PipelineHandler::acquire()
* Pipeline handlers shall not call this function directly as the Camera class
* handles access internally.
*
- * \context This function is \threadsafe.
+ * \context This function is called from the CameraManager thread.
*
* \sa acquire()
*/
void PipelineHandler::release(Camera *camera)
{
- MutexLocker locker(lock_);
-
ASSERT(useCount_);
- unlockMediaDevices();
-
releaseDevice(camera);
+ if (useCount_ == 1)
+ unlockMediaDevices();
+
--useCount_;
}
/**
+ * \brief Acquire resources associated with this camera
+ * \param[in] camera The camera for which to acquire resources
+ *
+ * Pipeline handlers may override this in order to get resources such as opening
+ * devices and allocating buffers when a camera is acquired.
+ *
+ * This is used by the uvcvideo pipeline handler to delay opening /dev/video#
+ * until the camera is acquired to avoid excess power consumption. The delayed
+ * opening of /dev/video# is a special case because the kernel uvcvideo driver
+ * powers on the USB device as soon as /dev/video# is opened. This behavior
+ * should *not* be copied by other pipeline handlers.
+ *
+ * \context This function is called from the CameraManager thread.
+ *
+ * \return True on success, false on failure
+ * \sa releaseDevice()
+ */
+bool PipelineHandler::acquireDevice([[maybe_unused]] Camera *camera)
+{
+ return true;
+}
+
+/**
* \brief Release resources associated with this camera
* \param[in] camera The camera for which to release resources
*
* Pipeline handlers may override this in order to perform cleanup operations
* when a camera is released, such as freeing memory.
+ *
+ * This is called once for every camera that is released. If there are resources
+ * shared by multiple cameras then the pipeline handler must take care to not
+ * release them until releaseDevice() has been called for all previously
+ * acquired cameras.
+ *
+ * \context This function is called from the CameraManager thread.
+ *
+ * \sa acquireDevice()
*/
void PipelineHandler::releaseDevice([[maybe_unused]] Camera *camera)
{
@@ -720,6 +752,13 @@ void PipelineHandler::disconnect()
*/
/**
+ * \fn PipelineHandler::cameraManager() const
+ * \brief Retrieve the CameraManager that this pipeline handler belongs to
+ * \context This function is \threadsafe.
+ * \return The CameraManager for this pipeline handler
+ */
+
+/**
* \class PipelineHandlerFactoryBase
* \brief Base class for pipeline handler factories
*
diff --git a/src/libcamera/process.cpp b/src/libcamera/process.cpp
index 86d27b2d..bc9833f4 100644
--- a/src/libcamera/process.cpp
+++ b/src/libcamera/process.cpp
@@ -10,7 +10,6 @@
#include <algorithm>
#include <dirent.h>
#include <fcntl.h>
-#include <iostream>
#include <list>
#include <signal.h>
#include <string.h>
@@ -189,7 +188,6 @@ const struct sigaction &ProcessManager::oldsa() const
return oldsa_;
}
-
/**
* \class Process
* \brief Process object
@@ -271,8 +269,8 @@ int Process::start(const std::string &path,
unsigned int len = args.size();
argv[0] = path.c_str();
for (unsigned int i = 0; i < len; i++)
- argv[i+1] = args[i].c_str();
- argv[len+1] = nullptr;
+ argv[i + 1] = args[i].c_str();
+ argv[len + 1] = nullptr;
execv(path.c_str(), (char **)argv);
diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in
deleted file mode 100644
index 8b274c38..00000000
--- a/src/libcamera/property_ids.cpp.in
+++ /dev/null
@@ -1,48 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2019, Google Inc.
- *
- * property_ids.cpp : Property ID list
- *
- * This file is auto-generated. Do not edit.
- */
-
-#include <libcamera/property_ids.h>
-
-/**
- * \file property_ids.h
- * \brief Camera property identifiers
- */
-
-namespace libcamera {
-
-/**
- * \brief Namespace for libcamera properties
- */
-namespace properties {
-
-${controls_doc}
-
-${vendor_controls_doc}
-
-#ifndef __DOXYGEN__
-/*
- * Keep the properties definitions hidden from doxygen as it incorrectly parses
- * them as functions.
- */
-${controls_def}
-
-${vendor_controls_def}
-
-#endif
-
-/**
- * \brief List of all supported libcamera properties
- */
-extern const ControlIdMap properties {
-${controls_map}
-};
-
-} /* namespace properties */
-
-} /* namespace libcamera */
diff --git a/src/libcamera/proxy/meson.build b/src/libcamera/proxy/meson.build
index 00ae5a8f..8bd1b135 100644
--- a/src/libcamera/proxy/meson.build
+++ b/src/libcamera/proxy/meson.build
@@ -13,7 +13,8 @@ foreach mojom : ipa_mojoms
'--libcamera_generate_proxy_cpp',
'--libcamera_output_path=@OUTPUT@',
'./' + '@INPUT@'
- ])
+ ],
+ env : py_build_env)
- libcamera_sources += proxy
+ libcamera_internal_sources += proxy
endforeach
diff --git a/src/libcamera/proxy/worker/meson.build b/src/libcamera/proxy/worker/meson.build
index aa4d9cd7..8c54a2e2 100644
--- a/src/libcamera/proxy/worker/meson.build
+++ b/src/libcamera/proxy/worker/meson.build
@@ -15,10 +15,10 @@ foreach mojom : ipa_mojoms
'--libcamera_generate_proxy_worker',
'--libcamera_output_path=@OUTPUT@',
'./' + '@INPUT@'
- ])
+ ],
+ env : py_build_env)
- proxy = executable(mojom['name'] + '_ipa_proxy',
- [worker, libcamera_generated_ipa_headers],
+ proxy = executable(mojom['name'] + '_ipa_proxy', worker,
install : true,
install_dir : proxy_install_dir,
dependencies : libcamera_private)
diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
index cfb451e9..8c56ed30 100644
--- a/src/libcamera/request.cpp
+++ b/src/libcamera/request.cpp
@@ -28,10 +28,17 @@
* \brief Describes a frame capture request to be processed by a camera
*/
+/**
+ * \internal
+ * \file libcamera/internal/request.h
+ * \brief Internal support for request handling
+ */
+
namespace libcamera {
LOG_DEFINE_CATEGORY(Request)
+#ifndef __DOXYGEN_PUBLIC__
/**
* \class Request::Private
* \brief Request private data
@@ -300,6 +307,7 @@ void Request::Private::timeout()
emitPrepareCompleted();
}
+#endif /* __DOXYGEN_PUBLIC__ */
/**
* \enum Request::Status
diff --git a/src/libcamera/sensor/camera_sensor.cpp b/src/libcamera/sensor/camera_sensor.cpp
index c6d7f801..4a990bb9 100644
--- a/src/libcamera/sensor/camera_sensor.cpp
+++ b/src/libcamera/sensor/camera_sensor.cpp
@@ -6,25 +6,23 @@
*/
#include "libcamera/internal/camera_sensor.h"
-#include "libcamera/internal/media_device.h"
#include <algorithm>
#include <float.h>
-#include <iomanip>
#include <limits.h>
#include <math.h>
#include <string.h>
+#include <libcamera/base/utils.h>
+
#include <libcamera/camera.h>
#include <libcamera/orientation.h>
#include <libcamera/property_ids.h>
-#include <libcamera/base/utils.h>
-
#include "libcamera/internal/bayer_format.h"
#include "libcamera/internal/camera_lens.h"
#include "libcamera/internal/camera_sensor_properties.h"
-#include "libcamera/internal/formats.h"
+#include "libcamera/internal/media_device.h"
#include "libcamera/internal/sysfs.h"
/**
diff --git a/src/libcamera/sensor/camera_sensor_properties.cpp b/src/libcamera/sensor/camera_sensor_properties.cpp
index b18524d8..6d4136d0 100644
--- a/src/libcamera/sensor/camera_sensor_properties.cpp
+++ b/src/libcamera/sensor/camera_sensor_properties.cpp
@@ -52,6 +52,15 @@ LOG_DEFINE_CATEGORY(CameraSensorProperties)
const CameraSensorProperties *CameraSensorProperties::get(const std::string &sensor)
{
static const std::map<std::string, const CameraSensorProperties> sensorProps = {
+ { "ar0144", {
+ .unitCellSize = { 3000, 3000 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeSolidColor, 1 },
+ { controls::draft::TestPatternModeColorBars, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ },
+ } },
{ "ar0521", {
.unitCellSize = { 2200, 2200 },
.testPatternModes = {
@@ -79,6 +88,16 @@ const CameraSensorProperties *CameraSensorProperties::get(const std::string &sen
*/
},
} },
+ { "imx214", {
+ .unitCellSize = { 1120, 1120 },
+ .testPatternModes = {
+ { controls::draft::TestPatternModeOff, 0 },
+ { controls::draft::TestPatternModeColorBars, 1 },
+ { controls::draft::TestPatternModeSolidColor, 2 },
+ { controls::draft::TestPatternModeColorBarsFadeToGray, 3 },
+ { controls::draft::TestPatternModePn9, 4 },
+ },
+ } },
{ "imx219", {
.unitCellSize = { 1120, 1120 },
.testPatternModes = {
diff --git a/src/libcamera/sensor/meson.build b/src/libcamera/sensor/meson.build
index bf4b131a..61234e95 100644
--- a/src/libcamera/sensor/meson.build
+++ b/src/libcamera/sensor/meson.build
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
-libcamera_sources += files([
+libcamera_internal_sources += files([
'camera_sensor.cpp',
'camera_sensor_properties.cpp',
])
diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp
index 809fbdaf..d9b61d37 100644
--- a/src/libcamera/shared_mem_object.cpp
+++ b/src/libcamera/shared_mem_object.cpp
@@ -10,12 +10,11 @@
#include "libcamera/internal/shared_mem_object.h"
-#include <stddef.h>
#include <stdint.h>
#include <sys/mman.h>
-#include <sys/syscall.h>
#include <sys/types.h>
-#include <unistd.h>
+
+#include <libcamera/base/memfd.h>
/**
* \file shared_mem_object.cpp
@@ -58,22 +57,12 @@ SharedMem::SharedMem() = default;
*/
SharedMem::SharedMem(const std::string &name, std::size_t size)
{
-#if HAVE_MEMFD_CREATE
- int fd = memfd_create(name.c_str(), MFD_CLOEXEC);
-#else
- int fd = syscall(SYS_memfd_create, name.c_str(), MFD_CLOEXEC);
-#endif
- if (fd < 0)
- return;
-
- fd_ = SharedFD(std::move(fd));
- if (!fd_.isValid())
+ UniqueFD memfd = MemFd::create(name.c_str(), size,
+ MemFd::Seal::Shrink | MemFd::Seal::Grow);
+ if (!memfd.isValid())
return;
- if (ftruncate(fd_.get(), size) < 0) {
- fd_ = SharedFD();
- return;
- }
+ fd_ = SharedFD(std::move(memfd));
void *mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
fd_.get(), 0);
diff --git a/src/libcamera/software_isp/TODO b/src/libcamera/software_isp/TODO
index 6bdc5905..9978afc0 100644
--- a/src/libcamera/software_isp/TODO
+++ b/src/libcamera/software_isp/TODO
@@ -1,22 +1,3 @@
-1. Setting F_SEAL_SHRINK and F_SEAL_GROW after ftruncate()
-
->> SharedMem::SharedMem(const std::string &name, std::size_t size)
->> : name_(name), size_(size), mem_(nullptr)
->>
->> ...
->>
->> if (ftruncate(fd_.get(), size_) < 0)
->> return;
->
-> Should we set the GROW and SHRINK seals (in a separate patch) ?
-
-Yes, this can be done.
-Setting F_SEAL_SHRINK and F_SEAL_GROW after the ftruncate() call above could catch
-some potential errors related to improper access to the shared memory allocated by
-the SharedMemObject.
-
----
-
2. Reconsider stats sharing
>>> +void SwStatsCpu::finishFrame(void)
diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
index f8d2677d..8a757fe9 100644
--- a/src/libcamera/software_isp/debayer_cpu.cpp
+++ b/src/libcamera/software_isp/debayer_cpu.cpp
@@ -12,8 +12,11 @@
#include "debayer_cpu.h"
#include <stdlib.h>
+#include <sys/ioctl.h>
#include <time.h>
+#include <linux/dma-buf.h>
+
#include <libcamera/formats.h>
#include "libcamera/internal/bayer_format.h"
@@ -49,16 +52,9 @@ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
/* Initialize color lookup tables */
for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++)
red_[i] = green_[i] = blue_[i] = i;
-
- for (unsigned int i = 0; i < kMaxLineBuffers; i++)
- lineBuffers_[i] = nullptr;
}
-DebayerCpu::~DebayerCpu()
-{
- for (unsigned int i = 0; i < kMaxLineBuffers; i++)
- free(lineBuffers_[i]);
-}
+DebayerCpu::~DebayerCpu() = default;
#define DECLARE_SRC_POINTERS(pixel_t) \
const pixel_t *prev = (const pixel_t *)src[0] + xShift_; \
@@ -526,13 +522,10 @@ int DebayerCpu::configure(const StreamConfiguration &inputCfg,
lineBufferPadding_ = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
lineBufferLength_ = window_.width * inputConfig_.bpp / 8 +
2 * lineBufferPadding_;
- for (unsigned int i = 0;
- i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_;
- i++) {
- free(lineBuffers_[i]);
- lineBuffers_[i] = (uint8_t *)malloc(lineBufferLength_);
- if (!lineBuffers_[i])
- return -ENOMEM;
+
+ if (enableInputMemcpy_) {
+ for (unsigned int i = 0; i <= inputConfig_.patternSize.height; i++)
+ lineBuffers_[i].resize(lineBufferLength_);
}
measuredFrames_ = 0;
@@ -587,9 +580,10 @@ void DebayerCpu::setupInputMemcpy(const uint8_t *linePointers[])
return;
for (unsigned int i = 0; i < patternHeight; i++) {
- memcpy(lineBuffers_[i], linePointers[i + 1] - lineBufferPadding_,
+ memcpy(lineBuffers_[i].data(),
+ linePointers[i + 1] - lineBufferPadding_,
lineBufferLength_);
- linePointers[i + 1] = lineBuffers_[i] + lineBufferPadding_;
+ linePointers[i + 1] = lineBuffers_[i].data() + lineBufferPadding_;
}
/* Point lineBufferIndex_ to first unused lineBuffer */
@@ -614,9 +608,11 @@ void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[])
if (!enableInputMemcpy_)
return;
- memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - lineBufferPadding_,
+ memcpy(lineBuffers_[lineBufferIndex_].data(),
+ linePointers[patternHeight] - lineBufferPadding_,
lineBufferLength_);
- linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + lineBufferPadding_;
+ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_].data()
+ + lineBufferPadding_;
lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);
}
@@ -725,12 +721,33 @@ void DebayerCpu::process4(const uint8_t *src, uint8_t *dst)
}
}
-static inline int64_t timeDiff(timespec &after, timespec &before)
+namespace {
+
+void syncBufferForCPU(FrameBuffer *buffer, uint64_t syncFlags)
+{
+ for (const FrameBuffer::Plane &plane : buffer->planes()) {
+ const int fd = plane.fd.get();
+ struct dma_buf_sync sync = { syncFlags };
+ int ret;
+
+ ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
+ if (ret < 0) {
+ ret = errno;
+ LOG(Debayer, Error)
+ << "Syncing buffer FD " << fd << " with flags "
+ << syncFlags << " failed: " << strerror(ret);
+ }
+ }
+}
+
+inline int64_t timeDiff(timespec &after, timespec &before)
{
return (after.tv_sec - before.tv_sec) * 1000000000LL +
(int64_t)after.tv_nsec - (int64_t)before.tv_nsec;
}
+} /* namespace */
+
void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
{
timespec frameStartTime;
@@ -740,6 +757,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
}
+ syncBufferForCPU(input, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ);
+ syncBufferForCPU(output, DMA_BUF_SYNC_START | DMA_BUF_SYNC_WRITE);
+
green_ = params.green;
red_ = swapRedBlueGains_ ? params.blue : params.red;
blue_ = swapRedBlueGains_ ? params.red : params.blue;
@@ -767,6 +787,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
metadata.planes()[0].bytesused = out.planes()[0].size();
+ syncBufferForCPU(output, DMA_BUF_SYNC_END | DMA_BUF_SYNC_WRITE);
+ syncBufferForCPU(input, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ);
+
/* Measure before emitting signals */
if (measuredFrames_ < DebayerCpu::kLastFrameToMeasure &&
++measuredFrames_ > DebayerCpu::kFramesToSkip) {
diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
index 1dac6435..8237a64b 100644
--- a/src/libcamera/software_isp/debayer_cpu.h
+++ b/src/libcamera/software_isp/debayer_cpu.h
@@ -146,7 +146,7 @@ private:
DebayerInputConfig inputConfig_;
DebayerOutputConfig outputConfig_;
std::unique_ptr<SwStatsCpu> stats_;
- uint8_t *lineBuffers_[kMaxLineBuffers];
+ std::vector<uint8_t> lineBuffers_[kMaxLineBuffers];
unsigned int lineBufferLength_;
unsigned int lineBufferPadding_;
unsigned int lineBufferIndex_;
diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
index f7c66e28..aac7eda7 100644
--- a/src/libcamera/software_isp/meson.build
+++ b/src/libcamera/software_isp/meson.build
@@ -7,7 +7,7 @@ if not softisp_enabled
subdir_done()
endif
-libcamera_sources += files([
+libcamera_internal_sources += files([
'debayer.cpp',
'debayer_cpu.cpp',
'software_isp.cpp',
diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
index 20fb6f48..1140372c 100644
--- a/src/libcamera/software_isp/software_isp.cpp
+++ b/src/libcamera/software_isp/software_isp.cpp
@@ -121,9 +121,8 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
* The API tuning file is made from the sensor name. If the tuning file
* isn't found, fall back to the 'uncalibrated' file.
*/
- std::string ipaTuningFile = ipa_->configurationFile(sensor->model() + ".yaml");
- if (ipaTuningFile.empty())
- ipaTuningFile = ipa_->configurationFile("uncalibrated.yaml");
+ std::string ipaTuningFile =
+ ipa_->configurationFile(sensor->model() + ".yaml", "uncalibrated.yaml");
int ret = ipa_->init(IPASettings{ ipaTuningFile, sensor->model() },
debayer_->getStatsFD(),
@@ -241,19 +240,19 @@ int SoftwareIsp::configure(const StreamConfiguration &inputCfg,
/**
* \brief Export the buffers from the Software ISP
- * \param[in] output Output stream index exporting the buffers
+ * \param[in] stream Output stream exporting the buffers
* \param[in] count Number of buffers to allocate
* \param[out] buffers Vector to store the allocated buffers
* \return The number of allocated buffers on success or a negative error code
* otherwise
*/
-int SoftwareIsp::exportBuffers(unsigned int output, unsigned int count,
+int SoftwareIsp::exportBuffers(const Stream *stream, unsigned int count,
std::vector<std::unique_ptr<FrameBuffer>> *buffers)
{
ASSERT(debayer_ != nullptr);
/* single output for now */
- if (output >= 1)
+ if (stream == nullptr)
return -EINVAL;
for (unsigned int i = 0; i < count; i++) {
@@ -280,35 +279,29 @@ int SoftwareIsp::exportBuffers(unsigned int output, unsigned int count,
/**
* \brief Queue buffers to Software ISP
* \param[in] input The input framebuffer
- * \param[in] outputs The container holding the output stream indexes and
+ * \param[in] outputs The container holding the output stream pointers and
* their respective frame buffer outputs
* \return 0 on success, a negative errno on failure
*/
int SoftwareIsp::queueBuffers(FrameBuffer *input,
- const std::map<unsigned int, FrameBuffer *> &outputs)
+ const std::map<const Stream *, FrameBuffer *> &outputs)
{
- unsigned int mask = 0;
-
/*
* Validate the outputs as a sanity check: at least one output is
- * required, all outputs must reference a valid stream and no two
- * outputs can reference the same stream.
+ * required, all outputs must reference a valid stream.
*/
if (outputs.empty())
return -EINVAL;
- for (auto [index, buffer] : outputs) {
+ for (auto [stream, buffer] : outputs) {
if (!buffer)
return -EINVAL;
- if (index >= 1) /* only single stream atm */
- return -EINVAL;
- if (mask & (1 << index))
+ if (outputs.size() != 1) /* only single stream atm */
return -EINVAL;
-
- mask |= 1 << index;
}
- process(input, outputs.at(0));
+ for (auto iter = outputs.begin(); iter != outputs.end(); iter++)
+ process(input, iter->second);
return 0;
}
diff --git a/src/libcamera/stream.cpp b/src/libcamera/stream.cpp
index 053cc4b8..1f75dbbc 100644
--- a/src/libcamera/stream.cpp
+++ b/src/libcamera/stream.cpp
@@ -9,15 +9,15 @@
#include <algorithm>
#include <array>
-#include <iomanip>
#include <limits.h>
-#include <sstream>
-
-#include <libcamera/request.h>
+#include <ostream>
+#include <string>
+#include <vector>
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/request.h>
/**
* \file stream.h
diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index 4a2048cf..68add4f2 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -8,8 +8,6 @@
#include "libcamera/internal/v4l2_device.h"
#include <fcntl.h>
-#include <iomanip>
-#include <limits.h>
#include <map>
#include <stdlib.h>
#include <string.h>
diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp
index 70568335..eb9ac222 100644
--- a/src/libcamera/v4l2_pixelformat.cpp
+++ b/src/libcamera/v4l2_pixelformat.cpp
@@ -139,6 +139,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
{ formats::R10_CSI2P, "10-bit Greyscale Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y12),
{ formats::R12, "12-bit Greyscale" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_Y12P),
+ { formats::R12_CSI2P, "12-bit Greyscale Packed" } },
{ V4L2PixelFormat(V4L2_PIX_FMT_Y16),
{ formats::R16, "16-bit Greyscale" } },
diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
index 82824433..9f2ec479 100644
--- a/src/libcamera/v4l2_subdevice.cpp
+++ b/src/libcamera/v4l2_subdevice.cpp
@@ -8,7 +8,6 @@
#include "libcamera/internal/v4l2_subdevice.h"
#include <fcntl.h>
-#include <iomanip>
#include <regex>
#include <sstream>
#include <string.h>
@@ -18,11 +17,11 @@
#include <linux/media-bus-format.h>
#include <linux/v4l2-subdev.h>
-#include <libcamera/geometry.h>
-
#include <libcamera/base/log.h>
#include <libcamera/base/utils.h>
+#include <libcamera/geometry.h>
+
#include "libcamera/internal/formats.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_object.h"
diff --git a/src/libcamera/v4l2_videodevice.cpp b/src/libcamera/v4l2_videodevice.cpp
index 4947aa3d..93cb1697 100644
--- a/src/libcamera/v4l2_videodevice.cpp
+++ b/src/libcamera/v4l2_videodevice.cpp
@@ -10,7 +10,6 @@
#include <algorithm>
#include <array>
#include <fcntl.h>
-#include <iomanip>
#include <sstream>
#include <string.h>
#include <sys/ioctl.h>
@@ -803,12 +802,19 @@ std::string V4L2VideoDevice::logPrefix() const
*/
int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format)
{
- if (caps_.isMeta())
- return getFormatMeta(format);
- else if (caps_.isMultiplanar())
- return getFormatMultiplane(format);
- else
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
return getFormatSingleplane(format);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ return getFormatMultiplane(format);
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ return getFormatMeta(format);
+ default:
+ return -EINVAL;
+ }
}
/**
@@ -823,12 +829,19 @@ int V4L2VideoDevice::getFormat(V4L2DeviceFormat *format)
*/
int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format)
{
- if (caps_.isMeta())
- return trySetFormatMeta(format, false);
- else if (caps_.isMultiplanar())
- return trySetFormatMultiplane(format, false);
- else
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
return trySetFormatSingleplane(format, false);
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ return trySetFormatMultiplane(format, false);
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ return trySetFormatMeta(format, false);
+ default:
+ return -EINVAL;
+ }
}
/**
@@ -842,13 +855,25 @@ int V4L2VideoDevice::tryFormat(V4L2DeviceFormat *format)
*/
int V4L2VideoDevice::setFormat(V4L2DeviceFormat *format)
{
- int ret = 0;
- if (caps_.isMeta())
- ret = trySetFormatMeta(format, true);
- else if (caps_.isMultiplanar())
- ret = trySetFormatMultiplane(format, true);
- else
+ int ret;
+
+ switch (bufferType_) {
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
ret = trySetFormatSingleplane(format, true);
+ break;
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+ ret = trySetFormatMultiplane(format, true);
+ break;
+ case V4L2_BUF_TYPE_META_CAPTURE:
+ case V4L2_BUF_TYPE_META_OUTPUT:
+ ret = trySetFormatMeta(format, true);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
/* Cache the set format on success. */
if (ret)
@@ -1815,7 +1840,7 @@ FrameBuffer *V4L2VideoDevice::dequeueBuffer()
* Detect kernel drivers which do not reset the sequence number to zero
* on stream start.
*/
- if (!firstFrame_) {
+ if (!firstFrame_.has_value()) {
if (buf.sequence)
LOG(V4L2, Info)
<< "Zero sequence expected for first frame (got "
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
index 025006bc..4784f2dc 100644
--- a/src/libcamera/yaml_parser.cpp
+++ b/src/libcamera/yaml_parser.cpp
@@ -18,7 +18,7 @@
#include <yaml.h>
/**
- * \file libcamera/internal/yaml_parser.h
+ * \file yaml_parser.h
* \brief A YAML parser helper
*/
@@ -38,12 +38,12 @@ static const YamlObject empty;
* \brief A class representing the tree structure of the YAML content
*
* The YamlObject class represents the tree structure of YAML content. A
- * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
- * leaf.
+ * YamlObject can be empty, a dictionary or list of YamlObjects, or a value if a
+ * tree leaf.
*/
YamlObject::YamlObject()
- : type_(Type::Value)
+ : type_(Type::Empty)
{
}
@@ -71,6 +71,20 @@ YamlObject::~YamlObject() = default;
*/
/**
+ * \fn YamlObject::isEmpty()
+ * \brief Return whether the YamlObject is an empty
+ *
+ * \return True if the YamlObject is empty, false otherwise
+ */
+
+/**
+ * \fn YamlObject::operator bool()
+ * \brief Return whether the YamlObject is a non-empty
+ *
+ * \return False if the YamlObject is empty, true otherwise
+ */
+
+/**
* \fn YamlObject::size()
* \brief Retrieve the number of elements in a dictionary or list YamlObject
*
@@ -443,7 +457,8 @@ template std::optional<std::vector<Size>> YamlObject::getList<Size>() const;
*
* This function retrieves an element of the YamlObject. Only YamlObject
* instances of List type associate elements with index, calling this function
- * on other types of instances is invalid and results in undefined behaviour.
+ * on other types of instances or with an invalid index results in an empty
+ * object.
*
* \return The YamlObject as an element of the list
*/
@@ -480,8 +495,8 @@ bool YamlObject::contains(const std::string &key) const
*
* This function retrieve a member of a YamlObject by name. Only YamlObject
* instances of Dictionary type associate elements with names, calling this
- * function on other types of instances is invalid and results in undefined
- * behaviour.
+ * function on other types of instances or with a nonexistent key results in an
+ * empty object.
*
* \return The YamlObject corresponding to the \a key member
*/
diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
index c1723b44..22d8c4da 100644
--- a/src/py/cam/cam_qt.py
+++ b/src/py/cam/cam_qt.py
@@ -2,7 +2,7 @@
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
from helpers import mfb_to_rgb
-from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt6 import QtCore, QtGui, QtWidgets
import libcamera as libcam
import libcamera.utils
import sys
@@ -63,10 +63,10 @@ class QtRenderer:
self.buf_mmap_map = buf_mmap_map
def run(self):
- camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Read)
+ camnotif = QtCore.QSocketNotifier(self.cm.event_fd, QtCore.QSocketNotifier.Type.Read)
camnotif.activated.connect(lambda _: self.readcam())
- keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)
+ keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read)
keynotif.activated.connect(lambda _: self.readkey())
print('Capturing...')
diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
index 6cfbd347..35b4b06b 100644
--- a/src/py/cam/cam_qtgl.py
+++ b/src/py/cam/cam_qtgl.py
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
-from PyQt5 import QtCore, QtWidgets
-from PyQt5.QtCore import Qt
+from PyQt6 import QtCore, QtWidgets
+from PyQt6.QtCore import Qt
import math
import os
@@ -142,10 +142,10 @@ class QtRenderer:
self.window = window
def run(self):
- camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Read)
+ camnotif = QtCore.QSocketNotifier(self.state.cm.event_fd, QtCore.QSocketNotifier.Type.Read)
camnotif.activated.connect(lambda _: self.readcam())
- keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Read)
+ keynotif = QtCore.QSocketNotifier(sys.stdin.fileno(), QtCore.QSocketNotifier.Type.Read)
keynotif.activated.connect(lambda _: self.readkey())
print('Capturing...')
@@ -175,8 +175,8 @@ class MainWindow(QtWidgets.QWidget):
def __init__(self, state):
super().__init__()
- self.setAttribute(Qt.WA_PaintOnScreen)
- self.setAttribute(Qt.WA_NativeWindow)
+ self.setAttribute(Qt.WidgetAttribute.WA_PaintOnScreen)
+ self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow)
self.state = state
diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py
index 8efbf95b..cf09c146 100755
--- a/src/py/libcamera/gen-py-controls.py
+++ b/src/py/libcamera/gen-py-controls.py
@@ -4,10 +4,12 @@
# Generate Python bindings controls from YAML
import argparse
-import string
+import jinja2
import sys
import yaml
+from controls import Control
+
def find_common_prefix(strings):
prefix = strings[0]
@@ -21,70 +23,39 @@ def find_common_prefix(strings):
return prefix
-def generate_py(controls, mode):
- out = ''
-
- vendors_class_def = []
- vendor_defs = []
- vendors = []
- for vendor, ctrl_list in controls.items():
- for ctrls in ctrl_list:
- name, ctrl = ctrls.popitem()
-
- if vendor not in vendors and vendor != 'libcamera':
- vendor_mode_str = f'{vendor.capitalize()}{mode.capitalize()}'
- vendors_class_def.append('class Py{}\n{{\n}};\n'.format(vendor_mode_str))
- vendor_defs.append('\tauto {} = py::class_<Py{}>(controls, \"{}\");'.format(vendor, vendor_mode_str, vendor))
- vendors.append(vendor)
-
- if vendor != 'libcamera':
- ns = 'libcamera::{}::{}::'.format(mode, vendor)
- container = vendor
- else:
- ns = 'libcamera::{}::'.format(mode)
- container = 'controls'
-
- out += f'\t{container}.def_readonly_static("{name}", static_cast<const libcamera::ControlId *>(&{ns}{name}));\n\n'
-
- enum = ctrl.get('enum')
- if not enum:
- continue
-
- cpp_enum = name + 'Enum'
-
- out += '\tpy::enum_<{}{}>({}, \"{}\")\n'.format(ns, cpp_enum, container, cpp_enum)
-
- if mode == 'controls':
- # Adjustments for controls
- if name == 'LensShadingMapMode':
- prefix = 'LensShadingMapMode'
- else:
- prefix = find_common_prefix([e['name'] for e in enum])
- else:
- # Adjustments for properties
- prefix = find_common_prefix([e['name'] for e in enum])
-
- for entry in enum:
- cpp_enum = entry['name']
- py_enum = entry['name'][len(prefix):]
-
- out += '\t\t.value(\"{}\", {}{})\n'.format(py_enum, ns, cpp_enum)
-
- out += '\t;\n\n'
-
- return {'controls': out,
- 'vendors_class_def': '\n'.join(vendors_class_def),
- 'vendors_defs': '\n'.join(vendor_defs)}
+def extend_control(ctrl, mode):
+ if ctrl.vendor != 'libcamera':
+ ctrl.klass = ctrl.vendor
+ ctrl.namespace = f'{ctrl.vendor}::'
+ else:
+ ctrl.klass = mode
+ ctrl.namespace = ''
+
+ if not ctrl.is_enum:
+ return ctrl
+
+ if mode == 'controls':
+ # Adjustments for controls
+ if ctrl.name == 'LensShadingMapMode':
+ prefix = 'LensShadingMapMode'
+ else:
+ prefix = find_common_prefix([e.name for e in ctrl.enum_values])
+ else:
+ # Adjustments for properties
+ prefix = find_common_prefix([e.name for e in ctrl.enum_values])
+ for enum in ctrl.enum_values:
+ enum.py_name = enum.name[len(prefix):]
-def fill_template(template, data):
- template = open(template, 'rb').read()
- template = template.decode('utf-8')
- template = string.Template(template)
- return template.substitute(data)
+ return ctrl
def main(argv):
+ headers = {
+ 'controls': 'control_ids.h',
+ 'properties': 'property_ids.h',
+ }
+
# Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('--mode', '-m', type=str, required=True,
@@ -97,26 +68,41 @@ def main(argv):
help='Input file name.')
args = parser.parse_args(argv[1:])
- if args.mode not in ['controls', 'properties']:
+ if not headers.get(args.mode):
print(f'Invalid mode option "{args.mode}"', file=sys.stderr)
return -1
- controls = {}
+ controls = []
+ vendors = []
+
for input in args.input:
- data = open(input, 'rb').read()
- vendor = yaml.safe_load(data)['vendor']
- controls[vendor] = yaml.safe_load(data)['controls']
+ data = yaml.safe_load(open(input, 'rb').read())
+
+ vendor = data['vendor']
+ if vendor != 'libcamera':
+ vendors.append(vendor)
+
+ for ctrl in data['controls']:
+ ctrl = Control(*ctrl.popitem(), vendor)
+ controls.append(extend_control(ctrl, args.mode))
- data = generate_py(controls, args.mode)
+ data = {
+ 'mode': args.mode,
+ 'header': headers[args.mode],
+ 'vendors': vendors,
+ 'controls': controls,
+ }
- data = fill_template(args.template, data)
+ env = jinja2.Environment()
+ template = env.from_string(open(args.template, 'r', encoding='utf-8').read())
+ string = template.render(data)
if args.output:
- output = open(args.output, 'wb')
- output.write(data.encode('utf-8'))
+ output = open(args.output, 'w', encoding='utf-8')
+ output.write(string)
output.close()
else:
- sys.stdout.write(data)
+ sys.stdout.write(string)
return 0
diff --git a/src/py/libcamera/meson.build b/src/py/libcamera/meson.build
index 4807ca7d..596a203c 100644
--- a/src/py/libcamera/meson.build
+++ b/src/py/libcamera/meson.build
@@ -26,37 +26,24 @@ pycamera_sources = files([
'py_transform.cpp',
])
-# Generate controls
+# Generate controls and properties
-gen_py_controls_input_files = []
gen_py_controls_template = files('py_controls_generated.cpp.in')
-
gen_py_controls = files('gen-py-controls.py')
-foreach file : controls_files
- gen_py_controls_input_files += files('../../libcamera/' + file)
-endforeach
-
pycamera_sources += custom_target('py_gen_controls',
- input : gen_py_controls_input_files,
+ input : controls_files,
output : ['py_controls_generated.cpp'],
command : [gen_py_controls, '--mode', 'controls', '-o', '@OUTPUT@',
- '-t', gen_py_controls_template, '@INPUT@'])
-
-# Generate properties
-
-gen_py_property_enums_input_files = []
-gen_py_properties_template = files('py_properties_generated.cpp.in')
-
-foreach file : properties_files
- gen_py_property_enums_input_files += files('../../libcamera/' + file)
-endforeach
+ '-t', gen_py_controls_template, '@INPUT@'],
+ env : py_build_env)
pycamera_sources += custom_target('py_gen_properties',
- input : gen_py_property_enums_input_files,
+ input : properties_files,
output : ['py_properties_generated.cpp'],
command : [gen_py_controls, '--mode', 'properties', '-o', '@OUTPUT@',
- '-t', gen_py_properties_template, '@INPUT@'])
+ '-t', gen_py_controls_template, '@INPUT@'],
+ env : py_build_env)
# Generate formats
diff --git a/src/py/libcamera/py_color_space.cpp b/src/py/libcamera/py_color_space.cpp
index 5201121a..fd5a5dab 100644
--- a/src/py/libcamera/py_color_space.cpp
+++ b/src/py/libcamera/py_color_space.cpp
@@ -12,6 +12,8 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
+#include "py_main.h"
+
namespace py = pybind11;
using namespace libcamera;
diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in
index 8d282ce5..22a132d1 100644
--- a/src/py/libcamera/py_controls_generated.cpp.in
+++ b/src/py/libcamera/py_controls_generated.cpp.in
@@ -2,27 +2,46 @@
/*
* Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*
- * Python bindings - Auto-generated controls
+ * Python bindings - Auto-generated {{mode}}
*
* This file is auto-generated. Do not edit.
*/
-#include <libcamera/control_ids.h>
+#include <libcamera/{{header}}>
#include <pybind11/pybind11.h>
+#include "py_main.h"
+
namespace py = pybind11;
-class PyControls
+class Py{{mode|capitalize}}
{
};
-${vendors_class_def}
+{% for vendor in vendors -%}
+class Py{{vendor|capitalize}}{{mode|capitalize}}
+{
+};
-void init_py_controls_generated(py::module& m)
+{% endfor -%}
+
+void init_py_{{mode}}_generated(py::module& m)
{
- auto controls = py::class_<PyControls>(m, "controls");
-${vendors_defs}
+ auto {{mode}} = py::class_<Py{{mode|capitalize}}>(m, "{{mode}}");
+{%- for vendor in vendors %}
+ auto {{vendor}} = py::class_<Py{{vendor|capitalize}}{{mode|capitalize}}>({{mode}}, "{{vendor}}");
+{%- endfor %}
+
+{% for ctrl in controls %}
+ {{ctrl.klass}}.def_readonly_static("{{ctrl.name}}", static_cast<const libcamera::ControlId *>(&libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}));
+{%- if ctrl.is_enum %}
-${controls}
+ py::enum_<libcamera::{{mode}}::{{ctrl.namespace}}{{ctrl.name}}Enum>({{ctrl.klass}}, "{{ctrl.name}}Enum")
+{%- for enum in ctrl.enum_values %}
+ .value("{{enum.py_name}}", libcamera::{{mode}}::{{ctrl.namespace}}{{enum.name}})
+{%- endfor %}
+ ;
+{%- endif %}
+{% endfor -%}
}
diff --git a/src/py/libcamera/py_enums.cpp b/src/py/libcamera/py_enums.cpp
index e25689c6..ca6aeb86 100644
--- a/src/py/libcamera/py_enums.cpp
+++ b/src/py/libcamera/py_enums.cpp
@@ -9,6 +9,8 @@
#include <pybind11/pybind11.h>
+#include "py_main.h"
+
namespace py = pybind11;
using namespace libcamera;
diff --git a/src/py/libcamera/py_formats_generated.cpp.in b/src/py/libcamera/py_formats_generated.cpp.in
index a3f7f94d..c5fb9063 100644
--- a/src/py/libcamera/py_formats_generated.cpp.in
+++ b/src/py/libcamera/py_formats_generated.cpp.in
@@ -11,6 +11,8 @@
#include <pybind11/pybind11.h>
+#include "py_main.h"
+
namespace py = pybind11;
class PyFormats
diff --git a/src/py/libcamera/py_geometry.cpp b/src/py/libcamera/py_geometry.cpp
index 5c2aeac4..c7e30360 100644
--- a/src/py/libcamera/py_geometry.cpp
+++ b/src/py/libcamera/py_geometry.cpp
@@ -14,6 +14,8 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
+#include "py_main.h"
+
namespace py = pybind11;
using namespace libcamera;
diff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp
index bce08218..983b76f6 100644
--- a/src/py/libcamera/py_main.cpp
+++ b/src/py/libcamera/py_main.cpp
@@ -85,14 +85,6 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, PyCameraSmartPtr<T>)
*/
static std::weak_ptr<PyCameraManager> gCameraManager;
-void init_py_color_space(py::module &m);
-void init_py_controls_generated(py::module &m);
-void init_py_enums(py::module &m);
-void init_py_formats_generated(py::module &m);
-void init_py_geometry(py::module &m);
-void init_py_properties_generated(py::module &m);
-void init_py_transform(py::module &m);
-
PYBIND11_MODULE(_libcamera, m)
{
init_py_enums(m);
@@ -412,7 +404,8 @@ PYBIND11_MODULE(_libcamera, m)
.def("__repr__", [](const ControlId &self) {
return py::str("libcamera.ControlId({}, {}, {})")
.format(self.id(), self.name(), self.type());
- });
+ })
+ .def("enumerators", &ControlId::enumerators);
pyControlInfo
.def_property_readonly("min", [](const ControlInfo &self) {
diff --git a/src/py/libcamera/py_main.h b/src/py/libcamera/py_main.h
index 5bb5f2d1..4d594326 100644
--- a/src/py/libcamera/py_main.h
+++ b/src/py/libcamera/py_main.h
@@ -7,8 +7,18 @@
#include <libcamera/base/log.h>
+#include <pybind11/pybind11.h>
+
namespace libcamera {
LOG_DECLARE_CATEGORY(Python)
}
+
+void init_py_color_space(pybind11::module &m);
+void init_py_controls_generated(pybind11::module &m);
+void init_py_enums(pybind11::module &m);
+void init_py_formats_generated(pybind11::module &m);
+void init_py_geometry(pybind11::module &m);
+void init_py_properties_generated(pybind11::module &m);
+void init_py_transform(pybind11::module &m);
diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in
deleted file mode 100644
index e3802b81..00000000
--- a/src/py/libcamera/py_properties_generated.cpp.in
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2022, Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
- *
- * Python bindings - Auto-generated properties
- *
- * This file is auto-generated. Do not edit.
- */
-
-#include <libcamera/property_ids.h>
-
-#include <pybind11/pybind11.h>
-
-namespace py = pybind11;
-
-class PyProperties
-{
-};
-
-${vendors_class_def}
-
-void init_py_properties_generated(py::module& m)
-{
- auto controls = py::class_<PyProperties>(m, "properties");
-${vendors_defs}
-
-${controls}
-}
diff --git a/src/py/libcamera/py_transform.cpp b/src/py/libcamera/py_transform.cpp
index f3a0bfaf..768260ff 100644
--- a/src/py/libcamera/py_transform.cpp
+++ b/src/py/libcamera/py_transform.cpp
@@ -12,6 +12,8 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
+#include "py_main.h"
+
namespace py = pybind11;
using namespace libcamera;
diff --git a/src/v4l2/v4l2_camera.cpp b/src/v4l2/v4l2_camera.cpp
index 0f3b862f..94d138cd 100644
--- a/src/v4l2/v4l2_camera.cpp
+++ b/src/v4l2/v4l2_camera.cpp
@@ -12,13 +12,15 @@
#include <libcamera/base/log.h>
+#include <libcamera/control_ids.h>
+
using namespace libcamera;
LOG_DECLARE_CATEGORY(V4L2Compat)
V4L2Camera::V4L2Camera(std::shared_ptr<Camera> camera)
- : camera_(camera), isRunning_(false), bufferAllocator_(nullptr),
- efd_(-1), bufferAvailableCount_(0)
+ : camera_(camera), controls_(controls::controls), isRunning_(false),
+ bufferAllocator_(nullptr), efd_(-1), bufferAvailableCount_(0)
{
camera_->requestCompleted.connect(this, &V4L2Camera::requestComplete);
}
@@ -202,10 +204,12 @@ int V4L2Camera::streamOn()
if (isRunning_)
return 0;
- int ret = camera_->start();
+ int ret = camera_->start(&controls_);
if (ret < 0)
return ret == -EACCES ? -EBUSY : ret;
+ controls_.clear();
+
isRunning_ = true;
for (Request *req : pendingRequests_) {
@@ -265,6 +269,8 @@ int V4L2Camera::qbuf(unsigned int index)
return 0;
}
+ request->controls().merge(std::move(controls_));
+
ret = camera_->queueRequest(request);
if (ret < 0) {
LOG(V4L2Compat, Error) << "Can't queue request";
diff --git a/src/v4l2/v4l2_camera.h b/src/v4l2/v4l2_camera.h
index 278cc33e..e54371fb 100644
--- a/src/v4l2/v4l2_camera.h
+++ b/src/v4l2/v4l2_camera.h
@@ -8,13 +8,15 @@
#pragma once
#include <deque>
-#include <utility>
+#include <memory>
+#include <vector>
#include <libcamera/base/mutex.h>
#include <libcamera/base/semaphore.h>
#include <libcamera/base/shared_fd.h>
#include <libcamera/camera.h>
+#include <libcamera/controls.h>
#include <libcamera/framebuffer.h>
#include <libcamera/framebuffer_allocator.h>
@@ -49,6 +51,8 @@ public:
const libcamera::Size &size,
libcamera::StreamConfiguration *streamConfigOut);
+ libcamera::ControlList &controls() { return controls_; }
+
int allocBuffers(unsigned int count);
void freeBuffers();
int getBufferFd(unsigned int index);
@@ -70,6 +74,8 @@ private:
std::shared_ptr<libcamera::Camera> camera_;
std::unique_ptr<libcamera::CameraConfiguration> config_;
+ libcamera::ControlList controls_;
+
bool isRunning_;
libcamera::Mutex bufferLock_;
diff --git a/src/v4l2/v4l2_camera_proxy.cpp b/src/v4l2/v4l2_camera_proxy.cpp
index 3f7c00a2..5ac8df4c 100644
--- a/src/v4l2/v4l2_camera_proxy.cpp
+++ b/src/v4l2/v4l2_camera_proxy.cpp
@@ -8,7 +8,6 @@
#include "v4l2_camera_proxy.h"
#include <algorithm>
-#include <array>
#include <errno.h>
#include <numeric>
#include <set>
@@ -23,9 +22,11 @@
#include <libcamera/base/utils.h>
#include <libcamera/camera.h>
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
#include <libcamera/formats.h>
-#include "libcamera/internal/formats.h"
+#include "libcamera/internal/v4l2_pixelformat.h"
#include "v4l2_camera.h"
#include "v4l2_camera_file.h"
@@ -34,6 +35,7 @@
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
using namespace libcamera;
+using namespace std::literals::chrono_literals;
LOG_DECLARE_CATEGORY(V4L2Compat)
@@ -756,6 +758,23 @@ int V4L2CameraProxy::vidioc_streamoff(V4L2CameraFile *file, int *arg)
return ret;
}
+int V4L2CameraProxy::vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg)
+{
+ LOG(V4L2Compat, Debug)
+ << "[" << file->description() << "] " << __func__ << "()";
+
+ if (!validateBufferType(arg->type))
+ return -EINVAL;
+
+ struct v4l2_fract *timeperframe = &arg->parm.capture.timeperframe;
+ utils::Duration frameDuration = 1.0s * timeperframe->numerator / timeperframe->denominator;
+
+ int64_t uDuration = frameDuration.get<std::micro>();
+ vcam_->controls().set(controls::FrameDurationLimits, { uDuration, uDuration });
+
+ return 0;
+}
+
const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
VIDIOC_QUERYCAP,
VIDIOC_ENUM_FRAMESIZES,
@@ -776,6 +795,7 @@ const std::set<unsigned long> V4L2CameraProxy::supportedIoctls_ = {
VIDIOC_EXPBUF,
VIDIOC_STREAMON,
VIDIOC_STREAMOFF,
+ VIDIOC_S_PARM,
};
int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void *arg)
@@ -863,6 +883,9 @@ int V4L2CameraProxy::ioctl(V4L2CameraFile *file, unsigned long longRequest, void
case VIDIOC_STREAMOFF:
ret = vidioc_streamoff(file, static_cast<int *>(arg));
break;
+ case VIDIOC_S_PARM:
+ ret = vidioc_s_parm(file, static_cast<struct v4l2_streamparm *>(arg));
+ break;
default:
ret = -ENOTTY;
break;
diff --git a/src/v4l2/v4l2_camera_proxy.h b/src/v4l2/v4l2_camera_proxy.h
index 3d8784df..c957db53 100644
--- a/src/v4l2/v4l2_camera_proxy.h
+++ b/src/v4l2/v4l2_camera_proxy.h
@@ -67,6 +67,7 @@ private:
int vidioc_expbuf(V4L2CameraFile *file, struct v4l2_exportbuffer *arg);
int vidioc_streamon(V4L2CameraFile *file, int *arg);
int vidioc_streamoff(V4L2CameraFile *file, int *arg);
+ int vidioc_s_parm(V4L2CameraFile *file, struct v4l2_streamparm *arg);
bool hasOwnership(V4L2CameraFile *file);
int acquire(V4L2CameraFile *file);
diff --git a/src/v4l2/v4l2_compat.cpp b/src/v4l2/v4l2_compat.cpp
index 8a44403e..ff833f57 100644
--- a/src/v4l2/v4l2_compat.cpp
+++ b/src/v4l2/v4l2_compat.cpp
@@ -7,12 +7,14 @@
#include "v4l2_compat_manager.h"
-#include <errno.h>
+#include <assert.h>
#include <fcntl.h>
#include <stdarg.h>
+#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <unistd.h>
#include <libcamera/base/utils.h>
@@ -28,71 +30,97 @@ using namespace libcamera;
va_end(ap); \
}
+namespace {
+
+/*
+ * Determine if the flags require a further mode arguments that needs to be
+ * parsed from va_args.
+ */
+bool needs_mode(int flags)
+{
+ return (flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE);
+}
+
+} /* namespace */
+
extern "C" {
LIBCAMERA_PUBLIC int open(const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
oflag, mode);
}
-/* _FORTIFY_SOURCE redirects open to __open_2 */
-LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
-{
- return open(path, oflag);
-}
-
#ifndef open64
LIBCAMERA_PUBLIC int open64(const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(AT_FDCWD, path,
oflag | O_LARGEFILE, mode);
}
-
-LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag)
-{
- return open64(path, oflag);
-}
#endif
LIBCAMERA_PUBLIC int openat(int dirfd, const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(dirfd, path, oflag, mode);
}
-LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
-{
- return openat(dirfd, path, oflag);
-}
-
#ifndef openat64
LIBCAMERA_PUBLIC int openat64(int dirfd, const char *path, int oflag, ...)
{
mode_t mode = 0;
- if (oflag & O_CREAT || oflag & O_TMPFILE)
+ if (needs_mode(oflag))
extract_va_arg(mode_t, mode, oflag);
return V4L2CompatManager::instance()->openat(dirfd, path,
oflag | O_LARGEFILE, mode);
}
+#endif
+
+/*
+ * _FORTIFY_SOURCE redirects open* to __open*_2. Disable the
+ * -Wmissing-declarations warnings, as the functions won't be declared if
+ * _FORTIFY_SOURCE is not in use.
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-declarations"
+
+LIBCAMERA_PUBLIC int __open_2(const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return open(path, oflag);
+}
+
+LIBCAMERA_PUBLIC int __open64_2(const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return open64(path, oflag);
+}
+
+LIBCAMERA_PUBLIC int __openat_2(int dirfd, const char *path, int oflag)
+{
+ assert(!needs_mode(oflag));
+ return openat(dirfd, path, oflag);
+}
LIBCAMERA_PUBLIC int __openat64_2(int dirfd, const char *path, int oflag)
{
+ assert(!needs_mode(oflag));
return openat64(dirfd, path, oflag);
}
-#endif
+
+#pragma GCC diagnostic pop
LIBCAMERA_PUBLIC int dup(int oldfd)
{
@@ -125,7 +153,11 @@ LIBCAMERA_PUBLIC int munmap(void *addr, size_t length)
return V4L2CompatManager::instance()->munmap(addr, length);
}
+#if HAVE_POSIX_IOCTL
+LIBCAMERA_PUBLIC int ioctl(int fd, int request, ...)
+#else
LIBCAMERA_PUBLIC int ioctl(int fd, unsigned long request, ...)
+#endif
{
void *arg;
extract_va_arg(void *, arg, request);
diff --git a/src/v4l2/v4l2_compat_manager.cpp b/src/v4l2/v4l2_compat_manager.cpp
index 6a00afb5..f53fb300 100644
--- a/src/v4l2/v4l2_compat_manager.cpp
+++ b/src/v4l2/v4l2_compat_manager.cpp
@@ -10,7 +10,6 @@
#include <dlfcn.h>
#include <fcntl.h>
#include <map>
-#include <stdarg.h>
#include <string.h>
#include <sys/eventfd.h>
#include <sys/mman.h>
diff --git a/test/gstreamer/gstreamer_memory_lifetime_test.cpp b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
new file mode 100644
index 00000000..1738cf56
--- /dev/null
+++ b/test/gstreamer/gstreamer_memory_lifetime_test.cpp
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Nicolas Dufresne
+ *
+ * gstreamer_memory_lifetime_test.cpp - GStreamer memory lifetime test
+ */
+
+#include <iostream>
+#include <unistd.h>
+
+#include <gst/app/app.h>
+#include <gst/gst.h>
+
+#include "gstreamer_test.h"
+#include "test.h"
+
+using namespace std;
+
+class GstreamerMemoryLifetimeTest : public GstreamerTest, public Test
+{
+public:
+ GstreamerMemoryLifetimeTest()
+ : GstreamerTest()
+ {
+ }
+
+protected:
+ int init() override
+ {
+ if (status_ != TestPass)
+ return status_;
+
+ appsink_ = gst_element_factory_make("appsink", nullptr);
+ if (!appsink_) {
+ g_printerr("Your installation is missing 'appsink'\n");
+ return TestFail;
+ }
+ g_object_ref_sink(appsink_);
+
+ return createPipeline();
+ }
+
+ int run() override
+ {
+ /* Build the pipeline */
+ gst_bin_add_many(GST_BIN(pipeline_), libcameraSrc_, appsink_, nullptr);
+ if (gst_element_link(libcameraSrc_, appsink_) != TRUE) {
+ g_printerr("Elements could not be linked.\n");
+ return TestFail;
+ }
+
+ if (startPipeline() != TestPass)
+ return TestFail;
+
+ sample_ = gst_app_sink_try_pull_sample(GST_APP_SINK(appsink_), GST_SECOND * 5);
+ if (!sample_) {
+ /* Failed to obtain a sample. Abort the test */
+ gst_element_set_state(pipeline_, GST_STATE_NULL);
+ return TestFail;
+ }
+
+ /*
+ * Keep the sample referenced and set the pipeline state to
+ * NULL. This causes the libcamerasrc element to synchronously
+ * release resources it holds. The sample will be released
+ * later in cleanup().
+ *
+ * The test case verifies that libcamerasrc keeps alive long
+ * enough all the resources that are needed until memory
+ * allocated for frames gets freed. We return TestPass at this
+ * stage, and any use-after-free will be caught by the test
+ * crashing in cleanup().
+ */
+ gst_element_set_state(pipeline_, GST_STATE_NULL);
+
+ return TestPass;
+ }
+
+ void cleanup() override
+ {
+ g_clear_pointer(&sample_, gst_sample_unref);
+ g_clear_object(&appsink_);
+ }
+
+private:
+ GstElement *appsink_;
+ GstSample *sample_;
+};
+
+TEST_REGISTER(GstreamerMemoryLifetimeTest)
diff --git a/test/gstreamer/gstreamer_test.cpp b/test/gstreamer/gstreamer_test.cpp
index e8119b85..a15fef0e 100644
--- a/test/gstreamer/gstreamer_test.cpp
+++ b/test/gstreamer/gstreamer_test.cpp
@@ -9,12 +9,17 @@
#include <libcamera/base/utils.h>
+#if HAVE_ASAN
+#include <sanitizer/asan_interface.h>
+#endif
+
#include "gstreamer_test.h"
#include "test.h"
using namespace std;
+#if HAVE_ASAN
extern "C" {
const char *__asan_default_options()
{
@@ -26,6 +31,7 @@ const char *__asan_default_options()
return "detect_leaks=false";
}
}
+#endif
GstreamerTest::GstreamerTest(unsigned int numStreams)
: pipeline_(nullptr), libcameraSrc_(nullptr)
diff --git a/test/gstreamer/meson.build b/test/gstreamer/meson.build
index f3ba5a23..e066c582 100644
--- a/test/gstreamer/meson.build
+++ b/test/gstreamer/meson.build
@@ -8,14 +8,24 @@ gstreamer_tests = [
{'name': 'single_stream_test', 'sources': ['gstreamer_single_stream_test.cpp']},
{'name': 'multi_stream_test', 'sources': ['gstreamer_multi_stream_test.cpp']},
{'name': 'device_provider_test', 'sources': ['gstreamer_device_provider_test.cpp']},
+ {'name': 'memory_lifetime_test', 'sources': ['gstreamer_memory_lifetime_test.cpp']},
]
gstreamer_dep = dependency('gstreamer-1.0', required : true)
+gstapp_dep = dependency('gstreamer-app-1.0', required : true)
+
+gstreamer_test_args = []
+
+if asan_enabled
+ gstreamer_test_args += ['-D', 'HAVE_ASAN=1']
+endif
foreach test : gstreamer_tests
exe = executable(test['name'], test['sources'], 'gstreamer_test.cpp',
- dependencies : [libcamera_private, gstreamer_dep],
+ cpp_args : gstreamer_test_args,
+ dependencies : [libcamera_private, gstreamer_dep, gstapp_dep],
link_with : test_libraries,
include_directories : test_includes_internal)
- test(test['name'], exe, suite : 'gstreamer', is_parallel : false, env : gst_env)
+ test(test['name'], exe, suite : 'gstreamer', is_parallel : false,
+ env : gst_env, should_fail : test.get('should_fail', false))
endforeach
diff --git a/test/ipa/ipa_interface_test.cpp b/test/ipa/ipa_interface_test.cpp
index e840f6ab..b8178366 100644
--- a/test/ipa/ipa_interface_test.cpp
+++ b/test/ipa/ipa_interface_test.cpp
@@ -20,11 +20,11 @@
#include <libcamera/base/thread.h>
#include <libcamera/base/timer.h>
+#include "libcamera/internal/camera_manager.h"
#include "libcamera/internal/device_enumerator.h"
#include "libcamera/internal/ipa_manager.h"
#include "libcamera/internal/ipa_module.h"
#include "libcamera/internal/pipeline_handler.h"
-#include "libcamera/internal/process.h"
#include "test.h"
@@ -44,20 +44,20 @@ public:
{
delete notifier_;
ipa_.reset();
- ipaManager_.reset();
+ cameraManager_.reset();
}
protected:
int init() override
{
- ipaManager_ = make_unique<IPAManager>();
+ cameraManager_ = make_unique<CameraManager>();
/* Create a pipeline handler for vimc. */
const std::vector<PipelineHandlerFactoryBase *> &factories =
PipelineHandlerFactoryBase::factories();
for (const PipelineHandlerFactoryBase *factory : factories) {
if (factory->name() == "vimc") {
- pipe_ = factory->create(nullptr);
+ pipe_ = factory->create(cameraManager_.get());
break;
}
}
@@ -171,11 +171,9 @@ private:
}
}
- ProcessManager processManager_;
-
std::shared_ptr<PipelineHandler> pipe_;
std::unique_ptr<ipa::vimc::IPAProxyVimc> ipa_;
- std::unique_ptr<IPAManager> ipaManager_;
+ std::unique_ptr<CameraManager> cameraManager_;
enum ipa::vimc::IPAOperationCode trace_;
EventNotifier *notifier_;
int fd_;
diff --git a/test/ipa/libipa/interpolator.cpp b/test/ipa/libipa/interpolator.cpp
new file mode 100644
index 00000000..6abb7760
--- /dev/null
+++ b/test/ipa/libipa/interpolator.cpp
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Ideas on Board Oy
+ *
+ * Interpolator tests
+ */
+
+#include "../src/ipa/libipa/interpolator.h"
+
+#include <cmath>
+#include <iostream>
+#include <map>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "test.h"
+
+using namespace std;
+using namespace libcamera;
+using namespace ipa;
+
+#define ASSERT_EQ(a, b) \
+ if ((a) != (b)) { \
+ printf(#a " != " #b "\n"); \
+ return TestFail; \
+ }
+
+class InterpolatorTest : public Test
+{
+protected:
+ int run()
+ {
+ Interpolator<int> interpolator;
+ interpolator.setData({ { 10, 100 }, { 20, 200 }, { 30, 300 } });
+
+ ASSERT_EQ(interpolator.getInterpolated(0), 100);
+ ASSERT_EQ(interpolator.getInterpolated(10), 100);
+ ASSERT_EQ(interpolator.getInterpolated(20), 200);
+ ASSERT_EQ(interpolator.getInterpolated(25), 250);
+ ASSERT_EQ(interpolator.getInterpolated(30), 300);
+ ASSERT_EQ(interpolator.getInterpolated(40), 300);
+
+ interpolator.setQuantization(10);
+ unsigned int q = 0;
+ ASSERT_EQ(interpolator.getInterpolated(25, &q), 300);
+ ASSERT_EQ(q, 30);
+ ASSERT_EQ(interpolator.getInterpolated(24, &q), 200);
+ ASSERT_EQ(q, 20);
+
+ return TestPass;
+ }
+};
+
+TEST_REGISTER(InterpolatorTest)
diff --git a/test/ipa/libipa/meson.build b/test/ipa/libipa/meson.build
new file mode 100644
index 00000000..4d2427db
--- /dev/null
+++ b/test/ipa/libipa/meson.build
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: CC0-1.0
+
+libipa_test = [
+ {'name': 'interpolator', 'sources': ['interpolator.cpp']},
+]
+
+foreach test : libipa_test
+ exe = executable(test['name'], test['sources'],
+ dependencies : [libcamera_private, libipa_dep],
+ link_with : [test_libraries],
+ include_directories : [test_includes_internal,
+ '../../../src/ipa/libipa/'])
+
+ test(test['name'], exe, suite : 'ipa')
+endforeach
diff --git a/test/ipa/meson.build b/test/ipa/meson.build
index fe21ca58..63820de5 100644
--- a/test/ipa/meson.build
+++ b/test/ipa/meson.build
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
+subdir('libipa')
subdir('rkisp1')
ipa_test = [
@@ -8,7 +9,7 @@ ipa_test = [
]
foreach test : ipa_test
- exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers,
+ exe = executable(test['name'], test['sources'],
dependencies : [libcamera_private, libipa_dep],
link_with : [test_libraries],
include_directories : [test_includes_internal])
diff --git a/test/ipa/rkisp1/meson.build b/test/ipa/rkisp1/meson.build
index 5b08e293..894523da 100644
--- a/test/ipa/rkisp1/meson.build
+++ b/test/ipa/rkisp1/meson.build
@@ -5,7 +5,7 @@ rkisp1_ipa_test = [
]
foreach test : rkisp1_ipa_test
- exe = executable(test['name'], test['sources'], libcamera_generated_ipa_headers,
+ exe = executable(test['name'], test['sources'],
dependencies : [libcamera_private, libipa_dep],
link_with : [test_libraries],
include_directories : [test_includes_internal,
diff --git a/test/ipc/unixsocket.cpp b/test/ipc/unixsocket.cpp
index f44ab9c9..f39bd986 100644
--- a/test/ipc/unixsocket.cpp
+++ b/test/ipc/unixsocket.cpp
@@ -15,6 +15,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <vector>
#include <libcamera/base/event_dispatcher.h>
#include <libcamera/base/thread.h>
@@ -34,6 +35,8 @@ using namespace libcamera;
using namespace std;
using namespace std::chrono_literals;
+namespace {
+
int calculateLength(int fd)
{
lseek(fd, 0, 0);
@@ -43,6 +46,8 @@ int calculateLength(int fd)
return size;
}
+} /* namespace */
+
class UnixSocketTestSlave
{
public:
@@ -336,14 +341,14 @@ protected:
for (unsigned int i = 0; i < std::size(strings); i++) {
unsigned int len = strlen(strings[i]);
- char buf[len];
+ std::vector<char> buf(len);
close(fds[i]);
- if (read(response.fds[0], &buf, len) <= 0)
+ if (read(response.fds[0], buf.data(), len) <= 0)
return TestFail;
- if (memcmp(buf, strings[i], len))
+ if (memcmp(buf.data(), strings[i], len))
return TestFail;
}
diff --git a/test/serialization/generated_serializer/generated_serializer_test.cpp b/test/serialization/generated_serializer/generated_serializer_test.cpp
index 4b11d67f..dd696885 100644
--- a/test/serialization/generated_serializer/generated_serializer_test.cpp
+++ b/test/serialization/generated_serializer/generated_serializer_test.cpp
@@ -6,6 +6,7 @@
*/
#include <algorithm>
+#include <iostream>
#include <tuple>
#include <vector>
diff --git a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build
index 6f8794c1..ae08e9be 100644
--- a/test/serialization/generated_serializer/include/libcamera/ipa/meson.build
+++ b/test/serialization/generated_serializer/include/libcamera/ipa/meson.build
@@ -9,7 +9,8 @@ mojom = custom_target('test_mojom_module',
'--output-root', meson.project_build_root(),
'--input-root', meson.project_source_root(),
'--mojoms', '@INPUT@'
- ])
+ ],
+ env : py_build_env)
# test_ipa_interface.h
generated_test_header = custom_target('test_ipa_interface_h',
@@ -23,7 +24,8 @@ generated_test_header = custom_target('test_ipa_interface_h',
'--libcamera_generate_header',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
# test_ipa_serializer.h
generated_test_serializer = custom_target('test_ipa_serializer_h',
@@ -37,4 +39,5 @@ generated_test_serializer = custom_target('test_ipa_serializer_h',
'--libcamera_generate_serializer',
'--libcamera_output_path=@OUTPUT@',
'./' +'@INPUT@'
- ])
+ ],
+ env : py_build_env)
diff --git a/test/utils.cpp b/test/utils.cpp
index 41af954b..d25475cb 100644
--- a/test/utils.cpp
+++ b/test/utils.cpp
@@ -176,6 +176,14 @@ protected:
std::ostringstream os;
std::string ref;
+ os << utils::hex(static_cast<int8_t>(0x42)) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<uint8_t>(0x42)) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<int16_t>(0x42)) << " ";
+ ref += "0x0042 ";
+ os << utils::hex(static_cast<uint16_t>(0x42)) << " ";
+ ref += "0x0042 ";
os << utils::hex(static_cast<int32_t>(0x42)) << " ";
ref += "0x00000042 ";
os << utils::hex(static_cast<uint32_t>(0x42)) << " ";
@@ -184,6 +192,15 @@ protected:
ref += "0x0000000000000042 ";
os << utils::hex(static_cast<uint64_t>(0x42)) << " ";
ref += "0x0000000000000042 ";
+
+ os << utils::hex(static_cast<int8_t>(0x42), 6) << " ";
+ ref += "0x000042 ";
+ os << utils::hex(static_cast<uint8_t>(0x42), 1) << " ";
+ ref += "0x42 ";
+ os << utils::hex(static_cast<int16_t>(0x42), 6) << " ";
+ ref += "0x000042 ";
+ os << utils::hex(static_cast<uint16_t>(0x42), 1) << " ";
+ ref += "0x42 ";
os << utils::hex(static_cast<int32_t>(0x42), 4) << " ";
ref += "0x0042 ";
os << utils::hex(static_cast<uint32_t>(0x42), 1) << " ";
diff --git a/test/yaml-parser.cpp b/test/yaml-parser.cpp
index 81c82983..1b22c87b 100644
--- a/test/yaml-parser.cpp
+++ b/test/yaml-parser.cpp
@@ -34,10 +34,12 @@ static const string testYaml =
"list:\n"
" - James\n"
" - Mary\n"
+ " - \n"
"dictionary:\n"
" a: 1\n"
" c: 3\n"
" b: 2\n"
+ " empty:\n"
"level1:\n"
" level2:\n"
" - [1, 2]\n"
@@ -430,9 +432,10 @@ protected:
if (testObjectType(listObj, "list", Type::List) != TestPass)
return TestFail;
- static constexpr std::array<const char *, 2> listValues{
+ static constexpr std::array<const char *, 3> listValues{
"James",
"Mary",
+ "",
};
if (listObj.size() != listValues.size()) {
@@ -465,16 +468,23 @@ protected:
i++;
}
+ /* Ensure that empty objects get parsed as empty strings. */
+ if (!listObj[2].isValue()) {
+ cerr << "Empty object is not a value" << std::endl;
+ return TestFail;
+ }
+
/* Test dictionary object */
auto &dictObj = (*root)["dictionary"];
if (testObjectType(dictObj, "dictionary", Type::Dictionary) != TestPass)
return TestFail;
- static constexpr std::array<std::pair<const char *, int>, 3> dictValues{ {
+ static constexpr std::array<std::pair<const char *, int>, 4> dictValues{ {
{ "a", 1 },
{ "c", 3 },
{ "b", 2 },
+ { "empty", -100 },
} };
size_t dictSize = dictValues.size();
@@ -505,7 +515,7 @@ protected:
return TestFail;
}
- if (elem.get<int32_t>(0) != item.second) {
+ if (elem.get<int32_t>(-100) != item.second) {
std::cerr << "Dictionary element " << i << " has wrong value"
<< std::endl;
return TestFail;
@@ -514,6 +524,42 @@ protected:
i++;
}
+ /* Ensure that empty objects get parsed as empty strings. */
+ if (!dictObj["empty"].isValue()) {
+ cerr << "Empty object is not of type value" << std::endl;
+ return TestFail;
+ }
+
+ /* Ensure that keys without values are added to a dict. */
+ if (!dictObj.contains("empty")) {
+ cerr << "Empty element is missing in dict" << std::endl;
+ return TestFail;
+ }
+
+ /* Test access to nonexistent member. */
+ if (dictObj["nonexistent"].get<std::string>("default") != "default") {
+ cerr << "Accessing nonexistent dict entry fails to return default" << std::endl;
+ return TestFail;
+ }
+
+ /* Test nonexistent object has value type empty. */
+ if (!dictObj["nonexistent"].isEmpty()) {
+ cerr << "Accessing nonexistent object returns non-empty object" << std::endl;
+ return TestFail;
+ }
+
+ /* Test explicit cast to bool on an empty object returns true. */
+ if (!!dictObj["empty"] != true) {
+ cerr << "Casting empty entry to bool returns false" << std::endl;
+ return TestFail;
+ }
+
+ /* Test explicit cast to bool on nonexistent object returns false. */
+ if (!!dictObj["nonexistent"] != false) {
+ cerr << "Casting nonexistent dict entry to bool returns true" << std::endl;
+ return TestFail;
+ }
+
/* Make sure utils::map_keys() works on the adapter. */
(void)utils::map_keys(dictObj.asDict());
diff --git a/utils/abi-compat.sh b/utils/abi-compat.sh
index c936ac05..31f61e32 100755
--- a/utils/abi-compat.sh
+++ b/utils/abi-compat.sh
@@ -156,15 +156,16 @@ create_abi_dump() {
# Generate a minimal libcamera build. "lib" and "prefix" are
# defined explicitly to avoid system default ambiguities.
meson setup "$build" "$worktree" \
- -Dlibdir=lib \
- -Dprefix=/usr/local/ \
- -Ddocumentation=disabled \
-Dcam=disabled \
- -Dqcam=disabled \
+ -Ddocumentation=disabled \
-Dgstreamer=disabled \
-Dlc-compliance=disabled \
- -Dtracing=disabled \
- -Dpipelines=
+ -Dlibdir=lib \
+ -Dpipelines= \
+ -Dprefix=/usr/local/ \
+ -Dpycamera=disabled \
+ -Dqcam=disabled \
+ -Dtracing=disabled
ninja -C "$build"
DESTDIR="$install" ninja -C "$build" install
diff --git a/utils/checkstyle.py b/utils/checkstyle.py
index 4185c39a..ab89c0a1 100755
--- a/utils/checkstyle.py
+++ b/utils/checkstyle.py
@@ -211,36 +211,66 @@ class CommitFile:
class Commit:
def __init__(self, commit):
- self.commit = commit
+ self._commit = commit
+ self._author = None
self._trailers = []
self._parse()
- def _parse_trailers(self, lines):
- for index in range(1, len(lines)):
- line = lines[index]
- if not line:
- break
+ def _parse_commit(self):
+ # Get and parse the commit message.
+ ret = subprocess.run(['git', 'show', '--format=%H%n%an <%ae>%n%s%n%b',
+ '--no-patch', self.commit],
+ stdout=subprocess.PIPE).stdout.decode('utf-8')
+ lines = ret.splitlines()
+
+ self._commit = lines[0]
+ self._author = lines[1]
+ self._title = lines[2]
+ self._body = lines[3:]
- self._trailers.append(line)
+ # Parse the trailers. Feed git-interpret-trailers with a full commit
+ # message that includes both the title and the body, as it otherwise
+ # fails to find trailers when the body contains trailers only.
+ message = self._title + '\n\n' + '\n'.join(self._body)
+ trailers = subprocess.run(['git', 'interpret-trailers', '--parse'],
+ input=message.encode('utf-8'),
+ stdout=subprocess.PIPE).stdout.decode('utf-8')
- return index
+ self._trailers = trailers.splitlines()
def _parse(self):
- # Get the commit title and list of files.
- ret = subprocess.run(['git', 'show', '--format=%s%n%(trailers:only,unfold)', '--name-status',
+ self._parse_commit()
+
+ # Get the list of files. Use an empty format specifier to suppress the
+ # commit message completely.
+ ret = subprocess.run(['git', 'show', '--format=', '--name-status',
self.commit],
stdout=subprocess.PIPE).stdout.decode('utf-8')
- lines = ret.splitlines()
-
- self._title = lines[0]
+ self._files = [CommitFile(f) for f in ret.splitlines()]
- index = self._parse_trailers(lines)
- self._files = [CommitFile(f) for f in lines[index:] if f]
+ def __repr__(self):
+ return '\n'.join([
+ f'commit {self.commit}',
+ f'Author: {self.author}',
+ f'',
+ f' {self.title}',
+ '',
+ '\n'.join([line and f' {line}' or '' for line in self._body]),
+ 'Trailers:',
+ ] + self.trailers)
def files(self, filter='AMR'):
return [f.filename for f in self._files if f.status in filter]
@property
+ def author(self):
+ return self._author
+
+ @property
+ def commit(self):
+ return self._commit
+
+ @property
def title(self):
return self._title
@@ -278,20 +308,14 @@ class StagedChanges(Commit):
class Amendment(Commit):
def __init__(self):
- Commit.__init__(self, '')
+ Commit.__init__(self, 'HEAD')
def _parse(self):
- # Create a title using HEAD commit and parse the trailers.
- ret = subprocess.run(['git', 'show', '--format=%H %s%n%(trailers:only,unfold)',
- '--no-patch'],
- stdout=subprocess.PIPE).stdout.decode('utf-8')
- lines = ret.splitlines()
+ self._parse_commit()
- self._title = 'Amendment of ' + lines[0].strip()
+ self._title = f'Amendment of "{self.title}"'
- self._parse_trailers(lines)
-
- # Extract the list of modified files
+ # Extract the list of modified files.
ret = subprocess.run(['git', 'diff', '--staged', '--name-status', 'HEAD~'],
stdout=subprocess.PIPE).stdout.decode('utf-8')
self._files = [CommitFile(f) for f in ret.splitlines()]
@@ -331,11 +355,16 @@ class CommitChecker(metaclass=ClassRegistry):
# Class methods
#
@classmethod
- def checkers(cls, names):
+ def checkers(cls, commit, names):
for checker in cls.subclasses:
if names and checker.__name__ not in names:
continue
- yield checker
+ if checker.supports(commit):
+ yield checker
+
+ @classmethod
+ def supports(cls, commit):
+ return type(commit) in cls.commit_types
class CommitIssue(object):
@@ -344,6 +373,8 @@ class CommitIssue(object):
class HeaderAddChecker(CommitChecker):
+ commit_types = (Commit, StagedChanges, Amendment)
+
@classmethod
def check(cls, commit, top_level):
issues = []
@@ -388,6 +419,8 @@ class HeaderAddChecker(CommitChecker):
class TitleChecker(CommitChecker):
+ commit_types = (Commit,)
+
prefix_regex = re.compile(r'^([a-zA-Z0-9_.-]+: )+')
release_regex = re.compile(r'libcamera v[0-9]+\.[0-9]+\.[0-9]+')
@@ -395,11 +428,6 @@ class TitleChecker(CommitChecker):
def check(cls, commit, top_level):
title = commit.title
- # Skip the check when validating staged changes (as done through a
- # pre-commit hook) as there is no title to check in that case.
- if isinstance(commit, StagedChanges):
- return []
-
# Ignore release commits, they don't need a prefix.
if TitleChecker.release_regex.fullmatch(title):
return []
@@ -455,6 +483,8 @@ class TitleChecker(CommitChecker):
class TrailersChecker(CommitChecker):
+ commit_types = (Commit,)
+
commit_regex = re.compile(r'[0-9a-f]{12}[0-9a-f]* \(".*"\)')
coverity_regex = re.compile(r'Coverity CID=.*')
@@ -493,6 +523,8 @@ class TrailersChecker(CommitChecker):
def check(cls, commit, top_level):
issues = []
+ sob_found = False
+
for trailer in commit.trailers:
match = TrailersChecker.trailer_regex.fullmatch(trailer)
if not match:
@@ -515,6 +547,13 @@ class TrailersChecker(CommitChecker):
issues.append(CommitIssue(f"Malformed value '{value}' for commit trailer '{key}'"))
continue
+ if key == 'Signed-off-by':
+ if value == commit.author:
+ sob_found = True
+
+ if not sob_found:
+ issues.append(CommitIssue(f"No 'Signed-off-by' trailer matching author '{commit.author}', see Documentation/contributing.rst"))
+
return issues
@@ -670,39 +709,6 @@ class MesonChecker(StyleChecker):
return issues
-class Pep8Checker(StyleChecker):
- patterns = ('*.py',)
- results_regex = re.compile(r'stdin:([0-9]+):([0-9]+)(.*)')
-
- def __init__(self, content):
- super().__init__()
- self.__content = content
-
- def check(self, line_numbers):
- issues = []
- data = ''.join(self.__content).encode('utf-8')
-
- try:
- ret = subprocess.run(['pycodestyle', '--ignore=E501', '-'],
- input=data, stdout=subprocess.PIPE)
- except FileNotFoundError:
- issues.append(StyleIssue(0, None, None, 'Please install pycodestyle to validate python additions'))
- return issues
-
- results = ret.stdout.decode('utf-8').splitlines()
- for item in results:
- search = re.search(Pep8Checker.results_regex, item)
- line_number = int(search.group(1))
- position = int(search.group(2))
- msg = search.group(3)
-
- if line_number in line_numbers:
- line = self.__content[line_number - 1]
- issues.append(StyleIssue(line_number, None, line, msg))
-
- return issues
-
-
class ShellChecker(StyleChecker):
patterns = ('*.sh',)
results_line_regex = re.compile(r'In - line ([0-9]+):')
@@ -904,6 +910,21 @@ class IncludeOrderFormatter(Formatter):
return '\n'.join(lines)
+class Pep8Formatter(Formatter):
+ patterns = ('*.py',)
+
+ @classmethod
+ def format(cls, filename, data):
+ try:
+ ret = subprocess.run(['autopep8', '--ignore=E501', '-'],
+ input=data.encode('utf-8'), stdout=subprocess.PIPE)
+ except FileNotFoundError:
+ issues.append(StyleIssue(0, None, None, 'Please install autopep8 to format python additions'))
+ return issues
+
+ return ret.stdout.decode('utf-8')
+
+
class StripTrailingSpaceFormatter(Formatter):
patterns = ('*.c', '*.cpp', '*.h', '*.py', 'meson.build')
@@ -998,7 +1019,7 @@ def check_style(top_level, commit, checkers):
issues = 0
# Apply the commit checkers first.
- for checker in CommitChecker.checkers(checkers):
+ for checker in CommitChecker.checkers(commit, checkers):
for issue in checker.check(commit, top_level):
print('%s%s%s' % (Colours.fg(Colours.Yellow), issue.msg, Colours.reset()))
issues += 1
diff --git a/utils/codegen/controls.py b/utils/codegen/controls.py
new file mode 100644
index 00000000..7bafee59
--- /dev/null
+++ b/utils/codegen/controls.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2019, Google Inc.
+#
+# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+#
+# Helper classes to handle source code generation for libcamera controls
+
+
+class ControlEnum(object):
+ def __init__(self, data):
+ self.__data = data
+
+ @property
+ def description(self):
+ """The enum description"""
+ return self.__data.get('description')
+
+ @property
+ def name(self):
+ """The enum name"""
+ return self.__data.get('name')
+
+ @property
+ def value(self):
+ """The enum value"""
+ return self.__data.get('value')
+
+
+class Control(object):
+ def __init__(self, name, data, vendor):
+ self.__name = name
+ self.__data = data
+ self.__enum_values = None
+ self.__size = None
+ self.__vendor = vendor
+
+ enum_values = data.get('enum')
+ if enum_values is not None:
+ self.__enum_values = [ControlEnum(enum) for enum in enum_values]
+
+ size = self.__data.get('size')
+ if size is not None:
+ if len(size) == 0:
+ raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension')
+
+ # Compute the total number of elements in the array. If any of the
+ # array dimension is a string, the array is variable-sized.
+ num_elems = 1
+ for dim in size:
+ if type(dim) is str:
+ num_elems = 0
+ break
+
+ dim = int(dim)
+ if dim <= 0:
+ raise RuntimeError(f'Control `{self.__name}` size must have positive values only')
+
+ num_elems *= dim
+
+ self.__size = num_elems
+
+ @property
+ def description(self):
+ """The control description"""
+ return self.__data.get('description')
+
+ @property
+ def enum_values(self):
+ """The enum values, if the control is an enumeration"""
+ if self.__enum_values is None:
+ return
+ for enum in self.__enum_values:
+ yield enum
+
+ @property
+ def enum_values_count(self):
+ """The number of enum values, if the control is an enumeration"""
+ if self.__enum_values is None:
+ return 0
+ return len(self.__enum_values)
+
+ @property
+ def is_enum(self):
+ """Is the control an enumeration"""
+ return self.__enum_values is not None
+
+ @property
+ def vendor(self):
+ """The vendor string, or None"""
+ return self.__vendor
+
+ @property
+ def name(self):
+ """The control name (CamelCase)"""
+ return self.__name
+
+ @property
+ def type(self):
+ typ = self.__data.get('type')
+ size = self.__data.get('size')
+
+ if typ == 'string':
+ return 'std::string'
+
+ if self.__size is None:
+ return typ
+
+ if self.__size:
+ return f"Span<const {typ}, {self.__size}>"
+ else:
+ return f"Span<const {typ}>"
diff --git a/utils/codegen/gen-controls.py b/utils/codegen/gen-controls.py
new file mode 100755
index 00000000..3034e9a5
--- /dev/null
+++ b/utils/codegen/gen-controls.py
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2019, Google Inc.
+#
+# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+#
+# Generate control definitions from YAML
+
+import argparse
+import jinja2
+import os
+import sys
+import yaml
+
+from controls import Control
+
+
+def snake_case(s):
+ return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_')
+
+
+def format_description(description):
+ description = description.strip('\n').split('\n')
+ for i in range(1, len(description)):
+ line = description[i]
+ description[i] = (line and ' * ' or ' *') + line
+ return '\n'.join(description)
+
+
+def extend_control(ctrl, id, ranges):
+ ctrl.id = ranges[ctrl.vendor] + id + 1
+
+ if ctrl.vendor != 'libcamera':
+ ctrl.namespace = f'{ctrl.vendor}::'
+ else:
+ ctrl.namespace = ''
+
+ return ctrl
+
+
+def main(argv):
+
+ # Parse command line arguments
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],
+ help='Mode of operation')
+ parser.add_argument('--output', '-o', metavar='file', type=str,
+ help='Output file name. Defaults to standard output if not specified.')
+ parser.add_argument('--ranges', '-r', type=str, required=True,
+ help='Control id range reservation file.')
+ parser.add_argument('--template', '-t', dest='template', type=str, required=True,
+ help='Template file name.')
+ parser.add_argument('input', type=str, nargs='+',
+ help='Input file name.')
+
+ args = parser.parse_args(argv[1:])
+
+ ranges = {}
+ with open(args.ranges, 'rb') as f:
+ data = open(args.ranges, 'rb').read()
+ ranges = yaml.safe_load(data)['ranges']
+
+ controls = {}
+ for input in args.input:
+ data = yaml.safe_load(open(input, 'rb').read())
+
+ vendor = data['vendor']
+ if vendor not in ranges.keys():
+ raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
+
+ ctrls = controls.setdefault(vendor, [])
+
+ for i, ctrl in enumerate(data['controls']):
+ ctrl = Control(*ctrl.popitem(), vendor)
+ ctrls.append(extend_control(ctrl, i, ranges))
+
+ # Sort the vendors by range numerical value
+ controls = [[vendor, ctrls] for vendor, ctrls in controls.items()]
+ controls.sort(key=lambda item: ranges[item[0]])
+
+ filename = {
+ 'controls': 'control_ids',
+ 'properties': 'property_ids',
+ }[args.mode]
+
+ data = {
+ 'filename': filename,
+ 'mode': args.mode,
+ 'controls': controls,
+ }
+
+ env = jinja2.Environment()
+ env.filters['format_description'] = format_description
+ env.filters['snake_case'] = snake_case
+ template = env.from_string(open(args.template, 'r', encoding='utf-8').read())
+ string = template.render(data)
+
+ if args.output:
+ output = open(args.output, 'w', encoding='utf-8')
+ output.write(string)
+ output.close()
+ else:
+ sys.stdout.write(string)
+
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))
diff --git a/utils/gen-formats.py b/utils/codegen/gen-formats.py
index 0c0932a5..0c0932a5 100755
--- a/utils/gen-formats.py
+++ b/utils/codegen/gen-formats.py
diff --git a/utils/gen-header.sh b/utils/codegen/gen-header.sh
index d4692758..c78f0859 100755
--- a/utils/gen-header.sh
+++ b/utils/codegen/gen-header.sh
@@ -1,7 +1,7 @@
#!/bin/sh
-src_dir="$1"
-dst_file="$2"
+dst_file="$1"
+shift
cat <<EOF > "$dst_file"
/* SPDX-License-Identifier: LGPL-2.1-or-later */
@@ -16,9 +16,8 @@ cat <<EOF > "$dst_file"
EOF
-headers=$(for header in "$src_dir"/*.h "$src_dir"/*.h.in ; do
+headers=$(for header in "$@" ; do
header=$(basename "$header")
- header="${header%.in}"
echo "$header"
done | sort)
diff --git a/utils/gen-ipa-pub-key.py b/utils/codegen/gen-ipa-pub-key.py
index dc3e7d5f..dc3e7d5f 100755
--- a/utils/gen-ipa-pub-key.py
+++ b/utils/codegen/gen-ipa-pub-key.py
diff --git a/utils/tracepoints/gen-tp-header.py b/utils/codegen/gen-tp-header.py
index 83606c32..83606c32 100755
--- a/utils/tracepoints/gen-tp-header.py
+++ b/utils/codegen/gen-tp-header.py
diff --git a/utils/ipc/extract-docs.py b/utils/codegen/ipc/extract-docs.py
index 61f44cae..61f44cae 100755
--- a/utils/ipc/extract-docs.py
+++ b/utils/codegen/ipc/extract-docs.py
diff --git a/utils/ipc/generate.py b/utils/codegen/ipc/generate.py
index c2b3fcb7..dfbe659b 100755
--- a/utils/ipc/generate.py
+++ b/utils/codegen/ipc/generate.py
@@ -9,9 +9,6 @@
import os
import sys
-# TODO set sys.pycache_prefix for >= python3.8
-sys.dont_write_bytecode = True
-
sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/bindings')
import mojo.public.tools.bindings.mojom_bindings_generator as generator
diff --git a/utils/ipc/generators/__init__.py b/utils/codegen/ipc/generators/__init__.py
index e69de29b..e69de29b 100644
--- a/utils/ipc/generators/__init__.py
+++ b/utils/codegen/ipc/generators/__init__.py
diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
index 7f2d0810..3942e570 100644
--- a/utils/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_interface.h.tmpl
@@ -15,8 +15,13 @@
#pragma once
{% if has_map %}#include <map>{% endif %}
+{% if has_string %}#include <string>{% endif %}
{% if has_array %}#include <vector>{% endif %}
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+
#include <libcamera/ipa/ipa_interface.h>
namespace libcamera {
diff --git a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
index 036518f6..036518f6 100644
--- a/utils/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/core_ipa_serializer.h.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl b/utils/codegen/ipc/generators/libcamera_templates/definition_functions.tmpl
index 8b8509f3..8b8509f3 100644
--- a/utils/ipc/generators/libcamera_templates/definition_functions.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/definition_functions.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/meson.build b/utils/codegen/ipc/generators/libcamera_templates/meson.build
index 70664eab..70664eab 100644
--- a/utils/ipc/generators/libcamera_templates/meson.build
+++ b/utils/codegen/ipc/generators/libcamera_templates/meson.build
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
index 4d88a3d7..5d70ea6a 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_interface.h.tmpl
@@ -14,12 +14,20 @@
#pragma once
-#include <libcamera/ipa/core_ipa_interface.h>
-#include <libcamera/ipa/ipa_interface.h>
-
{% if has_map %}#include <map>{% endif %}
+{% if has_string %}#include <string>{% endif %}
{% if has_array %}#include <vector>{% endif %}
+#include <libcamera/base/flags.h>
+#include <libcamera/base/signal.h>
+
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/geometry.h>
+
+#include <libcamera/ipa/core_ipa_interface.h>
+#include <libcamera/ipa/ipa_interface.h>
+
namespace libcamera {
{%- if has_namespace %}
{% for ns in namespace %}
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
index ce3cc5ab..ce3cc5ab 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
index e213b18a..e213b18a 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
index 1f990d3f..1f990d3f 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy_worker.cpp.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
index cd5a65a9..cd5a65a9 100644
--- a/utils/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_serializer.h.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl b/utils/codegen/ipc/generators/libcamera_templates/proxy_functions.tmpl
index b5797b14..b5797b14 100644
--- a/utils/ipc/generators/libcamera_templates/proxy_functions.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/proxy_functions.tmpl
diff --git a/utils/ipc/generators/libcamera_templates/serializer.tmpl b/utils/codegen/ipc/generators/libcamera_templates/serializer.tmpl
index 323e1293..323e1293 100644
--- a/utils/ipc/generators/libcamera_templates/serializer.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/serializer.tmpl
diff --git a/utils/ipc/generators/meson.build b/utils/codegen/ipc/generators/meson.build
index 504f1a46..504f1a46 100644
--- a/utils/ipc/generators/meson.build
+++ b/utils/codegen/ipc/generators/meson.build
diff --git a/utils/ipc/generators/mojom_libcamera_generator.py b/utils/codegen/ipc/generators/mojom_libcamera_generator.py
index b8209e51..d9c620a0 100644
--- a/utils/ipc/generators/mojom_libcamera_generator.py
+++ b/utils/codegen/ipc/generators/mojom_libcamera_generator.py
@@ -467,6 +467,7 @@ class Generator(generator.Generator):
'enums': self.module.enums,
'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
+ 'has_string': len([x for x in self.module.kinds.keys() if x[0] == 's']) > 0,
'has_namespace': self.module.mojom_namespace != '',
'interface_event': GetEventInterface(self.module.interfaces),
'interface_main': GetMainInterface(self.module.interfaces),
@@ -486,6 +487,7 @@ class Generator(generator.Generator):
'enums_gen_header': [x for x in self.module.enums if x.attributes is None or 'skipHeader' not in x.attributes],
'has_array': len([x for x in self.module.kinds.keys() if x[0] == 'a']) > 0,
'has_map': len([x for x in self.module.kinds.keys() if x[0] == 'm']) > 0,
+ 'has_string': len([x for x in self.module.kinds.keys() if x[0] == 's']) > 0,
'structs_gen_header': [x for x in self.module.structs if x.attributes is None or 'skipHeader' not in x.attributes],
'structs_gen_serializer': [x for x in self.module.structs if x.attributes is None or 'skipSerdes' not in x.attributes],
}
diff --git a/utils/ipc/meson.build b/utils/codegen/ipc/meson.build
index 973a5417..f77bf324 100644
--- a/utils/ipc/meson.build
+++ b/utils/codegen/ipc/meson.build
@@ -13,6 +13,7 @@ mojom_docs_extractor = find_program('./extract-docs.py')
mojom_templates = custom_target('mojom_templates',
input : mojom_template_files,
output : 'libcamera_templates.zip',
- command : [mojom_generator, '-o', '@OUTDIR@', 'precompile'])
+ command : [mojom_generator, '-o', '@OUTDIR@', 'precompile'],
+ env : py_build_env)
mojom_templates_dir = meson.current_build_dir()
diff --git a/utils/ipc/mojo/README b/utils/codegen/ipc/mojo/README
index 961cabd2..961cabd2 100644
--- a/utils/ipc/mojo/README
+++ b/utils/codegen/ipc/mojo/README
diff --git a/utils/ipc/mojo/public/LICENSE b/utils/codegen/ipc/mojo/public/LICENSE
index 513e8a6a..513e8a6a 100644
--- a/utils/ipc/mojo/public/LICENSE
+++ b/utils/codegen/ipc/mojo/public/LICENSE
diff --git a/utils/ipc/mojo/public/tools/.style.yapf b/utils/codegen/ipc/mojo/public/tools/.style.yapf
index b4ebbe24..b4ebbe24 100644
--- a/utils/ipc/mojo/public/tools/.style.yapf
+++ b/utils/codegen/ipc/mojo/public/tools/.style.yapf
diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/BUILD.gn
index 5328a34a..5328a34a 100644
--- a/utils/ipc/mojo/public/tools/BUILD.gn
+++ b/utils/codegen/ipc/mojo/public/tools/BUILD.gn
diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn
index eeca73ea..eeca73ea 100644
--- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/BUILD.gn
diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/codegen/ipc/mojo/public/tools/bindings/README.md
index b27b2d01..b27b2d01 100644
--- a/utils/ipc/mojo/public/tools/bindings/README.md
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/README.md
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py
index e69de29b..e69de29b 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/__init__.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
index e6e4f2c9..e6e4f2c9 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
index f1a50a4a..f1a50a4a 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
index 702d41c3..702d41c3 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
index 07f51a64..07f51a64 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
index e96152fd..e96152fd 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
index d570e26c..d570e26c 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
index a6cd71e2..a6cd71e2 100644
--- a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py
index 4dd26d4a..4dd26d4a 100755
--- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate-files.py
diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
index 7d56c9f9..7d56c9f9 100755
--- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py
diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py
index c6daff03..c6daff03 100644
--- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/gen_data_files_list.py
diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py
index 4a53e2bf..4a53e2bf 100755
--- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/generate_type_mappings.py
diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py
index cefee7a4..cefee7a4 100755
--- a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/minify_with_terser.py
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni
index 3f6e54e0..3f6e54e0 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom.gni
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
index 8c641c2a..8c641c2a 100755
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
index 761922b6..761922b6 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py
index 6bb7a209..6bb7a209 100755
--- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
+++ b/utils/codegen/ipc/mojo/public/tools/bindings/validate_typemap_config.py
diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn
index eafb95a1..eafb95a1 100644
--- a/utils/ipc/mojo/public/tools/mojom/BUILD.gn
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/BUILD.gn
diff --git a/utils/ipc/mojo/public/tools/mojom/README.md b/utils/codegen/ipc/mojo/public/tools/mojom/README.md
index e5d17ab0..e5d17ab0 100644
--- a/utils/ipc/mojo/public/tools/mojom/README.md
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/README.md
diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
index 35cd1cfd..35cd1cfd 100755
--- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
index 06769c95..06769c95 100755
--- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/const_unittest.py
index e8ed36a7..e8ed36a7 100644
--- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/const_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/enum_unittest.py
index 9269cde5..9269cde5 100644
--- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/enum_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py
index 5f014e1c..5f014e1c 100644
--- a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/feature_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
index a0edf0eb..a0edf0eb 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/__init__.py
index e69de29b..e69de29b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/__init__.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/__init__.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/error.py
index dd53b835..dd53b835 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/error.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil.py
index 124f12c1..124f12c1 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
index c93d2289..c93d2289 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py
index e69de29b..e69de29b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/__init__.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py
index 1efe2022..1efe2022 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/check.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
index 96fe3a2d..96fe3a2d 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
index 7143e07c..7143e07c 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module.py
index ca71059d..ca71059d 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
index 2a4e852c..2a4e852c 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
index 61240426..61240426 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
index 7d8e4e01..7d8e4e01 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
index 807e2a4f..807e2a4f 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
index 83bb297f..83bb297f 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
index b4fea924..b4fea924 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py
index e69de29b..e69de29b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/__init__.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
index aae9cdb6..aae9cdb6 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
index b289f7b1..b289f7b1 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
index 9687edbf..9687edbf 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
index cca1764b..cca1764b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
index 00136a8b..00136a8b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
index bc9f8354..bc9f8354 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
index 1dffd98b..1dffd98b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
index 0a26307b..0a26307b 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser.py
index 9693090e..9693090e 100755
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
index f0ee6966..f0ee6966 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
index 353a2b6e..353a2b6e 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
index d10d69c6..d10d69c6 100644
--- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py
index 6b2525e5..6b2525e5 100644
--- a/utils/ipc/mojo/public/tools/mojom/union_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/union_unittest.py
diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/codegen/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
index 45e45ec5..45e45ec5 100644
--- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
+++ b/utils/codegen/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/codegen/ipc/mojo/public/tools/run_all_python_unittests.py
index 98bce18c..98bce18c 100755
--- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
+++ b/utils/codegen/ipc/mojo/public/tools/run_all_python_unittests.py
diff --git a/utils/ipc/parser.py b/utils/codegen/ipc/parser.py
index cb5608b7..8e70322d 100755
--- a/utils/ipc/parser.py
+++ b/utils/codegen/ipc/parser.py
@@ -9,9 +9,6 @@
import os
import sys
-# TODO set sys.pycache_prefix for >= python3.8
-sys.dont_write_bytecode = True
-
# Make sure that mojom_parser.py can import mojom
sys.path.insert(0, f'{os.path.dirname(__file__)}/mojo/public/tools/mojom')
diff --git a/utils/ipc/tools/README b/utils/codegen/ipc/tools/README
index 961cabd2..961cabd2 100644
--- a/utils/ipc/tools/README
+++ b/utils/codegen/ipc/tools/README
diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/codegen/ipc/tools/diagnosis/crbug_1001171.py
index 40900d10..40900d10 100644
--- a/utils/ipc/tools/diagnosis/crbug_1001171.py
+++ b/utils/codegen/ipc/tools/diagnosis/crbug_1001171.py
diff --git a/utils/codegen/meson.build b/utils/codegen/meson.build
new file mode 100644
index 00000000..adf33bba
--- /dev/null
+++ b/utils/codegen/meson.build
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: CC0-1.0
+
+## Code generation
+
+py_build_env = environment()
+# \todo Investigate usage of PYTHONPYCACHEPREFIX for Python >= 3.8
+py_build_env.set('PYTHONDONTWRITEBYTECODE', '1')
+py_build_env.prepend('PYTHONPATH', meson.current_source_dir())
+
+py_modules += ['jinja2', 'yaml']
+
+gen_controls = files('gen-controls.py')
+gen_formats = files('gen-formats.py')
+gen_header = files('gen-header.sh')
+gen_ipa_pub_key = files('gen-ipa-pub-key.py')
+gen_tracepoints = files('gen-tp-header.py')
+
+subdir('ipc')
diff --git a/utils/gen-controls.py b/utils/gen-controls.py
deleted file mode 100755
index 56315f50..00000000
--- a/utils/gen-controls.py
+++ /dev/null
@@ -1,389 +0,0 @@
-#!/usr/bin/env python3
-# SPDX-License-Identifier: GPL-2.0-or-later
-# Copyright (C) 2019, Google Inc.
-#
-# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
-#
-# Generate control definitions from YAML
-
-import argparse
-from functools import reduce
-import operator
-import string
-import sys
-import yaml
-import os
-
-
-class ControlEnum(object):
- def __init__(self, data):
- self.__data = data
-
- @property
- def description(self):
- """The enum description"""
- return self.__data.get('description')
-
- @property
- def name(self):
- """The enum name"""
- return self.__data.get('name')
-
- @property
- def value(self):
- """The enum value"""
- return self.__data.get('value')
-
-
-class Control(object):
- def __init__(self, name, data, vendor):
- self.__name = name
- self.__data = data
- self.__enum_values = None
- self.__size = None
- self.__vendor = vendor
-
- enum_values = data.get('enum')
- if enum_values is not None:
- self.__enum_values = [ControlEnum(enum) for enum in enum_values]
-
- size = self.__data.get('size')
- if size is not None:
- if len(size) == 0:
- raise RuntimeError(f'Control `{self.__name}` size must have at least one dimension')
-
- # Compute the total number of elements in the array. If any of the
- # array dimension is a string, the array is variable-sized.
- num_elems = 1
- for dim in size:
- if type(dim) is str:
- num_elems = 0
- break
-
- dim = int(dim)
- if dim <= 0:
- raise RuntimeError(f'Control `{self.__name}` size must have positive values only')
-
- num_elems *= dim
-
- self.__size = num_elems
-
- @property
- def description(self):
- """The control description"""
- return self.__data.get('description')
-
- @property
- def enum_values(self):
- """The enum values, if the control is an enumeration"""
- if self.__enum_values is None:
- return
- for enum in self.__enum_values:
- yield enum
-
- @property
- def is_enum(self):
- """Is the control an enumeration"""
- return self.__enum_values is not None
-
- @property
- def vendor(self):
- """The vendor string, or None"""
- return self.__vendor
-
- @property
- def name(self):
- """The control name (CamelCase)"""
- return self.__name
-
- @property
- def type(self):
- typ = self.__data.get('type')
- size = self.__data.get('size')
-
- if typ == 'string':
- return 'std::string'
-
- if self.__size is None:
- return typ
-
- if self.__size:
- return f"Span<const {typ}, {self.__size}>"
- else:
- return f"Span<const {typ}>"
-
-
-def snake_case(s):
- return ''.join([c.isupper() and ('_' + c) or c for c in s]).strip('_')
-
-
-def format_description(description):
- description = description.strip('\n').split('\n')
- description[0] = '\\brief ' + description[0]
- return '\n'.join([(line and ' * ' or ' *') + line for line in description])
-
-
-def generate_cpp(controls):
- enum_doc_start_template = string.Template('''/**
- * \\enum ${name}Enum
- * \\brief Supported ${name} values''')
- enum_doc_value_template = string.Template(''' * \\var ${value}
-${description}''')
- doc_template = string.Template('''/**
- * \\var ${name}
-${description}
- */''')
- def_template = string.Template('extern const Control<${type}> ${name}(${id_name}, "${name}");')
- enum_values_doc = string.Template('''/**
- * \\var ${name}Values
- * \\brief List of all $name supported values
- */''')
- enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')
- enum_values_values = string.Template('''\tstatic_cast<int32_t>(${name}),''')
- name_value_map_doc = string.Template('''/**
- * \\var ${name}NameValueMap
- * \\brief Map of all $name supported value names (in std::string format) to value
- */''')
- name_value_map_start = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap = {''')
- name_value_values = string.Template('''\t{ "${name}", ${name} },''')
-
- ctrls_doc = {}
- ctrls_def = {}
- ctrls_map = []
-
- for ctrl in controls:
- id_name = snake_case(ctrl.name).upper()
-
- vendor = ctrl.vendor
- if vendor not in ctrls_doc:
- ctrls_doc[vendor] = []
- ctrls_def[vendor] = []
-
- info = {
- 'name': ctrl.name,
- 'type': ctrl.type,
- 'description': format_description(ctrl.description),
- 'id_name': id_name,
- }
-
- target_doc = ctrls_doc[vendor]
- target_def = ctrls_def[vendor]
-
- if ctrl.is_enum:
- enum_doc = []
- enum_doc.append(enum_doc_start_template.substitute(info))
-
- num_entries = 0
- for enum in ctrl.enum_values:
- value_info = {
- 'name': ctrl.name,
- 'value': enum.name,
- 'description': format_description(enum.description),
- }
- enum_doc.append(enum_doc_value_template.substitute(value_info))
- num_entries += 1
-
- enum_doc = '\n *\n'.join(enum_doc)
- enum_doc += '\n */'
- target_doc.append(enum_doc)
-
- values_info = {
- 'name': info['name'],
- 'type': ctrl.type,
- 'size': num_entries,
- }
- target_doc.append(enum_values_doc.substitute(values_info))
- target_def.append(enum_values_start.substitute(values_info))
- for enum in ctrl.enum_values:
- value_info = {
- 'name': enum.name
- }
- target_def.append(enum_values_values.substitute(value_info))
- target_def.append("};")
-
- target_doc.append(name_value_map_doc.substitute(values_info))
- target_def.append(name_value_map_start.substitute(values_info))
- for enum in ctrl.enum_values:
- value_info = {
- 'name': enum.name
- }
- target_def.append(name_value_values.substitute(value_info))
- target_def.append("};")
-
- target_doc.append(doc_template.substitute(info))
- target_def.append(def_template.substitute(info))
-
- vendor_ns = vendor + '::' if vendor != "libcamera" else ''
- ctrls_map.append('\t{ ' + vendor_ns + id_name + ', &' + vendor_ns + ctrl.name + ' },')
-
- vendor_ctrl_doc_sub = []
- vendor_ctrl_template = string.Template('''
-/**
- * \\brief Namespace for ${vendor} controls
- */
-namespace ${vendor} {
-
-${vendor_controls_str}
-
-} /* namespace ${vendor} */''')
-
- for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera']]:
- vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n\n'.join(ctrls_doc[vendor])}))
-
- vendor_ctrl_def_sub = []
- for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera']]:
- vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\n'.join(ctrls_def[vendor])}))
-
- return {
- 'controls_doc': '\n\n'.join(ctrls_doc['libcamera']),
- 'controls_def': '\n'.join(ctrls_def['libcamera']),
- 'controls_map': '\n'.join(ctrls_map),
- 'vendor_controls_doc': '\n'.join(vendor_ctrl_doc_sub),
- 'vendor_controls_def': '\n'.join(vendor_ctrl_def_sub),
- }
-
-
-def generate_h(controls, mode, ranges):
- enum_template_start = string.Template('''enum ${name}Enum {''')
- enum_value_template = string.Template('''\t${name} = ${value},''')
- enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')
- name_value_map_template = string.Template('''extern const std::map<std::string, ${type}> ${name}NameValueMap;''')
- template = string.Template('''extern const Control<${type}> ${name};''')
-
- ctrls = {}
- ids = {}
- id_value = {}
-
- for ctrl in controls:
- id_name = snake_case(ctrl.name).upper()
-
- vendor = ctrl.vendor
- if vendor not in ctrls:
- if vendor not in ranges.keys():
- raise RuntimeError(f'Control id range is not defined for vendor {vendor}')
- id_value[vendor] = ranges[vendor] + 1
- ids[vendor] = []
- ctrls[vendor] = []
-
- target_ids = ids[vendor]
- target_ids.append('\t' + id_name + ' = ' + str(id_value[vendor]) + ',')
-
- info = {
- 'name': ctrl.name,
- 'type': ctrl.type,
- }
-
- target_ctrls = ctrls[vendor]
-
- if ctrl.is_enum:
- target_ctrls.append(enum_template_start.substitute(info))
-
- num_entries = 0
- for enum in ctrl.enum_values:
- value_info = {
- 'name': enum.name,
- 'value': enum.value,
- }
- target_ctrls.append(enum_value_template.substitute(value_info))
- num_entries += 1
- target_ctrls.append("};")
-
- values_info = {
- 'name': info['name'],
- 'type': ctrl.type,
- 'size': num_entries,
- }
- target_ctrls.append(enum_values_template.substitute(values_info))
- target_ctrls.append(name_value_map_template.substitute(values_info))
-
- target_ctrls.append(template.substitute(info))
- id_value[vendor] += 1
-
- vendor_template = string.Template('''
-namespace ${vendor} {
-
-#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}
-
-enum {
-${vendor_enums}
-};
-
-${vendor_controls}
-
-} /* namespace ${vendor} */
-''')
-
- vendor_sub = []
- for vendor in [v for v in ctrls.keys() if v != 'libcamera']:
- vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),
- 'vendor': vendor,
- 'vendor_def': vendor.upper(),
- 'vendor_enums': '\n'.join(ids[vendor]),
- 'vendor_controls': '\n'.join(ctrls[vendor])}))
-
- return {
- 'ids': '\n'.join(ids['libcamera']),
- 'controls': '\n'.join(ctrls['libcamera']),
- 'vendor_controls': '\n'.join(vendor_sub)
- }
-
-
-def fill_template(template, data):
-
- template = open(template, 'rb').read()
- template = template.decode('utf-8')
- template = string.Template(template)
- return template.substitute(data)
-
-
-def main(argv):
-
- # Parse command line arguments
- parser = argparse.ArgumentParser()
- parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],
- help='Mode of operation')
- parser.add_argument('--output', '-o', metavar='file', type=str,
- help='Output file name. Defaults to standard output if not specified.')
- parser.add_argument('--ranges', '-r', type=str, required=True,
- help='Control id range reservation file.')
- parser.add_argument('--template', '-t', dest='template', type=str, required=True,
- help='Template file name.')
- parser.add_argument('input', type=str, nargs='+',
- help='Input file name.')
-
- args = parser.parse_args(argv[1:])
-
- ranges = {}
- with open(args.ranges, 'rb') as f:
- data = open(args.ranges, 'rb').read()
- ranges = yaml.safe_load(data)['ranges']
-
- controls = []
- for input in args.input:
- with open(input, 'rb') as f:
- data = f.read()
- vendor = yaml.safe_load(data)['vendor']
- ctrls = yaml.safe_load(data)['controls']
- controls = controls + [Control(*ctrl.popitem(), vendor) for ctrl in ctrls]
-
- if args.template.endswith('.cpp.in'):
- data = generate_cpp(controls)
- elif args.template.endswith('.h.in'):
- data = generate_h(controls, args.mode, ranges)
- else:
- raise RuntimeError('Unknown template type')
-
- data = fill_template(args.template, data)
-
- if args.output:
- output = open(args.output, 'wb')
- output.write(data.encode('utf-8'))
- output.close()
- else:
- sys.stdout.write(data)
-
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv))
diff --git a/utils/gen-version.sh b/utils/gen-version.sh
index e1f7ca7b..1b818e9e 100755
--- a/utils/gen-version.sh
+++ b/utils/gen-version.sh
@@ -42,7 +42,7 @@ if [ -z "$build_dir" ] || (echo "$build_dir" | grep -q "$src_dir")
then
git update-index --refresh > /dev/null 2>&1
fi
-git diff-index --quiet HEAD || version="$version-dirty ($(date --iso-8601=seconds))"
+git diff-index --quiet HEAD || version="$version-dirty ($(date +%Y-%m-%dT%H:%M:%S%Z))"
# If a project version is provided, use it to replace the version number.
if [ -n "$project_version" ]
diff --git a/utils/meson.build b/utils/meson.build
index 8e28ada7..95d657ac 100644
--- a/utils/meson.build
+++ b/utils/meson.build
@@ -1,15 +1,7 @@
# SPDX-License-Identifier: CC0-1.0
-subdir('ipc')
+subdir('codegen')
subdir('ipu3')
-subdir('tracepoints')
-
-## Code generation
-py_modules += ['yaml']
-gen_controls = files('gen-controls.py')
-gen_formats = files('gen-formats.py')
-gen_header = files('gen-header.sh')
## Module signing
gen_ipa_priv_key = files('gen-ipa-priv-key.sh')
-gen_ipa_pub_key = files('gen-ipa-pub-key.py')
diff --git a/utils/tracepoints/meson.build b/utils/tracepoints/meson.build
deleted file mode 100644
index 807230fc..00000000
--- a/utils/tracepoints/meson.build
+++ /dev/null
@@ -1,5 +0,0 @@
-# SPDX-License-Identifier: CC0-1.0
-
-py_modules += ['jinja2']
-
-gen_tracepoints_header = find_program('./gen-tp-header.py')
diff --git a/utils/tuning/README.rst b/utils/tuning/README.rst
index ef3e6ad7..89a1d61e 100644
--- a/utils/tuning/README.rst
+++ b/utils/tuning/README.rst
@@ -1,11 +1,20 @@
.. SPDX-License-Identifier: CC-BY-SA-4.0
-.. TODO: Write an overview of libtuning
+libcamera tuning tools
+======================
-Dependencies
-------------
+.. Note:: The tuning tools are still very much work in progress. If in doubt,
+ please ask on the mailing list.
+
+.. todo::
+ Write documentation
+
+Installation of dependencies
+----------------------------
+
+::
+ # Using a venv
+ python3 -m venv venv
+ . ./venv/bin/activate
+ pip3 install -r requirements.txt
-- numpy
-- opencv-python
-- py3exiv2
-- rawpy
diff --git a/utils/tuning/config-example.yaml b/utils/tuning/config-example.yaml
new file mode 100644
index 00000000..1b7f52cd
--- /dev/null
+++ b/utils/tuning/config-example.yaml
@@ -0,0 +1,12 @@
+general:
+ disable: []
+ plot: []
+ alsc:
+ do_alsc_colour: 1
+ luminance_strength: 0.5
+ awb:
+ greyworld: 0
+ macbeth:
+ small: 1
+ show: 0
+# blacklevel: 32 \ No newline at end of file
diff --git a/utils/tuning/libtuning/ctt_awb.py b/utils/tuning/libtuning/ctt_awb.py
new file mode 100644
index 00000000..abf22321
--- /dev/null
+++ b/utils/tuning/libtuning/ctt_awb.py
@@ -0,0 +1,378 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+#
+# camera tuning tool for AWB
+
+import matplotlib.pyplot as plt
+from bisect import bisect_left
+from scipy.optimize import fmin
+import numpy as np
+
+from .image import Image
+
+
+"""
+obtain piecewise linear approximation for colour curve
+"""
+def awb(Cam, cal_cr_list, cal_cb_list, plot):
+ imgs = Cam.imgs
+ """
+ condense alsc calibration tables into one dictionary
+ """
+ if cal_cr_list is None:
+ colour_cals = None
+ else:
+ colour_cals = {}
+ for cr, cb in zip(cal_cr_list, cal_cb_list):
+ cr_tab = cr['table']
+ cb_tab = cb['table']
+ """
+ normalise tables so min value is 1
+ """
+ cr_tab = cr_tab/np.min(cr_tab)
+ cb_tab = cb_tab/np.min(cb_tab)
+ colour_cals[cr['ct']] = [cr_tab, cb_tab]
+ """
+ obtain data from greyscale macbeth patches
+ """
+ rb_raw = []
+ rbs_hat = []
+ for Img in imgs:
+ Cam.log += '\nProcessing '+Img.name
+ """
+ get greyscale patches with alsc applied if alsc enabled.
+ Note: if alsc is disabled then colour_cals will be set to None and the
+ function will just return the greyscale patches
+ """
+ r_patchs, b_patchs, g_patchs = get_alsc_patches(Img, colour_cals)
+ """
+ calculate ratio of r, b to g
+ """
+ r_g = np.mean(r_patchs/g_patchs)
+ b_g = np.mean(b_patchs/g_patchs)
+ Cam.log += '\n r : {:.4f} b : {:.4f}'.format(r_g, b_g)
+ """
+ The curve tends to be better behaved in so-called hatspace.
+ R, B, G represent the individual channels. The colour curve is plotted in
+ r, b space, where:
+ r = R/G
+ b = B/G
+ This will be referred to as dehatspace... (sorry)
+ Hatspace is defined as:
+ r_hat = R/(R+B+G)
+ b_hat = B/(R+B+G)
+ To convert from dehatspace to hastpace (hat operation):
+ r_hat = r/(1+r+b)
+ b_hat = b/(1+r+b)
+ To convert from hatspace to dehatspace (dehat operation):
+ r = r_hat/(1-r_hat-b_hat)
+ b = b_hat/(1-r_hat-b_hat)
+ Proof is left as an excercise to the reader...
+ Throughout the code, r and b are sometimes referred to as r_g and b_g
+ as a reminder that they are ratios
+ """
+ r_g_hat = r_g/(1+r_g+b_g)
+ b_g_hat = b_g/(1+r_g+b_g)
+ Cam.log += '\n r_hat : {:.4f} b_hat : {:.4f}'.format(r_g_hat, b_g_hat)
+ rbs_hat.append((r_g_hat, b_g_hat, Img.col))
+ rb_raw.append((r_g, b_g))
+ Cam.log += '\n'
+
+ Cam.log += '\nFinished processing images'
+ """
+ sort all lits simultaneously by r_hat
+ """
+ rbs_zip = list(zip(rbs_hat, rb_raw))
+ rbs_zip.sort(key=lambda x: x[0][0])
+ rbs_hat, rb_raw = list(zip(*rbs_zip))
+ """
+ unzip tuples ready for processing
+ """
+ rbs_hat = list(zip(*rbs_hat))
+ rb_raw = list(zip(*rb_raw))
+ """
+ fit quadratic fit to r_g hat and b_g_hat
+ """
+ a, b, c = np.polyfit(rbs_hat[0], rbs_hat[1], 2)
+ Cam.log += '\nFit quadratic curve in hatspace'
+ """
+ the algorithm now approximates the shortest distance from each point to the
+ curve in dehatspace. Since the fit is done in hatspace, it is easier to
+ find the actual shortest distance in hatspace and use the projection back
+ into dehatspace as an overestimate.
+ The distance will be used for two things:
+ 1) In the case that colour temperature does not strictly decrease with
+ increasing r/g, the closest point to the line will be chosen out of an
+ increasing pair of colours.
+
+ 2) To calculate transverse negative an dpositive, the maximum positive
+ and negative distance from the line are chosen. This benefits from the
+ overestimate as the transverse pos/neg are upper bound values.
+ """
+ """
+ define fit function
+ """
+ def f(x):
+ return a*x**2 + b*x + c
+ """
+ iterate over points (R, B are x and y coordinates of points) and calculate
+ distance to line in dehatspace
+ """
+ dists = []
+ for i, (R, B) in enumerate(zip(rbs_hat[0], rbs_hat[1])):
+ """
+ define function to minimise as square distance between datapoint and
+ point on curve. Squaring is monotonic so minimising radius squared is
+ equivalent to minimising radius
+ """
+ def f_min(x):
+ y = f(x)
+ return((x-R)**2+(y-B)**2)
+ """
+ perform optimisation with scipy.optmisie.fmin
+ """
+ x_hat = fmin(f_min, R, disp=0)[0]
+ y_hat = f(x_hat)
+ """
+ dehat
+ """
+ x = x_hat/(1-x_hat-y_hat)
+ y = y_hat/(1-x_hat-y_hat)
+ rr = R/(1-R-B)
+ bb = B/(1-R-B)
+ """
+ calculate euclidean distance in dehatspace
+ """
+ dist = ((x-rr)**2+(y-bb)**2)**0.5
+ """
+ return negative if point is below the fit curve
+ """
+ if (x+y) > (rr+bb):
+ dist *= -1
+ dists.append(dist)
+ Cam.log += '\nFound closest point on fit line to each point in dehatspace'
+ """
+ calculate wiggle factors in awb. 10% added since this is an upper bound
+ """
+ transverse_neg = - np.min(dists) * 1.1
+ transverse_pos = np.max(dists) * 1.1
+ Cam.log += '\nTransverse pos : {:.5f}'.format(transverse_pos)
+ Cam.log += '\nTransverse neg : {:.5f}'.format(transverse_neg)
+ """
+ set minimum transverse wiggles to 0.1 .
+ Wiggle factors dictate how far off of the curve the algorithm searches. 0.1
+ is a suitable minimum that gives better results for lighting conditions not
+ within calibration dataset. Anything less will generalise poorly.
+ """
+ if transverse_pos < 0.01:
+ transverse_pos = 0.01
+ Cam.log += '\nForced transverse pos to 0.01'
+ if transverse_neg < 0.01:
+ transverse_neg = 0.01
+ Cam.log += '\nForced transverse neg to 0.01'
+
+ """
+ generate new b_hat values at each r_hat according to fit
+ """
+ r_hat_fit = np.array(rbs_hat[0])
+ b_hat_fit = a*r_hat_fit**2 + b*r_hat_fit + c
+ """
+ transform from hatspace to dehatspace
+ """
+ r_fit = r_hat_fit/(1-r_hat_fit-b_hat_fit)
+ b_fit = b_hat_fit/(1-r_hat_fit-b_hat_fit)
+ c_fit = np.round(rbs_hat[2], 0)
+ """
+ round to 4dp
+ """
+ r_fit = np.where((1000*r_fit) % 1 <= 0.05, r_fit+0.0001, r_fit)
+ r_fit = np.where((1000*r_fit) % 1 >= 0.95, r_fit-0.0001, r_fit)
+ b_fit = np.where((1000*b_fit) % 1 <= 0.05, b_fit+0.0001, b_fit)
+ b_fit = np.where((1000*b_fit) % 1 >= 0.95, b_fit-0.0001, b_fit)
+ r_fit = np.round(r_fit, 4)
+ b_fit = np.round(b_fit, 4)
+ """
+ The following code ensures that colour temperature decreases with
+ increasing r/g
+ """
+ """
+ iterate backwards over list for easier indexing
+ """
+ i = len(c_fit) - 1
+ while i > 0:
+ if c_fit[i] > c_fit[i-1]:
+ Cam.log += '\nColour temperature increase found\n'
+ Cam.log += '{} K at r = {} to '.format(c_fit[i-1], r_fit[i-1])
+ Cam.log += '{} K at r = {}'.format(c_fit[i], r_fit[i])
+ """
+ if colour temperature increases then discard point furthest from
+ the transformed fit (dehatspace)
+ """
+ error_1 = abs(dists[i-1])
+ error_2 = abs(dists[i])
+ Cam.log += '\nDistances from fit:\n'
+ Cam.log += '{} K : {:.5f} , '.format(c_fit[i], error_1)
+ Cam.log += '{} K : {:.5f}'.format(c_fit[i-1], error_2)
+ """
+ find bad index
+ note that in python false = 0 and true = 1
+ """
+ bad = i - (error_1 < error_2)
+ Cam.log += '\nPoint at {} K deleted as '.format(c_fit[bad])
+ Cam.log += 'it is furthest from fit'
+ """
+ delete bad point
+ """
+ r_fit = np.delete(r_fit, bad)
+ b_fit = np.delete(b_fit, bad)
+ c_fit = np.delete(c_fit, bad).astype(np.uint16)
+ """
+ note that if a point has been discarded then the length has decreased
+ by one, meaning that decreasing the index by one will reassess the kept
+ point against the next point. It is therefore possible, in theory, for
+ two adjacent points to be discarded, although probably rare
+ """
+ i -= 1
+
+ """
+ return formatted ct curve, ordered by increasing colour temperature
+ """
+ ct_curve = list(np.array(list(zip(b_fit, r_fit, c_fit))).flatten())[::-1]
+ Cam.log += '\nFinal CT curve:'
+ for i in range(len(ct_curve)//3):
+ j = 3*i
+ Cam.log += '\n ct: {} '.format(ct_curve[j])
+ Cam.log += ' r: {} '.format(ct_curve[j+1])
+ Cam.log += ' b: {} '.format(ct_curve[j+2])
+
+ """
+ plotting code for debug
+ """
+ if plot:
+ x = np.linspace(np.min(rbs_hat[0]), np.max(rbs_hat[0]), 100)
+ y = a*x**2 + b*x + c
+ plt.subplot(2, 1, 1)
+ plt.title('hatspace')
+ plt.plot(rbs_hat[0], rbs_hat[1], ls='--', color='blue')
+ plt.plot(x, y, color='green', ls='-')
+ plt.scatter(rbs_hat[0], rbs_hat[1], color='red')
+ for i, ct in enumerate(rbs_hat[2]):
+ plt.annotate(str(ct), (rbs_hat[0][i], rbs_hat[1][i]))
+ plt.xlabel('$\\hat{r}$')
+ plt.ylabel('$\\hat{b}$')
+ """
+ optional set axes equal to shortest distance so line really does
+ looks perpendicular and everybody is happy
+ """
+ # ax = plt.gca()
+ # ax.set_aspect('equal')
+ plt.grid()
+ plt.subplot(2, 1, 2)
+ plt.title('dehatspace - indoors?')
+ plt.plot(r_fit, b_fit, color='blue')
+ plt.scatter(rb_raw[0], rb_raw[1], color='green')
+ plt.scatter(r_fit, b_fit, color='red')
+ for i, ct in enumerate(c_fit):
+ plt.annotate(str(ct), (r_fit[i], b_fit[i]))
+ plt.xlabel('$r$')
+ plt.ylabel('$b$')
+ """
+ optional set axes equal to shortest distance so line really does
+ looks perpendicular and everybody is happy
+ """
+ # ax = plt.gca()
+ # ax.set_aspect('equal')
+ plt.subplots_adjust(hspace=0.5)
+ plt.grid()
+ plt.show()
+ """
+ end of plotting code
+ """
+ return(ct_curve, np.round(transverse_pos, 5), np.round(transverse_neg, 5))
+
+
+"""
+obtain greyscale patches and perform alsc colour correction
+"""
+def get_alsc_patches(Img, colour_cals, grey=True):
+ """
+ get patch centre coordinates, image colour and the actual
+ patches for each channel, remembering to subtract blacklevel
+ If grey then only greyscale patches considered
+ """
+ if grey:
+ cen_coords = Img.cen_coords[3::4]
+ col = Img.col
+ patches = [np.array(Img.patches[i]) for i in Img.order]
+ r_patchs = patches[0][3::4] - Img.blacklevel_16
+ b_patchs = patches[3][3::4] - Img.blacklevel_16
+ """
+ note two green channels are averages
+ """
+ g_patchs = (patches[1][3::4]+patches[2][3::4])/2 - Img.blacklevel_16
+ else:
+ cen_coords = Img.cen_coords
+ col = Img.color
+ patches = [np.array(Img.patches[i]) for i in Img.order]
+ r_patchs = patches[0] - Img.blacklevel_16
+ b_patchs = patches[3] - Img.blacklevel_16
+ g_patchs = (patches[1]+patches[2])/2 - Img.blacklevel_16
+
+ if colour_cals is None:
+ return r_patchs, b_patchs, g_patchs
+ """
+ find where image colour fits in alsc colour calibration tables
+ """
+ cts = list(colour_cals.keys())
+ pos = bisect_left(cts, col)
+ """
+ if img colour is below minimum or above maximum alsc calibration colour, simply
+ pick extreme closest to img colour
+ """
+ if pos % len(cts) == 0:
+ """
+ this works because -0 = 0 = first and -1 = last index
+ """
+ col_tabs = np.array(colour_cals[cts[-pos//len(cts)]])
+ """
+ else, perform linear interpolation between existing alsc colour
+ calibration tables
+ """
+ else:
+ bef = cts[pos-1]
+ aft = cts[pos]
+ da = col-bef
+ db = aft-col
+ bef_tabs = np.array(colour_cals[bef])
+ aft_tabs = np.array(colour_cals[aft])
+ col_tabs = (bef_tabs*db + aft_tabs*da)/(da+db)
+ col_tabs = np.reshape(col_tabs, (2, 12, 16))
+ """
+ calculate dx, dy used to calculate alsc table
+ """
+ w, h = Img.w/2, Img.h/2
+ dx, dy = int(-(-(w-1)//16)), int(-(-(h-1)//12))
+ """
+ make list of pairs of gains for each patch by selecting the correct value
+ in alsc colour calibration table
+ """
+ patch_gains = []
+ for cen in cen_coords:
+ x, y = cen[0]//dx, cen[1]//dy
+ # We could probably do with some better spatial interpolation here?
+ col_gains = (col_tabs[0][y][x], col_tabs[1][y][x])
+ patch_gains.append(col_gains)
+
+ """
+ multiply the r and b channels in each patch by the respective gain, finally
+ performing the alsc colour correction
+ """
+ for i, gains in enumerate(patch_gains):
+ r_patchs[i] = r_patchs[i] * gains[0]
+ b_patchs[i] = b_patchs[i] * gains[1]
+
+ """
+ return greyscale patches, g channel and correct r, b channels
+ """
+ return r_patchs, b_patchs, g_patchs
diff --git a/utils/tuning/libtuning/ctt_ccm.py b/utils/tuning/libtuning/ctt_ccm.py
new file mode 100644
index 00000000..2e87a667
--- /dev/null
+++ b/utils/tuning/libtuning/ctt_ccm.py
@@ -0,0 +1,408 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+#
+# camera tuning tool for CCM (colour correction matrix)
+
+import logging
+
+import numpy as np
+from scipy.optimize import minimize
+
+from . import ctt_colors as colors
+from .image import Image
+from .ctt_awb import get_alsc_patches
+from .utils import visualise_macbeth_chart
+
+logger = logging.getLogger(__name__)
+
+"""
+takes 8-bit macbeth chart values, degammas and returns 16 bit
+"""
+
+'''
+This program has many options from which to derive the color matrix from.
+The first is average. This minimises the average delta E across all patches of
+the macbeth chart. Testing across all cameras yeilded this as the most color
+accurate and vivid. Other options are avalible however.
+Maximum minimises the maximum Delta E of the patches. It iterates through till
+a minimum maximum is found (so that there is
+not one patch that deviates wildly.)
+This yields generally good results but overall the colors are less accurate
+Have a fiddle with maximum and see what you think.
+The final option allows you to select the patches for which to average across.
+This means that you can bias certain patches, for instance if you want the
+reds to be more accurate.
+'''
+
+matrix_selection_types = ["average", "maximum", "patches"]
+typenum = 0 # select from array above, 0 = average, 1 = maximum, 2 = patches
+test_patches = [1, 2, 5, 8, 9, 12, 14]
+
+'''
+Enter patches to test for. Can also be entered twice if you
+would like twice as much bias on one patch.
+'''
+
+
+def degamma(x):
+ x = x / ((2 ** 8) - 1) # takes 255 and scales it down to one
+ x = np.where(x < 0.04045, x / 12.92, ((x + 0.055) / 1.055) ** 2.4)
+ x = x * ((2 ** 16) - 1) # takes one and scales up to 65535, 16 bit color
+ return x
+
+
+def gamma(x):
+ # Take 3 long array of color values and gamma them
+ return [((colour / 255) ** (1 / 2.4) * 1.055 - 0.055) * 255 for colour in x]
+
+
+"""
+FInds colour correction matrices for list of images
+"""
+
+
+def ccm(imgs, cal_cr_list, cal_cb_list):
+ global matrix_selection_types, typenum
+ """
+ standard macbeth chart colour values
+ """
+ m_rgb = np.array([ # these are in RGB
+ [116, 81, 67], # dark skin
+ [199, 147, 129], # light skin
+ [91, 122, 156], # blue sky
+ [90, 108, 64], # foliage
+ [130, 128, 176], # blue flower
+ [92, 190, 172], # bluish green
+ [224, 124, 47], # orange
+ [68, 91, 170], # purplish blue
+ [198, 82, 97], # moderate red
+ [94, 58, 106], # purple
+ [159, 189, 63], # yellow green
+ [230, 162, 39], # orange yellow
+ [35, 63, 147], # blue
+ [67, 149, 74], # green
+ [180, 49, 57], # red
+ [238, 198, 20], # yellow
+ [193, 84, 151], # magenta
+ [0, 136, 170], # cyan (goes out of gamut)
+ [245, 245, 243], # white 9.5
+ [200, 202, 202], # neutral 8
+ [161, 163, 163], # neutral 6.5
+ [121, 121, 122], # neutral 5
+ [82, 84, 86], # neutral 3.5
+ [49, 49, 51] # black 2
+ ])
+ """
+ convert reference colours from srgb to rgb
+ """
+ m_srgb = degamma(m_rgb) # now in 16 bit color.
+
+ # Produce array of LAB values for ideal color chart
+ m_lab = [colors.RGB_to_LAB(color / 256) for color in m_srgb]
+
+ """
+ reorder reference values to match how patches are ordered
+ """
+ m_srgb = np.array([m_srgb[i::6] for i in range(6)]).reshape((24, 3))
+ m_lab = np.array([m_lab[i::6] for i in range(6)]).reshape((24, 3))
+ m_rgb = np.array([m_rgb[i::6] for i in range(6)]).reshape((24, 3))
+ """
+ reformat alsc correction tables or set colour_cals to None if alsc is
+ deactivated
+ """
+ if cal_cr_list is None:
+ colour_cals = None
+ else:
+ colour_cals = {}
+ for cr, cb in zip(cal_cr_list, cal_cb_list):
+ cr_tab = cr['table']
+ cb_tab = cb['table']
+ """
+ normalise tables so min value is 1
+ """
+ cr_tab = cr_tab / np.min(cr_tab)
+ cb_tab = cb_tab / np.min(cb_tab)
+ colour_cals[cr['ct']] = [cr_tab, cb_tab]
+
+ """
+ for each image, perform awb and alsc corrections.
+ Then calculate the colour correction matrix for that image, recording the
+ ccm and the colour tempertaure.
+ """
+ ccm_tab = {}
+ for Img in imgs:
+ logger.info('Processing image: ' + Img.name)
+ """
+ get macbeth patches with alsc applied if alsc enabled.
+ Note: if alsc is disabled then colour_cals will be set to None and no
+ the function will simply return the macbeth patches
+ """
+ r, b, g = get_alsc_patches(Img, colour_cals, grey=False)
+ # 256 values for each patch of sRGB values
+
+ """
+ do awb
+ Note: awb is done by measuring the macbeth chart in the image, rather
+ than from the awb calibration. This is done so the awb will be perfect
+ and the ccm matrices will be more accurate.
+ """
+ r_greys, b_greys, g_greys = r[3::4], b[3::4], g[3::4]
+ r_g = np.mean(r_greys / g_greys)
+ b_g = np.mean(b_greys / g_greys)
+ r = r / r_g
+ b = b / b_g
+ """
+ normalise brightness wrt reference macbeth colours and then average
+ each channel for each patch
+ """
+ gain = np.mean(m_srgb) / np.mean((r, g, b))
+ logger.info(f'Gain with respect to standard colours: {gain:.3f}')
+ r = np.mean(gain * r, axis=1)
+ b = np.mean(gain * b, axis=1)
+ g = np.mean(gain * g, axis=1)
+ """
+ calculate ccm matrix
+ """
+ # ==== All of below should in sRGB ===##
+ sumde = 0
+ ccm = do_ccm(r, g, b, m_srgb)
+ # This is the initial guess that our optimisation code works with.
+ original_ccm = ccm
+ r1 = ccm[0]
+ r2 = ccm[1]
+ g1 = ccm[3]
+ g2 = ccm[4]
+ b1 = ccm[6]
+ b2 = ccm[7]
+ '''
+ COLOR MATRIX LOOKS AS BELOW
+ R1 R2 R3 Rval Outr
+ G1 G2 G3 * Gval = G
+ B1 B2 B3 Bval B
+ Will be optimising 6 elements and working out the third element using 1-r1-r2 = r3
+ '''
+
+ x0 = [r1, r2, g1, g2, b1, b2]
+ '''
+ We use our old CCM as the initial guess for the program to find the
+ optimised matrix
+ '''
+ result = minimize(guess, x0, args=(r, g, b, m_lab), tol=0.01)
+ '''
+ This produces a color matrix which has the lowest delta E possible,
+ based off the input data. Note it is impossible for this to reach
+ zero since the input data is imperfect
+ '''
+
+ [r1, r2, g1, g2, b1, b2] = result.x
+ # The new, optimised color correction matrix values
+ # This is the optimised Color Matrix (preserving greys by summing rows up to 1)
+ optimised_ccm = [r1, r2, (1 - r1 - r2), g1, g2, (1 - g1 - g2), b1, b2, (1 - b1 - b2)]
+
+ logger.info(f'Optimized Matrix: {np.round(optimised_ccm, 4)}')
+ logger.info(f'Old Matrix: {np.round(ccm, 4)}')
+
+ formatted_ccm = np.array(original_ccm).reshape((3, 3))
+
+ '''
+ below is a whole load of code that then applies the latest color
+ matrix, and returns LAB values for color. This can then be used
+ to calculate the final delta E
+ '''
+ optimised_ccm_rgb = [] # Original Color Corrected Matrix RGB / LAB
+ optimised_ccm_lab = []
+
+ formatted_optimised_ccm = np.array(optimised_ccm).reshape((3, 3))
+ after_gamma_rgb = []
+ after_gamma_lab = []
+
+ for RGB in zip(r, g, b):
+ ccm_applied_rgb = np.dot(formatted_ccm, (np.array(RGB) / 256))
+ optimised_ccm_rgb.append(gamma(ccm_applied_rgb))
+ optimised_ccm_lab.append(colors.RGB_to_LAB(ccm_applied_rgb))
+
+ optimised_ccm_applied_rgb = np.dot(formatted_optimised_ccm, np.array(RGB) / 256)
+ after_gamma_rgb.append(gamma(optimised_ccm_applied_rgb))
+ after_gamma_lab.append(colors.RGB_to_LAB(optimised_ccm_applied_rgb))
+ '''
+ Gamma After RGB / LAB - not used in calculations, only used for visualisation
+ We now want to spit out some data that shows
+ how the optimisation has improved the color matrices
+ '''
+ logger.info("Here are the Improvements")
+
+ # CALCULATE WORST CASE delta e
+ old_worst_delta_e = 0
+ before_average = transform_and_evaluate(formatted_ccm, r, g, b, m_lab)
+ new_worst_delta_e = 0
+ after_average = transform_and_evaluate(formatted_optimised_ccm, r, g, b, m_lab)
+ for i in range(24):
+ old_delta_e = deltae(optimised_ccm_lab[i], m_lab[i]) # Current Old Delta E
+ new_delta_e = deltae(after_gamma_lab[i], m_lab[i]) # Current New Delta E
+ if old_delta_e > old_worst_delta_e:
+ old_worst_delta_e = old_delta_e
+ if new_delta_e > new_worst_delta_e:
+ new_worst_delta_e = new_delta_e
+
+ logger.info(f'delta E optimized: average: {after_average:.2f} max:{new_worst_delta_e:.2f}')
+ logger.info(f'delta E old: average: {before_average:.2f} max:{old_worst_delta_e:.2f}')
+
+ visualise_macbeth_chart(m_rgb, optimised_ccm_rgb, after_gamma_rgb, str(Img.color) + str(matrix_selection_types[typenum]))
+ '''
+ The program will also save some visualisations of improvements.
+ Very pretty to look at. Top rectangle is ideal, Left square is
+ before optimisation, right square is after.
+ '''
+
+ """
+ if a ccm has already been calculated for that temperature then don't
+ overwrite but save both. They will then be averaged later on
+ """ # Now going to use optimised color matrix, optimised_ccm
+ if Img.color in ccm_tab.keys():
+ ccm_tab[Img.color].append(optimised_ccm)
+ else:
+ ccm_tab[Img.color] = [optimised_ccm]
+
+ logger.info('Finished processing images')
+ """
+ average any ccms that share a colour temperature
+ """
+ for k, v in ccm_tab.items():
+ tab = np.mean(v, axis=0)
+ tab = np.where((10000 * tab) % 1 <= 0.05, tab + 0.00001, tab)
+ tab = np.where((10000 * tab) % 1 >= 0.95, tab - 0.00001, tab)
+ ccm_tab[k] = list(np.round(tab, 5))
+ logger.info(f'Matrix calculated for colour temperature of {k} K')
+
+ """
+ return all ccms with respective colour temperature in the correct format,
+ sorted by their colour temperature
+ """
+ sorted_ccms = sorted(ccm_tab.items(), key=lambda kv: kv[0])
+ ccms = []
+ for i in sorted_ccms:
+ ccms.append({
+ 'ct': i[0],
+ 'ccm': i[1]
+ })
+ return ccms
+
+
+def guess(x0, r, g, b, m_lab): # provides a method of numerical feedback for the optimisation code
+ [r1, r2, g1, g2, b1, b2] = x0
+ ccm = np.array([r1, r2, (1 - r1 - r2),
+ g1, g2, (1 - g1 - g2),
+ b1, b2, (1 - b1 - b2)]).reshape((3, 3)) # format the matrix correctly
+ return transform_and_evaluate(ccm, r, g, b, m_lab)
+
+
+def transform_and_evaluate(ccm, r, g, b, m_lab): # Transforms colors to LAB and applies the correction matrix
+ # create list of matrix changed colors
+ realrgb = []
+ for RGB in zip(r, g, b):
+ rgb_post_ccm = np.dot(ccm, np.array(RGB) / 256) # This is RGB values after the color correction matrix has been applied
+ realrgb.append(colors.RGB_to_LAB(rgb_post_ccm))
+ # now compare that with m_lab and return numeric result, averaged for each patch
+ return (sumde(realrgb, m_lab) / 24) # returns an average result of delta E
+
+
+def sumde(listA, listB):
+ global typenum, test_patches
+ sumde = 0
+ maxde = 0
+ patchde = [] # Create array of the delta E values for each patch. useful for optimisation of certain patches
+ for listA_item, listB_item in zip(listA, listB):
+ if maxde < (deltae(listA_item, listB_item)):
+ maxde = deltae(listA_item, listB_item)
+ patchde.append(deltae(listA_item, listB_item))
+ sumde += deltae(listA_item, listB_item)
+ '''
+ The different options specified at the start allow for
+ the maximum to be returned, average or specific patches
+ '''
+ if typenum == 0:
+ return sumde
+ if typenum == 1:
+ return maxde
+ if typenum == 2:
+ output = sum([patchde[test_patch] for test_patch in test_patches])
+ # Selects only certain patches and returns the output for them
+ return output
+
+
+"""
+calculates the ccm for an individual image.
+ccms are calculated in rgb space, and are fit by hand. Although it is a 3x3
+matrix, each row must add up to 1 in order to conserve greyness, simplifying
+calculation.
+The initial CCM is calculated in RGB, and then optimised in LAB color space
+This simplifies the initial calculation but then gets us the accuracy of
+using LAB color space.
+"""
+
+
+def do_ccm(r, g, b, m_srgb):
+ rb = r-b
+ gb = g-b
+ rb_2s = (rb * rb)
+ rb_gbs = (rb * gb)
+ gb_2s = (gb * gb)
+
+ r_rbs = rb * (m_srgb[..., 0] - b)
+ r_gbs = gb * (m_srgb[..., 0] - b)
+ g_rbs = rb * (m_srgb[..., 1] - b)
+ g_gbs = gb * (m_srgb[..., 1] - b)
+ b_rbs = rb * (m_srgb[..., 2] - b)
+ b_gbs = gb * (m_srgb[..., 2] - b)
+
+ """
+ Obtain least squares fit
+ """
+ rb_2 = np.sum(rb_2s)
+ gb_2 = np.sum(gb_2s)
+ rb_gb = np.sum(rb_gbs)
+ r_rb = np.sum(r_rbs)
+ r_gb = np.sum(r_gbs)
+ g_rb = np.sum(g_rbs)
+ g_gb = np.sum(g_gbs)
+ b_rb = np.sum(b_rbs)
+ b_gb = np.sum(b_gbs)
+
+ det = rb_2 * gb_2 - rb_gb * rb_gb
+
+ """
+ Raise error if matrix is singular...
+ This shouldn't really happen with real data but if it does just take new
+ pictures and try again, not much else to be done unfortunately...
+ """
+ if det < 0.001:
+ raise ArithmeticError
+
+ r_a = (gb_2 * r_rb - rb_gb * r_gb) / det
+ r_b = (rb_2 * r_gb - rb_gb * r_rb) / det
+ """
+ Last row can be calculated by knowing the sum must be 1
+ """
+ r_c = 1 - r_a - r_b
+
+ g_a = (gb_2 * g_rb - rb_gb * g_gb) / det
+ g_b = (rb_2 * g_gb - rb_gb * g_rb) / det
+ g_c = 1 - g_a - g_b
+
+ b_a = (gb_2 * b_rb - rb_gb * b_gb) / det
+ b_b = (rb_2 * b_gb - rb_gb * b_rb) / det
+ b_c = 1 - b_a - b_b
+
+ """
+ format ccm
+ """
+ ccm = [r_a, r_b, r_c, g_a, g_b, g_c, b_a, b_b, b_c]
+
+ return ccm
+
+
+def deltae(colorA, colorB):
+ return ((colorA[0] - colorB[0]) ** 2 + (colorA[1] - colorB[1]) ** 2 + (colorA[2] - colorB[2]) ** 2) ** 0.5
+ # return ((colorA[1]-colorB[1]) * * 2 + (colorA[2]-colorB[2]) * * 2) * * 0.5
+ # UNCOMMENT IF YOU WANT TO NEGLECT LUMINANCE FROM CALCULATION OF DELTA E
diff --git a/utils/tuning/libtuning/ctt_colors.py b/utils/tuning/libtuning/ctt_colors.py
new file mode 100644
index 00000000..cb4d236b
--- /dev/null
+++ b/utils/tuning/libtuning/ctt_colors.py
@@ -0,0 +1,30 @@
+# Program to convert from RGB to LAB color space
+def RGB_to_LAB(RGB): # where RGB is a 1x3 array. e.g RGB = [100, 255, 230]
+ num = 0
+ XYZ = [0, 0, 0]
+ # converted all the three R, G, B to X, Y, Z
+ X = RGB[0] * 0.4124 + RGB[1] * 0.3576 + RGB[2] * 0.1805
+ Y = RGB[0] * 0.2126 + RGB[1] * 0.7152 + RGB[2] * 0.0722
+ Z = RGB[0] * 0.0193 + RGB[1] * 0.1192 + RGB[2] * 0.9505
+
+ XYZ[0] = X / 255 * 100
+ XYZ[1] = Y / 255 * 100 # XYZ Must be in range 0 -> 100, so scale down from 255
+ XYZ[2] = Z / 255 * 100
+ XYZ[0] = XYZ[0] / 95.047 # ref_X = 95.047 Observer= 2°, Illuminant= D65
+ XYZ[1] = XYZ[1] / 100.0 # ref_Y = 100.000
+ XYZ[2] = XYZ[2] / 108.883 # ref_Z = 108.883
+ num = 0
+ for value in XYZ:
+ if value > 0.008856:
+ value = value ** (0.3333333333333333)
+ else:
+ value = (7.787 * value) + (16 / 116)
+ XYZ[num] = value
+ num = num + 1
+
+ # L, A, B, values calculated below
+ L = (116 * XYZ[1]) - 16
+ a = 500 * (XYZ[0] - XYZ[1])
+ b = 200 * (XYZ[1] - XYZ[2])
+
+ return [L, a, b]
diff --git a/utils/tuning/libtuning/ctt_ransac.py b/utils/tuning/libtuning/ctt_ransac.py
new file mode 100644
index 00000000..01bba302
--- /dev/null
+++ b/utils/tuning/libtuning/ctt_ransac.py
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (C) 2019, Raspberry Pi Ltd
+#
+# camera tuning tool RANSAC selector for Macbeth chart locator
+
+import numpy as np
+
+scale = 2
+
+
+"""
+constructs normalised macbeth chart corners for ransac algorithm
+"""
+def get_square_verts(c_err=0.05, scale=scale):
+ """
+ define macbeth chart corners
+ """
+ b_bord_x, b_bord_y = scale*8.5, scale*13
+ s_bord = 6*scale
+ side = 41*scale
+ x_max = side*6 + 5*s_bord + 2*b_bord_x
+ y_max = side*4 + 3*s_bord + 2*b_bord_y
+ c1 = (0, 0)
+ c2 = (0, y_max)
+ c3 = (x_max, y_max)
+ c4 = (x_max, 0)
+ mac_norm = np.array((c1, c2, c3, c4), np.float32)
+ mac_norm = np.array([mac_norm])
+
+ square_verts = []
+ square_0 = np.array(((0, 0), (0, side),
+ (side, side), (side, 0)), np.float32)
+ offset_0 = np.array((b_bord_x, b_bord_y), np.float32)
+ c_off = side * c_err
+ offset_cont = np.array(((c_off, c_off), (c_off, -c_off),
+ (-c_off, -c_off), (-c_off, c_off)), np.float32)
+ square_0 += offset_0
+ square_0 += offset_cont
+ """
+ define macbeth square corners
+ """
+ for i in range(6):
+ shift_i = np.array(((i*side, 0), (i*side, 0),
+ (i*side, 0), (i*side, 0)), np.float32)
+ shift_bord = np.array(((i*s_bord, 0), (i*s_bord, 0),
+ (i*s_bord, 0), (i*s_bord, 0)), np.float32)
+ square_i = square_0 + shift_i + shift_bord
+ for j in range(4):
+ shift_j = np.array(((0, j*side), (0, j*side),
+ (0, j*side), (0, j*side)), np.float32)
+ shift_bord = np.array(((0, j*s_bord),
+ (0, j*s_bord), (0, j*s_bord),
+ (0, j*s_bord)), np.float32)
+ square_j = square_i + shift_j + shift_bord
+ square_verts.append(square_j)
+ # print('square_verts')
+ # print(square_verts)
+ return np.array(square_verts, np.float32), mac_norm
+
+
+def get_square_centres(c_err=0.05, scale=scale):
+ """
+ define macbeth square centres
+ """
+ verts, mac_norm = get_square_verts(c_err, scale=scale)
+
+ centres = np.mean(verts, axis=1)
+ # print('centres')
+ # print(centres)
+ return np.array(centres, np.float32)
diff --git a/utils/tuning/libtuning/generators/yaml_output.py b/utils/tuning/libtuning/generators/yaml_output.py
index 8f22d386..c490081d 100644
--- a/utils/tuning/libtuning/generators/yaml_output.py
+++ b/utils/tuning/libtuning/generators/yaml_output.py
@@ -9,8 +9,9 @@ from .generator import Generator
from numbers import Number
from pathlib import Path
-import libtuning.utils as utils
+import logging
+logger = logging.getLogger(__name__)
class YamlOutput(Generator):
def __init__(self):
@@ -106,13 +107,16 @@ class YamlOutput(Generator):
]
for module in output_order:
+ if module not in output_dict:
+ continue
+
out_lines.append(f' - {module.out_name}:')
if len(output_dict[module]) == 0:
continue
if not isinstance(output_dict[module], dict):
- utils.eprint(f'Error: Output of {module.type} is not a dictionary')
+ logger.error(f'Error: Output of {module.type} is not a dictionary')
continue
lines = self._stringify_dict(output_dict[module])
diff --git a/utils/tuning/libtuning/image.py b/utils/tuning/libtuning/image.py
index e2181b11..c8911a0f 100644
--- a/utils/tuning/libtuning/image.py
+++ b/utils/tuning/libtuning/image.py
@@ -13,6 +13,9 @@ import re
import libtuning as lt
import libtuning.utils as utils
+import logging
+
+logger = logging.getLogger(__name__)
class Image:
@@ -21,17 +24,18 @@ class Image:
self.lsc_only = False
self.color = -1
self.lux = -1
+ self.macbeth = None
try:
self._load_metadata_exif()
except Exception as e:
- utils.eprint(f'Failed to load metadata from {self.path}: {e}')
+ logger.error(f'Failed to load metadata from {self.path}: {e}')
raise e
try:
self._read_image_dng()
except Exception as e:
- utils.eprint(f'Failed to load image data from {self.path}: {e}')
+ logger.error(f'Failed to load image data from {self.path}: {e}')
raise e
@property
@@ -79,7 +83,7 @@ class Image:
# is R, then G, then G, then B.
bayer_case = {
'0 1 1 2': (lt.Color.R, lt.Color.GR, lt.Color.GB, lt.Color.B),
- '1 2 0 1': (lt.Color.GB, lt.Color.R, lt.Color.B, lt.Color.GR),
+ '1 2 0 1': (lt.Color.GB, lt.Color.B, lt.Color.R, lt.Color.GR),
'2 1 1 0': (lt.Color.B, lt.Color.GB, lt.Color.GR, lt.Color.R),
'1 0 2 1': (lt.Color.GR, lt.Color.R, lt.Color.B, lt.Color.GB)
}
diff --git a/utils/tuning/libtuning/libtuning.py b/utils/tuning/libtuning/libtuning.py
index 5e22288d..bac57323 100644
--- a/utils/tuning/libtuning/libtuning.py
+++ b/utils/tuning/libtuning/libtuning.py
@@ -5,13 +5,14 @@
# An infrastructure for camera tuning tools
import argparse
+import logging
import libtuning as lt
import libtuning.utils as utils
-from libtuning.utils import eprint
from enum import Enum, IntEnum
+logger = logging.getLogger(__name__)
class Color(IntEnum):
R = 0
@@ -94,7 +95,10 @@ class Tuner(object):
self.output = {}
def add(self, module):
- self.modules.append(module)
+ if isinstance(module, list):
+ self.modules.extend(module)
+ else:
+ self.modules.append(module)
def set_input_parser(self, parser):
self.parser = parser
@@ -112,10 +116,10 @@ class Tuner(object):
for module_type in output_order:
modules = [module for module in self.modules if module.type == module_type.type]
if len(modules) > 1:
- eprint(f'Multiple modules found for module type "{module_type.type}"')
+ logger.error(f'Multiple modules found for module type "{module_type.type}"')
return False
if len(modules) < 1:
- eprint(f'No module found for module type "{module_type.type}"')
+ logger.error(f'No module found for module type "{module_type.type}"')
return False
self.output_order.append(modules[0])
@@ -124,19 +128,19 @@ class Tuner(object):
# \todo Validate parser and generator at Tuner construction time?
def _validate_settings(self):
if self.parser is None:
- eprint('Missing parser')
+ logger.error('Missing parser')
return False
if self.generator is None:
- eprint('Missing generator')
+ logger.error('Missing generator')
return False
if len(self.modules) == 0:
- eprint('No modules added')
+ logger.error('No modules added')
return False
if len(self.output_order) != len(self.modules):
- eprint('Number of outputs does not match number of modules')
+ logger.error('Number of outputs does not match number of modules')
return False
return True
@@ -183,7 +187,7 @@ class Tuner(object):
for module in self.modules:
if not module.validate_config(self.config):
- eprint(f'Config is invalid for module {module.type}')
+ logger.error(f'Config is invalid for module {module.type}')
return -1
has_lsc = any(isinstance(m, lt.modules.lsc.LSC) for m in self.modules)
@@ -192,15 +196,15 @@ class Tuner(object):
images = utils.load_images(args.input, self.config, not has_only_lsc, has_lsc)
if images is None or len(images) == 0:
- eprint(f'No images were found, or able to load')
+ logger.error(f'No images were found, or able to load')
return -1
# Do the tuning
for module in self.modules:
out = module.process(self.config, images, self.output)
if out is None:
- eprint(f'Module {module.name} failed to process, aborting')
- break
+ logger.warning(f'Module {module.hr_name} failed to process...')
+ continue
self.output[module] = out
self.generator.write(args.output, self.output, self.output_order)
diff --git a/utils/tuning/libtuning/macbeth.py b/utils/tuning/libtuning/macbeth.py
index e1182464..4a2006b0 100644
--- a/utils/tuning/libtuning/macbeth.py
+++ b/utils/tuning/libtuning/macbeth.py
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (C) 2019, Raspberry Pi Ltd
+# Copyright (C) 2024, Ideas on Board Oy
#
# Locate and extract Macbeth charts from images
# (Copied from: ctt_macbeth_locator.py)
@@ -11,8 +12,18 @@ import cv2
import os
from pathlib import Path
import numpy as np
+import warnings
+import logging
+from sklearn import cluster as cluster
-from libtuning.image import Image
+from .ctt_ransac import get_square_verts, get_square_centres
+from .image import Image
+
+logger = logging.getLogger(__name__)
+
+
+class MacbethError(Exception):
+ pass
# Reshape image to fixed width without distorting returns image and scale
@@ -369,7 +380,9 @@ def get_macbeth_chart(img, ref_data):
# Catch macbeth errors and continue with code
except MacbethError as error:
- eprint(error)
+ # \todo: This happens so many times in a normal run, that it shadows
+ # all the relevant output
+ # logger.warning(error)
return (0, None, None, False)
@@ -403,10 +416,15 @@ def find_macbeth(img, mac_config):
# nothing more is tried as this is a high enough confidence to ensure
# reliable macbeth square centre placement.
+ # Keep a list that will include this and any brightened up versions of
+ # the image for reuse.
+ all_images = [img]
+
for brightness in [2, 4]:
if cor >= 0.75:
break
img_br = cv2.convertScaleAbs(img, alpha=brightness, beta=0)
+ all_images.append(img_br)
cor_b, mac_b, coords_b, ret_b = get_macbeth_chart(img_br, ref_data)
if cor_b > cor:
cor, mac, coords, ret = cor_b, mac_b, coords_b, ret_b
@@ -456,23 +474,24 @@ def find_macbeth(img, mac_config):
w_inc = int(w * pair['inc'])
h_inc = int(h * pair['inc'])
- loop = ((1 - pair['sel']) / pair['inc']) + 1
+ loop = int(((1 - pair['sel']) / pair['inc']) + 1)
# For each subselection, look for a macbeth chart
- for i in range(loop):
- for j in range(loop):
- w_s, h_s = i * w_inc, j * h_inc
- img_sel = img[w_s:w_s + w_sel, h_s:h_s + h_sel]
- cor_ij, mac_ij, coords_ij, ret_ij = get_macbeth_chart(img_sel, ref_data)
-
- # If the correlation is better than the best then record the
- # scale and current subselection at which macbeth chart was
- # found. Also record the coordinates, macbeth chart and message.
- if cor_ij > cor:
- cor = cor_ij
- mac, coords, ret = mac_ij, coords_ij, ret_ij
- ii, jj = i, j
- w_best, h_best = w_inc, h_inc
- d_best = index + 1
+ for img_br in all_images:
+ for i in range(loop):
+ for j in range(loop):
+ w_s, h_s = i * w_inc, j * h_inc
+ img_sel = img_br[w_s:w_s + w_sel, h_s:h_s + h_sel]
+ cor_ij, mac_ij, coords_ij, ret_ij = get_macbeth_chart(img_sel, ref_data)
+
+ # If the correlation is better than the best then record the
+ # scale and current subselection at which macbeth chart was
+ # found. Also record the coordinates, macbeth chart and message.
+ if cor_ij > cor:
+ cor = cor_ij
+ mac, coords, ret = mac_ij, coords_ij, ret_ij
+ ii, jj = i, j
+ w_best, h_best = w_inc, h_inc
+ d_best = index + 1
# Transform coordinates from subselection to original image
if ii != -1:
@@ -486,7 +505,7 @@ def find_macbeth(img, mac_config):
coords_fit = coords
if cor < 0.75:
- eprint(f'Warning: Low confidence {cor:.3f} for macbeth chart in {img.path.name}')
+ logger.warning(f'Low confidence {cor:.3f} for macbeth chart')
if show:
draw_macbeth_results(img, coords_fit)
@@ -499,18 +518,20 @@ def locate_macbeth(image: Image, config: dict):
av_chan = (np.mean(np.array(image.channels), axis=0) / (2**16))
av_val = np.mean(av_chan)
if av_val < image.blacklevel_16 / (2**16) + 1 / 64:
- eprint(f'Image {image.path.name} too dark')
+ logger.warning(f'Image {image.path.name} too dark')
return None
macbeth = find_macbeth(av_chan, config['general']['macbeth'])
if macbeth is None:
- eprint(f'No macbeth chart found in {image.path.name}')
+ logger.warning(f'No macbeth chart found in {image.path.name}')
return None
mac_cen_coords = macbeth[1]
if not image.get_patches(mac_cen_coords):
- eprint(f'Macbeth patches have saturated in {image.path.name}')
+ logger.warning(f'Macbeth patches have saturated in {image.path.name}')
return None
+ image.macbeth = macbeth
+
return macbeth
diff --git a/utils/tuning/libtuning/macbeth_ref.pgm b/utils/tuning/libtuning/macbeth_ref.pgm
index 37897140..089ea91f 100644
--- a/utils/tuning/libtuning/macbeth_ref.pgm
+++ b/utils/tuning/libtuning/macbeth_ref.pgm
@@ -1,5 +1,5 @@
-# SPDX-License-Identifier: BSD-2-Clause
P5
+# SPDX-License-Identifier: BSD-2-Clause
# Reference macbeth chart
120 80
255
diff --git a/utils/tuning/libtuning/modules/agc/rkisp1.py b/utils/tuning/libtuning/modules/agc/rkisp1.py
index 19a5555b..7147028a 100644
--- a/utils/tuning/libtuning/modules/agc/rkisp1.py
+++ b/utils/tuning/libtuning/modules/agc/rkisp1.py
@@ -64,7 +64,7 @@ class AGCRkISP1(AGC):
return {'ConstraintNormal': normal, 'ConstraintHighlight': highlight}
def _generate_y_target(self) -> list:
- return 0.16
+ return 0.5
def process(self, config: dict, images: list, outputs: dict) -> dict:
output = {}
diff --git a/utils/tuning/libtuning/modules/ccm/__init__.py b/utils/tuning/libtuning/modules/ccm/__init__.py
new file mode 100644
index 00000000..322602af
--- /dev/null
+++ b/utils/tuning/libtuning/modules/ccm/__init__.py
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+
+from libtuning.modules.ccm.ccm import CCM
+from libtuning.modules.ccm.rkisp1 import CCMRkISP1
diff --git a/utils/tuning/libtuning/modules/ccm/ccm.py b/utils/tuning/libtuning/modules/ccm/ccm.py
new file mode 100644
index 00000000..18702f8d
--- /dev/null
+++ b/utils/tuning/libtuning/modules/ccm/ccm.py
@@ -0,0 +1,41 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+# Copyright (C) 2024, Ideas on Board
+#
+# Base Ccm tuning module
+
+from ..module import Module
+
+from libtuning.ctt_ccm import ccm
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class CCM(Module):
+ type = 'ccm'
+ hr_name = 'CCM (Base)'
+ out_name = 'GenericCCM'
+
+ def __init__(self, debug: list):
+ super().__init__()
+
+ self.debug = debug
+
+ def do_calibration(self, images):
+ logger.info('Starting CCM calibration')
+
+ imgs = [img for img in images if img.macbeth is not None]
+
+ # todo: Take LSC calibration results into account.
+ cal_cr_list = None
+ cal_cb_list = None
+
+ try:
+ ccms = ccm(imgs, cal_cr_list, cal_cb_list)
+ except ArithmeticError:
+ logger.error('CCM calibration failed')
+ return None
+
+ return ccms
diff --git a/utils/tuning/libtuning/modules/ccm/rkisp1.py b/utils/tuning/libtuning/modules/ccm/rkisp1.py
new file mode 100644
index 00000000..be0252d9
--- /dev/null
+++ b/utils/tuning/libtuning/modules/ccm/rkisp1.py
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024, Paul Elder <paul.elder@ideasonboard.com>
+# Copyright (C) 2024, Ideas on Board
+#
+# Ccm module for tuning rkisp1
+
+from .ccm import CCM
+
+
+class CCMRkISP1(CCM):
+ hr_name = 'Crosstalk Correction (RkISP1)'
+ out_name = 'Ccm'
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ # We don't need anything from the config file.
+ def validate_config(self, config: dict) -> bool:
+ return True
+
+ def process(self, config: dict, images: list, outputs: dict) -> dict:
+ output = {}
+
+ ccms = self.do_calibration(images)
+ output['ccms'] = ccms
+
+ return output
diff --git a/utils/tuning/libtuning/modules/lsc/lsc.py b/utils/tuning/libtuning/modules/lsc/lsc.py
index 344a07a3..e0ca22eb 100644
--- a/utils/tuning/libtuning/modules/lsc/lsc.py
+++ b/utils/tuning/libtuning/modules/lsc/lsc.py
@@ -59,7 +59,10 @@ class LSC(Module):
def _lsc_single_channel(self, channel: np.array,
image: lt.Image, green_grid: np.array = None):
grid = self._get_grid(channel, image.w, image.h)
- grid -= image.blacklevel_16
+ # Clamp the values to a small positive, so that the following 1/grid
+ # doesn't produce negative results.
+ grid = np.maximum(grid - image.blacklevel_16, 0.1)
+
if green_grid is None:
table = np.reshape(1 / grid, self.sector_shape[::-1])
else:
diff --git a/utils/tuning/libtuning/modules/lsc/raspberrypi.py b/utils/tuning/libtuning/modules/lsc/raspberrypi.py
index f19c7163..99bc4fe6 100644
--- a/utils/tuning/libtuning/modules/lsc/raspberrypi.py
+++ b/utils/tuning/libtuning/modules/lsc/raspberrypi.py
@@ -12,7 +12,9 @@ import libtuning.utils as utils
from numbers import Number
import numpy as np
+import logging
+logger = logging.getLogger(__name__)
class ALSCRaspberryPi(LSC):
# Override the type name so that the parser can match the entry in the
@@ -35,7 +37,7 @@ class ALSCRaspberryPi(LSC):
def validate_config(self, config: dict) -> bool:
if self not in config:
- utils.eprint(f'{self.type} not in config')
+ logger.error(f'{self.type} not in config')
return False
valid = True
@@ -46,14 +48,14 @@ class ALSCRaspberryPi(LSC):
color_key = self.do_color.name
if lum_key not in conf and self.luminance_strength.required:
- utils.eprint(f'{lum_key} is not in config')
+ logger.error(f'{lum_key} is not in config')
valid = False
if lum_key in conf and (conf[lum_key] < 0 or conf[lum_key] > 1):
- utils.eprint(f'Warning: {lum_key} is not in range [0, 1]; defaulting to 0.5')
+ logger.warning(f'{lum_key} is not in range [0, 1]; defaulting to 0.5')
if color_key not in conf and self.do_color.required:
- utils.eprint(f'{color_key} is not in config')
+ logger.error(f'{color_key} is not in config')
valid = False
return valid
@@ -235,7 +237,7 @@ class ALSCRaspberryPi(LSC):
if count == 1:
output['sigma'] = 0.005
output['sigma_Cb'] = 0.005
- utils.eprint('Warning: Only one alsc calibration found; standard sigmas used for adaptive algorithm.')
+ logger.warning('Only one alsc calibration found; standard sigmas used for adaptive algorithm.')
return output
# Obtain worst-case scenario residual sigmas
diff --git a/utils/tuning/libtuning/modules/lsc/rkisp1.py b/utils/tuning/libtuning/modules/lsc/rkisp1.py
index 20406e43..c02b2306 100644
--- a/utils/tuning/libtuning/modules/lsc/rkisp1.py
+++ b/utils/tuning/libtuning/modules/lsc/rkisp1.py
@@ -33,13 +33,13 @@ class LSCRkISP1(LSC):
# table, flattened array of (blue's) green calibration table
def _do_single_lsc(self, image: lt.Image):
- cgr, gr = self._lsc_single_channel(image.channels[lt.Color.GR], image)
- cgb, gb = self._lsc_single_channel(image.channels[lt.Color.GB], image)
-
- # \todo Should these ratio against the average of both greens or just
- # each green like we've done here?
- cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image, gr)
- cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image, gb)
+ # Perform LSC on each colour channel independently. A future enhancement
+ # worth investigating would be splitting the luminance and chrominance
+ # LSC as done by Raspberry Pi.
+ cgr, _ = self._lsc_single_channel(image.channels[lt.Color.GR], image)
+ cgb, _ = self._lsc_single_channel(image.channels[lt.Color.GB], image)
+ cr, _ = self._lsc_single_channel(image.channels[lt.Color.R], image)
+ cb, _ = self._lsc_single_channel(image.channels[lt.Color.B], image)
return image.color, cr.flatten(), cb.flatten(), cgr.flatten(), cgb.flatten()
@@ -80,7 +80,8 @@ class LSCRkISP1(LSC):
tables = []
for lis in [list_cr, list_cgr, list_cgb, list_cb]:
table = np.mean(lis[indices], axis=0)
- table = output_map_func((1, 3.999), (1024, 4095), table)
+ table = output_map_func((1, 4), (1024, 4096), table)
+ table = np.clip(table, 1024, 4095)
table = np.round(table).astype('int32').tolist()
tables.append(table)
@@ -106,6 +107,9 @@ class LSCRkISP1(LSC):
output['sets'] = self._do_all_lsc(images)
+ if len(output['sets']) == 0:
+ return None
+
# \todo Validate images from greyscale camera and force grescale mode
# \todo Debug functionality
diff --git a/utils/tuning/libtuning/modules/static.py b/utils/tuning/libtuning/modules/static.py
new file mode 100644
index 00000000..4d0f7e18
--- /dev/null
+++ b/utils/tuning/libtuning/modules/static.py
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2024, Ideas on Board
+#
+# Module implementation for static data
+
+from .module import Module
+
+
+# This module can be used in cases where the tuning file should contain
+# static data.
+class StaticModule(Module):
+ def __init__(self, out_name: str, output: dict = {}):
+ super().__init__()
+ self.out_name = out_name
+ self.hr_name = f'Static {out_name}'
+ self.type = f'static_{out_name}'
+ self.output = output
+
+ def validate_config(self, config: dict) -> bool:
+ return True
+
+ def process(self, config: dict, images: list, outputs: dict) -> dict:
+ return self.output
diff --git a/utils/tuning/libtuning/parsers/yaml_parser.py b/utils/tuning/libtuning/parsers/yaml_parser.py
index 244db24d..1fa6b7a8 100644
--- a/utils/tuning/libtuning/parsers/yaml_parser.py
+++ b/utils/tuning/libtuning/parsers/yaml_parser.py
@@ -5,13 +5,16 @@
# Parser for YAML format config file
from .parser import Parser
+import yaml
class YamlParser(Parser):
def __init__(self):
super().__init__()
- # \todo Implement this (it's fine for now as we don't need a config for
- # rkisp1 LSC, which is the only user of this so far)
def parse(self, config_file: str, modules: list) -> (dict, list):
- return {}, []
+ # Dummy implementation that just reads the file
+ with open(config_file, 'r') as f:
+ config = yaml.safe_load(f)
+
+ return config, []
diff --git a/utils/tuning/libtuning/utils.py b/utils/tuning/libtuning/utils.py
index 1e8128ea..e35cf409 100644
--- a/utils/tuning/libtuning/utils.py
+++ b/utils/tuning/libtuning/utils.py
@@ -5,6 +5,7 @@
#
# Utilities for libtuning
+import cv2
import decimal
import math
import numpy as np
@@ -12,16 +13,15 @@ import os
from pathlib import Path
import re
import sys
+import logging
import libtuning as lt
from libtuning.image import Image
-from libtuning.macbeth import locate_macbeth
-
-# Utility functions
+from .macbeth import locate_macbeth
+logger = logging.getLogger(__name__)
-def eprint(*args, **kwargs):
- print(*args, file=sys.stderr, **kwargs)
+# Utility functions
def get_module_by_type_name(modules, name):
@@ -43,16 +43,30 @@ def _list_image_files(directory):
def _parse_image_filename(fn: Path):
- result = re.search(r'^(alsc_)?(\d+)[kK]_(\d+)?[lLuU]?.\w{3,4}$', fn.name)
- if result is None:
- eprint(f'The file name of {fn.name} is incorrectly formatted')
- return None, None, None
+ lsc_only = False
+ color_temperature = None
+ lux = None
+
+ parts = fn.stem.split('_')
+ for part in parts:
+ if part == 'alsc':
+ lsc_only = True
+ continue
+ r = re.match(r'(\d+)[kK]', part)
+ if r:
+ color_temperature = int(r.group(1))
+ continue
+ r = re.match(r'(\d+)[lLuU]', part)
+ if r:
+ lux = int(r.group(1))
+
+ if color_temperature is None:
+ logger.error(f'The file name of "{fn.name}" does not contain a color temperature')
- color = int(result.group(2))
- lsc_only = result.group(1) is not None
- lux = None if lsc_only else int(result.group(3))
+ if lux is None and lsc_only is False:
+ logger.error(f'The file name of "{fn.name}" must either contain alsc or a lux level')
- return color, lux, lsc_only
+ return color_temperature, lux, lsc_only
# \todo Implement this from check_imgs() in ctt.py
@@ -72,30 +86,34 @@ def _validate_images(images):
def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool) -> list:
files = _list_image_files(input_dir)
if len(files) == 0:
- eprint(f'No images found in {input_dir}')
+ logger.error(f'No images found in {input_dir}')
return None
images = []
for f in files:
color, lux, lsc_only = _parse_image_filename(f)
+
if color is None:
+ logger.warning(f'Ignoring "{f.name}" as it has no associated color temperature')
continue
+ logger.info(f'Process image "{f.name}" (color={color}, lux={lux}, lsc_only={lsc_only})')
+
# Skip lsc image if we don't need it
if lsc_only and not load_lsc:
- eprint(f'Skipping {f.name} as this tuner has no LSC module')
+ logger.warning(f'Skipping {f.name} as this tuner has no LSC module')
continue
# Skip non-lsc image if we don't need it
if not lsc_only and not load_nonlsc:
- eprint(f'Skipping {f.name} as this tuner only has an LSC module')
+ logger.warning(f'Skipping {f.name} as this tuner only has an LSC module')
continue
# Load image
try:
image = Image(f)
except Exception as e:
- eprint(f'Failed to load image {f.name}: {e}')
+ logger.error(f'Failed to load image {f.name}: {e}')
continue
# Populate simple fields
@@ -113,7 +131,7 @@ def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool)
continue
# Handle macbeth
- macbeth = locate_macbeth(config)
+ macbeth = locate_macbeth(image, config)
if macbeth is None:
continue
@@ -123,3 +141,46 @@ def load_images(input_dir: str, config: dict, load_nonlsc: bool, load_lsc: bool)
return None
return images
+
+
+
+"""
+Some code that will save virtual macbeth charts that show the difference between optimised matrices and non optimised matrices
+
+The function creates an image that is 1550 by 1050 pixels wide, and fills it with patches which are 200x200 pixels in size
+Each patch contains the ideal color, the color from the original matrix, and the color from the final matrix
+_________________
+| |
+| Ideal Color |
+|_______________|
+| Old | new |
+| Color | Color |
+|_______|_______|
+
+Nice way of showing how the optimisation helps change the colors and the color matricies
+"""
+def visualise_macbeth_chart(macbeth_rgb, original_rgb, new_rgb, output_filename):
+ image = np.zeros((1050, 1550, 3), dtype=np.uint8)
+ colorindex = -1
+ for y in range(6):
+ for x in range(4): # Creates 6 x 4 grid of macbeth chart
+ colorindex += 1
+ xlocation = 50 + 250 * x # Means there is 50px of black gap between each square, more like the real macbeth chart.
+ ylocation = 50 + 250 * y
+ for g in range(200):
+ for i in range(100):
+ image[xlocation + i, ylocation + g] = macbeth_rgb[colorindex]
+ xlocation = 150 + 250 * x
+ ylocation = 50 + 250 * y
+ for i in range(100):
+ for g in range(100):
+ image[xlocation + i, ylocation + g] = original_rgb[colorindex] # Smaller squares below to compare the old colors with the new ones
+ xlocation = 150 + 250 * x
+ ylocation = 150 + 250 * y
+ for i in range(100):
+ for g in range(100):
+ image[xlocation + i, ylocation + g] = new_rgb[colorindex]
+
+ im_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
+ cv2.imwrite(f'{output_filename} Generated Macbeth Chart.png', im_bgr)
+
diff --git a/utils/tuning/requirements.txt b/utils/tuning/requirements.txt
new file mode 100644
index 00000000..3705769b
--- /dev/null
+++ b/utils/tuning/requirements.txt
@@ -0,0 +1,9 @@
+coloredlogs
+matplotlib
+numpy
+opencv-python
+py3exiv2
+pyyaml
+rawpy
+scikit-learn
+scipy
diff --git a/utils/tuning/rkisp1.py b/utils/tuning/rkisp1.py
index d0ce15d5..f5c42a61 100755
--- a/utils/tuning/rkisp1.py
+++ b/utils/tuning/rkisp1.py
@@ -5,6 +5,8 @@
#
# Tuning script for rkisp1
+import coloredlogs
+import logging
import sys
import libtuning as lt
@@ -12,31 +14,42 @@ from libtuning.parsers import YamlParser
from libtuning.generators import YamlOutput
from libtuning.modules.lsc import LSCRkISP1
from libtuning.modules.agc import AGCRkISP1
+from libtuning.modules.ccm import CCMRkISP1
+from libtuning.modules.static import StaticModule
+
+coloredlogs.install(level=logging.INFO, fmt='%(name)s %(levelname)s %(message)s')
+
+agc = AGCRkISP1(debug=[lt.Debug.Plot])
+awb = StaticModule('Awb')
+blc = StaticModule('BlackLevelCorrection')
+ccm = CCMRkISP1(debug=[lt.Debug.Plot])
+color_processing = StaticModule('ColorProcessing')
+filter = StaticModule('Filter')
+gamma_out = StaticModule('GammaOutCorrection', {'gamma': 2.2})
+lsc = LSCRkISP1(debug=[lt.Debug.Plot],
+ # This is for the actual LSC tuning, and is part of the base LSC
+ # module. rkisp1's table sector sizes (16x16 programmed as mirrored
+ # 8x8) are separate, and is hardcoded in its specific LSC tuning
+ # module.
+ sector_shape=(17, 17),
+
+ sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+ sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
+
+ # This is the function that will be used to average the pixels in
+ # each sector. This can also be a custom function.
+ sector_average_function=lt.average.Mean(),
+
+ # This is the function that will be used to smooth the color ratio
+ # values. This can also be a custom function.
+ smoothing_function=lt.smoothing.MedianBlur(3),)
tuner = lt.Tuner('RkISP1')
-tuner.add(LSCRkISP1(
- debug=[lt.Debug.Plot],
- # This is for the actual LSC tuning, and is part of the base LSC
- # module. rkisp1's table sector sizes (16x16 programmed as mirrored
- # 8x8) are separate, and is hardcoded in its specific LSC tuning
- # module.
- sector_shape=(17, 17),
-
- sector_x_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
- sector_y_gradient=lt.gradient.Linear(lt.Remainder.DistributeFront),
-
- # This is the function that will be used to average the pixels in
- # each sector. This can also be a custom function.
- sector_average_function=lt.average.Mean(),
-
- # This is the function that will be used to smooth the color ratio
- # values. This can also be a custom function.
- smoothing_function=lt.smoothing.MedianBlur(3),
- ))
-tuner.add(AGCRkISP1(debug=[lt.Debug.Plot]))
+tuner.add([agc, awb, blc, ccm, color_processing, filter, gamma_out, lsc])
tuner.set_input_parser(YamlParser())
tuner.set_output_formatter(YamlOutput())
-tuner.set_output_order([AGCRkISP1, LSCRkISP1])
+tuner.set_output_order([agc, awb, blc, ccm, color_processing,
+ filter, gamma_out, lsc])
if __name__ == '__main__':
sys.exit(tuner.run(sys.argv))
diff --git a/utils/update-kernel-headers.sh b/utils/update-kernel-headers.sh
index 8405be0a..9a64dfb5 100755
--- a/utils/update-kernel-headers.sh
+++ b/utils/update-kernel-headers.sh
@@ -9,7 +9,7 @@ if [ $# != 1 ] ; then
fi
header_dir="$(dirname "$(realpath "$0")")/../include/linux"
-kernel_dir="$1"
+kernel_dir="$(realpath "$1")"
# Bail out if the directory doesn't contain kernel sources
line=$(head -3 "${kernel_dir}/Kbuild" 2>/dev/null | tail -1)