#include <cstring>

#include <netdb.h>
#include <sys/types.h>

#include <linux/media.h>

#include "compiler.h"
#include "v4l2-ctl.h"
#include "v4l-stream.h"
#include <media-info.h>

extern "C" {
#include "v4l2-tpg.h"
}

static unsigned stream_count;
static unsigned stream_skip;
static __u32 memory = V4L2_MEMORY_MMAP;
static __u32 out_memory = V4L2_MEMORY_MMAP;
static int stream_sleep_count = -1;
static int stream_sleep_ms = 1000;
static unsigned stream_sleep_mode = 1;
static bool stream_no_query;
static unsigned stream_pat;
static bool stream_loop;
static bool stream_out_square;
static bool stream_out_border;
static bool stream_out_sav;
static bool stream_out_eav;
static int stream_out_pixel_aspect = -1;
static tpg_video_aspect stream_out_video_aspect;
static u8 stream_out_alpha;
static bool stream_out_alpha_red_only;
static bool stream_out_rgb_lim_range;
static unsigned stream_out_perc_fill = 100;
static v4l2_std_id stream_out_std;
static bool stream_out_refresh;
static tpg_move_mode stream_out_hor_mode = TPG_MOVE_NONE;
static tpg_move_mode stream_out_vert_mode = TPG_MOVE_NONE;
static unsigned reqbufs_count_cap = 4;
static unsigned reqbufs_count_out = 4;
static char *file_to;
static bool to_with_hdr;
static char *host_to;
#ifndef NO_STREAM_TO
static unsigned host_port_to = V4L_STREAM_PORT;
static unsigned bpl_cap[VIDEO_MAX_PLANES];
#endif
static bool host_lossless;
static int host_fd_to = -1;
static unsigned comp_perc;
static unsigned comp_perc_count;
static char *file_from;
static bool from_with_hdr;
static char *host_from;
static unsigned host_port_from = V4L_STREAM_PORT;
static int host_fd_from = -1;
static struct tpg_data tpg;
static unsigned output_field = V4L2_FIELD_NONE;
static bool output_field_alt;
static unsigned bpl_out[VIDEO_MAX_PLANES];
static bool last_buffer = false;
static codec_ctx *ctx;

static unsigned int cropped_width;
static unsigned int cropped_height;
static unsigned int composed_width;
static unsigned int composed_height;
static bool support_cap_compose;
static bool support_out_crop;
static bool in_source_change_event;

static __u64 last_fwht_bf_ts;
static fwht_cframe_hdr last_fwht_hdr;

struct request_fwht {
	int fd;
	__u64 ts;
	struct v4l2_ctrl_fwht_params params;
};

static request_fwht fwht_reqs[VIDEO_MAX_FRAME];

#define TS_WINDOW 241
#define FILE_HDR_ID			v4l2_fourcc('V', 'h', 'd', 'r')

enum codec_type {
	NOT_CODEC,
	ENCODER,
	DECODER
};

static enum codec_type codec_type;

#define QUEUE_ERROR -1
#define QUEUE_STOPPED -2
#define QUEUE_OFF_ON -3

class fps_timestamps {
private:
	unsigned idx;
	bool full;
	double first;
	double sum;
	double ts[TS_WINDOW];
	unsigned seq[TS_WINDOW];
	unsigned dropped_buffers;
	bool alternate_fields;
	unsigned field_cnt;
	unsigned last_field;

public:
	fps_timestamps()
	{
		reset();
	}

	void reset() {
		idx = 0;
		full = false;
		first = sum = 0;
		dropped_buffers = 0;
		last_field = 0;
		field_cnt = 0;
		alternate_fields = false;
	}

	void determine_field(int fd, unsigned type);
	bool add_ts(double ts_secs, unsigned sequence, unsigned field);
	bool has_fps(bool continuous);
	double fps();
	unsigned dropped();
};

static bool need_sleep(unsigned count)
{
	if (stream_sleep_count <= 0)
		return false;

	if (!(stream_sleep_mode & 1) && count > (unsigned)stream_sleep_count)
		return false;

	return !(count % stream_sleep_count);
}

static void do_sleep()
{
	int ms = stream_sleep_ms;

	if (!ms) {
		// For modes 2 and 3 just don't sleep in this case
		if (stream_sleep_mode >= 2) {
			stderr_info("streamoff, streamon\n");
			return;
		}

		stderr_info("sleeping forever...\n");
		while (1)
			sleep(100);
	}

	if (ms < 0)
		ms = ((__u64)-ms * rand()) / RAND_MAX + 1;

	if (stream_sleep_mode >= 2)
		stderr_info("streamoff, sleep %d ms, streamon\n", ms);
	else if (stream_sleep_ms < 0 || verbose)
		stderr_info("sleep %d ms\n", ms);
	else
		stderr_info("\n");
	fflush(stderr);

	struct timespec t;
	t.tv_sec = ms / 1000;
	t.tv_nsec = (ms % 1000) * 1000000;
	nanosleep(&t, NULL);
}

void fps_timestamps::determine_field(int fd, unsigned type)
{
	struct v4l2_format fmt = { };

	fmt.type = type;
	ioctl(fd, VIDIOC_G_FMT, &fmt);
	cv4l_fmt cfmt(fmt);
	alternate_fields = cfmt.g_field() == V4L2_FIELD_ALTERNATE;
}

bool fps_timestamps::add_ts(double ts_secs, unsigned sequence, unsigned field)
{
	if (ts_secs <= 0) {
		struct timespec ts_cur;

		clock_gettime(CLOCK_MONOTONIC, &ts_cur);
		ts_secs = ts_cur.tv_sec + ts_cur.tv_nsec / 1000000000.0;
	}

	if (alternate_fields &&
	    field != V4L2_FIELD_TOP && field != V4L2_FIELD_BOTTOM)
		return false;

	if (!full && idx == 0) {
		ts[idx] = ts_secs;
		seq[TS_WINDOW - 1] = sequence;
		seq[idx++] = sequence;
		first = ts_secs;
		last_field = field;
		field_cnt++;
		return true;
	}

	unsigned prev_idx = (idx + TS_WINDOW - 1) % TS_WINDOW;
	unsigned next_idx = (idx + 1) % TS_WINDOW;

	if (seq[prev_idx] == sequence) {
		if (alternate_fields) {
			if (last_field == field)
				return false;
			if (field_cnt != 1)
				return false;
			field_cnt++;
			last_field = field;
		}
	} else {
		if (alternate_fields) {
			if (field_cnt == 1) {
				dropped_buffers++;
				last_field = last_field == V4L2_FIELD_TOP ?
					V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;
			}
			field_cnt = 1;
			if (field == last_field) {
				dropped_buffers++;
				field_cnt++;
			}
			last_field = field;
		}
		if (seq[prev_idx] - sequence > 1) {
			unsigned dropped = sequence - seq[prev_idx] - 1;

			if (alternate_fields)
				dropped *= 2;
			dropped_buffers += dropped;
		}
	}

	if (!full) {
		sum += ts_secs - ts[idx - 1];
		ts[idx] = ts_secs;
		seq[idx++] = sequence;
		if (idx == TS_WINDOW) {
			full = true;
			idx = 0;
		}
		return true;
	}

	sum -= ts[next_idx] - ts[idx];
	ts[idx] = ts_secs;
	seq[idx] = sequence;
	idx = next_idx;
	sum += ts_secs - ts[prev_idx];
	return true;
}

bool fps_timestamps::has_fps(bool continuous = false)
{
	unsigned prev_idx = (idx + TS_WINDOW - 1) % TS_WINDOW;

	if (!continuous && ts[prev_idx] - first < 1.0)
		return false;
	return full || idx > 4;
}

unsigned fps_timestamps::dropped()
{
	unsigned res = dropped_buffers;

	dropped_buffers = 0;
	return res;
}

double fps_timestamps::fps()
{
	unsigned prev_idx = (idx + TS_WINDOW - 1) % TS_WINDOW;
	double cnt = seq[prev_idx] - seq[full ? idx : 0];
	double period = sum / cnt;
	double fps = 1.0 / period;

	first += static_cast<unsigned>(ts[prev_idx] - first);
	return fps;
};

void streaming_usage()
{
	printf("\nVideo Streaming options:\n"
	       "  --stream-count <count>\n"
	       "                     stream <count> buffers. The default is to keep streaming\n"
	       "                     forever. This count does not include the number of initial\n"
	       "                     skipped buffers as is passed by --stream-skip.\n"
	       "  --stream-skip <count>\n"
	       "                     skip the first <count> buffers. The default is 0.\n"
	       "  --stream-sleep count=<c>,sleep=<ms>,mode=<mode>\n"
	       "                     Sleep for <ms> milliseconds (default=1000) after <c> buffers.\n"
	       "                     If <c> is 0, then only sleep right after streaming starts.\n"
	       "                     If <ms> is 0, then sleep forever (modes 0 and 1) or not at all\n"
	       "                     (for modes 2 and 3), if <ms> is positive, then sleep for <ms>\n"
	       "                     milliseconds, if <ms> is negative, then sleep for a random\n"
	       "                     time between 1 and -<ms> milliseconds.\n"
	       "                     There are different modes for this:\n"
	       "                     <mode>=0: the sleep happens only once after <c> buffers.\n"
	       "                     <mode>=1: the sleep happens every <c> buffers (default).\n"
	       "                     <mode>=2: as 0, but call STREAMOFF/ON before/after the sleep.\n"
	       "                     <mode>=3: as 1, but call STREAMOFF/ON before/after the sleep.\n"
#ifndef NO_STREAM_TO
	       "  --stream-to <file> stream to this file. The default is to discard the\n"
	       "                     data. If <file> is '-', then the data is written to stdout\n"
	       "                     and the --silent option is turned on automatically.\n"
	       "  --stream-to-hdr <file> stream to this file. Same as --stream-to, but each\n"
	       "                     frame is prefixed by a header. Use for compressed data.\n"
	       "  --stream-to-host <hostname[:port]>\n"
               "                     stream to this host. The default port is %d.\n"
	       "  --stream-lossless  always use lossless video compression.\n"
#endif
	       "  --stream-poll      use non-blocking mode and select() to stream.\n"
	       "  --stream-buf-caps  show capture buffer capabilities\n"
	       "  --stream-show-delta-now\n"
	       "                     output the difference between the buffer timestamp and current\n"
	       "                     clock, if the buffer timestamp source is the monotonic clock.\n"
	       "                     Requires --verbose as well.\n"
	       "  --stream-mmap <count>\n"
	       "                     capture video using mmap() [VIDIOC_(D)QBUF]\n"
	       "                     count: the number of buffers to allocate. The default is 3.\n"
	       "  --stream-user <count>\n"
	       "                     capture video using user pointers [VIDIOC_(D)QBUF]\n"
	       "                     count: the number of buffers to allocate. The default is 3.\n"
	       "  --stream-dmabuf    capture video using dmabuf [VIDIOC_(D)QBUF]\n"
	       "                     Requires a corresponding --stream-out-mmap option.\n"
	       "  --stream-from <file>\n"
	       "                     stream from this file. The default is to generate a pattern.\n"
	       "                     If <file> is '-', then the data is read from stdin.\n"
	       "  --stream-from-hdr <file> stream from this file. Same as --stream-from, but each\n"
	       "                     frame is prefixed by a header. Use for compressed data.\n"
	       "  --stream-from-host <hostname[:port]>\n"
	       "                     stream from this host. The default port is %d.\n"
	       "  --stream-no-query  Do not query and set the DV timings or standard before streaming.\n"
	       "  --stream-loop      loop when the end of the file we are streaming from is reached.\n"
	       "                     The default is to stop.\n"
	       "  --stream-out-pattern <count>\n"
	       "                     choose output test pattern. The default is 0.\n"
	       "  --stream-out-square\n"
	       "                     show a square in the middle of the output test pattern.\n"
	       "  --stream-out-border\n"
	       "                     show a border around the pillar/letterboxed video.\n"
	       "  --stream-out-sav   insert an SAV code in every line.\n"
	       "  --stream-out-eav   insert an EAV code in every line.\n"
	       "  --stream-out-pixel-aspect <aspect>\n"
	       "                     select a pixel aspect ratio. The default is to autodetect.\n"
	       "                     <aspect> can be one of: square, ntsc, pal\n"
	       "  --stream-out-video-aspect <aspect>\n"
	       "                     select a video aspect ratio. The default is to use the frame ratio.\n"
	       "                     <aspect> can be one of: 4x3, 14x9, 16x9, anamorphic\n"
	       "  --stream-out-alpha <alpha-value>\n"
	       "                     value to use for the alpha component, range 0-255. The default is 0.\n"
	       "  --stream-out-alpha-red-only\n"
	       "                     only use the --stream-out-alpha value for the red colors,\n"
	       "                     for all others use 0.\n"
	       "  --stream-out-rgb-lim-range\n"
	       "                     Encode RGB values as limited [16-235] instead of full range.\n"
	       "  --stream-out-hor-speed <speed>\n"
	       "                     choose speed for horizontal movement. The default is 0,\n"
	       "                     and the range is [-3...3].\n"
	       "  --stream-out-vert-speed <speed>\n"
	       "                     choose speed for vertical movement. The default is 0,\n"
	       "                     and the range is [-3...3].\n"
	       "  --stream-out-perc-fill <percentage>\n"
	       "                     percentage of the frame to actually fill. The default is 100%%.\n"
	       "  --stream-out-buf-caps\n"
	       "                     show output buffer capabilities\n"
	       "  --stream-out-mmap <count>\n"
	       "                     output video using mmap() [VIDIOC_(D)QBUF]\n"
	       "                     count: the number of buffers to allocate. The default is 4.\n"
	       "  --stream-out-user <count>\n"
	       "                     output video using user pointers [VIDIOC_(D)QBUF]\n"
	       "                     count: the number of buffers to allocate. The default is 4.\n"
	       "  --stream-out-dmabuf\n"
	       "                     output video using dmabuf [VIDIOC_(D)QBUF]\n"
	       "                     Requires a corresponding --stream-mmap option.\n"
	       "  --list-patterns    list available patterns for use with --stream-out-pattern.\n"
	       "  --list-buffers     list all video buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-out list all video output buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-vbi list all VBI buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-vbi-out\n"
	       "                     list all VBI output buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-sliced-vbi\n"
	       "                     list all sliced VBI buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-sliced-vbi-out\n"
	       "                     list all sliced VBI output buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-sdr\n"
	       "                     list all SDR RX buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-sdr-out\n"
	       "                     list all SDR TX buffers [VIDIOC_QUERYBUF]\n"
	       "  --list-buffers-meta\n"
	       "                     list all Meta RX buffers [VIDIOC_QUERYBUF]\n",
#ifndef NO_STREAM_TO
		V4L_STREAM_PORT,
#endif
	       	V4L_STREAM_PORT);
}

