linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC 0/2] v4l2 stateless tracer/retracer utilities
@ 2022-08-20  0:50 Deborah Brouwer
  2022-08-20  0:50 ` [RFC 1/2] utils: add stateless tracer utility Deborah Brouwer
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Deborah Brouwer @ 2022-08-20  0:50 UTC (permalink / raw)
  To: linux-media
  Cc: daniel.almeida, nfraprado, nicolas.dufresne, hverkuil-cisco,
	Deborah Brouwer

This project helps to test v4l2 stateless decoder drivers by tracing,
recording and replaying (i.e. "retracing") userspace's interaction with
a stateless decoder driver.

The tracer utility attaches to a userspace application and generates a
json file with relevant system calls, parameters and encoded data.

The retracer utility reads the json-file and makes the same system calls
to a v4l2 stateless driver. Since the retracer is independent from the
original userspace application that was traced, testing can be decoupled
from extraneous factors in the userspace environment. The json-file can
also be edited to inject errors and test a driver's error-handling
abilities.

NOTE:
This project is work in progress and currently only traces VP8, but
H264 and FWHT will follow shortly.

EXAMPLE:
./tracer gst-launch-1.0 -- filesrc location=<some_vp8_file> ! parsebin !
v4l2slvp8dec ! videocodectestsink

./retracer 10284_trace.json

FURTHER INFO AND TEST FILES:
https://gitlab.collabora.com/dbrouwer/v4l2-stateless-tracer-utility/-/tree/main/

Deborah Brouwer (2):
  utils: add stateless tracer utility
  utils: add stateless retracer utility

 configure.ac                    |    6 +
 utils/Makefile.am               |    5 +
 utils/common/v4l2-info.cpp      |    7 +-
 utils/common/v4l2-info.h        |    8 +
 utils/tracer/.gitignore         |    9 +
 utils/tracer/Makefile.am        |   23 +
 utils/tracer/libtracer.cpp      |  217 ++++++
 utils/tracer/libtracer.h        |   92 +++
 utils/tracer/retrace-helper.cpp |  141 ++++
 utils/tracer/retrace-helper.h   |   18 +
 utils/tracer/retrace-vp8.cpp    |  288 ++++++++
 utils/tracer/retrace-vp8.h      |   11 +
 utils/tracer/retracer.cpp       | 1090 +++++++++++++++++++++++++++++++
 utils/tracer/retracer.h         |   24 +
 utils/tracer/trace-helper.cpp   |  218 +++++++
 utils/tracer/trace-info.cpp     |  358 ++++++++++
 utils/tracer/trace-info.h       |   72 ++
 utils/tracer/trace-vp8.cpp      |  183 ++++++
 utils/tracer/trace.cpp          |  520 +++++++++++++++
 utils/tracer/tracer.cpp         |   91 +++
 20 files changed, 3375 insertions(+), 6 deletions(-)
 create mode 100644 utils/tracer/.gitignore
 create mode 100644 utils/tracer/Makefile.am
 create mode 100644 utils/tracer/libtracer.cpp
 create mode 100644 utils/tracer/libtracer.h
 create mode 100755 utils/tracer/retrace-helper.cpp
 create mode 100644 utils/tracer/retrace-helper.h
 create mode 100755 utils/tracer/retrace-vp8.cpp
 create mode 100644 utils/tracer/retrace-vp8.h
 create mode 100755 utils/tracer/retracer.cpp
 create mode 100644 utils/tracer/retracer.h
 create mode 100644 utils/tracer/trace-helper.cpp
 create mode 100644 utils/tracer/trace-info.cpp
 create mode 100644 utils/tracer/trace-info.h
 create mode 100644 utils/tracer/trace-vp8.cpp
 create mode 100644 utils/tracer/trace.cpp
 create mode 100644 utils/tracer/tracer.cpp

-- 
2.37.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC 1/2] utils: add stateless tracer utility
  2022-08-20  0:50 [RFC 0/2] v4l2 stateless tracer/retracer utilities Deborah Brouwer
@ 2022-08-20  0:50 ` Deborah Brouwer
  2022-08-26 19:18   ` Nicolas Dufresne
  2022-08-20  0:50 ` [RFC 2/2] utils: add stateless retracer utility Deborah Brouwer
  2022-08-26 19:05 ` [RFC 0/2] v4l2 stateless tracer/retracer utilities Nicolas Dufresne
  2 siblings, 1 reply; 5+ messages in thread
From: Deborah Brouwer @ 2022-08-20  0:50 UTC (permalink / raw)
  To: linux-media
  Cc: daniel.almeida, nfraprado, nicolas.dufresne, hverkuil-cisco,
	Deborah Brouwer

The tracer utility helps to test v4l2 stateless decoder drivers by tracing
and recording userspace's interaction with a stateless decoder driver.
Only system calls relevant to the v4l2 memory-to-memory stateless video
decoder interface are traced. The tracer traces encoded data from output
buffers and, optionally, may trace decoded data from capture buffers. The
tracer records its results in a json-formatted trace file.

Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
---
 configure.ac                  |   6 +
 utils/Makefile.am             |   5 +
 utils/common/v4l2-info.cpp    |   7 +-
 utils/common/v4l2-info.h      |   8 +
 utils/tracer/.gitignore       |   9 +
 utils/tracer/Makefile.am      |  19 ++
 utils/tracer/libtracer.cpp    | 217 ++++++++++++++
 utils/tracer/libtracer.h      |  92 ++++++
 utils/tracer/trace-helper.cpp | 218 ++++++++++++++
 utils/tracer/trace-info.cpp   | 358 +++++++++++++++++++++++
 utils/tracer/trace-info.h     |  72 +++++
 utils/tracer/trace-vp8.cpp    | 183 ++++++++++++
 utils/tracer/trace.cpp        | 520 ++++++++++++++++++++++++++++++++++
 utils/tracer/tracer.cpp       |  91 ++++++
 14 files changed, 1799 insertions(+), 6 deletions(-)
 create mode 100644 utils/tracer/.gitignore
 create mode 100644 utils/tracer/Makefile.am
 create mode 100644 utils/tracer/libtracer.cpp
 create mode 100644 utils/tracer/libtracer.h
 create mode 100644 utils/tracer/trace-helper.cpp
 create mode 100644 utils/tracer/trace-info.cpp
 create mode 100644 utils/tracer/trace-info.h
 create mode 100644 utils/tracer/trace-vp8.cpp
 create mode 100644 utils/tracer/trace.cpp
 create mode 100644 utils/tracer/tracer.cpp

diff --git a/configure.ac b/configure.ac
index 05298981..cc604cad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,6 +42,7 @@ AC_CONFIG_FILES([Makefile
 	utils/cec-follower/cec-follower.1
 	utils/qvidcap/Makefile
 	utils/rds-ctl/Makefile
+	utils/tracer/Makefile
 
 	contrib/Makefile
 	contrib/freebsd/Makefile
@@ -311,6 +312,11 @@ AS_IF([test "x$with_libudev" != xno -o "x$enable_libdvbv5" != xno],
 
 AC_SUBST([JPEG_LIBS])
 
+PKG_CHECK_MODULES(JSONC, [json-c >= 0.16], [jsonc_pkgconfig=yes], [jsonc_pkgconfig=no])
+AC_SUBST([JSONC_CFLAGS])
+AC_SUBST([JSONC_LIBS])
+AM_CONDITIONAL([HAVE_JSONC], [test x$jsonc_pkgconfig = xyes])
+
 # Check for pthread
 
 AS_IF([test x$enable_shared != xno],
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 0e68a612..3ccc3d81 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -15,6 +15,11 @@ SUBDIRS = \
 	cec-follower \
 	rds-ctl
 
+if HAVE_JSONC
+SUBDIRS += \
+	tracer
+endif
+
 if WITH_LIBDVBV5
 SUBDIRS += \
 	dvb
diff --git a/utils/common/v4l2-info.cpp b/utils/common/v4l2-info.cpp
index 40d45471..74e77b7a 100644
--- a/utils/common/v4l2-info.cpp
+++ b/utils/common/v4l2-info.cpp
@@ -16,12 +16,7 @@ static std::string num2s(unsigned num, bool is_hex = true)
 	return buf;
 }
 
-struct flag_def {
-	unsigned flag;
-	const char *str;
-};
-
-static std::string flags2s(unsigned val, const flag_def *def)
+std::string flags2s(unsigned int val, const flag_def *def)
 {
 	std::string s;
 
diff --git a/utils/common/v4l2-info.h b/utils/common/v4l2-info.h
index 35237853..8eb14bc4 100644
--- a/utils/common/v4l2-info.h
+++ b/utils/common/v4l2-info.h
@@ -11,6 +11,14 @@
 #include <linux/videodev2.h>
 #include <linux/v4l2-subdev.h>
 
+struct flag_def {
+	unsigned int flag;
+	const char *str;
+};
+
+/* Return a comma-separated string of flags or hex value if unknown */
+std::string flags2s(unsigned int val, const flag_def *def);
+
 /* Print capability information */
 void v4l2_info_capability(const v4l2_capability &cap);
 void v4l2_info_subdev_capability(const v4l2_subdev_capability &subdevcap);
diff --git a/utils/tracer/.gitignore b/utils/tracer/.gitignore
new file mode 100644
index 00000000..4098662d
--- /dev/null
+++ b/utils/tracer/.gitignore
@@ -0,0 +1,9 @@
+*.json
+*.yuv
+*.webm
+*.h264
+*.ivf
+*.vp8
+tracer
+retracer
+.clang-tidy
diff --git a/utils/tracer/Makefile.am b/utils/tracer/Makefile.am
new file mode 100644
index 00000000..f5579198
--- /dev/null
+++ b/utils/tracer/Makefile.am
@@ -0,0 +1,19 @@
+if HAVE_JSONC
+
+lib_LTLIBRARIES = libtracer.la
+include_HEADERS = libtracer.h ../../utils/common/v4l2-info.h ../../utils/common/media-info.h
+libtracer_la_SOURCES = libtracer.cpp trace.cpp trace-vp8.cpp trace-helper.cpp \
+trace-info.cpp ../../utils/common/v4l2-info.cpp ../../utils/common/media-info.cpp
+
+libtracer_la_CFLAGS = $(JSONC_CFLAGS)
+libtracer_la_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
+libtracer_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -ldl $(JSONC_LIBS)
+libtracer_la_LIBTOOLFLAGS = --tag=disable-static
+
+bin_PROGRAMS = tracer
+
+tracer_SOURCES = tracer.cpp
+tracer_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
+tracer_LDFLAGS = -lrt -lpthread $(JSONC_LIBS)
+
+endif
diff --git a/utils/tracer/libtracer.cpp b/utils/tracer/libtracer.cpp
new file mode 100644
index 00000000..0d18b224
--- /dev/null
+++ b/utils/tracer/libtracer.cpp
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "libtracer.h"
+
+int open64(const char *path, int oflag, ...)
+{
+	errno = 0;
+	mode_t mode = 0;
+
+	if (oflag & O_CREAT) {
+		va_list ap;
+		va_start(ap, oflag);
+		mode = va_arg(ap, PROMOTED_MODE_T);
+		va_end(ap);
+	}
+
+	int (*original_open64)(const char *path, int oflag, ...);
+	original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64");
+	int fd = (*original_open64)(path, oflag, mode);
+
+	/* Only trace calls to video or media devices. */
+	std::string dev_path_video = "/dev/video";
+	std::string dev_path_media = "/dev/media";
+	if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) &&
+	    strncmp(path, dev_path_media.c_str(), dev_path_media.length()))
+		return fd;
+
+	/* Set trace options if this is the first call to libtracer. */
+	if (!options_are_set())
+		set_options();
+
+	add_device(fd, path);
+
+	json_object *open_obj = json_object_new_object();
+	json_object_object_add(open_obj, "syscall_str", json_object_new_string("open64"));
+	json_object_object_add(open_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_OPEN64));
+	json_object_object_add(open_obj, "fd", json_object_new_int(fd));
+	json_object *open_args = trace_open(path, oflag, mode);
+	json_object_object_add(open_obj, "open_args", open_args);
+
+	write_json_object_to_json_file(open_obj);
+	json_object_put(open_obj);
+
+	return fd;
+}
+
+void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
+{
+	errno = 0;
+
+	void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
+	original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64");
+	void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off);
+
+	/* Only trace mmap64 calls for output or capture buffers. */
+	__u32 type = get_buffer_type_trace(fildes);
+	if (!type)
+		return buf_address_pointer;
+
+	/* Save the buffer address for future reference. */
+	set_buffer_address_trace(fildes, (long int) buf_address_pointer);
+
+	json_object *mmap_obj = json_object_new_object();
+	if (errno) {
+		json_object_object_add(mmap_obj, "errno", json_object_new_int(errno));
+		json_object_object_add(mmap_obj, "errno_str", json_object_new_string(strerror(errno)));
+	}
+	json_object_object_add(mmap_obj, "syscall_str", json_object_new_string("mmap64"));
+	json_object_object_add(mmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MMAP64));
+
+	json_object *mmap64_args = trace_mmap64(addr, len, prot, flags, fildes, off);
+	json_object_object_add(mmap_obj, "mmap64_args", mmap64_args);
+
+	json_object_object_add(mmap_obj, "buffer_address", json_object_new_int64((long int) buf_address_pointer));
+	write_json_object_to_json_file(mmap_obj);
+	json_object_put(mmap_obj);
+
+	/*
+	 * The capture buffer is traced for the first time here when the buffer is first mapped.
+	 * Subsequently, the capture buffer will be traced when VIDIOC_DQBUF is called.
+	 */
+	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		trace_mem(fildes);
+
+	return buf_address_pointer;
+}
+
+int munmap(void *start, size_t length)
+{
+	errno = 0;
+
+	int(*original_munmap)(void *start, size_t length);
+	original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap");
+	int ret = (*original_munmap)(start, length);
+
+	/* Only trace the unmapping if the original mapping was traced. */
+	if (!buffer_is_mapped((long int) start))
+		return ret;
+
+	json_object *munmap_obj = json_object_new_object();
+	json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap"));
+	json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP));
+
+	if (errno) {
+		json_object_object_add(munmap_obj, "errno", json_object_new_int(errno));
+		json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno)));
+	}
+
+	json_object *munmap_args = json_object_new_object();
+	json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start));
+	json_object_object_add(munmap_args, "length", json_object_new_uint64(length));
+	json_object_object_add(munmap_obj, "munmap_args", munmap_args);
+
+	write_json_object_to_json_file(munmap_obj);
+	json_object_put(munmap_obj);
+
+	return ret;
+}
+
+int ioctl(int fd, unsigned long int request, ...)
+{
+	errno = 0;
+
+	va_list ap;
+	va_start(ap, request);
+	void *arg = va_arg(ap, void *);
+	va_end(ap);
+
+	int (*original_ioctl)(int fd, unsigned long int request, ...);
+	original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl");
+
+	/* If the ioctl is queuing an output buffer, trace the output buffer before tracing the ioctl. */
+	if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) {
+		struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
+		if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+			int buf_fd = get_buffer_fd_trace(buf->type, buf->index);
+			if (buf_fd) {
+				set_buffer_bytesused_trace(buf_fd, buf->m.planes[0].bytesused);
+				trace_mem(buf_fd);
+			}
+		}
+	}
+
+	json_object *ioctl_obj = json_object_new_object();
+	json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl"));
+	json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL));
+	json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd));
+	json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request));
+	json_object_object_add(ioctl_obj, "request_str", json_object_new_string(get_ioctl_request_str(request).c_str()));
+
+	/* Trace the ioctl arguments provided by userspace if relevant. */
+	json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg);
+	if (json_object_object_length(ioctl_args_userspace))
+		json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace);
+
+	/* Make the original ioctl call. */
+	int ret = (*original_ioctl)(fd, request, arg);
+
+	if (errno) {
+		json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno));
+		json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno)));
+	}
+
+	/* Also trace the ioctl arguments as modified by the driver if relevant. */
+	json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false);
+	if (json_object_object_length(ioctl_args_driver))
+		json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver);
+
+	write_json_object_to_json_file(ioctl_obj);
+	json_object_put(ioctl_obj);
+
+	/* If the ioctl is exporting a buffer, store the buffer file descriptor and index for future access. */
+	if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) {
+		struct v4l2_exportbuffer *export_buffer = static_cast<struct v4l2_exportbuffer*>(arg);
+		add_buffer_trace(export_buffer->fd, export_buffer->type, export_buffer->index);
+	}
+
+	/* If the ioctl is dequeuing a capture buffer, trace the buffer. */
+	if ((request == VIDIOC_DQBUF) && (_IOC_TYPE(request) == 'V')) {
+		struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
+		if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+			int buf_fd = get_buffer_fd_trace(buf->type, buf->index);
+			if (buf_fd) {
+				/* TODO tracer only works for decoded formats with one plane e.g. V4L2_PIX_FMT_NV12 */
+				set_buffer_bytesused_trace(buf_fd, buf->m.planes[0].bytesused);
+				trace_mem(buf_fd);
+			}
+		}
+	}
+
+	return ret;
+}
+
+int close(int fd)
+{
+	std::string path = get_device(fd);
+
+	/* Only trace the close if a corresponding open was also traced. */
+	if (!path.empty()) {
+		json_object *close_obj = json_object_new_object();
+		json_object_object_add(close_obj, "syscall_str", json_object_new_string("close"));
+		json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE));
+		json_object_object_add(close_obj, "fd", json_object_new_int(fd));
+		json_object_object_add(close_obj, "path", json_object_new_string(path.c_str()));
+		write_json_object_to_json_file(close_obj);
+		json_object_put(close_obj);
+		remove_device(fd);
+	}
+
+	int (*original_close)(int fd);
+	original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
+
+	return (*original_close)(fd);
+}
diff --git a/utils/tracer/libtracer.h b/utils/tracer/libtracer.h
new file mode 100644
index 00000000..5f86aadf
--- /dev/null
+++ b/utils/tracer/libtracer.h
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#ifndef LIBTRACER_H
+#define LIBTRACER_H
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <config.h>
+#include <pthread.h>
+#include <unistd.h> /* needed for close */
+#include <linux/dma-buf.h>
+#include <json-c/json.h>
+#include <v4l2-info.h>
+#include <unordered_map>
+#include <list>
+#include "trace-info.h"
+
+struct trace_options {
+	bool set;
+	bool pretty_print_all;
+	bool pretty_print_mem;
+	bool decoded_data_to_json;
+	bool create_yuv_file;
+};
+
+struct buffer_trace {
+	int fd;
+	__u32 type; /* enum v4l2_buf_type */
+	__u32 index;
+	long address;
+	__u32 bytesused;
+};
+
+struct trace_context {
+	FILE *trace_file;
+	std::string trace_filename;
+	pthread_mutex_t lock;
+	/* Key is the file descriptor, value is the path of the video or media device. */
+	std::unordered_map<int, std::string> devices;
+	/* List of output and capture buffers being traced. */
+	std::list<struct buffer_trace> buffers;
+};
+
+bool options_are_set(void);
+void set_options(void);
+bool pretty_print_mem(void);
+bool pretty_print_all(void);
+bool write_decoded_data_to_json(void);
+bool create_yuv_file(void);
+
+void add_device(int fd, std::string path);
+std::string get_device(int fd);
+int remove_device(int fd);
+void add_buffer_trace(int fd, __u32 type, __u32 index);
+int get_buffer_fd_trace(__u32 type, __u32 index);
+__u32 get_buffer_type_trace(int fd);
+int get_buffer_index_trace(int fd);
+void set_buffer_address_trace(int fd, long address);
+long get_buffer_address_trace(int fd);
+void set_buffer_bytesused_trace(int fd, __u32 bytesused);
+long get_buffer_bytesused_trace(int fd);
+bool buffer_is_mapped(long buffer_address);
+void write_json_object_to_json_file(json_object *jobj, int flags = JSON_C_TO_STRING_PLAIN);
+
+json_object *trace_open(const char *path, int oflag, mode_t mode);
+json_object *trace_mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
+void trace_mem(int fd);
+std::string get_ioctl_request_str(unsigned long request);
+json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, bool from_userspace = true);
+
+void trace_v4l2_ctrl_h264_decode_mode(struct v4l2_ext_control ctrl, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_start_code(struct v4l2_ext_control ctrl, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_sps(void *p_h264_sps, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_pps(void *p_h264_pps, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_scaling_matrix(void *p_h264_scaling_matrix, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_pred_weights(void *p_h264_pred_weights, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_slice_params(void *p_h264_slice_params, json_object *ctrl_obj);
+void trace_v4l2_ctrl_h264_decode_params(void *p_h264_decode_params, json_object *ctrl_obj);
+
+void trace_v4l2_ctrl_vp8_frame(void *p_vp8_frame, json_object *ctrl_obj);
+
+void trace_v4l2_ctrl_fwht_params(void *p_fwht_params, json_object *ctrl_obj);
+
+#endif
diff --git a/utils/tracer/trace-helper.cpp b/utils/tracer/trace-helper.cpp
new file mode 100644
index 00000000..402c602b
--- /dev/null
+++ b/utils/tracer/trace-helper.cpp
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "libtracer.h"
+
+struct trace_options options;
+
+struct trace_context ctx_trace = {
+	.lock = PTHREAD_MUTEX_INITIALIZER
+};
+
+bool options_are_set(void)
+{
+	return options.set;
+}
+
+void set_options(void)
+{
+	options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false;
+	options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false;
+	options.decoded_data_to_json = getenv("TRACE_OPTION_DECODED_TO_JSON") ? true : false;
+	options.create_yuv_file = getenv("TRACE_OPTION_CREATE_YUV_FILE") ? true : false;
+	options.set = true;
+}
+
+bool pretty_print_mem(void)
+{
+	return options.pretty_print_mem;
+}
+
+bool pretty_print_all(void)
+{
+	return options.pretty_print_all;
+}
+
+bool write_decoded_data_to_json(void)
+{
+	return options.decoded_data_to_json;
+}
+
+bool create_yuv_file(void)
+{
+	return options.create_yuv_file;
+}
+
+void add_device(int fd, std::string path)
+{
+	std::pair<int, std::string> new_pair = std::make_pair(fd, path);
+	pthread_mutex_lock(&ctx_trace.lock);
+	ctx_trace.devices.insert(new_pair);
+	pthread_mutex_unlock(&ctx_trace.lock);
+}
+
+std::string get_device(int fd)
+{
+	std::string path;
+	std::unordered_map<int, std::string>::const_iterator it;
+	pthread_mutex_lock(&ctx_trace.lock);
+	it = ctx_trace.devices.find(fd);
+	if (it != ctx_trace.devices.end())
+		path = it->second;
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return path;
+}
+
+int remove_device(int fd)
+{
+	int ret = 0;
+	pthread_mutex_lock(&ctx_trace.lock);
+	ret = ctx_trace.devices.erase(fd);
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return ret;
+}
+
+void add_buffer_trace(int fd, __u32 type, __u32 index)
+{
+	struct buffer_trace buf;
+	memset(&buf, 0, sizeof(buffer_trace));
+	buf.fd = fd;
+	buf.type = type;
+	buf.index = index;
+	pthread_mutex_lock(&ctx_trace.lock);
+	ctx_trace.buffers.push_front(buf);
+	pthread_mutex_unlock(&ctx_trace.lock);
+}
+
+int get_buffer_fd_trace(__u32 type, __u32 index)
+{
+	int fd = 0;
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if ((it->type == type) && (it->index == index)) {
+			fd = it->fd;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return fd;
+}
+
+__u32 get_buffer_type_trace(int fd)
+{
+	__u32 ret = 0;
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			ret = it->type;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return ret;
+}
+
+int get_buffer_index_trace(int fd)
+{
+	int index = -1;
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			index = it->index;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return index;
+}
+
+void set_buffer_address_trace(int fd, long address)
+{
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			it->address = address;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+}
+
+long get_buffer_address_trace(int fd)
+{
+	long int address = 0;
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			address = it->address;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return address;
+}
+
+void set_buffer_bytesused_trace(int fd, __u32 bytesused)
+{
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			it->bytesused = bytesused;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+}
+
+long get_buffer_bytesused_trace(int fd)
+{
+	long int bytesused = -1;
+
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			bytesused = it->bytesused;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+
+	return bytesused;
+}
+
+/* Returns true if the memory address is a mapped buffer address, false otherwise. */
+bool buffer_is_mapped(long buffer_address)
+{
+	bool ret = false;
+	pthread_mutex_lock(&ctx_trace.lock);
+	for (auto it = ctx_trace.buffers.cbegin(); it != ctx_trace.buffers.cend(); ++it) {
+		if (it->address == buffer_address) {
+			ret = true;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_trace.lock);
+	return ret;
+}
+
+void write_json_object_to_json_file(json_object *jobj, int flags)
+{
+	if (pretty_print_all())
+		flags = JSON_C_TO_STRING_PRETTY;
+
+	std::string json_str = json_object_to_json_string_ext(jobj, flags);
+	pthread_mutex_lock(&ctx_trace.lock);
+
+	if (ctx_trace.trace_filename.empty()) {
+		ctx_trace.trace_filename = getenv("TRACE_ID");
+		ctx_trace.trace_filename += ".json";
+		ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a");
+	}
+
+	fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file);
+	fputs(",\n", ctx_trace.trace_file);
+	fflush(ctx_trace.trace_file);
+	pthread_mutex_unlock(&ctx_trace.lock);
+}
diff --git a/utils/tracer/trace-info.cpp b/utils/tracer/trace-info.cpp
new file mode 100644
index 00000000..a1d58c64
--- /dev/null
+++ b/utils/tracer/trace-info.cpp
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include <v4l2-info.h>
+#include "trace-info.h"
+
+struct definition {
+	unsigned long val;
+	const char *str;
+};
+
+static std::string val2s(unsigned long val, const definition *def)
+{
+	std::string s;
+
+	if (val == 0)
+		return s;
+
+	while ((def->val) && (def->val != val))
+		def++;
+
+	if (def->val == val)
+		s = def->str;
+
+	return s;
+}
+
+std::string ioctl2s_video(unsigned long request)
+{
+	static constexpr definition defs[] = {
+		{ VIDIOC_QUERYCAP,    "VIDIOC_QUERYCAP" },
+		{ VIDIOC_ENUM_FMT,    "VIDIOC_ENUM_FMT" },
+		{ VIDIOC_G_FMT,    "VIDIOC_G_FMT" },
+		{ VIDIOC_S_FMT,    "VIDIOC_S_FMT" },
+		{ VIDIOC_REQBUFS,    "VIDIOC_REQBUFS" },
+		{ VIDIOC_QUERYBUF,    "VIDIOC_QUERYBUF" },
+		{ VIDIOC_G_FBUF,    "VIDIOC_G_FBUF" },
+		{ VIDIOC_S_FBUF,    "VIDIOC_S_FBUF" },
+		{ VIDIOC_OVERLAY,    "VIDIOC_OVERLAY" },
+		{ VIDIOC_QBUF,    "VIDIOC_QBUF" },
+		{ VIDIOC_EXPBUF,    "VIDIOC_EXPBUF" },
+		{ VIDIOC_DQBUF,    "VIDIOC_DQBUF" },
+		{ VIDIOC_STREAMON,    "VIDIOC_STREAMON" },
+		{ VIDIOC_STREAMOFF,    "VIDIOC_STREAMOFF" },
+		{ VIDIOC_G_PARM,    "VIDIOC_G_PARM" },
+		{ VIDIOC_S_PARM,    "VIDIOC_S_PARM" },
+		{ VIDIOC_G_STD,    "VIDIOC_G_STD" },
+		{ VIDIOC_S_STD,    "VIDIOC_S_STD" },
+		{ VIDIOC_ENUMSTD,    "VIDIOC_ENUMSTD" },
+		{ VIDIOC_ENUMINPUT,    "VIDIOC_ENUMINPUT" },
+		{ VIDIOC_G_CTRL,    "VIDIOC_G_CTRL" },
+		{ VIDIOC_S_CTRL,    "VIDIOC_S_CTRL" },
+		{ VIDIOC_G_TUNER,    "VIDIOC_G_TUNER" },
+		{ VIDIOC_S_TUNER,    "VIDIOC_S_TUNER" },
+		{ VIDIOC_G_AUDIO,    "VIDIOC_G_AUDIO" },
+		{ VIDIOC_S_AUDIO,    "VIDIOC_S_AUDIO" },
+		{ VIDIOC_QUERYCTRL,    "VIDIOC_QUERYCTRL" },
+		{ VIDIOC_QUERYMENU,    "VIDIOC_QUERYMENU" },
+		{ VIDIOC_G_INPUT,    "VIDIOC_G_INPUT" },
+		{ VIDIOC_S_INPUT,    "VIDIOC_S_INPUT" },
+		{ VIDIOC_G_EDID,    "VIDIOC_G_EDID" },
+		{ VIDIOC_S_EDID,    "VIDIOC_S_EDID" },
+		{ VIDIOC_G_OUTPUT,    "VIDIOC_G_OUTPUT" },
+		{ VIDIOC_S_OUTPUT,    "VIDIOC_S_OUTPUT" },
+		{ VIDIOC_ENUMOUTPUT,    "VIDIOC_ENUMOUTPUT" },
+		{ VIDIOC_G_AUDOUT,    "VIDIOC_G_AUDOUT" },
+		{ VIDIOC_S_AUDOUT,    "VIDIOC_S_AUDOUT" },
+		{ VIDIOC_G_MODULATOR,    "VIDIOC_G_MODULATOR" },
+		{ VIDIOC_S_MODULATOR,    "VIDIOC_S_MODULATOR" },
+		{ VIDIOC_G_FREQUENCY,    "VIDIOC_G_FREQUENCY" },
+		{ VIDIOC_S_FREQUENCY,    "VIDIOC_S_FREQUENCY" },
+		{ VIDIOC_CROPCAP,    "VIDIOC_CROPCAP" },
+		{ VIDIOC_G_CROP,    "VIDIOC_G_CROP" },
+		{ VIDIOC_S_CROP,    "VIDIOC_S_CROP" },
+		{ VIDIOC_G_JPEGCOMP,    "VIDIOC_G_JPEGCOMP" },
+		{ VIDIOC_S_JPEGCOMP,    "VIDIOC_S_JPEGCOMP" },
+		{ VIDIOC_QUERYSTD,    "VIDIOC_QUERYSTD" },
+		{ VIDIOC_TRY_FMT,    "VIDIOC_TRY_FMT" },
+		{ VIDIOC_ENUMAUDIO,    "VIDIOC_ENUMAUDIO" },
+		{ VIDIOC_ENUMAUDOUT,    "VIDIOC_ENUMAUDOUT" },
+		{ VIDIOC_G_PRIORITY,    "VIDIOC_G_PRIORITY" },
+		{ VIDIOC_S_PRIORITY,    "VIDIOC_S_PRIORITY" },
+		{ VIDIOC_G_SLICED_VBI_CAP,    "VIDIOC_G_SLICED_VBI_CAP" },
+		{ VIDIOC_LOG_STATUS,    "VIDIOC_LOG_STATUS" },
+		{ VIDIOC_G_EXT_CTRLS,    "VIDIOC_G_EXT_CTRLS" },
+		{ VIDIOC_S_EXT_CTRLS,    "VIDIOC_S_EXT_CTRLS" },
+		{ VIDIOC_TRY_EXT_CTRLS,    "VIDIOC_TRY_EXT_CTRLS" },
+		{ VIDIOC_ENUM_FRAMESIZES,    "VIDIOC_ENUM_FRAMESIZES" },
+		{ VIDIOC_ENUM_FRAMEINTERVALS,    "VIDIOC_ENUM_FRAMEINTERVALS" },
+		{ VIDIOC_G_ENC_INDEX,    "VIDIOC_G_ENC_INDEX" },
+		{ VIDIOC_ENCODER_CMD,    "VIDIOC_ENCODER_CMD" },
+		{ VIDIOC_TRY_ENCODER_CMD,    "VIDIOC_TRY_ENCODER_CMD" },
+		{ VIDIOC_DBG_S_REGISTER,    "VIDIOC_DBG_S_REGISTER" },
+		{ VIDIOC_DBG_G_REGISTER,    "VIDIOC_DBG_G_REGISTER" },
+		{ VIDIOC_S_HW_FREQ_SEEK,    "VIDIOC_S_HW_FREQ_SEEK" },
+		{ VIDIOC_S_DV_TIMINGS,    "VIDIOC_S_DV_TIMINGS" },
+		{ VIDIOC_G_DV_TIMINGS,    "VIDIOC_G_DV_TIMINGS" },
+		{ VIDIOC_DQEVENT,    "VIDIOC_DQEVENT" },
+		{ VIDIOC_SUBSCRIBE_EVENT,    "VIDIOC_SUBSCRIBE_EVENT" },
+		{ VIDIOC_UNSUBSCRIBE_EVENT,    "VIDIOC_UNSUBSCRIBE_EVENT" },
+		{ VIDIOC_CREATE_BUFS,    "VIDIOC_CREATE_BUFS" },
+		{ VIDIOC_PREPARE_BUF,    "VIDIOC_PREPARE_BUF" },
+		{ VIDIOC_G_SELECTION,    "VIDIOC_G_SELECTION" },
+		{ VIDIOC_S_SELECTION,    "VIDIOC_S_SELECTION" },
+		{ VIDIOC_DECODER_CMD,    "VIDIOC_DECODER_CMD" },
+		{ VIDIOC_TRY_DECODER_CMD,    "VIDIOC_TRY_DECODER_CMD" },
+		{ VIDIOC_ENUM_DV_TIMINGS,    "VIDIOC_ENUM_DV_TIMINGS" },
+		{ VIDIOC_QUERY_DV_TIMINGS,    "VIDIOC_QUERY_DV_TIMINGS" },
+		{ VIDIOC_DV_TIMINGS_CAP,    "VIDIOC_DV_TIMINGS_CAP" },
+		{ VIDIOC_ENUM_FREQ_BANDS,    "VIDIOC_ENUM_FREQ_BANDS" },
+		{ VIDIOC_DBG_G_CHIP_INFO,    "VIDIOC_DBG_G_CHIP_INFO" },
+		{ VIDIOC_QUERY_EXT_CTRL,    "VIDIOC_QUERY_EXT_CTRL" },
+		{ BASE_VIDIOC_PRIVATE,    "BASE_VIDIOC_PRIVATE" },
+		{ 0, nullptr }
+	};
+	return val2s(request, defs);
+}
+
+std::string ioctl2s_media(unsigned long request)
+{
+	static constexpr definition defs[] = {
+		{ MEDIA_IOC_DEVICE_INFO,    "MEDIA_IOC_DEVICE_INFO" },
+		{ MEDIA_IOC_ENUM_ENTITIES,    "MEDIA_IOC_ENUM_ENTITIES" },
+		{ MEDIA_IOC_ENUM_LINKS,    "MEDIA_IOC_ENUM_LINKS" },
+		{ MEDIA_IOC_SETUP_LINK,    "MEDIA_IOC_SETUP_LINK" },
+		{ MEDIA_IOC_G_TOPOLOGY,    "MEDIA_IOC_G_TOPOLOGY" },
+		{ MEDIA_IOC_REQUEST_ALLOC,    "MEDIA_IOC_REQUEST_ALLOC" },
+		{ MEDIA_REQUEST_IOC_QUEUE,    "MEDIA_REQUEST_IOC_QUEUE" },
+		{ MEDIA_REQUEST_IOC_REINIT,    "MEDIA_REQUEST_IOC_REINIT" },
+		{ 0, nullptr }
+	};
+	return val2s(request, defs);
+}
+
+std::string ctrltype2s(__u32 val)
+{
+	static constexpr definition defs[] = {
+		{ V4L2_CTRL_TYPE_INTEGER,    "V4L2_CTRL_TYPE_INTEGER" },
+		{ V4L2_CTRL_TYPE_BOOLEAN,    "V4L2_CTRL_TYPE_BOOLEAN" },
+		{ V4L2_CTRL_TYPE_MENU,    "V4L2_CTRL_TYPE_MENU" },
+		{ V4L2_CTRL_TYPE_BUTTON,    "V4L2_CTRL_TYPE_BUTTON" },
+		{ V4L2_CTRL_TYPE_INTEGER64,    "V4L2_CTRL_TYPE_INTEGER64" },
+		{ V4L2_CTRL_TYPE_CTRL_CLASS,    "V4L2_CTRL_TYPE_CTRL_CLASS" },
+		{ V4L2_CTRL_TYPE_STRING,    "V4L2_CTRL_TYPE_STRING" },
+		{ V4L2_CTRL_TYPE_BITMASK,    "V4L2_CTRL_TYPE_BITMASK" },
+		{ V4L2_CTRL_TYPE_INTEGER_MENU,    "V4L2_CTRL_TYPE_INTEGER_MENU" },
+		{ V4L2_CTRL_COMPOUND_TYPES,    "V4L2_CTRL_COMPOUND_TYPES" },
+		{ V4L2_CTRL_TYPE_U8,    "V4L2_CTRL_TYPE_U8" },
+		{ V4L2_CTRL_TYPE_U16,    "V4L2_CTRL_TYPE_U16" },
+		{ V4L2_CTRL_TYPE_U32,    "V4L2_CTRL_TYPE_U32" },
+		{ V4L2_CTRL_TYPE_AREA,    "V4L2_CTRL_TYPE_AREA" },
+		{ V4L2_CTRL_TYPE_HDR10_CLL_INFO,    "V4L2_CTRL_TYPE_HDR10_CLL_INFO" },
+		{ V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY,    "V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY" },
+		{ V4L2_CTRL_TYPE_H264_SPS,    "V4L2_CTRL_TYPE_H264_SPS" },
+		{ V4L2_CTRL_TYPE_H264_PPS,    "V4L2_CTRL_TYPE_H264_PPS" },
+		{ V4L2_CTRL_TYPE_H264_SCALING_MATRIX,    "V4L2_CTRL_TYPE_H264_SCALING_MATRIX" },
+		{ V4L2_CTRL_TYPE_H264_SLICE_PARAMS,    "V4L2_CTRL_TYPE_H264_SLICE_PARAMS" },
+		{ V4L2_CTRL_TYPE_H264_DECODE_PARAMS,    "V4L2_CTRL_TYPE_H264_DECODE_PARAMS" },
+		{ V4L2_CTRL_TYPE_H264_PRED_WEIGHTS,    "V4L2_CTRL_TYPE_H264_PRED_WEIGHTS" },
+		{ V4L2_CTRL_TYPE_FWHT_PARAMS,    "V4L2_CTRL_TYPE_FWHT_PARAMS" },
+		{ V4L2_CTRL_TYPE_VP8_FRAME,    "V4L2_CTRL_TYPE_VP8_FRAME" },
+		{ V4L2_CTRL_TYPE_MPEG2_QUANTISATION,    "V4L2_CTRL_TYPE_MPEG2_QUANTISATION" },
+		{ V4L2_CTRL_TYPE_MPEG2_SEQUENCE,    "V4L2_CTRL_TYPE_MPEG2_SEQUENCE" },
+		{ V4L2_CTRL_TYPE_MPEG2_PICTURE,    "V4L2_CTRL_TYPE_MPEG2_PICTURE" },
+		{ V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR,    "V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR" },
+		{ V4L2_CTRL_TYPE_VP9_FRAME,    "V4L2_CTRL_TYPE_VP9_FRAME" },
+		{ 0, nullptr }
+	};
+	return val2s(val, defs);
+}
+
+std::string capflag2s(unsigned long cap)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_CAP_VIDEO_M2M_MPLANE,    "V4L2_CAP_VIDEO_M2M_MPLANE" },
+		{ V4L2_CAP_VIDEO_CAPTURE, "V4L2_CAP_VIDEO_CAPTURE" },
+		{ V4L2_CAP_VIDEO_OUTPUT, "V4L2_CAP_VIDEO_OUTPUT" },
+		{ V4L2_CAP_VIDEO_OVERLAY, "V4L2_CAP_VIDEO_OVERLAY" },
+		{ V4L2_CAP_VBI_CAPTURE, "V4L2_CAP_VBI_CAPTURE" },
+		{ V4L2_CAP_VBI_OUTPUT, "V4L2_CAP_VBI_OUTPUT" },
+		{ V4L2_CAP_SLICED_VBI_CAPTURE, "V4L2_CAP_SLICED_VBI_CAPTURE" },
+		{ V4L2_CAP_SLICED_VBI_OUTPUT, "V4L2_CAP_SLICED_VBI_OUTPUT" },
+		{ V4L2_CAP_RDS_CAPTURE, "V4L2_CAP_RDS_CAPTURE" },
+		{ V4L2_CAP_VIDEO_OUTPUT_OVERLAY, "V4L2_CAP_VIDEO_OUTPUT_OVERLAY" },
+		{ V4L2_CAP_HW_FREQ_SEEK, "V4L2_CAP_HW_FREQ_SEEK" },
+		{ V4L2_CAP_RDS_OUTPUT, "V4L2_CAP_RDS_OUTPUT" },
+		{ V4L2_CAP_VIDEO_CAPTURE_MPLANE, "V4L2_CAP_VIDEO_CAPTURE_MPLANE" },
+		{ V4L2_CAP_VIDEO_OUTPUT_MPLANE, "V4L2_CAP_VIDEO_OUTPUT_MPLANE" },
+		{ V4L2_CAP_VIDEO_M2M_MPLANE, "V4L2_CAP_VIDEO_M2M_MPLANE" },
+		{ V4L2_CAP_VIDEO_M2M, "V4L2_CAP_VIDEO_M2M" },
+		{ V4L2_CAP_TUNER, "V4L2_CAP_TUNER" },
+		{ V4L2_CAP_AUDIO, "V4L2_CAP_AUDIO" },
+		{ V4L2_CAP_RADIO, "V4L2_CAP_RADIO" },
+		{ V4L2_CAP_MODULATOR, "V4L2_CAP_MODULATOR" },
+		{ V4L2_CAP_SDR_CAPTURE, "V4L2_CAP_SDR_CAPTURE" },
+		{ V4L2_CAP_EXT_PIX_FORMAT, "V4L2_CAP_EXT_PIX_FORMAT" },
+		{ V4L2_CAP_SDR_OUTPUT, "V4L2_CAP_SDR_OUTPUT" },
+		{ V4L2_CAP_META_CAPTURE, "V4L2_CAP_META_CAPTURE" },
+		{ V4L2_CAP_READWRITE, "V4L2_CAP_READWRITE" },
+		{ V4L2_CAP_ASYNCIO, "V4L2_CAP_ASYNCIO" },
+		{ V4L2_CAP_STREAMING, "V4L2_CAP_STREAMING" },
+		{ V4L2_CAP_META_OUTPUT, "V4L2_CAP_META_OUTPUT" },
+		{ V4L2_CAP_TOUCH, "V4L2_CAP_TOUCH" },
+		{ V4L2_CAP_IO_MC, "V4L2_CAP_IO_MC" },
+		{ V4L2_CAP_DEVICE_CAPS, "V4L2_CAP_DEVICE_CAPS" },
+		{ 0, nullptr }
+	};
+	return flags2s(cap, def);
+}
+
+std::string bufsyncflag2s(unsigned long flag)
+{
+	static constexpr flag_def def[] = {
+		{ DMA_BUF_SYNC_READ , "DMA_BUF_SYNC_READ " },
+		{ DMA_BUF_SYNC_WRITE, "DMA_BUF_SYNC_WRITE" },
+		{ DMA_BUF_SYNC_RW , "DMA_BUF_SYNC_RW " },
+		{ DMA_BUF_SYNC_START , "DMA_BUF_SYNC_START" },
+		{ DMA_BUF_SYNC_END, "DMA_BUF_SYNC_END" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
+
+std::string vp8_segment_flag2s(unsigned long flag)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_VP8_SEGMENT_FLAG_ENABLED, "V4L2_VP8_SEGMENT_FLAG_ENABLED" },
+		{ V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP, "V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP" },
+		{ V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA, "V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA" },
+		{ V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE, "V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
+
+std::string vp8_loop_filter_flag2s(unsigned long flag)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_VP8_LF_ADJ_ENABLE, "V4L2_VP8_LF_ADJ_ENABLE" },
+		{ V4L2_VP8_LF_DELTA_UPDATE, "V4L2_VP8_LF_DELTA_UPDATE" },
+		{ V4L2_VP8_LF_FILTER_TYPE_SIMPLE, "V4L2_VP8_LF_FILTER_TYPE_SIMPLE" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
+
+std::string vp8_frame_flag2s(unsigned long flag)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_VP8_FRAME_FLAG_KEY_FRAME, "V4L2_VP8_FRAME_FLAG_KEY_FRAME" },
+		{ V4L2_VP8_FRAME_FLAG_EXPERIMENTAL, "V4L2_VP8_FRAME_FLAG_EXPERIMENTAL" },
+		{ V4L2_VP8_FRAME_FLAG_SHOW_FRAME, "V4L2_VP8_FRAME_FLAG_SHOW_FRAME" },
+		{ V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF, "V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF" },
+		{ V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN, "V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN" },
+		{ V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT, "V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
+
+std::string ver2s(unsigned int version)
+{
+	char buf[16];
+	sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff);
+	return buf;
+}
+
+std::string which2s(unsigned long which)
+{
+	std::string s = "unknown";
+
+	switch (which) {
+	case V4L2_CTRL_WHICH_CUR_VAL:
+		s = "V4L2_CTRL_WHICH_CUR_VAL";
+		break;
+	case V4L2_CTRL_WHICH_DEF_VAL:
+		s= "V4L2_CTRL_WHICH_DEF_VAL";
+		break;
+	case V4L2_CTRL_WHICH_REQUEST_VAL:
+		s = "V4L2_CTRL_WHICH_REQUEST_VAL";
+		break;
+	default:
+		break;
+	}
+
+	return s;
+}
+
+std::string ctrlclass2s(__u32 id)
+{
+	static constexpr definition defs[] = {
+		{ V4L2_CTRL_CLASS_USER, "V4L2_CTRL_CLASS_USER" },
+		{ V4L2_CTRL_CLASS_CODEC,	"V4L2_CTRL_CLASS_CODEC" },
+		{ V4L2_CTRL_CLASS_CAMERA, "V4L2_CTRL_CLASS_CAMERA" },
+		{ V4L2_CTRL_CLASS_FM_TX, "V4L2_CTRL_CLASS_FM_TX" },
+		{ V4L2_CTRL_CLASS_FLASH, "V4L2_CTRL_CLASS_FLASH" },
+		{ V4L2_CTRL_CLASS_JPEG,	"V4L2_CTRL_CLASS_JPEG" },
+		{ V4L2_CTRL_CLASS_IMAGE_SOURCE,	"V4L2_CTRL_CLASS_IMAGE_SOURCE" },
+		{ V4L2_CTRL_CLASS_IMAGE_PROC, "V4L2_CTRL_CLASS_IMAGE_PROC" },
+		{ V4L2_CTRL_CLASS_DV, "V4L2_CTRL_CLASS_DV" },
+		{ V4L2_CTRL_CLASS_FM_RX, "V4L2_CTRL_CLASS_FM_RX" },
+		{ V4L2_CTRL_CLASS_RF_TUNER, "V4L2_CTRL_CLASS_RF_TUNER" },
+		{ V4L2_CTRL_CLASS_DETECT, "V4L2_CTRL_CLASS_DETECT" },
+		{ V4L2_CTRL_CLASS_CODEC_STATELESS, "V4L2_CTRL_CLASS_CODEC_STATELESS" },
+		{ V4L2_CTRL_CLASS_COLORIMETRY, "V4L2_CTRL_CLASS_COLORIMETRY" },
+		{ 0, nullptr }
+	};
+	return val2s(id & 0xff0000, defs);
+}
+
+std::string request_buffers_flag2s(unsigned int flag)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_MEMORY_FLAG_NON_COHERENT, "V4L2_MEMORY_FLAG_NON_COHERENT" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
+
+std::string v4l2_memory2s(__u32 id)
+{
+	static constexpr definition defs[] = {
+		{ V4L2_MEMORY_MMAP, "V4L2_MEMORY_MMAP" },
+		{ V4L2_MEMORY_USERPTR, "V4L2_MEMORY_USERPTR" },
+		{ V4L2_MEMORY_OVERLAY, "V4L2_MEMORY_OVERLAY" },
+		{ V4L2_MEMORY_DMABUF, "V4L2_MEMORY_DMABUF" },
+		{ 0, nullptr }
+	};
+	return val2s(id, defs);
+}
+
+std::string tc_type2s(__u32 id)
+{
+	static constexpr definition defs[] = {
+		{ V4L2_TC_TYPE_24FPS, "V4L2_TC_TYPE_24FPS" },
+		{ V4L2_TC_TYPE_25FPS, "V4L2_TC_TYPE_25FPS" },
+		{ V4L2_TC_TYPE_30FPS, "V4L2_TC_TYPE_30FPS" },
+		{ V4L2_TC_TYPE_50FPS, "V4L2_TC_TYPE_50FPS" },
+		{ V4L2_TC_TYPE_60FPS, "V4L2_TC_TYPE_60FPS" },
+		{ 0, nullptr }
+	};
+	return val2s(id, defs);
+}
+
+std::string tc_flag2s(unsigned int flag)
+{
+	static constexpr flag_def def[] = {
+		{ V4L2_TC_FLAG_DROPFRAME, "V4L2_TC_FLAG_DROPFRAME" },
+		{ V4L2_TC_FLAG_COLORFRAME, "V4L2_TC_FLAG_COLORFRAME" },
+		{ V4L2_TC_USERBITS_field, "V4L2_TC_USERBITS_field" },
+		{ V4L2_TC_USERBITS_USERDEFINED, "V4L2_TC_USERBITS_USERDEFINED" },
+		{ V4L2_TC_USERBITS_8BITCHARS, "V4L2_TC_USERBITS_8BITCHARS" },
+		{ 0, nullptr }
+	};
+	return flags2s(flag, def);
+}
diff --git a/utils/tracer/trace-info.h b/utils/tracer/trace-info.h
new file mode 100644
index 00000000..4c06190a
--- /dev/null
+++ b/utils/tracer/trace-info.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#ifndef TRACE_INFO_H
+#define TRACE_INFO_H
+
+#include <cstring>
+#include <linux/videodev2.h>
+#include <linux/media.h>
+#include <linux/dma-buf.h>
+
+#define ARRAY_SIZE(array)	(sizeof(array) / sizeof((array)[0]))
+
+enum LIBTRACER_SYSCALL {
+	LIBTRACER_SYSCALL_IOCTL,
+	LIBTRACER_SYSCALL_OPEN,
+	LIBTRACER_SYSCALL_OPEN64,
+	LIBTRACER_SYSCALL_CLOSE,
+	LIBTRACER_SYSCALL_MMAP64,
+	LIBTRACER_SYSCALL_MUNMAP,
+	LIBTRACER_SYSCALL_SELECT,
+	LIBTRACER_SYSCALL_POLL,
+};
+
+/* Convert an ioctl request of type 'V' to string. */
+std::string ioctl2s_video(unsigned long request);
+
+/* Convert an ioctl request of type '|' to string. */
+std::string ioctl2s_media(unsigned long request);
+
+/* Convert capability flags to common separated string. */
+std::string capflag2s(unsigned long cap);
+
+/* Convert a decimal number to a string with kernel_version.major_revision.minor_revision. */
+std::string ver2s(unsigned int version);
+
+/* Convert v4l2_ext_controls member "which" to string. */
+std::string which2s(unsigned long which);
+
+/* Convert control class to string. */
+std::string ctrlclass2s(__u32 id);
+
+/* Convert control type to string. */
+std::string ctrltype2s(__u32 type);
+
+/* Convert struct dma_buf_sync flags to string. */
+std::string bufsyncflag2s(unsigned long flag);
+
+/* Convert v4l2_vp8_segment flags to string. */
+std::string vp8_segment_flag2s(unsigned long flag);
+
+/* Convert v4l2_vp8_loop_filter flags to string. */
+std::string vp8_loop_filter_flag2s(unsigned long flag);
+
+/* Convert v4l2_ctrl_vp8_frame flags to string. */
+std::string vp8_frame_flag2s(unsigned long flag);
+
+/* Convert v4l2_requestbuffers flags to string. */
+std::string request_buffers_flag2s(unsigned int flag);
+
+/* Convert enum v4l2_memory type to string. */
+std::string v4l2_memory2s(__u32 id);
+
+/* Convert v4l2_timecode type to string. */
+std::string tc_type2s(__u32 id);
+
+/* Convert v4l2_timecode flags to string. */
+std::string tc_flag2s(unsigned int flag);
+
+#endif
diff --git a/utils/tracer/trace-vp8.cpp b/utils/tracer/trace-vp8.cpp
new file mode 100644
index 00000000..a0a8ccdd
--- /dev/null
+++ b/utils/tracer/trace-vp8.cpp
@@ -0,0 +1,183 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "libtracer.h"
+
+json_object *trace_v4l2_vp8_segment(struct v4l2_vp8_segment segment)
+{
+	json_object *vp8_segment_obj = json_object_new_object();
+
+	/* __s8 quant_update[4] */
+	json_object *quant_update_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(segment.quant_update); i++)
+		json_object_array_add(quant_update_obj, json_object_new_int(segment.quant_update[i]));
+	json_object_object_add(vp8_segment_obj, "quant_update", quant_update_obj);
+
+	/* __s8 lf_update[4] */
+	json_object *lf_update_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(segment.lf_update); i++)
+		json_object_array_add(lf_update_obj, json_object_new_int(segment.lf_update[i]));
+	json_object_object_add(vp8_segment_obj, "lf_update", lf_update_obj);
+
+	/* __u8 segment_probs[3] */
+	json_object *segment_probs_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(segment.segment_probs); i++)
+		json_object_array_add(segment_probs_obj, json_object_new_int(segment.segment_probs[i]));
+	json_object_object_add(vp8_segment_obj, "segment_probs", segment_probs_obj);
+
+	json_object_object_add(vp8_segment_obj, "padding", json_object_new_int(segment.padding));
+	json_object_object_add(vp8_segment_obj, "flags", json_object_new_int(segment.flags));
+	json_object_object_add(vp8_segment_obj, "flags_str",
+						   json_object_new_string(vp8_segment_flag2s(segment.flags).c_str()));
+
+	return vp8_segment_obj;
+}
+
+json_object *trace_v4l2_vp8_loop_filter(struct v4l2_vp8_loop_filter lf)
+{
+	json_object *vp8_loop_filter_obj = json_object_new_object();
+
+	/* 	__s8 ref_frm_delta[4] */
+	json_object *ref_frm_delta_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(lf.ref_frm_delta); i++)
+		json_object_array_add(ref_frm_delta_obj, json_object_new_int(lf.ref_frm_delta[i]));
+	json_object_object_add(vp8_loop_filter_obj, "ref_frm_delta", ref_frm_delta_obj);
+
+	/* 	__s8 mb_mode_delta[4] */
+	json_object *mb_mode_delta_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(lf.mb_mode_delta); i++)
+		json_object_array_add(mb_mode_delta_obj, json_object_new_int(lf.mb_mode_delta[i]));
+	json_object_object_add(vp8_loop_filter_obj, "mb_mode_delta", mb_mode_delta_obj);
+
+	json_object_object_add(vp8_loop_filter_obj, "sharpness_level", json_object_new_int(lf.sharpness_level));
+	json_object_object_add(vp8_loop_filter_obj, "level", json_object_new_int(lf.level));
+	json_object_object_add(vp8_loop_filter_obj, "padding", json_object_new_int(lf.padding));
+	json_object_object_add(vp8_loop_filter_obj, "flags", json_object_new_int(lf.flags));
+	json_object_object_add(vp8_loop_filter_obj, "flags_str", json_object_new_string(vp8_loop_filter_flag2s(lf.flags).c_str()));
+
+	return vp8_loop_filter_obj;
+}
+
+json_object *trace_v4l2_vp8_quantization(struct v4l2_vp8_quantization quant)
+{
+	json_object *vp8_quantization_obj = json_object_new_object();
+
+	json_object_object_add(vp8_quantization_obj, "y_ac_qi", json_object_new_int(quant.y_ac_qi));
+	json_object_object_add(vp8_quantization_obj, "y_dc_delta", json_object_new_int(quant.y_dc_delta));
+	json_object_object_add(vp8_quantization_obj, "y2_dc_delta", json_object_new_int(quant.y2_dc_delta));
+	json_object_object_add(vp8_quantization_obj, "y2_ac_delta", json_object_new_int(quant.y2_ac_delta));
+	json_object_object_add(vp8_quantization_obj, "uv_dc_delta", json_object_new_int(quant.uv_dc_delta));
+	json_object_object_add(vp8_quantization_obj, "uv_ac_delta", json_object_new_int(quant.uv_ac_delta));
+	json_object_object_add(vp8_quantization_obj, "padding", json_object_new_int(quant.padding));
+
+	return vp8_quantization_obj;
+}
+
+json_object *trace_v4l2_vp8_entropy(struct v4l2_vp8_entropy entropy)
+{
+	json_object *vp8_entropy_obj = json_object_new_object();
+
+	/*__u8 coeff_probs[4][8][3][V4L2_VP8_COEFF_PROB_CNT] */
+	json_object *coeff_probs_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.coeff_probs); i++)
+		for (size_t j = 0; j < ARRAY_SIZE(entropy.coeff_probs[0]); j++)
+			for (size_t k = 0; k < ARRAY_SIZE(entropy.coeff_probs[0][0]); k++)
+				for (size_t l = 0; l < V4L2_VP8_COEFF_PROB_CNT; l++)
+					json_object_array_add(coeff_probs_obj,
+					                      json_object_new_int(entropy.coeff_probs[i][j][k][l]));
+	json_object_object_add(vp8_entropy_obj, "coeff_probs", coeff_probs_obj);
+
+	/* __u8 y_mode_probs[4] */
+	json_object *y_mode_probs_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.y_mode_probs); i++)
+		json_object_array_add(y_mode_probs_obj, json_object_new_int(entropy.y_mode_probs[i]));
+	json_object_object_add(vp8_entropy_obj, "y_mode_probs", y_mode_probs_obj);
+
+	/* __u8 uv_mode_probs[3] */
+	json_object *uv_mode_probs_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.uv_mode_probs); i++)
+		json_object_array_add(uv_mode_probs_obj, json_object_new_int(entropy.uv_mode_probs[i]));
+	json_object_object_add(vp8_entropy_obj, "uv_mode_probs", uv_mode_probs_obj);
+
+	/* __u8 mv_probs[2][V4L2_VP8_MV_PROB_CNT] */
+	json_object *mv_probs_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.mv_probs); i++)
+		for (size_t j = 0; j < V4L2_VP8_MV_PROB_CNT; j++)
+			json_object_array_add(mv_probs_obj, json_object_new_int(entropy.mv_probs[i][j]));
+	json_object_object_add(vp8_entropy_obj, "mv_probs", mv_probs_obj);
+
+	/*__u8 padding[3] */
+	json_object *padding_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.padding); i++)
+		json_object_array_add(padding_obj, json_object_new_int(entropy.padding[i]));
+	json_object_object_add(vp8_entropy_obj, "padding", padding_obj);
+
+	return vp8_entropy_obj;
+}
+
+json_object *trace_v4l2_vp8_entropy_coder_state(struct v4l2_vp8_entropy_coder_state coder_state)
+{
+	json_object *vp8_entropy_coder_state_obj = json_object_new_object();
+
+	json_object_object_add(vp8_entropy_coder_state_obj, "range", json_object_new_int(coder_state.range));
+	json_object_object_add(vp8_entropy_coder_state_obj, "value", json_object_new_int(coder_state.value));
+	json_object_object_add(vp8_entropy_coder_state_obj, "bit_count", json_object_new_int(coder_state.bit_count));
+	json_object_object_add(vp8_entropy_coder_state_obj, "padding", json_object_new_int(coder_state.padding));
+
+	return vp8_entropy_coder_state_obj;
+}
+
+void trace_v4l2_ctrl_vp8_frame(void *p_vp8_frame, json_object *ctrl_obj)
+{
+	json_object *vp8_frame_obj = json_object_new_object();
+	struct v4l2_ctrl_vp8_frame *vp8_frame = static_cast<struct v4l2_ctrl_vp8_frame*>(p_vp8_frame);
+
+	/* struct v4l2_vp8_segment segment */
+	json_object *v4l2_vp8_segment_obj = trace_v4l2_vp8_segment(vp8_frame->segment);
+	json_object_object_add(vp8_frame_obj, "segment", v4l2_vp8_segment_obj);
+
+	/* struct v4l2_vp8_loop_filter lf */
+	json_object *v4l2_vp8_loop_filter_obj = trace_v4l2_vp8_loop_filter(vp8_frame->lf);
+	json_object_object_add(vp8_frame_obj, "lf", v4l2_vp8_loop_filter_obj);
+
+	/* struct v4l2_vp8_quantization quant */
+	json_object *v4l2_vp8_quantization_obj = trace_v4l2_vp8_quantization(vp8_frame->quant);
+	json_object_object_add(vp8_frame_obj, "quant", v4l2_vp8_quantization_obj);
+
+	/* struct v4l2_vp8_entropy entropy */
+	json_object *v4l2_vp8_entropy_obj = trace_v4l2_vp8_entropy(vp8_frame->entropy);
+	json_object_object_add(vp8_frame_obj, "entropy", v4l2_vp8_entropy_obj);
+
+	/* struct v4l2_vp8_entropy_coder_state coder_state */
+	json_object *v4l2_vp8_entropy_coder_state_obj = trace_v4l2_vp8_entropy_coder_state(vp8_frame->coder_state);
+	json_object_object_add(vp8_frame_obj, "coder_state", v4l2_vp8_entropy_coder_state_obj);
+
+	json_object_object_add(vp8_frame_obj, "width", json_object_new_int(vp8_frame->width));
+	json_object_object_add(vp8_frame_obj, "height", json_object_new_int(vp8_frame->height));
+	json_object_object_add(vp8_frame_obj, "horizontal_scale", json_object_new_int(vp8_frame->horizontal_scale));
+	json_object_object_add(vp8_frame_obj, "vertical_scale", json_object_new_int(vp8_frame->vertical_scale));
+	json_object_object_add(vp8_frame_obj, "version", json_object_new_int(vp8_frame->version));
+	json_object_object_add(vp8_frame_obj, "prob_skip_false", json_object_new_int(vp8_frame->prob_skip_false));
+	json_object_object_add(vp8_frame_obj, "prob_intra", json_object_new_int(vp8_frame->prob_intra));
+	json_object_object_add(vp8_frame_obj, "prob_last", json_object_new_int(vp8_frame->prob_last));
+	json_object_object_add(vp8_frame_obj, "prob_gf", json_object_new_int(vp8_frame->prob_gf));
+	json_object_object_add(vp8_frame_obj, "num_dct_parts", json_object_new_int(vp8_frame->num_dct_parts));
+	json_object_object_add(vp8_frame_obj, "first_part_size", json_object_new_int(vp8_frame->first_part_size));
+	json_object_object_add(vp8_frame_obj, "first_part_header_bits", json_object_new_int(vp8_frame->first_part_header_bits));
+
+	/* __u32 dct_part_sizes[8] */
+	json_object *dct_part_sizes_obj = json_object_new_array();
+	for (size_t i = 0; i < ARRAY_SIZE(vp8_frame->dct_part_sizes); i++)
+		json_object_array_add(dct_part_sizes_obj, json_object_new_int(vp8_frame->dct_part_sizes[i]));
+	json_object_object_add(vp8_frame_obj, "dct_part_sizes", dct_part_sizes_obj);
+
+	json_object_object_add(vp8_frame_obj, "last_frame_ts", json_object_new_int(vp8_frame->last_frame_ts));
+	json_object_object_add(vp8_frame_obj, "golden_frame_ts", json_object_new_int(vp8_frame->golden_frame_ts));
+	json_object_object_add(vp8_frame_obj, "alt_frame_ts", json_object_new_int(vp8_frame->alt_frame_ts));
+	json_object_object_add(vp8_frame_obj, "flags", json_object_new_int(vp8_frame->flags));
+	json_object_object_add(vp8_frame_obj, "flags_str", json_object_new_string(vp8_frame_flag2s(vp8_frame->flags).c_str()));
+
+	json_object_object_add(ctrl_obj, "v4l2_ctrl_vp8_frame", vp8_frame_obj);
+}
diff --git a/utils/tracer/trace.cpp b/utils/tracer/trace.cpp
new file mode 100644
index 00000000..5e952a7b
--- /dev/null
+++ b/utils/tracer/trace.cpp
@@ -0,0 +1,520 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "libtracer.h"
+
+json_object *trace_open(const char *path, int oflag, mode_t mode)
+{
+	json_object *open_args = json_object_new_object();
+
+	json_object_object_add(open_args, "path", json_object_new_string(path));
+	json_object_object_add(open_args, "oflag", json_object_new_int(oflag));
+	json_object_object_add(open_args, "mode", json_object_new_uint64(mode));
+
+	return open_args;
+}
+
+json_object *trace_mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
+{
+	json_object *mmap_args = json_object_new_object();
+
+	json_object_object_add(mmap_args, "addr", json_object_new_int64((int64_t)addr));
+	json_object_object_add(mmap_args, "len", json_object_new_uint64(len));
+	json_object_object_add(mmap_args, "prot", json_object_new_int(prot));
+	json_object_object_add(mmap_args, "flags", json_object_new_int(flags));
+	json_object_object_add(mmap_args, "fildes", json_object_new_int(fildes));
+	json_object_object_add(mmap_args, "off", json_object_new_int64(off));
+
+	return mmap_args;
+}
+
+/*
+ * Get a buffer from memory and convert it to a json string array.
+ * The bytes are displayed with the most-significant bits first.
+ */
+json_object *trace_buffer(unsigned char *buffer_pointer, __u32 bytesused)
+{
+	char buf[5];
+	std::string s;
+	int byte_count_per_line = 0;
+	json_object *mem_array_obj = json_object_new_array();
+
+	for (__u32 i = 0; i < bytesused; i++) {
+		memset(buf, 0, sizeof(buf));
+
+		/* Each byte e.g. D9 will write a string of two characters "D9". */
+		sprintf(buf, "%02x", buffer_pointer[i]);
+		s += buf;
+		byte_count_per_line++;
+
+		/*  Add a space every two bytes e.g. "012A 4001" and a newline every 16 bytes. */
+		if (byte_count_per_line == 16) {
+			byte_count_per_line = 0;
+			json_object_array_add(mem_array_obj, json_object_new_string(s.c_str()));
+			s.clear();
+		} else if (i % 2) {
+			s += " ";
+		}
+	}
+
+	/* Trace the last line if it was less than a full 16 bytes. */
+	if (byte_count_per_line)
+		json_object_array_add(mem_array_obj, json_object_new_string(s.c_str()));
+
+	return mem_array_obj;
+}
+
+/* Get the decoded capture buffer from memory and write it to a binary yuv file. */
+void write_decoded_frames_to_yuv_file(unsigned char *buffer_pointer, __u32 bytesused, std::string filename)
+{
+	FILE *fp = fopen(filename.c_str(), "a");
+	for (__u32 i = 0; i < bytesused; i++)
+		fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp);
+	fflush(fp);
+	fclose(fp);
+}
+
+/* Trace an output or capture buffer. */
+void trace_mem(int fd)
+{
+	int index;
+	__u32 type;
+	__u32 bytesused;
+	unsigned char *buffer_pointer;
+	long int start = get_buffer_address_trace(fd);
+
+	/* Don't trace unmapped memory. */
+	if (!start)
+		return;
+
+	buffer_pointer = (unsigned char*) start;
+	type = get_buffer_type_trace(fd);
+	index = get_buffer_index_trace(fd);
+	bytesused = get_buffer_bytesused_trace(fd);
+
+	json_object *mem_obj = json_object_new_object();
+	json_object_object_add(mem_obj, "mem_dump", json_object_new_string(buftype2s(type).c_str()));
+	json_object_object_add(mem_obj, "fd", json_object_new_int(fd));
+	json_object_object_add(mem_obj, "type", json_object_new_uint64(type));
+	json_object_object_add(mem_obj, "index", json_object_new_int(index));
+	json_object_object_add(mem_obj, "bytesused", json_object_new_uint64(bytesused));
+	json_object_object_add(mem_obj, "address", json_object_new_int64(start));
+
+	if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		json_object *mem_array_obj = trace_buffer(buffer_pointer, bytesused);
+		json_object_object_add(mem_obj, "mem_array", mem_array_obj);
+	}
+
+	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+
+		if (create_yuv_file()) {
+			std::string filename = getenv("TRACE_ID");
+			filename +=  ".yuv";
+			write_decoded_frames_to_yuv_file(buffer_pointer, bytesused, filename);
+			json_object_object_add(mem_obj, "filename", json_object_new_string(filename.c_str()));
+		}
+
+		if (write_decoded_data_to_json()) {
+			json_object *mem_array_obj = trace_buffer(buffer_pointer, bytesused);
+			json_object_object_add(mem_obj, "mem_array_capture", mem_array_obj);
+		}
+
+	}
+
+	if (pretty_print_mem())
+		write_json_object_to_json_file(mem_obj, JSON_C_TO_STRING_PRETTY);
+	else
+		write_json_object_to_json_file(mem_obj);
+
+	json_object_put(mem_obj);
+}
+
+void trace_vidioc_querycap(void *arg, json_object *ioctl_args)
+{
+	json_object *cap_obj = json_object_new_object();
+	struct v4l2_capability *cap = static_cast<struct v4l2_capability*>(arg);
+
+	json_object_object_add(cap_obj, "driver", json_object_new_string((const char *)cap->driver));
+	json_object_object_add(cap_obj, "card", json_object_new_string((const char *)cap->card));
+	json_object_object_add(cap_obj, "bus_info", json_object_new_string((const char *)cap->bus_info));
+	json_object_object_add(cap_obj, "version", json_object_new_string(ver2s(cap->version).c_str()));
+	json_object_object_add(cap_obj, "capabilities", json_object_new_int64(cap->capabilities));
+	json_object_object_add(cap_obj, "capabilities_str",
+		                       json_object_new_string(capflag2s(cap->capabilities).c_str()));
+	json_object_object_add(cap_obj, "device_caps", json_object_new_int64(cap->device_caps));
+	json_object_object_add(ioctl_args, "v4l2_capability", cap_obj);
+}
+
+void trace_vidioc_enum_fmt(void *arg, json_object *ioctl_args)
+{
+	json_object *fmtdesc_obj = json_object_new_object();
+	struct v4l2_fmtdesc *fmtdesc = static_cast<struct v4l2_fmtdesc*>(arg);
+
+	json_object_object_add(fmtdesc_obj, "index", json_object_new_uint64(fmtdesc->index));
+	json_object_object_add(fmtdesc_obj, "type_str", json_object_new_string(buftype2s(fmtdesc->type).c_str()));
+	json_object_object_add(fmtdesc_obj, "type", json_object_new_uint64(fmtdesc->type));
+	json_object_object_add(fmtdesc_obj, "flags", json_object_new_uint64(fmtdesc->flags));
+	json_object_object_add(fmtdesc_obj, "description", json_object_new_string((const char *)fmtdesc->description));
+	json_object_object_add(fmtdesc_obj, "pixelformat", json_object_new_uint64(fmtdesc->pixelformat));
+	json_object_object_add(fmtdesc_obj, "mbus_code", json_object_new_uint64(fmtdesc->mbus_code));
+	json_object_object_add(ioctl_args, "v4l2_fmtdesc", fmtdesc_obj);
+}
+
+void trace_v4l2_plane_pix_format(json_object *pix_mp_obj, struct v4l2_plane_pix_format plane_fmt, int plane)
+{
+	json_object *plane_fmt_obj = json_object_new_object();
+
+	json_object_object_add(plane_fmt_obj, "sizeimage", json_object_new_uint64(plane_fmt.sizeimage));
+	json_object_object_add(plane_fmt_obj, "bytesperline", json_object_new_uint64(plane_fmt.bytesperline));
+
+	/* Create a unique key name for each plane. */
+	std::string unique_key_for_plane = "v4l2_plane_pix_format_";
+	unique_key_for_plane += std::to_string(plane);
+
+	json_object_object_add(pix_mp_obj, unique_key_for_plane.c_str(), plane_fmt_obj);
+}
+
+void trace_v4l2_pix_format_mplane(json_object *format_obj, struct v4l2_pix_format_mplane pix_mp)
+{
+	json_object *pix_mp_obj = json_object_new_object();
+
+	json_object_object_add(pix_mp_obj, "width", json_object_new_uint64(pix_mp.width));
+	json_object_object_add(pix_mp_obj, "height", json_object_new_uint64(pix_mp.height));
+	json_object_object_add(pix_mp_obj, "pixelformat", json_object_new_uint64(pix_mp.pixelformat));
+	json_object_object_add(pix_mp_obj, "pixelformat_str", json_object_new_string(fcc2s(pix_mp.pixelformat).c_str()));
+	json_object_object_add(pix_mp_obj, "field", json_object_new_uint64(pix_mp.field));
+	json_object_object_add(pix_mp_obj, "field_str", json_object_new_string(field2s(pix_mp.field).c_str()));
+	json_object_object_add(pix_mp_obj, "colorspace", json_object_new_uint64(pix_mp.colorspace));
+	json_object_object_add(pix_mp_obj, "colorspace_str", json_object_new_string(colorspace2s(pix_mp.colorspace).c_str()));
+	json_object_object_add(pix_mp_obj, "num_planes", json_object_new_int(pix_mp.num_planes));
+	for (int i = 0; i < pix_mp.num_planes; i++)
+		trace_v4l2_plane_pix_format(pix_mp_obj, pix_mp.plane_fmt[i], i);
+
+	json_object_object_add(pix_mp_obj, "flags", json_object_new_int(pix_mp.flags));
+	json_object_object_add(pix_mp_obj, "flags_str", json_object_new_string(pixflags2s(pix_mp.flags).c_str()));
+	json_object_object_add(pix_mp_obj, "ycbcr_enc", json_object_new_int(pix_mp.ycbcr_enc));
+	json_object_object_add(pix_mp_obj, "ycbcr_enc_str", json_object_new_string(ycbcr_enc2s(pix_mp.ycbcr_enc).c_str()));
+	json_object_object_add(pix_mp_obj, "quantization", json_object_new_int(pix_mp.quantization));
+	json_object_object_add(pix_mp_obj, "quantization_str", json_object_new_string(quantization2s(pix_mp.quantization).c_str()));
+	json_object_object_add(pix_mp_obj, "xfer_func", json_object_new_int(pix_mp.xfer_func));
+	json_object_object_add(pix_mp_obj, "xfer_func_str", json_object_new_string(xfer_func2s(pix_mp.xfer_func).c_str()));
+
+	json_object_object_add(format_obj, "v4l2_pix_format_mplane", pix_mp_obj);
+}
+
+void trace_v4l2_format(void *arg, json_object *ioctl_args)
+{
+	json_object *format_obj = json_object_new_object();
+	struct v4l2_format *format = static_cast<struct v4l2_format*>(arg);
+
+	json_object_object_add(format_obj, "type", json_object_new_uint64(format->type));
+	json_object_object_add(format_obj, "type_str", json_object_new_string(buftype2s(format->type).c_str()));
+
+	switch (format->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+	case V4L2_BUF_TYPE_VBI_OUTPUT:
+	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+	case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+		break;
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		trace_v4l2_pix_format_mplane(format_obj, format->fmt.pix_mp);
+		break;
+	case V4L2_BUF_TYPE_SDR_CAPTURE:
+	case V4L2_BUF_TYPE_SDR_OUTPUT:
+	case V4L2_BUF_TYPE_META_CAPTURE:
+	case V4L2_BUF_TYPE_META_OUTPUT:
+		break;
+	}
+	json_object_object_add(ioctl_args, "v4l2_format", format_obj);
+}
+
+void trace_v4l2_requestbuffers(void *arg, json_object *ioctl_args)
+{
+	json_object *request_buffers_obj = json_object_new_object();
+	struct v4l2_requestbuffers *request_buffers = static_cast<struct v4l2_requestbuffers*>(arg);
+
+	json_object_object_add(request_buffers_obj, "count", json_object_new_uint64(request_buffers->count));
+	json_object_object_add(request_buffers_obj, "type", json_object_new_uint64(request_buffers->type));
+	json_object_object_add(request_buffers_obj, "type_str",
+	                       json_object_new_string(buftype2s(request_buffers->type).c_str()));
+	json_object_object_add(request_buffers_obj, "memory", json_object_new_uint64(request_buffers->memory));
+	json_object_object_add(request_buffers_obj, "memory_str",
+	                       json_object_new_string(v4l2_memory2s(request_buffers->memory).c_str()));
+	json_object_object_add(request_buffers_obj, "capabilities", json_object_new_uint64(request_buffers->capabilities));
+	json_object_object_add(request_buffers_obj, "capabilities_str",
+	                       json_object_new_string(bufcap2s(request_buffers->capabilities).c_str()));
+	json_object_object_add(request_buffers_obj, "flags", json_object_new_int(request_buffers->flags));
+	json_object_object_add(request_buffers_obj, "flags_str",
+						   json_object_new_string(request_buffers_flag2s(request_buffers->flags).c_str()));
+
+	json_object_object_add(ioctl_args, "v4l2_requestbuffers", request_buffers_obj);
+}
+
+json_object *trace_v4l2_plane(struct v4l2_plane *p, __u32 memory)
+{
+	json_object *plane_obj = json_object_new_object();
+
+	json_object_object_add(plane_obj, "bytesused", json_object_new_int64(p->bytesused));
+	json_object_object_add(plane_obj, "length", json_object_new_uint64(p->length));
+
+	json_object *m_obj = json_object_new_object();
+	if (memory == V4L2_MEMORY_MMAP)
+		json_object_object_add(m_obj, "mem_offset", json_object_new_int64(p->m.mem_offset));
+	json_object_object_add(plane_obj, "m", m_obj);
+
+	json_object_object_add(plane_obj, "data_offset", json_object_new_int64(p->data_offset));
+
+	return plane_obj;
+}
+
+void trace_v4l2_buffer(void *arg, json_object *ioctl_args)
+{
+	json_object *buf_obj = json_object_new_object();
+	struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
+
+	json_object_object_add(buf_obj, "index", json_object_new_uint64(buf->index));
+	json_object_object_add(buf_obj, "type", json_object_new_uint64(buf->type));
+	json_object_object_add(buf_obj, "type_str",
+	                       json_object_new_string(buftype2s(buf->type).c_str()));
+	json_object_object_add(buf_obj, "bytesused", json_object_new_uint64(buf->bytesused));
+	json_object_object_add(buf_obj, "flags", json_object_new_uint64(buf->flags));
+	json_object_object_add(buf_obj, "flags_str",
+	                       json_object_new_string(bufferflags2s(buf->flags).c_str()));
+	json_object_object_add(buf_obj, "field", json_object_new_uint64(buf->field));
+
+	json_object *timestamp_obj = json_object_new_object();
+	json_object_object_add(timestamp_obj, "tv_sec", json_object_new_int64(buf->timestamp.tv_sec));
+	json_object_object_add(timestamp_obj, "tv_usec", json_object_new_int64(buf->timestamp.tv_usec));
+	json_object_object_add(buf_obj, "timestamp", timestamp_obj);
+	json_object_object_add(buf_obj, "sequence", json_object_new_uint64(buf->sequence));
+	json_object_object_add(buf_obj, "memory", json_object_new_uint64(buf->memory));
+	json_object_object_add(buf_obj, "memory_str",
+	                       json_object_new_string(v4l2_memory2s(buf->memory).c_str()));
+
+	json_object *m_obj = json_object_new_object();
+	if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
+	    buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+
+		json_object *planes_obj = json_object_new_array();
+		/* TODO tracer only works for decoded formats with one plane e.g. V4L2_PIX_FMT_NV12 */
+		json_object_array_add(planes_obj, trace_v4l2_plane(buf->m.planes, buf->memory));
+		json_object_object_add(m_obj, "planes", planes_obj);
+	}
+	json_object_object_add(buf_obj, "m", m_obj);
+	json_object_object_add(buf_obj, "length", json_object_new_uint64(buf->length));
+
+	/* For memory-to-memory devices you can use requests only for output buffers. */
+	if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+		json_object_object_add(buf_obj, "request_fd", json_object_new_int(buf->request_fd));
+
+	json_object_object_add(ioctl_args, "v4l2_buffer", buf_obj);
+}
+
+void trace_v4l2_exportbuffer(void *arg, json_object *ioctl_args)
+{
+	json_object *exportbuffer_obj = json_object_new_object();
+	struct v4l2_exportbuffer *export_buffer = static_cast<struct v4l2_exportbuffer*>(arg);
+
+	json_object_object_add(exportbuffer_obj, "type", json_object_new_uint64(export_buffer->type));
+	json_object_object_add(exportbuffer_obj, "type_str",
+	                       json_object_new_string(buftype2s(export_buffer->type).c_str()));
+
+	json_object_object_add(exportbuffer_obj, "index", json_object_new_uint64(export_buffer->index));
+	json_object_object_add(exportbuffer_obj, "plane", json_object_new_uint64(export_buffer->plane));
+	json_object_object_add(exportbuffer_obj, "flags", json_object_new_uint64(export_buffer->flags));
+	json_object_object_add(exportbuffer_obj, "fd", json_object_new_int(export_buffer->fd));
+
+	json_object_object_add(ioctl_args, "v4l2_exportbuffer", exportbuffer_obj);
+}
+
+void trace_vidioc_stream(void *arg, json_object *ioctl_args)
+{
+	v4l2_buf_type buf_type = *(static_cast<v4l2_buf_type*>(arg));
+	json_object_object_add(ioctl_args, "buf_type", json_object_new_int(buf_type));
+	json_object_object_add(ioctl_args, "buf_type_str", json_object_new_string(buftype2s(buf_type).c_str()));
+}
+
+void trace_v4l2_ext_control(json_object *ext_controls_obj, struct v4l2_ext_control ctrl, __u32 control_idx)
+{
+	std::string unique_key_for_control;
+
+	json_object *ctrl_obj = json_object_new_object();
+	json_object_object_add(ctrl_obj, "id", json_object_new_uint64(ctrl.id));
+	json_object_object_add(ctrl_obj, "control_class_str", json_object_new_string(ctrlclass2s(ctrl.id).c_str()));
+	json_object_object_add(ctrl_obj, "size", json_object_new_uint64(ctrl.size));
+
+	if ((ctrl.id & V4L2_CID_CODEC_STATELESS_BASE) == V4L2_CID_CODEC_STATELESS_BASE) {
+		switch (ctrl.id) {
+		case V4L2_CID_STATELESS_VP8_FRAME:
+			trace_v4l2_ctrl_vp8_frame(ctrl.p_vp8_frame, ctrl_obj);
+			break;
+		default:
+			break;
+		}
+	}
+
+	unique_key_for_control = "v4l2_ext_control_";
+	unique_key_for_control += std::to_string(control_idx);
+
+	json_object_object_add(ext_controls_obj, unique_key_for_control.c_str(), ctrl_obj);
+}
+
+void trace_v4l2_ext_controls(void *arg, json_object *ioctl_args)
+{
+	json_object *ext_controls_obj = json_object_new_object();
+	struct v4l2_ext_controls *ext_controls = static_cast<struct v4l2_ext_controls*>(arg);
+
+	json_object_object_add(ext_controls_obj, "which", json_object_new_int64(ext_controls->which));
+	json_object_object_add(ext_controls_obj, "which_str", json_object_new_string(which2s(ext_controls->which).c_str()));
+	json_object_object_add(ext_controls_obj, "count", json_object_new_uint64(ext_controls->count));
+
+	/* error_idx is defined only if the ioctl returned an error  */
+	if (errno)
+		json_object_object_add(ext_controls_obj, "error_idx", json_object_new_uint64(ext_controls->error_idx));
+
+	/* request_fd is only valid when "which" == V4L2_CTRL_WHICH_REQUEST_VAL */
+	if (ext_controls->which == V4L2_CTRL_WHICH_REQUEST_VAL)
+		json_object_object_add(ext_controls_obj, "request_fd", json_object_new_int(ext_controls->request_fd));
+
+	for (__u32 i = 0; i < ext_controls->count; i++)
+		trace_v4l2_ext_control(ext_controls_obj, ext_controls->controls[i], i);
+
+	json_object_object_add(ioctl_args, "v4l2_ext_controls", ext_controls_obj);
+}
+
+void trace_vidioc_query_ext_ctrl(void *arg, json_object *ioctl_args)
+{
+	json_object *query_ext_ctrl_obj = json_object_new_object();
+	struct v4l2_query_ext_ctrl *queryextctrl = static_cast<struct v4l2_query_ext_ctrl*>(arg);
+
+	json_object_object_add(query_ext_ctrl_obj, "id", json_object_new_uint64(queryextctrl->id));
+	json_object_object_add(query_ext_ctrl_obj, "control_class_str", json_object_new_string(ctrlclass2s(queryextctrl->id).c_str()));
+	json_object_object_add(query_ext_ctrl_obj, "type", json_object_new_uint64(queryextctrl->type));
+	json_object_object_add(query_ext_ctrl_obj, "type_str", json_object_new_string(ctrltype2s(queryextctrl->type).c_str()));
+	json_object_object_add(query_ext_ctrl_obj, "name", json_object_new_string(queryextctrl->name));
+	json_object_object_add(query_ext_ctrl_obj, "minimum", json_object_new_int64(queryextctrl->minimum));
+	json_object_object_add(query_ext_ctrl_obj, "maximum", json_object_new_int64(queryextctrl->maximum));
+	json_object_object_add(query_ext_ctrl_obj, "step", json_object_new_uint64(queryextctrl->step));
+	json_object_object_add(query_ext_ctrl_obj, "default_value", json_object_new_int64(queryextctrl->default_value));
+	json_object_object_add(query_ext_ctrl_obj, "flags", json_object_new_uint64(queryextctrl->flags));
+	json_object_object_add(query_ext_ctrl_obj, "flags_str", json_object_new_string(ctrlflags2s(queryextctrl->flags).c_str()));
+	json_object_object_add(query_ext_ctrl_obj, "elem_size", json_object_new_uint64(queryextctrl->elem_size));
+	json_object_object_add(query_ext_ctrl_obj, "elems", json_object_new_uint64(queryextctrl->elems));
+	json_object_object_add(query_ext_ctrl_obj, "nr_of_dims", json_object_new_uint64(queryextctrl->nr_of_dims));
+
+	/* 	__u32   dims[V4L2_CTRL_MAX_DIMS] */
+	json_object *dim_obj = json_object_new_array();
+
+	for (unsigned int i = 0; i < queryextctrl->nr_of_dims; i++)
+		json_object_array_add(dim_obj, json_object_new_uint64(queryextctrl->dims[i]));
+
+	json_object_object_add(query_ext_ctrl_obj, "dims", dim_obj);
+	json_object_object_add(ioctl_args, "v4l2_query_ext_ctrl", query_ext_ctrl_obj);
+}
+
+void trace_ioctl_media(unsigned long request, void *arg, json_object *ioctl_args)
+{
+	if (request ==  MEDIA_IOC_REQUEST_ALLOC) {
+		__s32 *request_fd = static_cast<__s32*>(arg);
+		json_object_object_add(ioctl_args, "request_fd", json_object_new_int(*request_fd));
+	}
+}
+
+void trace_ioctl_video(unsigned long int request, void *arg, json_object *ioctl_args, bool from_userspace)
+{
+	switch (request) {
+	case VIDIOC_QUERYCAP:
+		if (!from_userspace)
+			trace_vidioc_querycap(arg, ioctl_args);
+		break;
+	case VIDIOC_ENUM_FMT:
+		trace_vidioc_enum_fmt(arg, ioctl_args);
+		break;
+	case VIDIOC_G_FMT:
+	case VIDIOC_S_FMT:
+		trace_v4l2_format(arg, ioctl_args);
+		break;
+	case VIDIOC_REQBUFS:
+		trace_v4l2_requestbuffers(arg, ioctl_args);
+		break;
+	case VIDIOC_QUERYBUF:
+	case VIDIOC_QBUF:
+	case VIDIOC_DQBUF:
+		trace_v4l2_buffer(arg, ioctl_args);
+		break;
+	case VIDIOC_EXPBUF:
+		trace_v4l2_exportbuffer(arg, ioctl_args);
+		break;
+	case VIDIOC_STREAMON:
+	case VIDIOC_STREAMOFF:
+		if (from_userspace)
+			trace_vidioc_stream(arg, ioctl_args);
+		break;
+	case VIDIOC_G_EXT_CTRLS:
+	case VIDIOC_S_EXT_CTRLS:
+		trace_v4l2_ext_controls(arg, ioctl_args);
+		break;
+	case VIDIOC_QUERY_EXT_CTRL:
+		trace_vidioc_query_ext_ctrl(arg, ioctl_args);
+		break;
+	default:
+		break;
+	}
+}
+
+void trace_dma_buf_ioctl_sync(void *arg, json_object *ioctl_args)
+{
+	struct dma_buf_sync *sync = static_cast<struct dma_buf_sync*>(arg);
+	json_object *sync_obj = json_object_new_object();
+	json_object_object_add(sync_obj, "flags", json_object_new_uint64(sync->flags));
+	json_object_object_add(sync_obj, "flags_str", json_object_new_string(bufsyncflag2s(sync->flags).c_str()));
+	json_object_object_add(ioctl_args, "dma_buf_sync", sync_obj);
+}
+
+std::string get_ioctl_request_str(unsigned long request)
+{
+	__u8 ioctl_type = _IOC_TYPE(request);
+	switch (ioctl_type) {
+		case 'V':
+			return ioctl2s_video(request);
+		case '|':
+			return ioctl2s_media(request);
+		case 'b':
+			if (request == DMA_BUF_IOCTL_SYNC)
+				return "DMA_BUF_IOCTL_SYNC";
+			break;
+		default:
+			break;
+	}
+	return "unknown ioctl";
+}
+
+json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, bool from_userspace)
+{
+	json_object *ioctl_args = json_object_new_object();
+	__u8 ioctl_type = _IOC_TYPE(request);
+	switch (ioctl_type) {
+		case 'V':
+			trace_ioctl_video(request, arg, ioctl_args, from_userspace);
+			break;
+		case '|':
+			trace_ioctl_media(request, arg, ioctl_args);
+			break;
+		case 'b':
+			if (request == DMA_BUF_IOCTL_SYNC && from_userspace) {
+				trace_dma_buf_ioctl_sync(arg, ioctl_args);
+			}
+			break;
+		default:
+			break;
+	}
+
+	return ioctl_args;
+}
diff --git a/utils/tracer/tracer.cpp b/utils/tracer/tracer.cpp
new file mode 100644
index 00000000..1b84f85d
--- /dev/null
+++ b/utils/tracer/tracer.cpp
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <json.h>
+#include <time.h>
+#include <cstring>
+#include <string>
+
+int main(int argc, char *argv[])
+{
+	int ch;
+	char short_options[] = {'e', 'p', 'r', 'y'};
+
+	if (argc <= 1) {
+		fprintf(stderr, "usage: tracer [-e] [-p] [-r] [-y] <tracee>\n");
+		return -1;
+	}
+
+	do {
+		ch = getopt(argc, argv, short_options);
+		switch (ch){
+		case 'e':
+			setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0);
+			break;
+		case 'p':
+			setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0);
+			break;
+		case 'r':
+			setenv("TRACE_OPTION_DECODED_TO_JSON", "true", 0);
+			break;
+		case 'y':
+			setenv("TRACE_OPTION_CREATE_YUV_FILE", "true", 0);
+			break;
+		}
+	} while (ch != -1);
+
+	/* Get the tracee from the command line. */
+	int count = 0;
+	char *exec_array[argc];
+	while (optind < argc)
+		exec_array[count++] = argv[optind++];
+	exec_array[count] = nullptr;
+
+	/* Use a substring of the time to create a unique id for the trace. */
+	std::string trace_id = std::to_string(time(nullptr));
+	trace_id = trace_id.substr(5, trace_id.npos) + "_trace";
+
+	/* Create the trace file to hold the json-objects as a large json array. */
+	std::string trace_filename = trace_id + ".json";
+	FILE *trace_file = fopen(trace_filename.c_str(), "w");
+	fputs("[\n", trace_file);
+	fflush(trace_file);
+
+	setenv("TRACE_ID", trace_id.c_str(), 0);
+	setenv("LD_PRELOAD", ".libs/libtracer.so", 0);
+
+	if (fork() == 0) {
+		execvpe(exec_array[0], exec_array, environ);
+		perror("Could not execute tracee");
+		return -1;
+	}
+
+	int tracee_result;
+	wait(&tracee_result);
+	unsetenv("TRACE_ID");
+	unsetenv("LD_PRELOAD");
+
+	if (WEXITSTATUS(tracee_result)) {
+		fprintf(stderr, "Trace error: %s\n", trace_filename.c_str());
+		exit(EXIT_FAILURE);
+	}
+
+	/* Close the json-array and the trace file. */
+	trace_file = fopen(trace_filename.c_str(), "r+");
+	fseek(trace_file, 0L, SEEK_END);
+	fseek(trace_file, (ftell(trace_file) - 2), SEEK_SET);
+	std::string end = "\n]\n";
+	fwrite(end.c_str(), sizeof(char), end.length(), trace_file);
+	fclose(trace_file);
+
+	fprintf(stderr, "Trace complete: %s\n", trace_filename.c_str());
+	return 0;
+}
-- 
2.37.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC 2/2] utils: add stateless retracer utility
  2022-08-20  0:50 [RFC 0/2] v4l2 stateless tracer/retracer utilities Deborah Brouwer
  2022-08-20  0:50 ` [RFC 1/2] utils: add stateless tracer utility Deborah Brouwer
