[PATCH v4l-utils 1/1] test-media: Add a python script for rkisp1 tests
Dafna Hirschfeld
dafna.hirschfeld at collabora.com
Tue Nov 10 06:35:22 EST 2020
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 at collabora.com>
---
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
+}
+
--
2.17.1
More information about the Linux-rockchip
mailing list