static void get_codec_type(cv4l_fd &fd)
{
	cv4l_disable_trace dt(fd);
	struct v4l2_fmtdesc fmt_desc = {};
	int num_cap_fmts = 0;
	int num_compressed_cap_fmts = 0;
	int num_out_fmts = 0;
	int num_compressed_out_fmts = 0;

	codec_type = NOT_CODEC;

	if (!fd.has_vid_m2m())
		return;

	if (fd.enum_fmt(fmt_desc, true, 0, fd.g_type()))
		return;

	do {
		if (fmt_desc.flags & V4L2_FMT_FLAG_COMPRESSED)
			num_compressed_cap_fmts++;
		num_cap_fmts++;
	} while (!fd.enum_fmt(fmt_desc));


	if (fd.enum_fmt(fmt_desc, true, 0, v4l_type_invert(fd.g_type())))
		return;

	do {
		if (fmt_desc.flags & V4L2_FMT_FLAG_COMPRESSED)
			num_compressed_out_fmts++;
		num_out_fmts++;
	} while (!fd.enum_fmt(fmt_desc));

	if (num_compressed_out_fmts == 0 && num_compressed_cap_fmts == num_cap_fmts) {
		codec_type = ENCODER;
		return;
	}

	if (num_compressed_cap_fmts == 0 && num_compressed_out_fmts == num_out_fmts) {
		codec_type = DECODER;
		return;
	}
}

static void get_cap_compose_rect(cv4l_fd &fd)
{
	cv4l_disable_trace dt(fd);
	v4l2_selection sel;

	memset(&sel, 0, sizeof(sel));
	sel.type = vidcap_buftype;
	sel.target = V4L2_SEL_TGT_COMPOSE;

	if (fd.g_selection(sel) == 0) {
		support_cap_compose = true;
		composed_width = sel.r.width;
		composed_height = sel.r.height;
	} else {
		support_cap_compose = false;
    }
}

static void get_out_crop_rect(cv4l_fd &fd)
{
	cv4l_disable_trace dt(fd);
	v4l2_selection sel;

	memset(&sel, 0, sizeof(sel));
	sel.type = vidout_buftype;
	sel.target = V4L2_SEL_TGT_CROP;

	if (fd.g_selection(sel) == 0) {
		support_out_crop = true;
		cropped_width = sel.r.width;
		cropped_height = sel.r.height;
	} else {
		support_out_crop = false;
	}
}