@ 2022-08-20  0:50 ` Deborah Brouwer
  2022-08-26 19:05 ` [RFC 0/2] v4l2 stateless tracer/retracer utilities Nicolas Dufresne
  2 siblings, 0 replies; 5+ messages in thread
From: Deborah Brouwer @ 2022-08-20  0:50 UTC (permalink / raw)
  To: linux-media
  Cc: daniel.almeida, nfraprado, nicolas.dufresne, hverkuil-cisco,
	Deborah Brouwer

The retracer utility reads and replays a json-formatted file produced by
the stateless tracer utility. The retracer runs independently from other
userspace applications and so allows a driver to be tested in different
userspace environments. If the trace file is edited to send specific
errors to a driver, the retracer can be used to test a driver's error-
handling abilities.

Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
---
 utils/tracer/Makefile.am        |    6 +-
 utils/tracer/retrace-helper.cpp |  141 ++++
 utils/tracer/retrace-helper.h   |   18 +
 utils/tracer/retrace-vp8.cpp    |  288 ++++++++
 utils/tracer/retrace-vp8.h      |   11 +
 utils/tracer/retracer.cpp       | 1090 +++++++++++++++++++++++++++++++
 utils/tracer/retracer.h         |   24 +
 7 files changed, 1577 insertions(+), 1 deletion(-)
 create mode 100755 utils/tracer/retrace-helper.cpp
 create mode 100644 utils/tracer/retrace-helper.h
 create mode 100755 utils/tracer/retrace-vp8.cpp
 create mode 100644 utils/tracer/retrace-vp8.h
 create mode 100755 utils/tracer/retracer.cpp
 create mode 100644 utils/tracer/retracer.h

