#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # Copyright (C) 2022, Tomi Valkeinen # A simple capture example showing: # - How to setup the camera # - Capture certain number of frames in a blocking manner # - How to stop the camera # # This simple example is, in many ways, too simple. The purpose of the example # is to introduce the concepts. A more realistic example is given in # simple-continuous-capture.py. import argparse import libcamera as libcam import selectors import sys # Number of frames to capture TOTAL_FRAMES = 30 def main(): parser = argparse.ArgumentParser() parser.add_argument('-c', '--camera', type=str, default='1', help='Camera index number (starting from 1) or part of the name') parser.add_argument('-f', '--format', type=str, help='Pixel format') parser.add_argument('-s', '--size', type=str, help='Size ("WxH")') args = parser.parse_args() cm = libcam.CameraManager.singleton() try: if args.camera.isnumeric(): cam_idx = int(args.camera) cam = next((cam for i, cam in enumerate(cm.cameras) if i + 1 == cam_idx)) else: cam = next((cam for cam in cm.cameras if args.camera in cam.id)) except Exception: print(f'Failed to find camera "{args.camera}"') return -1 # Acquire the camera for our use cam.acquire() # Configure the camera cam_config = cam.generate_configuration([libcam.StreamRole.Viewfinder]) stream_config = cam_config.at(0) if args.format: fmt = libcam.PixelFormat(args.format) stream_config.pixel_format = fmt if args.size: w, h = [int(v) for v in args.size.split('x')] stream_config.size = libcam.Size(w, h) cam.configure(cam_config) print(f'Capturing {TOTAL_FRAMES} frames with {stream_config}') stream = stream_config.stream # Allocate the buffers for capture allocator = libcam.FrameBufferAllocator(cam) ret = allocator.allocate(stream) assert ret > 0 num_bufs = len(allocator.buffers(stream)) # Create the requests and assign a buffer for each request reqs = [] for i in range(num_bufs): # Use the buffer index as the cookie req = cam.create_request(i) buffer = allocator.buffers(stream)[i] req.add_buffer(stream, buffer) reqs.append(req) # Start the camera cam.start() # frames_queued and frames_done track the number of frames queued and done frames_queued = 0 frames_done = 0 # Queue the requests to the camera for req in reqs: cam.queue_request(req) frames_queued += 1 # The main loop. Wait for the queued Requests to complete, process them, # and re-queue them again. sel = selectors.DefaultSelector() sel.register(cm.event_fd, selectors.EVENT_READ) while frames_done < TOTAL_FRAMES: # cm.get_ready_requests() does not block, so we use a Selector to wait # for a camera event. Here we should almost always get a single # Request, but in some cases there could be multiple or none. events = sel.select() if not events: continue reqs = cm.get_ready_requests() for req in reqs: frames_done += 1 buffers = req.buffers # A ready Request could contain multiple buffers if multiple streams # were being used. Here we know we only have a single stream, # and we use next(iter()) to get the first and only buffer. assert len(buffers) == 1 stream, fb = next(iter(buffers.items())) # Here we could process the received buffer. In this example we only # print a few details below. meta = fb.metadata print("seq {:3}, bytes {}, frames queued/done {:3}/{:<3}" .format(meta.sequence, '/'.join([str(p.bytes_used) for p in meta.planes]), frames_queued, frames_done)) # If we want to capture more frames we need to queue more Requests. # We could create a totally new Request, but it is more efficient # to reuse the existing one that we just received. if frames_queued < TOTAL_FRAMES: req.reuse() cam.queue_request(req) frames_queued += 1 # Stop the camera cam.stop() # Release the camera cam.release() return 0 if __name__ == '__main__': sys.exit(main()) f='#n45'>45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * controls.cpp - V4L2 device controls handling test
 */

#include <algorithm>
#include <array>
#include <iostream>
#include <limits.h>

#include "libcamera/internal/v4l2_videodevice.h"

#include "v4l2_videodevice_test.h"

/* These come from the vivid driver. */
#define VIVID_CID_CUSTOM_BASE		(V4L2_CID_USER_BASE | 0xf000)
#define VIVID_CID_U8_4D_ARRAY		(VIVID_CID_CUSTOM_BASE + 10)

/* Helper for VIVID_CID_U8_4D_ARRAY control array size: not from kernel. */
#define VIVID_CID_U8_ARRAY_SIZE		(2 * 3 * 4 * 5)

using namespace std;
using namespace libcamera;

