From e9935bf2209ac0362dd0c061c05f9195b9b474ee Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Tue, 17 Oct 2023 13:41:22 +0100 Subject: [PATCH 1/2] core: Add ZSL stream configuration Add a ConfigureZsl() helper that configures libcamera for zero shutter lag operating mode. This sets up a full resolution stills capture stream, a viewfinder stream, and an optional raw stream. Signed-off-by: Naushir Patuck --- core/libcamera_app.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++ core/libcamera_app.hpp | 1 + core/still_options.hpp | 4 ++ 3 files changed, 100 insertions(+) diff --git a/core/libcamera_app.cpp b/core/libcamera_app.cpp index d3ebf7e0..3d3c181a 100644 --- a/core/libcamera_app.cpp +++ b/core/libcamera_app.cpp @@ -393,6 +393,101 @@ void LibcameraApp::ConfigureViewfinder() LOG(2, "Viewfinder setup complete"); } +void LibcameraApp::ConfigureZsl(unsigned int still_flags) +{ + LOG(2, "Configuring ZSL..."); + + StreamRoles stream_roles = { StreamRole::StillCapture, StreamRole::Viewfinder }; + if (!options_->no_raw) + stream_roles.push_back(StreamRole::Raw); + + configuration_ = camera_->generateConfiguration(stream_roles); + if (!configuration_) + throw std::runtime_error("failed to generate viewfinder configuration"); + + // Now we get to override any of the default settings from the options_-> + if (still_flags & FLAG_STILL_BGR) + configuration_->at(0).pixelFormat = libcamera::formats::BGR888; + else if (still_flags & FLAG_STILL_RGB) + configuration_->at(0).pixelFormat = libcamera::formats::RGB888; + else + configuration_->at(0).pixelFormat = libcamera::formats::YUV420; + if (options_->buffer_count > 0) + configuration_->at(0).bufferCount = options_->buffer_count; + else + // Use the viewfinder stream buffer count if none has been provided + configuration_->at(0).bufferCount = configuration_->at(1).bufferCount; + if (options_->width) + configuration_->at(0).size.width = options_->width; + if (options_->height) + configuration_->at(0).size.height = options_->height; + configuration_->at(0).colorSpace = libcamera::ColorSpace::Sycc; + configuration_->transform = options_->transform; + + post_processor_.AdjustConfig("still", &configuration_->at(0)); + + if (!options_->no_raw) + { + options_->mode.update(configuration_->at(0).size, options_->framerate); + options_->mode = selectMode(options_->mode); + + configuration_->at(2).size = options_->mode.Size(); + configuration_->at(2).pixelFormat = mode_to_pixel_format(options_->mode); + configuration_->sensorConfig = libcamera::SensorConfiguration(); + configuration_->sensorConfig->outputSize = options_->mode.Size(); + configuration_->sensorConfig->bitDepth = options_->mode.bit_depth; + configuration_->at(2).bufferCount = configuration_->at(0).bufferCount; + } + + Size size(1280, 960); + auto area = camera_->properties().get(properties::PixelArrayActiveAreas); + if (options_->viewfinder_width && options_->viewfinder_height) + size = Size(options_->viewfinder_width, options_->viewfinder_height); + else if (area) + { + // The idea here is that most sensors will have a 2x2 binned mode that + // we can pick up. If it doesn't, well, you can always specify the size + // you want exactly with the viewfinder_width/height options_-> + size = (*area)[0].size() / 2; + // If width and height were given, we might be switching to capture + // afterwards - so try to match the field of view. + if (options_->width && options_->height) + size = size.boundedToAspectRatio(Size(options_->width, options_->height)); + size.alignDownTo(2, 2); // YUV420 will want to be even + LOG(2, "Viewfinder size chosen is " << size.toString()); + } + + // Finally trim the image size to the largest that the preview can handle. + Size max_size; + preview_->MaxImageSize(max_size.width, max_size.height); + if (max_size.width && max_size.height) + { + size.boundTo(max_size.boundedToAspectRatio(size)).alignDownTo(2, 2); + LOG(2, "Final viewfinder size is " << size.toString()); + } + + // Now we get to override any of the default settings from the options_-> + configuration_->at(1).pixelFormat = libcamera::formats::YUV420; + configuration_->at(1).size = size; + configuration_->at(1).bufferCount = configuration_->at(0).bufferCount; + + configuration_->transform = options_->transform; + + post_processor_.AdjustConfig("viewfinder", &configuration_->at(1)); + + configureDenoise(options_->denoise == "auto" ? "cdn_hq" : options_->denoise); + setupCapture(); + + streams_["still"] = configuration_->at(0).stream(); + streams_["viewfinder"] = configuration_->at(1).stream(); + if (!options_->no_raw) + streams_["raw"] = configuration_->at(2).stream(); + + post_processor_.Configure(); + + LOG(2, "ZSL setup complete"); +} + void LibcameraApp::ConfigureStill(unsigned int flags) { LOG(2, "Configuring still capture..."); diff --git a/core/libcamera_app.hpp b/core/libcamera_app.hpp index 010534a2..1bdd2de1 100644 --- a/core/libcamera_app.hpp +++ b/core/libcamera_app.hpp @@ -135,6 +135,7 @@ class LibcameraApp void ConfigureViewfinder(); void ConfigureStill(unsigned int flags = FLAG_STILL_NONE); void ConfigureVideo(unsigned int flags = FLAG_VIDEO_NONE); + void ConfigureZsl(unsigned int still_flags = FLAG_STILL_NONE); void Teardown(); void StartCamera(); diff --git a/core/still_options.hpp b/core/still_options.hpp index b97efcbd..6d125bd4 100644 --- a/core/still_options.hpp +++ b/core/still_options.hpp @@ -48,6 +48,8 @@ struct StillOptions : public Options "Perform first capture immediately, with no preview phase") ("autofocus-on-capture", value(&af_on_capture)->default_value(false)->implicit_value(true), "Switch to AfModeAuto and trigger a scan just before capturing a still") + ("zsl", value(&zsl)->default_value(false)->implicit_value(true), + "Switch to AfModeAuto and trigger a scan just before capturing a still") ; // clang-format on } @@ -67,6 +69,7 @@ struct StillOptions : public Options bool raw; std::string latest; bool immediate; + bool zsl; virtual bool Parse(int argc, char *argv[]) override { @@ -114,6 +117,7 @@ struct StillOptions : public Options std::cerr << " latest: " << latest << std::endl; std::cerr << " immediate " << immediate << std::endl; std::cerr << " AF on capture: " << af_on_capture << std::endl; + std::cerr << " Zero shutter lag: " << zsl << std::endl; for (auto &s : exif) std::cerr << " EXIF: " << s << std::endl; } From 9a4ac24b9d4ebc484adaf89402d133730f41bfc6 Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Tue, 17 Oct 2023 14:48:56 +0100 Subject: [PATCH 2/2] app: libcamera_still: Enable optional ZSL mode Configure the application to configure ZSL mode if the --zsh command line option is set. This keeps the stream running continuously when doing a still image capture without a reconfiguration step in between. Signed-off-by: Naushir Patuck --- apps/libcamera_still.cpp | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/libcamera_still.cpp b/apps/libcamera_still.cpp index 1f03b1f4..04b01a02 100644 --- a/apps/libcamera_still.cpp +++ b/apps/libcamera_still.cpp @@ -193,6 +193,8 @@ static void event_loop(LibcameraStillApp &app) std::this_thread::sleep_for(10ms); } } + else if (options->zsl) + app.ConfigureZsl(); else app.ConfigureViewfinder(); app.StartCamera(); @@ -209,6 +211,7 @@ static void event_loop(LibcameraStillApp &app) } af_wait_state = AF_WAIT_NONE; int af_wait_timeout = 0; + bool want_capture = false; for (unsigned int count = 0;; count++) { LibcameraApp::Msg msg = app.Wait(); @@ -235,7 +238,7 @@ static void event_loop(LibcameraStillApp &app) // In viewfinder mode, run until the timeout or keypress. When that happens, // if the "--autofocus-on-capture" option was set, trigger an AF scan and wait // for it to complete. Then switch to capture mode if an output was requested. - if (app.ViewfinderStream()) + if (app.ViewfinderStream() && !want_capture) { LOG(2, "Viewfinder frame " << count); timelapse_frames++; @@ -244,7 +247,6 @@ static void event_loop(LibcameraStillApp &app) bool timelapse_timed_out = options->timelapse && (now - timelapse_time) > options->timelapse.value && timelapse_frames >= TIMELAPSE_MIN_FRAMES; - bool want_capture = false; if (af_wait_state != AF_WAIT_NONE) { @@ -281,9 +283,12 @@ static void event_loop(LibcameraStillApp &app) keypressed = false; af_wait_state = AF_WAIT_NONE; timelapse_time = std::chrono::high_resolution_clock::now(); - app.StopCamera(); - app.Teardown(); - app.ConfigureStill(still_flags); + if (!options->zsl) + { + app.StopCamera(); + app.Teardown(); + app.ConfigureStill(still_flags); + } if (options->af_on_capture) { libcamera::ControlList cl; @@ -291,16 +296,19 @@ static void event_loop(LibcameraStillApp &app) cl.set(libcamera::controls::AfTrigger, libcamera::controls::AfTriggerCancel); app.SetControls(cl); } - app.StartCamera(); + if (!options->zsl) + app.StartCamera(); } else app.ShowPreview(completed_request, app.ViewfinderStream()); } // In still capture mode, save a jpeg. Go back to viewfinder if in timelapse mode, // otherwise quit. - else if (app.StillStream()) + else if (app.StillStream() && want_capture) { - app.StopCamera(); + want_capture = false; + if (!options->zsl) + app.StopCamera(); LOG(1, "Still capture image received"); save_images(app, completed_request); if (!options->metadata.empty()) @@ -308,8 +316,11 @@ static void event_loop(LibcameraStillApp &app) timelapse_frames = 0; if (!options->immediate && (options->timelapse || options->signal || options->keypress)) { - app.Teardown(); - app.ConfigureViewfinder(); + if (!options->zsl) + { + app.Teardown(); + app.ConfigureViewfinder(); + } if (options->af_on_capture && options->afMode_index == -1) { libcamera::ControlList cl; @@ -317,7 +328,8 @@ static void event_loop(LibcameraStillApp &app) cl.set(libcamera::controls::AfTrigger, libcamera::controls::AfTriggerCancel); app.SetControls(cl); } - app.StartCamera(); + if (!options->zsl) + app.StartCamera(); af_wait_state = AF_WAIT_NONE; } else