diff --git a/utils/tracer/Makefile.am b/utils/tracer/Makefile.am
index f5579198..6cf5f000 100644
--- a/utils/tracer/Makefile.am
+++ b/utils/tracer/Makefile.am
@@ -10,10 +10,14 @@ libtracer_la_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
 libtracer_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -ldl $(JSONC_LIBS)
 libtracer_la_LIBTOOLFLAGS = --tag=disable-static
 
-bin_PROGRAMS = tracer
+bin_PROGRAMS = tracer retracer
 
 tracer_SOURCES = tracer.cpp
 tracer_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
 tracer_LDFLAGS = -lrt -lpthread $(JSONC_LIBS)
 
+retracer_SOURCES = retracer.cpp retrace-helper.cpp retrace-vp8.cpp trace-info.cpp ../common/v4l2-info.cpp
+retracer_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
+retracer_LDFLAGS = -lrt -lpthread  $(JSONC_LIBS)
+
 endif
diff --git a/utils/tracer/retrace-helper.cpp b/utils/tracer/retrace-helper.cpp
new file mode 100755
index 00000000..491bf42d
--- /dev/null
+++ b/utils/tracer/retrace-helper.cpp
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "retracer.h"
+
+struct buffer_retrace {
+	int fd;
+	long int address_trace;
+	long int address_retrace;
+};
+
+struct retrace_context {
+	pthread_mutex_t lock;
+
+	/* Key is a file descriptor from the trace, value is the corresponding fd in the retrace context. */
+	std::unordered_map<int, int> retrace_fds;
+
+	/* List of output and capture buffers being retraced. */
+	std::list<struct buffer_retrace> buffers;
+};
+
+static struct retrace_context ctx_retrace = {
+	.lock = PTHREAD_MUTEX_INITIALIZER
+};
+
+/* Take a buffer's file descriptor and create a new buffer entry in retrace context. */
+void add_buffer_retrace(int fd)
+{
+	struct buffer_retrace buf;
+	memset(&buf, 0, sizeof(buffer_retrace));
+	buf.fd = fd;
+	pthread_mutex_lock(&ctx_retrace.lock);
+	ctx_retrace.buffers.push_front(buf);
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+/*
+ * Use the buffer file descriptor to get a buffer entry from the retrace context.
+ * Add the buffer's memory address from both the trace and retrace context to the buffer entry.
+ */
+void set_buffer_address_retrace(int fd, long address_trace, long address_retrace)
+{
+	pthread_mutex_lock(&ctx_retrace.lock);
+	for (auto it = ctx_retrace.buffers.begin(); it != ctx_retrace.buffers.end(); ++it) {
+		if (it->fd == fd) {
+			it->address_trace = address_trace;
+			it->address_retrace = address_retrace;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+/* Take an address from the trace and return the corresponding address in the retrace context. */
+long int get_buffer_address_retrace(long address_trace)
+{
+	long int address_retrace = 0;
+
+	pthread_mutex_lock(&ctx_retrace.lock);
+	for (auto it = ctx_retrace.buffers.cbegin(); it != ctx_retrace.buffers.cend(); ++it) {
+		if (it->address_trace == address_trace) {
+			address_retrace = it->address_retrace;
+			break;
+		}
+	}
+	pthread_mutex_unlock(&ctx_retrace.lock);
+
+	return address_retrace;
+}
+
+void print_buffers_retrace(void)
+{
+	pthread_mutex_lock(&ctx_retrace.lock);
+	for (auto it = ctx_retrace.buffers.cbegin(); it != ctx_retrace.buffers.cend(); ++it) {
+		fprintf(stderr, "fd: %d, address_trace:%ld, address_retrace:%ld\n",
+				it->fd, it->address_trace, it->address_retrace);
+	}
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+/*
+ * Create a new file descriptor entry in retrace context.
+ * Add both the fd from the trace context and the corresponding fd from the retrace context.
+ */
+void add_fd(int fd_trace, int fd_retrace)
+{
+	std::pair<int, int> new_pair;
+	new_pair = std::make_pair(fd_trace, fd_retrace);
+	pthread_mutex_lock(&ctx_retrace.lock);
+	ctx_retrace.retrace_fds.insert(new_pair);
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+/* Take a file descriptor from the trace and return the corresponding fd in the retrace context. */
+int get_fd(int fd_trace)
+{
+	int fd_retrace = 0;
+	std::unordered_map<int, int>::const_iterator it;
+
+	pthread_mutex_lock(&ctx_retrace.lock);
+	it = ctx_retrace.retrace_fds.find(fd_trace);
+	if (it != ctx_retrace.retrace_fds.end())
+		fd_retrace = it->second;
+	pthread_mutex_unlock(&ctx_retrace.lock);
+
+	return fd_retrace;
+}
+
+/* Using a file descriptor from the trace, find and remove an fd entry from the retrace context.*/
+void remove_fd(int fd_trace)
+{
+	pthread_mutex_lock(&ctx_retrace.lock);
+	ctx_retrace.retrace_fds.erase(fd_trace);
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+void print_fds(void)
+{
+	pthread_mutex_lock(&ctx_retrace.lock);
+	if (ctx_retrace.retrace_fds.empty())
+		fprintf(stderr, "all devices closed\n");
+	for ( auto it = ctx_retrace.retrace_fds.cbegin(); it != ctx_retrace.retrace_fds.cend(); ++it )
+		fprintf(stderr, "fd_trace: %d, fd_retrace: %d\n", it->first, it->second);
+	pthread_mutex_unlock(&ctx_retrace.lock);
+}
+
+void print_context(void)
+{
+	print_fds();
+	print_buffers_retrace();
+	fprintf(stderr, "\n");
+}
+
+int retrace_v4l2_ext_control_value(json_object *ctrl_obj)
+{
+	json_object *value_obj;
+	json_object_object_get_ex(ctrl_obj, "value", &value_obj);
+	return json_object_get_int(value_obj);
+}
diff --git a/utils/tracer/retrace-helper.h b/utils/tracer/retrace-helper.h
new file mode 100644
index 00000000..3634d2a7
--- /dev/null
+++ b/utils/tracer/retrace-helper.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#ifndef RETRACE_HELPER_H
+#define RETRACE_HELPER_H
+
+void add_buffer_retrace(int fd);
+void set_buffer_address_retrace(int fd, long address_trace, long address_retrace);
+long int get_buffer_address_retrace(long address_trace);
+int get_fd(int fd_trace);
+void add_fd(int fd_trace, int fd_retrace);
+void remove_fd(int fd_trace);
+void print_context(void);
+int retrace_v4l2_ext_control_value(json_object *ctrl_obj);
+
+#endif
diff --git a/utils/tracer/retrace-vp8.cpp b/utils/tracer/retrace-vp8.cpp
new file mode 100755
index 00000000..5b893f19
--- /dev/null
+++ b/utils/tracer/retrace-vp8.cpp
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "retracer.h"
+
+struct v4l2_vp8_loop_filter retrace_v4l2_vp8_loop_filter(json_object *lf_obj)
+{
+	struct v4l2_vp8_loop_filter lf;
+	memset(&lf, 0, sizeof(lf));
+
+	/* __s8 ref_frm_delta[4] */
+	json_object *ref_frm_delta_obj;
+	json_object_object_get_ex(lf_obj, "ref_frm_delta", &ref_frm_delta_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(lf.ref_frm_delta); i++)
+		lf.ref_frm_delta[i] = (__s8) json_object_get_int(json_object_array_get_idx(ref_frm_delta_obj, i));
+
+	/* __s8 mb_mode_delta[4] */
+	json_object *mb_mode_delta_obj;
+	json_object_object_get_ex(lf_obj, "mb_mode_delta", &mb_mode_delta_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(lf.mb_mode_delta); i++)
+		lf.mb_mode_delta[i] = (__s8) json_object_get_int(json_object_array_get_idx(mb_mode_delta_obj, i));
+
+	json_object *sharpness_level_obj;
+	json_object_object_get_ex(lf_obj, "sharpness_level", &sharpness_level_obj);
+	lf.sharpness_level = json_object_get_int(sharpness_level_obj);
+
+	json_object *level_obj;
+	json_object_object_get_ex(lf_obj, "level", &level_obj);
+	lf.level = json_object_get_int(level_obj);
+
+	json_object *padding_obj;
+	json_object_object_get_ex(lf_obj, "padding", &padding_obj);
+	lf.padding = json_object_get_int(padding_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(lf_obj, "flags", &flags_obj);
+	lf.flags = json_object_get_int(flags_obj);
+
+	return lf;
+}
+
+struct v4l2_vp8_segment retrace_v4l2_vp8_segment(json_object *segment_obj)
+{
+	struct v4l2_vp8_segment segment;
+	memset(&segment, 0, sizeof(v4l2_vp8_segment));
+
+	/*	__s8 quant_update[4] */
+	json_object *quant_update_obj;
+	json_object_object_get_ex(segment_obj, "quant_update", &quant_update_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(segment.quant_update); i++)
+		segment.quant_update[i] = (__s8) json_object_get_int(json_object_array_get_idx(quant_update_obj, i));
+
+	/* __s8 lf_update[4] */
+	json_object *lf_update_obj;
+	json_object_object_get_ex(segment_obj, "lf_update", &lf_update_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(segment.lf_update); i++)
+		segment.lf_update[i] = (__s8) json_object_get_int(json_object_array_get_idx(lf_update_obj, i));
+
+	/* __u8 segment_probs[3] */
+	json_object *segment_probs_obj;
+	json_object_object_get_ex(segment_obj, "segment_probs", &segment_probs_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(segment.segment_probs); i++)
+		segment.segment_probs[i] = json_object_get_int(json_object_array_get_idx(segment_probs_obj, i));
+
+	json_object *padding_obj;
+	json_object_object_get_ex(segment_obj, "padding", &padding_obj);
+	segment.padding = json_object_get_int(padding_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(segment_obj, "flags", &flags_obj);
+	segment.flags = json_object_get_int(flags_obj);
+
+	return segment;
+}
+
+struct v4l2_vp8_quantization retrace_v4l2_vp8_quantization(json_object *quant_obj)
+{
+	struct v4l2_vp8_quantization quant;
+	memset(&quant, 0, sizeof(quant));
+
+	json_object *y_ac_qi_obj;
+	json_object_object_get_ex(quant_obj, "y_ac_qi", &y_ac_qi_obj);
+	quant.y_ac_qi = json_object_get_int(y_ac_qi_obj);
+
+	json_object *y_dc_delta_obj;
+	json_object_object_get_ex(quant_obj, "y_dc_delta", &y_dc_delta_obj);
+	quant.y_dc_delta = (__s8) json_object_get_int(y_dc_delta_obj);
+
+	json_object *y2_dc_delta_obj;
+	json_object_object_get_ex(quant_obj, "y2_dc_delta", &y2_dc_delta_obj);
+	quant.y2_dc_delta = (__s8) json_object_get_int(y2_dc_delta_obj);
+
+	json_object *y2_ac_delta_obj;
+	json_object_object_get_ex(quant_obj, "y2_ac_delta", &y2_ac_delta_obj);
+	quant.y2_ac_delta = (__s8) json_object_get_int(y2_ac_delta_obj);
+
+	json_object *uv_dc_delta_obj;
+	json_object_object_get_ex(quant_obj, "uv_dc_delta", &uv_dc_delta_obj);
+	quant.uv_dc_delta = (__s8) json_object_get_int(uv_dc_delta_obj);
+
+	json_object *uv_ac_delta_obj;
+	json_object_object_get_ex(quant_obj, "uv_ac_delta", &uv_ac_delta_obj);
+	quant.uv_ac_delta = (__s8) json_object_get_int(uv_ac_delta_obj);
+
+	json_object *padding_obj;
+	json_object_object_get_ex(quant_obj, "padding", &padding_obj);
+	quant.padding = json_object_get_int(padding_obj);
+
+	return quant;
+}
+
+struct v4l2_vp8_entropy retrace_v4l2_vp8_entropy(json_object *entropy_obj)
+{
+	struct v4l2_vp8_entropy entropy;
+	memset(&entropy, 0, sizeof(entropy));
+
+	int count = 0;
+
+	/* __u8 coeff_probs[4][8][3][V4L2_VP8_COEFF_PROB_CNT] */
+	json_object *coeff_probs_obj;
+	json_object_object_get_ex(entropy_obj, "coeff_probs", &coeff_probs_obj);
+
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.coeff_probs); i++)
+		for (size_t j = 0; j < ARRAY_SIZE(entropy.coeff_probs[0]); j++)
+			for (size_t k = 0; k < ARRAY_SIZE(entropy.coeff_probs[0][0]); k++)
+				for (size_t l = 0; l < V4L2_VP8_COEFF_PROB_CNT; l++)
+					entropy.coeff_probs[i][j][k][l] = json_object_get_int(json_object_array_get_idx(coeff_probs_obj, count++));
+
+	/* __u8 y_mode_probs[4] */
+	json_object *y_mode_probs_obj;
+	json_object_object_get_ex(entropy_obj, "y_mode_probs", &y_mode_probs_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.y_mode_probs); i++)
+		entropy.y_mode_probs[i] = json_object_get_int(json_object_array_get_idx(y_mode_probs_obj, i));
+
+	/* __u8 uv_mode_probs[3] */
+	json_object *uv_mode_probs_obj;
+	json_object_object_get_ex(entropy_obj, "uv_mode_probs", &uv_mode_probs_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.uv_mode_probs); i++)
+		entropy.uv_mode_probs[i] = json_object_get_int(json_object_array_get_idx(uv_mode_probs_obj, i));
+
+	/*  __u8 mv_probs[2][V4L2_VP8_MV_PROB_CNT] */
+	count = 0;
+	json_object *mv_probs_obj;
+	json_object_object_get_ex(entropy_obj, "mv_probs", &mv_probs_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.mv_probs); i++)
+		for (size_t j = 0; j < V4L2_VP8_MV_PROB_CNT; j++)
+			entropy.mv_probs[i][j] = json_object_get_int(json_object_array_get_idx(mv_probs_obj, count++));
+
+	/* __u8 padding[3] */
+	json_object *padding_obj;
+	json_object_object_get_ex(entropy_obj, "padding", &padding_obj);
+	for (size_t i = 0; i < ARRAY_SIZE(entropy.padding); i++)
+		entropy.padding[i] = json_object_get_int(json_object_array_get_idx(padding_obj, i));
+
+	return entropy;
+}
+
+struct v4l2_vp8_entropy_coder_state retrace_v4l2_vp8_entropy_coder_state(json_object *coder_state_obj)
+{
+	struct v4l2_vp8_entropy_coder_state coder_state;
+	memset(&coder_state, 0, sizeof(coder_state));
+
+	json_object *range_obj;
+	json_object_object_get_ex(coder_state_obj, "range", &range_obj);
+	coder_state.range = json_object_get_int(range_obj);
+
+	json_object *value_obj;
+	json_object_object_get_ex(coder_state_obj, "value", &value_obj);
+	coder_state.value = json_object_get_int(value_obj);
+
+	json_object *bit_count_obj;
+	json_object_object_get_ex(coder_state_obj, "bit_count", &bit_count_obj);
+	coder_state.bit_count = json_object_get_int(bit_count_obj);
+
+	json_object *padding_obj;
+	json_object_object_get_ex(coder_state_obj, "padding", &padding_obj);
+	coder_state.padding = json_object_get_int(padding_obj);
+
+	return coder_state;
+}
+
+struct v4l2_ctrl_vp8_frame *retrace_v4l2_ctrl_vp8_frame_pointer(json_object *ctrl_obj)
+{
+	struct v4l2_ctrl_vp8_frame *vp8_frame_pointer = (struct v4l2_ctrl_vp8_frame *) malloc(sizeof(v4l2_ctrl_vp8_frame));
+	memset(vp8_frame_pointer, 0, sizeof(v4l2_ctrl_vp8_frame));
+
+	json_object *vp8_frame_obj;
+	json_object_object_get_ex(ctrl_obj, "v4l2_ctrl_vp8_frame", &vp8_frame_obj);
+
+	/* struct v4l2_vp8_segment segment */
+	json_object *segment_obj;
+	json_object_object_get_ex(vp8_frame_obj, "segment", &segment_obj);
+	vp8_frame_pointer->segment = retrace_v4l2_vp8_segment(segment_obj);
+
+	/* struct v4l2_vp8_loop_filter lf */
+	json_object *lf_obj;
+	json_object_object_get_ex(vp8_frame_obj, "lf", &lf_obj);
+	vp8_frame_pointer->lf = retrace_v4l2_vp8_loop_filter(lf_obj);
+
+	/* struct v4l2_vp8_quantization quant */
+	json_object *quant_obj;
+	json_object_object_get_ex(vp8_frame_obj, "quant", &quant_obj);
+	vp8_frame_pointer->quant = retrace_v4l2_vp8_quantization(quant_obj);
+
+	/* struct v4l2_vp8_entropy entropy */
+	json_object *entropy_obj;
+	json_object_object_get_ex(vp8_frame_obj, "entropy", &entropy_obj);
+	vp8_frame_pointer->entropy = retrace_v4l2_vp8_entropy(entropy_obj);
+
+	/* struct v4l2_vp8_entropy_coder_state coder_state */
+	json_object *coder_state_obj;
+	json_object_object_get_ex(vp8_frame_obj, "coder_state", &coder_state_obj);
+	vp8_frame_pointer->coder_state = retrace_v4l2_vp8_entropy_coder_state(coder_state_obj);
+
+	json_object *width_obj;
+	json_object_object_get_ex(vp8_frame_obj, "width", &width_obj);
+	vp8_frame_pointer->width = json_object_get_int(width_obj);
+
+	json_object *height_obj;
+	json_object_object_get_ex(vp8_frame_obj, "height", &height_obj);
+	vp8_frame_pointer->height = json_object_get_int(height_obj);
+
+	json_object *horizontal_scale_obj;
+	json_object_object_get_ex(vp8_frame_obj, "horizontal_scale", &horizontal_scale_obj);
+	vp8_frame_pointer->horizontal_scale = json_object_get_int(horizontal_scale_obj);
+
+	json_object *vertical_scale_obj;
+	json_object_object_get_ex(vp8_frame_obj, "vertical_scale", &vertical_scale_obj);
+	vp8_frame_pointer->vertical_scale = json_object_get_int(vertical_scale_obj);
+
+	json_object *version_obj;
+	json_object_object_get_ex(vp8_frame_obj, "version", &version_obj);
+	vp8_frame_pointer->version = json_object_get_int(version_obj);
+
+	json_object *prob_skip_false_obj;
+	json_object_object_get_ex(vp8_frame_obj, "prob_skip_false", &prob_skip_false_obj);
+	vp8_frame_pointer->prob_skip_false = json_object_get_int(prob_skip_false_obj);
+
+	json_object *prob_intra_obj;
+	json_object_object_get_ex(vp8_frame_obj, "prob_intra", &prob_intra_obj);
+	vp8_frame_pointer->prob_intra = json_object_get_int(prob_intra_obj);
+
+	json_object *prob_last_obj;
+	json_object_object_get_ex(vp8_frame_obj, "prob_last", &prob_last_obj);
+	vp8_frame_pointer->prob_last = json_object_get_int(prob_last_obj);
+
+	json_object *prob_gf_obj;
+	json_object_object_get_ex(vp8_frame_obj, "prob_gf", &prob_gf_obj);
+	vp8_frame_pointer->prob_gf = json_object_get_int(prob_gf_obj);
+
+	json_object *num_dct_parts_obj;
+	json_object_object_get_ex(vp8_frame_obj, "num_dct_parts", &num_dct_parts_obj);
+	vp8_frame_pointer->num_dct_parts = json_object_get_int(num_dct_parts_obj);
+
+	json_object *first_part_size_obj;
+	json_object_object_get_ex(vp8_frame_obj, "first_part_size", &first_part_size_obj);
+	vp8_frame_pointer->first_part_size = json_object_get_int(first_part_size_obj);
+
+	json_object *first_part_header_bits_obj;
+	json_object_object_get_ex(vp8_frame_obj, "first_part_header_bits", &first_part_header_bits_obj);
+	vp8_frame_pointer->first_part_header_bits = json_object_get_int(first_part_header_bits_obj);
+
+	/* __u32 dct_part_sizes[8] */
+	json_object *dct_part_sizes_obj;
+	json_object_object_get_ex(vp8_frame_obj, "dct_part_sizes", &dct_part_sizes_obj);
+	for (int i = 0; i < 8; i++)
+		vp8_frame_pointer->dct_part_sizes[i] = json_object_get_int(json_object_array_get_idx(dct_part_sizes_obj, i));
+
+	json_object *last_frame_ts_obj;
+	json_object_object_get_ex(vp8_frame_obj, "last_frame_ts", &last_frame_ts_obj);
+	vp8_frame_pointer->last_frame_ts = json_object_get_int(last_frame_ts_obj);
+
+	json_object *golden_frame_ts_obj;
+	json_object_object_get_ex(vp8_frame_obj, "golden_frame_ts", &golden_frame_ts_obj);
+	vp8_frame_pointer->golden_frame_ts = json_object_get_int(golden_frame_ts_obj);
+
+	json_object *alt_frame_ts_obj;
+	json_object_object_get_ex(vp8_frame_obj, "alt_frame_ts", &alt_frame_ts_obj);
+	vp8_frame_pointer->alt_frame_ts = json_object_get_int(alt_frame_ts_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(vp8_frame_obj, "flags", &flags_obj);
+	vp8_frame_pointer->flags = json_object_get_int(flags_obj);
+
+	return vp8_frame_pointer;
+}
\ No newline at end of file
diff --git a/utils/tracer/retrace-vp8.h b/utils/tracer/retrace-vp8.h
new file mode 100644
index 00000000..a5bea25e
--- /dev/null
+++ b/utils/tracer/retrace-vp8.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#ifndef RETRACE_VP8_H
+#define RETRACE_VP8_H
+
+struct v4l2_ctrl_vp8_frame *retrace_v4l2_ctrl_vp8_frame_pointer(json_object *ctrl_obj);
+
+#endif
diff --git a/utils/tracer/retracer.cpp b/utils/tracer/retracer.cpp
new file mode 100755
index 00000000..049761f4
--- /dev/null
+++ b/utils/tracer/retracer.cpp
@@ -0,0 +1,1090 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#include "retracer.h"
+
+bool debug = false;
+std::string retrace_filename;
+std::string dev_path_video;
+std::string dev_path_media;
+
+void retrace_mmap64(json_object *mmap_obj)
+{
+	json_object *mmap64_args_obj;
+	json_object_object_get_ex(mmap_obj, "mmap64_args", &mmap64_args_obj);
+
+	json_object *len_obj;
+	json_object_object_get_ex(mmap64_args_obj, "len", &len_obj);
+	size_t len = (size_t) json_object_get_int(len_obj);
+
+	json_object *prot_obj;
+	json_object_object_get_ex(mmap64_args_obj, "prot", &prot_obj);
+	int prot = json_object_get_int(prot_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(mmap64_args_obj, "flags", &flags_obj);
+	int flags = json_object_get_int(flags_obj);
+
+	json_object *fildes_obj;
+	json_object_object_get_ex(mmap64_args_obj, "fildes", &fildes_obj);
+	int buf_fd_trace = json_object_get_int(fildes_obj);
+
+	json_object *off_obj;
+	json_object_object_get_ex(mmap64_args_obj, "off", &off_obj);
+	off_t off = (off_t) json_object_get_int(off_obj);
+
+	/* Only retrace mmap64 calls that map a buffer. */
+	int buf_fd_retrace = get_fd(buf_fd_trace);
+	if (buf_fd_retrace < 0)
+		return;
+
+	void *buf_address_retrace_pointer = mmap64(0, len, prot, flags, buf_fd_retrace, off);
+	long int buf_address_retrace = (long int) buf_address_retrace_pointer;
+
+	if (buf_address_retrace_pointer == MAP_FAILED) {
+		perror("mmap64");
+		print_context();
+		exit(EXIT_FAILURE);
+	}
+
+	/* Store the original trace address so that it can be matched with the munmap address later. */
+	json_object *buffer_address_obj;
+	json_object_object_get_ex(mmap_obj, "buffer_address", &buffer_address_obj);
+	long int buf_address_trace = json_object_get_int64(buffer_address_obj);
+	set_buffer_address_retrace(buf_fd_retrace, buf_address_trace, buf_address_retrace);
+
+	if (debug || (errno != 0)) {
+		perror("mmap64 ");
+		fprintf(stderr, "fd: %d\n", buf_fd_retrace);
+		print_context();
+	}
+}
+
+void retrace_munmap(json_object *syscall_obj)
+{
+	json_object *munmap_args_obj;
+	json_object_object_get_ex(syscall_obj, "munmap_args", &munmap_args_obj);
+
+	json_object *start_obj;
+	json_object_object_get_ex(munmap_args_obj, "start", &start_obj);
+	long int start = json_object_get_int64(start_obj);
+
+	json_object *length_obj;
+	json_object_object_get_ex(munmap_args_obj, "length", &length_obj);
+	size_t length = (size_t) json_object_get_int(length_obj);
+
+	long int buffer_address_retrace = get_buffer_address_retrace(start);
+
+	if (buffer_address_retrace < 0)
+		return;
+
+	munmap((void *)buffer_address_retrace, length);
+
+	if (debug || (errno != 0)) {
+		perror("munmap");
+		fprintf(stderr, "unmapped: %ld\n", buffer_address_retrace);
+		fprintf(stderr, "\n");
+	}
+}
+
+void retrace_open64(json_object *jobj)
+{
+	json_object *fd_trace_obj;
+	json_object_object_get_ex(jobj, "fd", &fd_trace_obj);
+	int fd_trace = json_object_get_int(fd_trace_obj);
+
+	json_object *open_args_obj;
+	json_object_object_get_ex(jobj, "open_args", &open_args_obj);
+
+	json_object *path_obj;
+	json_object_object_get_ex(open_args_obj, "path", &path_obj);
+	std::string path = json_object_get_string(path_obj);
+
+	json_object *oflag_obj;
+	json_object_object_get_ex(open_args_obj, "oflag", &oflag_obj);
+	int oflag = json_object_get_int(oflag_obj);
+
+	json_object *mode_obj;
+	json_object_object_get_ex(open_args_obj, "mode", &mode_obj);
+	int mode = json_object_get_int(mode_obj);
+
+	/* If a device is provided on the command line, use it instead of the device from the trace file. */
+	if ((path.find("video") != path.npos) && !dev_path_video.empty())
+		path = dev_path_video;
+
+	if ((path.find("media") != path.npos) && !dev_path_media.empty())
+		path = dev_path_media;
+
+	int fd_retrace = open64(path.c_str(), oflag, mode);
+
+	if (fd_retrace < 0) {
+		fprintf(stderr, "Cannot open: %s\n", path.c_str());
+		exit(fd_retrace);
+	}
+
+	add_fd(fd_trace, fd_retrace);
+
+	if (debug || (errno != 0)) {
+		perror("open64");
+		fprintf(stderr, "opened: %s \n", path.c_str());
+		print_context();
+	}
+}
+
+void retrace_close(json_object *jobj)
+{
+	json_object *fd_trace_obj;
+	json_object_object_get_ex(jobj, "fd", &fd_trace_obj);
+	int fd_retrace = get_fd(json_object_get_int(fd_trace_obj));
+
+	/* Only close devices that were opened in the retrace context. */
+	if (fd_retrace) {
+		close(fd_retrace);
+		remove_fd(json_object_get_int(fd_trace_obj));
+
+		if (debug || (errno != 0)) {
+			perror("close");
+			fprintf(stderr, "fd: %d\n\n", fd_retrace);
+			print_context();
+		}
+	}
+}
+
+struct v4l2_requestbuffers retrace_v4l2_requestbuffers(json_object *ioctl_args)
+{
+	struct v4l2_requestbuffers request_buffers;
+	memset(&request_buffers, 0, sizeof(v4l2_requestbuffers));
+
+	json_object *requestbuffers_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_requestbuffers", &requestbuffers_obj);
+
+	json_object *count_obj;
+	json_object_object_get_ex(requestbuffers_obj, "count", &count_obj);
+	request_buffers.count = json_object_get_int(count_obj);
+
+	json_object *type_obj;
+	json_object_object_get_ex(requestbuffers_obj, "type", &type_obj);
+	request_buffers.type = json_object_get_int(type_obj);
+
+	json_object *memory_obj;
+	json_object_object_get_ex(requestbuffers_obj, "memory", &memory_obj);
+	request_buffers.memory = json_object_get_int(memory_obj);
+
+	json_object *capabilities_obj;
+	json_object_object_get_ex(requestbuffers_obj, "capabilities", &capabilities_obj);
+	request_buffers.capabilities = json_object_get_int(capabilities_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(requestbuffers_obj, "flags", &flags_obj);
+	request_buffers.flags = json_object_get_int(flags_obj);
+
+	return request_buffers;
+}
+
+void retrace_vidioc_reqbufs(int fd_retrace, json_object *ioctl_args)
+{
+	struct v4l2_requestbuffers request_buffers = retrace_v4l2_requestbuffers(ioctl_args);
+
+	ioctl(fd_retrace, VIDIOC_REQBUFS, &request_buffers);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_REQBUFS");
+		fprintf(stderr, "type: %s, request_buffers.count: %d\n",
+		        buftype2s(request_buffers.type).c_str(), request_buffers.count);
+		print_context();
+	}
+}
+
+struct v4l2_plane *retrace_v4l2_plane(json_object *plane_obj, __u32 memory)
+{
+	struct v4l2_plane *pl = (struct v4l2_plane *) malloc(sizeof(v4l2_plane));
+
+	json_object *bytesused_obj;
+	json_object_object_get_ex(plane_obj, "bytesused", &bytesused_obj);
+	pl->bytesused = (__u32) json_object_get_int(bytesused_obj);
+
+	json_object *length_obj;
+	json_object_object_get_ex(plane_obj, "length", &length_obj);
+	pl->length = (__u32) json_object_get_int(length_obj);
+
+	json_object *m_obj;
+	json_object_object_get_ex(plane_obj, "m", &m_obj);
+	/* https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/buffer.html#c.V4L.v4l2_plane */
+	if (memory == V4L2_MEMORY_MMAP) {
+		json_object *mem_offset_obj;
+		json_object_object_get_ex(m_obj, "mem_offset", &mem_offset_obj);
+		pl->m.mem_offset = (__u32) json_object_get_int(mem_offset_obj);
+	}
+
+	json_object *data_offset_obj;
+	json_object_object_get_ex(plane_obj, "data_offset", &data_offset_obj);
+	pl->data_offset = (__u32) json_object_get_int(data_offset_obj);
+
+	return pl;
+}
+
+struct v4l2_buffer retrace_v4l2_buffer(json_object *ioctl_args)
+{
+	struct v4l2_buffer buf;
+	memset(&buf, 0, sizeof(v4l2_buffer));
+
+	json_object *buf_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_buffer", &buf_obj);
+
+	/* Index of the buffer. */
+	json_object *index_obj;
+	json_object_object_get_ex(buf_obj, "index", &index_obj);
+	buf.index = (__u32) json_object_get_int(index_obj);
+
+	/* Type of the buffer. */
+	json_object *type_obj;
+	json_object_object_get_ex(buf_obj, "type", &type_obj);
+	buf.type = (__u32) json_object_get_int(type_obj);
+
+	/* For multiplanar formats this bytesused field is ignored and the planes pointer is used instead. */
+	json_object *bytesused_obj;
+	json_object_object_get_ex(buf_obj, "bytesused", &bytesused_obj);
+	buf.bytesused = (__u32) json_object_get_int(bytesused_obj);
+
+	/* Flags set by application and modified by driver. */
+	json_object *flags_obj;
+	json_object_object_get_ex(buf_obj, "flags", &flags_obj);
+	buf.flags = (__u32) json_object_get_int(flags_obj);
+
+	/* Applications set the field for output, drivers set the field for capture. */
+	json_object *field_obj;
+	json_object_object_get_ex(buf_obj, "field", &field_obj);
+	buf.field = (__u32) json_object_get_int(field_obj);
+
+	json_object *timestamp_obj;
+	json_object_object_get_ex(buf_obj, "timestamp", &timestamp_obj);
+
+	struct timeval tv;
+	memset(&tv, 0, sizeof(timeval));
+	json_object *tv_sec_obj;
+	json_object_object_get_ex(timestamp_obj, "tv_sec", &tv_sec_obj);
+	tv.tv_sec = json_object_get_int(tv_sec_obj);
+	json_object *tv_usec_obj;
+	json_object_object_get_ex(timestamp_obj, "tv_usec", &tv_usec_obj);
+	tv.tv_usec = json_object_get_int(tv_usec_obj);
+	buf.timestamp = tv;
+
+	/* Sequence set by the driver. */
+	json_object *sequence_obj;
+	json_object_object_get_ex(buf_obj, "sequence", &sequence_obj);
+	buf.sequence = (__u32) json_object_get_int(sequence_obj);
+
+	json_object *memory_obj;
+	json_object_object_get_ex(buf_obj, "memory", &memory_obj);
+	buf.memory = (__u32) json_object_get_int(memory_obj);
+
+	/* Get the length before the m union, since the length holds the number of planes. */
+	json_object *length_obj;
+	json_object_object_get_ex(buf_obj, "length", &length_obj);
+	buf.length = (__u32) json_object_get_int(length_obj);
+
+	json_object *m_obj;
+	json_object_object_get_ex(buf_obj, "m", &m_obj);
+	if (buf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
+		buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		json_object *planes_obj;
+		json_object_object_get_ex(m_obj, "planes", &planes_obj);
+		json_object *plane_obj = json_object_array_get_idx(planes_obj, 0); /* TODO add planes > 0 */
+		buf.m.planes = retrace_v4l2_plane(plane_obj, buf.memory);
+	}
+
+	/* For memory-to-memory devices, applications use requests only for output buffers. */
+	if (buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		json_object *request_fd_obj;
+		json_object_object_get_ex(buf_obj, "request_fd", &request_fd_obj);
+		buf.request_fd = (__s32) get_fd(json_object_get_int(request_fd_obj));
+	}
+
+	return buf;
+}
+
+void retrace_vidioc_querybuf(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct v4l2_buffer buf = retrace_v4l2_buffer(ioctl_args_user);
+
+	ioctl(fd_retrace, VIDIOC_QUERYBUF, &buf);
+
+	if (buf.m.planes != nullptr)
+		free(buf.m.planes);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_QUERYBUF");
+		fprintf(stderr, "buf.type: %s, buf.index: %d, fd_retrace: %d, \n",
+		        buftype2s(buf.type).c_str(), buf.index, fd_retrace);
+		print_context();
+	}
+}
+
+
+void retrace_vidioc_qbuf(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct v4l2_buffer buf = retrace_v4l2_buffer(ioctl_args_user);
+
+	ioctl(fd_retrace, VIDIOC_QBUF, &buf);
+
+	if (buf.m.planes != nullptr)
+		free(buf.m.planes);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_QBUF");
+		fprintf(stderr, "buf.type: %s, buf.index: %d, fd_retrace: %d, \n",
+		        buftype2s(buf.type).c_str(), buf.index, fd_retrace);
+		print_context();
+	}
+}
+
+struct v4l2_exportbuffer retrace_v4l2_exportbuffer(json_object *ioctl_args)
+{
+	struct v4l2_exportbuffer export_buffer;
+	memset(&export_buffer, 0, sizeof(v4l2_exportbuffer));
+
+	json_object *exportbuffer_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_exportbuffer", &exportbuffer_obj);
+
+	json_object *type_obj;
+	json_object_object_get_ex(exportbuffer_obj, "type", &type_obj);
+	export_buffer.type = json_object_get_int(type_obj);
+
+	json_object *index_obj;
+	json_object_object_get_ex(exportbuffer_obj, "index", &index_obj);
+	export_buffer.index = json_object_get_int(index_obj);
+
+	json_object *plane_obj;
+	json_object_object_get_ex(exportbuffer_obj, "plane", &plane_obj);
+	export_buffer.plane = json_object_get_int(plane_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(exportbuffer_obj, "flags", &flags_obj);
+	export_buffer.flags = json_object_get_int(flags_obj);
+
+	json_object *fd_obj;
+	json_object_object_get_ex(exportbuffer_obj, "fd", &fd_obj);
+	export_buffer.fd = json_object_get_int(fd_obj);
+
+	return export_buffer;
+}
+
+void retrace_vidioc_expbuf(int fd_retrace, json_object *ioctl_args_user, json_object *ioctl_args_driver)
+{
+	struct v4l2_exportbuffer export_buffer;
+	memset(&export_buffer, 0, sizeof(v4l2_exportbuffer));
+
+	export_buffer = retrace_v4l2_exportbuffer(ioctl_args_user);
+	ioctl(fd_retrace, VIDIOC_EXPBUF, &export_buffer);
+	int buf_fd_retrace = export_buffer.fd;
+	add_buffer_retrace(buf_fd_retrace);
+
+	/*
+	 * Get the export buffer file descriptor as provided by the driver in the original trace context.
+	 * Then associate this original file descriptor with the current file descriptor in the retrace context.
+	 */
+	memset(&export_buffer, 0, sizeof(v4l2_exportbuffer));
+	export_buffer = retrace_v4l2_exportbuffer(ioctl_args_driver);
+	int buf_fd_trace = export_buffer.fd;
+	add_fd(buf_fd_trace, buf_fd_retrace);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_EXPBUF");
+		fprintf(stderr, "type: %s \n", buftype2s(export_buffer.type).c_str());
+		fprintf(stderr, "index: %d, fd: %d\n", export_buffer.index, buf_fd_retrace);
+		print_context();
+	}
+}
+
+void retrace_vidioc_dqbuf(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct v4l2_buffer buf = retrace_v4l2_buffer(ioctl_args_user);
+
+	struct pollfd *pfds = (struct pollfd *) calloc(1, sizeof(struct pollfd));
+	if (pfds == NULL)
+		exit(EXIT_FAILURE);
+	pfds[0].fd = fd_retrace;
+	pfds[0].events = POLLIN;
+	poll(pfds, 1, 5000);
+	free(pfds);
+
+	ioctl(fd_retrace, VIDIOC_DQBUF, &buf);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_DQBUF");
+		fprintf(stderr, "fd_retrace: %d\n", fd_retrace);
+		fprintf(stderr, "buf.index: %d\n", buf.index);
+		fprintf(stderr, "buf.type: %s,\n", buftype2s(buf.type).c_str());
+		fprintf(stderr, "buf.bytesused: %u, \n", buf.bytesused);
+		fprintf(stderr, "buf.flags: %u\n", buf.flags);
+		fprintf(stderr, "buf.field: %u, buf.request_fd: %d\n", buf.field, buf.request_fd);
+		fprintf(stderr, "buf.request_fd: %d\n", buf.request_fd);
+		print_context();
+	}
+
+	if (buf.m.planes != nullptr)
+		free(buf.m.planes);
+}
+
+void retrace_vidioc_streamon(int fd_retrace, json_object *ioctl_args)
+{
+	json_object *buf_type_obj;
+	json_object_object_get_ex(ioctl_args, "buf_type", &buf_type_obj);
+	v4l2_buf_type buf_type = (v4l2_buf_type) json_object_get_int(buf_type_obj);
+
+	ioctl(fd_retrace, VIDIOC_STREAMON, &buf_type);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_STREAMON");
+		fprintf(stderr, "buftype: %s\n\n", buftype2s(buf_type).c_str());
+	}
+}
+
+void retrace_vidioc_streamoff(int fd_retrace, json_object *ioctl_args)
+{
+	json_object *buf_type_obj;
+	json_object_object_get_ex(ioctl_args, "buf_type", &buf_type_obj);
+	v4l2_buf_type buf_type = (v4l2_buf_type) json_object_get_int(buf_type_obj);
+
+	ioctl(fd_retrace, VIDIOC_STREAMOFF, &buf_type);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_STREAMOFF");
+		fprintf(stderr, "buftype: %s\n", buftype2s(buf_type).c_str());
+		fprintf(stderr, "\n");
+	}
+}
+
+struct v4l2_plane_pix_format get_v4l2_plane_pix_format(json_object *pix_mp_obj, int plane)
+{
+	std::string key;
+	struct v4l2_plane_pix_format plane_fmt;
+	memset(&plane_fmt, 0, sizeof(v4l2_plane_pix_format));
+
+	json_object *plane_fmt_obj, *sizeimage_obj, *bytesperline_obj;
+
+	key = "v4l2_plane_pix_format_";
+	key += std::to_string(plane);
+	json_object_object_get_ex(pix_mp_obj, key.c_str(), &plane_fmt_obj);
+
+	json_object_object_get_ex(plane_fmt_obj, "sizeimage", &sizeimage_obj);
+	plane_fmt.sizeimage = json_object_get_int(sizeimage_obj);
+
+	json_object_object_get_ex(plane_fmt_obj, "bytesperline", &bytesperline_obj);
+	plane_fmt.bytesperline = json_object_get_int(bytesperline_obj);
+
+	return plane_fmt;
+}
+
+struct v4l2_pix_format_mplane retrace_v4l2_pix_format_mplane(json_object *v4l2_format_obj)
+{
+	struct v4l2_pix_format_mplane pix_mp;
+	memset(&pix_mp, 0, sizeof(v4l2_pix_format_mplane));
+
+	json_object *pix_mp_obj;
+	json_object_object_get_ex(v4l2_format_obj, "v4l2_pix_format_mplane", &pix_mp_obj);
+
+	json_object *width_obj;
+	json_object_object_get_ex(pix_mp_obj, "width", &width_obj);
+	pix_mp.width = json_object_get_int(width_obj);
+
+	json_object *height_obj;
+	json_object_object_get_ex(pix_mp_obj, "height", &height_obj);
+	pix_mp.height = json_object_get_int(height_obj);
+
+	json_object *pixelformat_obj;
+	json_object_object_get_ex(pix_mp_obj, "pixelformat", &pixelformat_obj);
+	pix_mp.pixelformat = json_object_get_int(pixelformat_obj);
+
+	json_object *field_obj;
+	json_object_object_get_ex(pix_mp_obj, "field", &field_obj);
+	pix_mp.field = json_object_get_int(field_obj);
+
+	json_object *colorspace_obj;
+	json_object_object_get_ex(pix_mp_obj, "colorspace", &colorspace_obj);
+	pix_mp.colorspace = json_object_get_int(colorspace_obj);
+
+	json_object *num_planes_obj;
+	json_object_object_get_ex(pix_mp_obj, "num_planes", &num_planes_obj);
+	pix_mp.num_planes = json_object_get_int(num_planes_obj);
+
+	for (int i = 0; i < pix_mp.num_planes; i++)
+		pix_mp.plane_fmt[i] = get_v4l2_plane_pix_format(pix_mp_obj, i);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(pix_mp_obj, "flags", &flags_obj);
+	pix_mp.flags = json_object_get_int(flags_obj);
+
+	json_object *ycbcr_enc_obj;
+	json_object_object_get_ex(pix_mp_obj, "ycbcr_enc", &ycbcr_enc_obj);
+	pix_mp.ycbcr_enc = json_object_get_int(ycbcr_enc_obj);
+
+	json_object *quantization_obj;
+	json_object_object_get_ex(pix_mp_obj, "quantization", &quantization_obj);
+	pix_mp.quantization = json_object_get_int(quantization_obj);
+
+	json_object *xfer_func_obj;
+	json_object_object_get_ex(pix_mp_obj, "xfer_func", &xfer_func_obj);
+	pix_mp.xfer_func = json_object_get_int(xfer_func_obj);
+
+	return pix_mp;
+}
+
+struct v4l2_format retrace_v4l2_format(json_object *ioctl_args)
+{
+	struct v4l2_format format;
+	memset(&format, 0, sizeof(format));
+
+	json_object *v4l2_format_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_format", &v4l2_format_obj);
+
+	json_object *type_obj;
+	json_object_object_get_ex(v4l2_format_obj, "type", &type_obj);
+	format.type = json_object_get_int(type_obj);
+
+	switch (format.type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+	case V4L2_BUF_TYPE_VBI_OUTPUT:
+	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+	case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
+		break;
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		format.fmt.pix_mp = retrace_v4l2_pix_format_mplane(v4l2_format_obj);
+		break;
+	case V4L2_BUF_TYPE_SDR_CAPTURE:
+	case V4L2_BUF_TYPE_SDR_OUTPUT:
+	case V4L2_BUF_TYPE_META_CAPTURE:
+	case V4L2_BUF_TYPE_META_OUTPUT:
+		break;
+	}
+
+	return format;
+}
+
+void retrace_vidioc_g_fmt(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct v4l2_format format;
+	memset(&format, 0, sizeof(format));
+
+	format = retrace_v4l2_format(ioctl_args_user);
+
+	ioctl(fd_retrace, VIDIOC_G_FMT, &format);
+
+	if (debug || (errno != 0))
+		perror("VIDIOC_G_FMT");
+}
+
+void retrace_vidioc_s_fmt(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct v4l2_format format;
+	memset(&format, 0, sizeof(format));
+
+	format = retrace_v4l2_format(ioctl_args_user);
+
+	ioctl(fd_retrace, VIDIOC_S_FMT, &format);
+
+	if (debug || (errno != 0)) {
+		perror("VIDIOC_S_FMT");
+		fprintf(stderr, "%s\n", buftype2s(format.type).c_str());
+		fprintf(stderr, "format.fmt.pix_mp.pixelformat: %s\n\n",
+		        fcc2s(format.fmt.pix_mp.pixelformat).c_str());
+	}
+}
+
+struct v4l2_ext_control retrace_v4l2_ext_control(json_object *ext_controls_obj, int ctrl_idx)
+{
+	struct v4l2_ext_control ctrl;
+	memset(&ctrl, 0, sizeof(v4l2_ext_control));
+
+	std::string unique_key_for_control = "v4l2_ext_control_";
+	unique_key_for_control += std::to_string(ctrl_idx);
+
+	json_object *ctrl_obj;
+	json_object_object_get_ex(ext_controls_obj, unique_key_for_control.c_str(), &ctrl_obj);
+
+	json_object *id_obj;
+	json_object_object_get_ex(ctrl_obj, "id", &id_obj);
+	ctrl.id = json_object_get_int(id_obj);
+
+	json_object *size_obj;
+	json_object_object_get_ex(ctrl_obj, "size", &size_obj);
+	ctrl.size = json_object_get_int(size_obj);
+
+	if ((ctrl.id & V4L2_CID_CODEC_STATELESS_BASE) == V4L2_CID_CODEC_STATELESS_BASE) {
+		switch (ctrl.id) {
+		case V4L2_CID_STATELESS_VP8_FRAME:
+			ctrl.ptr = retrace_v4l2_ctrl_vp8_frame_pointer(ctrl_obj);
+			break;
+		}
+	}
+	return ctrl;
+}
+
+struct v4l2_ext_control *retrace_v4l2_ext_control_array_pointer(json_object *ext_controls_obj, int count)
+{
+	struct v4l2_ext_control *ctrl_array_pointer = (struct v4l2_ext_control *) calloc(count, sizeof(v4l2_ext_control));
+
+	for (int i = 0; i < count; i++)
+		ctrl_array_pointer[i] = retrace_v4l2_ext_control(ext_controls_obj, i);
+
+	return ctrl_array_pointer;
+}
+
+void retrace_vidioc_s_ext_ctrls(int fd_retrace, json_object *ioctl_args)
+{
+	struct v4l2_ext_controls ext_controls;
+	memset(&ext_controls, 0, sizeof(ext_controls));
+
+	json_object *ext_controls_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_ext_controls", &ext_controls_obj);
+
+	json_object *which_obj;
+	json_object_object_get_ex(ext_controls_obj, "which", &which_obj);
+	ext_controls.which = json_object_get_int(which_obj);
+
+	json_object *count_obj;
+	json_object_object_get_ex(ext_controls_obj, "count", &count_obj);
+	ext_controls.count = json_object_get_int(count_obj);
+
+	/* request_fd is only valid for V4L2_CTRL_WHICH_REQUEST_VAL */
+	if (ext_controls.which == V4L2_CTRL_WHICH_REQUEST_VAL) {
+		json_object *request_fd_obj;
+		json_object_object_get_ex(ext_controls_obj, "request_fd", &request_fd_obj);
+		int request_fd_trace = json_object_get_int(request_fd_obj);
+		ext_controls.request_fd = get_fd(request_fd_trace);
+	}
+
+	ext_controls.controls = retrace_v4l2_ext_control_array_pointer(ext_controls_obj, ext_controls.count);
+
+	ioctl(fd_retrace, VIDIOC_S_EXT_CTRLS, &ext_controls);
+
+	if (debug || (errno != 0))
+		perror("VIDIOC_S_EXT_CTRLS");
+
+	/* Free controls working backwards from the end of the controls array. */
+	for (int i = (ext_controls.count - 1); i >= 0 ; i--) {
+		if (ext_controls.controls[i].ptr != nullptr)
+			free(ext_controls.controls[i].ptr);
+	}
+
+	if (ext_controls.controls != nullptr)
+		free(ext_controls.controls);
+}
+
+void retrace_query_ext_ctrl(int fd_retrace, json_object *ioctl_args)
+{
+	struct v4l2_query_ext_ctrl query_ext_ctrl;
+	memset(&query_ext_ctrl, 0, sizeof(v4l2_query_ext_ctrl));
+
+	json_object *query_ext_ctrl_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_query_ext_ctrl", &query_ext_ctrl_obj);
+
+	json_object *id_obj;
+	json_object_object_get_ex(query_ext_ctrl_obj, "id", &id_obj);
+	query_ext_ctrl.id = json_object_get_int(id_obj);
+
+	ioctl(fd_retrace, VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl);
+
+	if (debug) {
+		perror("VIDIOC_QUERY_EXT_CTRL");
+		fprintf(stderr, "id: %u\n\n", query_ext_ctrl.id);
+	}
+}
+
+void retrace_vidioc_enum_fmt(int fd_retrace, json_object *ioctl_args)
+{
+	struct v4l2_fmtdesc fmtdesc;
+	memset(&fmtdesc, 0, sizeof(v4l2_fmtdesc));
+
+	json_object *v4l2_fmtdesc_obj;
+	json_object_object_get_ex(ioctl_args, "v4l2_fmtdesc", &v4l2_fmtdesc_obj);
+
+	json_object *index_obj;
+	json_object_object_get_ex(v4l2_fmtdesc_obj, "index", &index_obj);
+	fmtdesc.index = json_object_get_int(index_obj);
+
+	json_object *type_obj;
+	json_object_object_get_ex(v4l2_fmtdesc_obj, "type", &type_obj);
+	fmtdesc.type = json_object_get_int(type_obj);
+
+	json_object *mbus_code_obj;
+	json_object_object_get_ex(v4l2_fmtdesc_obj, "mbus_code", &mbus_code_obj);
+	fmtdesc.mbus_code = json_object_get_int(mbus_code_obj);
+
+	ioctl(fd_retrace, VIDIOC_ENUM_FMT, &fmtdesc);
+
+	if (debug) {
+		perror("VIDIOC_ENUM_FMT");
+		fprintf(stderr, "index: %u\n", fmtdesc.index);
+		fprintf(stderr, "type: %u\n", fmtdesc.type);
+		fprintf(stderr, "flags: %u\n", fmtdesc.flags);
+		fprintf(stderr, "description: %s\n", fmtdesc.description);
+		fprintf(stderr, "pixelformat: %u\n", fmtdesc.pixelformat);
+		fprintf(stderr, "mbus_code: %u\n\n", fmtdesc.mbus_code);
+	}
+}
+
+void retrace_vidioc_querycap(int fd_retrace)
+{
+	struct v4l2_capability argp;
+	memset(&argp, 0, sizeof(v4l2_capability));
+
+	ioctl(fd_retrace, VIDIOC_QUERYCAP, &argp);
+
+	if (debug || (errno != 0))
+		perror("VIDIOC_QUERYCAP");
+}
+
+void retrace_media_ioc_request_alloc(int fd_retrace, json_object *ioctl_args)
+{
+	/* Get the original request file descriptor from the original trace file. */
+	json_object *request_fd_trace_obj;
+	json_object_object_get_ex(ioctl_args, "request_fd", &request_fd_trace_obj);
+	int request_fd_trace = json_object_get_int(request_fd_trace_obj);
+
+	/* Allocate a request in the retrace context. */
+	__s32 request_fd_retrace = 0;
+	ioctl(fd_retrace, MEDIA_IOC_REQUEST_ALLOC, &request_fd_retrace);
+
+	/* Associate the original request file descriptor with the current request file descriptor. */
+	add_fd(request_fd_trace, request_fd_retrace);
+
+	if (debug || (errno != 0))
+		perror("MEDIA_IOC_REQUEST_ALLOC");
+}
+
+void retrace_dma_buf_ioctl_sync(int fd_retrace, json_object *ioctl_args_user)
+{
+	struct dma_buf_sync sync;
+	memset(&sync, 0, sizeof(dma_buf_sync));
+
+	json_object *sync_obj;
+	json_object_object_get_ex(ioctl_args_user, "dma_buf_sync",&sync_obj);
+
+	json_object *flags_obj;
+	json_object_object_get_ex(sync_obj, "flags",&flags_obj);
+	sync.flags = json_object_get_int(flags_obj);
+
+	ioctl(fd_retrace, DMA_BUF_IOCTL_SYNC, &sync);
+
+	if (debug || (errno != 0))
+		perror("DMA_BUF_IOCTL_SYNC");
+}
+
+void retrace_ioctl_media(int fd_retrace, long int request, json_object *ioctl_args_driver)
+{
+	switch (request){
+	case MEDIA_IOC_DEVICE_INFO:
+	case MEDIA_IOC_ENUM_ENTITIES:
+	case MEDIA_IOC_ENUM_LINKS:
+	case MEDIA_IOC_SETUP_LINK:
+	case MEDIA_IOC_G_TOPOLOGY: {
+		struct media_v2_topology top;
+		memset(&top, 0, sizeof(media_v2_topology));
+		ioctl(fd_retrace, MEDIA_IOC_G_TOPOLOGY, &top);
+		if (debug || (errno != 0))
+			perror("MEDIA_IOC_G_TOPOLOGY");
+		break;
+	}
+	case MEDIA_IOC_REQUEST_ALLOC:
+		retrace_media_ioc_request_alloc(fd_retrace, ioctl_args_driver);
+		break;
+	case MEDIA_REQUEST_IOC_QUEUE: {
+		ioctl(fd_retrace, MEDIA_REQUEST_IOC_QUEUE);
+		if (debug || (errno != 0))
+			perror("MEDIA_REQUEST_IOC_QUEUE");
+		break;
+	}
+	case MEDIA_REQUEST_IOC_REINIT: {
+		ioctl(fd_retrace, MEDIA_REQUEST_IOC_REINIT);
+		if (debug || (errno != 0))
+			perror("MEDIA_REQUEST_IOC_REINIT");
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+void retrace_ioctl_video(int fd_retrace, long int request, json_object *ioctl_args_user, json_object *ioctl_args_driver)
+{
+	switch (request) {
+	//TODO ADD QUERYBUF
+	case VIDIOC_QUERYCAP:
+		retrace_vidioc_querycap(fd_retrace);
+		break;
+	case VIDIOC_ENUM_FMT:
+		retrace_vidioc_enum_fmt(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_G_FMT:
+		retrace_vidioc_g_fmt(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_S_FMT:
+		retrace_vidioc_s_fmt(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_REQBUFS:
+		retrace_vidioc_reqbufs(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_QUERYBUF:
+		retrace_vidioc_querybuf(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_QBUF:
+		retrace_vidioc_qbuf(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_EXPBUF:
+		retrace_vidioc_expbuf(fd_retrace, ioctl_args_user, ioctl_args_driver);
+		break;
+	case VIDIOC_DQBUF:
+		retrace_vidioc_dqbuf(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_STREAMON:
+		retrace_vidioc_streamon(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_STREAMOFF:
+		retrace_vidioc_streamoff(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_S_EXT_CTRLS:
+		retrace_vidioc_s_ext_ctrls(fd_retrace, ioctl_args_user);
+		break;
+	case VIDIOC_QUERY_EXT_CTRL:
+		retrace_query_ext_ctrl(fd_retrace, ioctl_args_user);
+		break;
+	default:
+		break;
+	}
+}
+
+void retrace_ioctl(json_object *syscall_obj)
+{
+	int fd_retrace = 0;
+	__u8 ioctl_type = 0;
+	long int request = 0;
+
+	json_object *fd_trace_obj;
+	json_object_object_get_ex(syscall_obj, "fd", &fd_trace_obj);
+	fd_retrace = get_fd(json_object_get_int(fd_trace_obj));
+
+	json_object *request_obj;
+	json_object_object_get_ex(syscall_obj, "request", &request_obj);
+	request = json_object_get_int64(request_obj);
+
+	json_object *ioctl_args_user;
+	json_object_object_get_ex(syscall_obj, "ioctl_args_from_userspace", &ioctl_args_user);
+
+	json_object *ioctl_args_driver;
+	json_object_object_get_ex(syscall_obj, "ioctl_args_from_driver", &ioctl_args_driver);
+
+	ioctl_type = _IOC_TYPE(request);
+	switch (ioctl_type) {
+	case 'V':
+		retrace_ioctl_video(fd_retrace, request, ioctl_args_user, ioctl_args_driver);
+		break;
+	case '|':
+		retrace_ioctl_media(fd_retrace, request, ioctl_args_driver);
+		break;
+	case 'b':
+		if (request == DMA_BUF_IOCTL_SYNC)
+			retrace_dma_buf_ioctl_sync(fd_retrace, ioctl_args_user);
+		break;
+	default:
+		break;
+	}
+}
+
+void write_to_output_buffer(unsigned char *buffer_pointer, int bytesused, json_object *mem_obj)
+{
+	std::string data;
+	int byteswritten = 0;
+	json_object *line_obj;
+	size_t number_of_lines;
+
+	json_object *mem_array_obj;
+	json_object_object_get_ex(mem_obj, "mem_array", &mem_array_obj);
+	number_of_lines = json_object_array_length(mem_array_obj);
+
+	for (long unsigned int i = 0; i < number_of_lines; i++) {
+		line_obj = json_object_array_get_idx(mem_array_obj, i);
+		data = json_object_get_string(line_obj);
+
+		for (long unsigned i = 0; i < data.length(); i++) {
+			if (std::isspace(data[i]))
+				continue;
+			try {
+				/* Two values from the string e.g. "D9" are needed to write one byte. */
+				*buffer_pointer = (char) std::stoi(data.substr(i,2), nullptr, 16);
+				buffer_pointer++;
+				i++;
+				byteswritten++;
+			} catch (std::invalid_argument& ia) {
+				fprintf(stderr, "Warning: \'%s\' is an invalid argument; %s line: %d.\n",
+				        data.substr(i,2).c_str(), __func__, __LINE__);
+			} catch (std::out_of_range& oor) {
+				fprintf(stderr, "Warning: \'%s\' is out of range; %s line: %d.\n",
+				        data.substr(i,2).c_str(), __func__, __LINE__);
+			}
+		}
+	}
+
+	if (debug) {
+		fprintf(stderr, "\nWrite to Output Buffer\n");
+		fprintf(stderr, "bytesused: %d, byteswritten: %d\n", bytesused, byteswritten);
+		fprintf(stderr, "\n");
+	}
+}
+
+void write_decoded_frames_to_yuv_file_retrace(unsigned char *buffer_pointer, int bytesused)
+{
+	int byteswritten = 0;
+
+	FILE *fp = fopen(retrace_filename.c_str(), "a");
+	for (int i = 0; i < bytesused; i++) {
+		fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp);
+		byteswritten++;
+	}
+	fclose(fp);
+
+	if (debug){
+		fprintf(stderr, "\nWrite to File\n");
+		fprintf(stderr, "%s\n", retrace_filename.c_str());
+		fprintf(stderr, "bytesused: %d, byteswritten: %d\n", bytesused, byteswritten);
+		fprintf(stderr, "\n");
+	}
+}
+
+void retrace_mem(json_object *mem_obj)
+{
+	json_object *type_obj;
+	json_object_object_get_ex(mem_obj, "type", &type_obj);
+	v4l2_buf_type type = (v4l2_buf_type) json_object_get_int(type_obj);
+
+	json_object *bytesused_obj;
+	json_object_object_get_ex(mem_obj, "bytesused", &bytesused_obj);
+	int bytesused = json_object_get_int(bytesused_obj);
+
+	json_object *address_obj;
+	json_object_object_get_ex(mem_obj, "address", &address_obj);
+	long int buffer_address_trace = json_object_get_int64(address_obj);
+
+	/* Convert the trace address to the corresponding retrace address. */
+	long int buffer_address_retrace = get_buffer_address_retrace(buffer_address_trace);
+
+	unsigned char *buffer_pointer = (unsigned char *) buffer_address_retrace;
+
+	/* Get the encoded data from the json file and write it to output buffer memory. */
+	if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+		write_to_output_buffer(buffer_pointer, bytesused, mem_obj);
+
+	/* Get the decoded capture buffer from memory and write it to a binary yuv file. */
+	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		write_decoded_frames_to_yuv_file_retrace(buffer_pointer, bytesused);
+}
+
+void retrace_object(json_object *jobj)
+{
+	json_object *syscall_obj;
+	int ret = json_object_object_get_ex(jobj, "syscall", &syscall_obj);
+	int syscall = json_object_get_int(syscall_obj);
+
+	/* If the json object doesn't hold a syscall, check if it holds a memory dump. */
+	if (ret == 0) {
+		json_object *temp_obj;
+		if (json_object_object_get_ex(jobj, "mem_dump", &temp_obj)) {
+			retrace_mem(jobj);
+		}
+		return;
+	}
+
+	errno = 0;
+
+	switch (syscall) {
+	case LIBTRACER_SYSCALL_IOCTL:
+		retrace_ioctl(jobj);
+		break;
+	case LIBTRACER_SYSCALL_OPEN:
+	case LIBTRACER_SYSCALL_OPEN64:
+		retrace_open64(jobj);
+		break;
+	case LIBTRACER_SYSCALL_CLOSE:
+		retrace_close(jobj);
+		break;
+	case LIBTRACER_SYSCALL_MMAP64:
+		retrace_mmap64(jobj);
+		break;
+	case LIBTRACER_SYSCALL_MUNMAP:
+		retrace_munmap(jobj);
+		break;
+	default:
+		break;
+	}
+}
+
+void retrace_array(json_object *root_array_obj)
+{
+	json_object *jobj;
+	struct array_list *al = json_object_get_array(root_array_obj);
+
+	for (size_t i = 0; i < array_list_length(al); i++) {
+		jobj = (json_object *) array_list_get_idx(al, i);
+		retrace_object(jobj);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int ch;
+	char short_options[] = {'d', 'm', ':', 'v', ':'};
+
+	do {
+		ch = getopt(argc, argv, short_options);
+		switch (ch){
+		case 'd':
+			debug = true;
+			break;
+		case 'm':
+			dev_path_media = *optarg;
+			dev_path_media = "/dev/media" + dev_path_media;
+			fprintf(stderr, "Using: %s\n", dev_path_media.c_str());
+			break;
+		case 'v':
+			dev_path_video = *optarg;
+			dev_path_video = "/dev/video" + dev_path_video;
+			fprintf(stderr, "Using: %s\n", dev_path_video.c_str());
+			break;
+		}
+	} while (ch != -1);
+
+
+	if (optind == argc) {
+		fprintf(stderr, "usage: retracer [-d] [-m <media device number>] [-v <video device number>] <some_trace_file>.json\n");
+		return -1;
+	}
+
+	std::string trace_filename = argv[optind];
+
+	FILE *trace_file = fopen(trace_filename.c_str(), "r");
+	if (trace_file == NULL) {
+		fprintf(stderr, "Trace file error: %s\n", trace_filename.c_str());
+		return -1;
+	}
+	fclose(trace_file);
+
+	fprintf(stderr, "Retracing: %s\n", trace_filename.c_str());
+
+	/* Create file to hold the decoded frames.  Discard previous retraced file if any. */
+	retrace_filename = trace_filename;
+	retrace_filename = retrace_filename.replace(5, retrace_filename.npos, "_retrace");
+	retrace_filename += ".yuv";
+
+	json_object *root_array_obj = json_object_from_file(trace_filename.c_str());
+	retrace_array(root_array_obj);
+	json_object_put(root_array_obj);
+
+	fprintf(stderr, "Retracing complete in %s\n", retrace_filename.c_str());
+}
diff --git a/utils/tracer/retracer.h b/utils/tracer/retracer.h
new file mode 100644
index 00000000..e575b4b5
--- /dev/null
+++ b/utils/tracer/retracer.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022 Collabora Ltd.
+ */
+
+#ifndef RETRACER_H
+#define RETRACER_H
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <poll.h>
+#include <unordered_map>
+#include <stdexcept>
+#include <list>
+#include <json.h>
+#include <v4l2-info.h>
+#include "trace-info.h"
+#include "retrace-vp8.h"
+#include "retrace-helper.h"
+
+#endif
-- 
2.37.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [RFC 0/2] v4l2 stateless tracer/retracer utilities
  2022-08-20  0:50 [RFC 0/2] v4l2 stateless tracer/retracer utilities Deborah Brouwer
  2022-08-20  0:50 ` [RFC 1/2] utils: add stateless tracer utility Deborah Brouwer
  2022-08-20  0:50 ` [RFC 2/2] utils: add stateless retracer utility Deborah Brouwer
@ 2022-08-26 19:05 ` Nicolas Dufresne
  2 siblings, 0 replies; 5+ messages in thread
From: Nicolas Dufresne @ 2022-08-26 19:05 UTC (permalink / raw)
  To: Deborah Brouwer, linux-media; +Cc: daniel.almeida, nfraprado, hverkuil-cisco

Hi Deborah,

very nice work, I'm very exciting to see such a tool coming to life.

Le vendredi 19 août 2022 à 17:50 -0700, Deborah Brouwer a écrit :
> This project helps to test v4l2 stateless decoder drivers by tracing,
> recording and replaying (i.e. "retracing") userspace's interaction with
> a stateless decoder driver.
> 
> The tracer utility attaches to a userspace application and generates a
> json file with relevant system calls, parameters and encoded data.
> 
> The retracer utility reads the json-file and makes the same system calls
> to a v4l2 stateless driver. Since the retracer is independent from the
> original userspace application that was traced, testing can be decoupled
> from extraneous factors in the userspace environment. The json-file can
> also be edited to inject errors and test a driver's error-handling
> abilities.
> 
> NOTE:
> This project is work in progress and currently only traces VP8, but
> H264 and FWHT will follow shortly.
> 
> EXAMPLE:
> ./tracer gst-launch-1.0 -- filesrc location=<some_vp8_file> ! parsebin !
> v4l2slvp8dec ! videocodectestsink
> 
> ./retracer 10284_trace.json

My first comment would be to polish a bit the user interface, and make the
installation of the tool a bit more friendly to Linux distribution. I would like
suggest to have a single executable, and to name it with a proper name space.
Something like:

v4l2-tracer trace -- gst-launch-1.0 filesrc location=<some_vp8_file> ...
v4l2-tracer retrace 10284_trace.json

We can refine this interface over time, but at least this gives a single command
to remember, and will be more friendly when installing this onto your system.

regards,
Nicolas

> 
> FURTHER INFO AND TEST FILES:
> https://gitlab.collabora.com/dbrouwer/v4l2-stateless-tracer-utility/-/tree/main/
> 
> Deborah Brouwer (2):
>   utils: add stateless tracer utility
>   utils: add stateless retracer utility
> 
>  configure.ac                    |    6 +
>  utils/Makefile.am               |    5 +
>  utils/common/v4l2-info.cpp      |    7 +-
>  utils/common/v4l2-info.h        |    8 +
>  utils/tracer/.gitignore         |    9 +
>  utils/tracer/Makefile.am        |   23 +
>  utils/tracer/libtracer.cpp      |  217 ++++++
>  utils/tracer/libtracer.h        |   92 +++
>  utils/tracer/retrace-helper.cpp |  141 ++++
>  utils/tracer/retrace-helper.h   |   18 +
>  utils/tracer/retrace-vp8.cpp    |  288 ++++++++
>  utils/tracer/retrace-vp8.h      |   11 +
>  utils/tracer/retracer.cpp       | 1090 +++++++++++++++++++++++++++++++
>  utils/tracer/retracer.h         |   24 +
>  utils/tracer/trace-helper.cpp   |  218 +++++++
>  utils/tracer/trace-info.cpp     |  358 ++++++++++
>  utils/tracer/trace-info.h       |   72 ++
>  utils/tracer/trace-vp8.cpp      |  183 ++++++
>  utils/tracer/trace.cpp          |  520 +++++++++++++++
>  utils/tracer/tracer.cpp         |   91 +++
>  20 files changed, 3375 insertions(+), 6 deletions(-)
>  create mode 100644 utils/tracer/.gitignore
>  create mode 100644 utils/tracer/Makefile.am
>  create mode 100644 utils/tracer/libtracer.cpp
>  create mode 100644 utils/tracer/libtracer.h
>  create mode 100755 utils/tracer/retrace-helper.cpp
>  create mode 100644 utils/tracer/retrace-helper.h
>  create mode 100755 utils/tracer/retrace-vp8.cpp
>  create mode 100644 utils/tracer/retrace-vp8.h
>  create mode 100755 utils/tracer/retracer.cpp
>  create mode 100644 utils/tracer/retracer.h
>  create mode 100644 utils/tracer/trace-helper.cpp
>  create mode 100644 utils/tracer/trace-info.cpp
>  create mode 100644 utils/tracer/trace-info.h
>  create mode 100644 utils/tracer/trace-vp8.cpp
>  create mode 100644 utils/tracer/trace.cpp
>  create mode 100644 utils/tracer/tracer.cpp
> 


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [RFC 1/2] utils: add stateless tracer utility
  2022-08-20  0:50 ` [RFC 1/2] utils: add stateless tracer utility Deborah Brouwer
@ 2022-08-26 19:18   ` Nicolas Dufresne
  0 siblings, 0 replies; 5+ messages in thread
From: Nicolas Dufresne @ 2022-08-26 19:18 UTC (permalink / raw)
  To: Deborah Brouwer, linux-media; +Cc: daniel.almeida, nfraprado, hverkuil-cisco

Hi Deborah,

I've been trying the tracer, nice work.

Le vendredi 19 août 2022 à 17:50 -0700, Deborah Brouwer a écrit :
> The tracer utility helps to test v4l2 stateless decoder drivers by tracing
> and recording userspace's interaction with a stateless decoder driver.
> Only system calls relevant to the v4l2 memory-to-memory stateless video
> decoder interface are traced. The tracer traces encoded data from output
> buffers and, optionally, may trace decoded data from capture buffers. The
> tracer records its results in a json-formatted trace file.
> 
> Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
> ---
>  configure.ac                  |   6 +
>  utils/Makefile.am             |   5 +
>  utils/common/v4l2-info.cpp    |   7 +-
>  utils/common/v4l2-info.h      |   8 +
>  utils/tracer/.gitignore       |   9 +
>  utils/tracer/Makefile.am      |  19 ++
>  utils/tracer/libtracer.cpp    | 217 ++++++++++++++
>  utils/tracer/libtracer.h      |  92 ++++++
>  utils/tracer/trace-helper.cpp | 218 ++++++++++++++
>  utils/tracer/trace-info.cpp   | 358 +++++++++++++++++++++++
>  utils/tracer/trace-info.h     |  72 +++++
>  utils/tracer/trace-vp8.cpp    | 183 ++++++++++++
>  utils/tracer/trace.cpp        | 520 ++++++++++++++++++++++++++++++++++
>  utils/tracer/tracer.cpp       |  91 ++++++
>  14 files changed, 1799 insertions(+), 6 deletions(-)
>  create mode 100644 utils/tracer/.gitignore
>  create mode 100644 utils/tracer/Makefile.am
>  create mode 100644 utils/tracer/libtracer.cpp
>  create mode 100644 utils/tracer/libtracer.h
>  create mode 100644 utils/tracer/trace-helper.cpp
>  create mode 100644 utils/tracer/trace-info.cpp
>  create mode 100644 utils/tracer/trace-info.h
>  create mode 100644 utils/tracer/trace-vp8.cpp
>  create mode 100644 utils/tracer/trace.cpp
>  create mode 100644 utils/tracer/tracer.cpp
> 
> diff --git a/configure.ac b/configure.ac
> index 05298981..cc604cad 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -42,6 +42,7 @@ AC_CONFIG_FILES([Makefile
>  	utils/cec-follower/cec-follower.1
>  	utils/qvidcap/Makefile
>  	utils/rds-ctl/Makefile
> +	utils/tracer/Makefile
>  
>  	contrib/Makefile
>  	contrib/freebsd/Makefile
> @@ -311,6 +312,11 @@ AS_IF([test "x$with_libudev" != xno -o "x$enable_libdvbv5" != xno],
>  
>  AC_SUBST([JPEG_LIBS])
>  
> +PKG_CHECK_MODULES(JSONC, [json-c >= 0.16], [jsonc_pkgconfig=yes], [jsonc_pkgconfig=no])

I found during compilation is that the dependency on json-c might be a bit over
agressive. My device OS is Fedora 36 (so not too old) and version 0.16 was not
available there. I downgrade to 0.15 and it "worked", at least I think. Are you
aware of any issue with downgrading this version for wider support ?

> +AC_SUBST([JSONC_CFLAGS])
> +AC_SUBST([JSONC_LIBS])
> +AM_CONDITIONAL([HAVE_JSONC], [test x$jsonc_pkgconfig = xyes])

nit: I think it would be nice to have some way to fail the configure stage if
the deps is missing.

> +
>  # Check for pthread
>  
>  AS_IF([test x$enable_shared != xno],
> diff --git a/utils/Makefile.am b/utils/Makefile.am
> index 0e68a612..3ccc3d81 100644
> --- a/utils/Makefile.am
> +++ b/utils/Makefile.am
> @@ -15,6 +15,11 @@ SUBDIRS = \
>  	cec-follower \
>  	rds-ctl
>  
> +if HAVE_JSONC
> +SUBDIRS += \
> +	tracer
> +endif
> +
>  if WITH_LIBDVBV5
>  SUBDIRS += \
>  	dvb
> diff --git a/utils/common/v4l2-info.cpp b/utils/common/v4l2-info.cpp
> index 40d45471..74e77b7a 100644
> --- a/utils/common/v4l2-info.cpp
> +++ b/utils/common/v4l2-info.cpp
> @@ -16,12 +16,7 @@ static std::string num2s(unsigned num, bool is_hex = true)
>  	return buf;
>  }
>  
> -struct flag_def {
> -	unsigned flag;
> -	const char *str;
> -};
> -
> -static std::string flags2s(unsigned val, const flag_def *def)
> +std::string flags2s(unsigned int val, const flag_def *def)
>  {
>  	std::string s;
>  
> diff --git a/utils/common/v4l2-info.h b/utils/common/v4l2-info.h
> index 35237853..8eb14bc4 100644
> --- a/utils/common/v4l2-info.h
> +++ b/utils/common/v4l2-info.h
> @@ -11,6 +11,14 @@
>  #include <linux/videodev2.h>
>  #include <linux/v4l2-subdev.h>
>  
> +struct flag_def {
> +	unsigned int flag;
> +	const char *str;
> +};
> +
> +/* Return a comma-separated string of flags or hex value if unknown */
> +std::string flags2s(unsigned int val, const flag_def *def);
> +
>  /* Print capability information */
>  void v4l2_info_capability(const v4l2_capability &cap);
>  void v4l2_info_subdev_capability(const v4l2_subdev_capability &subdevcap);
> diff --git a/utils/tracer/.gitignore b/utils/tracer/.gitignore
> new file mode 100644
> index 00000000..4098662d
> --- /dev/null
> +++ b/utils/tracer/.gitignore
> @@ -0,0 +1,9 @@
> +*.json
> +*.yuv
> +*.webm
> +*.h264
> +*.ivf
> +*.vp8
> +tracer
> +retracer
> +.clang-tidy
> diff --git a/utils/tracer/Makefile.am b/utils/tracer/Makefile.am
> new file mode 100644
> index 00000000..f5579198
> --- /dev/null
> +++ b/utils/tracer/Makefile.am
> @@ -0,0 +1,19 @@
> +if HAVE_JSONC
> +
> +lib_LTLIBRARIES = libtracer.la
> +include_HEADERS = libtracer.h ../../utils/common/v4l2-info.h ../../utils/common/media-info.h
> +libtracer_la_SOURCES = libtracer.cpp trace.cpp trace-vp8.cpp trace-helper.cpp \
> +trace-info.cpp ../../utils/common/v4l2-info.cpp ../../utils/common/media-info.cpp
> +
> +libtracer_la_CFLAGS = $(JSONC_CFLAGS)
> +libtracer_la_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
> +libtracer_la_LDFLAGS = -avoid-version -module -shared -export-dynamic -ldl $(JSONC_LIBS)
> +libtracer_la_LIBTOOLFLAGS = --tag=disable-static
> +
> +bin_PROGRAMS = tracer
> +
> +tracer_SOURCES = tracer.cpp
> +tracer_CPPFLAGS = -I$(top_srcdir)/utils/common $(JSONC_CFLAGS)
> +tracer_LDFLAGS = -lrt -lpthread $(JSONC_LIBS)
> +
> +endif
> diff --git a/utils/tracer/libtracer.cpp b/utils/tracer/libtracer.cpp
> new file mode 100644
> index 00000000..0d18b224
> --- /dev/null
> +++ b/utils/tracer/libtracer.cpp
> @@ -0,0 +1,217 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include "libtracer.h"
> +
> +int open64(const char *path, int oflag, ...)
> +{
> +	errno = 0;
> +	mode_t mode = 0;
> +
> +	if (oflag & O_CREAT) {
> +		va_list ap;
> +		va_start(ap, oflag);
> +		mode = va_arg(ap, PROMOTED_MODE_T);
> +		va_end(ap);
> +	}
> +
> +	int (*original_open64)(const char *path, int oflag, ...);
> +	original_open64 = (int (*)(const char*, int, ...)) dlsym(RTLD_NEXT, "open64");
> +	int fd = (*original_open64)(path, oflag, mode);
> +
> +	/* Only trace calls to video or media devices. */
> +	std::string dev_path_video = "/dev/video";
> +	std::string dev_path_media = "/dev/media";
> +	if (strncmp(path, dev_path_video.c_str(), dev_path_video.length()) &&
> +	    strncmp(path, dev_path_media.c_str(), dev_path_media.length()))
> +		return fd;
> +
> +	/* Set trace options if this is the first call to libtracer. */
> +	if (!options_are_set())
> +		set_options();
> +
> +	add_device(fd, path);
> +
> +	json_object *open_obj = json_object_new_object();
> +	json_object_object_add(open_obj, "syscall_str", json_object_new_string("open64"));
> +	json_object_object_add(open_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_OPEN64));
> +	json_object_object_add(open_obj, "fd", json_object_new_int(fd));
> +	json_object *open_args = trace_open(path, oflag, mode);
> +	json_object_object_add(open_obj, "open_args", open_args);
> +
> +	write_json_object_to_json_file(open_obj);
> +	json_object_put(open_obj);
> +
> +	return fd;
> +}
> +
> +void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
> +{
> +	errno = 0;
> +
> +	void *(*original_mmap64)(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
> +	original_mmap64 = (void*(*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap64");
> +	void *buf_address_pointer = (*original_mmap64)(addr, len, prot, flags, fildes, off);
> +
> +	/* Only trace mmap64 calls for output or capture buffers. */
> +	__u32 type = get_buffer_type_trace(fildes);
> +	if (!type)
> +		return buf_address_pointer;
> +
> +	/* Save the buffer address for future reference. */
> +	set_buffer_address_trace(fildes, (long int) buf_address_pointer);
> +
> +	json_object *mmap_obj = json_object_new_object();
> +	if (errno) {
> +		json_object_object_add(mmap_obj, "errno", json_object_new_int(errno));
> +		json_object_object_add(mmap_obj, "errno_str", json_object_new_string(strerror(errno)));
> +	}
> +	json_object_object_add(mmap_obj, "syscall_str", json_object_new_string("mmap64"));
> +	json_object_object_add(mmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MMAP64));
> +
> +	json_object *mmap64_args = trace_mmap64(addr, len, prot, flags, fildes, off);
> +	json_object_object_add(mmap_obj, "mmap64_args", mmap64_args);
> +
> +	json_object_object_add(mmap_obj, "buffer_address", json_object_new_int64((long int) buf_address_pointer));
> +	write_json_object_to_json_file(mmap_obj);
> +	json_object_put(mmap_obj);
> +
> +	/*
> +	 * The capture buffer is traced for the first time here when the buffer is first mapped.
> +	 * Subsequently, the capture buffer will be traced when VIDIOC_DQBUF is called.
> +	 */
> +	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +		trace_mem(fildes);
> +
> +	return buf_address_pointer;
> +}
> +
> +int munmap(void *start, size_t length)
> +{
> +	errno = 0;
> +
> +	int(*original_munmap)(void *start, size_t length);
> +	original_munmap = (int(*)(void *, size_t)) dlsym(RTLD_NEXT, "munmap");
> +	int ret = (*original_munmap)(start, length);
> +
> +	/* Only trace the unmapping if the original mapping was traced. */
> +	if (!buffer_is_mapped((long int) start))
> +		return ret;
> +
> +	json_object *munmap_obj = json_object_new_object();
> +	json_object_object_add(munmap_obj, "syscall_str", json_object_new_string("munmap"));
> +	json_object_object_add(munmap_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_MUNMAP));
> +
> +	if (errno) {
> +		json_object_object_add(munmap_obj, "errno", json_object_new_int(errno));
> +		json_object_object_add(munmap_obj, "errno_str", json_object_new_string(strerror(errno)));
> +	}
> +
> +	json_object *munmap_args = json_object_new_object();
> +	json_object_object_add(munmap_args, "start", json_object_new_int64((int64_t)start));
> +	json_object_object_add(munmap_args, "length", json_object_new_uint64(length));
> +	json_object_object_add(munmap_obj, "munmap_args", munmap_args);
> +
> +	write_json_object_to_json_file(munmap_obj);
> +	json_object_put(munmap_obj);
> +
> +	return ret;
> +}
> +
> +int ioctl(int fd, unsigned long int request, ...)
> +{
> +	errno = 0;
> +
> +	va_list ap;
> +	va_start(ap, request);
> +	void *arg = va_arg(ap, void *);
> +	va_end(ap);
> +
> +	int (*original_ioctl)(int fd, unsigned long int request, ...);
> +	original_ioctl = (int (*)(int, long unsigned int, ...)) dlsym(RTLD_NEXT, "ioctl");
> +
> +	/* If the ioctl is queuing an output buffer, trace the output buffer before tracing the ioctl. */
> +	if ((request == VIDIOC_QBUF) && (_IOC_TYPE(request) == 'V')) {
> +		struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
> +		if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> +			int buf_fd = get_buffer_fd_trace(buf->type, buf->index);
> +			if (buf_fd) {
> +				set_buffer_bytesused_trace(buf_fd, buf->m.planes[0].bytesused);
> +				trace_mem(buf_fd);
> +			}
> +		}
> +	}
> +
> +	json_object *ioctl_obj = json_object_new_object();
> +	json_object_object_add(ioctl_obj, "syscall_str", json_object_new_string("ioctl"));
> +	json_object_object_add(ioctl_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_IOCTL));
> +	json_object_object_add(ioctl_obj, "fd", json_object_new_int(fd));
> +	json_object_object_add(ioctl_obj, "request", json_object_new_uint64(request));
> +	json_object_object_add(ioctl_obj, "request_str", json_object_new_string(get_ioctl_request_str(request).c_str()));
> +
> +	/* Trace the ioctl arguments provided by userspace if relevant. */
> +	json_object *ioctl_args_userspace = trace_ioctl_args(fd, request, arg);
> +	if (json_object_object_length(ioctl_args_userspace))
> +		json_object_object_add(ioctl_obj, "ioctl_args_from_userspace", ioctl_args_userspace);
> +
> +	/* Make the original ioctl call. */
> +	int ret = (*original_ioctl)(fd, request, arg);
> +
> +	if (errno) {
> +		json_object_object_add(ioctl_obj, "errno", json_object_new_int(errno));
> +		json_object_object_add(ioctl_obj, "errno_str", json_object_new_string(strerror(errno)));
> +	}
> +
> +	/* Also trace the ioctl arguments as modified by the driver if relevant. */
> +	json_object *ioctl_args_driver = trace_ioctl_args(fd, request, arg, false);
> +	if (json_object_object_length(ioctl_args_driver))
> +		json_object_object_add(ioctl_obj, "ioctl_args_from_driver", ioctl_args_driver);
> +
> +	write_json_object_to_json_file(ioctl_obj);
> +	json_object_put(ioctl_obj);
> +
> +	/* If the ioctl is exporting a buffer, store the buffer file descriptor and index for future access. */
> +	if ((request == VIDIOC_EXPBUF) && (_IOC_TYPE(request) == 'V')) {
> +		struct v4l2_exportbuffer *export_buffer = static_cast<struct v4l2_exportbuffer*>(arg);
> +		add_buffer_trace(export_buffer->fd, export_buffer->type, export_buffer->index);
> +	}
> +
> +	/* If the ioctl is dequeuing a capture buffer, trace the buffer. */
> +	if ((request == VIDIOC_DQBUF) && (_IOC_TYPE(request) == 'V')) {
> +		struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
> +		if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> +			int buf_fd = get_buffer_fd_trace(buf->type, buf->index);
> +			if (buf_fd) {
> +				/* TODO tracer only works for decoded formats with one plane e.g. V4L2_PIX_FMT_NV12 */
> +				set_buffer_bytesused_trace(buf_fd, buf->m.planes[0].bytesused);
> +				trace_mem(buf_fd);
> +			}
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +int close(int fd)
> +{
> +	std::string path = get_device(fd);
> +
> +	/* Only trace the close if a corresponding open was also traced. */
> +	if (!path.empty()) {
> +		json_object *close_obj = json_object_new_object();
> +		json_object_object_add(close_obj, "syscall_str", json_object_new_string("close"));
> +		json_object_object_add(close_obj, "syscall", json_object_new_int(LIBTRACER_SYSCALL_CLOSE));
> +		json_object_object_add(close_obj, "fd", json_object_new_int(fd));
> +		json_object_object_add(close_obj, "path", json_object_new_string(path.c_str()));
> +		write_json_object_to_json_file(close_obj);
> +		json_object_put(close_obj);
> +		remove_device(fd);
> +	}
> +
> +	int (*original_close)(int fd);
> +	original_close = (int (*)(int)) dlsym(RTLD_NEXT, "close");
> +
> +	return (*original_close)(fd);
> +}
> diff --git a/utils/tracer/libtracer.h b/utils/tracer/libtracer.h
> new file mode 100644
> index 00000000..5f86aadf
> --- /dev/null
> +++ b/utils/tracer/libtracer.h
> @@ -0,0 +1,92 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#ifndef LIBTRACER_H
> +#define LIBTRACER_H
> +
> +#include <stdio.h>
> +#include <stdarg.h>
> +#include <dlfcn.h>
> +#include <stdlib.h>
> +#include <fcntl.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <config.h>
> +#include <pthread.h>
> +#include <unistd.h> /* needed for close */
> +#include <linux/dma-buf.h>
> +#include <json-c/json.h>
> +#include <v4l2-info.h>
> +#include <unordered_map>
> +#include <list>
> +#include "trace-info.h"
> +
> +struct trace_options {
> +	bool set;
> +	bool pretty_print_all;
> +	bool pretty_print_mem;
> +	bool decoded_data_to_json;
> +	bool create_yuv_file;
> +};
> +
> +struct buffer_trace {
> +	int fd;
> +	__u32 type; /* enum v4l2_buf_type */
> +	__u32 index;
> +	long address;
> +	__u32 bytesused;
> +};
> +
> +struct trace_context {
> +	FILE *trace_file;
> +	std::string trace_filename;
> +	pthread_mutex_t lock;
> +	/* Key is the file descriptor, value is the path of the video or media device. */
> +	std::unordered_map<int, std::string> devices;
> +	/* List of output and capture buffers being traced. */
> +	std::list<struct buffer_trace> buffers;
> +};
> +
> +bool options_are_set(void);
> +void set_options(void);
> +bool pretty_print_mem(void);
> +bool pretty_print_all(void);
> +bool write_decoded_data_to_json(void);
> +bool create_yuv_file(void);
> +
> +void add_device(int fd, std::string path);
> +std::string get_device(int fd);
> +int remove_device(int fd);
> +void add_buffer_trace(int fd, __u32 type, __u32 index);
> +int get_buffer_fd_trace(__u32 type, __u32 index);
> +__u32 get_buffer_type_trace(int fd);
> +int get_buffer_index_trace(int fd);
> +void set_buffer_address_trace(int fd, long address);
> +long get_buffer_address_trace(int fd);
> +void set_buffer_bytesused_trace(int fd, __u32 bytesused);
> +long get_buffer_bytesused_trace(int fd);
> +bool buffer_is_mapped(long buffer_address);
> +void write_json_object_to_json_file(json_object *jobj, int flags = JSON_C_TO_STRING_PLAIN);
> +
> +json_object *trace_open(const char *path, int oflag, mode_t mode);
> +json_object *trace_mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
> +void trace_mem(int fd);
> +std::string get_ioctl_request_str(unsigned long request);
> +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, bool from_userspace = true);
> +
> +void trace_v4l2_ctrl_h264_decode_mode(struct v4l2_ext_control ctrl, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_start_code(struct v4l2_ext_control ctrl, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_sps(void *p_h264_sps, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_pps(void *p_h264_pps, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_scaling_matrix(void *p_h264_scaling_matrix, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_pred_weights(void *p_h264_pred_weights, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_slice_params(void *p_h264_slice_params, json_object *ctrl_obj);
> +void trace_v4l2_ctrl_h264_decode_params(void *p_h264_decode_params, json_object *ctrl_obj);
> +
> +void trace_v4l2_ctrl_vp8_frame(void *p_vp8_frame, json_object *ctrl_obj);
> +
> +void trace_v4l2_ctrl_fwht_params(void *p_fwht_params, json_object *ctrl_obj);
> +
> +#endif
> diff --git a/utils/tracer/trace-helper.cpp b/utils/tracer/trace-helper.cpp
> new file mode 100644
> index 00000000..402c602b
> --- /dev/null
> +++ b/utils/tracer/trace-helper.cpp
> @@ -0,0 +1,218 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include "libtracer.h"
> +
> +struct trace_options options;
> +
> +struct trace_context ctx_trace = {
> +	.lock = PTHREAD_MUTEX_INITIALIZER
> +};
> +
> +bool options_are_set(void)
> +{
> +	return options.set;
> +}
> +
> +void set_options(void)
> +{
> +	options.pretty_print_mem = getenv("TRACE_OPTION_PRETTY_PRINT_MEM") ? true : false;
> +	options.pretty_print_all = getenv("TRACE_OPTION_PRETTY_PRINT_ALL") ? true : false;
> +	options.decoded_data_to_json = getenv("TRACE_OPTION_DECODED_TO_JSON") ? true : false;
> +	options.create_yuv_file = getenv("TRACE_OPTION_CREATE_YUV_FILE") ? true : false;
> +	options.set = true;
> +}
> +
> +bool pretty_print_mem(void)
> +{
> +	return options.pretty_print_mem;
> +}
> +
> +bool pretty_print_all(void)
> +{
> +	return options.pretty_print_all;
> +}
> +
> +bool write_decoded_data_to_json(void)
> +{
> +	return options.decoded_data_to_json;
> +}
> +
> +bool create_yuv_file(void)
> +{
> +	return options.create_yuv_file;
> +}
> +
> +void add_device(int fd, std::string path)
> +{
> +	std::pair<int, std::string> new_pair = std::make_pair(fd, path);
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	ctx_trace.devices.insert(new_pair);
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +}
> +
> +std::string get_device(int fd)
> +{
> +	std::string path;
> +	std::unordered_map<int, std::string>::const_iterator it;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	it = ctx_trace.devices.find(fd);
> +	if (it != ctx_trace.devices.end())
> +		path = it->second;
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return path;
> +}
> +
> +int remove_device(int fd)
> +{
> +	int ret = 0;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	ret = ctx_trace.devices.erase(fd);
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return ret;
> +}
> +
> +void add_buffer_trace(int fd, __u32 type, __u32 index)
> +{
> +	struct buffer_trace buf;
> +	memset(&buf, 0, sizeof(buffer_trace));
> +	buf.fd = fd;
> +	buf.type = type;
> +	buf.index = index;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	ctx_trace.buffers.push_front(buf);
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +}
> +
> +int get_buffer_fd_trace(__u32 type, __u32 index)
> +{
> +	int fd = 0;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if ((it->type == type) && (it->index == index)) {
> +			fd = it->fd;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return fd;
> +}
> +
> +__u32 get_buffer_type_trace(int fd)
> +{
> +	__u32 ret = 0;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			ret = it->type;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return ret;
> +}
> +
> +int get_buffer_index_trace(int fd)
> +{
> +	int index = -1;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			index = it->index;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return index;
> +}
> +
> +void set_buffer_address_trace(int fd, long address)
> +{
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			it->address = address;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +}
> +
> +long get_buffer_address_trace(int fd)
> +{
> +	long int address = 0;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			address = it->address;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return address;
> +}
> +
> +void set_buffer_bytesused_trace(int fd, __u32 bytesused)
> +{
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			it->bytesused = bytesused;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +}
> +
> +long get_buffer_bytesused_trace(int fd)
> +{
> +	long int bytesused = -1;
> +
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.begin(); it != ctx_trace.buffers.end(); ++it) {
> +		if (it->fd == fd) {
> +			bytesused = it->bytesused;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +
> +	return bytesused;
> +}
> +
> +/* Returns true if the memory address is a mapped buffer address, false otherwise. */
> +bool buffer_is_mapped(long buffer_address)
> +{
> +	bool ret = false;
> +	pthread_mutex_lock(&ctx_trace.lock);
> +	for (auto it = ctx_trace.buffers.cbegin(); it != ctx_trace.buffers.cend(); ++it) {
> +		if (it->address == buffer_address) {
> +			ret = true;
> +			break;
> +		}
> +	}
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +	return ret;
> +}
> +
> +void write_json_object_to_json_file(json_object *jobj, int flags)
> +{
> +	if (pretty_print_all())
> +		flags = JSON_C_TO_STRING_PRETTY;
> +
> +	std::string json_str = json_object_to_json_string_ext(jobj, flags);
> +	pthread_mutex_lock(&ctx_trace.lock);
> +
> +	if (ctx_trace.trace_filename.empty()) {
> +		ctx_trace.trace_filename = getenv("TRACE_ID");
> +		ctx_trace.trace_filename += ".json";
> +		ctx_trace.trace_file = fopen(ctx_trace.trace_filename.c_str(), "a");
> +	}
> +
> +	fwrite(json_str.c_str(), sizeof(char), json_str.length(), ctx_trace.trace_file);
> +	fputs(",\n", ctx_trace.trace_file);
> +	fflush(ctx_trace.trace_file);
> +	pthread_mutex_unlock(&ctx_trace.lock);
> +}
> diff --git a/utils/tracer/trace-info.cpp b/utils/tracer/trace-info.cpp
> new file mode 100644
> index 00000000..a1d58c64
> --- /dev/null
> +++ b/utils/tracer/trace-info.cpp
> @@ -0,0 +1,358 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include <v4l2-info.h>
> +#include "trace-info.h"
> +
> +struct definition {
> +	unsigned long val;
> +	const char *str;
> +};
> +
> +static std::string val2s(unsigned long val, const definition *def)
> +{
> +	std::string s;
> +
> +	if (val == 0)
> +		return s;
> +
> +	while ((def->val) && (def->val != val))
> +		def++;
> +
> +	if (def->val == val)
> +		s = def->str;
> +
> +	return s;
> +}
> +
> +std::string ioctl2s_video(unsigned long request)
> +{
> +	static constexpr definition defs[] = {
> +		{ VIDIOC_QUERYCAP,    "VIDIOC_QUERYCAP" },
> +		{ VIDIOC_ENUM_FMT,    "VIDIOC_ENUM_FMT" },
> +		{ VIDIOC_G_FMT,    "VIDIOC_G_FMT" },
> +		{ VIDIOC_S_FMT,    "VIDIOC_S_FMT" },
> +		{ VIDIOC_REQBUFS,    "VIDIOC_REQBUFS" },
> +		{ VIDIOC_QUERYBUF,    "VIDIOC_QUERYBUF" },
> +		{ VIDIOC_G_FBUF,    "VIDIOC_G_FBUF" },
> +		{ VIDIOC_S_FBUF,    "VIDIOC_S_FBUF" },
> +		{ VIDIOC_OVERLAY,    "VIDIOC_OVERLAY" },
> +		{ VIDIOC_QBUF,    "VIDIOC_QBUF" },
> +		{ VIDIOC_EXPBUF,    "VIDIOC_EXPBUF" },
> +		{ VIDIOC_DQBUF,    "VIDIOC_DQBUF" },
> +		{ VIDIOC_STREAMON,    "VIDIOC_STREAMON" },
> +		{ VIDIOC_STREAMOFF,    "VIDIOC_STREAMOFF" },
> +		{ VIDIOC_G_PARM,    "VIDIOC_G_PARM" },
> +		{ VIDIOC_S_PARM,    "VIDIOC_S_PARM" },
> +		{ VIDIOC_G_STD,    "VIDIOC_G_STD" },
> +		{ VIDIOC_S_STD,    "VIDIOC_S_STD" },
> +		{ VIDIOC_ENUMSTD,    "VIDIOC_ENUMSTD" },
> +		{ VIDIOC_ENUMINPUT,    "VIDIOC_ENUMINPUT" },
> +		{ VIDIOC_G_CTRL,    "VIDIOC_G_CTRL" },
> +		{ VIDIOC_S_CTRL,    "VIDIOC_S_CTRL" },
> +		{ VIDIOC_G_TUNER,    "VIDIOC_G_TUNER" },
> +		{ VIDIOC_S_TUNER,    "VIDIOC_S_TUNER" },
> +		{ VIDIOC_G_AUDIO,    "VIDIOC_G_AUDIO" },
> +		{ VIDIOC_S_AUDIO,    "VIDIOC_S_AUDIO" },
> +		{ VIDIOC_QUERYCTRL,    "VIDIOC_QUERYCTRL" },
> +		{ VIDIOC_QUERYMENU,    "VIDIOC_QUERYMENU" },
> +		{ VIDIOC_G_INPUT,    "VIDIOC_G_INPUT" },
> +		{ VIDIOC_S_INPUT,    "VIDIOC_S_INPUT" },
> +		{ VIDIOC_G_EDID,    "VIDIOC_G_EDID" },
> +		{ VIDIOC_S_EDID,    "VIDIOC_S_EDID" },
> +		{ VIDIOC_G_OUTPUT,    "VIDIOC_G_OUTPUT" },
> +		{ VIDIOC_S_OUTPUT,    "VIDIOC_S_OUTPUT" },
> +		{ VIDIOC_ENUMOUTPUT,    "VIDIOC_ENUMOUTPUT" },
> +		{ VIDIOC_G_AUDOUT,    "VIDIOC_G_AUDOUT" },
> +		{ VIDIOC_S_AUDOUT,    "VIDIOC_S_AUDOUT" },
> +		{ VIDIOC_G_MODULATOR,    "VIDIOC_G_MODULATOR" },
> +		{ VIDIOC_S_MODULATOR,    "VIDIOC_S_MODULATOR" },
> +		{ VIDIOC_G_FREQUENCY,    "VIDIOC_G_FREQUENCY" },
> +		{ VIDIOC_S_FREQUENCY,    "VIDIOC_S_FREQUENCY" },
> +		{ VIDIOC_CROPCAP,    "VIDIOC_CROPCAP" },
> +		{ VIDIOC_G_CROP,    "VIDIOC_G_CROP" },
> +		{ VIDIOC_S_CROP,    "VIDIOC_S_CROP" },
> +		{ VIDIOC_G_JPEGCOMP,    "VIDIOC_G_JPEGCOMP" },
> +		{ VIDIOC_S_JPEGCOMP,    "VIDIOC_S_JPEGCOMP" },
> +		{ VIDIOC_QUERYSTD,    "VIDIOC_QUERYSTD" },
> +		{ VIDIOC_TRY_FMT,    "VIDIOC_TRY_FMT" },
> +		{ VIDIOC_ENUMAUDIO,    "VIDIOC_ENUMAUDIO" },
> +		{ VIDIOC_ENUMAUDOUT,    "VIDIOC_ENUMAUDOUT" },
> +		{ VIDIOC_G_PRIORITY,    "VIDIOC_G_PRIORITY" },
> +		{ VIDIOC_S_PRIORITY,    "VIDIOC_S_PRIORITY" },
> +		{ VIDIOC_G_SLICED_VBI_CAP,    "VIDIOC_G_SLICED_VBI_CAP" },
> +		{ VIDIOC_LOG_STATUS,    "VIDIOC_LOG_STATUS" },
> +		{ VIDIOC_G_EXT_CTRLS,    "VIDIOC_G_EXT_CTRLS" },
> +		{ VIDIOC_S_EXT_CTRLS,    "VIDIOC_S_EXT_CTRLS" },
> +		{ VIDIOC_TRY_EXT_CTRLS,    "VIDIOC_TRY_EXT_CTRLS" },
> +		{ VIDIOC_ENUM_FRAMESIZES,    "VIDIOC_ENUM_FRAMESIZES" },
> +		{ VIDIOC_ENUM_FRAMEINTERVALS,    "VIDIOC_ENUM_FRAMEINTERVALS" },
> +		{ VIDIOC_G_ENC_INDEX,    "VIDIOC_G_ENC_INDEX" },
> +		{ VIDIOC_ENCODER_CMD,    "VIDIOC_ENCODER_CMD" },
> +		{ VIDIOC_TRY_ENCODER_CMD,    "VIDIOC_TRY_ENCODER_CMD" },
> +		{ VIDIOC_DBG_S_REGISTER,    "VIDIOC_DBG_S_REGISTER" },
> +		{ VIDIOC_DBG_G_REGISTER,    "VIDIOC_DBG_G_REGISTER" },
> +		{ VIDIOC_S_HW_FREQ_SEEK,    "VIDIOC_S_HW_FREQ_SEEK" },
> +		{ VIDIOC_S_DV_TIMINGS,    "VIDIOC_S_DV_TIMINGS" },
> +		{ VIDIOC_G_DV_TIMINGS,    "VIDIOC_G_DV_TIMINGS" },
> +		{ VIDIOC_DQEVENT,    "VIDIOC_DQEVENT" },
> +		{ VIDIOC_SUBSCRIBE_EVENT,    "VIDIOC_SUBSCRIBE_EVENT" },
> +		{ VIDIOC_UNSUBSCRIBE_EVENT,    "VIDIOC_UNSUBSCRIBE_EVENT" },
> +		{ VIDIOC_CREATE_BUFS,    "VIDIOC_CREATE_BUFS" },
> +		{ VIDIOC_PREPARE_BUF,    "VIDIOC_PREPARE_BUF" },
> +		{ VIDIOC_G_SELECTION,    "VIDIOC_G_SELECTION" },
> +		{ VIDIOC_S_SELECTION,    "VIDIOC_S_SELECTION" },
> +		{ VIDIOC_DECODER_CMD,    "VIDIOC_DECODER_CMD" },
> +		{ VIDIOC_TRY_DECODER_CMD,    "VIDIOC_TRY_DECODER_CMD" },
> +		{ VIDIOC_ENUM_DV_TIMINGS,    "VIDIOC_ENUM_DV_TIMINGS" },
> +		{ VIDIOC_QUERY_DV_TIMINGS,    "VIDIOC_QUERY_DV_TIMINGS" },
> +		{ VIDIOC_DV_TIMINGS_CAP,    "VIDIOC_DV_TIMINGS_CAP" },
> +		{ VIDIOC_ENUM_FREQ_BANDS,    "VIDIOC_ENUM_FREQ_BANDS" },
> +		{ VIDIOC_DBG_G_CHIP_INFO,    "VIDIOC_DBG_G_CHIP_INFO" },
> +		{ VIDIOC_QUERY_EXT_CTRL,    "VIDIOC_QUERY_EXT_CTRL" },
> +		{ BASE_VIDIOC_PRIVATE,    "BASE_VIDIOC_PRIVATE" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(request, defs);
> +}
> +
> +std::string ioctl2s_media(unsigned long request)
> +{
> +	static constexpr definition defs[] = {
> +		{ MEDIA_IOC_DEVICE_INFO,    "MEDIA_IOC_DEVICE_INFO" },
> +		{ MEDIA_IOC_ENUM_ENTITIES,    "MEDIA_IOC_ENUM_ENTITIES" },
> +		{ MEDIA_IOC_ENUM_LINKS,    "MEDIA_IOC_ENUM_LINKS" },
> +		{ MEDIA_IOC_SETUP_LINK,    "MEDIA_IOC_SETUP_LINK" },
> +		{ MEDIA_IOC_G_TOPOLOGY,    "MEDIA_IOC_G_TOPOLOGY" },
> +		{ MEDIA_IOC_REQUEST_ALLOC,    "MEDIA_IOC_REQUEST_ALLOC" },
> +		{ MEDIA_REQUEST_IOC_QUEUE,    "MEDIA_REQUEST_IOC_QUEUE" },
> +		{ MEDIA_REQUEST_IOC_REINIT,    "MEDIA_REQUEST_IOC_REINIT" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(request, defs);
> +}
> +
> +std::string ctrltype2s(__u32 val)
> +{
> +	static constexpr definition defs[] = {
> +		{ V4L2_CTRL_TYPE_INTEGER,    "V4L2_CTRL_TYPE_INTEGER" },
> +		{ V4L2_CTRL_TYPE_BOOLEAN,    "V4L2_CTRL_TYPE_BOOLEAN" },
> +		{ V4L2_CTRL_TYPE_MENU,    "V4L2_CTRL_TYPE_MENU" },
> +		{ V4L2_CTRL_TYPE_BUTTON,    "V4L2_CTRL_TYPE_BUTTON" },
> +		{ V4L2_CTRL_TYPE_INTEGER64,    "V4L2_CTRL_TYPE_INTEGER64" },
> +		{ V4L2_CTRL_TYPE_CTRL_CLASS,    "V4L2_CTRL_TYPE_CTRL_CLASS" },
> +		{ V4L2_CTRL_TYPE_STRING,    "V4L2_CTRL_TYPE_STRING" },
> +		{ V4L2_CTRL_TYPE_BITMASK,    "V4L2_CTRL_TYPE_BITMASK" },
> +		{ V4L2_CTRL_TYPE_INTEGER_MENU,    "V4L2_CTRL_TYPE_INTEGER_MENU" },
> +		{ V4L2_CTRL_COMPOUND_TYPES,    "V4L2_CTRL_COMPOUND_TYPES" },
> +		{ V4L2_CTRL_TYPE_U8,    "V4L2_CTRL_TYPE_U8" },
> +		{ V4L2_CTRL_TYPE_U16,    "V4L2_CTRL_TYPE_U16" },
> +		{ V4L2_CTRL_TYPE_U32,    "V4L2_CTRL_TYPE_U32" },
> +		{ V4L2_CTRL_TYPE_AREA,    "V4L2_CTRL_TYPE_AREA" },
> +		{ V4L2_CTRL_TYPE_HDR10_CLL_INFO,    "V4L2_CTRL_TYPE_HDR10_CLL_INFO" },
> +		{ V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY,    "V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY" },
> +		{ V4L2_CTRL_TYPE_H264_SPS,    "V4L2_CTRL_TYPE_H264_SPS" },
> +		{ V4L2_CTRL_TYPE_H264_PPS,    "V4L2_CTRL_TYPE_H264_PPS" },
> +		{ V4L2_CTRL_TYPE_H264_SCALING_MATRIX,    "V4L2_CTRL_TYPE_H264_SCALING_MATRIX" },
> +		{ V4L2_CTRL_TYPE_H264_SLICE_PARAMS,    "V4L2_CTRL_TYPE_H264_SLICE_PARAMS" },
> +		{ V4L2_CTRL_TYPE_H264_DECODE_PARAMS,    "V4L2_CTRL_TYPE_H264_DECODE_PARAMS" },
> +		{ V4L2_CTRL_TYPE_H264_PRED_WEIGHTS,    "V4L2_CTRL_TYPE_H264_PRED_WEIGHTS" },
> +		{ V4L2_CTRL_TYPE_FWHT_PARAMS,    "V4L2_CTRL_TYPE_FWHT_PARAMS" },
> +		{ V4L2_CTRL_TYPE_VP8_FRAME,    "V4L2_CTRL_TYPE_VP8_FRAME" },
> +		{ V4L2_CTRL_TYPE_MPEG2_QUANTISATION,    "V4L2_CTRL_TYPE_MPEG2_QUANTISATION" },
> +		{ V4L2_CTRL_TYPE_MPEG2_SEQUENCE,    "V4L2_CTRL_TYPE_MPEG2_SEQUENCE" },
> +		{ V4L2_CTRL_TYPE_MPEG2_PICTURE,    "V4L2_CTRL_TYPE_MPEG2_PICTURE" },
> +		{ V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR,    "V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR" },
> +		{ V4L2_CTRL_TYPE_VP9_FRAME,    "V4L2_CTRL_TYPE_VP9_FRAME" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(val, defs);
> +}
> +
> +std::string capflag2s(unsigned long cap)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_CAP_VIDEO_M2M_MPLANE,    "V4L2_CAP_VIDEO_M2M_MPLANE" },
> +		{ V4L2_CAP_VIDEO_CAPTURE, "V4L2_CAP_VIDEO_CAPTURE" },
> +		{ V4L2_CAP_VIDEO_OUTPUT, "V4L2_CAP_VIDEO_OUTPUT" },
> +		{ V4L2_CAP_VIDEO_OVERLAY, "V4L2_CAP_VIDEO_OVERLAY" },
> +		{ V4L2_CAP_VBI_CAPTURE, "V4L2_CAP_VBI_CAPTURE" },
> +		{ V4L2_CAP_VBI_OUTPUT, "V4L2_CAP_VBI_OUTPUT" },
> +		{ V4L2_CAP_SLICED_VBI_CAPTURE, "V4L2_CAP_SLICED_VBI_CAPTURE" },
> +		{ V4L2_CAP_SLICED_VBI_OUTPUT, "V4L2_CAP_SLICED_VBI_OUTPUT" },
> +		{ V4L2_CAP_RDS_CAPTURE, "V4L2_CAP_RDS_CAPTURE" },
> +		{ V4L2_CAP_VIDEO_OUTPUT_OVERLAY, "V4L2_CAP_VIDEO_OUTPUT_OVERLAY" },
> +		{ V4L2_CAP_HW_FREQ_SEEK, "V4L2_CAP_HW_FREQ_SEEK" },
> +		{ V4L2_CAP_RDS_OUTPUT, "V4L2_CAP_RDS_OUTPUT" },
> +		{ V4L2_CAP_VIDEO_CAPTURE_MPLANE, "V4L2_CAP_VIDEO_CAPTURE_MPLANE" },
> +		{ V4L2_CAP_VIDEO_OUTPUT_MPLANE, "V4L2_CAP_VIDEO_OUTPUT_MPLANE" },
> +		{ V4L2_CAP_VIDEO_M2M_MPLANE, "V4L2_CAP_VIDEO_M2M_MPLANE" },
> +		{ V4L2_CAP_VIDEO_M2M, "V4L2_CAP_VIDEO_M2M" },
> +		{ V4L2_CAP_TUNER, "V4L2_CAP_TUNER" },
> +		{ V4L2_CAP_AUDIO, "V4L2_CAP_AUDIO" },
> +		{ V4L2_CAP_RADIO, "V4L2_CAP_RADIO" },
> +		{ V4L2_CAP_MODULATOR, "V4L2_CAP_MODULATOR" },
> +		{ V4L2_CAP_SDR_CAPTURE, "V4L2_CAP_SDR_CAPTURE" },
> +		{ V4L2_CAP_EXT_PIX_FORMAT, "V4L2_CAP_EXT_PIX_FORMAT" },
> +		{ V4L2_CAP_SDR_OUTPUT, "V4L2_CAP_SDR_OUTPUT" },
> +		{ V4L2_CAP_META_CAPTURE, "V4L2_CAP_META_CAPTURE" },
> +		{ V4L2_CAP_READWRITE, "V4L2_CAP_READWRITE" },
> +		{ V4L2_CAP_ASYNCIO, "V4L2_CAP_ASYNCIO" },
> +		{ V4L2_CAP_STREAMING, "V4L2_CAP_STREAMING" },
> +		{ V4L2_CAP_META_OUTPUT, "V4L2_CAP_META_OUTPUT" },
> +		{ V4L2_CAP_TOUCH, "V4L2_CAP_TOUCH" },
> +		{ V4L2_CAP_IO_MC, "V4L2_CAP_IO_MC" },
> +		{ V4L2_CAP_DEVICE_CAPS, "V4L2_CAP_DEVICE_CAPS" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(cap, def);
> +}
> +
> +std::string bufsyncflag2s(unsigned long flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ DMA_BUF_SYNC_READ , "DMA_BUF_SYNC_READ " },
> +		{ DMA_BUF_SYNC_WRITE, "DMA_BUF_SYNC_WRITE" },
> +		{ DMA_BUF_SYNC_RW , "DMA_BUF_SYNC_RW " },
> +		{ DMA_BUF_SYNC_START , "DMA_BUF_SYNC_START" },
> +		{ DMA_BUF_SYNC_END, "DMA_BUF_SYNC_END" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> +
> +std::string vp8_segment_flag2s(unsigned long flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_VP8_SEGMENT_FLAG_ENABLED, "V4L2_VP8_SEGMENT_FLAG_ENABLED" },
> +		{ V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP, "V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP" },
> +		{ V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA, "V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA" },
> +		{ V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE, "V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> +
> +std::string vp8_loop_filter_flag2s(unsigned long flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_VP8_LF_ADJ_ENABLE, "V4L2_VP8_LF_ADJ_ENABLE" },
> +		{ V4L2_VP8_LF_DELTA_UPDATE, "V4L2_VP8_LF_DELTA_UPDATE" },
> +		{ V4L2_VP8_LF_FILTER_TYPE_SIMPLE, "V4L2_VP8_LF_FILTER_TYPE_SIMPLE" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> +
> +std::string vp8_frame_flag2s(unsigned long flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_VP8_FRAME_FLAG_KEY_FRAME, "V4L2_VP8_FRAME_FLAG_KEY_FRAME" },
> +		{ V4L2_VP8_FRAME_FLAG_EXPERIMENTAL, "V4L2_VP8_FRAME_FLAG_EXPERIMENTAL" },
> +		{ V4L2_VP8_FRAME_FLAG_SHOW_FRAME, "V4L2_VP8_FRAME_FLAG_SHOW_FRAME" },
> +		{ V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF, "V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF" },
> +		{ V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN, "V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN" },
> +		{ V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT, "V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> +
> +std::string ver2s(unsigned int version)
> +{
> +	char buf[16];
> +	sprintf(buf, "%d.%d.%d", version >> 16, (version >> 8) & 0xff, version & 0xff);
> +	return buf;
> +}
> +
> +std::string which2s(unsigned long which)
> +{
> +	std::string s = "unknown";
> +
> +	switch (which) {
> +	case V4L2_CTRL_WHICH_CUR_VAL:
> +		s = "V4L2_CTRL_WHICH_CUR_VAL";
> +		break;
> +	case V4L2_CTRL_WHICH_DEF_VAL:
> +		s= "V4L2_CTRL_WHICH_DEF_VAL";
> +		break;
> +	case V4L2_CTRL_WHICH_REQUEST_VAL:
> +		s = "V4L2_CTRL_WHICH_REQUEST_VAL";
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return s;
> +}
> +
> +std::string ctrlclass2s(__u32 id)
> +{
> +	static constexpr definition defs[] = {
> +		{ V4L2_CTRL_CLASS_USER, "V4L2_CTRL_CLASS_USER" },
> +		{ V4L2_CTRL_CLASS_CODEC,	"V4L2_CTRL_CLASS_CODEC" },
> +		{ V4L2_CTRL_CLASS_CAMERA, "V4L2_CTRL_CLASS_CAMERA" },
> +		{ V4L2_CTRL_CLASS_FM_TX, "V4L2_CTRL_CLASS_FM_TX" },
> +		{ V4L2_CTRL_CLASS_FLASH, "V4L2_CTRL_CLASS_FLASH" },
> +		{ V4L2_CTRL_CLASS_JPEG,	"V4L2_CTRL_CLASS_JPEG" },
> +		{ V4L2_CTRL_CLASS_IMAGE_SOURCE,	"V4L2_CTRL_CLASS_IMAGE_SOURCE" },
> +		{ V4L2_CTRL_CLASS_IMAGE_PROC, "V4L2_CTRL_CLASS_IMAGE_PROC" },
> +		{ V4L2_CTRL_CLASS_DV, "V4L2_CTRL_CLASS_DV" },
> +		{ V4L2_CTRL_CLASS_FM_RX, "V4L2_CTRL_CLASS_FM_RX" },
> +		{ V4L2_CTRL_CLASS_RF_TUNER, "V4L2_CTRL_CLASS_RF_TUNER" },
> +		{ V4L2_CTRL_CLASS_DETECT, "V4L2_CTRL_CLASS_DETECT" },
> +		{ V4L2_CTRL_CLASS_CODEC_STATELESS, "V4L2_CTRL_CLASS_CODEC_STATELESS" },
> +		{ V4L2_CTRL_CLASS_COLORIMETRY, "V4L2_CTRL_CLASS_COLORIMETRY" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(id & 0xff0000, defs);
> +}
> +
> +std::string request_buffers_flag2s(unsigned int flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_MEMORY_FLAG_NON_COHERENT, "V4L2_MEMORY_FLAG_NON_COHERENT" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> +
> +std::string v4l2_memory2s(__u32 id)
> +{
> +	static constexpr definition defs[] = {
> +		{ V4L2_MEMORY_MMAP, "V4L2_MEMORY_MMAP" },
> +		{ V4L2_MEMORY_USERPTR, "V4L2_MEMORY_USERPTR" },
> +		{ V4L2_MEMORY_OVERLAY, "V4L2_MEMORY_OVERLAY" },
> +		{ V4L2_MEMORY_DMABUF, "V4L2_MEMORY_DMABUF" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(id, defs);
> +}
> +
> +std::string tc_type2s(__u32 id)
> +{
> +	static constexpr definition defs[] = {
> +		{ V4L2_TC_TYPE_24FPS, "V4L2_TC_TYPE_24FPS" },
> +		{ V4L2_TC_TYPE_25FPS, "V4L2_TC_TYPE_25FPS" },
> +		{ V4L2_TC_TYPE_30FPS, "V4L2_TC_TYPE_30FPS" },
> +		{ V4L2_TC_TYPE_50FPS, "V4L2_TC_TYPE_50FPS" },
> +		{ V4L2_TC_TYPE_60FPS, "V4L2_TC_TYPE_60FPS" },
> +		{ 0, nullptr }
> +	};
> +	return val2s(id, defs);
> +}
> +
> +std::string tc_flag2s(unsigned int flag)
> +{
> +	static constexpr flag_def def[] = {
> +		{ V4L2_TC_FLAG_DROPFRAME, "V4L2_TC_FLAG_DROPFRAME" },
> +		{ V4L2_TC_FLAG_COLORFRAME, "V4L2_TC_FLAG_COLORFRAME" },
> +		{ V4L2_TC_USERBITS_field, "V4L2_TC_USERBITS_field" },
> +		{ V4L2_TC_USERBITS_USERDEFINED, "V4L2_TC_USERBITS_USERDEFINED" },
> +		{ V4L2_TC_USERBITS_8BITCHARS, "V4L2_TC_USERBITS_8BITCHARS" },
> +		{ 0, nullptr }
> +	};
> +	return flags2s(flag, def);
> +}
> diff --git a/utils/tracer/trace-info.h b/utils/tracer/trace-info.h
> new file mode 100644
> index 00000000..4c06190a
> --- /dev/null
> +++ b/utils/tracer/trace-info.h
> @@ -0,0 +1,72 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#ifndef TRACE_INFO_H
> +#define TRACE_INFO_H
> +
> +#include <cstring>
> +#include <linux/videodev2.h>
> +#include <linux/media.h>
> +#include <linux/dma-buf.h>
> +
> +#define ARRAY_SIZE(array)	(sizeof(array) / sizeof((array)[0]))
> +
> +enum LIBTRACER_SYSCALL {
> +	LIBTRACER_SYSCALL_IOCTL,
> +	LIBTRACER_SYSCALL_OPEN,
> +	LIBTRACER_SYSCALL_OPEN64,
> +	LIBTRACER_SYSCALL_CLOSE,
> +	LIBTRACER_SYSCALL_MMAP64,
> +	LIBTRACER_SYSCALL_MUNMAP,
> +	LIBTRACER_SYSCALL_SELECT,
> +	LIBTRACER_SYSCALL_POLL,
> +};
> +
> +/* Convert an ioctl request of type 'V' to string. */
> +std::string ioctl2s_video(unsigned long request);
> +
> +/* Convert an ioctl request of type '|' to string. */
> +std::string ioctl2s_media(unsigned long request);
> +
> +/* Convert capability flags to common separated string. */
> +std::string capflag2s(unsigned long cap);
> +
> +/* Convert a decimal number to a string with kernel_version.major_revision.minor_revision. */
> +std::string ver2s(unsigned int version);
> +
> +/* Convert v4l2_ext_controls member "which" to string. */
> +std::string which2s(unsigned long which);
> +
> +/* Convert control class to string. */
> +std::string ctrlclass2s(__u32 id);
> +
> +/* Convert control type to string. */
> +std::string ctrltype2s(__u32 type);
> +
> +/* Convert struct dma_buf_sync flags to string. */
> +std::string bufsyncflag2s(unsigned long flag);
> +
> +/* Convert v4l2_vp8_segment flags to string. */
> +std::string vp8_segment_flag2s(unsigned long flag);
> +
> +/* Convert v4l2_vp8_loop_filter flags to string. */
> +std::string vp8_loop_filter_flag2s(unsigned long flag);
> +
> +/* Convert v4l2_ctrl_vp8_frame flags to string. */
> +std::string vp8_frame_flag2s(unsigned long flag);
> +
> +/* Convert v4l2_requestbuffers flags to string. */
> +std::string request_buffers_flag2s(unsigned int flag);
> +
> +/* Convert enum v4l2_memory type to string. */
> +std::string v4l2_memory2s(__u32 id);
> +
> +/* Convert v4l2_timecode type to string. */
> +std::string tc_type2s(__u32 id);
> +
> +/* Convert v4l2_timecode flags to string. */
> +std::string tc_flag2s(unsigned int flag);
> +
> +#endif
> diff --git a/utils/tracer/trace-vp8.cpp b/utils/tracer/trace-vp8.cpp
> new file mode 100644
> index 00000000..a0a8ccdd
> --- /dev/null
> +++ b/utils/tracer/trace-vp8.cpp
> @@ -0,0 +1,183 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include "libtracer.h"
> +
> +json_object *trace_v4l2_vp8_segment(struct v4l2_vp8_segment segment)
> +{
> +	json_object *vp8_segment_obj = json_object_new_object();
> +
> +	/* __s8 quant_update[4] */
> +	json_object *quant_update_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(segment.quant_update); i++)
> +		json_object_array_add(quant_update_obj, json_object_new_int(segment.quant_update[i]));
> +	json_object_object_add(vp8_segment_obj, "quant_update", quant_update_obj);
> +
> +	/* __s8 lf_update[4] */
> +	json_object *lf_update_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(segment.lf_update); i++)
> +		json_object_array_add(lf_update_obj, json_object_new_int(segment.lf_update[i]));
> +	json_object_object_add(vp8_segment_obj, "lf_update", lf_update_obj);
> +
> +	/* __u8 segment_probs[3] */
> +	json_object *segment_probs_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(segment.segment_probs); i++)
> +		json_object_array_add(segment_probs_obj, json_object_new_int(segment.segment_probs[i]));
> +	json_object_object_add(vp8_segment_obj, "segment_probs", segment_probs_obj);
> +
> +	json_object_object_add(vp8_segment_obj, "padding", json_object_new_int(segment.padding));
> +	json_object_object_add(vp8_segment_obj, "flags", json_object_new_int(segment.flags));
> +	json_object_object_add(vp8_segment_obj, "flags_str",
> +						   json_object_new_string(vp8_segment_flag2s(segment.flags).c_str()));
> +
> +	return vp8_segment_obj;
> +}
> +
> +json_object *trace_v4l2_vp8_loop_filter(struct v4l2_vp8_loop_filter lf)
> +{
> +	json_object *vp8_loop_filter_obj = json_object_new_object();
> +
> +	/* 	__s8 ref_frm_delta[4] */
> +	json_object *ref_frm_delta_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(lf.ref_frm_delta); i++)
> +		json_object_array_add(ref_frm_delta_obj, json_object_new_int(lf.ref_frm_delta[i]));
> +	json_object_object_add(vp8_loop_filter_obj, "ref_frm_delta", ref_frm_delta_obj);
> +
> +	/* 	__s8 mb_mode_delta[4] */
> +	json_object *mb_mode_delta_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(lf.mb_mode_delta); i++)
> +		json_object_array_add(mb_mode_delta_obj, json_object_new_int(lf.mb_mode_delta[i]));
> +	json_object_object_add(vp8_loop_filter_obj, "mb_mode_delta", mb_mode_delta_obj);
> +
> +	json_object_object_add(vp8_loop_filter_obj, "sharpness_level", json_object_new_int(lf.sharpness_level));
> +	json_object_object_add(vp8_loop_filter_obj, "level", json_object_new_int(lf.level));
> +	json_object_object_add(vp8_loop_filter_obj, "padding", json_object_new_int(lf.padding));
> +	json_object_object_add(vp8_loop_filter_obj, "flags", json_object_new_int(lf.flags));
> +	json_object_object_add(vp8_loop_filter_obj, "flags_str", json_object_new_string(vp8_loop_filter_flag2s(lf.flags).c_str()));
> +
> +	return vp8_loop_filter_obj;
> +}
> +
> +json_object *trace_v4l2_vp8_quantization(struct v4l2_vp8_quantization quant)
> +{
> +	json_object *vp8_quantization_obj = json_object_new_object();
> +
> +	json_object_object_add(vp8_quantization_obj, "y_ac_qi", json_object_new_int(quant.y_ac_qi));
> +	json_object_object_add(vp8_quantization_obj, "y_dc_delta", json_object_new_int(quant.y_dc_delta));
> +	json_object_object_add(vp8_quantization_obj, "y2_dc_delta", json_object_new_int(quant.y2_dc_delta));
> +	json_object_object_add(vp8_quantization_obj, "y2_ac_delta", json_object_new_int(quant.y2_ac_delta));
> +	json_object_object_add(vp8_quantization_obj, "uv_dc_delta", json_object_new_int(quant.uv_dc_delta));
> +	json_object_object_add(vp8_quantization_obj, "uv_ac_delta", json_object_new_int(quant.uv_ac_delta));
> +	json_object_object_add(vp8_quantization_obj, "padding", json_object_new_int(quant.padding));
> +
> +	return vp8_quantization_obj;
> +}
> +
> +json_object *trace_v4l2_vp8_entropy(struct v4l2_vp8_entropy entropy)
> +{
> +	json_object *vp8_entropy_obj = json_object_new_object();
> +
> +	/*__u8 coeff_probs[4][8][3][V4L2_VP8_COEFF_PROB_CNT] */
> +	json_object *coeff_probs_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(entropy.coeff_probs); i++)
> +		for (size_t j = 0; j < ARRAY_SIZE(entropy.coeff_probs[0]); j++)
> +			for (size_t k = 0; k < ARRAY_SIZE(entropy.coeff_probs[0][0]); k++)
> +				for (size_t l = 0; l < V4L2_VP8_COEFF_PROB_CNT; l++)
> +					json_object_array_add(coeff_probs_obj,
> +					                      json_object_new_int(entropy.coeff_probs[i][j][k][l]));
> +	json_object_object_add(vp8_entropy_obj, "coeff_probs", coeff_probs_obj);
> +
> +	/* __u8 y_mode_probs[4] */
> +	json_object *y_mode_probs_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(entropy.y_mode_probs); i++)
> +		json_object_array_add(y_mode_probs_obj, json_object_new_int(entropy.y_mode_probs[i]));
> +	json_object_object_add(vp8_entropy_obj, "y_mode_probs", y_mode_probs_obj);
> +
> +	/* __u8 uv_mode_probs[3] */
> +	json_object *uv_mode_probs_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(entropy.uv_mode_probs); i++)
> +		json_object_array_add(uv_mode_probs_obj, json_object_new_int(entropy.uv_mode_probs[i]));
> +	json_object_object_add(vp8_entropy_obj, "uv_mode_probs", uv_mode_probs_obj);
> +
> +	/* __u8 mv_probs[2][V4L2_VP8_MV_PROB_CNT] */
> +	json_object *mv_probs_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(entropy.mv_probs); i++)
> +		for (size_t j = 0; j < V4L2_VP8_MV_PROB_CNT; j++)
> +			json_object_array_add(mv_probs_obj, json_object_new_int(entropy.mv_probs[i][j]));
> +	json_object_object_add(vp8_entropy_obj, "mv_probs", mv_probs_obj);
> +
> +	/*__u8 padding[3] */
> +	json_object *padding_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(entropy.padding); i++)
> +		json_object_array_add(padding_obj, json_object_new_int(entropy.padding[i]));
> +	json_object_object_add(vp8_entropy_obj, "padding", padding_obj);
> +
> +	return vp8_entropy_obj;
> +}
> +
> +json_object *trace_v4l2_vp8_entropy_coder_state(struct v4l2_vp8_entropy_coder_state coder_state)
> +{
> +	json_object *vp8_entropy_coder_state_obj = json_object_new_object();
> +
> +	json_object_object_add(vp8_entropy_coder_state_obj, "range", json_object_new_int(coder_state.range));
> +	json_object_object_add(vp8_entropy_coder_state_obj, "value", json_object_new_int(coder_state.value));
> +	json_object_object_add(vp8_entropy_coder_state_obj, "bit_count", json_object_new_int(coder_state.bit_count));
> +	json_object_object_add(vp8_entropy_coder_state_obj, "padding", json_object_new_int(coder_state.padding));
> +
> +	return vp8_entropy_coder_state_obj;
> +}
> +
> +void trace_v4l2_ctrl_vp8_frame(void *p_vp8_frame, json_object *ctrl_obj)
> +{
> +	json_object *vp8_frame_obj = json_object_new_object();
> +	struct v4l2_ctrl_vp8_frame *vp8_frame = static_cast<struct v4l2_ctrl_vp8_frame*>(p_vp8_frame);
> +
> +	/* struct v4l2_vp8_segment segment */
> +	json_object *v4l2_vp8_segment_obj = trace_v4l2_vp8_segment(vp8_frame->segment);
> +	json_object_object_add(vp8_frame_obj, "segment", v4l2_vp8_segment_obj);
> +
> +	/* struct v4l2_vp8_loop_filter lf */
> +	json_object *v4l2_vp8_loop_filter_obj = trace_v4l2_vp8_loop_filter(vp8_frame->lf);
> +	json_object_object_add(vp8_frame_obj, "lf", v4l2_vp8_loop_filter_obj);
> +
> +	/* struct v4l2_vp8_quantization quant */
> +	json_object *v4l2_vp8_quantization_obj = trace_v4l2_vp8_quantization(vp8_frame->quant);
> +	json_object_object_add(vp8_frame_obj, "quant", v4l2_vp8_quantization_obj);
> +
> +	/* struct v4l2_vp8_entropy entropy */
> +	json_object *v4l2_vp8_entropy_obj = trace_v4l2_vp8_entropy(vp8_frame->entropy);
> +	json_object_object_add(vp8_frame_obj, "entropy", v4l2_vp8_entropy_obj);
> +
> +	/* struct v4l2_vp8_entropy_coder_state coder_state */
> +	json_object *v4l2_vp8_entropy_coder_state_obj = trace_v4l2_vp8_entropy_coder_state(vp8_frame->coder_state);
> +	json_object_object_add(vp8_frame_obj, "coder_state", v4l2_vp8_entropy_coder_state_obj);
> +
> +	json_object_object_add(vp8_frame_obj, "width", json_object_new_int(vp8_frame->width));
> +	json_object_object_add(vp8_frame_obj, "height", json_object_new_int(vp8_frame->height));
> +	json_object_object_add(vp8_frame_obj, "horizontal_scale", json_object_new_int(vp8_frame->horizontal_scale));
> +	json_object_object_add(vp8_frame_obj, "vertical_scale", json_object_new_int(vp8_frame->vertical_scale));
> +	json_object_object_add(vp8_frame_obj, "version", json_object_new_int(vp8_frame->version));
> +	json_object_object_add(vp8_frame_obj, "prob_skip_false", json_object_new_int(vp8_frame->prob_skip_false));
> +	json_object_object_add(vp8_frame_obj, "prob_intra", json_object_new_int(vp8_frame->prob_intra));
> +	json_object_object_add(vp8_frame_obj, "prob_last", json_object_new_int(vp8_frame->prob_last));
> +	json_object_object_add(vp8_frame_obj, "prob_gf", json_object_new_int(vp8_frame->prob_gf));
> +	json_object_object_add(vp8_frame_obj, "num_dct_parts", json_object_new_int(vp8_frame->num_dct_parts));
> +	json_object_object_add(vp8_frame_obj, "first_part_size", json_object_new_int(vp8_frame->first_part_size));
> +	json_object_object_add(vp8_frame_obj, "first_part_header_bits", json_object_new_int(vp8_frame->first_part_header_bits));
> +
> +	/* __u32 dct_part_sizes[8] */
> +	json_object *dct_part_sizes_obj = json_object_new_array();
> +	for (size_t i = 0; i < ARRAY_SIZE(vp8_frame->dct_part_sizes); i++)
> +		json_object_array_add(dct_part_sizes_obj, json_object_new_int(vp8_frame->dct_part_sizes[i]));
> +	json_object_object_add(vp8_frame_obj, "dct_part_sizes", dct_part_sizes_obj);
> +
> +	json_object_object_add(vp8_frame_obj, "last_frame_ts", json_object_new_int(vp8_frame->last_frame_ts));
> +	json_object_object_add(vp8_frame_obj, "golden_frame_ts", json_object_new_int(vp8_frame->golden_frame_ts));
> +	json_object_object_add(vp8_frame_obj, "alt_frame_ts", json_object_new_int(vp8_frame->alt_frame_ts));
> +	json_object_object_add(vp8_frame_obj, "flags", json_object_new_int(vp8_frame->flags));
> +	json_object_object_add(vp8_frame_obj, "flags_str", json_object_new_string(vp8_frame_flag2s(vp8_frame->flags).c_str()));
> +
> +	json_object_object_add(ctrl_obj, "v4l2_ctrl_vp8_frame", vp8_frame_obj);
> +}
> diff --git a/utils/tracer/trace.cpp b/utils/tracer/trace.cpp
> new file mode 100644
> index 00000000..5e952a7b
> --- /dev/null
> +++ b/utils/tracer/trace.cpp
> @@ -0,0 +1,520 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include "libtracer.h"
> +
> +json_object *trace_open(const char *path, int oflag, mode_t mode)
> +{
> +	json_object *open_args = json_object_new_object();
> +
> +	json_object_object_add(open_args, "path", json_object_new_string(path));
> +	json_object_object_add(open_args, "oflag", json_object_new_int(oflag));
> +	json_object_object_add(open_args, "mode", json_object_new_uint64(mode));
> +
> +	return open_args;
> +}
> +
> +json_object *trace_mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
> +{
> +	json_object *mmap_args = json_object_new_object();
> +
> +	json_object_object_add(mmap_args, "addr", json_object_new_int64((int64_t)addr));
> +	json_object_object_add(mmap_args, "len", json_object_new_uint64(len));
> +	json_object_object_add(mmap_args, "prot", json_object_new_int(prot));
> +	json_object_object_add(mmap_args, "flags", json_object_new_int(flags));
> +	json_object_object_add(mmap_args, "fildes", json_object_new_int(fildes));
> +	json_object_object_add(mmap_args, "off", json_object_new_int64(off));
> +
> +	return mmap_args;
> +}
> +
> +/*
> + * Get a buffer from memory and convert it to a json string array.
> + * The bytes are displayed with the most-significant bits first.
> + */
> +json_object *trace_buffer(unsigned char *buffer_pointer, __u32 bytesused)
> +{
> +	char buf[5];
> +	std::string s;
> +	int byte_count_per_line = 0;
> +	json_object *mem_array_obj = json_object_new_array();
> +
> +	for (__u32 i = 0; i < bytesused; i++) {
> +		memset(buf, 0, sizeof(buf));
> +
> +		/* Each byte e.g. D9 will write a string of two characters "D9". */
> +		sprintf(buf, "%02x", buffer_pointer[i]);
> +		s += buf;
> +		byte_count_per_line++;
> +
> +		/*  Add a space every two bytes e.g. "012A 4001" and a newline every 16 bytes. */
> +		if (byte_count_per_line == 16) {
> +			byte_count_per_line = 0;
> +			json_object_array_add(mem_array_obj, json_object_new_string(s.c_str()));
> +			s.clear();
> +		} else if (i % 2) {
> +			s += " ";
> +		}
> +	}
> +
> +	/* Trace the last line if it was less than a full 16 bytes. */
> +	if (byte_count_per_line)
> +		json_object_array_add(mem_array_obj, json_object_new_string(s.c_str()));
> +
> +	return mem_array_obj;
> +}
> +
> +/* Get the decoded capture buffer from memory and write it to a binary yuv file. */
> +void write_decoded_frames_to_yuv_file(unsigned char *buffer_pointer, __u32 bytesused, std::string filename)
> +{
> +	FILE *fp = fopen(filename.c_str(), "a");
> +	for (__u32 i = 0; i < bytesused; i++)
> +		fwrite(&buffer_pointer[i], sizeof(unsigned char), 1, fp);
> +	fflush(fp);
> +	fclose(fp);
> +}
> +
> +/* Trace an output or capture buffer. */
> +void trace_mem(int fd)
> +{
> +	int index;
> +	__u32 type;
> +	__u32 bytesused;
> +	unsigned char *buffer_pointer;
> +	long int start = get_buffer_address_trace(fd);
> +
> +	/* Don't trace unmapped memory. */
> +	if (!start)
> +		return;
> +
> +	buffer_pointer = (unsigned char*) start;
> +	type = get_buffer_type_trace(fd);
> +	index = get_buffer_index_trace(fd);
> +	bytesused = get_buffer_bytesused_trace(fd);
> +
> +	json_object *mem_obj = json_object_new_object();
> +	json_object_object_add(mem_obj, "mem_dump", json_object_new_string(buftype2s(type).c_str()));
> +	json_object_object_add(mem_obj, "fd", json_object_new_int(fd));
> +	json_object_object_add(mem_obj, "type", json_object_new_uint64(type));
> +	json_object_object_add(mem_obj, "index", json_object_new_int(index));
> +	json_object_object_add(mem_obj, "bytesused", json_object_new_uint64(bytesused));
> +	json_object_object_add(mem_obj, "address", json_object_new_int64(start));
> +
> +	if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> +		json_object *mem_array_obj = trace_buffer(buffer_pointer, bytesused);
> +		json_object_object_add(mem_obj, "mem_array", mem_array_obj);
> +	}
> +
> +	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> +
> +		if (create_yuv_file()) {
> +			std::string filename = getenv("TRACE_ID");
> +			filename +=  ".yuv";
> +			write_decoded_frames_to_yuv_file(buffer_pointer, bytesused, filename);
> +			json_object_object_add(mem_obj, "filename", json_object_new_string(filename.c_str()));
> +		}
> +
> +		if (write_decoded_data_to_json()) {
> +			json_object *mem_array_obj = trace_buffer(buffer_pointer, bytesused);
> +			json_object_object_add(mem_obj, "mem_array_capture", mem_array_obj);
> +		}
> +
> +	}
> +
> +	if (pretty_print_mem())
> +		write_json_object_to_json_file(mem_obj, JSON_C_TO_STRING_PRETTY);
> +	else
> +		write_json_object_to_json_file(mem_obj);
> +
> +	json_object_put(mem_obj);
> +}
> +
> +void trace_vidioc_querycap(void *arg, json_object *ioctl_args)
> +{
> +	json_object *cap_obj = json_object_new_object();
> +	struct v4l2_capability *cap = static_cast<struct v4l2_capability*>(arg);
> +
> +	json_object_object_add(cap_obj, "driver", json_object_new_string((const char *)cap->driver));
> +	json_object_object_add(cap_obj, "card", json_object_new_string((const char *)cap->card));
> +	json_object_object_add(cap_obj, "bus_info", json_object_new_string((const char *)cap->bus_info));
> +	json_object_object_add(cap_obj, "version", json_object_new_string(ver2s(cap->version).c_str()));
> +	json_object_object_add(cap_obj, "capabilities", json_object_new_int64(cap->capabilities));
> +	json_object_object_add(cap_obj, "capabilities_str",
> +		                       json_object_new_string(capflag2s(cap->capabilities).c_str()));
> +	json_object_object_add(cap_obj, "device_caps", json_object_new_int64(cap->device_caps));
> +	json_object_object_add(ioctl_args, "v4l2_capability", cap_obj);
> +}
> +
> +void trace_vidioc_enum_fmt(void *arg, json_object *ioctl_args)
> +{
> +	json_object *fmtdesc_obj = json_object_new_object();
> +	struct v4l2_fmtdesc *fmtdesc = static_cast<struct v4l2_fmtdesc*>(arg);
> +
> +	json_object_object_add(fmtdesc_obj, "index", json_object_new_uint64(fmtdesc->index));
> +	json_object_object_add(fmtdesc_obj, "type_str", json_object_new_string(buftype2s(fmtdesc->type).c_str()));
> +	json_object_object_add(fmtdesc_obj, "type", json_object_new_uint64(fmtdesc->type));
> +	json_object_object_add(fmtdesc_obj, "flags", json_object_new_uint64(fmtdesc->flags));
> +	json_object_object_add(fmtdesc_obj, "description", json_object_new_string((const char *)fmtdesc->description));
> +	json_object_object_add(fmtdesc_obj, "pixelformat", json_object_new_uint64(fmtdesc->pixelformat));
> +	json_object_object_add(fmtdesc_obj, "mbus_code", json_object_new_uint64(fmtdesc->mbus_code));
> +	json_object_object_add(ioctl_args, "v4l2_fmtdesc", fmtdesc_obj);
> +}
> +
> +void trace_v4l2_plane_pix_format(json_object *pix_mp_obj, struct v4l2_plane_pix_format plane_fmt, int plane)
> +{
> +	json_object *plane_fmt_obj = json_object_new_object();
> +
> +	json_object_object_add(plane_fmt_obj, "sizeimage", json_object_new_uint64(plane_fmt.sizeimage));
> +	json_object_object_add(plane_fmt_obj, "bytesperline", json_object_new_uint64(plane_fmt.bytesperline));
> +
> +	/* Create a unique key name for each plane. */
> +	std::string unique_key_for_plane = "v4l2_plane_pix_format_";
> +	unique_key_for_plane += std::to_string(plane);
> +
> +	json_object_object_add(pix_mp_obj, unique_key_for_plane.c_str(), plane_fmt_obj);
> +}
> +
> +void trace_v4l2_pix_format_mplane(json_object *format_obj, struct v4l2_pix_format_mplane pix_mp)
> +{
> +	json_object *pix_mp_obj = json_object_new_object();
> +
> +	json_object_object_add(pix_mp_obj, "width", json_object_new_uint64(pix_mp.width));
> +	json_object_object_add(pix_mp_obj, "height", json_object_new_uint64(pix_mp.height));
> +	json_object_object_add(pix_mp_obj, "pixelformat", json_object_new_uint64(pix_mp.pixelformat));
> +	json_object_object_add(pix_mp_obj, "pixelformat_str", json_object_new_string(fcc2s(pix_mp.pixelformat).c_str()));
> +	json_object_object_add(pix_mp_obj, "field", json_object_new_uint64(pix_mp.field));
> +	json_object_object_add(pix_mp_obj, "field_str", json_object_new_string(field2s(pix_mp.field).c_str()));
> +	json_object_object_add(pix_mp_obj, "colorspace", json_object_new_uint64(pix_mp.colorspace));
> +	json_object_object_add(pix_mp_obj, "colorspace_str", json_object_new_string(colorspace2s(pix_mp.colorspace).c_str()));
> +	json_object_object_add(pix_mp_obj, "num_planes", json_object_new_int(pix_mp.num_planes));
> +	for (int i = 0; i < pix_mp.num_planes; i++)
> +		trace_v4l2_plane_pix_format(pix_mp_obj, pix_mp.plane_fmt[i], i);
> +
> +	json_object_object_add(pix_mp_obj, "flags", json_object_new_int(pix_mp.flags));
> +	json_object_object_add(pix_mp_obj, "flags_str", json_object_new_string(pixflags2s(pix_mp.flags).c_str()));
> +	json_object_object_add(pix_mp_obj, "ycbcr_enc", json_object_new_int(pix_mp.ycbcr_enc));
> +	json_object_object_add(pix_mp_obj, "ycbcr_enc_str", json_object_new_string(ycbcr_enc2s(pix_mp.ycbcr_enc).c_str()));
> +	json_object_object_add(pix_mp_obj, "quantization", json_object_new_int(pix_mp.quantization));
> +	json_object_object_add(pix_mp_obj, "quantization_str", json_object_new_string(quantization2s(pix_mp.quantization).c_str()));
> +	json_object_object_add(pix_mp_obj, "xfer_func", json_object_new_int(pix_mp.xfer_func));
> +	json_object_object_add(pix_mp_obj, "xfer_func_str", json_object_new_string(xfer_func2s(pix_mp.xfer_func).c_str()));
> +
> +	json_object_object_add(format_obj, "v4l2_pix_format_mplane", pix_mp_obj);
> +}
> +
> +void trace_v4l2_format(void *arg, json_object *ioctl_args)
> +{
> +	json_object *format_obj = json_object_new_object();
> +	struct v4l2_format *format = static_cast<struct v4l2_format*>(arg);
> +
> +	json_object_object_add(format_obj, "type", json_object_new_uint64(format->type));
> +	json_object_object_add(format_obj, "type_str", json_object_new_string(buftype2s(format->type).c_str()));
> +
> +	switch (format->type) {
> +	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
> +	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
> +	case V4L2_BUF_TYPE_VBI_CAPTURE:
> +	case V4L2_BUF_TYPE_VBI_OUTPUT:
> +	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
> +	case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
> +		break;
> +	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> +		trace_v4l2_pix_format_mplane(format_obj, format->fmt.pix_mp);
> +		break;
> +	case V4L2_BUF_TYPE_SDR_CAPTURE:
> +	case V4L2_BUF_TYPE_SDR_OUTPUT:
> +	case V4L2_BUF_TYPE_META_CAPTURE:
> +	case V4L2_BUF_TYPE_META_OUTPUT:
> +		break;
> +	}
> +	json_object_object_add(ioctl_args, "v4l2_format", format_obj);
> +}
> +
> +void trace_v4l2_requestbuffers(void *arg, json_object *ioctl_args)
> +{
> +	json_object *request_buffers_obj = json_object_new_object();
> +	struct v4l2_requestbuffers *request_buffers = static_cast<struct v4l2_requestbuffers*>(arg);
> +
> +	json_object_object_add(request_buffers_obj, "count", json_object_new_uint64(request_buffers->count));
> +	json_object_object_add(request_buffers_obj, "type", json_object_new_uint64(request_buffers->type));
> +	json_object_object_add(request_buffers_obj, "type_str",
> +	                       json_object_new_string(buftype2s(request_buffers->type).c_str()));
> +	json_object_object_add(request_buffers_obj, "memory", json_object_new_uint64(request_buffers->memory));
> +	json_object_object_add(request_buffers_obj, "memory_str",
> +	                       json_object_new_string(v4l2_memory2s(request_buffers->memory).c_str()));
> +	json_object_object_add(request_buffers_obj, "capabilities", json_object_new_uint64(request_buffers->capabilities));
> +	json_object_object_add(request_buffers_obj, "capabilities_str",
> +	                       json_object_new_string(bufcap2s(request_buffers->capabilities).c_str()));
> +	json_object_object_add(request_buffers_obj, "flags", json_object_new_int(request_buffers->flags));
> +	json_object_object_add(request_buffers_obj, "flags_str",
> +						   json_object_new_string(request_buffers_flag2s(request_buffers->flags).c_str()));
> +
> +	json_object_object_add(ioctl_args, "v4l2_requestbuffers", request_buffers_obj);
> +}
> +
> +json_object *trace_v4l2_plane(struct v4l2_plane *p, __u32 memory)
> +{
> +	json_object *plane_obj = json_object_new_object();
> +
> +	json_object_object_add(plane_obj, "bytesused", json_object_new_int64(p->bytesused));
> +	json_object_object_add(plane_obj, "length", json_object_new_uint64(p->length));
> +
> +	json_object *m_obj = json_object_new_object();
> +	if (memory == V4L2_MEMORY_MMAP)
> +		json_object_object_add(m_obj, "mem_offset", json_object_new_int64(p->m.mem_offset));
> +	json_object_object_add(plane_obj, "m", m_obj);
> +
> +	json_object_object_add(plane_obj, "data_offset", json_object_new_int64(p->data_offset));
> +
> +	return plane_obj;
> +}
> +
> +void trace_v4l2_buffer(void *arg, json_object *ioctl_args)
> +{
> +	json_object *buf_obj = json_object_new_object();
> +	struct v4l2_buffer *buf = static_cast<struct v4l2_buffer*>(arg);
> +
> +	json_object_object_add(buf_obj, "index", json_object_new_uint64(buf->index));
> +	json_object_object_add(buf_obj, "type", json_object_new_uint64(buf->type));
> +	json_object_object_add(buf_obj, "type_str",
> +	                       json_object_new_string(buftype2s(buf->type).c_str()));
> +	json_object_object_add(buf_obj, "bytesused", json_object_new_uint64(buf->bytesused));
> +	json_object_object_add(buf_obj, "flags", json_object_new_uint64(buf->flags));
> +	json_object_object_add(buf_obj, "flags_str",
> +	                       json_object_new_string(bufferflags2s(buf->flags).c_str()));
> +	json_object_object_add(buf_obj, "field", json_object_new_uint64(buf->field));
> +
> +	json_object *timestamp_obj = json_object_new_object();
> +	json_object_object_add(timestamp_obj, "tv_sec", json_object_new_int64(buf->timestamp.tv_sec));
> +	json_object_object_add(timestamp_obj, "tv_usec", json_object_new_int64(buf->timestamp.tv_usec));
> +	json_object_object_add(buf_obj, "timestamp", timestamp_obj);
> +	json_object_object_add(buf_obj, "sequence", json_object_new_uint64(buf->sequence));
> +	json_object_object_add(buf_obj, "memory", json_object_new_uint64(buf->memory));
> +	json_object_object_add(buf_obj, "memory_str",
> +	                       json_object_new_string(v4l2_memory2s(buf->memory).c_str()));
> +
> +	json_object *m_obj = json_object_new_object();
> +	if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
> +	    buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
> +
> +		json_object *planes_obj = json_object_new_array();
> +		/* TODO tracer only works for decoded formats with one plane e.g. V4L2_PIX_FMT_NV12 */
> +		json_object_array_add(planes_obj, trace_v4l2_plane(buf->m.planes, buf->memory));
> +		json_object_object_add(m_obj, "planes", planes_obj);
> +	}
> +	json_object_object_add(buf_obj, "m", m_obj);
> +	json_object_object_add(buf_obj, "length", json_object_new_uint64(buf->length));
> +
> +	/* For memory-to-memory devices you can use requests only for output buffers. */
> +	if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +		json_object_object_add(buf_obj, "request_fd", json_object_new_int(buf->request_fd));
> +
> +	json_object_object_add(ioctl_args, "v4l2_buffer", buf_obj);
> +}
> +
> +void trace_v4l2_exportbuffer(void *arg, json_object *ioctl_args)
> +{
> +	json_object *exportbuffer_obj = json_object_new_object();
> +	struct v4l2_exportbuffer *export_buffer = static_cast<struct v4l2_exportbuffer*>(arg);
> +
> +	json_object_object_add(exportbuffer_obj, "type", json_object_new_uint64(export_buffer->type));
> +	json_object_object_add(exportbuffer_obj, "type_str",
> +	                       json_object_new_string(buftype2s(export_buffer->type).c_str()));
> +
> +	json_object_object_add(exportbuffer_obj, "index", json_object_new_uint64(export_buffer->index));
> +	json_object_object_add(exportbuffer_obj, "plane", json_object_new_uint64(export_buffer->plane));
> +	json_object_object_add(exportbuffer_obj, "flags", json_object_new_uint64(export_buffer->flags));
> +	json_object_object_add(exportbuffer_obj, "fd", json_object_new_int(export_buffer->fd));
> +
> +	json_object_object_add(ioctl_args, "v4l2_exportbuffer", exportbuffer_obj);
> +}
> +
> +void trace_vidioc_stream(void *arg, json_object *ioctl_args)
> +{
> +	v4l2_buf_type buf_type = *(static_cast<v4l2_buf_type*>(arg));
> +	json_object_object_add(ioctl_args, "buf_type", json_object_new_int(buf_type));
> +	json_object_object_add(ioctl_args, "buf_type_str", json_object_new_string(buftype2s(buf_type).c_str()));
> +}
> +
> +void trace_v4l2_ext_control(json_object *ext_controls_obj, struct v4l2_ext_control ctrl, __u32 control_idx)
> +{
> +	std::string unique_key_for_control;
> +
> +	json_object *ctrl_obj = json_object_new_object();
> +	json_object_object_add(ctrl_obj, "id", json_object_new_uint64(ctrl.id));
> +	json_object_object_add(ctrl_obj, "control_class_str", json_object_new_string(ctrlclass2s(ctrl.id).c_str()));
> +	json_object_object_add(ctrl_obj, "size", json_object_new_uint64(ctrl.size));
> +
> +	if ((ctrl.id & V4L2_CID_CODEC_STATELESS_BASE) == V4L2_CID_CODEC_STATELESS_BASE) {
> +		switch (ctrl.id) {
> +		case V4L2_CID_STATELESS_VP8_FRAME:
> +			trace_v4l2_ctrl_vp8_frame(ctrl.p_vp8_frame, ctrl_obj);
> +			break;
> +		default:
> +			break;
> +		}
> +	}
> +
> +	unique_key_for_control = "v4l2_ext_control_";
> +	unique_key_for_control += std::to_string(control_idx);
> +
> +	json_object_object_add(ext_controls_obj, unique_key_for_control.c_str(), ctrl_obj);
> +}
> +
> +void trace_v4l2_ext_controls(void *arg, json_object *ioctl_args)
> +{
> +	json_object *ext_controls_obj = json_object_new_object();
> +	struct v4l2_ext_controls *ext_controls = static_cast<struct v4l2_ext_controls*>(arg);
> +
> +	json_object_object_add(ext_controls_obj, "which", json_object_new_int64(ext_controls->which));
> +	json_object_object_add(ext_controls_obj, "which_str", json_object_new_string(which2s(ext_controls->which).c_str()));
> +	json_object_object_add(ext_controls_obj, "count", json_object_new_uint64(ext_controls->count));
> +
> +	/* error_idx is defined only if the ioctl returned an error  */
> +	if (errno)
> +		json_object_object_add(ext_controls_obj, "error_idx", json_object_new_uint64(ext_controls->error_idx));
> +
> +	/* request_fd is only valid when "which" == V4L2_CTRL_WHICH_REQUEST_VAL */
> +	if (ext_controls->which == V4L2_CTRL_WHICH_REQUEST_VAL)
> +		json_object_object_add(ext_controls_obj, "request_fd", json_object_new_int(ext_controls->request_fd));
> +
> +	for (__u32 i = 0; i < ext_controls->count; i++)
> +		trace_v4l2_ext_control(ext_controls_obj, ext_controls->controls[i], i);
> +
> +	json_object_object_add(ioctl_args, "v4l2_ext_controls", ext_controls_obj);
> +}
> +
> +void trace_vidioc_query_ext_ctrl(void *arg, json_object *ioctl_args)
> +{
> +	json_object *query_ext_ctrl_obj = json_object_new_object();
> +	struct v4l2_query_ext_ctrl *queryextctrl = static_cast<struct v4l2_query_ext_ctrl*>(arg);
> +
> +	json_object_object_add(query_ext_ctrl_obj, "id", json_object_new_uint64(queryextctrl->id));
> +	json_object_object_add(query_ext_ctrl_obj, "control_class_str", json_object_new_string(ctrlclass2s(queryextctrl->id).c_str()));
> +	json_object_object_add(query_ext_ctrl_obj, "type", json_object_new_uint64(queryextctrl->type));
> +	json_object_object_add(query_ext_ctrl_obj, "type_str", json_object_new_string(ctrltype2s(queryextctrl->type).c_str()));
> +	json_object_object_add(query_ext_ctrl_obj, "name", json_object_new_string(queryextctrl->name));
> +	json_object_object_add(query_ext_ctrl_obj, "minimum", json_object_new_int64(queryextctrl->minimum));
> +	json_object_object_add(query_ext_ctrl_obj, "maximum", json_object_new_int64(queryextctrl->maximum));
> +	json_object_object_add(query_ext_ctrl_obj, "step", json_object_new_uint64(queryextctrl->step));
> +	json_object_object_add(query_ext_ctrl_obj, "default_value", json_object_new_int64(queryextctrl->default_value));
> +	json_object_object_add(query_ext_ctrl_obj, "flags", json_object_new_uint64(queryextctrl->flags));
> +	json_object_object_add(query_ext_ctrl_obj, "flags_str", json_object_new_string(ctrlflags2s(queryextctrl->flags).c_str()));
> +	json_object_object_add(query_ext_ctrl_obj, "elem_size", json_object_new_uint64(queryextctrl->elem_size));
> +	json_object_object_add(query_ext_ctrl_obj, "elems", json_object_new_uint64(queryextctrl->elems));
> +	json_object_object_add(query_ext_ctrl_obj, "nr_of_dims", json_object_new_uint64(queryextctrl->nr_of_dims));
> +
> +	/* 	__u32   dims[V4L2_CTRL_MAX_DIMS] */
> +	json_object *dim_obj = json_object_new_array();
> +
> +	for (unsigned int i = 0; i < queryextctrl->nr_of_dims; i++)
> +		json_object_array_add(dim_obj, json_object_new_uint64(queryextctrl->dims[i]));
> +
> +	json_object_object_add(query_ext_ctrl_obj, "dims", dim_obj);
> +	json_object_object_add(ioctl_args, "v4l2_query_ext_ctrl", query_ext_ctrl_obj);
> +}
> +
> +void trace_ioctl_media(unsigned long request, void *arg, json_object *ioctl_args)
> +{
> +	if (request ==  MEDIA_IOC_REQUEST_ALLOC) {
> +		__s32 *request_fd = static_cast<__s32*>(arg);
> +		json_object_object_add(ioctl_args, "request_fd", json_object_new_int(*request_fd));
> +	}
> +}
> +
> +void trace_ioctl_video(unsigned long int request, void *arg, json_object *ioctl_args, bool from_userspace)
> +{
> +	switch (request) {
> +	case VIDIOC_QUERYCAP:
> +		if (!from_userspace)
> +			trace_vidioc_querycap(arg, ioctl_args);
> +		break;
> +	case VIDIOC_ENUM_FMT:
> +		trace_vidioc_enum_fmt(arg, ioctl_args);
> +		break;
> +	case VIDIOC_G_FMT:
> +	case VIDIOC_S_FMT:
> +		trace_v4l2_format(arg, ioctl_args);
> +		break;
> +	case VIDIOC_REQBUFS:
> +		trace_v4l2_requestbuffers(arg, ioctl_args);
> +		break;
> +	case VIDIOC_QUERYBUF:
> +	case VIDIOC_QBUF:
> +	case VIDIOC_DQBUF:
> +		trace_v4l2_buffer(arg, ioctl_args);
> +		break;
> +	case VIDIOC_EXPBUF:
> +		trace_v4l2_exportbuffer(arg, ioctl_args);
> +		break;
> +	case VIDIOC_STREAMON:
> +	case VIDIOC_STREAMOFF:
> +		if (from_userspace)
> +			trace_vidioc_stream(arg, ioctl_args);
> +		break;
> +	case VIDIOC_G_EXT_CTRLS:
> +	case VIDIOC_S_EXT_CTRLS:
> +		trace_v4l2_ext_controls(arg, ioctl_args);
> +		break;
> +	case VIDIOC_QUERY_EXT_CTRL:
> +		trace_vidioc_query_ext_ctrl(arg, ioctl_args);
> +		break;
> +	default:
> +		break;
> +	}
> +}
> +
> +void trace_dma_buf_ioctl_sync(void *arg, json_object *ioctl_args)
> +{
> +	struct dma_buf_sync *sync = static_cast<struct dma_buf_sync*>(arg);
> +	json_object *sync_obj = json_object_new_object();
> +	json_object_object_add(sync_obj, "flags", json_object_new_uint64(sync->flags));
> +	json_object_object_add(sync_obj, "flags_str", json_object_new_string(bufsyncflag2s(sync->flags).c_str()));
> +	json_object_object_add(ioctl_args, "dma_buf_sync", sync_obj);
> +}
> +
> +std::string get_ioctl_request_str(unsigned long request)
> +{
> +	__u8 ioctl_type = _IOC_TYPE(request);
> +	switch (ioctl_type) {
> +		case 'V':
> +			return ioctl2s_video(request);
> +		case '|':
> +			return ioctl2s_media(request);
> +		case 'b':
> +			if (request == DMA_BUF_IOCTL_SYNC)
> +				return "DMA_BUF_IOCTL_SYNC";
> +			break;
> +		default:
> +			break;
> +	}
> +	return "unknown ioctl";
> +}
> +
> +json_object *trace_ioctl_args(int fd, unsigned long request, void *arg, bool from_userspace)
> +{
> +	json_object *ioctl_args = json_object_new_object();
> +	__u8 ioctl_type = _IOC_TYPE(request);
> +	switch (ioctl_type) {
> +		case 'V':
> +			trace_ioctl_video(request, arg, ioctl_args, from_userspace);
> +			break;
> +		case '|':
> +			trace_ioctl_media(request, arg, ioctl_args);
> +			break;
> +		case 'b':
> +			if (request == DMA_BUF_IOCTL_SYNC && from_userspace) {
> +				trace_dma_buf_ioctl_sync(arg, ioctl_args);
> +			}
> +			break;
> +		default:
> +			break;
> +	}
> +
> +	return ioctl_args;
> +}
> diff --git a/utils/tracer/tracer.cpp b/utils/tracer/tracer.cpp
> new file mode 100644
> index 00000000..1b84f85d
> --- /dev/null
> +++ b/utils/tracer/tracer.cpp
> @@ -0,0 +1,91 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright 2022 Collabora Ltd.
> + */
> +
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <getopt.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <json.h>
> +#include <time.h>
> +#include <cstring>
> +#include <string>
> +
> +int main(int argc, char *argv[])
> +{
> +	int ch;
> +	char short_options[] = {'e', 'p', 'r', 'y'};
> +
> +	if (argc <= 1) {
> +		fprintf(stderr, "usage: tracer [-e] [-p] [-r] [-y] <tracee>\n");
> +		return -1;
> +	}
> +
> +	do {
> +		ch = getopt(argc, argv, short_options);
> +		switch (ch){
> +		case 'e':
> +			setenv("TRACE_OPTION_PRETTY_PRINT_MEM", "true", 0);
> +			break;
> +		case 'p':
> +			setenv("TRACE_OPTION_PRETTY_PRINT_ALL", "true", 0);
> +			break;
> +		case 'r':
> +			setenv("TRACE_OPTION_DECODED_TO_JSON", "true", 0);
> +			break;
> +		case 'y':
> +			setenv("TRACE_OPTION_CREATE_YUV_FILE", "true", 0);
> +			break;
> +		}
> +	} while (ch != -1);
> +
> +	/* Get the tracee from the command line. */
> +	int count = 0;
> +	char *exec_array[argc];
> +	while (optind < argc)
> +		exec_array[count++] = argv[optind++];
> +	exec_array[count] = nullptr;
> +
> +	/* Use a substring of the time to create a unique id for the trace. */
> +	std::string trace_id = std::to_string(time(nullptr));
> +	trace_id = trace_id.substr(5, trace_id.npos) + "_trace";
> +
> +	/* Create the trace file to hold the json-objects as a large json array. */
> +	std::string trace_filename = trace_id + ".json";
> +	FILE *trace_file = fopen(trace_filename.c_str(), "w");
> +	fputs("[\n", trace_file);
> +	fflush(trace_file);
> +
> +	setenv("TRACE_ID", trace_id.c_str(), 0);
> +	setenv("LD_PRELOAD", ".libs/libtracer.so", 0);

While trying out the tracer, I stumbled across this being hardcoded. It is
rather inconvenient since you must be in the build tree to use it. I wonder how
we could fix this, or even hide the re-load library ? Have you checked if you
could preload using the ld library C interface ?

> +
> +	if (fork() == 0) {
> +		execvpe(exec_array[0], exec_array, environ);
> +		perror("Could not execute tracee");
> +		return -1;
> +	}
> +
> +	int tracee_result;
> +	wait(&tracee_result);
> +	unsetenv("TRACE_ID");
> +	unsetenv("LD_PRELOAD");
> +
> +	if (WEXITSTATUS(tracee_result)) {
> +		fprintf(stderr, "Trace error: %s\n", trace_filename.c_str());
> +		exit(EXIT_FAILURE);
> +	}
> +
> +	/* Close the json-array and the trace file. */
> +	trace_file = fopen(trace_filename.c_str(), "r+");
> +	fseek(trace_file, 0L, SEEK_END);
> +	fseek(trace_file, (ftell(trace_file) - 2), SEEK_SET);
> +	std::string end = "\n]\n";
> +	fwrite(end.c_str(), sizeof(char), end.length(), trace_file);
> +	fclose(trace_file);
> +
> +	fprintf(stderr, "Trace complete: %s\n", trace_filename.c_str());
> +	return 0;
> +}


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2022-08-26 19:18 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-20  0:50 [RFC 0/2] v4l2 stateless tracer/retracer utilities Deborah Brouwer
2022-08-20  0:50 ` [RFC 1/2] utils: add stateless tracer utility Deborah Brouwer
2022-08-26 19:18   ` Nicolas Dufresne
2022-08-20  0:50 ` [RFC 2/2] utils: add stateless retracer utility Deborah Brouwer
2022-08-26 19:05 ` [RFC 0/2] v4l2 stateless tracer/retracer utilities Nicolas Dufresne

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).