class V4L2ControlTest : public V4L2VideoDeviceTest
{
public:
	V4L2ControlTest()
		: V4L2VideoDeviceTest("vivid", "vivid-000-vid-cap")
	{
	}

protected:
	int run()
	{
		const ControlInfoMap &infoMap = capture_->controls();

		/* Test control enumeration. */
		if (infoMap.empty()) {
			cerr << "Failed to enumerate controls" << endl;
			return TestFail;
		}

		if (infoMap.find(V4L2_CID_BRIGHTNESS) == infoMap.end() ||
		    infoMap.find(V4L2_CID_CONTRAST) == infoMap.end() ||
		    infoMap.find(V4L2_CID_SATURATION) == infoMap.end() ||
		    infoMap.find(VIVID_CID_U8_4D_ARRAY) == infoMap.end()) {
			cerr << "Missing controls" << endl;
			return TestFail;
		}

		const ControlInfo &brightness = infoMap.find(V4L2_CID_BRIGHTNESS)->second;
		const ControlInfo &contrast = infoMap.find(V4L2_CID_CONTRAST)->second;
		const ControlInfo &saturation = infoMap.find(V4L2_CID_SATURATION)->second;
		const ControlInfo &u8 = infoMap.find(VIVID_CID_U8_4D_ARRAY)->second;

		/* Test getting controls. */
		ControlList ctrls = capture_->getControls({ V4L2_CID_BRIGHTNESS,
							    V4L2_CID_CONTRAST,
							    V4L2_CID_SATURATION,
							    VIVID_CID_U8_4D_ARRAY });
		if (ctrls.empty()) {
			cerr << "Failed to get controls" << endl;
			return TestFail;
		}

		if (ctrls.infoMap() != &infoMap) {
			cerr << "Incorrect infoMap for retrieved controls" << endl;
			return TestFail;
		}

		if (ctrls.get(V4L2_CID_BRIGHTNESS).get<int32_t>() == -1 ||
		    ctrls.get(V4L2_CID_CONTRAST).get<int32_t>() == -1 ||
		    ctrls.get(V4L2_CID_SATURATION).get<int32_t>() == -1) {
			cerr << "Incorrect value for retrieved controls" << endl;
			return TestFail;
		}

		uint8_t u8Min = u8.min().get<uint8_t>();
		uint8_t u8Max = u8.max().get<uint8_t>();

		Span<const uint8_t> u8Span = ctrls.get(VIVID_CID_U8_4D_ARRAY).get<Span<const uint8_t>>();
		bool valid = std::all_of(u8Span.begin(), u8Span.end(),
					 [&](uint8_t v) { return v >= u8Min && v <= u8Max; });
		if (!valid) {
			cerr << "Incorrect value for retrieved array control"
			     << endl;
			return TestFail;
		}

		/* Test setting controls. */
		ctrls.set(V4L2_CID_BRIGHTNESS, brightness.min());
		ctrls.set(V4L2_CID_CONTRAST, contrast.max());
		ctrls.set(V4L2_CID_SATURATION, saturation.min());

		std::array<uint8_t, VIVID_CID_U8_ARRAY_SIZE> u8Values;
		std::fill(u8Values.begin(), u8Values.end(), u8.min().get<uint8_t>());
		ctrls.set(VIVID_CID_U8_4D_ARRAY, Span<const uint8_t>(u8Values));

		int ret = capture_->setControls(&ctrls);
		if (ret) {
			cerr << "Failed to set controls" << endl;
			return TestFail;
		}

		/* Test setting controls outside of range. */
		ctrls.set(V4L2_CID_BRIGHTNESS, brightness.min().get<int32_t>() - 1);
		ctrls.set(V4L2_CID_CONTRAST, contrast.max().get<int32_t>() + 1);
		ctrls.set(V4L2_CID_SATURATION, saturation.min().get<int32_t>() + 1);

		ret = capture_->setControls(&ctrls);
		if (ret) {
			cerr << "Failed to set controls (out of range)" << endl;
			return TestFail;
		}

		if (ctrls.get(V4L2_CID_BRIGHTNESS) != brightness.min() ||
		    ctrls.get(V4L2_CID_CONTRAST) != contrast.max() ||
		    ctrls.get(V4L2_CID_SATURATION) != saturation.min().get<int32_t>() + 1) {
			cerr << "Controls not updated when set" << endl;
			return TestFail;
		}

		return TestPass;
	}
};

TEST_REGISTER(V4L2ControlTest)