[PATCH v4l-utils 1/1] test-media: Add a python script for rkisp1 tests
Helen Koike
helen.koike at collabora.com
Thu Nov 12 08:19:09 EST 2020
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 at collabora.com>
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
> +}
> +
>
More information about the Linux-rockchip
mailing list