static void set_time_stamp(cv4l_buffer &buf)
{
	if ((buf.g_flags() & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_COPY)
		return;
	buf.s_timestamp_clock();
}

static __u32 read_u32(FILE *f)
{
	__u32 v;

	if (fread(&v, 1, sizeof(v), f) != sizeof(v))
		return 0;
	return ntohl(v);
}

static void write_u32(FILE *f, __u32 v)
{
	v = htonl(v);
	fwrite(&v, 1, sizeof(v), f);
}

static std::string timestamp_type2s(__u32 flags)
{
	char buf[20];

	switch (flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) {
	case V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN:
		return "Unknown";
	case V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC:
		return "Monotonic";
	case V4L2_BUF_FLAG_TIMESTAMP_COPY:
		return "Copy";
	default:
		sprintf(buf, "Type %d", (flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) >> 13);
		return std::string(buf);
	}
}

static std::string timestamp_src2s(__u32 flags)
{
	char buf[20];

	switch (flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK) {
	case V4L2_BUF_FLAG_TSTAMP_SRC_EOF:
		return "End-of-Frame";
	case V4L2_BUF_FLAG_TSTAMP_SRC_SOE:
		return "Start-of-Exposure";
	default:
		sprintf(buf, "Source %d", (flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK) >> 16);
		return std::string(buf);
	}
}

static void print_buffer(FILE *f, struct v4l2_buffer &buf)
{
	const unsigned ts_flags = V4L2_BUF_FLAG_TIMESTAMP_MASK | V4L2_BUF_FLAG_TSTAMP_SRC_MASK;

	fprintf(f, "\tIndex    : %d\n", buf.index);
	fprintf(f, "\tType     : %s\n", buftype2s(buf.type).c_str());
	fprintf(f, "\tFlags    : %s\n", bufferflags2s(buf.flags & ~ts_flags).c_str());
	fprintf(f, "\tField    : %s\n", field2s(buf.field).c_str());
	fprintf(f, "\tSequence : %u\n", buf.sequence);
	fprintf(f, "\tLength   : %u\n", buf.length);
	fprintf(f, "\tBytesused: %u\n", buf.bytesused);
	fprintf(f, "\tTimestamp: %llu.%06llus (%s, %s)\n",
		static_cast<__u64>(buf.timestamp.tv_sec), static_cast<__u64>(buf.timestamp.tv_usec),
		timestamp_type2s(buf.flags).c_str(), timestamp_src2s(buf.flags).c_str());
	if (buf.flags & V4L2_BUF_FLAG_TIMECODE) {
		static constexpr int fps_types[] = { 0, 24, 25, 30, 50, 60 };
		int fps = buf.timecode.type;

		if (fps > 5)
			fps = 0;
		fprintf(f, "\tTimecode : %dfps %s %dh %dm %ds %df (0x%02x 0x%02x 0x%02x 0x%02x)\n",
			fps_types[fps],
			tc_flags2s(buf.timecode.flags).c_str(),
			buf.timecode.hours, buf.timecode.minutes,
			buf.timecode.seconds, buf.timecode.frames,
			buf.timecode.userbits[0], buf.timecode.userbits[1],
			buf.timecode.userbits[2], buf.timecode.userbits[3]);
	}
	if (buf.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
	    buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
		for (unsigned i = 0; i < buf.length; i++) {
			struct v4l2_plane *p = buf.m.planes + i;

			fprintf(f, "\tPlane    : %d\n", i);
			fprintf(f, "\t\tLength     : %u\n", p->length);
			fprintf(f, "\t\tBytesused  : %u\n", p->bytesused);
			fprintf(f, "\t\tData Offset: %u\n", p->data_offset);
		}
	}

	fprintf(f, "\n");
}

static void print_concise_buffer(FILE *f, cv4l_buffer &buf, cv4l_fmt &fmt,
				 cv4l_queue &q, fps_timestamps &fps_ts,
				 int comp_perc, bool skip_ts = false)
{
	static double last_ts;

	fprintf(f, "%s dqbuf: %*u seq: %6u bytesused: ",
		v4l_type_is_output(buf.g_type()) ? "out" : "cap",
		reqbufs_count_cap > 10 ? 2 : 1, buf.g_index(), buf.g_sequence());

	bool have_data_offset = false;

	for (unsigned i = 0; i < buf.g_num_planes(); i++) {
		fprintf(f, "%s%u", i ? "/" : "", buf.g_bytesused(i));
		if (buf.g_data_offset(i))
			have_data_offset = true;
	}
	if (have_data_offset) {
		fprintf(f, " offset: ");
		for (unsigned i = 0; i < buf.g_num_planes(); i++)
			fprintf(f, "%s%u", i ? "/" : "", buf.g_data_offset(i));
	}
	if (comp_perc >= 0)
		fprintf(f, " compression: %d%%", comp_perc);

	if (!skip_ts && (buf.g_flags() & V4L2_BUF_FLAG_TIMESTAMP_MASK) != V4L2_BUF_FLAG_TIMESTAMP_COPY) {
		double ts = buf.g_timestamp().tv_sec + buf.g_timestamp().tv_usec / 1000000.0;
		fprintf(f, " ts: %.06f", ts);
		if (last_ts != 0.0)
			fprintf(f, " delta: %.03f ms", (ts - last_ts) * 1000.0);
		if (options[OptStreamShowDeltaNow] &&
		    (buf.g_flags() & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
		    V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC) {
			timespec ts_clock;

			clock_gettime(CLOCK_MONOTONIC, &ts_clock);
			fprintf(f, " delta now: %+.03f ms",
				((ts_clock.tv_sec + ts_clock.tv_nsec / 1000000000.0) - ts) * 1000.0);
		}
		last_ts = ts;

		if (fps_ts.has_fps(true))
			fprintf(stderr, " fps: %.02f", fps_ts.fps());

		unsigned dropped = fps_ts.dropped();

		if (dropped)
			fprintf(stderr, " dropped: %u", dropped);
	}

	fprintf(f, " field: %s", field2s(buf.g_field()).c_str());

	__u32 fl = buf.g_flags() & (V4L2_BUF_FLAG_ERROR |
				    V4L2_BUF_FLAG_KEYFRAME |
				    V4L2_BUF_FLAG_PFRAME |
				    V4L2_BUF_FLAG_BFRAME |
				    V4L2_BUF_FLAG_LAST |
				    V4L2_BUF_FLAG_TIMESTAMP_MASK |
				    V4L2_BUF_FLAG_TSTAMP_SRC_MASK |
				    V4L2_BUF_FLAG_TIMECODE);
	if (fl)
		fprintf(f, " (%s)", bufferflags2s(fl).c_str());
	fprintf(f, "\n");
	if (v4l_type_is_meta(buf.g_type()) && buf.g_bytesused(0) &&
	    !(buf.g_flags() & V4L2_BUF_FLAG_ERROR))
		print_meta_buffer(f, buf, fmt, q);

	if ((capabilities & V4L2_CAP_TOUCH) && buf.g_bytesused(0) &&
	    !(buf.g_flags() & V4L2_BUF_FLAG_ERROR) &&
	    (fmt.g_width() < 64 ||  fmt.g_height() < 64))
		print_touch_buffer(f, buf, fmt, q);
}

static void stream_buf_caps(cv4l_fd &fd, unsigned buftype)
{
	v4l2_create_buffers cbufs;

	memset(&cbufs, 0, sizeof(cbufs));
	cbufs.format.type = buftype;
	cbufs.memory = V4L2_MEMORY_MMAP;
	if (!v4l_ioctl(fd.g_v4l_fd(), VIDIOC_CREATE_BUFS, &cbufs)) {
		printf("Streaming I/O Capabilities for %s: %s\n",
		       buftype2s(buftype).c_str(),
		       bufcap2s(cbufs.capabilities).c_str());
		return;
	}
	v4l2_requestbuffers rbufs;
	memset(&rbufs, 0, sizeof(rbufs));
	rbufs.type = buftype;
	rbufs.memory = V4L2_MEMORY_MMAP;
	if (v4l_ioctl(fd.g_v4l_fd(), VIDIOC_REQBUFS, &rbufs))
		return;
	printf("Streaming I/O Capabilities for %s: %s\n",
	       buftype2s(buftype).c_str(),
	       bufcap2s(rbufs.capabilities).c_str());
}

static void list_buffers(cv4l_fd &fd, unsigned buftype)
{
	cv4l_disable_trace dt(fd);
	int i;

	for (i = 0; i < VIDEO_MAX_FRAME; i++) {
		cv4l_buffer buf(buftype);

		if (fd.querybuf(buf, i))
			break;
		if (i == 0)
			printf("VIDIOC_QUERYBUF:\n");
		print_buffer(stdout, buf.buf);
	}
}

void streaming_cmd(int ch, char *optarg)
{
	char *value, *subs;
	unsigned i;
	int speed;

	switch (ch) {
	case OptStreamCount:
		stream_count = strtoul(optarg, nullptr, 0);
		break;
	case OptStreamSkip:
		stream_skip = strtoul(optarg, nullptr, 0);
		break;
	case OptStreamSleep:
		subs = optarg;
		while (*subs != '\0') {
			static constexpr const char *subopts[] = {
				"count",
				"sleep",
				"mode",
				nullptr
			};

			switch (parse_subopt(&subs, subopts, &value)) {
			case 0:
				stream_sleep_count = strtoul(value, nullptr, 0);
				break;
			case 1:
				stream_sleep_ms = strtoul(value, nullptr, 0);
				break;
			case 2:
				stream_sleep_mode = strtoul(value, nullptr, 0);
				if (stream_sleep_mode < 4)
					break;
				fallthrough;
			default:
				streaming_usage();
				std::exit(EXIT_FAILURE);
			}
		}
		break;
	case OptStreamNoQuery:
		stream_no_query = true;
		break;
	case OptStreamLoop:
		stream_loop = true;
		break;
	case OptStreamOutPattern:
		stream_pat = strtoul(optarg, nullptr, 0);
		for (i = 0; tpg_pattern_strings[i]; i++) ;
		if (stream_pat >= i)
			stream_pat = 0;
		break;
	case OptStreamOutSquare:
		stream_out_square = true;
		break;
	case OptStreamOutBorder:
		stream_out_border = true;
		break;
	case OptStreamOutInsertSAV:
		stream_out_sav = true;
		break;
	case OptStreamOutInsertEAV:
		stream_out_eav = true;
		break;
	case OptStreamOutPixelAspect:
		if (!strcmp(optarg, "square"))
			stream_out_pixel_aspect = TPG_PIXEL_ASPECT_SQUARE;
		else if (!strcmp(optarg, "ntsc"))
			stream_out_pixel_aspect = TPG_PIXEL_ASPECT_NTSC;
		else if (!strcmp(optarg, "pal"))
			stream_out_pixel_aspect = TPG_PIXEL_ASPECT_PAL;
		else
			streaming_usage();
		break;
	case OptStreamOutVideoAspect:
		if (!strcmp(optarg, "4x3"))
			stream_out_video_aspect = TPG_VIDEO_ASPECT_4X3;
		else if (!strcmp(optarg, "14x9"))
			stream_out_video_aspect = TPG_VIDEO_ASPECT_14X9_CENTRE;
		else if (!strcmp(optarg, "16x9"))
			stream_out_video_aspect = TPG_VIDEO_ASPECT_16X9_CENTRE;
		else if (!strcmp(optarg, "anamorphic"))
			stream_out_video_aspect = TPG_VIDEO_ASPECT_16X9_ANAMORPHIC;
		else
			streaming_usage();
		break;
	case OptStreamOutAlphaComponent:
		stream_out_alpha = strtoul(optarg, nullptr, 0);
		break;
	case OptStreamOutAlphaRedOnly:
		stream_out_alpha_red_only = true;
		break;
	case OptStreamOutRGBLimitedRange:
		stream_out_rgb_lim_range = true;
		break;
	case OptStreamOutHorSpeed:
	case OptStreamOutVertSpeed:
		speed = strtol(optarg, nullptr, 0);
		if (speed < -3)
			speed = -3;
		if (speed > 3)
			speed = 3;
		if (ch == OptStreamOutHorSpeed)
			stream_out_hor_mode = static_cast<tpg_move_mode>(speed + 3);
		else
			stream_out_vert_mode = static_cast<tpg_move_mode>(speed + 3);
		break;
	case OptStreamOutPercFill:
		stream_out_perc_fill = strtoul(optarg, nullptr, 0);
		if (stream_out_perc_fill > 100)
			stream_out_perc_fill = 100;
		if (stream_out_perc_fill < 1)
			stream_out_perc_fill = 1;
		break;
	case OptStreamTo:
		file_to = optarg;
		to_with_hdr = false;
		if (!strcmp(file_to, "-"))
			options[OptSilent] = true;
		break;
	case OptStreamToHdr:
		file_to = optarg;
		to_with_hdr = true;
		if (!strcmp(file_to, "-"))
			options[OptSilent] = true;
		break;
	case OptStreamToHost:
		host_to = optarg;
		break;
	case OptStreamLossless:
		host_lossless = true;
		break;
	case OptStreamFrom:
		file_from = optarg;
		from_with_hdr = false;
		break;
	case OptStreamFromHdr:
		file_from = optarg;
		from_with_hdr = true;
		break;
	case OptStreamFromHost:
		host_from = optarg;
		break;
	case OptStreamUser:
		memory = V4L2_MEMORY_USERPTR;
		fallthrough;
	case OptStreamMmap:
		if (optarg) {
			reqbufs_count_cap = strtoul(optarg, nullptr, 0);
			if (reqbufs_count_cap == 0)
				reqbufs_count_cap = 3;
		}
		break;
	case OptStreamDmaBuf:
		memory = V4L2_MEMORY_DMABUF;
		break;
	case OptStreamOutUser:
		out_memory = V4L2_MEMORY_USERPTR;
		fallthrough;
	case OptStreamOutMmap:
		if (optarg) {
			reqbufs_count_out = strtoul(optarg, nullptr, 0);
			if (reqbufs_count_out == 0)
				reqbufs_count_out = 3;
		}
		break;
	case OptStreamOutDmaBuf:
		out_memory = V4L2_MEMORY_DMABUF;
		break;
	}
}

/*
 * Assume that the fwht stream is valid and that each
 * frame starts right after the previous one.
 */
static bool read_fwht_frame(cv4l_fmt &fmt, unsigned char *buf,
			    FILE *fpointer, unsigned &sz,
			    unsigned &expected_len, unsigned buf_len)
{
	expected_len = sizeof(struct fwht_cframe_hdr);
	if (expected_len > buf_len)
		return false;
	sz = fread(&last_fwht_hdr, 1, sizeof(struct fwht_cframe_hdr), fpointer);
	if (sz < sizeof(struct fwht_cframe_hdr))
		return true;

	expected_len = ntohl(last_fwht_hdr.size);
	if (expected_len > buf_len)
		return false;
	sz = fread(buf, 1, ntohl(last_fwht_hdr.size), fpointer);
	return true;
}

static void set_fwht_stateless_params(struct v4l2_ctrl_fwht_params &fwht_params,
				      const struct fwht_cframe_hdr *hdr,
				      __u64 last_bf_ts)
{
	fwht_params.backward_ref_ts = last_bf_ts;
	fwht_params.version = ntohl(hdr->version);
	fwht_params.width = ntohl(hdr->width);
	fwht_params.height = ntohl(hdr->height);
	fwht_params.flags = ntohl(hdr->flags);
	fwht_params.colorspace = ntohl(hdr->colorspace);
	fwht_params.xfer_func = ntohl(hdr->xfer_func);
	fwht_params.ycbcr_enc = ntohl(hdr->ycbcr_enc);
	fwht_params.quantization = ntohl(hdr->quantization);

	/*
	 * if last_bf_ts is 0 it indicates that either this is the
	 * first frame, or that last frame returned with an error so
	 * it is better not to reference it so the error won't propagate
	 */
	if (!last_bf_ts)
		fwht_params.flags |= V4L2_FWHT_FL_I_FRAME;
}

static int alloc_fwht_req(int media_fd, unsigned index)
{
	int rc = 0;

	rc = ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &fwht_reqs[index]);
	if (rc < 0) {
		fprintf(stderr, "Unable to allocate media request: %s\n",
			strerror(errno));
		return rc;
	}

	return 0;
}

static void set_fwht_req_by_idx(unsigned idx, const struct fwht_cframe_hdr *hdr,
				__u64 last_bf_ts, __u64 ts)
{
	struct v4l2_ctrl_fwht_params fwht_params;

	set_fwht_stateless_params(fwht_params, hdr, last_bf_ts);

	fwht_reqs[idx].ts = ts;
	fwht_reqs[idx].params = fwht_params;
}

static int get_fwht_req_by_ts(__u64 ts)
{
	for (int idx = 0; idx < VIDEO_MAX_FRAME; idx++) {
		if (fwht_reqs[idx].ts == ts)
			return idx;
	}
	return -1;
}

static bool set_fwht_req_by_fd(const struct fwht_cframe_hdr *hdr,
			       int req_fd, __u64 last_bf_ts, __u64 ts)
{
	struct v4l2_ctrl_fwht_params fwht_params;

	set_fwht_stateless_params(fwht_params, hdr, last_bf_ts);

	for (auto &fwht_req : fwht_reqs) {
		if (fwht_req.fd == req_fd) {
			fwht_req.ts = ts;
			fwht_req.params = fwht_params;
			return true;
		}
	}
	return false;
}

static int set_fwht_ext_ctrl(cv4l_fd &fd, const struct fwht_cframe_hdr *hdr,
			     __u64 last_bf_ts, int req_fd)
{
	v4l2_ext_controls controls;
	struct v4l2_ext_control control;
	struct v4l2_ctrl_fwht_params fwht_params;

	memset(&control, 0, sizeof(control));
	memset(&controls, 0, sizeof(controls));

	set_fwht_stateless_params(fwht_params, hdr, last_bf_ts);

	control.id = V4L2_CID_STATELESS_FWHT_PARAMS;
	control.ptr = &fwht_params;
	control.size = sizeof(fwht_params);
	controls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
	controls.request_fd = req_fd;
	controls.controls = &control;
	controls.count = 1;
	return fd.s_ext_ctrls(controls);
}

static bool read_write_padded_frame(cv4l_fmt &fmt, unsigned char *buf,
				    FILE *fpointer, unsigned &sz,
				    unsigned &expected_len, unsigned buf_len,
				    bool is_read)
{
	const struct v4l2_fwht_pixfmt_info *info =
			v4l2_fwht_find_pixfmt(fmt.g_pixelformat());
	unsigned coded_height = fmt.g_height();
	unsigned real_width;
	unsigned real_height;
	unsigned char *plane_p = buf;
	unsigned char *row_p;
	unsigned stride = fmt.g_bytesperline();

	if (is_read) {
		real_width  = cropped_width;
		real_height = cropped_height;
	} else {
		real_width  = composed_width;
		real_height = composed_height;
	}

	sz = 0;
	expected_len = real_width * real_height * info->sizeimage_mult / info->sizeimage_div;
	if (expected_len > buf_len)
		return false;
	for (unsigned plane_idx = 0; plane_idx < info->planes_num; plane_idx++) {
		bool is_chroma_plane = plane_idx == 1 || plane_idx == 2;
		unsigned h_div = is_chroma_plane ? info->height_div : 1;
		unsigned w_div = is_chroma_plane ? info->width_div : 1;
		unsigned step = is_chroma_plane ? info->chroma_step :
			info->luma_alpha_step;
		unsigned int consume_sz = step * real_width / w_div;

		if (info->planes_num == 3 && plane_idx == 1)
			stride /= 2;

		if (plane_idx == 1 &&
		    (info->id == V4L2_PIX_FMT_NV24 || info->id == V4L2_PIX_FMT_NV42))
			stride *= 2;

		row_p = plane_p;
		for (unsigned i = 0; i < real_height / h_div; i++) {
			unsigned int wsz = 0;

			if (is_read)
				wsz = fread(row_p, 1, consume_sz, fpointer);
			else
				wsz = fwrite(row_p, 1, consume_sz, fpointer);
			if (wsz == 0 && i == 0 && plane_idx == 0)
				break;
			if (wsz != consume_sz) {
				fprintf(stderr, "padding: needed %u bytes, got %u\n", consume_sz, wsz);
				return true;
			}
			sz += wsz;
			row_p += stride;
		}
		plane_p += stride * (coded_height / h_div);
		if (sz == 0)
			break;
	}
	return true;
}

static bool fill_buffer_from_file(cv4l_fd &fd, cv4l_queue &q, cv4l_buffer &b,
				  cv4l_fmt &fmt, FILE *fin)
{
	static bool first = true;
	static bool is_fwht = false;

	if (host_fd_from >= 0) {
		for (;;) {
			unsigned packet = read_u32(fin);

			if (packet == V4L_STREAM_PACKET_END) {
				fprintf(stderr, "END packet read\n");
				return false;
			}

			char buf[1024];
			unsigned sz = read_u32(fin);

			if (packet == V4L_STREAM_PACKET_FRAME_VIDEO_RLE ||
			    packet == V4L_STREAM_PACKET_FRAME_VIDEO_FWHT) {
				is_fwht = packet == V4L_STREAM_PACKET_FRAME_VIDEO_FWHT;
				if (is_fwht && !ctx) {
					fprintf(stderr, "cannot support FWHT encoding\n");
					return false;
				}
				break;
			}

			fprintf(stderr, "expected FRAME_VIDEO, got 0x%08x\n", packet);
			while (sz) {
				unsigned rdsize = sz > sizeof(buf) ? sizeof(buf) : sz;

				int n = fread(buf, 1, rdsize, fin);
				if (n < 0) {
					fprintf(stderr, "error reading %d bytes\n", sz);
					return false;
				}
				sz -= n;
			}
		}

		unsigned sz = read_u32(fin);

		if (sz != V4L_STREAM_PACKET_FRAME_VIDEO_SIZE_HDR) {
			fprintf(stderr, "unsupported FRAME_VIDEO size\n");
			return false;
		}
		read_u32(fin);  // ignore field
		read_u32(fin);  // ignore flags
		for (unsigned j = 0; j < q.g_num_planes(); j++) {
			__u8 *buf = static_cast<__u8 *>(q.g_dataptr(b.g_index(), j));

			sz = read_u32(fin);
			if (sz != V4L_STREAM_PACKET_FRAME_VIDEO_SIZE_PLANE_HDR) {
				fprintf(stderr, "unsupported FRAME_VIDEO plane size\n");
				return false;
			}
			__u32 size = read_u32(fin);
			__u32 comp_size = read_u32(fin);
			__u32 offset = size - comp_size;
			unsigned sz = comp_size;
			__u8 *read_buf = buf;

			if (is_fwht) {
				read_buf = new __u8[comp_size];
				offset = 0;
			}

			if (size > q.g_length(j)) {
				fprintf(stderr, "plane size is too large (%u > %u)\n",
					size, q.g_length(j));
				return false;
			}
			while (sz) {
				int n = fread(read_buf + offset, 1, sz, fin);
				if (n < 0) {
					fprintf(stderr, "error reading %d bytes\n", sz);
					return false;
				}
				if (static_cast<__u32>(n) == sz)
					break;
				offset += n;
				sz -= n;
			}
			if (is_fwht) {
				fwht_decompress(ctx, read_buf, comp_size, buf, size);
				delete [] read_buf;
			} else {
				rle_decompress(buf, size, comp_size, bpl_out[j]);
			}
		}
		return true;
	}

restart:
	if (from_with_hdr) {
		__u32 v;

		if (!fread(&v, sizeof(v), 1, fin)) {
			if (first) {
				fprintf(stderr, "Insufficient data\n");
				return false;
			}
			if (stream_loop) {
				fseek(fin, 0, SEEK_SET);
				first = true;
				goto restart;
			}
			return false;
		}
		if (ntohl(v) != FILE_HDR_ID) {
			fprintf(stderr, "Unknown header ID\n");
			return false;
		}
	}

	for (unsigned j = 0; j < q.g_num_planes(); j++) {
		void *buf = q.g_dataptr(b.g_index(), j);
		unsigned buf_len = q.g_length(j);
		unsigned expected_len = q.g_length(j);
		unsigned sz;
		cv4l_fmt fmt;
		bool res = true;

		fd.g_fmt(fmt, q.g_type());
		if (from_with_hdr) {
			expected_len = read_u32(fin);
			if (expected_len > q.g_length(j)) {
				fprintf(stderr, "plane size is too large (%u > %u)\n",
					expected_len, q.g_length(j));
				return false;
			}
		}

		if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS)
			res = read_fwht_frame(fmt, static_cast<unsigned char *>(buf), fin,
					      sz, expected_len, buf_len);
		else if (codec_type != NOT_CODEC && support_out_crop &&
			 v4l2_fwht_find_pixfmt(fmt.g_pixelformat()))
			res = read_write_padded_frame(fmt, static_cast<unsigned char *>(buf),
						      fin, sz, expected_len, buf_len, true);
		else
			sz = fread(buf, 1, expected_len, fin);

		if (!res) {
			fprintf(stderr, "amount intended to be read/written is larger than the buffer size\n");
			return false;
		}
		if (first && sz != expected_len && fmt.g_bytesperline(j)) {
			fprintf(stderr, "%s: size read (%u) is different than needed (%u) in the first frame\n",
				__func__, sz, expected_len);
			return false;
		}
		if (j == 0 && sz == 0 && stream_loop) {
			fseek(fin, 0, SEEK_SET);
			first = true;
			goto restart;
		}
		b.s_bytesused(sz, j);
		if (sz == expected_len)
			continue;
		if (sz == 0)
			return false;
		if (sz && fmt.g_bytesperline(j))
			fprintf(stderr, "%u != %u\n", sz, expected_len);
	}
	first = false;
	return true;
}

