v4l2_stateful_decoder: Initial commit

CLI that decodes a h264 or ivf bitstream
and writes the raw frames out or displays
them on the screen.

BUG=b:179075377
TEST=None

Change-Id: I39e7f673713d86428ac52b7b506cb57cc476eca3
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/drm-tests/+/2680066
Reviewed-by: Fritz Koenig <frkoenig@chromium.org>
Reviewed-by: Miguel Casas <mcasas@chromium.org>
Reviewed-by: Steve Cho <stevecho@chromium.org>
Tested-by: Fritz Koenig <frkoenig@chromium.org>
Commit-Queue: Steve Cho <stevecho@chromium.org>
diff --git a/Makefile b/Makefile
index 3fee3f7..f2c9ebd 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,7 @@
 	CC_BINARY(synctest) \
 	CC_BINARY(udmabuf_create_test) \
 	CC_BINARY(dmabuf_test) \
+	CC_BINARY(v4l2_stateful_decoder) \
 	CC_BINARY(v4l2_stateful_encoder) \
 
 
@@ -77,4 +78,7 @@
 CC_BINARY(vk_glow): LDLIBS += -lm -lvulkan $(DRM_LIBS)
 endif
 
+CC_BINARY(v4l2_stateful_decoder): v4l2_stateful_decoder.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(v4l2_stateful_decoder): LDLIBS += $(DRM_LIBS)
+
 CC_BINARY(v4l2_stateful_encoder): v4l2_stateful_encoder.o
