├── LICENSE ├── LVVC.cbp ├── README.md ├── example ├── takephoto.cpp └── takevideo.cpp ├── include ├── lccv.hpp ├── libcamera_app.hpp └── libcamera_app_options.hpp └── src ├── lccv.cpp ├── libcamera_app.cpp └── libcamera_app_options.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Q-engineering 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /LVVC.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 62 | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libcamera API wrapper for OpenCV RPi Bullseye 64OS 2 | ![output image]( https://qengineering.eu/images/CameraWall.webp )
3 | ## Libcamera C++ API wrapper for OpenCV on a Raspberry Pi 4 with 64-bit Bullseye OS 4 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)

5 | In the new Debian 11, Bullseye, you have libcamera as the default camera stack. This C++ code is a example on how to conect the libcamera API in OpenCV. It requires less CPU-resources compared to GStreamer.
6 | 7 | | Size | FPS | Gstreamer | LCCV | 8 | | ---- | :-----: | :-----: | :-----: | 9 | | 640 X 480 | 15 | 24% | **10%** | 10 | | 640 X 480 | 30 | 25% | **17%** | 11 | | 1024 x 768 | 15 | 41% | **16%** | 12 | | 1280 x 960 | 15 | 42% | **18%** | 13 | | 2592 x 1944 | 15 | 50% | **48%** | 14 | | 3280 x 2464 | 15 | - | **51%** | 15 | 16 | A time consuming process can be a full size preview on the screen, especially at high resolutions. 17 | 18 | ------------ 19 | 20 | ## Dependencies.
21 | To run the application, you have to: 22 | - A Raspberry Pi 4. 23 | - Raspbian's libcamera-apps source code installed (```$ sudo apt install libcamera-dev```) 24 | - OpenCV 64 bit installed. [Install OpenCV 4.5](https://qengineering.eu/install-opencv-4.5-on-raspberry-64-os.html)
25 | - Code::Blocks installed. (```$ sudo apt-get install codeblocks```) 26 | - A working Raspicam 27 | 28 | ------------ 29 | 30 | ## Installing the app. 31 | To extract and run the app in Code::Blocks
32 | $ mkdir *MyDir*
33 | $ cd *MyDir*
34 | $ wget https://github.com/Qengineering/LCCV/archive/refs/heads/main.zip
35 | $ unzip -j master.zip
36 | Remove master.zip, LICENSE and README.md as they are no longer needed.
37 | $ rm main.zip
38 | $ rm LICENSE
39 | $ rm README.md

40 | Your *MyDir* folder must now look like this:
41 | ``` 42 | ├── example 43 | │   ├── takephoto.cpp 44 | │   └── takevideo.cpp 45 | ├── include 46 | │   ├── lccv.hpp 47 | │   ├── libcamera_app.hpp 48 | │   └── libcamera_app_options.hpp 49 | ├── LVVC.cbp 50 | └── src 51 | ├── lccv.cpp 52 | ├── libcamera_app.cpp 53 | └── libcamera_app_options.cpp 54 | ``` 55 | 56 | ------------ 57 | 58 | ## Running the app. 59 | To run the application load the project file LCCV.cbp in Code::Blocks.
60 | 61 | A remark. The main loop looks somewhat odd with its if-else statements, where a `break` is usually applied when a frame is missed.
62 | ``` 63 | int ch=0; 64 | while(ch!=27){ 65 | if(!cam.getVideoFrame(image,1000)){ 66 | std::cout<<"Timeout error"<
75 | ![output image]( https://qengineering.eu/images/LCCVtime.png )
76 | 77 | Many thanks to [kbarni](https://github.com/kbarni) for this hard work on the original repo!

78 | 79 | ------------ 80 | 81 | ## Extract of [_kbarni_](https://github.com/kbarni) original readme. 82 | 83 | LibCamera bindings for OpenCV 84 | 85 | LCCV (*LibCamera bindings for OpenCV*) is a small wrapper library that provides access to the Raspberry Pi camera in OpenCV. 86 | 87 | IMPORTANT WARNING: 88 | 89 | *This is a very early version, so expect to have several bugs.*
90 | *Note that the API still might have some changes!*
91 | *Please help with the development by reporting the bugs and issues you encounter, committing bugfixes, and proposing ideas!*
92 | 93 | ### Context 94 | 95 | ------- 96 | 97 | In Raspbian Bullseye, the Raspberry Pi camera framework was completely rebased from MMAL to the LibCamera library - thus breaking most of the previous camera dependencies. 98 | 99 | Raspbian comes with the handy `libcamera-apps` package that duplicates the old `raspistill` and `raspivid` applications, with some added functionnality, like the possibility of adding postprocessing routines to the capturing process. 100 | 101 | However this is still limited, as it doesn't allow full integration of the camera in your software. 102 | 103 | LCCV aims to provide a simple to use wrapper library that allows you to access the camera from a C++ program and capture images in cv::Mat format. 104 | 105 | Features and limitations 106 | 107 | ------------------------ 108 | 109 | LCCV is heavily based on Raspbian's `libcamera-apps` source code. It is aimed to offer full control over the camera, so the original options class was kept instead of a new one based on OpenCV's VideoCapture class. Note that only the camera parameters are available, other parameters and functions, like previewing, cropping and post-processing were stripped from the library. 110 | -------------------------------------------------------------------------------- /example/takephoto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | cv::Mat image; 7 | lccv::PiCamera cam; 8 | //cam.options->width=4056; 9 | //cam.options->height=3040; 10 | cam.options->photo_width=2028; 11 | cam.options->photo_height=1520; 12 | cam.options->verbose=true; 13 | 14 | for(int i=0;i<100;i++){ 15 | std::cout< 2 | #include 3 | 4 | int main() 5 | { 6 | std::cout<<"Sample program for LCCV video capture"<video_width=1024; 11 | cam.options->video_height=768; 12 | cam.options->framerate=5; 13 | cam.options->verbose=true; 14 | 15 | cam.startVideo(); 16 | int ch=0; 17 | while(ch!=27){ 18 | if(!cam.getVideoFrame(image,1000)){ 19 | std::cout<<"Timeout error"< 5 | #include 6 | #include 7 | #include 8 | 9 | #include "libcamera_app.hpp" 10 | #include "libcamera_app_options.hpp" 11 | 12 | namespace lccv { 13 | 14 | class PiCamera { 15 | public: 16 | PiCamera(); 17 | ~PiCamera(); 18 | 19 | Options *options; 20 | 21 | //Photo mode 22 | bool startPhoto(); 23 | bool capturePhoto(cv::Mat &frame); 24 | bool stopPhoto(); 25 | 26 | //Video mode 27 | bool startVideo(); 28 | bool getVideoFrame(cv::Mat &frame, unsigned int timeout); 29 | void stopVideo(); 30 | 31 | protected: 32 | void run(); 33 | protected: 34 | LibcameraApp *app; 35 | void getImage(cv::Mat &frame, CompletedRequestPtr &payload); 36 | static void *videoThreadFunc(void *p); 37 | pthread_t videothread; 38 | unsigned int still_flags; 39 | unsigned int vw,vh,vstr; 40 | std::atomic running,frameready; 41 | uint8_t *framebuffer; 42 | std::mutex mtx; 43 | bool camerastarted; 44 | }; 45 | 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /include/libcamera_app.hpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-2-Clause */ 2 | /* 3 | * Copyright (C) 2020-2021, Raspberry Pi (Trading) Ltd. 4 | * 5 | * libcamera_app.hpp - base class for libcamera apps. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | class Options; 35 | struct CompletedRequest; 36 | using CompletedRequestPtr = std::shared_ptr; 37 | 38 | namespace controls = libcamera::controls; 39 | namespace properties = libcamera::properties; 40 | 41 | class LibcameraApp 42 | { 43 | public: 44 | using Stream = libcamera::Stream; 45 | using FrameBuffer = libcamera::FrameBuffer; 46 | using ControlList = libcamera::ControlList; 47 | using Request = libcamera::Request; 48 | using CameraManager = libcamera::CameraManager; 49 | using Camera = libcamera::Camera; 50 | using CameraConfiguration = libcamera::CameraConfiguration; 51 | using FrameBufferAllocator = libcamera::FrameBufferAllocator; 52 | using StreamRole = libcamera::StreamRole; 53 | using StreamRoles = std::vector; 54 | using PixelFormat = libcamera::PixelFormat; 55 | using StreamConfiguration = libcamera::StreamConfiguration; 56 | using BufferMap = Request::BufferMap; 57 | using Size = libcamera::Size; 58 | using Rectangle = libcamera::Rectangle; 59 | enum class MsgType 60 | { 61 | RequestComplete, 62 | Quit 63 | }; 64 | typedef std::variant MsgPayload; 65 | struct Msg 66 | { 67 | Msg(MsgType const &t) : type(t) {} 68 | template 69 | Msg(MsgType const &t, T p) : type(t), payload(std::forward(p)) 70 | { 71 | } 72 | MsgType type; 73 | MsgPayload payload; 74 | }; 75 | 76 | // Some flags that can be used to give hints to the camera configuration. 77 | static constexpr unsigned int FLAG_STILL_NONE = 0; 78 | static constexpr unsigned int FLAG_STILL_BGR = 1; // supply BGR images, not YUV 79 | static constexpr unsigned int FLAG_STILL_RGB = 2; // supply RGB images, not YUV 80 | static constexpr unsigned int FLAG_STILL_RAW = 4; // request raw image stream 81 | static constexpr unsigned int FLAG_STILL_DOUBLE_BUFFER = 8; // double-buffer stream 82 | static constexpr unsigned int FLAG_STILL_TRIPLE_BUFFER = 16; // triple-buffer stream 83 | static constexpr unsigned int FLAG_STILL_BUFFER_MASK = 24; // mask for buffer flags 84 | 85 | static constexpr unsigned int FLAG_VIDEO_NONE = 0; 86 | static constexpr unsigned int FLAG_VIDEO_RAW = 1; // request raw image stream 87 | static constexpr unsigned int FLAG_VIDEO_JPEG_COLOURSPACE = 2; // force JPEG colour space 88 | 89 | LibcameraApp(std::unique_ptr const opts = nullptr); 90 | virtual ~LibcameraApp(); 91 | 92 | Options *GetOptions() const { return options_.get(); } 93 | 94 | std::string const &CameraId() const; 95 | void OpenCamera(); 96 | void CloseCamera(); 97 | 98 | void ConfigureStill(unsigned int flags = FLAG_STILL_NONE); 99 | void ConfigureViewfinder(); 100 | 101 | void Teardown(); 102 | void StartCamera(); 103 | void StopCamera(); 104 | 105 | Msg Wait(); 106 | void PostMessage(MsgType &t, MsgPayload &p); 107 | 108 | Stream *GetStream(std::string const &name, unsigned int *w = nullptr, unsigned int *h = nullptr, 109 | unsigned int *stride = nullptr) const; 110 | Stream *ViewfinderStream(unsigned int *w = nullptr, unsigned int *h = nullptr, 111 | unsigned int *stride = nullptr) const; 112 | Stream *StillStream(unsigned int *w = nullptr, unsigned int *h = nullptr, unsigned int *stride = nullptr) const; 113 | Stream *RawStream(unsigned int *w = nullptr, unsigned int *h = nullptr, unsigned int *stride = nullptr) const; 114 | Stream *VideoStream(unsigned int *w = nullptr, unsigned int *h = nullptr, unsigned int *stride = nullptr) const; 115 | Stream *LoresStream(unsigned int *w = nullptr, unsigned int *h = nullptr, unsigned int *stride = nullptr) const; 116 | Stream *GetMainStream() const; 117 | 118 | std::vector> Mmap(FrameBuffer *buffer) const; 119 | 120 | void SetControls(ControlList &controls); 121 | void StreamDimensions(Stream const *stream, unsigned int *w, unsigned int *h, unsigned int *stride) const; 122 | 123 | protected: 124 | std::unique_ptr options_; 125 | 126 | private: 127 | template 128 | class MessageQueue 129 | { 130 | public: 131 | template 132 | void Post(U &&msg) 133 | { 134 | std::unique_lock lock(mutex_); 135 | queue_.push(std::forward(msg)); 136 | cond_.notify_one(); 137 | } 138 | T Wait() 139 | { 140 | std::unique_lock lock(mutex_); 141 | cond_.wait(lock, [this] { return !queue_.empty(); }); 142 | T msg = std::move(queue_.front()); 143 | queue_.pop(); 144 | return msg; 145 | } 146 | void Clear() 147 | { 148 | std::unique_lock lock(mutex_); 149 | queue_ = {}; 150 | } 151 | 152 | private: 153 | std::queue queue_; 154 | std::mutex mutex_; 155 | std::condition_variable cond_; 156 | }; 157 | 158 | void setupCapture(); 159 | void makeRequests(); 160 | void queueRequest(CompletedRequest *completed_request); 161 | void requestComplete(Request *request); 162 | void configureDenoise(const std::string &denoise_mode); 163 | 164 | std::unique_ptr camera_manager_; 165 | std::shared_ptr camera_; 166 | bool camera_acquired_ = false; 167 | std::unique_ptr configuration_; 168 | std::map>> mapped_buffers_; 169 | std::map streams_; 170 | FrameBufferAllocator *allocator_ = nullptr; 171 | std::map> frame_buffers_; 172 | std::queue free_requests_; 173 | std::vector> requests_; 174 | std::mutex completed_requests_mutex_; 175 | std::set completed_requests_; 176 | bool camera_started_ = false; 177 | std::mutex camera_stop_mutex_; 178 | MessageQueue msg_queue_; 179 | // For setting camera controls. 180 | std::mutex control_mutex_; 181 | ControlList controls_; 182 | // Other: 183 | uint64_t last_timestamp_; 184 | uint64_t sequence_ = 0; 185 | }; 186 | 187 | struct FrameInfo 188 | { 189 | FrameInfo(libcamera::ControlList &ctrls) 190 | : exposure_time(0.0), digital_gain(0.0), colour_gains({ { 0.0f, 0.0f } }), focus(0.0), aelock(false) 191 | { 192 | auto exp = ctrls.get(libcamera::controls::ExposureTime); 193 | if (exp) 194 | exposure_time = *exp; 195 | 196 | auto ag = ctrls.get(libcamera::controls::AnalogueGain); 197 | if (ag) 198 | analogue_gain = *ag; 199 | 200 | auto dg = ctrls.get(libcamera::controls::DigitalGain); 201 | if (dg) 202 | digital_gain = *dg; 203 | 204 | auto cg = ctrls.get(libcamera::controls::ColourGains); 205 | if (cg) 206 | { 207 | colour_gains[0] = (*cg)[0], colour_gains[1] = (*cg)[1]; 208 | } 209 | 210 | auto fom = ctrls.get(libcamera::controls::FocusFoM); 211 | if (fom) 212 | focus = *fom; 213 | 214 | auto ae = ctrls.get(libcamera::controls::AeLocked); 215 | if (ae) 216 | aelock = *ae; 217 | } 218 | 219 | std::string ToString(std::string &info_string) const 220 | { 221 | std::string parsed(info_string); 222 | 223 | for (auto const &t : tokens) 224 | { 225 | std::size_t pos = parsed.find(t); 226 | if (pos != std::string::npos) 227 | { 228 | std::stringstream value; 229 | value << std::fixed << std::setprecision(2); 230 | 231 | if (t == "%frame") 232 | value << sequence; 233 | else if (t == "%fps") 234 | value << fps; 235 | else if (t == "%exp") 236 | value << exposure_time; 237 | else if (t == "%ag") 238 | value << analogue_gain; 239 | else if (t == "%dg") 240 | value << digital_gain; 241 | else if (t == "%rg") 242 | value << colour_gains[0]; 243 | else if (t == "%bg") 244 | value << colour_gains[1]; 245 | else if (t == "%focus") 246 | value << focus; 247 | else if (t == "%aelock") 248 | value << aelock; 249 | 250 | parsed.replace(pos, t.length(), value.str()); 251 | } 252 | } 253 | 254 | return parsed; 255 | } 256 | 257 | unsigned int sequence; 258 | float exposure_time; 259 | float analogue_gain; 260 | float digital_gain; 261 | std::array colour_gains; 262 | float focus; 263 | float fps; 264 | bool aelock; 265 | 266 | private: 267 | // Info text tokens. 268 | inline static const std::string tokens[] = { "%frame", "%fps", "%exp", "%ag", "%dg", 269 | "%rg", "%bg", "%focus", "%aelock" }; 270 | }; 271 | 272 | class Metadata 273 | { 274 | public: 275 | Metadata() = default; 276 | 277 | Metadata(Metadata const &other) 278 | { 279 | std::scoped_lock other_lock(other.mutex_); 280 | data_ = other.data_; 281 | } 282 | 283 | Metadata(Metadata &&other) 284 | { 285 | std::scoped_lock other_lock(other.mutex_); 286 | data_ = std::move(other.data_); 287 | other.data_.clear(); 288 | } 289 | 290 | template 291 | void Set(std::string const &tag, T &&value) 292 | { 293 | std::scoped_lock lock(mutex_); 294 | data_.insert_or_assign(tag, std::forward(value)); 295 | } 296 | 297 | template 298 | int Get(std::string const &tag, T &value) const 299 | { 300 | std::scoped_lock lock(mutex_); 301 | auto it = data_.find(tag); 302 | if (it == data_.end()) 303 | return -1; 304 | value = std::any_cast(it->second); 305 | return 0; 306 | } 307 | 308 | void Clear() 309 | { 310 | std::scoped_lock lock(mutex_); 311 | data_.clear(); 312 | } 313 | 314 | Metadata &operator=(Metadata const &other) 315 | { 316 | std::scoped_lock lock(mutex_, other.mutex_); 317 | data_ = other.data_; 318 | return *this; 319 | } 320 | 321 | Metadata &operator=(Metadata &&other) 322 | { 323 | std::scoped_lock lock(mutex_, other.mutex_); 324 | data_ = std::move(other.data_); 325 | other.data_.clear(); 326 | return *this; 327 | } 328 | 329 | void Merge(Metadata &other) 330 | { 331 | std::scoped_lock lock(mutex_, other.mutex_); 332 | data_.merge(other.data_); 333 | } 334 | 335 | template 336 | T *GetLocked(std::string const &tag) 337 | { 338 | // This allows in-place access to the Metadata contents, 339 | // for which you should be holding the lock. 340 | auto it = data_.find(tag); 341 | if (it == data_.end()) 342 | return nullptr; 343 | return std::any_cast(&it->second); 344 | } 345 | 346 | template 347 | void SetLocked(std::string const &tag, T &&value) 348 | { 349 | // Use this only if you're holding the lock yourself. 350 | data_.insert_or_assign(tag, std::forward(value)); 351 | } 352 | 353 | // Note: use of (lowercase) lock and unlock means you can create scoped 354 | // locks with the standard lock classes. 355 | // e.g. std::lock_guard lock(metadata) 356 | void lock() { mutex_.lock(); } 357 | void unlock() { mutex_.unlock(); } 358 | 359 | private: 360 | mutable std::mutex mutex_; 361 | std::map data_; 362 | }; 363 | 364 | struct CompletedRequest 365 | { 366 | using BufferMap = libcamera::Request::BufferMap; 367 | using ControlList = libcamera::ControlList; 368 | using Request = libcamera::Request; 369 | 370 | CompletedRequest(unsigned int seq, Request *r) 371 | : sequence(seq), buffers(r->buffers()), metadata(r->metadata()), request(r) 372 | { 373 | r->reuse(); 374 | } 375 | unsigned int sequence; 376 | BufferMap buffers; 377 | ControlList metadata; 378 | Request *request; 379 | float framerate; 380 | Metadata post_process_metadata; 381 | }; 382 | -------------------------------------------------------------------------------- /include/libcamera_app_options.hpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-2-Clause */ 2 | /* 3 | * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. 4 | * 5 | * options.hpp - common program options 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | enum Exposure_Modes { 20 | EXPOSURE_NORMAL = libcamera::controls::ExposureNormal, 21 | EXPOSURE_SHORT = libcamera::controls::ExposureShort, 22 | EXPOSURE_CUSTOM = libcamera::controls::ExposureCustom 23 | }; 24 | 25 | enum Metering_Modes { 26 | METERING_CENTRE = libcamera::controls::MeteringCentreWeighted, 27 | METERING_SPOT = libcamera::controls::MeteringSpot, 28 | METERING_MATRIX = libcamera::controls::MeteringMatrix, 29 | METERING_CUSTOM = libcamera::controls::MeteringCustom 30 | }; 31 | 32 | enum WhiteBalance_Modes { 33 | WB_AUTO = libcamera::controls::AwbAuto, 34 | WB_NORMAL = libcamera::controls::AwbAuto, 35 | WB_INCANDESCENT = libcamera::controls::AwbIncandescent, 36 | WB_TUNGSTEN = libcamera::controls::AwbTungsten, 37 | WB_FLUORESCENT = libcamera::controls::AwbFluorescent, 38 | WB_INDOOR = libcamera::controls::AwbIndoor, 39 | WB_DAYLIGHT = libcamera::controls::AwbDaylight, 40 | WB_CLOUDY = libcamera::controls::AwbCloudy, 41 | WB_CUSTOM = libcamera::controls::AwbAuto 42 | }; 43 | 44 | class Options 45 | { 46 | public: 47 | Options() 48 | { 49 | timeout=1000; 50 | metering_index = Metering_Modes::METERING_CENTRE; 51 | exposure_index=Exposure_Modes::EXPOSURE_NORMAL; 52 | awb_index=WhiteBalance_Modes::WB_AUTO; 53 | saturation=1.0f; 54 | contrast=1.0f; 55 | sharpness=1.0f; 56 | brightness=0.0f; 57 | shutter=0.0f; 58 | gain=0.0f; 59 | ev=0.0f; 60 | roi_x=roi_y=roi_width=roi_height=0; 61 | awb_gain_r=awb_gain_b=0; 62 | denoise="auto"; 63 | verbose=false; 64 | transform=libcamera::Transform::Identity; 65 | camera=0; 66 | } 67 | 68 | virtual ~Options() {} 69 | 70 | virtual void Print() const; 71 | 72 | void setMetering(Metering_Modes meteringmode){metering_index=meteringmode;} 73 | void setWhiteBalance(WhiteBalance_Modes wb){awb_index = wb;} 74 | void setExposureMode(Exposure_Modes exp){exposure_index = exp;} 75 | 76 | int getExposureMode(){return exposure_index;} 77 | int getMeteringMode(){return metering_index;} 78 | int getWhiteBalance(){return awb_index;} 79 | 80 | bool help; 81 | bool version; 82 | bool list_cameras; 83 | bool verbose; 84 | uint64_t timeout; // in ms 85 | unsigned int photo_width, photo_height; 86 | unsigned int video_width, video_height; 87 | bool rawfull; 88 | libcamera::Transform transform; 89 | float roi_x, roi_y, roi_width, roi_height; 90 | float shutter; 91 | float gain; 92 | float ev; 93 | float awb_gain_r; 94 | float awb_gain_b; 95 | float brightness; 96 | float contrast; 97 | float saturation; 98 | float sharpness; 99 | float framerate; 100 | std::string denoise; 101 | std::string info_text; 102 | unsigned int camera; 103 | 104 | protected: 105 | int metering_index; 106 | int exposure_index; 107 | int awb_index; 108 | 109 | private: 110 | }; 111 | -------------------------------------------------------------------------------- /src/lccv.cpp: -------------------------------------------------------------------------------- 1 | #include "lccv.hpp" 2 | #include 3 | #include 4 | 5 | using namespace cv; 6 | using namespace lccv; 7 | 8 | PiCamera::PiCamera() 9 | { 10 | app = new LibcameraApp(std::make_unique()); 11 | options = static_cast(app->GetOptions()); 12 | still_flags = LibcameraApp::FLAG_STILL_NONE; 13 | options->photo_width = 4056; 14 | options->photo_height = 3040; 15 | options->video_width = 640; 16 | options->video_height = 480; 17 | options->framerate = 30; 18 | options->denoise = "auto"; 19 | options->timeout = 1000; 20 | options->setMetering(Metering_Modes::METERING_MATRIX); 21 | options->setExposureMode(Exposure_Modes::EXPOSURE_NORMAL); 22 | options->setWhiteBalance(WhiteBalance_Modes::WB_AUTO); 23 | options->contrast = 1.0f; 24 | options->saturation = 1.0f; 25 | still_flags |= LibcameraApp::FLAG_STILL_RGB; 26 | running.store(false, std::memory_order_release);; 27 | frameready.store(false, std::memory_order_release);; 28 | framebuffer=nullptr; 29 | camerastarted=false; 30 | } 31 | 32 | PiCamera::~PiCamera() 33 | { 34 | delete app; 35 | } 36 | 37 | void PiCamera::getImage(cv::Mat &frame, CompletedRequestPtr &payload) 38 | { 39 | unsigned int w, h, stride; 40 | libcamera::Stream *stream = app->StillStream(); 41 | app->StreamDimensions(stream, &w, &h, &stride); 42 | const std::vector> mem = app->Mmap(payload->buffers[stream]); 43 | frame.create(h,w,CV_8UC3); 44 | uint ls = w*3; 45 | uint8_t *ptr = (uint8_t *)mem[0].data(); 46 | for (unsigned int i = 0; i < h; i++, ptr += stride) 47 | { 48 | memcpy(frame.ptr(i),ptr,ls); 49 | } 50 | } 51 | 52 | bool PiCamera::startPhoto() 53 | { 54 | app->OpenCamera(); 55 | app->ConfigureStill(still_flags); 56 | camerastarted=true; 57 | return true; 58 | } 59 | bool PiCamera::stopPhoto() 60 | { 61 | if(camerastarted){ 62 | camerastarted=false; 63 | app->Teardown(); 64 | app->CloseCamera(); 65 | } 66 | return true; 67 | } 68 | 69 | bool PiCamera::capturePhoto(cv::Mat &frame) 70 | { 71 | if(!camerastarted){ 72 | app->OpenCamera(); 73 | app->ConfigureStill(still_flags); 74 | } 75 | app->StartCamera(); 76 | LibcameraApp::Msg msg = app->Wait(); 77 | if (msg.type == LibcameraApp::MsgType::Quit) 78 | return false; 79 | else if (msg.type != LibcameraApp::MsgType::RequestComplete) 80 | return false; 81 | if (app->StillStream()) 82 | { 83 | app->StopCamera(); 84 | getImage(frame, std::get(msg.payload)); 85 | app->Teardown(); 86 | app->CloseCamera(); 87 | } else { 88 | std::cerr<<"Incorrect stream received"<StopCamera(); 91 | if(!camerastarted){ 92 | app->Teardown(); 93 | app->CloseCamera(); 94 | } 95 | } 96 | return true; 97 | } 98 | 99 | bool PiCamera::startVideo() 100 | { 101 | if(camerastarted)stopPhoto(); 102 | if(running.load(std::memory_order_release)){ 103 | std::cerr<<"Video thread already running"; 104 | return false; 105 | } 106 | frameready.store(false, std::memory_order_release); 107 | app->OpenCamera(); 108 | app->ConfigureViewfinder(); 109 | app->StartCamera(); 110 | 111 | int ret = pthread_create(&videothread, NULL, &videoThreadFunc, this); 112 | if (ret != 0) { 113 | std::cerr<<"Error starting video thread"; 114 | return false; 115 | } 116 | return true; 117 | } 118 | 119 | void PiCamera::stopVideo() 120 | { 121 | if(!running)return; 122 | 123 | running.store(false, std::memory_order_release);; 124 | 125 | //join thread 126 | void *status; 127 | int ret = pthread_join(videothread, &status); 128 | if(ret<0) 129 | std::cerr<<"Error joining thread"<StopCamera(); 132 | app->Teardown(); 133 | app->CloseCamera(); 134 | frameready.store(false, std::memory_order_release);; 135 | } 136 | 137 | bool PiCamera::getVideoFrame(cv::Mat &frame, unsigned int timeout) 138 | { 139 | if(!running.load(std::memory_order_acquire))return false; 140 | auto start_time = std::chrono::high_resolution_clock::now(); 141 | bool timeout_reached = false; 142 | timespec req; 143 | req.tv_sec=0; 144 | req.tv_nsec=1000000;//1ms 145 | while((!frameready.load(std::memory_order_acquire))&&(!timeout_reached)){ 146 | nanosleep(&req,NULL); 147 | timeout_reached = (std::chrono::high_resolution_clock::now() - start_time > std::chrono::milliseconds(timeout)); 148 | } 149 | if(frameready.load(std::memory_order_acquire)){ 150 | frame.create(vh,vw,CV_8UC3); 151 | uint ls = vw*3; 152 | mtx.lock(); 153 | uint8_t *ptr = framebuffer; 154 | for (unsigned int i = 0; i < vh; i++, ptr += vstr) 155 | memcpy(frame.ptr(i),ptr,ls); 156 | mtx.unlock(); 157 | frameready.store(false, std::memory_order_release);; 158 | return true; 159 | } 160 | else 161 | return false; 162 | } 163 | 164 | void *PiCamera::videoThreadFunc(void *p) 165 | { 166 | PiCamera *t = (PiCamera *)p; 167 | t->running.store(true, std::memory_order_release); 168 | //allocate framebuffer 169 | //unsigned int vw,vh,vstr; 170 | libcamera::Stream *stream = t->app->ViewfinderStream(&t->vw,&t->vh,&t->vstr); 171 | int buffersize=t->vh*t->vstr; 172 | if(t->framebuffer)delete[] t->framebuffer; 173 | t->framebuffer=new uint8_t[buffersize]; 174 | std::vector> mem; 175 | 176 | //main loop 177 | while(t->running.load(std::memory_order_acquire)){ 178 | LibcameraApp::Msg msg = t->app->Wait(); 179 | if (msg.type == LibcameraApp::MsgType::Quit){ 180 | std::cerr<<"Quit message received"<running.store(false,std::memory_order_release); 182 | } 183 | else if (msg.type != LibcameraApp::MsgType::RequestComplete) 184 | throw std::runtime_error("unrecognised message!"); 185 | 186 | 187 | CompletedRequestPtr payload = std::get(msg.payload); 188 | mem = t->app->Mmap(payload->buffers[stream]); 189 | t->mtx.lock(); 190 | memcpy(t->framebuffer,mem[0].data(),buffersize); 191 | t->mtx.unlock(); 192 | t->frameready.store(true, std::memory_order_release); 193 | } 194 | if(t->framebuffer){ 195 | delete[] t->framebuffer; 196 | t->framebuffer=nullptr; 197 | } 198 | return NULL; 199 | } 200 | -------------------------------------------------------------------------------- /src/libcamera_app.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-2-Clause */ 2 | /* 3 | * Copyright (C) 2021, Raspberry Pi (Trading) Ltd. 4 | * 5 | * libcamera_app.cpp - base class for libcamera apps. 6 | */ 7 | 8 | #include "libcamera_app.hpp" 9 | #include "libcamera_app_options.hpp" 10 | 11 | LibcameraApp::LibcameraApp(std::unique_ptr opts) 12 | : options_(std::move(opts)), controls_(controls::controls) 13 | 14 | { 15 | if (!options_) 16 | options_ = std::make_unique(); 17 | controls_.clear(); 18 | } 19 | 20 | LibcameraApp::~LibcameraApp() 21 | { 22 | StopCamera(); 23 | Teardown(); 24 | CloseCamera(); 25 | } 26 | 27 | std::string const &LibcameraApp::CameraId() const 28 | { 29 | return camera_->id(); 30 | } 31 | 32 | void LibcameraApp::OpenCamera() 33 | { 34 | 35 | if (options_->verbose) 36 | std::cerr << "Opening camera..." << std::endl; 37 | 38 | camera_manager_ = std::make_unique(); 39 | int ret = camera_manager_->start(); 40 | if (ret) 41 | throw std::runtime_error("camera manager failed to start, code " + std::to_string(-ret)); 42 | 43 | if (camera_manager_->cameras().size() == 0) 44 | throw std::runtime_error("no cameras available"); 45 | if (options_->camera >= camera_manager_->cameras().size()) 46 | throw std::runtime_error("selected camera is not available"); 47 | 48 | std::string const &cam_id = camera_manager_->cameras()[options_->camera]->id(); 49 | camera_ = camera_manager_->get(cam_id); 50 | if (!camera_) 51 | throw std::runtime_error("failed to find camera " + cam_id); 52 | 53 | if (camera_->acquire()) 54 | throw std::runtime_error("failed to acquire camera " + cam_id); 55 | camera_acquired_ = true; 56 | 57 | if (options_->verbose) 58 | std::cerr << "Acquired camera " << cam_id << std::endl; 59 | 60 | } 61 | 62 | void LibcameraApp::CloseCamera() 63 | { 64 | if (camera_acquired_) 65 | camera_->release(); 66 | camera_acquired_ = false; 67 | 68 | camera_.reset(); 69 | 70 | camera_manager_.reset(); 71 | 72 | if (options_->verbose && !options_->help) 73 | std::cerr << "Camera closed" << std::endl; 74 | } 75 | 76 | void LibcameraApp::ConfigureStill(unsigned int flags) 77 | { 78 | if (options_->verbose) 79 | std::cerr << "Configuring still capture..." << std::endl; 80 | 81 | // Always request a raw stream as this forces the full resolution capture mode. 82 | // (options_->mode can override the choice of camera mode, however.) 83 | StreamRoles stream_roles = { StreamRole::StillCapture, StreamRole::Raw }; 84 | configuration_ = camera_->generateConfiguration(stream_roles); 85 | if (!configuration_) 86 | throw std::runtime_error("failed to generate still capture configuration"); 87 | 88 | // Now we get to override any of the default settings from the options_-> 89 | if (flags & FLAG_STILL_BGR) 90 | configuration_->at(0).pixelFormat = libcamera::formats::BGR888; 91 | else if (flags & FLAG_STILL_RGB) 92 | configuration_->at(0).pixelFormat = libcamera::formats::RGB888; 93 | else 94 | configuration_->at(0).pixelFormat = libcamera::formats::YUV420; 95 | if ((flags & FLAG_STILL_BUFFER_MASK) == FLAG_STILL_DOUBLE_BUFFER) 96 | configuration_->at(0).bufferCount = 2; 97 | else if ((flags & FLAG_STILL_BUFFER_MASK) == FLAG_STILL_TRIPLE_BUFFER) 98 | configuration_->at(0).bufferCount = 3; 99 | if (options_->photo_width) 100 | configuration_->at(0).size.width = options_->photo_width; 101 | if (options_->photo_height) 102 | configuration_->at(0).size.height = options_->photo_height; 103 | 104 | configuration_->transform = options_->transform; 105 | 106 | //if (have_raw_stream && !options_->rawfull) 107 | { 108 | configuration_->at(1).size.width = configuration_->at(0).size.width; 109 | configuration_->at(1).size.height = configuration_->at(0).size.height; 110 | } 111 | configuration_->at(1).bufferCount = configuration_->at(0).bufferCount; 112 | 113 | configureDenoise(options_->denoise == "auto" ? "cdn_hq" : options_->denoise); 114 | setupCapture(); 115 | 116 | streams_["still"] = configuration_->at(0).stream(); 117 | streams_["raw"] = configuration_->at(1).stream(); 118 | 119 | if (options_->verbose) 120 | std::cerr << "Still capture setup complete" << std::endl; 121 | } 122 | 123 | void LibcameraApp::ConfigureViewfinder() 124 | { 125 | if (options_->verbose) 126 | std::cerr << "Configuring viewfinder..." << std::endl; 127 | 128 | StreamRoles stream_roles = { StreamRole::Viewfinder }; 129 | configuration_ = camera_->generateConfiguration(stream_roles); 130 | if (!configuration_) 131 | throw std::runtime_error("failed to generate viewfinder configuration"); 132 | 133 | // Now we get to override any of the default settings from the options_-> 134 | configuration_->at(0).pixelFormat = libcamera::formats::RGB888; 135 | configuration_->at(0).size.width = options_->video_width; 136 | configuration_->at(0).size.height = options_->video_height; 137 | configuration_->at(0).bufferCount = 4; 138 | 139 | configuration_->transform = options_->transform; 140 | 141 | configureDenoise(options_->denoise == "auto" ? "cdn_off" : options_->denoise); 142 | setupCapture(); 143 | 144 | streams_["viewfinder"] = configuration_->at(0).stream(); 145 | 146 | if (options_->verbose) 147 | std::cerr << "Viewfinder setup complete" << std::endl; 148 | } 149 | 150 | void LibcameraApp::Teardown() 151 | { 152 | if (options_->verbose && !options_->help) 153 | std::cerr << "Tearing down requests, buffers and configuration" << std::endl; 154 | 155 | for (auto &iter : mapped_buffers_) 156 | { 157 | // assert(iter.first->planes().size() == iter.second.size()); 158 | // for (unsigned i = 0; i < iter.first->planes().size(); i++) 159 | for (auto &span : iter.second) 160 | munmap(span.data(), span.size()); 161 | } 162 | mapped_buffers_.clear(); 163 | 164 | delete allocator_; 165 | allocator_ = nullptr; 166 | 167 | configuration_.reset(); 168 | 169 | frame_buffers_.clear(); 170 | 171 | streams_.clear(); 172 | } 173 | 174 | void LibcameraApp::StartCamera() 175 | { 176 | // This makes all the Request objects that we shall need. 177 | makeRequests(); 178 | 179 | // Build a list of initial controls that we must set in the camera before starting it. 180 | // We don't overwrite anything the application may have set before calling us. 181 | if (!controls_.get(controls::ScalerCrop) && options_->roi_width != 0 && options_->roi_height != 0) 182 | { 183 | Rectangle sensor_area = *camera_->properties().get(properties::ScalerCropMaximum); 184 | int x = options_->roi_x * sensor_area.width; 185 | int y = options_->roi_y * sensor_area.height; 186 | int w = options_->roi_width * sensor_area.width; 187 | int h = options_->roi_height * sensor_area.height; 188 | Rectangle crop(x, y, w, h); 189 | crop.translateBy(sensor_area.topLeft()); 190 | if (options_->verbose) 191 | std::cerr << "Using crop " << crop.toString() << std::endl; 192 | controls_.set(controls::ScalerCrop, crop); 193 | } 194 | 195 | // Framerate is a bit weird. If it was set programmatically, we go with that, but 196 | // otherwise it applies only to preview/video modes. For stills capture we set it 197 | // as long as possible so that we get whatever the exposure profile wants. 198 | if (!controls_.get(controls::FrameDurationLimits)) 199 | { 200 | if (StillStream()) 201 | controls_.set(controls::FrameDurationLimits, libcamera::Span({ INT64_C(100), INT64_C(1000000000) })); 202 | else if (options_->framerate > 0) 203 | { 204 | int64_t frame_time = 1000000 / options_->framerate; // in us 205 | controls_.set(controls::FrameDurationLimits, libcamera::Span({ frame_time, frame_time })); 206 | } 207 | } 208 | 209 | if (!controls_.get(controls::ExposureTime) && options_->shutter) 210 | controls_.set(controls::ExposureTime, options_->shutter); 211 | if (!controls_.get(controls::AnalogueGain) && options_->gain) 212 | controls_.set(controls::AnalogueGain, options_->gain); 213 | if (!controls_.get(controls::AeMeteringMode)) 214 | controls_.set(controls::AeMeteringMode, options_->getMeteringMode()); 215 | if (!controls_.get(controls::AeExposureMode)) 216 | controls_.set(controls::AeExposureMode, options_->getExposureMode()); 217 | if (!controls_.get(controls::ExposureValue)) 218 | controls_.set(controls::ExposureValue, options_->ev); 219 | if (!controls_.get(controls::AwbMode)) 220 | controls_.set(controls::AwbMode, options_->getWhiteBalance()); 221 | if (!controls_.get(controls::ColourGains) && options_->awb_gain_r && options_->awb_gain_b) 222 | controls_.set(controls::ColourGains, libcamera::Span({ options_->awb_gain_r, options_->awb_gain_b })); 223 | if (!controls_.get(controls::Brightness)) 224 | controls_.set(controls::Brightness, options_->brightness); 225 | if (!controls_.get(controls::Contrast)) 226 | controls_.set(controls::Contrast, options_->contrast); 227 | if (!controls_.get(controls::Saturation)) 228 | controls_.set(controls::Saturation, options_->saturation); 229 | if (!controls_.get(controls::Sharpness)) 230 | controls_.set(controls::Sharpness, options_->sharpness); 231 | 232 | if (camera_->start(&controls_)) 233 | throw std::runtime_error("failed to start camera"); 234 | controls_.clear(); 235 | camera_started_ = true; 236 | last_timestamp_ = 0; 237 | 238 | camera_->requestCompleted.connect(this, &LibcameraApp::requestComplete); 239 | 240 | for (std::unique_ptr &request : requests_) 241 | { 242 | if (camera_->queueRequest(request.get()) < 0) 243 | throw std::runtime_error("Failed to queue request"); 244 | } 245 | 246 | if (options_->verbose) 247 | std::cerr << "Camera started!" << std::endl; 248 | } 249 | 250 | void LibcameraApp::StopCamera() 251 | { 252 | { 253 | // We don't want QueueRequest to run asynchronously while we stop the camera. 254 | std::lock_guard lock(camera_stop_mutex_); 255 | if (camera_started_) 256 | { 257 | if (camera_->stop()) 258 | throw std::runtime_error("failed to stop camera"); 259 | 260 | camera_started_ = false; 261 | } 262 | } 263 | 264 | if (camera_) 265 | camera_->requestCompleted.disconnect(this, &LibcameraApp::requestComplete); 266 | 267 | // An application might be holding a CompletedRequest, so queueRequest will get 268 | // called to delete it later, but we need to know not to try and re-queue it. 269 | completed_requests_.clear(); 270 | 271 | msg_queue_.Clear(); 272 | 273 | while (!free_requests_.empty()) 274 | free_requests_.pop(); 275 | 276 | requests_.clear(); 277 | 278 | controls_.clear(); // no need for mutex here 279 | 280 | if (options_->verbose && !options_->help) 281 | std::cerr << "Camera stopped!" << std::endl; 282 | } 283 | 284 | LibcameraApp::Msg LibcameraApp::Wait() 285 | { 286 | return msg_queue_.Wait(); 287 | } 288 | 289 | void LibcameraApp::queueRequest(CompletedRequest *completed_request) 290 | { 291 | BufferMap buffers(std::move(completed_request->buffers)); 292 | 293 | Request *request = completed_request->request; 294 | delete completed_request; 295 | assert(request); 296 | 297 | // This function may run asynchronously so needs protection from the 298 | // camera stopping at the same time. 299 | std::lock_guard stop_lock(camera_stop_mutex_); 300 | if (!camera_started_) 301 | return; 302 | 303 | // An application could be holding a CompletedRequest while it stops and re-starts 304 | // the camera, after which we don't want to queue another request now. 305 | { 306 | std::lock_guard lock(completed_requests_mutex_); 307 | auto it = completed_requests_.find(completed_request); 308 | if (it == completed_requests_.end()) 309 | return; 310 | completed_requests_.erase(it); 311 | } 312 | 313 | for (auto const &p : buffers) 314 | { 315 | if (request->addBuffer(p.first, p.second) < 0) 316 | throw std::runtime_error("failed to add buffer to request in QueueRequest"); 317 | } 318 | 319 | { 320 | std::lock_guard lock(control_mutex_); 321 | request->controls() = std::move(controls_); 322 | } 323 | 324 | if (camera_->queueRequest(request) < 0) 325 | throw std::runtime_error("failed to queue request"); 326 | } 327 | 328 | void LibcameraApp::PostMessage(MsgType &t, MsgPayload &p) 329 | { 330 | msg_queue_.Post(Msg(t, std::move(p))); 331 | } 332 | 333 | libcamera::Stream *LibcameraApp::GetStream(std::string const &name, unsigned int *w, unsigned int *h, 334 | unsigned int *stride) const 335 | { 336 | auto it = streams_.find(name); 337 | if (it == streams_.end()) 338 | return nullptr; 339 | StreamDimensions(it->second, w, h, stride); 340 | return it->second; 341 | } 342 | 343 | libcamera::Stream *LibcameraApp::ViewfinderStream(unsigned int *w, unsigned int *h, unsigned int *stride) const 344 | { 345 | return GetStream("viewfinder", w, h, stride); 346 | } 347 | 348 | libcamera::Stream *LibcameraApp::StillStream(unsigned int *w, unsigned int *h, unsigned int *stride) const 349 | { 350 | return GetStream("still", w, h, stride); 351 | } 352 | 353 | libcamera::Stream *LibcameraApp::RawStream(unsigned int *w, unsigned int *h, unsigned int *stride) const 354 | { 355 | return GetStream("raw", w, h, stride); 356 | } 357 | 358 | libcamera::Stream *LibcameraApp::VideoStream(unsigned int *w, unsigned int *h, unsigned int *stride) const 359 | { 360 | return GetStream("video", w, h, stride); 361 | } 362 | 363 | libcamera::Stream *LibcameraApp::LoresStream(unsigned int *w, unsigned int *h, unsigned int *stride) const 364 | { 365 | return GetStream("lores", w, h, stride); 366 | } 367 | 368 | libcamera::Stream *LibcameraApp::GetMainStream() const 369 | { 370 | for (auto &p : streams_) 371 | { 372 | if (p.first == "viewfinder" || p.first == "still" || p.first == "video") 373 | return p.second; 374 | } 375 | 376 | return nullptr; 377 | } 378 | 379 | std::vector> LibcameraApp::Mmap(FrameBuffer *buffer) const 380 | { 381 | auto item = mapped_buffers_.find(buffer); 382 | if (item == mapped_buffers_.end()) 383 | return {}; 384 | return item->second; 385 | } 386 | 387 | void LibcameraApp::SetControls(ControlList &controls) 388 | { 389 | std::lock_guard lock(control_mutex_); 390 | controls_ = std::move(controls); 391 | } 392 | 393 | void LibcameraApp::StreamDimensions(Stream const *stream, unsigned int *w, unsigned int *h, unsigned int *stride) const 394 | { 395 | StreamConfiguration const &cfg = stream->configuration(); 396 | if (w) 397 | *w = cfg.size.width; 398 | if (h) 399 | *h = cfg.size.height; 400 | if (stride) 401 | *stride = cfg.stride; 402 | } 403 | 404 | void LibcameraApp::setupCapture() 405 | { 406 | // First finish setting up the configuration. 407 | 408 | CameraConfiguration::Status validation = configuration_->validate(); 409 | if (validation == CameraConfiguration::Invalid) 410 | throw std::runtime_error("failed to valid stream configurations"); 411 | else if (validation == CameraConfiguration::Adjusted) 412 | std::cerr << "Stream configuration adjusted" << std::endl; 413 | 414 | if (camera_->configure(configuration_.get()) < 0) 415 | throw std::runtime_error("failed to configure streams"); 416 | if (options_->verbose) 417 | std::cerr << "Camera streams configured" << std::endl; 418 | 419 | // Next allocate all the buffers we need, mmap them and store them on a free list. 420 | 421 | allocator_ = new FrameBufferAllocator(camera_); 422 | for (StreamConfiguration &config : *configuration_) 423 | { 424 | Stream *stream = config.stream(); 425 | 426 | if (allocator_->allocate(stream) < 0) 427 | throw std::runtime_error("failed to allocate capture buffers"); 428 | 429 | for (const std::unique_ptr &buffer : allocator_->buffers(stream)) 430 | { 431 | // "Single plane" buffers appear as multi-plane here, but we can spot them because then 432 | // planes all share the same fd. We accumulate them so as to mmap the buffer only once. 433 | size_t buffer_size = 0; 434 | for (unsigned i = 0; i < buffer->planes().size(); i++) 435 | { 436 | const FrameBuffer::Plane &plane = buffer->planes()[i]; 437 | buffer_size += plane.length; 438 | if (i == buffer->planes().size() - 1 || plane.fd.get() != buffer->planes()[i + 1].fd.get()) 439 | { 440 | void *memory = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, plane.fd.get(), 0); 441 | mapped_buffers_[buffer.get()].push_back( 442 | libcamera::Span(static_cast(memory), buffer_size)); 443 | buffer_size = 0; 444 | } 445 | } 446 | frame_buffers_[stream].push(buffer.get()); 447 | } 448 | } 449 | if (options_->verbose) 450 | std::cerr << "Buffers allocated and mapped" << std::endl; 451 | 452 | // The requests will be made when StartCamera() is called. 453 | } 454 | 455 | void LibcameraApp::makeRequests() 456 | { 457 | auto free_buffers(frame_buffers_); 458 | while (true) 459 | { 460 | for (StreamConfiguration &config : *configuration_) 461 | { 462 | Stream *stream = config.stream(); 463 | if (stream == configuration_->at(0).stream()) 464 | { 465 | if (free_buffers[stream].empty()) 466 | { 467 | if (options_->verbose) 468 | std::cerr << "Requests created" << std::endl; 469 | return; 470 | } 471 | std::unique_ptr request = camera_->createRequest(); 472 | if (!request) 473 | throw std::runtime_error("failed to make request"); 474 | requests_.push_back(std::move(request)); 475 | } 476 | else if (free_buffers[stream].empty()) 477 | throw std::runtime_error("concurrent streams need matching numbers of buffers"); 478 | 479 | FrameBuffer *buffer = free_buffers[stream].front(); 480 | free_buffers[stream].pop(); 481 | if (requests_.back()->addBuffer(stream, buffer) < 0) 482 | throw std::runtime_error("failed to add buffer to request"); 483 | } 484 | } 485 | } 486 | 487 | void LibcameraApp::requestComplete(Request *request) 488 | { 489 | if (request->status() == Request::RequestCancelled) 490 | return; 491 | 492 | CompletedRequest *r = new CompletedRequest(sequence_++, request); 493 | CompletedRequestPtr payload(r, [this](CompletedRequest *cr) { this->queueRequest(cr); }); 494 | { 495 | std::lock_guard lock(completed_requests_mutex_); 496 | completed_requests_.insert(r); 497 | } 498 | 499 | // We calculate the instantaneous framerate in case anyone wants it. 500 | uint64_t timestamp = payload->buffers.begin()->second->metadata().timestamp; 501 | if (last_timestamp_ == 0 || last_timestamp_ == timestamp) 502 | payload->framerate = 0; 503 | else 504 | payload->framerate = 1e9 / (timestamp - last_timestamp_); 505 | last_timestamp_ = timestamp; 506 | 507 | msg_queue_.Post(Msg(MsgType::RequestComplete, std::move(payload))); 508 | } 509 | 510 | void LibcameraApp::configureDenoise(const std::string &denoise_mode) 511 | { 512 | using namespace libcamera::controls::draft; 513 | 514 | static const std::map denoise_table = { 515 | { "off", NoiseReductionModeOff }, 516 | { "cdn_off", NoiseReductionModeMinimal }, 517 | { "cdn_fast", NoiseReductionModeFast }, 518 | { "cdn_hq", NoiseReductionModeHighQuality } 519 | }; 520 | NoiseReductionModeEnum denoise; 521 | 522 | auto const mode = denoise_table.find(denoise_mode); 523 | if (mode == denoise_table.end()) 524 | throw std::runtime_error("Invalid denoise mode " + denoise_mode); 525 | denoise = mode->second; 526 | 527 | controls_.set(NoiseReductionMode, denoise); 528 | } 529 | -------------------------------------------------------------------------------- /src/libcamera_app_options.cpp: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-2-Clause */ 2 | /* 3 | * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. 4 | * 5 | * options.cpp - common program options helpers 6 | */ 7 | #include "libcamera_app_options.hpp" 8 | 9 | void Options::Print() const 10 | { 11 | std::cerr << "Options:" << std::endl; 12 | std::cerr << " verbose: " << verbose << std::endl; 13 | std::cerr << " info_text:" << info_text << std::endl; 14 | std::cerr << " timeout: " << timeout << std::endl; 15 | std::cerr << " photo resolution: " << photo_width << " x "<< photo_height << std::endl; 16 | std::cerr << " video resolution: " << video_width << " x " << video_height << std::endl; 17 | std::cerr << " rawfull: " << rawfull << std::endl; 18 | std::cerr << " transform: " << transformToString(transform) << std::endl; 19 | if (roi_width == 0 || roi_height == 0) 20 | std::cerr << " roi: all" << std::endl; 21 | else 22 | std::cerr << " roi: " << roi_x << "," << roi_y << "," << roi_width << "," << roi_height << std::endl; 23 | if (shutter) 24 | std::cerr << " shutter: " << shutter << std::endl; 25 | if (gain) 26 | std::cerr << " gain: " << gain << std::endl; 27 | std::cerr << " metering: " << metering_index << std::endl; 28 | std::cerr << " exposure: " << exposure_index << std::endl; 29 | std::cerr << " ev: " << ev << std::endl; 30 | std::cerr << " awb: " << awb_index << std::endl; 31 | if (awb_gain_r && awb_gain_b) 32 | std::cerr << " awb gains: red " << awb_gain_r << " blue " << awb_gain_b << std::endl; 33 | std::cerr << " brightness: " << brightness << std::endl; 34 | std::cerr << " contrast: " << contrast << std::endl; 35 | std::cerr << " saturation: " << saturation << std::endl; 36 | std::cerr << " sharpness: " << sharpness << std::endl; 37 | std::cerr << " framerate: " << framerate << std::endl; 38 | std::cerr << " denoise: " << denoise << std::endl; 39 | } 40 | --------------------------------------------------------------------------------