static int do_setup_out_buffers(cv4l_fd &fd, cv4l_queue &q, FILE *fin, bool qbuf,
				bool ignore_count_skip)
{
	tpg_pixel_aspect aspect = TPG_PIXEL_ASPECT_SQUARE;
	cv4l_fmt fmt(q.g_type());
	u32 field;
	unsigned p;
	bool can_fill = false;
	bool is_video = q.g_type() == V4L2_BUF_TYPE_VIDEO_OUTPUT ||
			q.g_type() == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
	bool is_meta = q.g_type() == V4L2_BUF_TYPE_META_OUTPUT;

	if (q.obtain_bufs(&fd)) {
		fprintf(stderr, "%s q.obtain_bufs failed\n", __func__);
		return QUEUE_ERROR;
	}

	fd.g_fmt(fmt, q.g_type());
	{
		cv4l_disable_trace dt(fd);
		if (fd.g_std(stream_out_std)) {
			struct v4l2_dv_timings timings;

			stream_out_std = 0;
			if (fd.g_dv_timings(timings))
				memset(&timings, 0, sizeof(timings));
			else if (timings.bt.width == 720 && timings.bt.height == 480)
				aspect = TPG_PIXEL_ASPECT_NTSC;
			else if (timings.bt.width == 720 && timings.bt.height == 576)
				aspect = TPG_PIXEL_ASPECT_PAL;
		} else if (stream_out_std & V4L2_STD_525_60) {
			aspect = TPG_PIXEL_ASPECT_NTSC;
		} else if (stream_out_std & V4L2_STD_625_50) {
			aspect = TPG_PIXEL_ASPECT_PAL;
		}
	}

	field = fmt.g_field();

	output_field = field;
	output_field_alt = field == V4L2_FIELD_ALTERNATE;
	if (V4L2_FIELD_HAS_T_OR_B(field))
		output_field = (stream_out_std & V4L2_STD_525_60) ?
			V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP;

	if (is_video) {
		tpg_init(&tpg, 640, 360);
		tpg_alloc(&tpg, fmt.g_width());
		can_fill = tpg_s_fourcc(&tpg, fmt.g_pixelformat());
		tpg_reset_source(&tpg, fmt.g_width(), fmt.g_frame_height(), field);
		tpg_s_colorspace(&tpg, fmt.g_colorspace());
		tpg_s_xfer_func(&tpg, fmt.g_xfer_func());
		tpg_s_ycbcr_enc(&tpg, fmt.g_ycbcr_enc());
		tpg_s_quantization(&tpg, fmt.g_quantization());
		for (p = 0; p < fmt.g_num_planes(); p++)
			tpg_s_bytesperline(&tpg, p,
					   fmt.g_bytesperline(p));
		tpg_s_pattern(&tpg, static_cast<tpg_pattern>(stream_pat));
		tpg_s_mv_hor_mode(&tpg, stream_out_hor_mode);
		tpg_s_mv_vert_mode(&tpg, stream_out_vert_mode);
		tpg_s_show_square(&tpg, stream_out_square);
		tpg_s_show_border(&tpg, stream_out_border);
		tpg_s_insert_sav(&tpg, stream_out_sav);
		tpg_s_insert_eav(&tpg, stream_out_eav);
		tpg_s_perc_fill(&tpg, stream_out_perc_fill);
		if (stream_out_rgb_lim_range)
			tpg_s_real_rgb_range(&tpg, V4L2_DV_RGB_RANGE_LIMITED);
		tpg_s_alpha_component(&tpg, stream_out_alpha);
		tpg_s_alpha_mode(&tpg, stream_out_alpha_red_only);
		tpg_s_video_aspect(&tpg, stream_out_video_aspect);
		switch (stream_out_pixel_aspect) {
		case -1:
			tpg_s_pixel_aspect(&tpg, aspect);
			break;
		default:
			tpg_s_pixel_aspect(&tpg, static_cast<tpg_pixel_aspect>(stream_out_pixel_aspect));
			break;
		}
		field = output_field;
		if (can_fill && ((V4L2_FIELD_HAS_T_OR_B(field) && (stream_count & 1)) ||
				 !tpg_pattern_is_static(&tpg)))
			stream_out_refresh = true;
	}

	for (unsigned i = 0; i < q.g_buffers(); i++) {
		cv4l_buffer buf(q);

		if (fd.querybuf(buf, i)) {
			fprintf(stderr, "%s fd.querybuf failed\n", __func__);
			return QUEUE_ERROR;
		}

		buf.update(q, i);
		for (unsigned j = 0; j < q.g_num_planes(); j++)
			buf.s_bytesused(buf.g_length(j), j);
		if (is_video) {
			buf.s_field(field);
			tpg_s_field(&tpg, field, output_field_alt);
			if (output_field_alt) {
				if (field == V4L2_FIELD_TOP)
					field = V4L2_FIELD_BOTTOM;
				else if (field == V4L2_FIELD_BOTTOM)
					field = V4L2_FIELD_TOP;
			}

			if (can_fill) {
				for (unsigned j = 0; j < q.g_num_planes(); j++)
					tpg_fillbuffer(&tpg, stream_out_std, j, static_cast<u8 *>(q.g_dataptr(i, j)));
			}
		}
		if (is_meta)
			meta_fillbuffer(buf, fmt, q);

		if (fin && !fill_buffer_from_file(fd, q, buf, fmt, fin))
			return QUEUE_STOPPED;

		if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) {
			struct v4l2_capability vcap = {};
			fd.querycap(vcap);
			int media_fd = mi_get_media_fd(fd.g_fd(), (const char *)vcap.bus_info);

			if (media_fd < 0) {
				fprintf(stderr, "%s: mi_get_media_fd failed\n", __func__);
				return media_fd;
			}

			if (alloc_fwht_req(media_fd, i))
				return QUEUE_ERROR;
			buf.s_request_fd(fwht_reqs[i].fd);
			buf.or_flags(V4L2_BUF_FLAG_REQUEST_FD);

			if (set_fwht_ext_ctrl(fd, &last_fwht_hdr, last_fwht_bf_ts,
					      buf.g_request_fd())) {
				fprintf(stderr, "%s: set_fwht_ext_ctrl failed on %dth buf: %s\n",
					__func__, i, strerror(errno));
				return QUEUE_ERROR;
			}
		}
		if (qbuf) {
			fps_timestamps fps_ts;

			set_time_stamp(buf);
			if (fd.qbuf(buf))
				return QUEUE_ERROR;
			tpg_update_mv_count(&tpg, V4L2_FIELD_HAS_T_OR_B(field));
			if (!verbose)
				stderr_info(">");
			fflush(stderr);
			if (!ignore_count_skip && stream_count)
				if (!--stream_count)
					return QUEUE_STOPPED;
		}
		if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) {
			set_fwht_req_by_idx(i, &last_fwht_hdr,
					    last_fwht_bf_ts, buf.g_timestamp_ns());
			last_fwht_bf_ts = buf.g_timestamp_ns();
			if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_QUEUE) < 0) {
				fprintf(stderr, "Unable to queue media request: %s\n",
					strerror(errno));
				return QUEUE_ERROR;
			}
		}
	}
	if (qbuf)
		output_field = field;
	return 0;
}

static void write_buffer_to_file(cv4l_fd &fd, cv4l_queue &q, cv4l_buffer &buf,
				 cv4l_fmt &fmt, FILE *fout)
{
#ifndef NO_STREAM_TO
	unsigned comp_size[VIDEO_MAX_PLANES];
	__u8 *comp_ptr[VIDEO_MAX_PLANES];

	if (host_fd_to >= 0) {
		unsigned tot_comp_size = 0;
		unsigned tot_used = 0;

		for (unsigned j = 0; j < buf.g_num_planes(); j++) {
			__u32 used = buf.g_bytesused(j);
			unsigned offset = buf.g_data_offset(j);
			u8 *p = static_cast<u8 *>(q.g_dataptr(buf.g_index(), j)) + offset;

			if (ctx) {
				comp_ptr[j] = fwht_compress(ctx, p,
							    used - offset, &comp_size[j]);
			} else {
				comp_ptr[j] = p;
				comp_size[j] = rle_compress(p, used - offset,
							    bpl_cap[j]);
			}
			tot_comp_size += comp_size[j];
			tot_used += used - offset;
		}
		write_u32(fout, ctx ? V4L_STREAM_PACKET_FRAME_VIDEO_FWHT :
				V4L_STREAM_PACKET_FRAME_VIDEO_RLE);
		write_u32(fout, V4L_STREAM_PACKET_FRAME_VIDEO_SIZE(buf.g_num_planes()) + tot_comp_size);
		write_u32(fout, V4L_STREAM_PACKET_FRAME_VIDEO_SIZE_HDR);
		write_u32(fout, buf.g_field());
		write_u32(fout, buf.g_flags());
		comp_perc += (tot_comp_size * 100 / tot_used);
		comp_perc_count++;
	}
	if (to_with_hdr)
		write_u32(fout, FILE_HDR_ID);
	for (unsigned j = 0; j < buf.g_num_planes(); j++) {
		__u32 used = buf.g_bytesused(j);
		unsigned offset = buf.g_data_offset(j);
		unsigned sz;

		if (offset > used) {
			// Should never happen
			fprintf(stderr, "offset %d > used %d!\n",
				offset, used);
			offset = 0;
		}
		used -= offset;
		if (host_fd_to >= 0) {
			write_u32(fout, V4L_STREAM_PACKET_FRAME_VIDEO_SIZE_PLANE_HDR);
			write_u32(fout, used);
			write_u32(fout, comp_size[j]);
			used = comp_size[j];
		} else if (to_with_hdr) {
			write_u32(fout, used);
		}
		if (host_fd_to >= 0)
			sz = fwrite(comp_ptr[j] + offset, 1, used, fout);
		else if (codec_type != NOT_CODEC && support_cap_compose &&
			 v4l2_fwht_find_pixfmt(fmt.g_pixelformat()))
			read_write_padded_frame(fmt, static_cast<u8 *>(q.g_dataptr(buf.g_index(), j)) + offset,
						fout, sz, used, used, false);
		else
			sz = fwrite(static_cast<u8 *>(q.g_dataptr(buf.g_index(), j)) + offset, 1, used, fout);

		if (sz != used)
			fprintf(stderr, "%u != %u\n", sz, used);
	}
	if (host_fd_to >= 0)
		fflush(fout);
#endif
}

