Hi Dafna, On 11/10/20 8:35 AM, Dafna Hirschfeld wrote: > The script runs tests that configure and streams > the rkisp1. The script wraps the commands > provided by v4l-utils and can optionally > use the 'cam' command from libcamera. > > Signed-off-by: Dafna Hirschfeld <dafna.hirschfeld@xxxxxxxxxxxxx> Thanks for this work! Could you please fix the issues found by pycodestyle tool? Example: pycodestyle --show-source --show-pep8 contrib/test/test-rkisp1.py project page: https://pypi.org/project/pycodestyle/ Also, this is my log.txt http://ix.io/2DTZ Maybe this could be improved a bit to show which tests suceeded, failed or have warnings, what do you think? Thanks Helen > --- > contrib/test/test-rkisp1.py | 576 ++++++++++++++++++++++++++++++++++++ > contrib/test/v4l2lib.py | 90 ++++++ > 2 files changed, 666 insertions(+) > create mode 100755 contrib/test/test-rkisp1.py > create mode 100644 contrib/test/v4l2lib.py > > diff --git a/contrib/test/test-rkisp1.py b/contrib/test/test-rkisp1.py > new file mode 100755 > index 00000000..dcdd1ca6 > --- /dev/null > +++ b/contrib/test/test-rkisp1.py > @@ -0,0 +1,576 @@ > +#!/bin/env python3 > + > +# SPDX-License-Identifier: GPL-2.0-only > +# Copyright 2020 Collabora > + > +import argparse > +import enum > +import logging > +import re > +import subprocess > +import sys > +import os > +import v4l2lib > + > +BUS_INFO = "platform:rkisp1" > +MODULE = "rockchip_isp1" > + > +# -------------------------------------------------------- > +# Generic v4l2 functions > +# -------------------------------------------------------- > + > +libcamera_dic = { > + "YUYV" : "YUYV", > + "422P" : None, > + "NV16" : "NV16", > + "NV61" : "NV61", > + "YM61" : None, > + "NV21" : "NV21", > + "NV12" : "NV12", > + "NM21" : None, > + "NM12" : None, > + "YU12" : None, > + "YV12" : None, > + "GREY" : "R8" > +} > + > +v4l2_ffmpeg_fmt = { > + "422P" : "yuv422p", > + "YM24" : "yuv444p", > + "YU12" : "yuv420p", > + "YV12" : "yuv420p", #this will swap the colors but ffmpeg has no support for yvu > + "NV12" : "nv12", > + "NV21" : "nv21", > + "NM12" : "nv12", > + "NM21" : "nv21", > +# The nv16 does not work on ffplay :( > +# "NV16" : "nv16", > + "BA24" : "argb", > + "GREY" : "gray", > + "YUYV" : "yuyv422", > + "RGBP" : "rgb565le", > + "RGB3" : "rgb24", > + "XR24" : "bgr0" > +} > + > +rsz_yuv_fmts = [ "YUYV8_2X8", "YUYV8_1_5X8"] > + > +pix_mbus_bayer_map = { > +'RGGB' : 'SRGGB8_1X8', > +'GRBG' : 'SGRBG8_1X8', > +'GBRG' : 'SGBRG8_1X8', > +'BA81' : 'SBGGR8_1X8', > +'RG10' : 'SRGGB10_1X10', > +'BA10' : 'SGRBG10_1X10', > +'GB10' : 'SGBRG10_1X10', > +'BG10' : 'SBGGR10_1X10', > +'RG12' : 'SRGGB12_1X12', > +'BA12' : 'SGRBG12_1X12', > +'GB12' : 'SGBRG12_1X12', > +'BG12' : 'SBGGR12_1X12', > +} > + > +pix_mbus_rsz_map = { > + "422P" : "YUYV8_2X8", > + "GREY" : "YUYV8_2X8", > + "NM12" : "YUYV8_1_5X8", > + "NM21" : "YUYV8_1_5X8", > + "NV12" : "YUYV8_1_5X8", > + "NV16" : "YUYV8_2X8", > + "NV21" : "YUYV8_1_5X8", > + "NV61" : "YUYV8_2X8", > + "RGBP" : "YUYV8_2X8", > + "VYUY" : "YUYV8_2X8", > + "XR24" : "YUYV8_2X8", > + "YM61" : "YUYV8_2X8", > + "YU12" : "YUYV8_1_5X8", > + "YUYV" : "YUYV8_2X8", > + "YV12" : "YUYV8_1_5X8", > + "YVYU" : "YUYV8_2X8", > +} > + > +pix_mbus_map = { **pix_mbus_bayer_map, **pix_mbus_rsz_map} > + > +# run a shell command. > +# by default wait for the command to finish and return its output. > +# if called with wait=False, then run the command and return > +# immediately. The return value is then the the handle to the subprocess. > +# the calling function can use it to wait > +# wait=False is used for simultaneous streaming > +def run(cmd, wait=True): > + logging.debug(" ".join(['"{0}"'.format(x) for x in cmd])) > + if wait: > + try: > + proc = subprocess.run(cmd, capture_output=True, check=True, timeout=10) > + except subprocess.CalledProcessError as e: > + logging.error(e.stdout.decode('ascii')) > + raise > + return proc.stdout.decode('ascii') > + else: > + try: > + proc = subprocess.Popen(cmd) > + except subprocess.CalledProcessError as e: > + logging.error(e.stdout.decode('ascii')) > + raise > + return proc > + > +def find_sensor(): > + topo = run(["media-ctl", "-d", BUS_INFO, "-p"]) > + entities = re.findall("^- entity \d+:.*\n", topo, re.MULTILINE) > + for ent in entities: > + if "rkisp1" not in ent: > + sensor = re.search("^- entity \d+: (.+) \(", ent) > + sensor = sensor.group(1) > + logging.debug("found sensor " + sensor) > + return sensor > + return None > + > +def get_subdev_fmt(entity, pad): > + padprop = run(["media-ctl", "-d", BUS_INFO, "--get-v4l2", > + '"{entity}":{pad}'.format(entity=entity, pad=pad)]) > + fmt = re.search("fmt:(.*?)/(\d+)x(\d+)", padprop) > + bounds = re.search("crop.bounds:\((\d+),(\d+)\)/(\d+)x(\d+)", padprop) > + crop = re.search("crop:\((\d+),(\d+)\)/(\d+)x(\d+)", padprop) > + ret = { > + "mbus": fmt.group(1), > + "size": tuple(map(int, (fmt.group(2), fmt.group(3)))) > + } > + if bounds: > + ret["crop_bounds"] = tuple(map(int, bounds.groups())) > + if crop: > + ret["crop"] = tuple(map(int, crop.groups())) > + > + return ret > + > + > +def set_subdev_fmt(entity, pad, fmt): > + curr_fmt = get_subdev_fmt(entity, pad) > + > + if not fmt: > + return curr_fmt > + > + logging.debug("set format for {entity}:{pad}".format(entity=entity,pad=pad)) > + logging.debug("old format: {curr}".format(curr=curr_fmt)) > + > + if "size" in fmt: > + curr_fmt["size"] = fmt["size"] > + if "mbus" in fmt: > + curr_fmt["mbus"] = fmt["mbus"] > + if "crop" in fmt: > + curr_fmt["crop"] = fmt["crop"] > + logging.debug("cropping") > + > + # we have no control on the behaviour of the sensor > + # the imx219 for example have "crop" as read only > + # so we have to delete it, otherwise the media-ctl command fails > + if entity == args.sensor: > + del(curr_fmt["crop"]) > + properties = "fmt:{mbus}/{width}x{height}".format(mbus=curr_fmt["mbus"], > + width=curr_fmt["size"][0], > + height=curr_fmt["size"][1]) > + if ("crop" in curr_fmt): > + crop = " crop: ({left},{top})/{width}x{height}".format(left=curr_fmt["crop"][0], > + top=curr_fmt["crop"][1], > + width=curr_fmt["crop"][2], > + height=curr_fmt["crop"][3]) > + properties = properties + crop > + > + run(["media-ctl", "-d", BUS_INFO, "--set-v4l2", > + '"{entity}":{pad} [{properties}]'.format(entity=entity, > + pad=pad, > + properties=properties)]) > + > + updated_fmt = get_subdev_fmt(entity, pad) > + > + logging.debug("new format: {new}".format(new=updated_fmt)) > + return updated_fmt > + > + > +def get_video_fmt(entity): > + fmt = run(["v4l2-ctl", "-z", BUS_INFO, "-d", str(entity), "-V"]) > + size = re.search("(\d+)/(\d+)", fmt) > + pixelformat = re.search("'(.*?)'", fmt) > + return { > + "size": (int(size.group(1)), int(size.group(2))), > + "pixelformat": pixelformat.group(1) > + } > + > + > +def set_video_fmt(entity, fmt): > + properties = "" > + if "size" in fmt: > + size = "width={width},height={height},".format(width=fmt["size"][0], > + height=fmt["size"][1]) > + properties = properties + size > + if "pixelformat" in fmt: > + pixfmt = "pixelformat={pixelformat}" > + properties = properties + pixfmt.format(pixelformat=fmt["pixelformat"]) > + > + run(["v4l2-ctl", "-z", BUS_INFO, "-d", str(entity), "-v", properties]) > + > + current_fmt = get_video_fmt(entity) > + if ( > + ("size" in fmt and fmt["size"] != current_fmt["size"]) or > + ("pixelformat" in fmt and > + fmt["pixelformat"] != current_fmt["pixelformat"]) > + ): > + error_msg = "{entity}: Format couldn't be set. " \ > + "Expected {expected}; Got {got}" > + raise Exception(error_msg.format(entity=entity, expected=fmt, got=current_fmt)) > + return current_fmt > + > +def disable_links(): > + run(["media-ctl", "-r"]) > + > +def get_mbus_codes(entity, pad): > + output = run(["v4l2-ctl", "-z", BUS_INFO, "-d", str(entity), > + "--list-subdev-mbus-codes", str(pad)]) > + return re.findall("MEDIA_BUS_FMT_([^,]+)", output) > + > +def get_pixelformats(entity): > + output = run(["v4l2-ctl", "-z", BUS_INFO, "-d", str(entity), > + "--list-formats"]) > + return re.findall("'([A-Z0-9_]*)'", output) > + > +def get_expected_file_size(n_frames, entity): > + fmt = get_video_fmt(entity) > + pix = fmt["pixelformat"] > + return int(n_frames) * fmt["size"][0] * fmt["size"][1] * v4l2lib.V4L2_PIX_MUL[pix] / v4l2lib.V4L2_PIX_DIV[pix] > + > +def set_file_name_for_stream(entity): > + if not args.store: > + return None > + > + fmt = get_video_fmt(entity) > + out_file = "{out}/stream-{path}-{w}x{h}-{pixfmt}.raw".format(out=args.output, > + path=str(entity), > + w=fmt["size"][0], h=fmt["size"][1], > + pixfmt=fmt["pixelformat"]) > + > + if fmt["pixelformat"] in v4l2_ffmpeg_fmt: > + ffplay_file = "{out}/ffplay.sh".format(out=args.output) > + with open(ffplay_file, "a") as ff: > + cmd = "ffplay -f rawvideo -pixel_format {pixfmt} -video_size {w}x{h} {f}\n" > + cmd = cmd.format(pixfmt=v4l2_ffmpeg_fmt[fmt["pixelformat"]], w=fmt["size"][0], h=fmt["size"][1],f=out_file) > + ff.write(cmd) > + return out_file > + > + > +#the wait argument will be False when testing simultaneous streaming > +def start_stream(entity, out_file=None, wait=True, n_frames = "1"): > + stream_cmd = ["v4l2-ctl", "-z", BUS_INFO, "-d", str(entity), "--stream-mmap", > + "--stream-count", n_frames] > + if out_file: > + stream_cmd = stream_cmd + ["--stream-to", out_file] > + logging.debug("will capture stream to file {f}".format(f=out_file)) > + > + ret = run(stream_cmd,wait=wait) > + > + > + if args.compliance and wait: > + output = run(["v4l2-compliance", "-z", BUS_INFO, "-d", str(entity), "-s", n_frames]) > + logging.debug(output) > + return ret > + > +class LimDim(enum.IntEnum): > + RSZ_MP_SRC_MAX_WIDTH = 4416 > + RSZ_MP_SRC_MAX_HEIGHT = 3312 > + RSZ_SP_SRC_MAX_WIDTH = 1920 > + RSZ_SP_SRC_MAX_HEIGHT = 1920 > + RSZ_SRC_MIN_WIDTH = 32 > + RSZ_SRC_MIN_HEIGHT = 16 > + > + > +class Link(enum.IntEnum): > + ENABLED = 1 > + ENABLE = 1 > + DISABLED = 0 > + DISABLE = 0 > + > +class Entities(enum.Enum): > + isp = "rkisp1_isp" > + resizer_mp = "rkisp1_resizer_mainpath" > + resizer_sp = "rkisp1_resizer_selfpath" > + cap_mp = "rkisp1_mainpath" > + cap_sp = "rkisp1_selfpath" > + > + def __str__(self): > + return str(self.value) > + > +class IspPads(enum.IntEnum): > + SINK_VIDEO = 0, > + SINK_PARAMS = 1, > + SOURCE_VIDEO = 2, > + SOURCE_STATS = 3, > + > + def __str__(self): > + return str(self.value) > + > +class ResizerPads(enum.IntEnum): > + SINK = 0, > + SOURCE = 1, > + > +def rkisp1_is_link_to_sink_enabled(sink_entity, sink_pad): > + topology = run(["media-ctl", "-d", BUS_INFO, "-p"]) > + pattern = '"{sink_entity}":{sink_pad} \[(.+?)\]' > + pattern = pattern.format(sink_entity=sink_entity, sink_pad=sink_pad) > + status = re.findall(pattern, topology) > + if 'ENABLED' in status or 'ENABLED,IMMUTABLE' in status: > + return True > + else: > + return False > + > +def rkisp1_set_link(src_entity, src_pad, sink_entity, sink_pad, is_on=1): > + link = "'{src_entity}':{src_pad} -> '{sink_entity}':{sink_pad} [{is_on}]" > + link = link.format(src_entity=src_entity, > + src_pad=src_pad, > + sink_entity=sink_entity, > + sink_pad=sink_pad, is_on=is_on) > + run(["media-ctl", "-d", BUS_INFO, "-l", link]) > + is_en = rkisp1_is_link_to_sink_enabled(sink_entity, sink_pad) > + if (is_en and not is_on) or (not is_en and is_on): > + raise Exception("Couldn't set link {link}".format(link=link)) > + > +def rkisp1_disable_link(src_entity, src_pad, sink_entity, sink_pad): > + rkisp1_set_link(src_entity, src_pad, sink_entity, sink_pad, is_on=0) > + > +def rkisp1_enable_link(src_entity, src_pad, sink_entity, sink_pad): > + rkisp1_set_link(src_entity, src_pad, sink_entity, sink_pad, is_on=1) > + > +def rkisp1_propagate_resizer_fmt(resizer, src_fmt=None): > + pfmt = set_subdev_fmt(resizer, ResizerPads.SOURCE, src_fmt) > + if resizer == Entities.resizer_sp: > + set_video_fmt(Entities.cap_sp, pfmt) > + else: > + set_video_fmt(Entities.cap_mp, pfmt) > + > +# Note, for the isp source format, only the crop can change on the sink pad > +# since the format should be the same as the sensor > +# otherwise we get EPIPE > +# for now we support cropping only in the sink > +# and the mbus format of the source > +def rkisp1_propagate_isp_fmt(src_fmt=None, sink_fmt=None): > + set_subdev_fmt(Entities.isp, IspPads.SINK_VIDEO, sink_fmt) > + pfmt = set_subdev_fmt(Entities.isp, IspPads.SOURCE_VIDEO, src_fmt) > + # this settings already include cropping > + set_subdev_fmt(Entities.resizer_mp, ResizerPads.SINK, pfmt) > + set_subdev_fmt(Entities.resizer_sp, ResizerPads.SINK, pfmt) > + > +def rkisp1_propagate_sensor_fmt(src_fmt=None): > + pfmt = set_subdev_fmt(args.sensor, 0, src_fmt) > + set_subdev_fmt(Entities.isp, IspPads.SINK_VIDEO, pfmt) > + > +# Put the driver in a known state > +def rkisp1_prepare_test(): > + disable_links() > + rkisp1_enable_link(args.sensor, 0, Entities.isp, IspPads.SINK_VIDEO) > + rkisp1_enable_link(Entities.isp, IspPads.SOURCE_VIDEO, > + Entities.resizer_mp, ResizerPads.SINK) > + rkisp1_enable_link(Entities.isp, IspPads.SOURCE_VIDEO, > + Entities.resizer_sp, ResizerPads.SINK) > + > + rkisp1_propagate_sensor_fmt(src_fmt={"size": (800, 600)}) > + rkisp1_propagate_isp_fmt(src_fmt={"mbus" : "YUYV8_2X8", "crop": (0,0,800,600)},sink_fmt={"crop" : (0,0,800,600)}) > + rkisp1_propagate_resizer_fmt(Entities.resizer_mp, src_fmt={"size":(800,600)}) > + rkisp1_propagate_resizer_fmt(Entities.resizer_sp, src_fmt={"size":(800,600)}) > + set_video_fmt(Entities.cap_mp, {"pixelformat": "NV12"}) > + set_video_fmt(Entities.cap_sp, {"pixelformat": "NV12"}) > + logging.debug("end of test prepare") > + > +def test_debugfs(): > + debugfs_files = [ "outform_size_err", "img_stabilization_size_error", "inform_size_error"] > + for dfs in debugfs_files: > + dfs_path = "/sys/kernel/debug/rkisp1/" + dfs > + with open(dfs_path) as de: > + if de.read(1) != '0': > + raise Exception("the debugfs file {dfs} indicates an error".format(dfs=dfs_path)) > + > + > +# -------------------------------------------------------- > +# rkisp1 tests > +# -------------------------------------------------------- > +def configure_and_stream(pixelformat, path, isp_dim, resizer_dim): > + caps = [] > + resizers = [] > + > + for p in path: > + if p == "mainpath": > + resizers.append(Entities.resizer_mp) > + caps.append(Entities.cap_mp) > + other_resizer = Entities.resizer_sp > + elif p == "selfpath": > + resizers.append(Entities.resizer_sp) > + caps.append(Entities.cap_sp) > + other_resizer = Entities.resizer_mp > + else: > + logging.error("bad path value '{path}' only selfpath and mainpath are supported".format(path=path)) > + return > + > + if len(caps) == 0: > + logging.error("at least one path is needed") > + return > + > + rkisp1_prepare_test() > + if len(caps) == 1: > + rkisp1_disable_link(Entities.isp, IspPads.SOURCE_VIDEO, > + other_resizer, ResizerPads.SINK) > + > + sensor_fmt = None > + isp_sink_fmt = None > + isp_src_fmt = {} > + rsz_fmt = None > + > + pix0 = pixelformat[0] > + if pix0 in pix_mbus_rsz_map: > + isp_src_fmt["mbus"] = "YUYV8_2X8" > + elif pix0 in pix_mbus_bayer_map: > + isp_src_fmt["mbus"] = pix_mbus_bayer_map[pix0] > + else: > + logging.error("bad pixelformat: {p}".format(p=pix0)) > + return > + > + if isp_dim: > + isp_width = re.match("(\d+)x(\d+)", isp_dim).group(1) > + isp_height = re.match("(\d+)x(\d+)", isp_dim).group(2) > + sensor_fmt={"size" : tuple([isp_width,isp_height])} > + isp_sink_fmt={"crop" : tuple([0,0,isp_width,isp_height])} > + isp_src_fmt["crop"] = tuple([0,0,isp_width,isp_height]) > + > + rkisp1_propagate_sensor_fmt(src_fmt=sensor_fmt) > + rkisp1_propagate_isp_fmt(src_fmt=isp_src_fmt, sink_fmt=isp_sink_fmt) > + > + #we first loop the two paths to configure the whole topology > + for (resz_dim, pixfmt, cap, resz) in zip(resizer_dim, pixelformat, caps, resizers): > + > + pixfmts = get_pixelformats(cap) > + > + if pixfmt not in pixfmts: > + logging.info("given pixelformat {p} is not supported, possible values are:".format(p=pixfmt)) > + for p in pixfmts: > + logging.info(p) > + return > + > + rsz_width = re.match("(\d+)x(\d+)", resz_dim).group(1) > + rsz_height = re.match("(\d+)x(\d+)", resz_dim).group(2) > + rsz_fmt={"size" : tuple([rsz_width,rsz_height])} > + rsz_fmt["mbus"] = pix_mbus_map[pixfmt] > + > + rkisp1_propagate_resizer_fmt(resz, src_fmt=rsz_fmt) > + set_video_fmt(cap, {"pixelformat": pixfmt}) > + # after EVERYTHING is configured we start streaming > + n_frames = '5' if len(caps) > 1 else '1' > + handles = [] > + out_files = [] > + for cap in caps: > + out_file = set_file_name_for_stream(cap) > + handles.append(start_stream(cap, out_file=out_file, wait=(len(caps) == 1), n_frames=n_frames)) > + if out_file: > + out_files.append(out_file) > + > + # if we run both mainpath and selfpath together then wait both of them to finish. > + if (len(caps) > 1): > + for h in handles: > + h.wait() > + for (f,cap) in zip(out_files,caps): > + file_size = os.path.getsize(f) > + expected_size = get_expected_file_size(n_frames, cap) > + if (file_size != expected_size): > + raise Exception("file size is {fs}, expected size is {es}".format(fs=file_size, es=expected_size)) > + > + test_debugfs() > + > +def cam_streamer(pixelformat, width, height): > + > + n_frames = '10' > + cam_file="{out}/cam-{p}-{w}-{h}.raw".format(out=args.output, p=pixelformat, w=width, h=height) > + cam_cmd = ['cam','-c', '1', '--capture=' + n_frames, '-s', "pixelformat={p},width={w},height={h}".format(p=pixelformat,w=width,h=height)] > + > + if args.store: > + cam_file="{out}/cam-{p}-{w}-{h}.raw".format(out=args.output, p=pixelformat, w=width, h=height) > + cam_cmd = cam_cmd + ["--file={f}".format(f=cam_file)] > + logging.debug("will capture stream to file {f}".format(f=cam_file)) > + > + run(cam_cmd) > + test_debugfs() > + > +def automatic_tests(): > + fmts = ["YUYV", "422P","NV16","NV61","YM61","NV21","NV12","NM21","NM12","YU12","YV12","GREY" ] > + > + > + for fmt in fmts: > + logging.info("pixel {p}".format(p=fmt)) > + logging.info("mainpath") > + configure_and_stream(pixelformat=[fmt], path=["mainpath"], isp_dim="1920x1080", resizer_dim=["1900x1000"]) > + logging.info("selfpath") > + configure_and_stream(pixelformat=[fmt], path=["selfpath"], isp_dim="1620x1000", resizer_dim=["900x100"]) > + if args.cam and libcamera_dic[fmt]: > + logging.info("cam") > + cam_streamer(libcamera_dic[fmt], 1000, 1000) > + > + logging.info("Simultaneous streaming:") > + > + configure_and_stream(pixelformat=["YUYV","NV12"], path=["mainpath","selfpath"], isp_dim="1920x1080", resizer_dim=["1900x1000","800x1100"]) > + configure_and_stream(pixelformat=["YV12","NV61"], path=["mainpath","selfpath"], isp_dim="1920x1080", resizer_dim=["1900x500","800x700"]) > + > +if __name__ == "__main__": > + rformatter = argparse.RawDescriptionHelpFormatter > + parser = argparse.ArgumentParser(formatter_class=rformatter, description='''TL;DR: just run `python3 test-rkisp1.py`.\n > + This is a tests script for rkisp1 driver. There are two ways to run the script, either by using > + a hardcoded set of tests, or by giving parameters for a custom test (see 'customized test' options).\n > + To run a custom test, you should define all the parameters under the 'customized test' section. > + If both selfpath and mainpath are given in the '--path' option then the '--pixelformat' and '--resizer-dim' > + should also have two values, one for each path. > + For example: > + Configure selfpath to YUYV,640x480 and mainpath to NV12,800x600:\n > + python3 ./test-rkisp1.py -p selfpath mainpath -P YUYV NV12 --isp-dim 640x480 --resizer-dim 640x480 800x600 -S\n > + The script was developed for python version 3.7.6 and might not work with other versions. > + > + ''') > + parser.add_argument("-v", "--verbose", help="verbose output, logs are printed to stdout and to {outputdir}/log.txt", action="store_true") > + parser.add_argument("-s", "--sensor", help="sensor to use. If not given, then the first sensor found is used") > + parser.add_argument("-o", "--output", help="directory to add output streams. Default is current directory '.'", default=".") > + parser.add_argument("-S", "--store", help="store stream to output folder", action="store_true") > + parser.add_argument("-c", "--compliance", help="run compliance tests when streaming", action="store_true") > + parser.add_argument("-C", "--cam", help="also run some tests with the 'cam' command from libcamera", action="store_true") > + group = parser.add_argument_group('customized test', 'those options are for running specific tests of you own.') > + group.add_argument("-P", "--pixelformat", nargs="+", help="the pixelformat(s)") > + group.add_argument("-p", "--path", nargs="+", help="the stream path. Allowed values are 'selfpath', 'mainpath'") > + group.add_argument("--isp-dim", help="the {width}x{height} of the isp output.") > + group.add_argument("--resizer-dim", nargs="+", help="the {width}x{height} of the final image") > + > + args = parser.parse_args() > + > + if args.verbose: > + level = logging.DEBUG > + else: > + level = logging.INFO > + > + logfile = "{output_folder}/log.txt".format(output_folder=args.output) > + logging.basicConfig(level=level, filename=logfile, filemode='w') > + > + # define a Handler which writes INFO messages or higher to the sys.stderr > + console = logging.StreamHandler() > + console.setLevel(level) > + > + # add the handler to the root logger > + logging.getLogger('').addHandler(console) > + > + if not args.sensor: > + args.sensor = find_sensor() > + logging.info("Saving logs at " + logfile) > + logging.info("Testing with sensor " + args.sensor) > + logging.info("Output directory " + args.output) > + > + > + # now we parse all the options of the customized tests. > + # if all of them are set we run only the customized test > + # if none of them are set, we run our tests. > + # if only some of them are set we return error > + if (args.pixelformat and args.path and args.isp_dim and args.resizer_dim): > + configure_and_stream(pixelformat=args.pixelformat, path=args.path, isp_dim=args.isp_dim, resizer_dim=args.resizer_dim) > + exit(0) > + elif (args.pixelformat or args.path or args.isp_dim or args.resizer_dim): > + logging.error("For customized test all customized test options should be given") > + exit(1) > + > + automatic_tests() > diff --git a/contrib/test/v4l2lib.py b/contrib/test/v4l2lib.py > new file mode 100644 > index 00000000..fdfbf790 > --- /dev/null > +++ b/contrib/test/v4l2lib.py > @@ -0,0 +1,90 @@ > +import enum > + > +class FmtTypes(enum.IntEnum): > + BAYER = 0 > + YUV = 1 > + RGB = 2 > + > +CAP_FORMAT_TYPES = { > + "YUYV": FmtTypes.YUV, # (YUYV 4:2:2) > + "YVYU": FmtTypes.YUV, # (YVYU 4:2:2) > + "VYUY": FmtTypes.YUV, # (VYUY 4:2:2) > + "422P": FmtTypes.YUV, # (Planar YUV 4:2:2) > + "NV16": FmtTypes.YUV, # (Y/CbCr 4:2:2) > + "NV61": FmtTypes.YUV, # (Y/CrCb 4:2:2) > + "YM61": FmtTypes.YUV, # (Planar YVU 4:2:2 (N-C)) > + "NV21": FmtTypes.YUV, # (Y/CrCb 4:2:0) > + "NV12": FmtTypes.YUV, # (Y/CbCr 4:2:0) > + "NM21": FmtTypes.YUV, # (Y/CrCb 4:2:0 (N-C)) > + "NM12": FmtTypes.YUV, # (Y/CbCr 4:2:0 (N-C)) > + "YU12": FmtTypes.YUV, # (Planar YUV 4:2:0) > + "YV12": FmtTypes.YUV, # (Planar YVU 4:2:0) > + "YM24": FmtTypes.YUV, # (Planar YUV 4:4:4 (N-C)) > + "GREY": FmtTypes.YUV, # (8-bit Greyscale) > + "RGGB": FmtTypes.BAYER, # (8-bit Bayer RGRG/GBGB) > + "GRBG": FmtTypes.BAYER, # (8-bit Bayer GRGR/BGBG) > + "GBRG": FmtTypes.BAYER, # (8-bit Bayer GBGB/RGRG) > + "BA81": FmtTypes.BAYER, # (8-bit Bayer BGBG/GRGR) > + "RG10": FmtTypes.BAYER, # (10-bit Bayer RGRG/GBGB) > + "BA10": FmtTypes.BAYER, # (10-bit Bayer GRGR/BGBG) > + "GB10": FmtTypes.BAYER, # (10-bit Bayer GBGB/RGRG) > + "BG10": FmtTypes.BAYER, # (10-bit Bayer BGBG/GRGR) > + "RG12": FmtTypes.BAYER, # (12-bit Bayer RGRG/GBGB) > + "BA12": FmtTypes.BAYER, # (12-bit Bayer GRGR/BGBG) > + "GB12": FmtTypes.BAYER, # (12-bit Bayer GBGB/RGRG) > + "BG12": FmtTypes.BAYER, # (12-bit Bayer BGBG/GRGR) > + "BGRH" : FmtTypes.RGB, # (18 BGR-6-6-6), V4L2_PIX_FMT_BGR666 > + "RGBP" : FmtTypes.RGB, # (16 RGB-5-6-5), V4L2_PIX_FMT_RGB565 > + "RGB3" : FmtTypes.RGB, # (24 RGB-8-8-8), V4L2_PIX_FMT_RGB24 > +} > + > +ISP_FORMAT_TYPES = { > + "YUYV8_2X8": FmtTypes.YUV, > + "SRGGB10_1X10": FmtTypes.BAYER, > + "SBGGR10_1X10": FmtTypes.BAYER, > + "SGBRG10_1X10": FmtTypes.BAYER, > + "SGRBG10_1X10": FmtTypes.BAYER, > + "SRGGB12_1X12": FmtTypes.BAYER, > + "SBGGR12_1X12": FmtTypes.BAYER, > + "SGBRG12_1X12": FmtTypes.BAYER, > + "SGRBG12_1X12": FmtTypes.BAYER, > + "SRGGB8_1X8": FmtTypes.BAYER, > + "SBGGR8_1X8": FmtTypes.BAYER, > + "SGBRG8_1X8": FmtTypes.BAYER, > + "SGRBG8_1X8": FmtTypes.BAYER, > + "YUYV8_1X16": FmtTypes.YUV, > + "YVYU8_1X16": FmtTypes.YUV, > + "UYVY8_1X16": FmtTypes.YUV, > + "VYUY8_1X16": FmtTypes.YUV, > +} > + > +V4L2_PIX_MUL = { > + "YUYV": 2, > + "422P": 2, > + "NV16": 2, > + "NV61": 2, > + "YM61": 2, > + "NV21": 3, > + "NV12": 3, > + "NM21": 3, > + "NM12": 3, > + "YU12": 3, > + "YV12": 3, > + "GREY": 1 > +} > + > +V4L2_PIX_DIV = { > + "YUYV": 1, > + "422P": 1, > + "NV16": 1, > + "NV61": 1, > + "YM61": 1, > + "NV21": 2, > + "NV12": 2, > + "NM21": 2, > + "NM12": 2, > + "YU12": 2, > + "YV12": 2, > + "GREY": 1 > +} > + >