diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..3c32d25 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f4edc74 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +IndentWidth: 2 diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..69699a5 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,13 @@ +Checks: " + -checks=clang-diagnostic-*, + clang-analyzer-*, + modernize-*, + cppcoreguidelines-*, + readability-*, + portability-*, + bugprone-*, + llvm-*, + cert-*, + hicpp-*, + -hicpp-exception-baseclass, + -modernize-use-trailing-return-type" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4960585 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index d0b9098..4747bd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ .idea .vscode +*.bak~ /target Cargo.lock + +plane_*.bin +image_*.png +tmp.bgr diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b3388b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libcamera"] + path = libcamera + url = https://git.libcamera.org/libcamera/libcamera.git +[submodule "gnutls"] + path = gnutls + url = https://gitlab.com/gnutls/gnutls.git diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..b196eaa --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 2 diff --git a/Cargo.toml b/Cargo.toml index c3d202d..d29dfb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,23 @@ [package] name = "libcamera-rs" +description = "Libcamera bindings for rust." +authors = ["John Brandt ", "Ben Heard "] version = "0.1.0" edition = "2021" +license = "ISC" [dependencies] cxx = "1.0" +thiserror = "1" +log = "0.4" +image = { version = "0.24", optional = true } [build-dependencies] cxx-build = "1.0" +[dev-dependencies] +lazy_static = "1" +simplelog = "0.12" + +[features] +default = [ "image" ] diff --git a/build.rs b/build.rs index 7697fd7..59b2223 100644 --- a/build.rs +++ b/build.rs @@ -1,15 +1,39 @@ +const SOURCES: [&str; 15] = [ + "libcamera-bridge/camera_manager.cpp", + "libcamera-bridge/camera.cpp", + "libcamera-bridge/camera_configuration.cpp", + "libcamera-bridge/stream_configuration.cpp", + "libcamera-bridge/pixel_format.cpp", + "libcamera-bridge/size.cpp", + "libcamera-bridge/stream.cpp", + "libcamera-bridge/frame_buffer_allocator.cpp", + "libcamera-bridge/frame_buffer.cpp", + "libcamera-bridge/frame_buffer_plane.cpp", + "libcamera-bridge/fd.cpp", + "libcamera-bridge/memory_buffer.cpp", + "libcamera-bridge/request.cpp", + "libcamera-bridge/control_id.cpp", + "libcamera-bridge/control_value.cpp", +]; + fn main() { - cxx_build::bridge("src/bridge.rs") - .file("libcamera-bridge/core.cpp") - .flag_if_supported("-std=c++17") - .include("/usr/local/include/libcamera") - .compile("libcamera-bridge"); + println!("cargo:rerun-if-changed=src/bridge.rs"); + println!("cargo:rerun-if-changed=libcamera-bridge/core.hpp"); + for source in &SOURCES { + println!("cargo:rerun-if-changed={source}"); + } - println!("cargo:rerun-if-changed=src/bridge.rs"); - println!("cargo:rerun-if-changed=libcamera-bridge/core.cpp"); - println!("cargo:rerun-if-changed=libcamera-bridge/core.hpp"); + cxx_build::bridge("src/bridge.rs") + .flag_if_supported("-std=c++17") + .warnings(true) + .extra_warnings(true) + .files(SOURCES) + .include("/usr/local/include/libcamera") + .include("libcamera/build/include/libcamera") + .compile("libcamera-bridge"); - // link libcamera - println!("cargo:rustc-link-lib=dylib=camera"); - println!("cargo:rustc-link-lib=dylib=camera-base"); + // link libcamera + println!("cargo:rustc-link-search=/usr/local/lib"); + println!("cargo:rustc-link-lib=dylib=camera"); + println!("cargo:rustc-link-lib=dylib=camera-base"); } diff --git a/libcamera-bridge/camera.cpp b/libcamera-bridge/camera.cpp new file mode 100644 index 0000000..175bba2 --- /dev/null +++ b/libcamera-bridge/camera.cpp @@ -0,0 +1,184 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +Camera::Camera(std::shared_ptr inner_) + : inner{std::move(inner_)} { + this->inner->bufferCompleted.connect( + this, + [&](libcamera::Request *request, libcamera::FrameBuffer *framebuffer) { + this->message_mutex.lock(); + this->message_queue.push(CameraMessage{ + .message_type = CameraMessageType::BufferComplete, + .request_cookie = request->cookie(), + .buffer_cookie = framebuffer->cookie(), + }); + this->message_mutex.unlock(); + }); + this->inner->requestCompleted.connect(this, [&](libcamera::Request *request) { + this->message_mutex.lock(); + this->message_queue.push(CameraMessage{ + .message_type = CameraMessageType::RequestComplete, + .request_cookie = request->cookie(), + .buffer_cookie = 0, + }); + this->message_mutex.unlock(); + }); +} + +Camera::~Camera() { + this->inner->bufferCompleted.disconnect(); + this->inner->requestCompleted.disconnect(); +} + +std::shared_ptr Camera::into_shared() { + VALIDATE_POINTERS() + + return this->inner; +} + +void Camera::acquire() { + VALIDATE_POINTERS() + + int ret = this->inner->acquire(); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +void Camera::release() { + VALIDATE_POINTERS() + + int ret = this->inner->release(); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +BindCameraConfiguration +Camera::generate_configuration(rust::Slice roles) { + VALIDATE_POINTERS() + + std::vector roles_vec; + for (libcamera::StreamRole role : roles) { + roles_vec.push_back(role); + } + + std::unique_ptr conf = + this->inner->generateConfiguration(roles_vec); + if (!conf) { + throw error_from_code(ENODEV); + } + + BindCameraConfiguration bind_conf{ + .inner = std::make_unique(std::move(conf)), + }; + return bind_conf; +} + +void Camera::configure(CameraConfiguration &conf) { + VALIDATE_POINTERS() + + int ret = this->inner->configure(conf.into_ptr()); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +BindRequest Camera::create_request(uint64_t cookie) { + VALIDATE_POINTERS() + + std::unique_ptr req = this->inner->createRequest(cookie); + if (!req) { + throw error_from_code(ENODEV); + } + + BindRequest bind_req{ + .inner = std::make_unique(std::move(req)), + }; + return bind_req; +} + +void Camera::queue_request(Request &req) { + VALIDATE_POINTERS() + + int ret = this->inner->queueRequest(req.into_ptr()); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +void Camera::start() { + VALIDATE_POINTERS() + + int ret = this->inner->start(); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +void Camera::stop() { + VALIDATE_POINTERS() + + int ret = this->inner->stop(); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +rust::Vec Camera::get_controls() const { + VALIDATE_POINTERS() + + rust::Vec controls; + for (const auto &[control, value] : this->inner->controls()) { + rust::Vec possible_values; + for (const auto &value : value.values()) { + BindControlValue bind_value{ + .inner = std::make_unique(value), + }; + possible_values.push_back(std::move(bind_value)); + } + ControlPair control_pair{ + .id = + { + .inner = std::make_unique(control), + }, + .min = + { + .inner = std::make_unique(value.min()), + }, + .max = + { + .inner = std::make_unique(value.max()), + }, + .value = + { + .inner = std::make_unique(value.def()), + }, + .valid_values = std::move(possible_values), + }; + controls.push_back(std::move(control_pair)); + } + return controls; +} + +rust::Vec Camera::poll_events() { + rust::Vec messages; + while (!this->message_queue.empty()) { + messages.push_back(this->message_queue.front()); + message_queue.pop(); + } + return messages; +} + +rust::Vec +Camera::poll_events_with_cookie(uint64_t request_cookie) { + rust::Vec messages; + while (!this->message_queue.empty()) { + if (this->message_queue.front().request_cookie == request_cookie) { + messages.push_back(this->message_queue.front()); + message_queue.pop(); + } + } + return messages; +} diff --git a/libcamera-bridge/camera_configuration.cpp b/libcamera-bridge/camera_configuration.cpp new file mode 100644 index 0000000..9cf7ed7 --- /dev/null +++ b/libcamera-bridge/camera_configuration.cpp @@ -0,0 +1,34 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +libcamera::CameraConfiguration *CameraConfiguration::into_ptr() { + VALIDATE_POINTERS() + + return this->inner.get(); +} + +size_t CameraConfiguration::size() const { + VALIDATE_POINTERS() + + return this->inner->size(); +} + +BindStreamConfiguration CameraConfiguration::at(uint32_t idx) { + VALIDATE_POINTERS() + + libcamera::StreamConfiguration *str = &this->inner->at(idx); + if (str == nullptr) { + throw std::runtime_error("No stream configuration with specified id."); + } + BindStreamConfiguration conf{ + .inner = std::make_unique(str), + }; + return conf; +} + +CameraConfigurationStatus CameraConfiguration::validate() { + VALIDATE_POINTERS() + + return this->inner->validate(); +} diff --git a/libcamera-bridge/camera_manager.cpp b/libcamera-bridge/camera_manager.cpp new file mode 100644 index 0000000..c1290c4 --- /dev/null +++ b/libcamera-bridge/camera_manager.cpp @@ -0,0 +1,53 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +#include + +BindCameraManager make_camera_manager() { + BindCameraManager manager{ + .inner = std::make_unique( + std::make_unique()), + }; + return manager; +} + +void CameraManager::start() { + VALIDATE_POINTERS() + + int ret = this->inner->start(); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +void CameraManager::stop() { + VALIDATE_POINTERS() + + this->inner->stop(); +} + +rust::Vec CameraManager::get_camera_ids() const { + VALIDATE_POINTERS() + + rust::Vec camera_ids; + for (std::shared_ptr cam : this->inner->cameras()) { + camera_ids.push_back(cam->id()); + } + return camera_ids; +} + +BindCamera CameraManager::get_camera_by_id(rust::Str rust_id) { + VALIDATE_POINTERS() + + std::string cam_id = std::string(rust_id); + std::shared_ptr cam = this->inner->get(cam_id); + // C++ header mismatch will cause this to be completely silly + if (!cam || cam.use_count() > INT_MAX) { + throw error_from_code(ENODEV); + } + BindCamera bind_cam{ + .inner = std::make_unique(cam), + }; + return bind_cam; +} diff --git a/libcamera-bridge/control_id.cpp b/libcamera-bridge/control_id.cpp new file mode 100644 index 0000000..6b1fd0c --- /dev/null +++ b/libcamera-bridge/control_id.cpp @@ -0,0 +1,21 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +rust::String ControlId::get_name() const { + VALIDATE_POINTERS() + + return this->inner->name(); +} + +uint32_t ControlId::get_id() const { + VALIDATE_POINTERS() + + return this->inner->id(); +} + +CameraControlType ControlId::get_type() const { + VALIDATE_POINTERS() + + return static_cast(this->inner->type()); +} diff --git a/libcamera-bridge/control_value.cpp b/libcamera-bridge/control_value.cpp new file mode 100644 index 0000000..cdf87af --- /dev/null +++ b/libcamera-bridge/control_value.cpp @@ -0,0 +1,192 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +BindControlValue new_control_value_bool(bool value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(value)), + }; + return control_value; +} + +BindControlValue new_control_value_u8(uint8_t value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(value)), + }; + return control_value; +} + +BindControlValue new_control_value_i32(int32_t value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(value)), + }; + return control_value; +} + +BindControlValue new_control_value_i64(int64_t value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(value)), + }; + return control_value; +} + +BindControlValue new_control_value_f32(float value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(value)), + }; + return control_value; +} + +BindControlValue +new_control_value_f32_array(rust::Slice values_rust) { + std::vector values; + for (float value : values_rust) { + values.push_back(value); + } + libcamera::Span span{values}; + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue(span)), + }; + return control_value; +} + +BindControlValue new_control_value_string(rust::Str value) { + BindControlValue control_value{ + .inner = std::make_unique( + libcamera::ControlValue(static_cast(value))), + }; + return control_value; +} + +BindControlValue new_control_value_rectangle(ControlRectangle value) { + BindControlValue control_value{ + .inner = std::make_unique(libcamera::ControlValue( + libcamera::Rectangle(value.x, value.y, value.width, value.height))), + }; + return control_value; +} + +BindControlValue new_control_value_size(ControlSize value) { + BindControlValue control_value{ + .inner = std::make_unique( + libcamera::ControlValue(libcamera::Size(value.width, value.height))), + }; + return control_value; +} + +const libcamera::ControlValue &ControlValue::get_inner() const { + return this->inner; +} + +CameraControlType ControlValue::get_type() const { + return static_cast(this->inner.type()); +} + +bool ControlValue::is_array() const { return this->inner.isArray(); } + +size_t ControlValue::len() const { return this->inner.numElements(); } + +bool ControlValue::get_bool() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeBool || + this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Single Bool."); + } + return this->inner.get(); +} + +uint8_t ControlValue::get_u8() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeByte || + this->inner.isArray()) { + if (this->inner.type() == libcamera::ControlType::ControlTypeInteger32 && + !this->inner.isArray()) { + return static_cast(this->get_i32()); + } + throw std::runtime_error("Bad type! Expected Single Byte."); + } + return this->inner.get(); +} + +int32_t ControlValue::get_i32() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeInteger32 || + this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Single I32."); + } + return this->inner.get(); +} + +int64_t ControlValue::get_i64() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeInteger64 || + this->inner.isArray()) { + if (this->inner.type() == libcamera::ControlType::ControlTypeInteger32 && + !this->inner.isArray()) { + return static_cast(this->get_i32()); + } + throw std::runtime_error("Bad type! Expected Single I64."); + } + return this->inner.get(); +} + +float ControlValue::get_f32() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeFloat || + this->inner.isArray()) { + if (this->inner.type() == libcamera::ControlType::ControlTypeInteger32 && + !this->inner.isArray()) { + return static_cast(this->get_i32()); + } + throw std::runtime_error("Bad type! Expected Single Float."); + } + return this->inner.get(); +} + +rust::Vec ControlValue::get_f32_array() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeFloat || + !this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Float Array."); + } + auto span = this->inner.get>(); + rust::Vec values; + for (float value : span) { + values.push_back(value); + } + return values; +} + +rust::String ControlValue::get_string() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeString || + this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Single String."); + } + return static_cast(this->inner.get()); +} + +ControlRectangle ControlValue::get_rectangle() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeRectangle || + this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Single Rectangle."); + } + auto rect = this->inner.get(); + ControlRectangle crect{ + .x = rect.x, + .y = rect.y, + .width = rect.width, + .height = rect.height, + }; + return crect; +} + +ControlSize ControlValue::get_size() const { + if (this->inner.type() != libcamera::ControlType::ControlTypeSize || + this->inner.isArray()) { + throw std::runtime_error("Bad type! Expected Single Size."); + } + auto size = this->inner.get(); + ControlSize csize{ + .width = size.width, + .height = size.height, + }; + return csize; +} + +rust::String ControlValue::raw_to_string() const { + return this->inner.toString(); +} diff --git a/libcamera-bridge/core.cpp b/libcamera-bridge/core.cpp deleted file mode 100644 index ebdf127..0000000 --- a/libcamera-bridge/core.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include "./core.hpp" -#include "libcamera-rs/src/bridge.rs.h" - -void CameraManager::start() { - int res = libcamera::CameraManager::start(); - - if (res < 0) { - throw res; - } - - return; -} - -rust::String CameraManager::version() { - return libcamera::CameraManager::version(); -} - -rust::Vec CameraManager::cameras() const { - auto cameras = libcamera::CameraManager::cameras(); - rust::Vec rust_cameras; - - for (auto camera : cameras) { - rust_cameras.push_back(BridgeCamera { .inner = camera }); - } - - return rust_cameras; -} - -std::unique_ptr -make_camera_manager() { - return std::make_unique(); -} - -libcamera::Camera& -get_mut_camera(std::shared_ptr& cam) { - return *cam.get(); -} - -std::unique_ptr generate_camera_configuration(libcamera::Camera& cam, const rust::Vec& roles) { - std::vector cpp_roles; - - for (auto role : roles) { - cpp_roles.push_back(role); - } - - return cam.generateConfiguration(cpp_roles); -} diff --git a/libcamera-bridge/core.hpp b/libcamera-bridge/core.hpp index 9e5aa94..6899666 100644 --- a/libcamera-bridge/core.hpp +++ b/libcamera-bridge/core.hpp @@ -1,23 +1,308 @@ +#ifndef _LIBCAMERA_BRIDGE_CORE_HPP +#define _LIBCAMERA_BRIDGE_CORE_HPP + #pragma once -#include "libcamera/camera_manager.h" -#include "libcamera/camera.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + #include "rust/cxx.h" +struct BindCameraManager; +struct BindCamera; +struct BindCameraConfiguration; +struct BindStreamConfiguration; +struct BindPixelFormat; +struct BindSize; +struct BindStream; +struct BindFrameBufferAllocator; +struct BindFrameBuffer; +struct BindFrameBufferPlane; +struct BindFd; +struct BindMemoryBuffer; +struct BindRequest; +struct BindControlId; +struct BindControlValue; + +struct CameraConfiguration; +struct PixelFormat; +struct Size; +struct Request; +struct ControlValue; +struct ControlPair; + +enum class DefaultPixelFormat; +enum class CameraControlType; +enum class CameraMessageType; +struct CameraMessage; +struct ControlRectangle; +struct ControlSize; + using CameraConfigurationStatus = libcamera::CameraConfiguration::Status; -struct BridgeCamera; +static inline std::runtime_error error_from_code(int code) { + return std::runtime_error(strerror(code)); +} + +// Make sure this->inner is non-null +#define VALIDATE_POINTERS() \ + if (!this->inner) { \ + throw std::runtime_error("Inner pointer invalid."); \ + } + +BindCameraManager make_camera_manager(); + +struct CameraManager { +private: + std::unique_ptr inner; + +public: + explicit CameraManager(std::unique_ptr inner_) + : inner{std::move(inner_)} {} + + void start(); + void stop(); + [[nodiscard]] rust::Vec get_camera_ids() const; + BindCamera get_camera_by_id(rust::Str id); +}; + +struct Camera { +private: + std::shared_ptr inner; + + std::mutex message_mutex; + std::queue message_queue; + + std::unordered_map controls_by_id; + +public: + explicit Camera(std::shared_ptr inner_); + ~Camera(); + std::shared_ptr into_shared(); + const libcamera::ControlId *get_control_by_id(uint32_t id) const; + + void acquire(); + void release(); + BindCameraConfiguration generate_configuration( + rust::Slice /*roles*/); + void configure(CameraConfiguration &conf); + BindRequest create_request(uint64_t cookie); + void queue_request(Request &req); + void start(); + void stop(); + rust::Vec get_controls() const; + rust::Vec poll_events(); + rust::Vec poll_events_with_cookie(uint64_t request_cookie); +}; + +struct CameraConfiguration { +private: + std::unique_ptr inner; + +public: + explicit CameraConfiguration( + std::unique_ptr inner_) + : inner{std::move(inner_)} {} + libcamera::CameraConfiguration *into_ptr(); + + size_t size() const; + BindStreamConfiguration at(uint32_t idx); + CameraConfigurationStatus validate(); +}; + +struct StreamConfiguration { +private: + libcamera::StreamConfiguration *inner; + +public: + explicit StreamConfiguration(libcamera::StreamConfiguration *inner_) + : inner(inner_) {} + + [[nodiscard]] BindStream stream() const; + void set_pixel_format(BindPixelFormat pixel_format); + [[nodiscard]] BindPixelFormat get_pixel_format() const; + void set_size(BindSize size); + [[nodiscard]] BindSize get_size() const; + void set_buffer_count(size_t buffer_count); + [[nodiscard]] size_t get_buffer_count() const; + [[nodiscard]] rust::String raw_to_string() const; +}; + +BindPixelFormat get_default_pixel_format(DefaultPixelFormat default_format); + +struct PixelFormat { +private: + libcamera::PixelFormat inner; + +public: + explicit PixelFormat(libcamera::PixelFormat inner_) : inner(inner_) {} + libcamera::PixelFormat into_inner(); + + DefaultPixelFormat as_default_pixel_format() const; + [[nodiscard]] rust::String raw_to_string() const; +}; + +BindSize new_size(uint32_t width, uint32_t height); + +struct Size { +private: + libcamera::Size inner; + +public: + explicit Size(libcamera::Size inner_) : inner(inner_) {} + libcamera::Size into_inner(); + + void set_width(uint32_t width); + [[nodiscard]] uint32_t get_width() const; + void set_height(uint32_t height); + [[nodiscard]] uint32_t get_height() const; -class CameraManager: public libcamera::CameraManager { - public: - void start(); - rust::String version(); - rust::Vec cameras() const; + [[nodiscard]] rust::String raw_to_string() const; }; -std::unique_ptr -make_camera_manager(); +struct Stream { +private: + libcamera::Stream *inner; -libcamera::Camera& -get_mut_camera(std::shared_ptr& cam); +public: + explicit Stream(libcamera::Stream *inner_) : inner(inner_) {} + libcamera::Stream *into_ptr(); + const libcamera::Stream *into_ptr() const; +}; + +BindFrameBufferAllocator make_frame_buffer_allocator(Camera &camera); + +struct FrameBufferAllocator { +private: + std::unique_ptr inner; + +public: + explicit FrameBufferAllocator( + std::unique_ptr inner_) + : inner{std::move(inner_)} {} + + size_t allocate(Stream &stream); + void free(Stream &stream); + rust::Vec buffers(Stream &stream) const; +}; + +struct FrameBuffer { +private: + libcamera::FrameBuffer *inner; + +public: + explicit FrameBuffer(libcamera::FrameBuffer *inner_) : inner(inner_) {} + libcamera::FrameBuffer *into_ptr(); + + [[nodiscard]] rust::Vec planes() const; + void set_cookie(uint64_t cookie); + uint64_t get_cookie() const; +}; + +size_t fd_len(int file); + +struct FrameBufferPlane { +private: + const libcamera::FrameBuffer::Plane *inner; + +public: + explicit FrameBufferPlane(const libcamera::FrameBuffer::Plane *inner_) + : inner(inner_) {} + + [[nodiscard]] int get_fd() const; + [[nodiscard]] size_t get_offset() const; + [[nodiscard]] size_t get_length() const; +}; + +// File descriptor functions + +BindMemoryBuffer mmap_plane(int file, size_t len); + +struct MemoryBuffer { +private: + const uint8_t *pointer; + size_t length; + +public: + MemoryBuffer(const uint8_t *pointer_, size_t length_) + : pointer(pointer_), length(length_) {} + + BindMemoryBuffer sub_buffer(size_t offset, size_t length); + [[nodiscard]] rust::Vec read_to_vec() const; + size_t get_len() const; + size_t read_to_mut_slice(rust::Slice buf) const; +}; + +struct Request { +private: + std::unique_ptr inner; + +public: + explicit Request(std::unique_ptr inner_) + : inner{std::move(inner_)} {} + libcamera::Request *into_ptr(); + + void add_buffer(const Stream &stream, FrameBuffer &buffer); + BindControlValue get_control(uint32_t id) const; + void set_control(uint32_t control_, const ControlValue &value); + [[nodiscard]] rust::String raw_to_string() const; +}; + +struct ControlId { +private: + const libcamera::ControlId *inner; + +public: + explicit ControlId(const libcamera::ControlId *inner_) : inner{inner_} {} + + rust::String get_name() const; + uint32_t get_id() const; + CameraControlType get_type() const; +}; + +BindControlValue new_control_value_bool(bool value); +BindControlValue new_control_value_u8(uint8_t value); +BindControlValue new_control_value_i32(int32_t value); +BindControlValue new_control_value_i64(int64_t value); +BindControlValue new_control_value_f32(float value); +BindControlValue +new_control_value_f32_array(rust::Slice values_rust); +BindControlValue new_control_value_string(rust::Str value); +BindControlValue new_control_value_rectangle(ControlRectangle value); +BindControlValue new_control_value_size(ControlSize value); + +struct ControlValue { +private: + const libcamera::ControlValue inner; + +public: + explicit ControlValue(libcamera::ControlValue inner_) : inner{inner_} {} + const libcamera::ControlValue &get_inner() const; + + CameraControlType get_type() const; + bool is_array() const; + size_t len() const; + + bool get_bool() const; + uint8_t get_u8() const; + int32_t get_i32() const; + int64_t get_i64() const; + float get_f32() const; + rust::Vec get_f32_array() const; + rust::String get_string() const; + ControlRectangle get_rectangle() const; + ControlSize get_size() const; + + [[nodiscard]] rust::String raw_to_string() const; +}; -std::unique_ptr generate_camera_configuration(libcamera::Camera& cam, const rust::Vec& roles); +#endif diff --git a/libcamera-bridge/fd.cpp b/libcamera-bridge/fd.cpp new file mode 100644 index 0000000..2b2d8f9 --- /dev/null +++ b/libcamera-bridge/fd.cpp @@ -0,0 +1,26 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +#include +#include + +size_t fd_len(int file) { + long ret = lseek(file, 0, SEEK_END); + if (ret < 0) { + throw error_from_code(errno); + } + return ret; +} + +BindMemoryBuffer mmap_plane(int file, size_t len) { + void *address = mmap(nullptr, len, PROT_READ, MAP_SHARED, file, 0); + // MAP_FAILED expands to a silly C-style cast. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + if ((address == nullptr) || address == MAP_FAILED) { + throw error_from_code(errno); + } + BindMemoryBuffer buffer{.inner = std::make_unique( + static_cast(address), len)}; + return buffer; +} diff --git a/libcamera-bridge/frame_buffer.cpp b/libcamera-bridge/frame_buffer.cpp new file mode 100644 index 0000000..e7467fa --- /dev/null +++ b/libcamera-bridge/frame_buffer.cpp @@ -0,0 +1,35 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +libcamera::FrameBuffer *FrameBuffer::into_ptr() { + VALIDATE_POINTERS() + + return this->inner; +} + +rust::Vec FrameBuffer::planes() const { + VALIDATE_POINTERS() + + rust::Vec vec; + for (const libcamera::FrameBuffer::Plane &plane : this->inner->planes()) { + BindFrameBufferPlane bind_plane{ + .inner = std::make_unique(&plane), + }; + + vec.push_back(std::move(bind_plane)); + } + return vec; +} + +void FrameBuffer::set_cookie(uint64_t cookie) { + VALIDATE_POINTERS() + + this->inner->setCookie(cookie); +} + +uint64_t FrameBuffer::get_cookie() const { + VALIDATE_POINTERS() + + return this->inner->cookie(); +} diff --git a/libcamera-bridge/frame_buffer_allocator.cpp b/libcamera-bridge/frame_buffer_allocator.cpp new file mode 100644 index 0000000..ae421cf --- /dev/null +++ b/libcamera-bridge/frame_buffer_allocator.cpp @@ -0,0 +1,47 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +BindFrameBufferAllocator make_frame_buffer_allocator(Camera &camera) { + BindFrameBufferAllocator allocator{ + .inner = std::make_unique( + std::make_unique( + camera.into_shared())), + }; + return allocator; +} + +size_t FrameBufferAllocator::allocate(Stream &stream) { + VALIDATE_POINTERS() + + int ret = this->inner->allocate(stream.into_ptr()); + if (ret < 0) { + throw error_from_code(-ret); + } + + return ret; +} + +void FrameBufferAllocator::free(Stream &stream) { + VALIDATE_POINTERS() + + int ret = this->inner->free(stream.into_ptr()); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +rust::Vec FrameBufferAllocator::buffers(Stream &stream) const { + VALIDATE_POINTERS() + + rust::Vec vec; + for (const std::unique_ptr &buffer : + this->inner->buffers(stream.into_ptr())) { + BindFrameBuffer bind_buffer{ + .inner = std::make_unique(buffer.get()), + }; + + vec.push_back(std::move(bind_buffer)); + } + return vec; +} diff --git a/libcamera-bridge/frame_buffer_plane.cpp b/libcamera-bridge/frame_buffer_plane.cpp new file mode 100644 index 0000000..6886f3f --- /dev/null +++ b/libcamera-bridge/frame_buffer_plane.cpp @@ -0,0 +1,21 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +int FrameBufferPlane::get_fd() const { + VALIDATE_POINTERS() + + return this->inner->fd.get(); +} + +size_t FrameBufferPlane::get_offset() const { + VALIDATE_POINTERS() + + return (size_t)this->inner->offset; +} + +size_t FrameBufferPlane::get_length() const { + VALIDATE_POINTERS() + + return (size_t)this->inner->length; +} diff --git a/libcamera-bridge/memory_buffer.cpp b/libcamera-bridge/memory_buffer.cpp new file mode 100644 index 0000000..08447f5 --- /dev/null +++ b/libcamera-bridge/memory_buffer.cpp @@ -0,0 +1,36 @@ +#include "core.hpp" + +#include +#include + +#include "libcamera-rs/src/bridge.rs.h" + +BindMemoryBuffer MemoryBuffer::sub_buffer(size_t offset, size_t length) { + if (offset > this->length || offset + length > this->length) { + throw std::runtime_error("Sub buffer out of range of outer buffer."); + } + BindMemoryBuffer buffer{ + // The point of this class is to safely wrap raw memory pointers. + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + .inner = std::make_unique(this->pointer + offset, length)}; + return buffer; +} + +rust::Vec MemoryBuffer::read_to_vec() const { + rust::Vec buf; + buf.reserve(this->length); + for (size_t i = 0; i < this->length; i++) { + // The point of this class is to safely wrap raw memory pointers. + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + buf.push_back(this->pointer[i]); + } + return buf; +} + +size_t MemoryBuffer::get_len() const { return this->length; } + +size_t MemoryBuffer::read_to_mut_slice(rust::Slice buf) const { + size_t len_to_read = std::min(this->length, buf.size()); + memcpy(buf.data(), this->pointer, len_to_read); + return len_to_read; +} diff --git a/libcamera-bridge/pixel_format.cpp b/libcamera-bridge/pixel_format.cpp new file mode 100644 index 0000000..6275ff8 --- /dev/null +++ b/libcamera-bridge/pixel_format.cpp @@ -0,0 +1,88 @@ +#include "core.hpp" + +#include + +#include "libcamera-rs/src/bridge.rs.h" + +BindPixelFormat get_default_pixel_format(DefaultPixelFormat default_format) { + const libcamera::PixelFormat *fmt = nullptr; + switch (default_format) { + case DefaultPixelFormat::R8: + fmt = &libcamera::formats::R8; + break; + case DefaultPixelFormat::Rgb888: + fmt = &libcamera::formats::RGB888; + break; + case DefaultPixelFormat::Rgb565: + fmt = &libcamera::formats::RGB565; + break; + case DefaultPixelFormat::Bgr888: + fmt = &libcamera::formats::BGR888; + break; + case DefaultPixelFormat::Yuyv: + fmt = &libcamera::formats::YUYV; + break; + case DefaultPixelFormat::Yvyu: + fmt = &libcamera::formats::YVYU; + break; + case DefaultPixelFormat::Yuv420: + fmt = &libcamera::formats::YUV420; + break; + case DefaultPixelFormat::Yuv422: + fmt = &libcamera::formats::YUV422; + break; + case DefaultPixelFormat::Mjpeg: + fmt = &libcamera::formats::MJPEG; + break; + case DefaultPixelFormat::Nv12: + fmt = &libcamera::formats::NV12; + break; + } + if (fmt == nullptr) { + throw std::runtime_error("Unknown default pixel format."); + } + BindPixelFormat pixel_format{ + .inner = std::make_unique(*fmt), + }; + return pixel_format; +} + +DefaultPixelFormat PixelFormat::as_default_pixel_format() const { + if (this->inner == libcamera::formats::R8) { + return DefaultPixelFormat::R8; + } + if (this->inner == libcamera::formats::RGB888) { + return DefaultPixelFormat::Rgb888; + } + if (this->inner == libcamera::formats::RGB565) { + return DefaultPixelFormat::Rgb565; + } + if (this->inner == libcamera::formats::BGR888) { + return DefaultPixelFormat::Bgr888; + } + if (this->inner == libcamera::formats::YUYV) { + return DefaultPixelFormat::Yuyv; + } + if (this->inner == libcamera::formats::YVYU) { + return DefaultPixelFormat::Yvyu; + } + if (this->inner == libcamera::formats::YUV420) { + return DefaultPixelFormat::Yuv420; + } + if (this->inner == libcamera::formats::YUV422) { + return DefaultPixelFormat::Yuv422; + } + if (this->inner == libcamera::formats::MJPEG) { + return DefaultPixelFormat::Mjpeg; + } + if (this->inner == libcamera::formats::NV12) { + return DefaultPixelFormat::Nv12; + } + throw std::runtime_error("Unknown pixel format."); +} + +libcamera::PixelFormat PixelFormat::into_inner() { return this->inner; } + +rust::String PixelFormat::raw_to_string() const { + return this->inner.toString(); +} diff --git a/libcamera-bridge/request.cpp b/libcamera-bridge/request.cpp new file mode 100644 index 0000000..7ea4708 --- /dev/null +++ b/libcamera-bridge/request.cpp @@ -0,0 +1,47 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +void Request::add_buffer(const Stream &stream, FrameBuffer &buffer) { + VALIDATE_POINTERS() + + int ret = this->inner->addBuffer(stream.into_ptr(), buffer.into_ptr()); + if (ret < 0) { + throw error_from_code(-ret); + } +} + +libcamera::Request *Request::into_ptr() { + VALIDATE_POINTERS() + + return this->inner.get(); +} + +BindControlValue Request::get_control(uint32_t control_id) const { + VALIDATE_POINTERS() + + libcamera::ControlList &controls = this->inner->controls(); + + if (!controls.contains(control_id)) { + throw std::runtime_error( + "No control has been set in this request with the specified id."); + } + BindControlValue control_value{ + .inner = std::make_unique(controls.get(control_id)), + }; + return control_value; +} + +void Request::set_control(uint32_t control_, const ControlValue &value) { + VALIDATE_POINTERS() + + libcamera::ControlList &controls = this->inner->controls(); + + controls.set(control_, value.get_inner()); +} + +rust::String Request::raw_to_string() const { + VALIDATE_POINTERS() + + return this->inner->toString(); +} diff --git a/libcamera-bridge/size.cpp b/libcamera-bridge/size.cpp new file mode 100644 index 0000000..6eafe6c --- /dev/null +++ b/libcamera-bridge/size.cpp @@ -0,0 +1,22 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +BindSize new_size(uint32_t width, uint32_t height) { + BindSize size{ + .inner = std::make_unique(libcamera::Size(width, height)), + }; + return size; +} + +libcamera::Size Size::into_inner() { return this->inner; } + +void Size::set_width(uint32_t width) { this->inner.width = width; } + +uint32_t Size::get_width() const { return this->inner.width; } + +void Size::set_height(uint32_t height) { this->inner.height = height; } + +uint32_t Size::get_height() const { return this->inner.height; } + +rust::String Size::raw_to_string() const { return this->inner.toString(); } diff --git a/libcamera-bridge/stream.cpp b/libcamera-bridge/stream.cpp new file mode 100644 index 0000000..7dc3873 --- /dev/null +++ b/libcamera-bridge/stream.cpp @@ -0,0 +1,15 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +libcamera::Stream *Stream::into_ptr() { + VALIDATE_POINTERS() + + return this->inner; +} + +const libcamera::Stream *Stream::into_ptr() const { + VALIDATE_POINTERS() + + return this->inner; +} diff --git a/libcamera-bridge/stream_configuration.cpp b/libcamera-bridge/stream_configuration.cpp new file mode 100644 index 0000000..4d85c9d --- /dev/null +++ b/libcamera-bridge/stream_configuration.cpp @@ -0,0 +1,55 @@ +#include "core.hpp" + +#include "libcamera-rs/src/bridge.rs.h" + +BindStream StreamConfiguration::stream() const { + BindStream stream{ + .inner = std::make_unique(this->inner->stream()), + }; + return stream; +} + +void StreamConfiguration::set_pixel_format(BindPixelFormat pixel_format) { + VALIDATE_POINTERS() + + this->inner->pixelFormat = pixel_format.inner->into_inner(); +} + +BindPixelFormat StreamConfiguration::get_pixel_format() const { + VALIDATE_POINTERS() + + BindPixelFormat pixel_format{ + .inner = std::make_unique(this->inner->pixelFormat), + }; + return pixel_format; +} + +void StreamConfiguration::set_size(BindSize size) { + + this->inner->size = size.inner->into_inner(); +} + +BindSize StreamConfiguration::get_size() const { + VALIDATE_POINTERS() + + BindSize size{ + .inner = std::make_unique(this->inner->size), + }; + return size; +} + +void StreamConfiguration::set_buffer_count(size_t buffer_count) { + VALIDATE_POINTERS() + + this->inner->bufferCount = buffer_count; +} + +size_t StreamConfiguration::get_buffer_count() const { + VALIDATE_POINTERS() + + return this->inner->bufferCount; +} + +rust::String StreamConfiguration::raw_to_string() const { + return this->inner->toString(); +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..2d292a9 --- /dev/null +++ b/makefile @@ -0,0 +1,16 @@ +c: check +chk: check +lint: check +check: + cargo clippy + cargo test -- --test-threads=1 + cppcheck -q libcamera-bridge/*.hpp libcamera-bridge/*.cpp + clang-tidy --format-style=file libcamera-bridge/*.cpp -- -I/usr/local/include/libcamera -I./target/cxxbridge -I.. --std=c++17 + +f: format +fmt: format +format: + clang-format -style=file -i libcamera-bridge/* + cargo fmt + clang-tidy --format-style=file --fix --fix-errors --fix-notes libcamera-bridge/*.cpp -- -I/usr/local/include/libcamera -I./target/cxxbridge -I.. --std=c++17 + cargo clippy --fix --allow-dirty --allow-staged diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..6c5826c --- /dev/null +++ b/readme.md @@ -0,0 +1,65 @@ +# Libcamera-rs +[Libcamera](https://www.libcamera.org/) bindings for [Rust](https://www.rust-lang.org/). + +## Example usage to take a bunch of pictures and change some camera parameters: +```rust +use libcamera_rs::prelude::*; + +fn main() { + let mut cm = CameraManager::new().unwrap(); + // Get the first camera from the camera manager. + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + // Generate a configuration with one "viewfinder" optimized stream. + let conf = cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + let stream = &mut conf.streams_mut()[0]; + // Attempt to assign the stream's pixel format and size. + stream.set_pixel_format(PixelFormat::Mjpeg); + stream.set_size(640, 480); + // Apply the configuration. + cam.apply_config().unwrap(); + // Start the camera stream. + cam.start_stream().unwrap(); + // Take 10 pictures + for i in 0..10 { + // Change brightness and contrast + let brightness = cam.get_controls_mut().brightness.as_mut().unwrap(); + brightness.set_value((brightness.get_value() + 0.9).rem_euclid(1.5) - 0.5); + let contrast = cam.get_controls_mut().contrast.as_mut().unwrap(); + contrast.set_value(contrast.get_value() + 0.1); + // Queue the capture request + cam.capture_next_picture(0).unwrap(); + // Wait for a bit. + std::thread::sleep(std::time::Duration::from_millis(200)); + // Poll events (containing the images) + let events = cam.poll_events(None).unwrap(); + for event in events { + match event { + CameraEvent::RequestComplete { + serial_id, image, .. + } => { + // Reencode the image to PNG and save it. + let decoded_image = image.read_image(&cam).try_decode().unwrap(); + let rgb_image = decoded_image + .as_bgr() + .unwrap() + .as_rgb() + .encode_png() + .unwrap(); + let filename = format!("image_{serial_id}.png"); + std::fs::write(&filename, rgb_image).unwrap(); + } + _ => todo!() + } + } + } +} +``` + +Most functions should be well enough documented with rustdoc. + +# NOTE + +When cross compiling for raspberry pi, ensure `_GLIBCXX_HAVE_ATOMIC_LOCK_POLICY` is unset in `c++config.h`. +Otherwise the shared pointer to the camera will be misinterpreted by the inner reference counted lock type. +This results in the shared pointer that is returned by libcamera appearing to the main thread to only have 1 reference, +which causes it to be immediately destructed, causing it to be invalid by get_camera is called. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..e69de29 diff --git a/src/bridge.rs b/src/bridge.rs index b09daa2..d81a6c6 100644 --- a/src/bridge.rs +++ b/src/bridge.rs @@ -1,85 +1,533 @@ +#![allow(dead_code)] + use std::pin::Pin; -use cxx::{SharedPtr, UniquePtr}; + +#[cfg(test)] +mod test; #[cxx::bridge] pub mod ffi { - struct BridgeCamera { - inner: SharedPtr, - } + /// Represents a "role" that a camera stream can be optimized for. + #[namespace = "libcamera"] + #[repr(i32)] + #[derive(Debug)] + enum StreamRole { + /// ??? + Raw, + /// Capturing still images + StillCapture, + /// Recording a video + VideoRecording, + /// Displaying a viewfinder + Viewfinder, + } - #[namespace = "libcamera"] - #[repr(i32)] - #[derive(Debug)] - enum StreamRole { - Raw, - StillCapture, - VideoRecording, - Viewfinder, - } + #[namespace = "libcamera"] + #[repr(i32)] + #[derive(Debug)] + enum CameraConfigurationStatus { + Valid, + Adjusted, + Invalid, + } + + /// Represents common pixel formats. + #[repr(i32)] + #[derive(Debug)] + enum DefaultPixelFormat { + /// 8bpp, single channel image + R8, + /// 24bpp, three channel image, order red, green, blue + Rgb888, + /// 16bpp, three channel image, order red, green, blue + Rgb565, + /// 24bpp, three channel image, order blue, green, red + Bgr888, + /// 16bpp, three channel image, YUV (4:2:2) encoding, order Y' U Y' V + Yuyv, + /// 16bpp, three channel image, YUV (4:2:2) encoding, order Y' V Y' U + Yvyu, + /// 16bpp*, chroma subsampling (U, V half width + height), three channel image, YUV (4:2:0) encoing, order Y', U, V + Yuv420, + /// 16bpp*, chroma subsampling (U, V half width), three channel image, YUV (4:2:2) encoing, order Y', U, V + Yuv422, + /// 16bpp*, chroma subsampling (U, V half width + height), two channel image, YUV (4:2:0) encoing, order Y', U/V interlaced + Nv12, + /// MJPEG (motion JPEG) encoding, effectively one JPEG image per frame + Mjpeg, + } + + #[repr(i32)] + #[derive(Debug)] + enum BindErrorCode { + /// Operation not permitted + EPerm = 1, + /// No such file or directory + ENoEnt = 2, + /// No such process + ESrch = 3, + /// Interrupted system call + EIntr = 4, + /// I/O error + EIo = 5, + /// No such device or address + ENxIo = 6, + /// Argument list too long + E2Big = 7, + /// EXec format error + ENoExec = 8, + /// Bad file number + EBadF = 9, + /// No child processes + EChild = 10, + /// Try again + EAgain = 11, + /// Out of memory + ENoMem = 12, + /// Permission denied + EAcces = 13, + /// Bad address + EFault = 14, + /// Block device required + ENotBlk = 15, + /// Device or resource busy + EBusy = 16, + /// File exists + EExist = 17, + /// Cross-device link + EXDev = 18, + /// No such device + ENoDev = 19, + /// Not a directory + ENotDir = 20, + /// Is a directory + EIsDir = 21, + /// Invalid argument + EInval = 22, + /// File table overflow + ENFile = 23, + /// Too many open files + EMFile = 24, + /// Not a typewriter + ENotTy = 25, + /// Text file busy + ETxtBsy = 26, + /// File too large + EFBig = 27, + /// No space left on device + ENoSpc = 28, + /// Illegal seek + ESPipe = 29, + /// Read-only file system + ERoFs = 30, + /// Too many links + EMLink = 31, + /// Broken pipe + EPipe = 32, + /// Math argument out of domain of func + EDom = 33, + /// Math result not representable + ERange = 34, + } + + struct BindCameraManager { + inner: UniquePtr, + } + struct BindCamera { + inner: UniquePtr, + } + struct BindCameraConfiguration { + inner: UniquePtr, + } + struct BindPixelFormat { + inner: UniquePtr, + } + struct BindSize { + inner: UniquePtr, + } + struct BindStreamConfiguration { + inner: UniquePtr, + } + struct BindStream { + inner: UniquePtr, + } + struct BindFrameBufferAllocator { + inner: UniquePtr, + } + struct BindFrameBuffer { + inner: UniquePtr, + } + struct BindFrameBufferPlane { + inner: UniquePtr, + } + struct BindMemoryBuffer { + inner: UniquePtr, + } + struct BindRequest { + inner: UniquePtr, + } + struct BindControlId { + inner: UniquePtr, + } + struct BindControlValue { + inner: UniquePtr, + } + + #[repr(i32)] + #[derive(Debug)] + enum CameraMessageType { + RequestComplete, + BufferComplete, + } + + #[derive(Debug)] + struct CameraMessage { + message_type: CameraMessageType, + request_cookie: u64, + buffer_cookie: u64, + } + + #[repr(i32)] + #[derive(Debug)] + enum CameraControlType { + None = 0, + Bool = 1, + Byte = 2, + Integer32 = 3, + Integer64 = 4, + Float = 5, + String = 6, + Rectangle = 7, + Size = 8, + } + + struct ControlPair { + id: BindControlId, + min: BindControlValue, + max: BindControlValue, + value: BindControlValue, + valid_values: Vec, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct ControlRectangle { + x: i32, + y: i32, + width: u32, + height: u32, + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct ControlSize { + width: u32, + height: u32, + } + + extern "C++" { + include!("libcamera-rs/libcamera-bridge/core.hpp"); #[namespace = "libcamera"] - #[repr(i32)] - #[derive(Debug)] - enum CameraConfigurationStatus { - Valid, - Adjusted, - Invalid, - } + type StreamRole; + type CameraConfigurationStatus; + + type CameraManager; + pub unsafe fn make_camera_manager() -> BindCameraManager; + + pub unsafe fn start(self: Pin<&mut CameraManager>) -> Result<()>; + pub unsafe fn stop(self: Pin<&mut CameraManager>); + pub unsafe fn get_camera_ids(self: &CameraManager) -> Vec; + pub unsafe fn get_camera_by_id(self: Pin<&mut CameraManager>, id: &str) -> Result; + + type Camera; + pub unsafe fn acquire(self: Pin<&mut Camera>) -> Result<()>; + pub unsafe fn release(self: Pin<&mut Camera>) -> Result<()>; + pub unsafe fn generate_configuration( + self: Pin<&mut Camera>, + roles: &[StreamRole], + ) -> Result; + pub unsafe fn configure( + self: Pin<&mut Camera>, + conf: Pin<&mut CameraConfiguration>, + ) -> Result<()>; + pub unsafe fn create_request(self: Pin<&mut Camera>, cookie: u64) -> Result; + pub unsafe fn queue_request(self: Pin<&mut Camera>, req: Pin<&mut Request>) -> Result<()>; + pub unsafe fn start(self: Pin<&mut Camera>) -> Result<()>; + pub unsafe fn stop(self: Pin<&mut Camera>) -> Result<()>; + pub unsafe fn get_controls(self: &Camera) -> Vec; + pub unsafe fn poll_events(self: Pin<&mut Camera>) -> Vec; + pub unsafe fn poll_events_with_cookie( + self: Pin<&mut Camera>, + request_cookie: u64, + ) -> Vec; + + type CameraConfiguration; + pub unsafe fn size(self: &CameraConfiguration) -> usize; + pub unsafe fn at( + self: Pin<&mut CameraConfiguration>, + idx: u32, + ) -> Result; + pub unsafe fn validate(self: Pin<&mut CameraConfiguration>) -> CameraConfigurationStatus; + + type StreamConfiguration; + pub unsafe fn stream(self: &StreamConfiguration) -> BindStream; + pub unsafe fn set_pixel_format( + self: Pin<&mut StreamConfiguration>, + pixel_format: BindPixelFormat, + ); + pub unsafe fn get_pixel_format(self: &StreamConfiguration) -> BindPixelFormat; + pub unsafe fn set_size(self: Pin<&mut StreamConfiguration>, size: BindSize); + pub unsafe fn get_size(self: &StreamConfiguration) -> BindSize; + pub unsafe fn set_buffer_count(self: Pin<&mut StreamConfiguration>, buffer_count: usize); + pub unsafe fn get_buffer_count(self: &StreamConfiguration) -> usize; + pub unsafe fn raw_to_string(self: &StreamConfiguration) -> String; + + pub unsafe fn get_default_pixel_format(default_format: DefaultPixelFormat) -> BindPixelFormat; + + type PixelFormat; + pub unsafe fn as_default_pixel_format(self: &PixelFormat) -> Result; + pub unsafe fn raw_to_string(self: &PixelFormat) -> String; + + type Size; + pub unsafe fn new_size(width: u32, height: u32) -> BindSize; + pub unsafe fn set_width(self: Pin<&mut Size>, width: u32); + pub unsafe fn get_width(self: &Size) -> u32; + pub unsafe fn set_height(self: Pin<&mut Size>, height: u32); + pub unsafe fn get_height(self: &Size) -> u32; + pub unsafe fn raw_to_string(self: &Size) -> String; + + type Stream; + + type FrameBufferAllocator; + pub unsafe fn make_frame_buffer_allocator(camera: Pin<&mut Camera>) + -> BindFrameBufferAllocator; - unsafe extern "C++" { - include!("libcamera/stream.h"); + pub unsafe fn allocate( + self: Pin<&mut FrameBufferAllocator>, + stream: Pin<&mut Stream>, + ) -> Result; + pub unsafe fn free( + self: Pin<&mut FrameBufferAllocator>, + stream: Pin<&mut Stream>, + ) -> Result<()>; + pub unsafe fn buffers( + self: &FrameBufferAllocator, + stream: Pin<&mut Stream>, + ) -> Vec; - #[namespace = "libcamera"] - type StreamRole; + type FrameBuffer; + pub unsafe fn planes(self: &FrameBuffer) -> Vec; + pub unsafe fn set_cookie(self: Pin<&mut FrameBuffer>, cookie: u64); + pub unsafe fn get_cookie(self: &FrameBuffer) -> u64; - include!("libcamera/camera.h"); + type FrameBufferPlane; + pub unsafe fn get_fd(self: &FrameBufferPlane) -> i32; + pub unsafe fn get_offset(self: &FrameBufferPlane) -> usize; + pub unsafe fn get_length(self: &FrameBufferPlane) -> usize; - #[namespace = "libcamera"] - type CameraConfiguration; + /// File descriptor functions + pub unsafe fn fd_len(fd: i32) -> Result; + pub unsafe fn mmap_plane(fd: i32, length: usize) -> Result; - pub fn validate(self: Pin<&mut CameraConfiguration>) -> CameraConfigurationStatus; + type MemoryBuffer; + pub unsafe fn sub_buffer( + self: Pin<&mut MemoryBuffer>, + offset: usize, + length: usize, + ) -> Result; + pub unsafe fn read_to_vec(self: &MemoryBuffer) -> Vec; + pub unsafe fn get_len(self: &MemoryBuffer) -> usize; + pub unsafe fn read_to_mut_slice(self: &MemoryBuffer, buf: &mut [u8]) -> usize; - #[namespace = "libcamera"] - type Camera; + type Request; + pub unsafe fn add_buffer( + self: Pin<&mut Request>, + stream: &Stream, + buffer: Pin<&mut FrameBuffer>, + ) -> Result<()>; + pub unsafe fn get_control(self: &Request, id: u32) -> Result; + pub unsafe fn set_control(self: Pin<&mut Request>, id: u32, value: &ControlValue); + pub unsafe fn raw_to_string(self: &Request) -> String; - pub fn id(self: &Camera) -> &CxxString; - pub fn acquire(self: Pin<&mut Camera>) -> i32; + type ControlId; - pub(crate) fn generate_camera_configuration(cam: Pin<&mut Camera>, roles: &Vec) -> UniquePtr; + pub unsafe fn get_name(self: &ControlId) -> String; + pub unsafe fn get_id(self: &ControlId) -> u32; + pub unsafe fn get_type(self: &ControlId) -> CameraControlType; - include!("libcamera-rs/libcamera-bridge/core.hpp"); + type ControlValue; - type CameraConfigurationStatus; + pub unsafe fn new_control_value_bool(value: bool) -> BindControlValue; + pub unsafe fn new_control_value_u8(value: u8) -> BindControlValue; + pub unsafe fn new_control_value_i32(value: i32) -> BindControlValue; + pub unsafe fn new_control_value_i64(value: i64) -> BindControlValue; + pub unsafe fn new_control_value_f32(value: f32) -> BindControlValue; + pub unsafe fn new_control_value_f32_array(value: &[f32]) -> BindControlValue; + pub unsafe fn new_control_value_string(value: &str) -> BindControlValue; + pub unsafe fn new_control_value_rectangle(value: ControlRectangle) -> BindControlValue; + pub unsafe fn new_control_value_size(value: ControlSize) -> BindControlValue; - pub fn get_mut_camera(cam: &mut SharedPtr) -> Pin<&mut Camera>; + pub unsafe fn get_type(self: &ControlValue) -> CameraControlType; + pub unsafe fn is_array(self: &ControlValue) -> bool; + pub unsafe fn len(self: &ControlValue) -> usize; - type CameraManager; + pub unsafe fn get_bool(self: &ControlValue) -> Result; + pub unsafe fn get_u8(self: &ControlValue) -> Result; + pub unsafe fn get_i32(self: &ControlValue) -> Result; + pub unsafe fn get_i64(self: &ControlValue) -> Result; + pub unsafe fn get_f32(self: &ControlValue) -> Result; + pub unsafe fn get_f32_array(self: &ControlValue) -> Result>; + pub unsafe fn get_string(self: &ControlValue) -> Result; + pub unsafe fn get_rectangle(self: &ControlValue) -> Result; + pub unsafe fn get_size(self: &ControlValue) -> Result; - pub fn make_camera_manager() -> UniquePtr; - pub fn start(self: Pin<&mut CameraManager>) -> Result<()>; - pub fn stop(self: Pin<&mut CameraManager>); - pub fn version(self: Pin<&mut CameraManager>) -> String; - pub fn cameras(self: &CameraManager) -> Vec; - pub fn get(self: Pin<&mut CameraManager>, id: &CxxString) -> SharedPtr; - } + pub unsafe fn raw_to_string(self: &ControlValue) -> String; + } } -impl ffi::Camera { - pub fn generate_configuration(self: Pin<&mut Self>, roles: &Vec) -> UniquePtr { - ffi::generate_camera_configuration(self, roles) - } +/// # Safety +/// The inner pointer to the libcamera object must be valid. +pub unsafe trait GetInner { + type Inner; + unsafe fn get(&self) -> &Self::Inner; + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner>; } -pub trait MutFromSharedPtr { - type Target; +unsafe impl GetInner for ffi::BindCameraManager { + type Inner = ffi::CameraManager; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindCamera { + type Inner = ffi::Camera; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindCameraConfiguration { + type Inner = ffi::CameraConfiguration; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindStreamConfiguration { + type Inner = ffi::StreamConfiguration; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindPixelFormat { + type Inner = ffi::PixelFormat; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindSize { + type Inner = ffi::Size; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} - fn pin_mut(&mut self) -> Pin<&mut Self::Target>; +unsafe impl GetInner for ffi::BindStream { + type Inner = ffi::Stream; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } } -impl MutFromSharedPtr for SharedPtr { - type Target = ffi::Camera; +unsafe impl GetInner for ffi::BindFrameBufferAllocator { + type Inner = ffi::FrameBufferAllocator; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindFrameBuffer { + type Inner = ffi::FrameBuffer; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindFrameBufferPlane { + type Inner = ffi::FrameBufferPlane; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindMemoryBuffer { + type Inner = ffi::MemoryBuffer; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindRequest { + type Inner = ffi::Request; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} + +unsafe impl GetInner for ffi::BindControlId { + type Inner = ffi::ControlId; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } +} - fn pin_mut(&mut self) -> Pin<&mut Self::Target> { - ffi::get_mut_camera(self) - } +unsafe impl GetInner for ffi::BindControlValue { + type Inner = ffi::ControlValue; + unsafe fn get(&self) -> &Self::Inner { + &self.inner + } + unsafe fn get_mut(&mut self) -> Pin<&mut Self::Inner> { + self.inner.pin_mut() + } } diff --git a/src/bridge/test.rs b/src/bridge/test.rs new file mode 100644 index 0000000..497733f --- /dev/null +++ b/src/bridge/test.rs @@ -0,0 +1,152 @@ +use crate::bridge::ffi; +use crate::bridge::GetInner; + +use std::collections::HashMap; + +#[test] +fn test_unsafe_camera() { + let mut cm = unsafe { ffi::make_camera_manager() }; + unsafe { cm.get_mut().start() }.unwrap(); + let camera_ids = unsafe { cm.get().get_camera_ids() }; + println!("Available Cameras: {camera_ids:?}"); + let mut camera = unsafe { cm.get_mut().get_camera_by_id(&camera_ids[0]) }.unwrap(); + + unsafe { camera.get_mut().acquire() }.unwrap(); + + let mut allocator = unsafe { ffi::make_frame_buffer_allocator(camera.get_mut()) }; + + let mut config = unsafe { + camera + .get_mut() + .generate_configuration(&[ffi::StreamRole::StillCapture]) + } + .unwrap(); + + let mut stream_config = unsafe { config.get_mut().at(0) }.unwrap(); + + unsafe { + stream_config + .get_mut() + .set_pixel_format(ffi::get_default_pixel_format( + ffi::DefaultPixelFormat::Mjpeg, + )) + }; + + unsafe { stream_config.get_mut().set_size(ffi::new_size(1280, 720)) }; + + let status = unsafe { config.get_mut().validate() }; + if status == ffi::CameraConfigurationStatus::Invalid { + panic!("Invalid Camera Configuration!"); + } + if status == ffi::CameraConfigurationStatus::Adjusted { + println!("Camera Configuration Adjusted."); + } + + unsafe { stream_config.get_mut().set_buffer_count(1) }; + + unsafe { camera.get_mut().configure(config.get_mut()) }.unwrap(); + + let controls = unsafe { camera.get().get_controls() }; + for control in &controls { + println!( + "Camera control '{}': ID={}, type={:?}, value={}, values={:?}", + unsafe { control.id.get().get_name() }, + unsafe { control.id.get().get_id() }, + unsafe { control.id.get().get_type() }, + unsafe { control.value.get().raw_to_string() }, + control + .valid_values + .iter() + .map(|v| unsafe { v.get().raw_to_string() }) + .collect::>(), + ); + } + + let mut stream = unsafe { stream_config.get().stream() }; + + let buffer_count = unsafe { allocator.get_mut().allocate(stream.get_mut()) }.unwrap(); + println!("Buffers: {buffer_count}"); + + let mut requests = Vec::new(); + let mut planes = Vec::new(); + for mut buffer in unsafe { allocator.get().buffers(stream.get_mut()) } { + let mut request = unsafe { camera.get_mut().create_request(69) }.unwrap(); + + unsafe { + request + .get_mut() + .set_control(9, ffi::new_control_value_f32(-0.5).get()) + }; + for control in &controls { + println!( + "Control '{}' value in current request: {:?}", + unsafe { control.id.get().get_id() }, + unsafe { request.get().get_control(control.id.get().get_id()) } + .map(|c| unsafe { c.get().raw_to_string() }) + ); + } + + unsafe { buffer.get_mut().set_cookie(420) }; + + let mut mapped_buffers: HashMap, usize, usize)> = + HashMap::new(); + for plane in unsafe { buffer.get().planes() } { + let fd = unsafe { plane.get().get_fd() }; + let length = mapped_buffers + .entry(fd) + .or_insert((None, 0, unsafe { ffi::fd_len(fd) }.unwrap())) + .2; + if unsafe { plane.get().get_offset() } + unsafe { plane.get().get_length() } > length { + panic!( + "Plane is out of buffer: buffer length = {length}, plane offset = {}, plane length = {}", + unsafe { plane.get().get_offset() }, + unsafe { plane.get().get_length() } + ); + } + let map_len = mapped_buffers[&fd].1; + mapped_buffers.get_mut(&fd).unwrap().1 = + map_len.max(unsafe { plane.get().get_offset() } + unsafe { plane.get().get_length() }); + } + for plane in unsafe { buffer.get().planes() } { + let fd = unsafe { plane.get().get_fd() }; + let mapped_buffer = mapped_buffers.get_mut(&fd).unwrap(); + if mapped_buffer.0.is_none() { + mapped_buffer.0 = Some(unsafe { ffi::mmap_plane(fd, mapped_buffer.1) }.unwrap()); + } + planes.push( + unsafe { + mapped_buffer + .0 + .as_mut() + .unwrap() + .get_mut() + .sub_buffer(plane.get().get_offset(), plane.get().get_length()) + } + .unwrap(), + ); + } + + unsafe { request.get_mut().add_buffer(stream.get(), buffer.get_mut()) }.unwrap(); + requests.push(request); + } + + unsafe { camera.get_mut().start() }.unwrap(); + + for request in &mut requests { + unsafe { camera.get_mut().queue_request(request.get_mut()) }.unwrap(); + } + + std::thread::sleep(std::time::Duration::from_millis(1000)); + + println!("Events: {:?}", unsafe { camera.get_mut().poll_events() }); + + for (i, plane) in planes.iter_mut().enumerate() { + std::fs::write(&format!("plane_{i}.bin"), unsafe { + plane.get().read_to_vec() + }) + .unwrap(); + } + + unsafe { camera.get_mut().stop() }.unwrap(); + unsafe { camera.get_mut().release() }.unwrap(); +} diff --git a/src/camera.rs b/src/camera.rs new file mode 100644 index 0000000..b559c2a --- /dev/null +++ b/src/camera.rs @@ -0,0 +1,542 @@ +use std::collections::HashMap; +use std::fmt::{self, Debug}; +use std::marker::PhantomData; +use std::sync::RwLock; +use std::time::Instant; + +use log::{debug, trace}; + +use crate::bridge::{ffi, GetInner}; +use crate::config::{CameraConfig, PixelFormat}; +use crate::controls::CameraControls; +use crate::image::{self, CameraImage, MultiImage}; +use crate::{LibcameraError, Result}; + +pub use ffi::StreamRole; + +/// Manages cameras +pub struct CameraManager { + inner: RwLock, +} + +unsafe impl Send for CameraManager {} +unsafe impl Sync for CameraManager {} + +impl Debug for CameraManager { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CameraManager").finish_non_exhaustive() + } +} + +impl CameraManager { + /// Constructs a new camera manager + pub fn new() -> Result { + let mut cm = unsafe { ffi::make_camera_manager() }; + // The primary safety concern for the CM is that it must be started once before calling all functions. + unsafe { cm.get_mut().start() }?; + Ok(CameraManager { + inner: RwLock::new(cm), + }) + } + /// Get a list of all attached cameras + pub fn get_camera_names(&self) -> Vec { + unsafe { self.inner.read().unwrap().get().get_camera_ids() } + } + /// Get a camera with a given name + pub fn get_camera_by_name(&self, name: &str) -> Result> { + let mut cam = unsafe { self.inner.write().unwrap().get_mut().get_camera_by_id(name) }?; + unsafe { cam.get_mut().acquire() }?; + let allocator = unsafe { ffi::make_frame_buffer_allocator(cam.get_mut()) }; + let controls = CameraControls::from_libcamera(unsafe { cam.get().get_controls() }); + Ok(Camera { + _camera_manager: PhantomData, + name: name.to_string(), + config: None, + inner: cam, + allocator, + streams: Vec::new(), + configured: false, + started: false, + controls, + next_request_id: 0, + request_infos: HashMap::new(), + }) + } +} + +impl Drop for CameraManager { + fn drop(&mut self) { + unsafe { self.inner.write().unwrap().get_mut().stop() }; + } +} + +struct CameraBuffer { + buffer: ffi::BindFrameBuffer, + request: Option, + planes: Vec, +} + +impl Debug for CameraBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CameraBuffer") + .field("plane_count", &self.planes.len()) + .finish_non_exhaustive() + } +} + +struct CameraStream { + pixel_format: Option, + width: u32, + height: u32, + stream: ffi::BindStream, + next_buffer: usize, + buffers: Vec, +} + +impl Debug for CameraStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CameraStream") + .field("pixel_format", &self.pixel_format) + .field("width", &self.width) + .field("height", &self.height) + .field("next_buffer", &self.next_buffer) + .field("buffers", &self.buffers) + .finish_non_exhaustive() + } +} + +struct RequestInfo { + stream_id: usize, + buffer_id: usize, + timestamp: Instant, +} + +/// Represents a camera +pub struct Camera<'a> { + _camera_manager: PhantomData<&'a CameraManager>, + name: String, + config: Option, + inner: ffi::BindCamera, + allocator: ffi::BindFrameBufferAllocator, + streams: Vec, + configured: bool, + started: bool, + controls: CameraControls, + next_request_id: u64, + request_infos: HashMap, +} + +unsafe impl Send for Camera<'_> {} +unsafe impl Sync for Camera<'_> {} + +impl Debug for Camera<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Camera") + .field("name", &self.name) + .field("config", &self.config) + .field("streams", &self.streams) + .field("started", &self.started) + .field("controls", &self.controls) + .field("next_request_id", &self.next_request_id) + .finish_non_exhaustive() + } +} + +impl Camera<'_> { + /// Generate a configuration for this camera using the given set of stream roles to generate an corresponding set of streams. + pub fn generate_config(&mut self, caps: &[StreamRole]) -> Result<&mut CameraConfig> { + self.configured = false; + let config = unsafe { self.inner.get_mut().generate_configuration(caps) }?; + self.config = Some(CameraConfig::wrap_inner(config)?); + self.config.as_mut().ok_or(LibcameraError::InvalidConfig) + } + /// Validate and apply the configuration previously generated by this camera. + pub fn apply_config(&mut self) -> Result { + if let Some(config) = &mut self.config { + let config_status = unsafe { config.get_inner().get_mut().validate() }; + let (set, result) = match config_status { + ffi::CameraConfigurationStatus::Valid => (true, Ok(ConfigStatus::Unchanged)), + ffi::CameraConfigurationStatus::Adjusted => (true, Ok(ConfigStatus::Changed)), + _ => (false, Err(LibcameraError::InvalidConfig)), + }; + if set { + self.configured = true; + unsafe { self.inner.get_mut().configure(config.get_inner().get_mut()) }?; + } + result + } else { + Err(LibcameraError::InvalidConfig) + } + } + /// Borrow this camera's config. + pub fn get_config(&self) -> Option<&CameraConfig> { + self.config.as_ref() + } + /// Borrow the camera's controls + pub fn get_controls(&self) -> &CameraControls { + &self.controls + } + /// Borrow the camera's controls mutably + pub fn get_controls_mut(&mut self) -> &mut CameraControls { + &mut self.controls + } + /// Start the camera so that it's ready to capture images. + /// + /// This should only be called once, future calls will do nothing and the camera's streams cannot be configured while it is started. + /// # Returns + /// On success returns whether the stream was newly started (i.e. false means the stream was already running). + /// This will fail if the camera has not been properly configured, or if libcamera decides to not work. + /// # Panics + /// This will panic if the buffer sizes produced by libcamera extend past the end of the actual camera memory buffer. + pub fn start_stream(&mut self) -> Result { + if !self.configured { + return Err(LibcameraError::InvalidConfig); + } + if self.started { + // Do nothing if the camera was already started. + return Ok(false); + } + self.started = true; + // Ok so: + // The camera contains streams, each stream has multiple buffers and each buffer has multiple planes. + // Each request to the camera operates on one buffer on one stream and fills the buffer with data from that stream. + // To start the camera we must allocate the buffers for all the streams and save them somewhere for future reading. + // We also must create a request for each buffer that we can re-use later every time we need an image. + // Technically requests can have multiple buffers, but I don't think know why this would be the case and I don't think it's necessary. + + debug!("Starting camera..."); + + // For each stream... + for stream_config in self + .config + .as_ref() + .ok_or(LibcameraError::InvalidConfig)? + .streams() + { + trace!("Stream config: {stream_config:?}"); + let mut stream = unsafe { stream_config.get_inner().get().stream() }; + // Allocate buffers + let _buffer_count = unsafe { self.allocator.get_mut().allocate(stream.get_mut()) }; + let (width, height) = stream_config.get_size(); + let mut camera_stream = CameraStream { + pixel_format: stream_config.get_pixel_format(), + width, + height, + stream, + next_buffer: 0, + buffers: Vec::new(), + }; + trace!("Camera stream: {camera_stream:?}"); + // Map memory for buffers + for mut buffer in unsafe { self.allocator.get().buffers(camera_stream.stream.get_mut()) } { + let buffer_id = camera_stream.buffers.len(); + unsafe { buffer.get_mut().set_cookie(buffer_id as u64) }; + let mut planes = Vec::new(); + let mut mapped_buffers: HashMap, usize, usize)> = + HashMap::new(); + for plane in unsafe { buffer.get().planes() } { + let fd = unsafe { plane.get().get_fd() }; + let mapped_buffer = mapped_buffers + .entry(fd) + .or_insert((None, 0, unsafe { ffi::fd_len(fd) }?)); + let length = mapped_buffer.2; + let plane_offset = unsafe { plane.get().get_offset() }; + let plane_length = unsafe { plane.get().get_length() }; + if plane_offset + plane_length > length { + panic!( + "Plane is out of buffer: buffer length = {length}, plane offset = {}, plane length = {}", + unsafe { plane.get().get_offset() }, + unsafe { plane.get().get_length() }, + ); + } + mapped_buffer.1 = mapped_buffer.1.max(plane_offset + plane_length); + } + for plane in unsafe { buffer.get().planes() } { + let fd = unsafe { plane.get().get_fd() }; + let mapped_buffer = mapped_buffers.get_mut(&fd).unwrap(); + if mapped_buffer.0.is_none() { + mapped_buffer.0 = Some(unsafe { ffi::mmap_plane(fd, mapped_buffer.1) }?); + } + planes.push(unsafe { + mapped_buffer + .0 + .as_mut() + .unwrap() + .get_mut() + .sub_buffer(plane.get().get_offset(), plane.get().get_length()) + }?); + } + + camera_stream.buffers.push(CameraBuffer { + request: None, + buffer, + planes, + }); + } + self.streams.push(camera_stream); + } + unsafe { self.inner.get_mut().start() }?; + Ok(true) + } + /// Start the process to capture an image from the camera. + /// + /// # Returns + /// On success returns the `serial_id` of the request, which can be used to match with the correct request complete event. + /// + /// # Errors + /// Errors if there are no buffers currently available (all buffers are in-use, if this happens take pictures slower!) + pub fn capture_next_picture(&mut self, stream_id: usize) -> Result { + let mut stream = &mut self.streams[stream_id]; + if stream.buffers.is_empty() { + return Err(LibcameraError::NoBufferReady); + } + let buffer = &mut stream.buffers[stream.next_buffer]; + if buffer.request.is_none() { + let request_id = self.next_request_id; + let mut req = unsafe { self.inner.get_mut().create_request(request_id) }?; + unsafe { + req + .get_mut() + .add_buffer(stream.stream.get(), buffer.buffer.get_mut()) + }?; + for (control_id, control_value) in self.controls.get_libcamera() { + unsafe { req.get_mut().set_control(control_id, control_value.get()) }; + } + let timestamp = Instant::now(); + unsafe { self.inner.get_mut().queue_request(req.get_mut()) }?; + self.request_infos.insert( + request_id, + RequestInfo { + stream_id, + buffer_id: stream.next_buffer, + timestamp, + }, + ); + self.next_request_id += 1; + buffer.request = Some(req); + stream.next_buffer += 1; + stream.next_buffer %= stream.buffers.len(); + Ok(request_id) + } else { + Err(LibcameraError::NoBufferReady) + } + } + /// Poll events from the camera. + /// + /// The results should be in order of when the camera sent them, but not neccesarily in order of when they were initially queued. Make sure to use `serial_id`, or the event `timestamp` to keep track of that if you need to. + pub fn poll_events(&mut self, match_id: Option) -> Result> { + let events = if let Some(match_id) = match_id { + unsafe { self.inner.get_mut().poll_events_with_cookie(match_id) } + } else { + unsafe { self.inner.get_mut().poll_events() } + }; + Ok( + events + .into_iter() + .flat_map(|event| match event.message_type { + ffi::CameraMessageType::RequestComplete => { + let request_id = event.request_cookie; + let request_info = self.request_infos.remove(&request_id)?; + // trace!( + // "Request completed on stream {}, buffer {}.", + // request_info.stream_id, + // request_info.buffer_id + // ); + let stream = &mut self.streams[request_info.stream_id]; + let buffer = &mut stream.buffers[request_info.buffer_id]; + buffer.request = None; + let width = stream.width as usize; + let height = stream.height as usize; + let pixel_format = stream.pixel_format; + + Some(CameraEvent::RequestComplete { + serial_id: request_id, + queue_timestamp: request_info.timestamp, + image: ImageBuffer { + width, + height, + pixel_format, + stream_id: request_info.stream_id, + buffer_id: request_info.buffer_id, + }, + }) + } + _ => None, + }) + .collect(), + ) + } +} + +impl Drop for Camera<'_> { + fn drop(&mut self) { + log::trace!("Waiting for requests to complete before dropping camera..."); + // Ensure there are no outstanding requests before deallocating everything. + // TODO: It would potentially be a better idea to use a thread to hold on to important c++ references instead of blocking here. + while !self.request_infos.is_empty() { + std::thread::sleep(std::time::Duration::from_millis(50)); + for event in unsafe { self.inner.get_mut().poll_events() } { + self.request_infos.remove(&event.request_cookie); + } + } + log::trace!("Stopping camera..."); + self.streams = Vec::new(); + unsafe { self.inner.get_mut().stop() }.unwrap(); + unsafe { self.inner.get_mut().release() }.unwrap(); + log::trace!("Dropping camera..."); + } +} + +/// Represents raw image data fetched from the camera. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RawCameraImage { + /// The pixel format for the image, if it is known. + pub pixel_format: Option, + /// The width of the image. + pub width: usize, + /// The height of the image. + pub height: usize, + /// The raw data planes for the image. + pub planes: Vec>, +} + +impl RawCameraImage { + /// Attempts to decode this camera image. + /// + /// Currently only supports Bgr, Rgb, Yuyv, and Yuv420 formats, and Mjpeg with the `image` feature. + pub fn try_decode(self) -> Option { + debug!("Tying to decode image..."); + match (self.pixel_format, self.planes.as_slice()) { + (Some(PixelFormat::Bgr888), [data]) => { + image::BgrImage::from_planes(self.width, self.height, [data.to_owned()]) + .map(MultiImage::Bgr) + } + (Some(PixelFormat::Rgb888), [data]) => { + image::RgbImage::from_planes(self.width, self.height, [data.to_owned()]) + .map(MultiImage::Rgb) + } + (Some(PixelFormat::Yuyv), [data]) => { + image::YuyvImage::from_planes(self.width, self.height, [data.to_owned()]) + .map(MultiImage::Yuyv) + } + (Some(PixelFormat::Yuv420), [y, u, v]) => { + trace!( + "Decoding YUV with size {}x{} and plane sizes {} {} {}", + self.width, + self.height, + y.len(), + u.len(), + v.len() + ); + image::Yuv420Image::from_planes( + self.width, + self.height, + [y.to_owned(), u.to_owned(), v.to_owned()], + ) + .map(MultiImage::Yuv420) + } + #[cfg(feature = "image")] + (Some(PixelFormat::Mjpeg), [data]) => { + image::RgbImage::decode_jpeg(data).ok().map(MultiImage::Rgb) + } + (Some(PixelFormat::Nv12), [y, uv]) => { + trace!( + "Decoding NV12 with size {}x{} and plane sizes {} {}", + self.width, + self.height, + y.len(), + uv.len(), + ); + image::Nv12Image::from_planes(self.width, self.height, [y.to_owned(), uv.to_owned()]) + .map(MultiImage::Nv12) + } + (fmt, planes) => { + trace!( + "Image is of unknown format: {:?} with {} planes", + fmt, + planes.len() + ); + None + } + } + } +} + +/// Represents an event from the camera +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum CameraEvent { + /// Triggered when a capture request has completed, containing a vec of the resulting image planes. + RequestComplete { + /// The same `serial_id` that was returned from the function that queued this request. + serial_id: u64, + /// When this event was __queued__ to the camera. + queue_timestamp: Instant, + /// A reference to the image buffer for this request. + /// Contains the raw image data for this request, might not actually contain a real image (at the moment there isn't any way of determining success as far as I can tell). + /// + /// The user is responsibe for immediately polling this otherwise the image might be overridden by a newer one (typically in around ~67ms). + image: ImageBuffer, + }, +} + +/// References a camera buffer that you can read an image from. +#[derive(Clone)] +#[non_exhaustive] +pub struct ImageBuffer { + /// The pixel format for the image, if it is known. + pub pixel_format: Option, + /// The width of the image. + pub width: usize, + /// The height of the image. + pub height: usize, + stream_id: usize, + buffer_id: usize, +} + +impl Debug for ImageBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ImageBuffer") + .field("pixel_format", &self.pixel_format) + .field("width", &self.width) + .field("height", &self.height) + .field("stream_id", &self.stream_id) + .field("buffer_id", &self.buffer_id) + .finish_non_exhaustive() + } +} + +impl ImageBuffer { + /// Read the image into a [RawCameraImage]. + /// + /// This function is *slow* especially in a debug build. + pub fn read_image(self, cam: &Camera<'_>) -> RawCameraImage { + let start = Instant::now(); + let planes = cam.streams[self.stream_id].buffers[self.buffer_id] + .planes + .iter() + .map(|plane| { + //let buf = unsafe { plane.get().read_to_vec() }; + let mut buf = vec![0; unsafe { plane.get().get_len() }]; + unsafe { plane.get().read_to_mut_slice(&mut buf) }; + buf + }) + .collect(); + debug!("Read image from buffer in {:?}", start.elapsed()); + RawCameraImage { + pixel_format: self.pixel_format, + width: self.width, + height: self.height, + planes, + } + } +} + +/// Represents the result of applying a configuration to a camera. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConfigStatus { + /// The configuration was applied to the camera unchanged + Unchanged, + /// The configuration was applied to the camera, but some values have been adjusted by the driver to a supported configuration for this camera + Changed, +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..167e5d0 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,100 @@ +use std::fmt; + +use crate::bridge::{ffi, GetInner}; + +use crate::Result; + +pub use ffi::DefaultPixelFormat as PixelFormat; + +/// Represents the configuration for a camera. +pub struct CameraConfig { + inner: ffi::BindCameraConfiguration, + streams: Vec, +} + +impl CameraConfig { + pub(crate) fn wrap_inner(mut inner: ffi::BindCameraConfiguration) -> Result { + let streams = (0..unsafe { inner.get().size() }) + .map(|n| { + Ok(StreamConfig::wrap_inner(unsafe { + inner.get_mut().at(n.try_into()?) + }?)) + }) + .collect::>>()?; + Ok(CameraConfig { inner, streams }) + } + pub(crate) fn get_inner(&mut self) -> &mut ffi::BindCameraConfiguration { + &mut self.inner + } + /// Get a reference to the vec of stream configurations contained within this camera configuration. + pub fn streams(&self) -> &Vec { + &self.streams + } + /// Get a mutable reference to the vec of stream configurations contained within this camera configuration. + pub fn streams_mut(&mut self) -> &mut Vec { + &mut self.streams + } +} + +impl fmt::Debug for CameraConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CameraConfig") + .field("streams", &self.streams) + .finish_non_exhaustive() + } +} + +/// Represents the configuration for a stream in a camera. +pub struct StreamConfig { + inner: ffi::BindStreamConfiguration, +} + +impl StreamConfig { + pub(crate) fn wrap_inner(inner: ffi::BindStreamConfiguration) -> Self { + StreamConfig { inner } + } + pub(crate) fn get_inner(&self) -> &ffi::BindStreamConfiguration { + &self.inner + } + /// Set the pixel format for this stream to a [PixelFormat] + pub fn set_pixel_format(&mut self, fmt: PixelFormat) { + unsafe { + self + .inner + .get_mut() + .set_pixel_format(ffi::get_default_pixel_format(fmt)) + }; + } + /// Retrieve the current pixel format + /// + /// # Returns + /// Returns None if the pixel format is not a known pixel format. + pub fn get_pixel_format(&self) -> Option { + let pixel_format = unsafe { self.inner.get().get_pixel_format() }; + unsafe { pixel_format.get().as_default_pixel_format() }.ok() + } + /// Set the target image size for this stream. + pub fn set_size(&mut self, width: u32, height: u32) { + unsafe { self.inner.get_mut().set_size(ffi::new_size(width, height)) }; + } + /// Get the target image size for this stream. + pub fn get_size(&self) -> (u32, u32) { + let size = unsafe { self.inner.get().get_size() }; + (unsafe { size.get().get_width() }, unsafe { + size.get().get_height() + }) + } + /// Get a human-readable description of this stream configuraiton from libcamera. + pub fn description(&self) -> String { + unsafe { self.inner.get().raw_to_string() } + } +} + +impl fmt::Debug for StreamConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StreamConfig") + .field("size", &self.get_size()) + .field("pixel_format", &self.get_pixel_format()) + .finish_non_exhaustive() + } +} diff --git a/src/controls.rs b/src/controls.rs new file mode 100644 index 0000000..1eaec4c --- /dev/null +++ b/src/controls.rs @@ -0,0 +1,828 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::fmt::Debug; +use std::ops::RangeInclusive; + +use log::{error, warn}; + +use crate::bridge::{ffi, GetInner}; +use crate::{LibcameraError, Result}; + +/// Contains a value with an acceptable minimum and maximum and a default. +#[derive(Debug)] +pub struct MinMaxValue { + range: Option>, + default: T, + value: T, +} + +/// A pair of two float values. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub struct FloatPair(pub f32, pub f32); + +/// Things that can be clamped to a range. +pub trait Clampable { + /// Clamp self to fit inside range. + fn clamp(self, range: &RangeInclusive) -> Self + where + Self: Sized + PartialOrd + Clone, + { + if range.start() > &self { + range.start().clone() + } else if range.end() < &self { + range.end().clone() + } else { + self + } + } +} + +impl Clampable for bool {} +impl Clampable for u8 {} +impl Clampable for i32 {} +impl Clampable for i64 {} +impl Clampable for f32 {} +impl Clampable for String {} + +impl Clampable for FloatPair { + fn clamp(self, range: &RangeInclusive) -> Self { + FloatPair( + self.0.clamp(range.start().0, range.end().0), + self.1.clamp(range.start().1, range.end().1), + ) + } +} + +/// Represents a control value rectangle. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rectangle { + /// The starting x position + x: i32, + /// The starting y position + y: i32, + /// The width + width: u32, + /// The height + height: u32, +} + +impl From for Rectangle { + fn from(v: ffi::ControlRectangle) -> Rectangle { + Rectangle { + x: v.x, + y: v.y, + width: v.width, + height: v.height, + } + } +} + +impl From for ffi::ControlRectangle { + fn from(v: Rectangle) -> ffi::ControlRectangle { + ffi::ControlRectangle { + x: v.x, + y: v.y, + width: v.width, + height: v.height, + } + } +} + +impl PartialOrd for Rectangle { + fn partial_cmp(&self, other: &Rectangle) -> Option { + let x = self.x.cmp(&other.x); + let y = self.y.cmp(&other.y); + let w = self.width.cmp(&other.width); + let h = self.height.cmp(&other.height); + if (x == y && w == h && x == w) || (y == Ordering::Equal && w == y && h == y) { + Some(x) + } else if x == Ordering::Equal && w == x && h == x { + Some(y) + } else if x == Ordering::Equal && y == x && h == x { + Some(w) + } else if x == Ordering::Equal && y == x && w == x { + Some(h) + } else { + None + } + } +} + +impl Clampable for Rectangle { + fn clamp(self, range: &RangeInclusive) -> Self { + Rectangle { + x: Ord::clamp(self.x, range.start().x, range.end().x), + y: Ord::clamp(self.y, range.start().y, range.end().y), + width: Ord::clamp(self.width, range.start().width, range.end().height), + height: Ord::clamp(self.height, range.start().width, range.end().height), + } + } +} + +/// Represents a control value size. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Size { + /// The width. + width: u32, + /// The height. + height: u32, +} + +impl From for Size { + fn from(v: ffi::ControlSize) -> Size { + Size { + width: v.width, + height: v.height, + } + } +} + +impl From for ffi::ControlSize { + fn from(v: Size) -> ffi::ControlSize { + ffi::ControlSize { + width: v.width, + height: v.height, + } + } +} + +impl Clampable for Size { + fn clamp(self, range: &RangeInclusive) -> Self { + Size { + width: Ord::clamp(self.width, range.start().width, range.end().height), + height: Ord::clamp(self.height, range.start().width, range.end().height), + } + } +} + +impl PartialOrd for Size { + fn partial_cmp(&self, other: &Size) -> Option { + let w = self.width.cmp(&other.width); + let h = self.height.cmp(&other.height); + if w == h || h == Ordering::Equal { + Some(w) + } else if w == Ordering::Equal { + Some(h) + } else { + None + } + } +} + +impl MinMaxValue { + /// Creates a new MinMaxValue out of a given min, max, and default + /// + /// # Returns + /// Returns None if default is not within min and max. + pub fn new(min: T, max: T, default: T) -> Result> { + if min >= max { + return Ok(MinMaxValue { + range: None, + value: default.clone(), + default, + }); + } + let range = min..=max; + if range.contains(&default) { + Ok(MinMaxValue { + range: Some(range), + value: default.clone(), + default, + }) + } else { + Err(LibcameraError::InvalidControlValue(Box::new(default))) + } + } + /// Retrieve the default value + pub fn get_default(&self) -> &T { + &self.default + } + /// Retrieve the minimum value + pub fn min(&self) -> Option<&T> { + self.range.as_ref().map(|r| r.start()) + } + /// Retrieve the maximum value + pub fn max(&self) -> Option<&T> { + self.range.as_ref().map(|r| r.end()) + } + /// Gets the stored value + /// + /// It is gurenteed to lie within MinMaxValue::min() and MinMaxValue::max(). + pub fn get_value(&self) -> &T { + &self.value + } + /// Gets the stored value if it is not equal to the default stored value. + pub fn get_value_if_changed(&self) -> Option<&T> { + if self.value != self.default { + Some(&self.value) + } else { + None + } + } + /// Verifies that value lies within the acceptable range for this value, then sets this value. + /// + /// # Returns + /// `true` if the value lies within the acceptable range for this value and was stored, `false` otherwise. + pub fn set_value(&mut self, value: T) -> bool { + if let Some(range) = &self.range { + if range.contains(&value) { + self.value = value; + true + } else { + false + } + } else { + self.value = value; + true + } + } + /// Set this value to the given value. + pub fn set_value_clamped(&mut self, value: T) { + if let Some(range) = &self.range { + self.value = value.clamp(range) + } else { + self.value = value; + } + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_bool() }?, + unsafe { pair.max.get().get_bool() }?, + unsafe { pair.value.get().get_bool() }?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_u8() }?, + unsafe { pair.max.get().get_u8() }?, + unsafe { pair.value.get().get_u8() }?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_i32() }?, + unsafe { pair.max.get().get_i32() }?, + unsafe { pair.value.get().get_i32() }?, + ) + } +} + +impl< + T: 'static + + TryFrom + + ControlEnum + + Clampable + + Copy + + Debug + + PartialOrd + + Send + + Sync, + > TryFrom<&ffi::ControlPair> for MinMaxValue +{ + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_i32() }?.try_into()?, + unsafe { pair.max.get().get_i32() }?.try_into()?, + unsafe { pair.value.get().get_i32() }?.try_into()?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_i64() }?, + unsafe { pair.max.get().get_i64() }?, + unsafe { pair.value.get().get_i64() }?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_f32() }?, + unsafe { pair.max.get().get_f32() }?, + unsafe { pair.value.get().get_f32() }?, + ) + } +} + +impl TryFrom> for FloatPair { + type Error = LibcameraError; + fn try_from(arr: Vec) -> Result { + if let &[a1, a2] = &arr[..] { + Ok(FloatPair(a1, a2)) + } else { + Err(LibcameraError::ControlValueError) + } + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_f32_array() } + .map_err(LibcameraError::InnerError) + .and_then(|v| v.try_into()) + .or_else(|_| unsafe { pair.min.get().get_f32() }.map(|v| FloatPair(v, v)))?, + unsafe { pair.max.get().get_f32_array() } + .map_err(LibcameraError::InnerError) + .and_then(|v| v.try_into()) + .or_else(|_| unsafe { pair.min.get().get_f32() }.map(|v| FloatPair(v, v)))?, + unsafe { pair.value.get().get_f32_array() } + .map_err(LibcameraError::InnerError) + .and_then(|v| v.try_into()) + .or_else(|_| unsafe { pair.min.get().get_f32() }.map(|v| FloatPair(v, v)))?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_string() }?, + unsafe { pair.max.get().get_string() }?, + unsafe { pair.value.get().get_string() }?, + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_rectangle() }?.into(), + unsafe { pair.max.get().get_rectangle() }?.into(), + unsafe { pair.value.get().get_rectangle() }?.into(), + ) + } +} + +impl TryFrom<&ffi::ControlPair> for MinMaxValue { + type Error = LibcameraError; + fn try_from(pair: &ffi::ControlPair) -> Result> { + MinMaxValue::new( + unsafe { pair.min.get().get_size() }?.into(), + unsafe { pair.max.get().get_size() }?.into(), + unsafe { pair.value.get().get_size() }?.into(), + ) + } +} + +/// Represents a camera control value with an unknown type +/// +/// Most of the time you probably want to use `CameraControls` instead. +#[non_exhaustive] +#[derive(Debug)] +pub enum CameraControlValue { + /// A control value not containing a value. + None, + /// A control value containing a boolean, e.g. autoexpose enable. + Bool(MinMaxValue), + /// A control value containing a single byte value. + Byte(MinMaxValue), + /// A control value containing a 32-bit integer, e.g. exposure time. + Integer32(MinMaxValue), + /// A control value containing a 64-bit integer, e.g. frame duration limit. + Integer64(MinMaxValue), + /// A control value containing a 32-bit float, e.g. brightness. + Float(MinMaxValue), + /// A control value containing an array of 32-bit floats. + FloatArray(Vec>), + /// A control value containing a String. + String(MinMaxValue), + /// A control value containing a Rectangle + Rectangle(MinMaxValue), + /// A control value containing a Size. + Size(MinMaxValue), +} + +/// Camera auto exposure metering mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AeMeteringMode { + /// Hah brittish spelling + CentreWeighted, + /// Spot + Spot, + /// Oooh fancy sounding + Matrix, + /// ??? + Custom, +} + +/// Camera auto exposure constraint mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AeConstraintMode { + /// Normal + Normal, + /// Highlights??? + Highlight, + /// ??? + Custom, +} + +/// Camera auto exposure mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AeExposureMode { + /// Normal + Normal, + /// Shorter than normal + Short, + /// Longer than normal + Long, +} + +/// Camera auto white balance mode +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum AwbMode { + /// Auto + Auto, + /// Incandescent + Incandescent, + /// Tungsten + Tungsten, + /// Fluorescent + Fluorescent, + /// Indoor + Indoor, + /// Daylight + Daylight, + /// Cloudy + Cloudy, + /// Custom + Custom, +} + +trait ControlEnum {} + +impl ControlEnum for AeMeteringMode {} +impl ControlEnum for AeConstraintMode {} +impl ControlEnum for AeExposureMode {} +impl ControlEnum for AwbMode {} + +impl Clampable for AeMeteringMode {} +impl Clampable for AeConstraintMode {} +impl Clampable for AeExposureMode {} +impl Clampable for AwbMode {} + +impl TryFrom for AeMeteringMode { + type Error = LibcameraError; + fn try_from(i: i32) -> Result { + match i { + 0 => Ok(Self::CentreWeighted), + 1 => Ok(Self::Spot), + 2 => Ok(Self::Matrix), + 3 => Ok(Self::Custom), + i => Err(LibcameraError::InvalidControlValue(Box::new(i))), + } + } +} +impl TryFrom for AeConstraintMode { + type Error = LibcameraError; + fn try_from(i: i32) -> Result { + match i { + 0 => Ok(Self::Normal), + 1 => Ok(Self::Highlight), + 2 => Ok(Self::Custom), + i => Err(LibcameraError::InvalidControlValue(Box::new(i))), + } + } +} +impl TryFrom for AeExposureMode { + type Error = LibcameraError; + fn try_from(i: i32) -> Result { + match i { + 0 => Ok(Self::Normal), + 1 => Ok(Self::Short), + 2 => Ok(Self::Long), + i => Err(LibcameraError::InvalidControlValue(Box::new(i))), + } + } +} +impl TryFrom for AwbMode { + type Error = LibcameraError; + fn try_from(i: i32) -> Result { + match i { + 0 => Ok(Self::Auto), + 1 => Ok(Self::Incandescent), + 2 => Ok(Self::Tungsten), + 3 => Ok(Self::Fluorescent), + 4 => Ok(Self::Indoor), + 5 => Ok(Self::Daylight), + 6 => Ok(Self::Cloudy), + 7 => Ok(Self::Custom), + i => Err(LibcameraError::InvalidControlValue(Box::new(i))), + } + } +} + +/// Stores camera controls. +/// +/// Common controls are fields on this struct +#[non_exhaustive] +#[derive(Debug, Default)] +pub struct CameraControls { + /// Autoexposure enable. + pub ae_enable: Option>, + /// Autoexposure metering mode. + pub ae_metering_mode: Option>, + /// Autoexposure constraint mode. + pub ae_constraint_mode: Option>, + /// Autoexposure mode. + pub ae_exposure_mode: Option>, + /// Exposure "value". + pub exposure_value: Option>, + /// Exposure time. + pub exposure_time: Option>, + /// Analogue signal gain. + pub analogue_gain: Option>, + /// Brightness + pub brightness: Option>, + /// Contrast + pub contrast: Option>, + /// Auto white balance enable. + pub awb_enable: Option>, + /// Auto white balance mode. + pub awb_mode: Option>, + /// Red/Blue colour gains. + pub colour_gains: Option>, + /// Saturation. + pub saturation: Option>, + /// Sharpness. + pub sharpness: Option>, + /// Colour correction matrix. + /// **TODO**: Make this actually a 3x3 matrix + pub colour_correction_matrix: Option>, + /// Scaler crop + pub scaler_crop: Option>, // Rectangle TODO + /// Frame duration limit. + pub frame_duration_limits: Option>, + /// Noise reduction mode. + /// **TODO**: This should be an enum. + pub noise_reduction_mode: Option>, + /// Values not directly handled by this struct but found on your camera, maps control IDs to a tuple containing a name for the control as well as the value. + pub others: HashMap, +} + +impl CameraControls { + pub(crate) fn from_libcamera(control_list: Vec) -> Self { + let mut controls = CameraControls::default(); + for control in control_list { + let name = unsafe { control.id.get().get_name() }; + let did_name_match = match name.as_ref() { + "AeEnable" => (&control) + .try_into() + .map(|control| controls.ae_enable = Some(control)) + .is_ok(), + "AeMeteringMode" => (&control) + .try_into() + .map(|control| controls.ae_metering_mode = Some(control)) + .is_ok(), + "AeConstraintMode" => (&control) + .try_into() + .map(|control| controls.ae_constraint_mode = Some(control)) + .is_ok(), + "AeExposureMode" => (&control) + .try_into() + .map(|control| controls.ae_exposure_mode = Some(control)) + .is_ok(), + "ExposureValue" => (&control) + .try_into() + .map(|control| controls.exposure_value = Some(control)) + .is_ok(), + "ExposureTime" => (&control) + .try_into() + .map(|control| controls.exposure_time = Some(control)) + .is_ok(), + "AnalogueGain" => (&control) + .try_into() + .map(|control| controls.analogue_gain = Some(control)) + .is_ok(), + "Brightness" => (&control) + .try_into() + .map(|control| controls.brightness = Some(control)) + .is_ok(), + "Contrast" => (&control) + .try_into() + .map(|control| controls.contrast = Some(control)) + .is_ok(), + "AwbEnable" => (&control) + .try_into() + .map(|control| controls.awb_enable = Some(control)) + .is_ok(), + "AwbMode" => (&control) + .try_into() + .map(|control| controls.awb_mode = Some(control)) + .is_ok(), + "ColourGains" => (&control) + .try_into() + .map(|control| controls.colour_gains = Some(control)) + .is_ok(), + "Saturation" => (&control) + .try_into() + .map(|control| controls.saturation = Some(control)) + .is_ok(), + "Sharpness" => (&control) + .try_into() + .map(|control| controls.sharpness = Some(control)) + .is_ok(), + "ColourCorrectionMatrix" => (&control) + .try_into() + .map(|control| controls.colour_correction_matrix = Some(control)) + .is_ok(), + // "ScalerCrop" => (&control).try_into().map(|control| controls.scaler_crop = Some(control)).is_ok(), + "FrameDurationLimits" => (&control) + .try_into() + .map(|control| controls.frame_duration_limits = Some(control)) + .is_ok(), + "NoiseReductionMode" => (&control) + .try_into() + .map(|control| controls.noise_reduction_mode = Some(control)) + .is_ok(), + _ => false, + }; + if !did_name_match { + let control_type = unsafe { control.id.get().get_type() }; + let control_array = unsafe { control.value.get().is_array() }; + let control_value = match (control_type, control_array) { + (ffi::CameraControlType::None, false) => Some(Ok(CameraControlValue::None)), + (ffi::CameraControlType::Bool, false) => { + Some((&control).try_into().map(CameraControlValue::Bool)) + } + (ffi::CameraControlType::Byte, false) => { + Some((&control).try_into().map(CameraControlValue::Byte)) + } + (ffi::CameraControlType::Integer32, false) => { + Some((&control).try_into().map(CameraControlValue::Integer32)) + } + (ffi::CameraControlType::Integer64, false) => { + Some((&control).try_into().map(CameraControlValue::Integer64)) + } + (ffi::CameraControlType::Float, false) => { + Some((&control).try_into().map(CameraControlValue::Float)) + } + _ => None, + }; + match control_value { + Some(Ok(control_value)) => { + controls + .others + .insert(unsafe { control.id.get().get_id() }, (name, control_value)); + } + Some(Err(e)) => error!("Camera control with conflicting types: {name} is supposed to have type of {control_type:?}, err: {e}"), + None => warn!("Unknown type for camera control {name}."), + }; + } + } + controls + } + pub(crate) fn get_libcamera(&self) -> Vec<(u32, ffi::BindControlValue)> { + let mut controls = Vec::new(); + if let Some(ae_enable) = &self.ae_enable { + if let Some(&value) = ae_enable.get_value_if_changed() { + controls.push((1, unsafe { ffi::new_control_value_bool(value) })); + } + } + if let Some(ae_metering_mode) = &self.ae_metering_mode { + if let Some(&value) = ae_metering_mode.get_value_if_changed() { + controls.push((3, unsafe { ffi::new_control_value_i32(value as i32) })); + } + } + if let Some(ae_constraint_mode) = &self.ae_constraint_mode { + if let Some(&value) = ae_constraint_mode.get_value_if_changed() { + controls.push((4, unsafe { ffi::new_control_value_i32(value as i32) })); + } + } + if let Some(ae_exposure_mode) = &self.ae_exposure_mode { + if let Some(&value) = ae_exposure_mode.get_value_if_changed() { + controls.push((5, unsafe { ffi::new_control_value_i32(value as i32) })); + } + } + if let Some(exposure_value) = &self.exposure_value { + if let Some(&value) = exposure_value.get_value_if_changed() { + controls.push((6, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(exposure_time) = &self.exposure_time { + if let Some(&value) = exposure_time.get_value_if_changed() { + controls.push((7, unsafe { ffi::new_control_value_i32(value) })); + } + } + if let Some(analogue_gain) = &self.analogue_gain { + if let Some(&value) = analogue_gain.get_value_if_changed() { + controls.push((8, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(brightness) = &self.brightness { + if let Some(&value) = brightness.get_value_if_changed() { + controls.push((9, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(contrast) = &self.contrast { + if let Some(&value) = contrast.get_value_if_changed() { + controls.push((10, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(awb_enable) = &self.awb_enable { + if let Some(&value) = awb_enable.get_value_if_changed() { + controls.push((12, unsafe { ffi::new_control_value_bool(value) })); + } + } + if let Some(awb_mode) = &self.awb_mode { + if let Some(&value) = awb_mode.get_value_if_changed() { + controls.push((13, unsafe { ffi::new_control_value_i32(value as i32) })); + } + } + if let Some(colour_gains) = &self.colour_gains { + if let Some(&value) = colour_gains.get_value_if_changed() { + controls.push((15, unsafe { + ffi::new_control_value_f32_array(&[value.0, value.1]) + })); + } + } + if let Some(saturation) = &self.saturation { + if let Some(&value) = saturation.get_value_if_changed() { + controls.push((17, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(sharpness) = &self.sharpness { + if let Some(&value) = sharpness.get_value_if_changed() { + controls.push((19, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(colour_correction_matrix) = &self.colour_correction_matrix { + if let Some(&value) = colour_correction_matrix.get_value_if_changed() { + controls.push((21, unsafe { ffi::new_control_value_f32(value) })); + } + } + if let Some(frame_duration_limits) = &self.frame_duration_limits { + if let Some(&value) = frame_duration_limits.get_value_if_changed() { + controls.push((25, unsafe { ffi::new_control_value_i64(value) })); + } + } + if let Some(noise_reduction_mode) = &self.noise_reduction_mode { + if let Some(&value) = noise_reduction_mode.get_value_if_changed() { + controls.push((39, unsafe { ffi::new_control_value_i32(value) })); + } + } + for (id, (_name, value)) in &self.others { + if let Some(value) = match value { + CameraControlValue::None => None, + CameraControlValue::Bool(value) => { + Some(unsafe { ffi::new_control_value_bool(*value.get_value()) }) + } + CameraControlValue::Byte(value) => { + Some(unsafe { ffi::new_control_value_u8(*value.get_value()) }) + } + CameraControlValue::Integer32(value) => { + Some(unsafe { ffi::new_control_value_i32(*value.get_value()) }) + } + CameraControlValue::Integer64(value) => { + Some(unsafe { ffi::new_control_value_i64(*value.get_value()) }) + } + CameraControlValue::Float(value) => { + Some(unsafe { ffi::new_control_value_f32(*value.get_value()) }) + } + CameraControlValue::FloatArray(value) => Some(unsafe { + ffi::new_control_value_f32_array( + value + .iter() + .map(|v| *v.get_value()) + .collect::>() + .as_slice(), + ) + }), + CameraControlValue::String(value) => { + Some(unsafe { ffi::new_control_value_string(value.get_value()) }) + } + CameraControlValue::Rectangle(value) => Some(unsafe { + ffi::new_control_value_rectangle(ffi::ControlRectangle::from(*value.get_value())) + }), + CameraControlValue::Size(value) => { + Some(unsafe { ffi::new_control_value_size(ffi::ControlSize::from(*value.get_value())) }) + } + } { + controls.push((*id, value)); + } + } + controls + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..11ee2e9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,35 @@ +use thiserror::Error; + +/// An error. +#[derive(Debug, Error)] +pub enum LibcameraError { + /// A C++ exception + #[error("Inner C++ error: {0}")] + InnerError(#[from] cxx::Exception), + /// An error converting an integer + #[error("Int conversion error: {0}")] + IntConversion(#[from] std::num::TryFromIntError), + /// An error when validating configuration + #[error("Configuration Invalid or Missing")] + InvalidConfig, + /// An error produced when it an image capture is requested but there are no buffers available. + #[error("No buffer ready for capture (all buffers in use, capture pictures slower!)")] + NoBufferReady, + /// An error emitted when a control value is attempted to be set to a value outside of the acceptable range. + #[error("Control value ({0:?}) out of range!")] + InvalidControlValue(Box), + /// An error reading a control value + #[error("Error converting control value!")] + ControlValueError, + /// An error en/decoding an image. + #[cfg(feature = "image")] + #[error("Image error: {0}")] + ImageError(#[from] ::image::ImageError), + /// An error produced when the decoded JPEG image is not in RGB888 format. + #[cfg(feature = "image")] + #[error("Image error: Bad image format.")] + BadImageFormat, +} + +/// Internal result type for convenience. +pub type Result = std::result::Result; diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000..a32cc8d --- /dev/null +++ b/src/image.rs @@ -0,0 +1,355 @@ +use log::trace; + +use crate::{LibcameraError, Result}; + +/// Represents an image. +pub trait CameraImage { + /// Create an image out of a size and the image data planes. + fn from_planes(width: usize, height: usize, planes: [Vec; PLANES]) -> Option + where + Self: Sized; + /// Convert this image into BGR format. + fn as_bgr(&self) -> Option; + /// Get the size of this image. + fn get_size(&self) -> (usize, usize); + /// Get the raw pixel planes for this image. + fn get_planes(&self) -> [&[u8]; PLANES]; +} + +/// Contains an image in any format. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum MultiImage { + /// Image in the BGR format. + Bgr(BgrImage), + /// Image in the RGB format. + Rgb(RgbImage), + /// Image in the YUYV 4:2:2 format. + Yuyv(YuyvImage), + /// Image in the YUV 4:2:0 format. + Yuv420(Yuv420Image), + /// Image in the NV12 (YUV 4:2:0, interleaved U/V) format. + Nv12(Nv12Image), +} + +impl MultiImage { + /// Convert this image into a [`BgrImage`]. + pub fn as_bgr(&self) -> Option { + match self { + MultiImage::Bgr(img) => img.as_bgr(), + MultiImage::Rgb(img) => img.as_bgr(), + MultiImage::Yuyv(img) => img.as_bgr(), + MultiImage::Yuv420(img) => img.as_bgr(), + MultiImage::Nv12(img) => img.as_bgr(), + } + } + /// Get the size of this image. + pub fn get_size(&self) -> (usize, usize) { + match self { + MultiImage::Bgr(img) => img.get_size(), + MultiImage::Rgb(img) => img.get_size(), + MultiImage::Yuyv(img) => img.get_size(), + MultiImage::Yuv420(img) => img.get_size(), + MultiImage::Nv12(img) => img.get_size(), + } + } +} + +/// Contains an image in BGR Format. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BgrImage { + width: usize, + height: usize, + data: Vec, +} + +impl CameraImage<1> for BgrImage { + fn from_planes(width: usize, height: usize, planes: [Vec; 1]) -> Option { + let [data] = planes; + if width * height * 3 == data.len() { + Some(BgrImage { + width, + height, + data, + }) + } else { + None + } + } + fn as_bgr(&self) -> Option { + Some(self.clone()) + } + fn get_size(&self) -> (usize, usize) { + (self.width, self.height) + } + fn get_planes(&self) -> [&[u8]; 1] { + [&self.data] + } +} + +impl BgrImage { + /// Convert this image into a BGR image. + pub fn as_rgb(&self) -> RgbImage { + RgbImage::from_planes( + self.width, + self.height, + [self + .data + .chunks_exact(3) + .flat_map(|chunk| { + if let &[b, g, r] = chunk { + [r, g, b] + } else { + panic!("Exact chunks not exact!"); + } + }) + .collect()], + ) + .expect("Failed to convert RGB to BGR (this should never fail.)") + } +} + +/// Contains an image in RGB Format. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RgbImage { + width: usize, + height: usize, + data: Vec, +} + +impl CameraImage<1> for RgbImage { + fn from_planes(width: usize, height: usize, planes: [Vec; 1]) -> Option { + let [data] = planes; + if width * height * 3 == data.len() { + Some(RgbImage { + width, + height, + data, + }) + } else { + None + } + } + /// Convert this image into BGR format. This should never return None. + fn as_bgr(&self) -> Option { + BgrImage::from_planes( + self.width, + self.height, + [self + .data + .chunks_exact(3) + .flat_map(|chunk| { + if let &[r, g, b] = chunk { + [b, g, r] + } else { + panic!("Exact chunks not exact!"); + } + }) + .collect()], + ) + } + fn get_size(&self) -> (usize, usize) { + (self.width, self.height) + } + fn get_planes(&self) -> [&[u8]; 1] { + [&self.data] + } +} + +/// These functions are only available with the `image` feature/crate. +#[cfg(feature = "image")] +impl RgbImage { + /// Decode an [`RgbImage`] from a JPEG image stream in a Vec. + /// + /// Available only with the `image` feature/crate. + pub fn decode_jpeg(data: &[u8]) -> Result { + let image = image::load_from_memory_with_format(data, image::ImageFormat::Jpeg)?; + trace!("Image loaded"); + if let image::DynamicImage::ImageRgb8(img) = image { + let (width, height) = img.dimensions(); + trace!("JPEG image size {width}x{height}."); + Ok( + RgbImage::from_planes(width as usize, height as usize, [img.into_raw()]) + .ok_or(LibcameraError::BadImageFormat)?, + ) + } else { + Err(LibcameraError::BadImageFormat) + } + } + /// Encode an [`RgbImage`] to a PNG image stream in a Vec. + /// + /// Available only with the `image` feature/crate. + pub fn encode_png(&self) -> Result> { + let mut buffer = std::io::Cursor::new(Vec::new()); + image::write_buffer_with_format( + &mut buffer, + &self.data, + self.width as u32, + self.height as u32, + image::ColorType::Rgb8, + image::ImageOutputFormat::Png, + )?; + Ok(buffer.into_inner()) + } +} + +fn yuv2rgb(y: u8, u: u8, v: u8) -> (u8, u8, u8) { + ( + (y as f32 + (1.370705 * (v as f32 - 128.0))).clamp(0.0, 255.0) as u8, + (y as f32 - (0.698001 * (v as f32 - 128.0)) - (0.337633 * (u as f32 - 128.0))).clamp(0.0, 255.0) + as u8, + (y as f32 + (1.732446 * (u as f32 - 128.0))).clamp(0.0, 255.0) as u8, + ) +} + +/// Contains an image in YUVU 4:2:2 Format. +#[derive(Debug, Clone)] +pub struct YuyvImage { + width: usize, + height: usize, + data: Vec, +} + +impl CameraImage<1> for YuyvImage { + fn from_planes(width: usize, height: usize, planes: [Vec; 1]) -> Option { + let [data] = planes; + if width * height * 2 == data.len() { + Some(YuyvImage { + width, + height, + data, + }) + } else { + None + } + } + fn as_bgr(&self) -> Option { + BgrImage::from_planes( + self.width, + self.height, + [self + .data + .chunks_exact(4) + .flat_map(|chunk| { + if let &[y1, u, y2, v] = chunk { + // Map each section of y*u*y*v* to two BGR pixels + let (r1, g1, b1) = yuv2rgb(y1, u, v); + let (r2, g2, b2) = yuv2rgb(y2, u, v); + [b1, g1, r1, b2, g2, r2] + } else { + panic!("Exact chunks not exact!"); + } + }) + .collect()], + ) + } + fn get_size(&self) -> (usize, usize) { + (self.width, self.height) + } + fn get_planes(&self) -> [&[u8]; 1] { + [&self.data] + } +} + +/// Contains an image in YUV 4:2:0 Format. +#[derive(Debug, Clone)] +pub struct Yuv420Image { + width: usize, + height: usize, + y_plane: Vec, + u_plane: Vec, + v_plane: Vec, +} + +impl CameraImage<3> for Yuv420Image { + fn from_planes(width: usize, height: usize, planes: [Vec; 3]) -> Option { + let [y_plane, u_plane, v_plane] = planes; + if width * height == y_plane.len() + && width / 2 * height / 2 == u_plane.len() + && width / 2 * height / 2 == v_plane.len() + { + Some(Yuv420Image { + width, + height, + y_plane, + u_plane, + v_plane, + }) + } else { + None + } + } + fn as_bgr(&self) -> Option { + let mut new_plane = Vec::new(); + new_plane.reserve_exact(self.width * self.height * 3); + for y in 0..self.height { + for x in 0..self.width { + let (y, u, v) = ( + self.y_plane[y * self.width + x], + self.u_plane[y / 2 * self.width / 2 + x / 2], + self.v_plane[y / 2 * self.width / 2 + x / 2], + ); + let (r, g, b) = yuv2rgb(y, u, v); + new_plane.push(b); + new_plane.push(g); + new_plane.push(r); + } + } + BgrImage::from_planes(self.width, self.height, [new_plane]) + } + fn get_size(&self) -> (usize, usize) { + (self.width, self.height) + } + fn get_planes(&self) -> [&[u8]; 3] { + [&self.y_plane, &self.u_plane, &self.v_plane] + } +} + +/// Contains an image in NV12 (YUV 4:2:0, interleaved U/V) Format. +#[derive(Debug, Clone)] +pub struct Nv12Image { + width: usize, + height: usize, + y_plane: Vec, + uv_plane: Vec, +} + +impl CameraImage<2> for Nv12Image { + fn from_planes(width: usize, height: usize, planes: [Vec; 2]) -> Option { + let [y_plane, uv_plane] = planes; + if width * height == y_plane.len() && width / 2 * height / 2 * 2 == uv_plane.len() { + Some(Nv12Image { + width, + height, + y_plane, + uv_plane, + }) + } else { + None + } + } + fn as_bgr(&self) -> Option { + let mut new_plane = Vec::new(); + new_plane.reserve_exact(self.width * self.height * 3); + for y in 0..self.height { + for x in 0..self.width { + let (y, u, v) = ( + self.y_plane[y * self.width + x], + self.uv_plane[(y / 2 * self.width / 2 + x / 2) * 2], + self.uv_plane[(y / 2 * self.width / 2 + x / 2) * 2 + 1], + ); + let (r, g, b) = yuv2rgb(y, u, v); + new_plane.push(b); + new_plane.push(g); + new_plane.push(r); + } + } + BgrImage::from_planes(self.width, self.height, [new_plane]) + } + fn get_size(&self) -> (usize, usize) { + (self.width, self.height) + } + fn get_planes(&self) -> [&[u8]; 2] { + [&self.y_plane, &self.uv_plane] + } +} diff --git a/src/lib.rs b/src/lib.rs index de65f68..df23f16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,20 @@ -mod bridge; - -pub use bridge::ffi; +#![warn(missing_docs)] +#![doc = include_str!("../readme.md")] +mod bridge; +/// Handles interfacing with cameras, get started using a CameraManager. +pub mod camera; +/// Camera and stream configuration, e.g. how many streams, resolutions, and pixel formats. +pub mod config; +/// Camera (runtime) controls, e.g. brightness, contrast. +pub mod controls; +/// Errors +pub mod error; +/// Decoding images from the camera +pub mod image; +/// All the things you *should* need to get started. +pub mod prelude; #[cfg(test)] -mod tests { - use crate::ffi; - use crate::bridge::MutFromSharedPtr; - - #[test] - fn it_works() { - let mut manager = ffi::make_camera_manager(); - manager.pin_mut().version(); - } - - #[test] - fn generate_configuration() { - let mut manager = ffi::make_camera_manager(); - manager.pin_mut().start().unwrap(); - - let mut cameras = manager.pin_mut().cameras(); - let camera = &mut cameras[0]; - - let roles = vec![ffi::StreamRole::StillCapture]; - - let mut config = camera.inner.pin_mut().generate_configuration(&roles); +mod test; - assert_eq!(config.pin_mut().validate(), ffi::CameraConfigurationStatus::Valid); - } -} +pub use error::{LibcameraError, Result}; diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..4744372 --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,4 @@ +pub use crate::camera::{CameraEvent, CameraManager, ConfigStatus, StreamRole}; +pub use crate::config::PixelFormat; +pub use crate::error::LibcameraError; +pub use crate::image::CameraImage; diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..2bf3d06 --- /dev/null +++ b/src/test.rs @@ -0,0 +1,265 @@ +use crate::prelude::{ + CameraEvent, CameraImage, CameraManager, ConfigStatus, LibcameraError, PixelFormat, StreamRole, +}; + +#[cfg(feature = "image")] +#[test] +fn test_camera() { + let cm = CameraManager::new().unwrap(); + println!("cm: {cm:?}"); + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + println!("cam: {cam:?}"); + let conf = cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + println!("conf: {conf:?}"); + let stream = &mut conf.streams_mut()[0]; + stream.set_pixel_format(PixelFormat::Yuv420); + stream.set_size(640, 480); + println!("Configuration applied: {:?}", cam.apply_config().unwrap()); + cam.start_stream().unwrap(); + println!("Started stream."); + println!("Capturing frames..."); + for i in 0..6 { + let brightness = cam.get_controls_mut().brightness.as_mut().unwrap(); + brightness.set_value((brightness.get_value() + 0.6).rem_euclid(1.5) - 0.5); + let contrast = cam.get_controls_mut().contrast.as_mut().unwrap(); + contrast.set_value(contrast.get_value() + 0.02); + cam.capture_next_picture(0).unwrap(); + println!("Capturing image #{}", i); + std::thread::sleep(std::time::Duration::from_millis(200)); + let events = cam.poll_events(None).unwrap(); + for event in events { + match event { + CameraEvent::RequestComplete { + serial_id, image, .. + } => { + let decoded_image = image.read_image(&cam).try_decode().unwrap(); + let rgb_image = decoded_image + .as_bgr() + .unwrap() + .as_rgb() + .encode_png() + .unwrap(); + let filename = format!("image_{serial_id}.png"); + std::fs::write(&filename, rgb_image).unwrap(); + println!("Got responce back for request {serial_id} and saved to {filename}."); + } + } + } + } + println!("Done!"); +} + +#[test] +#[should_panic] +fn panic_with_camera() { + // Wait to ensure we can lock the camera (when running many tests back-to-back). + std::thread::sleep(std::time::Duration::from_secs(5)); + let cm = CameraManager::new().unwrap(); + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + cam.apply_config().unwrap(); + cam.start_stream().unwrap(); + panic!("Success"); +} + +#[test] +fn try_start_before_configure() { + let cm = CameraManager::new().unwrap(); + println!("camers: {:?}", cm.get_camera_names()); + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + assert!(matches!( + cam.start_stream(), + Err(LibcameraError::InvalidConfig) + )); + cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); +} + +#[test] +fn capture_then_drop() { + // Wait to ensure we can lock the camera (when running many tests back-to-back). + std::thread::sleep(std::time::Duration::from_secs(5)); + let cm = CameraManager::new().unwrap(); + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + cam.apply_config().unwrap(); + cam.start_stream().unwrap(); + cam.capture_next_picture(0).unwrap(); + cam.capture_next_picture(0).unwrap(); +} + +#[test] +#[should_panic] +fn capture_then_panic() { + // Wait to ensure we can lock the camera (when running many tests back-to-back). + std::thread::sleep(std::time::Duration::from_secs(5)); + let cm = CameraManager::new().unwrap(); + let mut cam = cm.get_camera_by_name(&cm.get_camera_names()[0]).unwrap(); + cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + cam.apply_config().unwrap(); + cam.start_stream().unwrap(); + cam.capture_next_picture(0).unwrap(); + cam.capture_next_picture(0).unwrap(); + panic!("Success"); +} + +lazy_static::lazy_static! { + static ref CAMERA_MANAGER: CameraManager = { + let cm = CameraManager::new().unwrap(); + log::debug!("Camera Manager: {cm:?}"); + cm + }; +} + +/// This test is designed to behave as similar as possible to ps-native. +#[test] +fn background_capture_thread() { + use simplelog::SimpleLogger; + use std::sync::mpsc::{self, channel}; + use std::sync::{Arc, RwLock}; + use std::thread; + use std::time::Duration; + + use crate::controls; + + SimpleLogger::init(simplelog::LevelFilter::Trace, simplelog::Config::default()).unwrap(); + + let mut cam = { + let selected_cam = CAMERA_MANAGER.get_camera_names()[0].clone(); + log::debug!("Selected camera: {selected_cam:?}"); + CAMERA_MANAGER.get_camera_by_name(&selected_cam).unwrap() + }; + log::debug!("Created camera."); + let camera_config = cam.generate_config(&[StreamRole::Viewfinder]).unwrap(); + let stream_config = &mut camera_config.streams_mut()[0]; + log::debug!("Updating stream config..."); + stream_config.set_pixel_format(PixelFormat::Yuv420); + stream_config.set_size(1280, 960); + log::debug!("Applying config..."); + let status = cam.apply_config().unwrap(); + if status == ConfigStatus::Changed { + log::debug!("Configuration updated by camera."); + } + log::debug!("Starting initial stream: {}", cam.start_stream().unwrap(),); + + log::info!("Camera ready!"); + let cam = Arc::new(RwLock::new(cam)); + let (req_tx, req_rx) = channel(); + let (frame_tx, frame_rx) = channel(); + let thread_join_handle = { + let cam = cam.clone(); + thread::spawn(move || { + for _i in 0..4 { + cam.write().unwrap().capture_next_picture(0).unwrap(); + } + let mut capture_next = false; + let mut capture_id = None; + loop { + log::trace!("[CAM] Camera Loop"); + match req_rx.try_recv() { + Ok(()) => capture_next = true, + Err(mpsc::TryRecvError::Disconnected) => { + log::debug!("Exiting Thread."); + break; + } + Err(mpsc::TryRecvError::Empty) => {} + }; + log::trace!("[CAM] Polling camera..."); + let mut cam = cam.write().unwrap(); + let events = cam.poll_events(None).unwrap(); + log::trace!("[CAM] Processing events..."); + for event in events { + match event { + CameraEvent::RequestComplete { + serial_id, image, .. + } => { + if capture_id == Some(serial_id) { + log::debug!("[CAM] Captured frame: {serial_id}"); + capture_id = None; + frame_tx.send(image.read_image(&cam)).unwrap(); + log::debug!("[CAM] Sent."); + } + log::trace!("[CAM] Captured image, queuing another request."); + let next = cam.capture_next_picture(0).unwrap(); + log::trace!("[CAM] Queued."); + if capture_id.is_none() && capture_next { + capture_id = Some(next); + capture_next = false; + log::debug!("[CAM] Capture next frame: {next}"); + } + } + } + } + log::trace!("[CAM] Done processing events, sleeping..."); + thread::sleep(Duration::from_millis(50)); + } + }) + }; + log::debug!("Setting default controls."); + { + let mut cam = cam.write().unwrap(); + let controls = cam.get_controls_mut(); + if let Some(brightness) = controls.brightness.as_mut() { + brightness.set_value_clamped(0.0); + } + if let Some(contrast) = controls.contrast.as_mut() { + contrast.set_value_clamped(1.1); + } + if let Some(awb_enable) = controls.awb_enable.as_mut() { + awb_enable.set_value_clamped(false); + } + if let Some(ae_enable) = controls.ae_enable.as_mut() { + ae_enable.set_value_clamped(false); + } + if let Some(colour_gains) = controls.colour_gains.as_mut() { + colour_gains.set_value_clamped(controls::FloatPair(1.63, 1.63)); + } + if let Some(exposure_time) = controls.exposure_time.as_mut() { + exposure_time.set_value_clamped(243); + } + } + thread::sleep(Duration::from_secs(1)); + log::info!("Starting image capture..."); + for i in 0..50 { + if (i % 4 > 1 || i > 40) && (i % 3 < 2 || i > 45) { + log::debug!("Setting controls..."); + let mut cam = cam.write().unwrap(); + let controls = cam.get_controls_mut(); + if let Some(colour_gains) = controls.colour_gains.as_mut() { + colour_gains.set_value_clamped(if i % 2 == 0 { + controls::FloatPair(1.63, 1.63) + } else { + controls::FloatPair(1.0, 1.0) + }); + } + if let Some(exposure_time) = controls.exposure_time.as_mut() { + exposure_time.set_value_clamped(243 + 10 - i * 2); + } + log::debug!("Controls set."); + } + thread::sleep(Duration::from_millis(if i == 35 { + 2000 + } else if i < 10 { + 200 + } else { + 0 + })); + let start = std::time::Instant::now(); + log::debug!("[IMG] Requesting image..."); + req_tx.send(()).unwrap(); + log::debug!("[IMG] Waiting to get image..."); + let image = frame_rx.recv().unwrap(); + log::debug!("[IMG] Took picture in {:?}", start.elapsed()); + let image = image.try_decode().unwrap(); + log::debug!("[IMG] Decoded image in {:?}", start.elapsed()); + let image = image.as_bgr().unwrap(); + log::debug!("[IMG] Converted colour space in {:?}.", start.elapsed()); + let (width, height) = image.get_size(); + log::debug!("[IMG] Image size: {width}x{height}"); + std::fs::write("tmp.bgr", image.get_planes()[0]).unwrap(); + } + log::debug!("[IMG] Dropping request tx."); + drop(req_tx); + log::debug!("[IMG] Joining thread."); + thread_join_handle.join().unwrap(); + log::info!("[IMG] Exiting"); +}