static int do_handle_cap(cv4l_fd &fd, cv4l_queue &q, FILE *fout, int *index,
			 unsigned &count, fps_timestamps &fps_ts, cv4l_fmt &fmt,
			 bool ignore_count_skip)
{
	char ch = '<';
	int ret;
	cv4l_buffer buf(q);

	for (;;) {
		ret = fd.dqbuf(buf);
		if (ret == EAGAIN)
			return 0;
		if (ret == EPIPE)
			return QUEUE_STOPPED;
		if (ret) {
			fprintf(stderr, "%s: failed: %s\n", "VIDIOC_DQBUF", strerror(errno));
			return QUEUE_ERROR;
		}
		if (buf.g_flags() & V4L2_BUF_FLAG_LAST) {
			last_buffer = true;
			break;
		}
		if (!(buf.g_flags() & V4L2_BUF_FLAG_ERROR))
			break;
		if (verbose)
			print_concise_buffer(stderr, buf, fmt, q, fps_ts, -1);
		if (fd.qbuf(buf))
			return QUEUE_ERROR;
	}

	bool is_empty_frame = !buf.g_bytesused(0);
	bool is_error_frame = buf.g_flags() & V4L2_BUF_FLAG_ERROR;

	double ts_secs = buf.g_timestamp().tv_sec + buf.g_timestamp().tv_usec / 1000000.0;
	fps_ts.add_ts(ts_secs, buf.g_sequence(), buf.g_field());

	if (fout && (!stream_skip || ignore_count_skip) &&
	    !is_empty_frame && !is_error_frame)
		write_buffer_to_file(fd, q, buf, fmt, fout);

	if (buf.g_flags() & V4L2_BUF_FLAG_KEYFRAME)
		ch = 'K';
	else if (buf.g_flags() & V4L2_BUF_FLAG_PFRAME)
		ch = 'P';
	else if (buf.g_flags() & V4L2_BUF_FLAG_BFRAME)
		ch = 'B';
	if (verbose) {
		print_concise_buffer(stderr, buf, fmt, q, fps_ts,
				     host_fd_to >= 0 ? 100 - comp_perc / comp_perc_count : -1);
		comp_perc_count = comp_perc = 0;
	}
	if (!last_buffer && index == nullptr) {
		/*
		 * EINVAL in qbuf can happen if this is the last buffer before
		 * a dynamic resolution change sequence. In this case the buffer
		 * has the size that fits the old resolution and might not
		 * fit to the new one.
		 */
		if (fd.qbuf(buf) && errno != EINVAL) {
			fprintf(stderr, "%s: qbuf error\n", __func__);
			return QUEUE_ERROR;
		}
	}
	if (index)
		*index = buf.g_index();

	if (!verbose) {
		stderr_info("%c", ch);
		fflush(stderr);

		if (fps_ts.has_fps()) {
			unsigned dropped = fps_ts.dropped();

			stderr_info(" %.02f fps", fps_ts.fps());
			if (dropped)
				stderr_info(", dropped buffers: %u", dropped);
			if (host_fd_to >= 0)
				stderr_info(" %d%% compression", 100 - comp_perc / comp_perc_count);
			comp_perc_count = comp_perc = 0;
			stderr_info("\n");
		}
	}
	count++;

	if (ignore_count_skip)
		return 0;

	if (need_sleep(count)) {
		if (stream_sleep_mode & 2)
			return QUEUE_OFF_ON;
		do_sleep();
	}

	if (is_empty_frame || is_error_frame)
		return 0;

	if (stream_skip) {
		stream_skip--;
		return 0;
	}

	if (stream_count == 0)
		return 0;

	if (--stream_count == 0)
		return QUEUE_STOPPED;

	return 0;
}

static int do_handle_out(cv4l_fd &fd, cv4l_queue &q, FILE *fin, cv4l_buffer *cap,
			 unsigned &count, fps_timestamps &fps_ts, cv4l_fmt fmt,
			 bool stopped, bool ignore_count_skip)
{
	cv4l_buffer buf(q);
	bool is_meta = q.g_type() == V4L2_BUF_TYPE_META_OUTPUT;
	int ret = 0;

	if (cap) {
		buf.s_index(cap->g_index());

		if (fd.querybuf(buf)) {
			fprintf(stderr, "%s fd.querybuf failed\n", __func__);
			return QUEUE_ERROR;
		}

		for (unsigned j = 0; j < buf.g_num_planes(); j++) {
			unsigned data_offset = cap->g_data_offset(j);

			if (q.g_memory() == V4L2_MEMORY_USERPTR) {
				buf.s_userptr(static_cast<u8 *>(cap->g_userptr(j)) + data_offset, j);
				buf.s_bytesused(cap->g_bytesused(j) - data_offset, j);
				buf.s_data_offset(0, j);
			} else if (q.g_memory() == V4L2_MEMORY_DMABUF) {
				__u32 bytesused = cap->g_bytesused(j);
				/*
				 * bytesused comes from the exported cap buffer
				 * but the length of the out buffer might be smaller
				 * so take the smaller of the two
				 */
				if (bytesused > buf.g_length(j))
					bytesused = buf.g_length(j);
				buf.s_bytesused(bytesused, j);
				buf.s_data_offset(data_offset, j);
				buf.s_fd(q.g_fd(buf.g_index(), j));
			}
		}
	} else {
		ret = fd.dqbuf(buf);
		if (ret == EAGAIN)
			return 0;

		double ts_secs = buf.g_timestamp().tv_sec + buf.g_timestamp().tv_usec / 1000000.0;
		fps_ts.add_ts(ts_secs, buf.g_sequence(), buf.g_field());
		if (verbose)
			print_concise_buffer(stderr, buf, fmt, q, fps_ts, -1);

		for (unsigned j = 0; j < buf.g_num_planes(); j++)
			buf.s_bytesused(buf.g_length(j), j);
	}
	if (ret) {
		fprintf(stderr, "%s: failed: %s\n", "VIDIOC_DQBUF", strerror(ret));
		return QUEUE_ERROR;
	}
	if (fps_ts.has_fps()) {
		unsigned dropped = fps_ts.dropped();

		stderr_info(" %.02f fps", fps_ts.fps());
		if (dropped)
			stderr_info(", dropped buffers: %u", dropped);
		stderr_info("\n");
	}
	if (stopped)
		return 0;

	buf.s_field(output_field);
	tpg_s_field(&tpg, output_field, output_field_alt);
	if (output_field_alt) {
		if (output_field == V4L2_FIELD_TOP)
			output_field = V4L2_FIELD_BOTTOM;
		else if (output_field == V4L2_FIELD_BOTTOM)
			output_field = V4L2_FIELD_TOP;
	}

	if (fin && !fill_buffer_from_file(fd, q, buf, fmt, fin))
		return QUEUE_STOPPED;

	if (!fin && stream_out_refresh) {
		for (unsigned j = 0; j < buf.g_num_planes(); j++)
			tpg_fillbuffer(&tpg, stream_out_std, j,
				       static_cast<u8 *>(q.g_dataptr(buf.g_index(), j)));
	}
	if (is_meta)
		meta_fillbuffer(buf, fmt, q);

	if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) {
		if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_REINIT, NULL)) {
			fprintf(stderr, "Unable to reinit media request: %s\n",
				strerror(errno));
			return QUEUE_ERROR;
		}

		if (set_fwht_ext_ctrl(fd, &last_fwht_hdr, last_fwht_bf_ts,
				      buf.g_request_fd())) {
			fprintf(stderr, "%s: set_fwht_ext_ctrl failed: %s\n",
				__func__, strerror(errno));
			return QUEUE_ERROR;
		}
	}

	set_time_stamp(buf);

	if (fd.qbuf(buf)) {
		fprintf(stderr, "%s: failed: %s\n", "VIDIOC_QBUF", strerror(errno));
		return QUEUE_ERROR;
	}
	if (fmt.g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS) {
		if (!set_fwht_req_by_fd(&last_fwht_hdr, buf.g_request_fd(), last_fwht_bf_ts,
					buf.g_timestamp_ns())) {
			fprintf(stderr, "%s: request for fd %d does not exist\n",
				__func__, buf.g_request_fd());
			return QUEUE_ERROR;
		}

		last_fwht_bf_ts = buf.g_timestamp_ns();
		if (ioctl(buf.g_request_fd(), MEDIA_REQUEST_IOC_QUEUE) < 0) {
			fprintf(stderr, "Unable to queue media request: %s\n",
				strerror(errno));
			return QUEUE_ERROR;
		}
	}

	tpg_update_mv_count(&tpg, V4L2_FIELD_HAS_T_OR_B(output_field));

	if (!verbose)
		stderr_info(">");
	fflush(stderr);

	count++;

	if (ignore_count_skip)
		return 0;

	if (need_sleep(count)) {
		if (stream_sleep_mode & 2)
			return QUEUE_OFF_ON;
		do_sleep();
	}

	if (stream_skip) {
		stream_skip--;
		return 0;
	}

	if (stream_count == 0)
		return 0;
	if (--stream_count == 0)
		return QUEUE_STOPPED;

	return 0;
}

static int do_handle_out_to_in(cv4l_fd &out_fd, cv4l_fd &fd, cv4l_queue &out, cv4l_queue &in)
{
	cv4l_buffer buf(out);
	int ret;

	do {
		ret = out_fd.dqbuf(buf);
	} while (ret == EAGAIN);
	if (ret) {
		fprintf(stderr, "%s: failed: %s\n", "VIDIOC_DQBUF", strerror(errno));
		return QUEUE_ERROR;
	}
	buf.init(in, buf.g_index());
	ret = fd.querybuf(buf);
	if (ret == 0)
		ret = fd.qbuf(buf);
	if (ret) {
		fprintf(stderr, "%s: failed: %s\n", "VIDIOC_QBUF", strerror(errno));
		return QUEUE_ERROR;
	}
	return 0;
}

static FILE *open_output_file(cv4l_fd &fd)
{
	FILE *fout = nullptr;

#ifndef NO_STREAM_TO
	if (file_to) {
		if (!strcmp(file_to, "-"))
			return stdout;
		fout = fopen(file_to, "w+");
		if (!fout)
			fprintf(stderr, "could not open %s for writing\n", file_to);
		return fout;
	}
	if (!host_to)
		return nullptr;

	char *p = std::strchr(host_to, ':');
	struct sockaddr_in serv_addr;
	struct hostent *server;
	struct v4l2_fract aspect;
	unsigned width, height;
	cv4l_fmt cfmt;

	fd.g_fmt(cfmt);

	aspect = fd.g_pixel_aspect(width, height);
	if (p) {
		host_port_to = strtoul(p + 1, nullptr, 0);
		*p = '\0';
	}
	host_fd_to = socket(AF_INET, SOCK_STREAM, 0);
	if (host_fd_to < 0) {
		fprintf(stderr, "cannot open socket");
		std::exit(EXIT_SUCCESS);
	}
	server = gethostbyname(host_to);
	if (server == nullptr) {
		fprintf(stderr, "no such host %s\n", host_to);
		std::exit(EXIT_SUCCESS);
	}
	memset(reinterpret_cast<char *>(&serv_addr), 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	memcpy(reinterpret_cast<char *>(&serv_addr.sin_addr.s_addr),
	       server->h_addr,
	       server->h_length);
	serv_addr.sin_port = htons(host_port_to);
	if (connect(host_fd_to, reinterpret_cast<struct sockaddr *>(&serv_addr), sizeof(serv_addr)) < 0) {
		fprintf(stderr, "could not connect\n");
		std::exit(EXIT_SUCCESS);
	}
	fout = fdopen(host_fd_to, "a");
	write_u32(fout, V4L_STREAM_ID);
	write_u32(fout, V4L_STREAM_VERSION);
	write_u32(fout, V4L_STREAM_PACKET_FMT_VIDEO);
	write_u32(fout, V4L_STREAM_PACKET_FMT_VIDEO_SIZE(cfmt.g_num_planes()));
	write_u32(fout, V4L_STREAM_PACKET_FMT_VIDEO_SIZE_FMT);
	write_u32(fout, cfmt.g_num_planes());
	write_u32(fout, cfmt.g_pixelformat());
	write_u32(fout, cfmt.g_width());
	write_u32(fout, cfmt.g_height());
	write_u32(fout, cfmt.g_field());
	write_u32(fout, cfmt.g_colorspace());
	write_u32(fout, cfmt.g_ycbcr_enc());
	write_u32(fout, cfmt.g_quantization());
	write_u32(fout, cfmt.g_xfer_func());
	write_u32(fout, cfmt.g_flags());
	write_u32(fout, aspect.numerator);
	write_u32(fout, aspect.denominator);
	for (unsigned i = 0; i < cfmt.g_num_planes(); i++) {
		write_u32(fout, V4L_STREAM_PACKET_FMT_VIDEO_SIZE_FMT_PLANE);
		write_u32(fout, cfmt.g_sizeimage(i));
		write_u32(fout, cfmt.g_bytesperline(i));
		bpl_cap[i] = rle_calc_bpl(cfmt.g_bytesperline(i), cfmt.g_pixelformat());
	}
	if (!host_lossless) {
		unsigned visible_width = support_cap_compose ? composed_width : cfmt.g_width();
		unsigned visible_height = support_cap_compose ? composed_height : cfmt.g_height();

		ctx = fwht_alloc(cfmt.g_pixelformat(), visible_width, visible_height,
				 cfmt.g_width(), cfmt.g_height(),
				 cfmt.g_field(), cfmt.g_colorspace(), cfmt.g_xfer_func(),
				 cfmt.g_ycbcr_enc(), cfmt.g_quantization());
	}
	fflush(fout);
#endif
	return fout;
}

static void streaming_set_cap(cv4l_fd &fd, cv4l_fd &exp_fd)
{
	int fd_flags = fcntl(fd.g_fd(), F_GETFL);
	cv4l_queue q(fd.g_type(), memory);
	cv4l_queue exp_q(exp_fd.g_type(), V4L2_MEMORY_MMAP);
	fps_timestamps fps_ts;
	bool use_poll = options[OptStreamPoll];
	unsigned count;
	bool eos;
	bool source_change;
	FILE *fout = nullptr;
	cv4l_fmt fmt;

	if (!(capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE |
			      V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_CAPTURE |
			      V4L2_CAP_META_CAPTURE | V4L2_CAP_SDR_CAPTURE |
			      V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))) {
		fprintf(stderr, "unsupported stream type\n");
		return;
	}
	if (options[OptStreamDmaBuf] && exp_fd.g_fd() < 0) {
		fprintf(stderr, "--stream-dmabuf can only work in combination with --export-device\n");
		return;
	}
	switch (q.g_type()) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
		break;
	default:
		if (host_to) {
			fprintf(stderr, "--stream-to-host is not supported for non-video streams\n");
			return;
		}
		break;
	}

	subscribe_event(fd, V4L2_EVENT_EOS);
	if (use_poll)
		subscribe_event(fd, V4L2_EVENT_SOURCE_CHANGE);