diff --git a/v4l2_stateful_decoder.c b/v4l2_stateful_decoder.c
new file mode 100644
index 0000000..aee3e16
--- /dev/null
+++ b/v4l2_stateful_decoder.c
@@ -0,0 +1,675 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// As per https://www.kernel.org/doc/html/v5.4/media/uapi/v4l/dev-decoder.html
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <linux/videodev2.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "bs_drm.h"
+
+static const char *kDecodeDevice = "/dev/video-dec0";
+static const int kInputbufferMaxSize = 4 * 1024 * 1024;
+static const int kRequestBufferCount = 8;
+static const uint32_t kIVFHeaderSignature = v4l2_fourcc('D', 'K', 'I', 'F');
+
+struct mmap_buffers {
+	void *start[VIDEO_MAX_PLANES];
+	size_t length[VIDEO_MAX_PLANES];
+	struct gbm_bo *bo;
+};
+
+struct queue {
+	int v4lfd;
+	enum v4l2_buf_type type;
+	uint32_t fourcc;
+	struct mmap_buffers *buffers;
+	uint32_t image_width;
+	uint32_t image_height;
+	uint32_t cnt;
+	uint32_t num_planes;
+	uint32_t memory;
+};
+
+struct ivf_file_header {
+	uint32_t signature;
+	uint16_t version;
+	uint16_t header_length;
+	uint32_t fourcc;
+	uint16_t width;
+	uint16_t height;
+	uint32_t denominator;
+	uint32_t numerator;
+	uint32_t frame_cnt;
+	uint32_t unused;
+} __attribute__((packed));
+
+struct ivf_frame_header {
+	uint32_t size;
+	uint64_t timestamp;
+} __attribute__((packed));
+
+struct compressed_file {
+	FILE *fp;
+	struct ivf_file_header header;
+	uint32_t submitted_frames;
+};
+
+void print_fourcc(uint32_t fourcc)
+{
+	printf("%c%c%c%c\n", fourcc & 0xff, fourcc >> 8 & 0xff, fourcc >> 16 & 0xff,
+	       fourcc >> 24 & 0xff);
+}
+
+struct compressed_file open_file(const char *file_name)
+{
+	struct compressed_file file = { 0 };
+
+	FILE *fp = fopen(file_name, "rb");
+	if (fp) {
+		if (fread(&file.header, sizeof(struct ivf_file_header), 1, fp) != 1) {
+			fclose(fp);
+			fprintf(stderr, "unable to read ivf file header\n");
+		}
+
+		if (file.header.signature != kIVFHeaderSignature) {
+			fclose(fp);
+			fprintf(stderr, "Incorrect header signature : 0x%0x != 0x%0x\n",
+				file.header.signature, kIVFHeaderSignature);
+		}
+
+		file.fp = fp;
+		print_fourcc(file.header.fourcc);
+		printf("ivf file header: %d x %d\n", file.header.width, file.header.height);
+	} else {
+		fprintf(stderr, "unable to open file: %s\n", file_name);
+	}
+
+	return file;
+}
+
+int query_format(int v4lfd, enum v4l2_buf_type type, uint32_t fourcc)
+{
+	struct v4l2_fmtdesc fmtdesc;
+	memset(&fmtdesc, 0, sizeof(fmtdesc));
+
+	fmtdesc.type = type;
+	while (ioctl(v4lfd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
+		if (fourcc == 0)
+			print_fourcc(fmtdesc.pixelformat);
+		else if (fourcc == fmtdesc.pixelformat)
+			return 1;
+		fmtdesc.index++;
+	}
+
+	return 0;
+}
+
+int capabilities(int v4lfd, uint32_t compressed_format, uint32_t uncompressed_format)
+{
+	struct v4l2_capability cap;
+	memset(&cap, 0, sizeof(cap));
+	int ret = ioctl(v4lfd, VIDIOC_QUERYCAP, &cap);
+	if (ret != 0)
+		perror("VIDIOC_QUERYCAP failed");
+
+	printf("driver=\"%s\" bus_info=\"%s\" card=\"%s\" fd=0x%x\n", cap.driver, cap.bus_info,
+	       cap.card, v4lfd);
+
+	if (!query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, compressed_format)) {
+		printf("Supported compressed formats:\n");
+		query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, 0);
+		ret = 1;
+	}
+
+	if (!query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, uncompressed_format)) {
+		printf("Supported uncompressed formats:\n");
+		query_format(v4lfd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, 0);
+		ret = 1;
+	}
+
+	return ret;
+}
+
+int request_mmap_buffers(struct queue *queue, struct v4l2_requestbuffers *reqbuf)
+{
+	const int v4lfd = queue->v4lfd;
+	const uint32_t buffer_alloc = reqbuf->count * sizeof(struct mmap_buffers);
+	struct mmap_buffers *buffers = (struct mmap_buffers *)malloc(buffer_alloc);
+	memset(buffers, 0, buffer_alloc);
+	queue->buffers = buffers;
+	queue->cnt = reqbuf->count;
+
+	int ret;
+	for (uint32_t i = 0; i < reqbuf->count; i++) {
+		struct v4l2_buffer buffer;
+		struct v4l2_plane planes[VIDEO_MAX_PLANES];
+		memset(&buffer, 0, sizeof(buffer));
+		buffer.type = reqbuf->type;
+		buffer.memory = queue->memory;
+		buffer.index = i;
+		buffer.length = queue->num_planes;
+		buffer.m.planes = planes;
+		ret = ioctl(v4lfd, VIDIOC_QUERYBUF, &buffer);
+		if (ret != 0) {
+			printf("VIDIOC_QUERYBUF failed: %d\n", ret);
+			break;
+		}
+
+		for (uint32_t j = 0; j < queue->num_planes; j++) {
+			buffers[i].length[j] = buffer.m.planes[j].length;
+			buffers[i].start[j] =
+			    mmap(NULL, buffer.m.planes[j].length, PROT_READ | PROT_WRITE,
+				 MAP_SHARED, v4lfd, buffer.m.planes[j].m.mem_offset);
+			if (MAP_FAILED == buffers[i].start[j]) {
+				fprintf(stderr,
+					"failed to mmap buffer of length(%d) and offset(0x%x)\n",
+					buffer.m.planes[j].length, buffer.m.planes[j].m.mem_offset);
+			}
+		}
+	}
+
+	return ret;
+}
+
+// this is the input queue that will take compressed data
+// 4.5.1.5
+int setup_OUTPUT(struct queue *OUTPUT_queue)
+{
+	int ret = 0;
+
+	// 1. Set the coded format on OUTPUT via VIDIOC_S_FMT()
+	if (!ret) {
+		struct v4l2_format fmt;
+		memset(&fmt, 0, sizeof(fmt));
+
+		fmt.type = OUTPUT_queue->type;
+		fmt.fmt.pix_mp.pixelformat = OUTPUT_queue->fourcc;
+		fmt.fmt.pix_mp.plane_fmt[0].sizeimage = kInputbufferMaxSize;
+		fmt.fmt.pix_mp.num_planes = 1;
+
+		int ret = ioctl(OUTPUT_queue->v4lfd, VIDIOC_S_FMT, &fmt);
+		if (ret != 0)
+			perror("VIDIOC_S_FMT failed");
+	}
+
+	// 2. Allocate source (bytestream) buffers via VIDIOC_REQBUFS() on OUTPUT.
+	if (!ret) {
+		struct v4l2_requestbuffers reqbuf;
+		memset(&reqbuf, 0, sizeof(reqbuf));
+		reqbuf.count = kRequestBufferCount;
+		reqbuf.type = OUTPUT_queue->type;
+		reqbuf.memory = OUTPUT_queue->memory;
+
+		ret = ioctl(OUTPUT_queue->v4lfd, VIDIOC_REQBUFS, &reqbuf);
+		if (ret != 0)
+			perror("VIDIOC_REQBUFS failed");
+
+		printf("%d buffers requested, %d buffers for compressed data returned\n",
+		       kRequestBufferCount, reqbuf.count);
+
+		ret = request_mmap_buffers(OUTPUT_queue, &reqbuf);
+	}
+
+	// 3. Start streaming on the OUTPUT queue via VIDIOC_STREAMON().
+	if (!ret) {
+		ret = ioctl(OUTPUT_queue->v4lfd, VIDIOC_STREAMON, &OUTPUT_queue->type);
+		if (ret != 0)
+			perror("VIDIOC_STREAMON failed");
+	}
+
+	return ret;
+}
+
+int submit_compressed_frame(struct compressed_file *file, struct queue *OUTPUT_queue,
+			    uint32_t index)
+{
+	const uint32_t num = file->header.numerator;
+	const uint32_t den = file->header.denominator;
+
+	struct ivf_frame_header frame_header = { 0 };
+	if (fread(&frame_header, sizeof(struct ivf_frame_header), 1, file->fp) != 1) {
+		if (!feof(file->fp))
+			fprintf(stderr, "unable to read ivf frame header\n");
+		return -1;
+	}
+
+	struct mmap_buffers *buffers = OUTPUT_queue->buffers;
+	if (fread(buffers[index].start[0], sizeof(uint8_t), frame_header.size, file->fp) !=
+	    frame_header.size) {
+		fprintf(stderr, "unable to read ivf frame data\n");
+		return -1;
+	}
+
+	struct v4l2_buffer v4l2_buffer;
+	struct v4l2_plane planes[VIDEO_MAX_PLANES];
+	memset(&v4l2_buffer, 0, sizeof(v4l2_buffer));
+	v4l2_buffer.index = index;
+	v4l2_buffer.type = OUTPUT_queue->type;
+	v4l2_buffer.memory = OUTPUT_queue->memory;
+	v4l2_buffer.length = 1;
+	v4l2_buffer.timestamp.tv_sec = 0;
+	v4l2_buffer.timestamp.tv_usec = ((frame_header.timestamp * den) / num) * 100;
+	v4l2_buffer.m.planes = planes;
+	v4l2_buffer.m.planes[0].length = buffers[index].length[0];
+	v4l2_buffer.m.planes[0].bytesused = frame_header.size;
+	v4l2_buffer.m.planes[0].data_offset = 0;
+	int ret = ioctl(OUTPUT_queue->v4lfd, VIDIOC_QBUF, &v4l2_buffer);
+	if (ret != 0) {
+		perror("VIDIOC_QBUF failed");
+		return -1;
+	}
+
+	file->submitted_frames++;
+
+	return 0;
+}
+
+int prime_OUTPUT(struct compressed_file *file, struct queue *OUTPUT_queue)
+{
+	int ret = 0;
+
+	for (uint32_t i = 0; i < OUTPUT_queue->cnt; ++i) {
+		ret = submit_compressed_frame(file, OUTPUT_queue, i);
+		if (ret)
+			break;
+	}
+	return ret;
+}
+
+void cleanup_queue(struct queue *queue)
+{
+	if (queue->cnt) {
+		struct mmap_buffers *buffers = queue->buffers;
+
+		for (uint32_t i = 0; i < queue->cnt; i++)
+			for (uint32_t j = 0; j < queue->num_planes; j++) {
+				if (buffers[i].length[j])
+					munmap(buffers[i].start[j], buffers[i].length[j]);
+				if (buffers[i].bo)
+					gbm_bo_destroy(buffers[i].bo);
+			}
+
+		free(queue->buffers);
+		queue->cnt = 0;
+	}
+}
+
+int queue_buffer_CAPTURE(struct queue *queue, uint32_t index)
+{
+	struct v4l2_buffer v4l2_buffer;
+	struct v4l2_plane planes[VIDEO_MAX_PLANES];
+	memset(&v4l2_buffer, 0, sizeof v4l2_buffer);
+	memset(&planes, 0, sizeof planes);
+
+	v4l2_buffer.type = queue->type;
+	v4l2_buffer.memory = queue->memory;
+	v4l2_buffer.index = index;
+	v4l2_buffer.m.planes = planes;
+	v4l2_buffer.length = queue->num_planes;
+
+	struct gbm_bo *bo = queue->buffers[index].bo;
+	for (uint32_t i = 0; i < queue->num_planes; ++i) {
+		if (queue->memory == V4L2_MEMORY_DMABUF) {
+			v4l2_buffer.m.planes[i].m.fd = gbm_bo_get_plane_fd(bo, i);
+		} else if (queue->memory == V4L2_MEMORY_MMAP) {
+			struct mmap_buffers *buffers = queue->buffers;
+
+			v4l2_buffer.m.planes[i].length = buffers[index].length[i];
+			v4l2_buffer.m.planes[i].bytesused = buffers[index].length[i];
+			v4l2_buffer.m.planes[i].data_offset = 0;
+		}
+	}
+
+	int ret = ioctl(queue->v4lfd, VIDIOC_QBUF, &v4l2_buffer);
+	if (ret != 0) {
+		perror("VIDIOC_QBUF failed");
+	}
+
+	return ret;
+}
+
+// this is the output queue that will produce uncompressed frames
+// 4.5.1.6
+int setup_CAPTURE(struct gbm_device *gbm, struct queue *CAPTURE_queue, uint64_t modifier)
+{
+	int ret = 0;
+
+	// 1. Call VIDIOC_G_FMT() on the CAPTURE queue to get format for the
+	//    destination buffers parsed/decoded from the bytestream.
+	if (!ret) {
+		struct v4l2_format fmt;
+		memset(&fmt, 0, sizeof(fmt));
+		fmt.type = CAPTURE_queue->type;
+
+		int ret = ioctl(CAPTURE_queue->v4lfd, VIDIOC_G_FMT, &fmt);
+		if (ret != 0)
+			perror("VIDIOC_G_FMT failed");
+
+		CAPTURE_queue->image_width = fmt.fmt.pix_mp.width;
+		CAPTURE_queue->image_height = fmt.fmt.pix_mp.height;
+		CAPTURE_queue->num_planes = fmt.fmt.pix_mp.num_planes;
+
+		printf("CAPTURE: %d x %d\n", fmt.fmt.pix_mp.width, fmt.fmt.pix_mp.height);
+	}
+
+	// 4. Optional. Set the CAPTURE format via VIDIOC_S_FMT() on the CAPTURE queue.
+	//    The client may choose a different format than selected/suggested by the decoder in
+	//    VIDIOC_G_FMT().
+	if (!ret) {
+		struct v4l2_format fmt;
+		memset(&fmt, 0, sizeof(fmt));
+		fmt.type = CAPTURE_queue->type;
+		fmt.fmt.pix_mp.pixelformat = CAPTURE_queue->fourcc;
+
+		fmt.fmt.pix_mp.width = CAPTURE_queue->image_width;
+		fmt.fmt.pix_mp.height = CAPTURE_queue->image_height;
+
+		ret = ioctl(CAPTURE_queue->v4lfd, VIDIOC_S_FMT, &fmt);
+		if (ret != 0)
+			perror("VIDIOC_S_FMT failed");
+	}
+
+	// 10. Allocate CAPTURE buffers via VIDIOC_REQBUFS() on the CAPTURE queue.
+	if (!ret) {
+		struct v4l2_requestbuffers reqbuf;
+		memset(&reqbuf, 0, sizeof(reqbuf));
+		reqbuf.count = kRequestBufferCount;
+		reqbuf.type = CAPTURE_queue->type;
+		reqbuf.memory = CAPTURE_queue->memory;
+
+		ret = ioctl(CAPTURE_queue->v4lfd, VIDIOC_REQBUFS, &reqbuf);
+		if (ret != 0)
+			perror("VIDIOC_REQBUFS failed");
+
+		printf("%d buffers requested, %d buffers for decoded data returned\n",
+		       kRequestBufferCount, reqbuf.count);
+
+		if (CAPTURE_queue->memory == V4L2_MEMORY_DMABUF) {
+			const uint32_t buffer_alloc = reqbuf.count * sizeof(struct mmap_buffers);
+			struct mmap_buffers *buffers = (struct mmap_buffers *)malloc(buffer_alloc);
+			memset(buffers, 0, buffer_alloc);
+			CAPTURE_queue->buffers = buffers;
+			CAPTURE_queue->cnt = reqbuf.count;
+
+			for (uint32_t i = 0; i < CAPTURE_queue->cnt; ++i) {
+				const uint32_t width = CAPTURE_queue->image_width;
+				const uint32_t height = CAPTURE_queue->image_height;
+
+				struct gbm_bo *bo = gbm_bo_create_with_modifiers(
+				    gbm, width, height, GBM_FORMAT_NV12, &modifier, 1);
+				CAPTURE_queue->buffers[i].bo = bo;
+
+				if (bo) {
+					ret = queue_buffer_CAPTURE(CAPTURE_queue, i);
+					if (ret != 0)
+						break;
+				} else {
+					fprintf(stderr, "could not allocate a bo %d x %d\n", width,
+						height);
+					ret = -1;
+					break;
+				}
+			}
+		} else if (CAPTURE_queue->memory == V4L2_MEMORY_MMAP) {
+			ret = request_mmap_buffers(CAPTURE_queue, &reqbuf);
+			for (uint32_t i = 0; i < reqbuf.count; i++) {
+				queue_buffer_CAPTURE(CAPTURE_queue, i);
+			}
+		} else {
+			ret = -1;
+		}
+	}
+
+	// 11. Call VIDIOC_STREAMON() on the CAPTURE queue to start decoding frames.
+	if (!ret) {
+		ret = ioctl(CAPTURE_queue->v4lfd, VIDIOC_STREAMON, &CAPTURE_queue->type);
+		if (ret != 0)
+			perror("VIDIOC_STREAMON failed");
+	}
+
+	return ret;
+}
+
+void write_file_to_disk(struct queue *CAPTURE_queue, uint32_t index, uint32_t cnt)
+{
+	char filename[256];
+	sprintf(filename, "image_%dx%d_%d.yuv", CAPTURE_queue->image_width,
+		CAPTURE_queue->image_height, cnt);
+	FILE *fp = fopen(filename, "wb");
+	if (fp) {
+		if (V4L2_MEMORY_DMABUF == CAPTURE_queue->memory) {
+			struct gbm_bo *bo = CAPTURE_queue->buffers[index].bo;
+			int bo_fd = gbm_bo_get_fd(bo);
+			size_t buffer_size = lseek(bo_fd, 0, SEEK_END);
+			lseek(bo_fd, 0, SEEK_SET);
+
+			uint8_t *buffer = mmap(0, buffer_size, PROT_READ, MAP_SHARED, bo_fd, 0);
+
+			fwrite(buffer, buffer_size, 1, fp);
+			munmap(buffer, buffer_size);
+		} else {
+			if (CAPTURE_queue->num_planes == 1) {
+				size_t buffer_size = (3 * CAPTURE_queue->image_width *
+						      CAPTURE_queue->image_height) >>
+						     1;
+				uint8_t *buffer = CAPTURE_queue->buffers[index].start[0];
+				fwrite(buffer, buffer_size, 1, fp);
+			} else {
+				for (uint32_t i = 0; i < CAPTURE_queue->num_planes; ++i) {
+					size_t buffer_size = (CAPTURE_queue->image_width *
+							      CAPTURE_queue->image_height) >>
+							     i;
+					uint8_t *buffer = CAPTURE_queue->buffers[index].start[i];
+					fwrite(buffer, buffer_size, 1, fp);
+				}
+			}
+		}
+		fclose(fp);
+	} else {
+		fprintf(stderr, "Unable to open file: %s\n", filename);
+	}
+}
+
+int dequeue_buffer(struct queue *queue, uint32_t *index)
+{
+	struct v4l2_buffer v4l2_buffer;
+	struct v4l2_plane planes[VIDEO_MAX_PLANES] = { 0 };
+	memset(&v4l2_buffer, 0, sizeof(v4l2_buffer));
+	v4l2_buffer.type = queue->type;
+	v4l2_buffer.length = queue->num_planes;
+	v4l2_buffer.m.planes = planes;
+	v4l2_buffer.m.planes[0].bytesused = 0;
+	int ret = ioctl(queue->v4lfd, VIDIOC_DQBUF, &v4l2_buffer);
+
+	*index = v4l2_buffer.index;
+	return ret;
+}
+
+int decode(struct compressed_file *file, struct queue *CAPTURE_queue,
+	   struct queue *OUTPUT_queue, uint64_t modifier, bool write_out, uint32_t frames_to_decode)
+{
+	int ret = 0;
+
+	if (!ret) {
+		uint32_t cnt = 0;
+		while (cnt < frames_to_decode) {
+			{
+				uint32_t index = 0;
+				ret = dequeue_buffer(CAPTURE_queue, &index);
+				if (ret != 0) {
+					if (errno != EAGAIN)
+						perror("VIDIOC_DQBUF failed");
+					continue;
+				}
+
+				if (write_out)
+					write_file_to_disk(CAPTURE_queue, index, cnt);
+
+				// Done with buffer, queue it back up.
+				ret = queue_buffer_CAPTURE(CAPTURE_queue, index);
+			}
+
+			// A frame was recieved on the CAPTURE queue, that means there should
+			// now be a free OUTPUT buffer.
+			{
+				uint32_t index = 0;
+				ret = dequeue_buffer(OUTPUT_queue, &index);
+				if (ret != 0) {
+					if (errno != EAGAIN)
+						perror("VIDIOC_DQBUF failed");
+					continue;
+				}
+
+				if (submit_compressed_frame(file, OUTPUT_queue, index))
+					break;
+			}
+			cnt++;
+		}
+		printf("%d frames decoded.\n", cnt);
+	}
+
+	return ret;
+}
+
+static void print_help(const char *argv0)
+{
+	printf("usage: %s [OPTIONS]\n", argv0);
+	printf("  -f, --file        ivf file to decode\n");
+	printf("  -w, --write       write out decompressed frames\n");
+	printf("  -m, --max         max number of frames to decode\n");
+	printf("  -b, --buffer      use mmap instead of dmabuf\n");
+	printf("  -o, --output_fmt  fourcc of output format\n");
+}
+
+static const struct option longopts[] = {
+	{ "file", required_argument, NULL, 'f' },
+	{ "write", no_argument, NULL, 'w' },
+	{ "max", required_argument, NULL, 'm' },
+	{ "buffer", no_argument, NULL, 'b' },
+	{ "output_fmt", no_argument, NULL, 'o' },
+	{ 0, 0, 0, 0 },
+};
+
+int main(int argc, char *argv[])
+{
+	printf("simple v4l2 decode\n");
+	int c;
+	char *file_name = NULL;
+	bool write_out = false;
+	uint32_t frames_to_decode = UINT_MAX;
+	uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
+	uint32_t uncompressed_fourcc = v4l2_fourcc('N', 'V', '1', '2');
+	uint32_t CAPTURE_memory = V4L2_MEMORY_DMABUF;
+	while ((c = getopt_long(argc, argv, "wbm:f:o:", longopts, NULL)) != -1) {
+		switch (c) {
+			case 'f':
+				file_name = strdup(optarg);
+				break;
+			case 'm':
+				frames_to_decode = atoi(optarg);
+				printf("only decoding a max of %d frames.\n", frames_to_decode);
+				break;
+			case 'w':
+				write_out = true;
+				break;
+			case 'b':
+				CAPTURE_memory = V4L2_MEMORY_MMAP;
+				break;
+			case 'o':
+				if (strlen(optarg) == 4) {
+					uncompressed_fourcc =
+					    v4l2_fourcc(toupper(optarg[0]), toupper(optarg[1]),
+							toupper(optarg[2]), toupper(optarg[3]));
+					printf("using (%s) as the CAPTURE format\n", optarg);
+					if (uncompressed_fourcc ==
+					    v4l2_fourcc('Q', '1', '2', '8')) {
+						printf("compressed format, setting modifier\n");
+						modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED;
+					}
+				}
+				break;
+			default:
+				break;
+		}
+	}
+
+	if (!file_name) {
+		print_help(argv[0]);
+		exit(1);
+	}
+
+	int drm_device_fd = bs_drm_open_main_display();
+	if (drm_device_fd < 0) {
+		fprintf(stderr, "failed to open card for display\n");
+		return 1;
+	}
+
+	struct gbm_device *gbm = gbm_create_device(drm_device_fd);
+	if (!gbm) {
+		fprintf(stderr, "failed to create gbm device\n");
+		close(drm_device_fd);
+		exit(EXIT_FAILURE);
+	}
+
+	struct compressed_file compressed_file = open_file(file_name);
+	if (!compressed_file.fp) {
+		fprintf(stderr, "Unable to open ivf file: %s\n", file_name);
+		exit(EXIT_FAILURE);
+	}
+
+	int v4lfd = open(kDecodeDevice, O_RDWR | O_NONBLOCK | O_CLOEXEC);
+	if (v4lfd < 0) {
+		fprintf(stderr, "Unable to open device file: %s\n", kDecodeDevice);
+		exit(EXIT_FAILURE);
+	}
+
+	if (capabilities(v4lfd, compressed_file.header.fourcc, uncompressed_fourcc) != 0) {
+		fprintf(stderr, "Capabilities not present for decode.\n");
+		exit(EXIT_FAILURE);
+	}
+
+	struct queue OUTPUT_queue = { .v4lfd = v4lfd,
+				      .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
+				      .fourcc = compressed_file.header.fourcc,
+				      .num_planes = 1,
+				      .memory = V4L2_MEMORY_MMAP };
+	int ret = setup_OUTPUT(&OUTPUT_queue);
+
+	if (!ret)
+		ret = prime_OUTPUT(&compressed_file, &OUTPUT_queue);
+
+	struct queue CAPTURE_queue = { .v4lfd = v4lfd,
+				       .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
+				       .fourcc = uncompressed_fourcc,
+				       .num_planes = 1,
+				       .memory = CAPTURE_memory };
+	if (!ret)
+		ret = setup_CAPTURE(gbm, &CAPTURE_queue, modifier);
+
+	if (!ret)
+		ret = decode(&compressed_file, &CAPTURE_queue, &OUTPUT_queue, modifier,
+			     write_out, frames_to_decode);
+
+	cleanup_queue(&OUTPUT_queue);
+	cleanup_queue(&CAPTURE_queue);
+	close(v4lfd);
+	fclose(compressed_file.fp);
+	close(drm_device_fd);
+	free(file_name);
+
+	return 0;
+}