/* * This file is part of MotionPlus. * * MotionPlus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MotionPlus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MotionPlus. If not, see . * * Copyright 2020-2023 MotionMrDave@gmail.com * */ /* TODO: * 1. Identify parameters for camera and print them out to the user. * 2. Apply user provided parameters to the camera. * 3. Determine if we need to have multiple requests or buffers. * (The current logic is just a single request and buffer but * this may need to change to allow for multiple requests or buffers * so as to reduce latency. As of now, it is kept simple with * a single request and buffer.) * 4. Need to determine flags for designating start up, shutdown * etc. and possibly add mutex locking. Startup currently has * a SLEEP to allow for initialization but this should change * 5. The shutdown is not completely cleaning up something with libcamera. * Upon a stop of a camera and starting it again libcamera reports * that the v4l2 devices are busy and can not be opened. Then it * seg aborts upon us. */ #include "motionplus.hpp" #include "conf.hpp" #include "logger.hpp" #include "util.hpp" #include "rotate.hpp" #include "video_common.hpp" #include "libcam.hpp" #ifdef HAVE_LIBCAM using namespace libcamera; void cls_libcam::cam_start_params(ctx_dev *ptr) { int indx; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting."); camctx = ptr; camctx->libcam->params = (ctx_params*)mymalloc(sizeof(ctx_params)); camctx->libcam->params->update_params = true; util_parms_parse(camctx->libcam->params, camctx->conf->libcam_params); for (indx = 0; indx < camctx->libcam->params->params_count; indx++) { MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s : %s" ,camctx->libcam->params->params_array[indx].param_name ,camctx->libcam->params->params_array[indx].param_value ); } MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Finished."); } int cls_libcam::cam_start_mgr() { int retcd; std::string camid; libcamera::Size picsz; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting."); cam_mgr = std::make_unique(); retcd = cam_mgr->start(); if (retcd != 0) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Error starting camera manager. Return code: %d",retcd); return retcd; } started_mgr = true; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "cam_mgr started."); if (camctx->conf->libcam_device == "camera0"){ camid = cam_mgr->cameras()[0]->id(); } else { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Invalid libcam_device '%s'. The only name supported is 'camera0' " ,camctx->conf->libcam_device.c_str()); return -1; } camera = cam_mgr->get(camid); camera->acquire(); started_aqr = true; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Finished."); return 0; } int cls_libcam::cam_start_config() { int retcd; libcamera::Size picsz; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting."); config = camera->generateConfiguration({ StreamRole::Viewfinder }); config->at(0).pixelFormat = PixelFormat::fromString("YUV420"); config->at(0).size.width = camctx->conf->width; config->at(0).size.height = camctx->conf->height; config->at(0).bufferCount = 1; retcd = config->validate(); if (retcd == CameraConfiguration::Adjusted) { if (config->at(0).pixelFormat != PixelFormat::fromString("YUV420")) { MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO , "Pixel format was adjusted to %s." , config->at(0).pixelFormat.toString().c_str()); return -1; } } else if (retcd == CameraConfiguration::Invalid) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Error setting configuration"); return -1; } if ((config->at(0).size.width != (unsigned int)camctx->conf->width) || (config->at(0).size.height != (unsigned int)camctx->conf->height)) { MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO , "Image size adjusted from %d x %d to %d x %d" , camctx->conf->width , camctx->conf->height , config->at(0).size.width , config->at(0).size.height); } camctx->imgs.width = config->at(0).size.width; camctx->imgs.height = config->at(0).size.height; camctx->imgs.size_norm = (camctx->imgs.width * camctx->imgs.height * 3) / 2; camctx->imgs.motionsize = camctx->imgs.width * camctx->imgs.height; camera->configure(config.get()); MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Finished."); return 0; } int cls_libcam::req_add(Request *request) { int retcd; retcd = camera->queueRequest(request); return retcd; } int cls_libcam::cam_start_req() { int retcd, bytes; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting."); camera->requestCompleted.connect(this, &cls_libcam::req_complete); frmbuf = std::make_unique(camera); retcd = frmbuf->allocate(config->at(0).stream()); if (retcd < 0) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Buffer allocation error."); return -1; } std::unique_ptr request = camera->createRequest(); if (!request) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Create request error."); return -1; } Stream *stream = config->at(0).stream(); const std::vector> &buffers = frmbuf->buffers(stream); const std::unique_ptr &buffer = buffers[0]; retcd = request->addBuffer(stream, buffer.get()); if (retcd < 0) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Add buffer for request error."); return -1; } const FrameBuffer::Plane &plane0 = buffer->planes()[0]; bytes = plane0.length; if (buffer->planes().size() > 1) { const FrameBuffer::Plane &plane1 = buffer->planes()[1]; bytes += plane1.length; } if (buffer->planes().size() > 2) { const FrameBuffer::Plane &plane2 = buffer->planes()[2]; bytes += plane2.length; } if (bytes > camctx->imgs.size_norm){ MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Image size error. Usual size is %d but planes size is %d" ,camctx->imgs.size_norm, bytes); return -1; } membuf.buf = (uint8_t *)mmap(NULL,bytes, PROT_READ , MAP_SHARED, plane0.fd.get(), 0); membuf.bufsz = bytes; requests.push_back(std::move(request)); started_req = true; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Finished."); return 0; } int cls_libcam::cam_start_capture() { int retcd; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting."); retcd = camera->start(&this->controls); if (retcd) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Failed to start capture."); return -1; } controls.clear(); for (std::unique_ptr &request : requests) { retcd = req_add(request.get()); if (retcd < 0) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO , "Failed to queue request."); if (started_cam) { camera->stop(); } return -1; } } MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Finished."); return 0; } void cls_libcam::req_complete(Request *request) { if (request->status() == Request::RequestCancelled) { return; } req_queue.push(request); } int cls_libcam::cam_start(ctx_dev *cam) { int retcd; started_cam = false; started_mgr = false; started_aqr = false; started_req = false; cam_start_params(cam); retcd = cam_start_mgr(); if (retcd != 0) { return -1; } retcd = cam_start_config(); if (retcd != 0) { return -1; } retcd = cam_start_req(); if (retcd != 0) { return -1; } retcd = cam_start_capture(); if (retcd != 0) { return -1; } SLEEP(2,0); started_cam = true; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Started all."); return 0; } void cls_libcam::cam_stop() { util_parms_free(camctx->libcam->params); myfree(&camctx->libcam->params); camctx->libcam->params = NULL; if (started_aqr) { camera->stop(); } if (started_req) { camera->requestCompleted.disconnect(this, &cls_libcam::req_complete); while (req_queue.empty() == false) { req_queue.pop(); } requests.clear(); frmbuf->free(config->at(0).stream()); frmbuf.reset(); } controls.clear(); if (started_aqr){ camera->release(); camera.reset(); } if (started_mgr) { cam_mgr->stop(); cam_mgr.reset(); } MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Stopped."); } /* get the image from libcam */ int cls_libcam::cam_next(ctx_image_data *img_data) { int indx; if (started_cam == false) { return CAPTURE_FAILURE; } /* Allow time for request to finish.*/ indx=0; while ((req_queue.empty() == true) && (indx < 50)) { SLEEP(0,2000) indx++; } if (req_queue.empty() == false) { Request *request = this->req_queue.front(); memcpy(img_data->image_norm, membuf.buf, membuf.bufsz); this->req_queue.pop(); request->reuse(Request::ReuseBuffers); req_add(request); return CAPTURE_SUCCESS; } else { return CAPTURE_FAILURE; } } #endif /** close and stop libcam */ void libcam_cleanup(ctx_dev *cam) { #ifdef HAVE_LIBCAM cam->libcam->cam_stop(); delete cam->libcam; cam->libcam = nullptr; #endif cam->device_status = STATUS_CLOSED; } /** initialize and start libcam */ void libcam_start(ctx_dev *cam) { #ifdef HAVE_LIBCAM int retcd; MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO,_("Opening libcam")); MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "Starting experimental libcamera ."); MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "EXPECT crashes and hung processes!!!"); cam->libcam = new cls_libcam; retcd = cam->libcam->cam_start(cam); if (retcd < 0) { MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO,_("libcam failed to open")); libcam_cleanup(cam); } else { cam->device_status = STATUS_OPENED; } #else cam->device_status = STATUS_CLOSED; #endif } /** get next image from libcam */ int libcam_next(ctx_dev *cam, ctx_image_data *img_data) { #ifdef HAVE_LIBCAM int retcd; if (cam->libcam == nullptr){ return CAPTURE_FAILURE; } retcd = cam->libcam->cam_next(img_data); if (retcd == CAPTURE_SUCCESS) { rotate_map(cam, img_data); } return retcd; #else (void)cam; (void)img_data; return CAPTURE_FAILURE; #endif }