recover:
	eos = false;
	source_change = false;
	count = 0;

	if (!stream_no_query) {
		cv4l_disable_trace dt(fd);
		struct v4l2_dv_timings new_dv_timings = {};
		v4l2_std_id new_std;
		struct v4l2_input in = { };

		if (!fd.g_input(in.index) && !fd.enum_input(in, true, in.index)) {
			if (in.capabilities & V4L2_IN_CAP_DV_TIMINGS) {
				while (fd.query_dv_timings(new_dv_timings))
					sleep(1);
				fd.s_dv_timings(new_dv_timings);
				stderr_info("New timings found\n");
			} else if (in.capabilities & V4L2_IN_CAP_STD) {
				if (!fd.query_std(new_std))
					fd.s_std(new_std);
			}
		}
	}

	fout = open_output_file(fd);

	if (q.reqbufs(&fd, reqbufs_count_cap)) {
		if (q.g_type() != V4L2_BUF_TYPE_VBI_CAPTURE ||
		    !fd.has_raw_vbi_cap() || !fd.has_sliced_vbi_cap())
			goto done;
		fd.s_type(V4L2_BUF_TYPE_SLICED_VBI_CAPTURE);
		q.init(fd.g_type(), memory);
		if (q.reqbufs(&fd, reqbufs_count_cap))
			goto done;
	}

	if (options[OptStreamDmaBuf]) {
		if (exp_q.reqbufs(&exp_fd, reqbufs_count_cap))
			goto done;
		if (q.export_bufs(&exp_fd, exp_fd.g_type()))
			goto done;
	}

	if (q.obtain_bufs(&fd))
		goto done;

	fd.g_fmt(fmt);

restart:
	if (q.queue_all(&fd))
		goto done;

	fps_ts.determine_field(fd.g_fd(), q.g_type());

	if (fd.streamon())
		goto done;

	fd.s_trace(0);
	exp_fd.s_trace(0);

	if (stream_sleep_count == 0)
		do_sleep();

	if (use_poll)
		fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK);

	while (!eos && !source_change) {
		fd_set read_fds;
		fd_set exception_fds;
		struct timeval tv = { use_poll ? 2 : 0, 0 };
		int r;

		FD_ZERO(&exception_fds);
		FD_SET(fd.g_fd(), &exception_fds);
		FD_ZERO(&read_fds);
		FD_SET(fd.g_fd(), &read_fds);
		r = select(fd.g_fd() + 1, use_poll ? &read_fds : nullptr, nullptr, &exception_fds, &tv);

		if (r == -1) {
			if (EINTR == errno)
				continue;
			stderr_info("select error: %s\n",
					strerror(errno));
			goto done;
		}
		if (use_poll && r == 0) {
			stderr_info("select timeout\n");
			goto done;
		}

		if (FD_ISSET(fd.g_fd(), &exception_fds)) {
			struct v4l2_event ev;

			while (!fd.dqevent(ev)) {
				switch (ev.type) {
				case V4L2_EVENT_SOURCE_CHANGE:
					source_change = true;
					if (!verbose)
						stderr_info("\n");
					stderr_info("SOURCE CHANGE EVENT\n");
					break;
				case V4L2_EVENT_EOS:
					eos = true;
					if (!verbose)
						stderr_info("\n");
					stderr_info("EOS EVENT\n");
					fflush(stderr);
					break;
				}
			}
		}

		if (FD_ISSET(fd.g_fd(), &read_fds)) {
			r = do_handle_cap(fd, q, fout, nullptr,
					  count, fps_ts, fmt, false);
			if (r == QUEUE_OFF_ON) {
				fd.streamoff();
				fps_ts.reset();
				do_sleep();
				goto restart;
			}

			if (r < 0)
				break;
		}

	}
	fd.streamoff();
	fcntl(fd.g_fd(), F_SETFL, fd_flags);
	stderr_info("\n");

	q.free(&fd);
	tpg_free(&tpg);
	if (source_change && !stream_no_query)
		goto recover;

done:
	if (options[OptStreamDmaBuf])
		exp_q.close_exported_fds();
	if (fout && fout != stdout) {
		if (host_fd_to >= 0)
			write_u32(fout, V4L_STREAM_PACKET_END);
		fclose(fout);
	}
}

static FILE *open_input_file(cv4l_fd &fd, __u32 type)
{
	FILE *fin = nullptr;

	if (file_from) {
		if (!strcmp(file_from, "-"))
			return stdin;
		fin = fopen(file_from, "r");
		if (!fin)
			fprintf(stderr, "could not open %s for reading\n", file_from);
		return fin;
	}
	if (!host_from)
		return nullptr;

	char *p = std::strchr(host_from, ':');
	int listen_fd;
	socklen_t clilen;
	struct sockaddr_in serv_addr = {}, cli_addr;

	if (p) {
		host_port_from = strtoul(p + 1, nullptr, 0);
		*p = '\0';
	}
	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_fd < 0) {
		fprintf(stderr, "could not opening socket\n");
		std::exit(EXIT_FAILURE);
	}
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(host_port_from);
	if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&serv_addr), sizeof(serv_addr)) < 0) {
		fprintf(stderr, "could not bind\n");
		std::exit(EXIT_FAILURE);
	}
	listen(listen_fd, 1);
	clilen = sizeof(cli_addr);
	host_fd_from = accept(listen_fd, reinterpret_cast<struct sockaddr *>(&cli_addr), &clilen);
	if (host_fd_from < 0) {
		fprintf(stderr, "could not accept\n");
		std::exit(EXIT_FAILURE);
	}
	fin = fdopen(host_fd_from, "r");
	if (read_u32(fin) != V4L_STREAM_ID) {
		fprintf(stderr, "unknown protocol ID\n");
		std::exit(EXIT_FAILURE);
	}
	if (read_u32(fin) != V4L_STREAM_VERSION) {
		fprintf(stderr, "unknown protocol version\n");
		std::exit(EXIT_FAILURE);
	}
	for (;;) {
		__u32 packet = read_u32(fin);
		char buf[1024];

		if (packet == V4L_STREAM_PACKET_END) {
			fprintf(stderr, "END packet read\n");
			std::exit(EXIT_FAILURE);
		}

		if (packet == V4L_STREAM_PACKET_FMT_VIDEO)
			break;

		unsigned sz = read_u32(fin);
		while (sz) {
			unsigned rdsize = sz > sizeof(buf) ? sizeof(buf) : sz;
			int n;

			n = fread(buf, 1, rdsize, fin);
			if (n < 0) {
				fprintf(stderr, "error reading %d bytes\n", sz);
				std::exit(EXIT_FAILURE);
			}
			sz -= n;
		}
	}
	read_u32(fin);

	cv4l_fmt cfmt(type);

	unsigned sz = read_u32(fin);

	if (sz != V4L_STREAM_PACKET_FMT_VIDEO_SIZE_FMT) {
		fprintf(stderr, "unsupported FMT_VIDEO size\n");
		std::exit(EXIT_FAILURE);
	}
	cfmt.s_num_planes(read_u32(fin));
	cfmt.s_pixelformat(read_u32(fin));
	cfmt.s_width(read_u32(fin));
	cfmt.s_height(read_u32(fin));
	cfmt.s_field(read_u32(fin));
	cfmt.s_colorspace(read_u32(fin));
	cfmt.s_ycbcr_enc(read_u32(fin));
	cfmt.s_quantization(read_u32(fin));
	cfmt.s_xfer_func(read_u32(fin));
	cfmt.s_flags(read_u32(fin));
	unsigned visible_width = support_out_crop ? cropped_width : cfmt.g_width();
	unsigned visible_height = support_out_crop ? cropped_height : cfmt.g_height();

	ctx = fwht_alloc(cfmt.g_pixelformat(), visible_width, visible_height,
			 cfmt.g_width(), cfmt.g_height(),
			 cfmt.g_field(), cfmt.g_colorspace(), cfmt.g_xfer_func(),
			 cfmt.g_ycbcr_enc(), cfmt.g_quantization());

	read_u32(fin); // pixelaspect.numerator
	read_u32(fin); // pixelaspect.denominator

	for (unsigned i = 0; i < cfmt.g_num_planes(); i++) {
		unsigned sz = read_u32(fin);

		if (sz != V4L_STREAM_PACKET_FMT_VIDEO_SIZE_FMT_PLANE) {
			fprintf(stderr, "unsupported FMT_VIDEO plane size\n");
			std::exit(EXIT_FAILURE);
		}
		cfmt.s_sizeimage(read_u32(fin), i);
		cfmt.s_bytesperline(read_u32(fin), i);
		bpl_out[i] = rle_calc_bpl(cfmt.g_bytesperline(i), cfmt.g_pixelformat());
	}
	if (fd.s_fmt(cfmt)) {
		fprintf(stderr, "failed to set new format\n");
		std::exit(EXIT_FAILURE);
	}
	return fin;
}

static void streaming_set_out(cv4l_fd &fd, cv4l_fd &exp_fd)
{
	__u32 type = fd.has_vid_m2m() ? v4l_type_invert(fd.g_type()) : fd.g_type();
	cv4l_queue q(type, out_memory);
	cv4l_queue exp_q(exp_fd.g_type(), V4L2_MEMORY_MMAP);
	int fd_flags = fcntl(fd.g_fd(), F_GETFL);
	bool use_poll = options[OptStreamPoll];
	fps_timestamps fps_ts;
	unsigned count = 0;
	bool stopped = false;
	FILE *fin = nullptr;
	cv4l_fmt fmt;

	fd.g_fmt(fmt);

	if (!(capabilities & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
			      V4L2_CAP_VBI_OUTPUT | V4L2_CAP_SLICED_VBI_OUTPUT |
			      V4L2_CAP_SDR_OUTPUT | V4L2_CAP_META_OUTPUT |
			      V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE))) {
		fprintf(stderr, "unsupported stream type\n");
		return;
	}
	if (options[OptStreamOutDmaBuf] && exp_fd.g_fd() < 0) {
		fprintf(stderr, "--stream-out-dmabuf can only work in combination with --export-device\n");
		return;
	}
	switch (q.g_type()) {
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		break;
	default:
		if (host_from) {
			fprintf(stderr, "--stream-from-host is not supported for non-video streams\n");
			return;
		}
		break;
	}

	fin = open_input_file(fd, type);

	if (q.reqbufs(&fd, reqbufs_count_out)) {
		if (q.g_type() != V4L2_BUF_TYPE_VBI_OUTPUT ||
		    !fd.has_raw_vbi_out() || !fd.has_sliced_vbi_out())
			goto done;
		fd.s_type(V4L2_BUF_TYPE_SLICED_VBI_OUTPUT);
		q.init(fd.g_type(), memory);
		if (q.reqbufs(&fd, reqbufs_count_out))
			goto done;
	}

	if (options[OptStreamOutDmaBuf]) {
		if (exp_q.reqbufs(&exp_fd, reqbufs_count_out))
			goto done;
		if (q.export_bufs(&exp_fd, exp_fd.g_type()))
			goto done;
	}

	if (q.obtain_bufs(&fd))
		goto done;

restart:
	if (do_setup_out_buffers(fd, q, fin, true, false) < 0)
		goto done;

	fps_ts.determine_field(fd.g_fd(), type);

	if (fd.streamon())
		goto done;

	fd.s_trace(0);
	exp_fd.s_trace(0);

	if (stream_sleep_count == 0)
		do_sleep();

	if (use_poll)
		fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK);

	for (;;) {
		int r;

		if (use_poll) {
			fd_set fds;
			struct timeval tv;

			FD_ZERO(&fds);
			FD_SET(fd.g_fd(), &fds);

			/* Timeout. */
			tv.tv_sec = 2;
			tv.tv_usec = 0;

			r = select(fd.g_fd() + 1, nullptr, &fds, nullptr, &tv);

			if (r == -1) {
				if (EINTR == errno)
					continue;
				stderr_info("select error: %s\n",
					strerror(errno));
				goto done;
			}

			if (r == 0) {
				stderr_info("select timeout\n");
				goto done;
			}
		}
		r = do_handle_out(fd, q, fin, nullptr,
				  count, fps_ts, fmt, stopped, false);
		if (r == QUEUE_OFF_ON) {
			fd.streamoff();
			fps_ts.reset();
			do_sleep();
			goto restart;
		}
		if (r == QUEUE_STOPPED)
			stopped = true;
		if (r < 0)
			break;

	}

	if (options[OptDecoderCmd]) {
		fd.decoder_cmd(dec_cmd);
		options[OptDecoderCmd] = false;
	}
	fd.streamoff();
	fcntl(fd.g_fd(), F_SETFL, fd_flags);
	stderr_info("\n");

	q.free(&fd);
	tpg_free(&tpg);

done:
	if (options[OptStreamOutDmaBuf])
		exp_q.close_exported_fds();
	if (fin && fin != stdin)
		fclose(fin);
}

enum stream_type {
	CAP,
	OUT,
};

static int capture_setup(cv4l_fd &fd, cv4l_queue &in, cv4l_fd *exp_fd, cv4l_fmt *new_fmt = nullptr)
{
	if (fd.streamoff(in.g_type())) {
		fprintf(stderr, "%s: fd.streamoff error\n", __func__);
		return -1;
	}

	/* release any buffer allocated */
	if (in.reqbufs(&fd)) {
		fprintf(stderr, "%s: in.reqbufs 0 error\n", __func__);
		return -1;
	}

	if (options[OptSetVideoFormat]) {
		cv4l_fmt fmt;

		if (vidcap_get_and_update_fmt(fd, fmt)) {
			fprintf(stderr, "%s: vidcap_get_and_update_fmt error\n",
				__func__);
			return -1;
		}
		fd.s_fmt(fmt, in.g_type());
		if (new_fmt)
			*new_fmt = fmt;
	} else if (new_fmt) {
		fd.g_fmt(*new_fmt, in.g_type());
	}
	get_cap_compose_rect(fd);

	if (in.reqbufs(&fd, reqbufs_count_cap)) {
		fprintf(stderr, "%s: in.reqbufs %u error\n", __func__,
			reqbufs_count_cap);
		return -1;
	}

	if (exp_fd && in.export_bufs(exp_fd, exp_fd->g_type()))
		return -1;
	if (in.obtain_bufs(&fd) || in.queue_all(&fd)) {
		fprintf(stderr, "%s: in.obtain_bufs error\n", __func__);
		return -1;
	}

	if (fd.streamon(in.g_type())) {
		fprintf(stderr, "%s: fd.streamon error\n", __func__);
		return -1;
	}
	return 0;
}

static void stateful_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out,
			 FILE *fin, FILE *fout, cv4l_fmt &fmt_in,
			 const cv4l_fmt &fmt_out, cv4l_fd *exp_fd_p)
{
	int fd_flags = fcntl(fd.g_fd(), F_GETFL);
	fps_timestamps fps_ts[2];
	unsigned count[2] = { 0, 0 };
	fd_set fds[3];
	fd_set *rd_fds = &fds[0]; /* for capture */
	fd_set *ex_fds = &fds[1]; /* for capture */
	fd_set *wr_fds = &fds[2]; /* for output */
	bool cap_streaming = false;
	static struct v4l2_encoder_cmd enc_stop = {
		.cmd = V4L2_ENC_CMD_STOP,
	};
	static struct v4l2_decoder_cmd dec_stop = {
		.cmd = V4L2_DEC_CMD_STOP,
	};

	bool have_eos = subscribe_event(fd, V4L2_EVENT_EOS);
	bool is_encoder = false;
	bool ignore_count_skip = codec_type == ENCODER;

	if (have_eos) {
		cv4l_fmt fmt(in.g_type());

		fd.g_fmt(fmt);
		is_encoder = !fmt.g_bytesperline();
	}

	bool have_source_change = subscribe_event(fd, V4L2_EVENT_SOURCE_CHANGE);
	bool stopped = false;

	if (out.reqbufs(&fd, reqbufs_count_out))
		return;

	switch (do_setup_out_buffers(fd, out, fout, true, !ignore_count_skip)) {
	case 0:
		break;
	case QUEUE_STOPPED:
		stopped = true;
		break;
	default:
		return;
	}

	if (fd.streamon(out.g_type()))
		return;

	fd.s_trace(0);
	if (exp_fd_p)
		exp_fd_p->s_trace(0);

	if (codec_type != DECODER || !have_source_change)
		if (capture_setup(fd, in, exp_fd_p))
			return;

	fps_ts[CAP].determine_field(fd.g_fd(), in.g_type());
	fps_ts[OUT].determine_field(fd.g_fd(), out.g_type());

	if (stream_sleep_count == 0)
		do_sleep();

	fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK);

	if (have_eos && stopped) {
		if (!verbose)
			stderr_info("\n");
		stderr_info("STOP %sCODER\n", is_encoder ? "EN" : "DE");
		if (is_encoder)
			fd.encoder_cmd(enc_stop);
		else
			fd.decoder_cmd(dec_stop);
	}

	while (rd_fds || wr_fds || ex_fds) {
		struct timeval tv = { 2, 0 };
		struct timeval stopped_tv = { 0, 500000 };
		int r = 0;

		if (rd_fds) {
			FD_ZERO(rd_fds);
			FD_SET(fd.g_fd(), rd_fds);
		}

		if (ex_fds) {
			FD_ZERO(ex_fds);
			FD_SET(fd.g_fd(), ex_fds);
		}

		if (wr_fds) {
			FD_ZERO(wr_fds);
			FD_SET(fd.g_fd(), wr_fds);
		}

		r = select(fd.g_fd() + 1, rd_fds, wr_fds, ex_fds,
			   stopped ? &stopped_tv : &tv);

		if (r == -1) {
			if (EINTR == errno)
				continue;
			stderr_info("select error: %s\n",
					strerror(errno));
			return;
		}
		if (r == 0) {
			if (!stopped)
				stderr_info("select timeout");
			stderr_info("\n");
			return;
		}

		if (rd_fds && FD_ISSET(fd.g_fd(), rd_fds)) {
			r = do_handle_cap(fd, in, fin, nullptr,
					  count[CAP], fps_ts[CAP], fmt_in,
					  ignore_count_skip);
			if (r == QUEUE_STOPPED)
				break;
			if (r < 0) {
				rd_fds = nullptr;
				if (!have_eos) {
					ex_fds = nullptr;
					break;
				}
			}
		}

		if (wr_fds && FD_ISSET(fd.g_fd(), wr_fds)) {
			r = do_handle_out(fd, out, fout, nullptr,
					  count[OUT], fps_ts[OUT], fmt_out, stopped,
					  !ignore_count_skip);
			if (r == QUEUE_STOPPED) {
				stopped = true;
				if (have_eos) {
					if (!verbose)
						stderr_info("\n");
					stderr_info("STOP %sCODER\n", is_encoder ? "EN" : "DE");
					if (is_encoder)
						fd.encoder_cmd(enc_stop);
					else
						fd.decoder_cmd(dec_stop);
				}
			} else if (r < 0) {
				break;
			}
		}

		if (ex_fds && FD_ISSET(fd.g_fd(), ex_fds)) {
			struct v4l2_event ev;

			while (!fd.dqevent(ev)) {
				if (ev.type == V4L2_EVENT_EOS) {
					wr_fds = nullptr;
					if (!verbose)
						stderr_info("\n");
					stderr_info("EOS EVENT\n");
					fflush(stderr);
				} else if (ev.type == V4L2_EVENT_SOURCE_CHANGE) {
					if (!verbose)
						stderr_info("\n");
					stderr_info("SOURCE CHANGE EVENT\n");
					in_source_change_event = true;

					/*
					 * if capture is not streaming, the
					 * driver will not send a last buffer so
					 * we set it here
					 */
					if (!cap_streaming)
						last_buffer = true;
				}
			}
		}

		if (last_buffer) {
			if (in_source_change_event) {
				in_source_change_event = false;
				last_buffer = false;
				if (capture_setup(fd, in, exp_fd_p, &fmt_in))
					return;
				fps_ts[CAP].reset();
				fps_ts[OUT].reset();
				cap_streaming = true;
			} else {
				break;
			}
		}
	}

	fcntl(fd.g_fd(), F_SETFL, fd_flags);
	stderr_info("\n");

	fd.streamoff(in.g_type());
	fd.streamoff(out.g_type());
	in.free(&fd);
	out.free(&fd);
	tpg_free(&tpg);
}

static void stateless_m2m(cv4l_fd &fd, cv4l_queue &in, cv4l_queue &out,
			  FILE *fin, FILE *fout, cv4l_fmt &fmt_in,
			  const cv4l_fmt &fmt_out, cv4l_fd *exp_fd_p)
{
	fps_timestamps fps_ts[2];
	unsigned count[2] = { 0, 0 };
	int fd_flags = fcntl(fd.g_fd(), F_GETFL);
	bool stopped = false;

	if (out.reqbufs(&fd, reqbufs_count_out)) {
		fprintf(stderr, "%s: out.reqbufs failed\n", __func__);
		return;
	}

	if (in.reqbufs(&fd, reqbufs_count_cap)) {
		fprintf(stderr, "%s: in.reqbufs failed\n", __func__);
		return;
	}

	if (exp_fd_p && in.export_bufs(exp_fd_p, exp_fd_p->g_type()))
		return;

	if (in.obtain_bufs(&fd)) {
		fprintf(stderr, "%s: in.obtain_bufs error\n", __func__);
		return;
	}

	if (do_setup_out_buffers(fd, out, fout, true, true) == QUEUE_ERROR) {
		fprintf(stderr, "%s: do_setup_out_buffers failed\n", __func__);
		return;
	}

	if (in.queue_all(&fd)) {
		fprintf(stderr, "%s: in.queue_all failed\n", __func__);
		return;
	}

	if (fd.streamon(out.g_type())) {
		fprintf(stderr, "%s: streamon for out failed\n", __func__);
		return;
	}

	if (fd.streamon(in.g_type())) {
		fprintf(stderr, "%s: streamon for in failed\n", __func__);
		return;
	}
	int index = 0;
	bool queue_lst_buf = false;
	cv4l_buffer last_in_buf;

	fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK);

	while (true) {
		fd_set except_fds;
		int req_fd = fwht_reqs[index].fd;
		struct timeval tv = { 2, 0 };

		if (req_fd < 0)
			break;

		FD_ZERO(&except_fds);
		FD_SET(req_fd, &except_fds);

		int rc = select(req_fd + 1, nullptr, nullptr, &except_fds, &tv);

		if (rc == 0) {
			fprintf(stderr, "Timeout when waiting for media request\n");
			return;
		}
		if (rc < 0) {
			fprintf(stderr, "Unable to select media request: %s\n",
				strerror(errno));
			return;
		}
		/*
		 * it is safe to queue back last cap buffer only after
		 * the following request is done so that the buffer
		 * is not needed anymore as a reference frame
		 */
		if (queue_lst_buf) {
			if (fd.qbuf(last_in_buf)) {
				stderr_info("%s: qbuf failed\n", __func__);
				return;
			}
		}
		int buf_idx = -1;
		/*
		 * fin is not sent to do_handle_cap since the capture buf is
		 * written to the file in current function
		 */
		rc = do_handle_cap(fd, in, nullptr, &buf_idx, count[CAP],
				   fps_ts[CAP], fmt_in, false);
		if (rc && rc != QUEUE_STOPPED) {
			stderr_info("%s: do_handle_cap err\n", __func__);
			return;
		}
		/*
		 * in case of an error in the frame, set last ts to 0 as a
		 * means to recover so that next request will not use a
		 * reference buffer. Otherwise the error flag will be set to
		 * all the future capture buffers.
		 */
		if (buf_idx == -1) {
			stderr_info("%s: frame returned with error\n", __func__);
			last_fwht_bf_ts	= 0;
		} else {
			cv4l_buffer cap_buf(in, index);
			if (fd.querybuf(cap_buf))
				return;
			last_in_buf = cap_buf;
			queue_lst_buf = true;
			if (fin && cap_buf.g_bytesused(0) &&
			    !(cap_buf.g_flags() & V4L2_BUF_FLAG_ERROR)) {
				int idx = get_fwht_req_by_ts(cap_buf.g_timestamp_ns());

				if (idx < 0) {
					fprintf(stderr, "%s: could not find request from buffer\n", __func__);
					fprintf(stderr, "%s: ts = %llu\n", __func__, cap_buf.g_timestamp_ns());
					return;
				}
				composed_width = fwht_reqs[idx].params.width;
				composed_height = fwht_reqs[idx].params.height;
				write_buffer_to_file(fd, in, cap_buf,
						     fmt_in, fin);
			}
		}
		if (rc == QUEUE_STOPPED)
			return;

		if (!stopped) {
			rc = do_handle_out(fd, out, fout, nullptr, count[OUT],
					   fps_ts[OUT], fmt_out, false, true);
			if (rc) {
				stopped = true;
				if (rc != QUEUE_STOPPED)
					stderr_info("%s: output stream ended\n", __func__);
				close(req_fd);
				fwht_reqs[index].fd = -1;
			}
		}
		index = (index + 1) % out.g_buffers();
	}

	fcntl(fd.g_fd(), F_SETFL, fd_flags);
	stderr_info("\n");

	fd.streamoff(in.g_type());
	fd.streamoff(out.g_type());
	in.free(&fd);
	out.free(&fd);
	tpg_free(&tpg);
}

static void streaming_set_m2m(cv4l_fd &fd, cv4l_fd &exp_fd)
{
	cv4l_queue in(fd.g_type(), memory);
	cv4l_queue out(v4l_type_invert(fd.g_type()), out_memory);
	cv4l_queue exp_q(exp_fd.g_type(), V4L2_MEMORY_MMAP);
	cv4l_fd *exp_fd_p = nullptr;
	FILE *file[2] = {nullptr, nullptr};
	cv4l_fmt fmt[2];

	fd.g_fmt(fmt[OUT], out.g_type());
	fd.g_fmt(fmt[CAP], in.g_type());

	if (!fd.has_vid_m2m()) {
		fprintf(stderr, "unsupported m2m stream type\n");
		return;
	}
	if (options[OptStreamDmaBuf] && options[OptStreamOutDmaBuf]) {
		fprintf(stderr, "--stream-dmabuf and --stream-out-dmabuf not supported for m2m devices\n");
		return;
	}
	if ((options[OptStreamDmaBuf] || options[OptStreamOutDmaBuf]) && exp_fd.g_fd() < 0) {
		fprintf(stderr, "--stream-dmabuf or --stream-out-dmabuf can only work in combination with --export-device\n");
		return;
	}

	file[CAP] = open_output_file(fd);
	file[OUT] = open_input_file(fd, out.g_type());

	if (options[OptStreamDmaBuf]) {
		if (exp_q.reqbufs(&exp_fd, reqbufs_count_cap))
			goto done;
		exp_fd_p = &exp_fd;
	}

	if (options[OptStreamOutDmaBuf]) {
		if (exp_q.reqbufs(&exp_fd, reqbufs_count_out))
			goto done;
		if (out.export_bufs(&exp_fd, exp_fd.g_type()))
			goto done;
	}
	if (fmt[OUT].g_pixelformat() == V4L2_PIX_FMT_FWHT_STATELESS)
		stateless_m2m(fd, in, out, file[CAP], file[OUT], fmt[CAP], fmt[OUT], exp_fd_p);
	else
		stateful_m2m(fd, in, out, file[CAP], file[OUT], fmt[CAP], fmt[OUT], exp_fd_p);

done:
	if (options[OptStreamDmaBuf] || options[OptStreamOutDmaBuf])
		exp_q.close_exported_fds();

	if (file[CAP] && file[CAP] != stdout)
		fclose(file[CAP]);

	if (file[OUT] && file[OUT] != stdin)
		fclose(file[OUT]);
}

static void streaming_set_cap2out(cv4l_fd &fd, cv4l_fd &out_fd)
{
	int fd_flags = fcntl(fd.g_fd(), F_GETFL);
	bool use_poll = options[OptStreamPoll];
	bool use_dmabuf = options[OptStreamDmaBuf] || options[OptStreamOutDmaBuf];
	bool use_userptr = options[OptStreamUser] && options[OptStreamOutUser];
	__u32 out_type = out_fd.has_vid_m2m() ? v4l_type_invert(out_fd.g_type()) : out_fd.g_type();
	cv4l_queue in(fd.g_type(), memory);
	cv4l_queue out(out_type, out_memory);
	fps_timestamps fps_ts[2];
	unsigned count[2] = { 0, 0 };
	FILE *file[2] = {nullptr, nullptr};
	fd_set fds;
	unsigned cnt = 0;
	cv4l_fmt fmt[2];

	out_fd.g_fmt(fmt[OUT], out.g_type());
	fd.g_fmt(fmt[CAP], in.g_type());
	if (!(capabilities & (V4L2_CAP_VIDEO_CAPTURE |
			      V4L2_CAP_VIDEO_CAPTURE_MPLANE |
			      V4L2_CAP_VIDEO_M2M |
			      V4L2_CAP_VIDEO_M2M_MPLANE))) {
		fprintf(stderr, "unsupported capture stream type\n");
		return;
	}
	if (!(out_capabilities & (V4L2_CAP_VIDEO_OUTPUT |
					 V4L2_CAP_VIDEO_OUTPUT_MPLANE |
					 V4L2_CAP_VIDEO_M2M |
					 V4L2_CAP_VIDEO_M2M_MPLANE))) {
		fprintf(stderr, "unsupported output stream type\n");
		return;
	}
	if (options[OptStreamDmaBuf] && !options[OptStreamOutMmap]) {
		fprintf(stderr, "--stream-dmabuf can only work in combination with --stream-out-mmap\n");
		return;
	}
	if (options[OptStreamOutDmaBuf] && !options[OptStreamMmap]) {
		fprintf(stderr, "--stream-out-dmabuf can only work in combination with --stream-mmap\n");
		return;
	}
	if (options[OptStreamDmaBuf])
		reqbufs_count_cap = reqbufs_count_out;
	if (options[OptStreamOutDmaBuf])
		reqbufs_count_out = reqbufs_count_cap;
	if (!use_dmabuf && !use_userptr) {
		fprintf(stderr, "Allowed combinations (for now):\n");
		fprintf(stderr, "\t--stream-mmap and --stream-out-dmabuf\n");
		fprintf(stderr, "\t--stream-dmabuf and --stream-out-mmap\n");
		fprintf(stderr, "\t--stream-user and --stream-out-user\n");
		return;
	}

	if (file_to) {
		if (!strcmp(file_to, "-"))
			file[CAP] = stdout;
		else
			file[CAP] = fopen(file_to, "w+");
		if (!file[CAP]) {
			fprintf(stderr, "could not open %s for writing\n", file_to);
			return;
		}
	}

	if (file_from) {
		if (!strcmp(file_from, "-"))
			file[OUT] = stdin;
		else
			file[OUT] = fopen(file_from, "r");
		if (!file[OUT]) {
			fprintf(stderr, "could not open %s for reading\n", file_from);
			return;
		}
	}

	if (in.reqbufs(&fd, reqbufs_count_cap) ||
	    out.reqbufs(&out_fd, reqbufs_count_out))
		goto done;

	if (options[OptStreamDmaBuf]) {
		if (in.export_bufs(&out_fd, out.g_type()))
			goto done;
	} else if (options[OptStreamOutDmaBuf]) {
		if (out.export_bufs(&fd, in.g_type()))
			goto done;
	}

	if (in.g_num_planes() != out.g_num_planes()) {
		fprintf(stderr, "mismatch between number of planes\n");
		goto done;
	}
	if (in.obtain_bufs(&fd)) {
		fprintf(stderr, "%s: in.obtain_bufs failed\n", __func__);
		goto done;
	}

	if (in.queue_all(&fd)) {
		fprintf(stderr, "%s: in.queue_all failed\n", __func__);
		goto done;
	}


	if (do_setup_out_buffers(out_fd, out, file[OUT], false, false) == QUEUE_ERROR) {
		fprintf(stderr, "%s: do_setup_out_buffers failed\n", __func__);
		goto done;
	}

	fps_ts[CAP].determine_field(fd.g_fd(), in.g_type());
	fps_ts[OUT].determine_field(out_fd.g_fd(), out.g_type());

	if (fd.streamon() || out_fd.streamon())
		goto done;

	fd.s_trace(0);
	out_fd.s_trace(0);

	if (stream_sleep_count == 0)
		do_sleep();

	if (use_poll)
		fcntl(fd.g_fd(), F_SETFL, fd_flags | O_NONBLOCK);

	while (true) {
		struct timeval tv = { use_poll ? 2 : 0, 0 };
		int r = 0;

		FD_ZERO(&fds);
		FD_SET(fd.g_fd(), &fds);

		if (use_poll)
			r = select(fd.g_fd() + 1, &fds, nullptr, nullptr, &tv);

		if (r == -1) {
			if (EINTR == errno)
				continue;
			stderr_info("select error: %s\n",
					strerror(errno));
			goto done;
		}
		if (use_poll && r == 0) {
			stderr_info("select timeout\n");
			goto done;
		}

		if (FD_ISSET(fd.g_fd(), &fds)) {
			int index = -1;

			r = do_handle_cap(fd, in, file[CAP], &index,
					  count[CAP], fps_ts[CAP], fmt[CAP], true);
			if (r)
				stderr_info("handle cap %d\n", r);
			if (!r) {
				cv4l_buffer buf(in, index);

				if (fd.querybuf(buf))
					break;
				r = do_handle_out(out_fd, out, file[OUT], &buf,
						  count[OUT], fps_ts[OUT], fmt[OUT],
						  false, false);
			}
			if (r)
				stderr_info("handle out %d\n", r);
			if (!r && cnt++ > 1)
				r = do_handle_out_to_in(out_fd, fd, out, in);
			if (r)
				stderr_info("handle out2in %d\n", r);
			if (r < 0) {
				fd.streamoff();
				out_fd.streamoff();
				break;
			}
		}
	}

done:
	fcntl(fd.g_fd(), F_SETFL, fd_flags);
	stderr_info("\n");

	if (options[OptStreamDmaBuf])
		out.close_exported_fds();
	if (options[OptStreamOutDmaBuf])
		in.close_exported_fds();

	in.free(&fd);
	out.free(&out_fd);
	tpg_free(&tpg);

	if (file[CAP] && file[CAP] != stdout)
		fclose(file[CAP]);

	if (file[OUT] && file[OUT] != stdin)
		fclose(file[OUT]);
}

void streaming_set(cv4l_fd &fd, cv4l_fd &out_fd, cv4l_fd &exp_fd)
{
	int do_cap = options[OptStreamMmap] + options[OptStreamUser] + options[OptStreamDmaBuf];
	int do_out = options[OptStreamOutMmap] + options[OptStreamOutUser] + options[OptStreamOutDmaBuf];

	if (out_fd.g_fd() < 0) {
		out_capabilities = capabilities;
		out_priv_magic = priv_magic;
	}

	if (do_cap > 1) {
		fprintf(stderr, "only one of --stream-mmap/user/dmabuf is allowed\n");
		return;
	}
	if (do_out > 1) {
		fprintf(stderr, "only one of --stream-out-mmap/user/dmabuf is allowed\n");
		return;
	}

	unsigned int old_trace_fd = fd.g_trace();
	unsigned int old_trace_out_fd = out_fd.g_trace();
	unsigned int old_trace_exp_fd = exp_fd.g_trace();

	get_cap_compose_rect(fd);
	get_out_crop_rect(fd);
	get_codec_type(fd);

	if (do_cap && do_out && out_fd.g_fd() < 0)
		streaming_set_m2m(fd, exp_fd);
	else if (do_cap && do_out)
		streaming_set_cap2out(fd, out_fd);
	else if (do_cap)
		streaming_set_cap(fd, exp_fd);
	else if (do_out)
		streaming_set_out(fd, exp_fd);

	fd.s_trace(old_trace_fd);
	out_fd.s_trace(old_trace_out_fd);
	exp_fd.s_trace(old_trace_exp_fd);
}

void streaming_list(cv4l_fd &fd, cv4l_fd &out_fd)
{
	cv4l_fd *p_out_fd = out_fd.g_fd() < 0 ? &fd : &out_fd;

	if (out_fd.g_fd() < 0)
		out_capabilities = capabilities;

	if (options[OptListBuffers])
		list_buffers(fd, fd.g_type());

	if (options[OptListBuffersOut])
		list_buffers(*p_out_fd, p_out_fd->has_vid_m2m() ?
			     v4l_type_invert(p_out_fd->g_type()) : p_out_fd->g_type());

	if (options[OptStreamBufCaps])
		stream_buf_caps(fd, fd.g_type());

	if (options[OptStreamOutBufCaps])
		stream_buf_caps(*p_out_fd, v4l_type_invert(p_out_fd->g_type()));

	if (options[OptListBuffersVbi])
		list_buffers(fd, V4L2_BUF_TYPE_VBI_CAPTURE);

	if (options[OptListBuffersSlicedVbi])
		list_buffers(fd, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE);

	if (options[OptListBuffersVbiOut])
		list_buffers(*p_out_fd, V4L2_BUF_TYPE_VBI_OUTPUT);

	if (options[OptListBuffersSlicedVbiOut])
		list_buffers(*p_out_fd, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT);

	if (options[OptListBuffersSdr])
		list_buffers(fd, V4L2_BUF_TYPE_SDR_CAPTURE);

	if (options[OptListBuffersSdrOut])
		list_buffers(fd, V4L2_BUF_TYPE_SDR_OUTPUT);

	if (options[OptListBuffersMeta])
		list_buffers(fd, V4L2_BUF_TYPE_META_CAPTURE);

	if (options[OptListPatterns]) {
		printf("List of available patterns:\n");
		for (unsigned i = 0; tpg_pattern_strings[i]; i++)
			printf("\t%2d: %s\n", i, tpg_pattern_strings[i]);
	}
}
