diff options
| -rw-r--r-- | kms++util/inc/kms++util/kms++util.h | 1 | ||||
| -rw-r--r-- | kms++util/inc/kms++util/resourcemanager.h | 27 | ||||
| -rw-r--r-- | kms++util/inc/kms++util/videodevice.h | 86 | ||||
| -rw-r--r-- | kms++util/src/resourcemanager.cpp | 147 | ||||
| -rw-r--r-- | kms++util/src/videodevice.cpp | 460 | ||||
| -rw-r--r-- | py/CMakeLists.txt | 2 | ||||
| -rwxr-xr-x | py/alpha-test.py | 6 | ||||
| -rwxr-xr-x | py/cam.py | 78 | ||||
| -rwxr-xr-x | py/db.py | 5 | ||||
| -rwxr-xr-x | py/functest.py | 7 | ||||
| -rwxr-xr-x | py/gamma.py | 6 | ||||
| -rw-r--r-- | py/helpers.py | 12 | ||||
| -rwxr-xr-x | py/iact.py | 7 | ||||
| -rw-r--r-- | py/pykms.cpp | 3 | ||||
| -rw-r--r-- | py/pykmsutil.cpp | 18 | ||||
| -rw-r--r-- | py/pyvid.cpp | 38 | ||||
| -rwxr-xr-x | py/test.py | 7 | ||||
| -rwxr-xr-x | py/trans-test.py | 6 | ||||
| -rw-r--r-- | utils/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | utils/kmsview.cpp | 23 | ||||
| -rw-r--r-- | utils/wbcap.cpp | 362 | ||||
| -rw-r--r-- | utils/wbm2m.cpp | 168 | 
22 files changed, 1421 insertions, 54 deletions
| diff --git a/kms++util/inc/kms++util/kms++util.h b/kms++util/inc/kms++util/kms++util.h index ca3c406..10a1f0a 100644 --- a/kms++util/inc/kms++util/kms++util.h +++ b/kms++util/inc/kms++util/kms++util.h @@ -8,6 +8,7 @@  #include <kms++util/extcpuframebuffer.h>  #include <kms++util/stopwatch.h>  #include <kms++util/opts.h> +#include <kms++util/resourcemanager.h>  #include <cstdio>  #include <cstdlib> diff --git a/kms++util/inc/kms++util/resourcemanager.h b/kms++util/inc/kms++util/resourcemanager.h new file mode 100644 index 0000000..92e7b93 --- /dev/null +++ b/kms++util/inc/kms++util/resourcemanager.h @@ -0,0 +1,27 @@ +#include <kms++/kms++.h> +#include <vector> +#include <string> + +namespace kms { + +class ResourceManager +{ +public: +	ResourceManager(Card& card); + +	void reset(); + +	Connector* reserve_connector(const std::string& name = ""); +	Crtc* reserve_crtc(Connector* conn); +	Plane* reserve_plane(Crtc* crtc, PlaneType type, PixelFormat format = PixelFormat::Undefined); +	Plane* reserve_primary_plane(Crtc* crtc, PixelFormat format = PixelFormat::Undefined); +	Plane* reserve_overlay_plane(Crtc* crtc, PixelFormat format = PixelFormat::Undefined); + +private: +	Card& m_card; +	std::vector<Connector*> m_reserved_connectors; +	std::vector<Crtc*> m_reserved_crtcs; +	std::vector<Plane*> m_reserved_planes; +}; + +} diff --git a/kms++util/inc/kms++util/videodevice.h b/kms++util/inc/kms++util/videodevice.h new file mode 100644 index 0000000..68e2b01 --- /dev/null +++ b/kms++util/inc/kms++util/videodevice.h @@ -0,0 +1,86 @@ +#pragma once + +#include <string> +#include <kms++/kms++.h> + +class VideoStreamer; + +class VideoDevice +{ +public: +	struct VideoFrameSize +	{ +		uint32_t min_w, max_w, step_w; +		uint32_t min_h, max_h, step_h; +	}; + +	VideoDevice(const std::string& dev); +	VideoDevice(int fd); +	~VideoDevice(); + +	VideoDevice(const VideoDevice& other) = delete; +	VideoDevice& operator=(const VideoDevice& other) = delete; + +	VideoStreamer* get_capture_streamer(); +	VideoStreamer* get_output_streamer(); + +	std::vector<std::tuple<uint32_t, uint32_t>> get_discrete_frame_sizes(kms::PixelFormat fmt); +	VideoFrameSize get_frame_sizes(kms::PixelFormat fmt); + +	int fd() const { return m_fd; } +	bool has_capture() const { return m_has_capture; } +	bool has_output() const { return m_has_output; } +	bool has_m2m() const { return m_has_m2m; } + +	static std::vector<std::string> get_capture_devices(); +	static std::vector<std::string> get_m2m_devices(); + +private: +	int m_fd; + +	bool m_has_capture; +	bool m_has_mplane_capture; + +	bool m_has_output; +	bool m_has_mplane_output; + +	bool m_has_m2m; +	bool m_has_mplane_m2m; + +	std::vector<kms::DumbFramebuffer*> m_capture_fbs; +	std::vector<kms::DumbFramebuffer*> m_output_fbs; + +	VideoStreamer* m_capture_streamer; +	VideoStreamer* m_output_streamer; +}; + +class VideoStreamer +{ +public: +	enum class StreamerType { +		CaptureSingle, +		CaptureMulti, +		OutputSingle, +		OutputMulti, +	}; + +	VideoStreamer(int fd, StreamerType type); + +	std::vector<std::string> get_ports(); +	void set_port(uint32_t index); + +	std::vector<kms::PixelFormat> get_formats(); +	void set_format(kms::PixelFormat fmt, uint32_t width, uint32_t height); +	void set_queue_size(uint32_t queue_size); +	void queue(kms::DumbFramebuffer* fb); +	kms::DumbFramebuffer* dequeue(); +	void stream_on(); +	void stream_off(); + +	int fd() const { return m_fd; } + +private: +	int m_fd; +	StreamerType m_type; +	std::vector<kms::DumbFramebuffer*> m_fbs; +}; diff --git a/kms++util/src/resourcemanager.cpp b/kms++util/src/resourcemanager.cpp new file mode 100644 index 0000000..cdd3e40 --- /dev/null +++ b/kms++util/src/resourcemanager.cpp @@ -0,0 +1,147 @@ +#include <kms++util/resourcemanager.h> +#include <algorithm> +#include <kms++util/strhelpers.h> + +using namespace kms; +using namespace std; + +template<class C, class T> +auto contains(const C& v, const T& x) +-> decltype(end(v), true) +{ +	return end(v) != std::find(begin(v), end(v), x); +} + +ResourceManager::ResourceManager(Card& card) +	: m_card(card) +{ +} + +void ResourceManager::reset() +{ +	m_reserved_connectors.clear(); +	m_reserved_crtcs.clear(); +	m_reserved_planes.clear(); +} + +static Connector* find_connector(Card& card, const vector<Connector*> reserved) +{ +	for (Connector* conn : card.get_connectors()) { +		if (!conn->connected()) +			continue; + +		if (contains(reserved, conn)) +			continue; + +		return conn; +	} + +	return nullptr; +} + +static Connector* resolve_connector(Card& card, const string& name, const vector<Connector*> reserved) +{ +	auto connectors = card.get_connectors(); + +	if (name[0] == '@') { +		char* endptr; +		unsigned id = strtoul(name.c_str() + 1, &endptr, 10); +		if (*endptr == 0) { +			Connector* c = card.get_connector(id); + +			if (!c || contains(reserved, c)) +				return nullptr; + +			return c; +		} +	} else { +		char* endptr; +		unsigned idx = strtoul(name.c_str(), &endptr, 10); +		if (*endptr == 0) { +			if (idx >= connectors.size()) +				return nullptr; + +			Connector* c = connectors[idx]; + +			if (contains(reserved, c)) +				return nullptr; + +			return c; +		} +	} + +	for (Connector* conn : connectors) { +		if (to_lower(conn->fullname()).find(to_lower(name)) == string::npos) +			continue; + +		if (contains(reserved, conn)) +			continue; + +		return conn; +	} + +	return nullptr; +} + +Connector* ResourceManager::reserve_connector(const string& name) +{ +	Connector* conn; + +	if (name.empty()) +		conn = find_connector(m_card, m_reserved_connectors); +	else +		conn = resolve_connector(m_card, name, m_reserved_connectors); + +	if (!conn) +		return nullptr; + +	m_reserved_connectors.push_back(conn); +	return conn; +} + +Crtc* ResourceManager::reserve_crtc(Connector* conn) +{ +	if (Crtc* crtc = conn->get_current_crtc()) { +		m_reserved_crtcs.push_back(crtc); +		return crtc; +	} + +	for (Crtc* crtc : conn->get_possible_crtcs()) { +		if (contains(m_reserved_crtcs, crtc)) +			continue; + +		m_reserved_crtcs.push_back(crtc); +		return crtc; +	} + +	return nullptr; +} + +Plane* ResourceManager::reserve_plane(Crtc* crtc, PlaneType type, PixelFormat format) +{ +	for (Plane* plane : crtc->get_possible_planes()) { +		if (plane->plane_type() != type) +			continue; + +		if (format != PixelFormat::Undefined && !plane->supports_format(format)) +			continue; + +		if (contains(m_reserved_planes, plane)) +			continue; + +		m_reserved_planes.push_back(plane); +		return plane; +	} + +	return nullptr; +} + +Plane* ResourceManager::reserve_primary_plane(Crtc* crtc, PixelFormat format) +{ +	return reserve_plane(crtc, PlaneType::Primary, format); +} + +Plane* ResourceManager::reserve_overlay_plane(Crtc* crtc, PixelFormat format) +{ +	return reserve_plane(crtc, PlaneType::Overlay, format); +} diff --git a/kms++util/src/videodevice.cpp b/kms++util/src/videodevice.cpp new file mode 100644 index 0000000..8cc18dc --- /dev/null +++ b/kms++util/src/videodevice.cpp @@ -0,0 +1,460 @@ +#include <string> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <linux/videodev2.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> +#include <kms++util/videodevice.h> + +using namespace std; +using namespace kms; + +/* V4L2 helper funcs */ +static vector<PixelFormat> v4l2_get_formats(int fd, uint32_t buf_type) +{ +	vector<PixelFormat> v; + +	v4l2_fmtdesc desc { }; +	desc.type = buf_type; + +	while (ioctl(fd, VIDIOC_ENUM_FMT, &desc) == 0) { +		v.push_back((PixelFormat)desc.pixelformat); +		desc.index++; +	} + +	return v; +} + +static void v4l2_set_format(int fd, PixelFormat fmt, uint32_t width, uint32_t height, uint32_t buf_type) +{ +	int r; + +	v4l2_format v4lfmt { }; + +	v4lfmt.type = buf_type; +	r = ioctl(fd, VIDIOC_G_FMT, &v4lfmt); +	ASSERT(r == 0); + +	const PixelFormatInfo& pfi = get_pixel_format_info(fmt); + +	bool mplane = buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + +	if (mplane) { +		v4l2_pix_format_mplane& mp = v4lfmt.fmt.pix_mp; + +		mp.pixelformat = (uint32_t)fmt; +		mp.width = width; +		mp.height = height; + +		mp.num_planes = pfi.num_planes; + +		for (unsigned i = 0; i < pfi.num_planes; ++i) { +			const PixelFormatPlaneInfo& pfpi = pfi.planes[i]; +			v4l2_plane_pix_format& p = mp.plane_fmt[i]; + +			p.bytesperline = width * pfpi.bitspp / 8; +			p.sizeimage = p.bytesperline * height / pfpi.ysub; +		} + +		r = ioctl(fd, VIDIOC_S_FMT, &v4lfmt); +		ASSERT(r == 0); + +		ASSERT(mp.pixelformat == (uint32_t)fmt); +		ASSERT(mp.width == width); +		ASSERT(mp.height == height); + +		ASSERT(mp.num_planes == pfi.num_planes); + +		for (unsigned i = 0; i < pfi.num_planes; ++i) { +			const PixelFormatPlaneInfo& pfpi = pfi.planes[i]; +			v4l2_plane_pix_format& p = mp.plane_fmt[i]; + +			ASSERT(p.bytesperline == width * pfpi.bitspp / 8); +			ASSERT(p.sizeimage == p.bytesperline * height / pfpi.ysub); +		} +	} else { +		ASSERT(pfi.num_planes == 1); + +		v4lfmt.fmt.pix.pixelformat = (uint32_t)fmt; +		v4lfmt.fmt.pix.width = width; +		v4lfmt.fmt.pix.height = height; +		v4lfmt.fmt.pix.bytesperline = width * pfi.planes[0].bitspp / 8; + +		r = ioctl(fd, VIDIOC_S_FMT, &v4lfmt); +		ASSERT(r == 0); + +		ASSERT(v4lfmt.fmt.pix.pixelformat == (uint32_t)fmt); +		ASSERT(v4lfmt.fmt.pix.width == width); +		ASSERT(v4lfmt.fmt.pix.height == height); +		ASSERT(v4lfmt.fmt.pix.bytesperline == width * pfi.planes[0].bitspp / 8); +	} +} + +static void v4l2_request_bufs(int fd, uint32_t queue_size, uint32_t buf_type) +{ +	v4l2_requestbuffers v4lreqbuf { }; +	v4lreqbuf.type = buf_type; +	v4lreqbuf.memory = V4L2_MEMORY_DMABUF; +	v4lreqbuf.count = queue_size; +	int r = ioctl(fd, VIDIOC_REQBUFS, &v4lreqbuf); +	ASSERT(r == 0); +	ASSERT(v4lreqbuf.count == queue_size); +} + +static void v4l2_queue_dmabuf(int fd, uint32_t index, DumbFramebuffer* fb, uint32_t buf_type) +{ +	v4l2_buffer buf { }; +	buf.type = buf_type; +	buf.memory = V4L2_MEMORY_DMABUF; +	buf.index = index; + +	const PixelFormatInfo& pfi = get_pixel_format_info(fb->format()); + +	bool mplane = buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + +	if (mplane) { +		buf.length = pfi.num_planes; + +		v4l2_plane planes[4] { }; +		buf.m.planes = planes; + +		for (unsigned i = 0; i < pfi.num_planes; ++i) { +			planes[i].m.fd = fb->prime_fd(i); +			planes[i].bytesused = fb->size(i); +			planes[i].length = fb->size(i); +		} + +		int r = ioctl(fd, VIDIOC_QBUF, &buf); +		ASSERT(r == 0); +	} else { +		buf.m.fd = fb->prime_fd(0); + +		int r = ioctl(fd, VIDIOC_QBUF, &buf); +		ASSERT(r == 0); +	} +} + +static uint32_t v4l2_dequeue(int fd, uint32_t buf_type) +{ +	v4l2_buffer buf { }; +	buf.type = buf_type; +	buf.memory = V4L2_MEMORY_DMABUF; + +	// V4L2 crashes if planes are not set +	v4l2_plane planes[4] { }; +	buf.m.planes = planes; +	buf.length = 4; + +	int r = ioctl(fd, VIDIOC_DQBUF, &buf); +	if (r) +		throw system_error(errno, generic_category()); + +	return buf.index; +} + + + + +VideoDevice::VideoDevice(const string& dev) +	:VideoDevice(::open(dev.c_str(), O_RDWR | O_NONBLOCK)) +{ +} + +VideoDevice::VideoDevice(int fd) +	: m_fd(fd), m_has_capture(false), m_has_output(false), m_has_m2m(false), m_capture_streamer(0), m_output_streamer(0) +{ +	FAIL_IF(fd < 0, "Bad fd"); + +	struct v4l2_capability cap = { }; +	int r = ioctl(fd, VIDIOC_QUERYCAP, &cap); +	ASSERT(r == 0); + +	if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) { +		m_has_capture = true; +		m_has_mplane_capture = true; +	} else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) { +		m_has_capture = true; +		m_has_mplane_capture = false; +	} + +	if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT_MPLANE) { +		m_has_output = true; +		m_has_mplane_output = true; +	} else if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) { +		m_has_output = true; +		m_has_mplane_output = false; +	} + +	if (cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE) { +		m_has_m2m = true; +		m_has_capture = true; +		m_has_output = true; +		m_has_mplane_m2m = true; +		m_has_mplane_capture = true; +		m_has_mplane_output = true; +	} else if (cap.capabilities & V4L2_CAP_VIDEO_M2M) { +		m_has_m2m = true; +		m_has_capture = true; +		m_has_output = true; +		m_has_mplane_m2m = false; +		m_has_mplane_capture = false; +		m_has_mplane_output = false; +	} +} + +VideoDevice::~VideoDevice() +{ +	::close(m_fd); +} + +VideoStreamer* VideoDevice::get_capture_streamer() +{ +	ASSERT(m_has_capture); + +	if (!m_capture_streamer) { +		auto type = m_has_mplane_capture ? VideoStreamer::StreamerType::CaptureMulti : VideoStreamer::StreamerType::CaptureSingle; +		m_capture_streamer = new VideoStreamer(m_fd, type); +	} + +	return m_capture_streamer; +} + +VideoStreamer* VideoDevice::get_output_streamer() +{ +	ASSERT(m_has_output); + +	if (!m_output_streamer) { +		auto type = m_has_mplane_output ? VideoStreamer::StreamerType::OutputMulti : VideoStreamer::StreamerType::OutputSingle; +		m_output_streamer = new VideoStreamer(m_fd, type); +	} + +	return m_output_streamer; +} + +vector<tuple<uint32_t, uint32_t>> VideoDevice::get_discrete_frame_sizes(PixelFormat fmt) +{ +	vector<tuple<uint32_t, uint32_t>> v; + +	v4l2_frmsizeenum v4lfrms { }; +	v4lfrms.pixel_format = (uint32_t)fmt; + +	int r = ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms); +	ASSERT(r); + +	FAIL_IF(v4lfrms.type != V4L2_FRMSIZE_TYPE_DISCRETE, "No discrete frame sizes"); + +	while (ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms) == 0) { +		v.emplace_back(v4lfrms.discrete.width, v4lfrms.discrete.height); +		v4lfrms.index++; +	}; + +	return v; +} + +VideoDevice::VideoFrameSize VideoDevice::get_frame_sizes(PixelFormat fmt) +{ +	v4l2_frmsizeenum v4lfrms { }; +	v4lfrms.pixel_format = (uint32_t)fmt; + +	int r = ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms); +	ASSERT(r); + +	FAIL_IF(v4lfrms.type == V4L2_FRMSIZE_TYPE_DISCRETE, "No continuous frame sizes"); + +	VideoFrameSize s; + +	s.min_w = v4lfrms.stepwise.min_width; +	s.max_w = v4lfrms.stepwise.max_width; +	s.step_w = v4lfrms.stepwise.step_width; + +	s.min_h = v4lfrms.stepwise.min_height; +	s.max_h = v4lfrms.stepwise.max_height; +	s.step_h = v4lfrms.stepwise.step_height; + +	return s; +} + +vector<string> VideoDevice::get_capture_devices() +{ +	vector<string> v; + +	for (int i = 0; i < 20; ++i) { +		string name = "/dev/video" + to_string(i); + +		struct stat buffer; +		if (stat(name.c_str(), &buffer) != 0) +			continue; + +		VideoDevice vid(name); + +		if (vid.has_capture() && !vid.has_m2m()) +			v.push_back(name); +	} + +	return v; +} + +vector<string> VideoDevice::get_m2m_devices() +{ +	vector<string> v; + +	for (int i = 0; i < 20; ++i) { +		string name = "/dev/video" + to_string(i); + +		struct stat buffer; +		if (stat(name.c_str(), &buffer) != 0) +			continue; + +		VideoDevice vid(name); + +		if (vid.has_m2m()) +			v.push_back(name); +	} + +	return v; +} + + +VideoStreamer::VideoStreamer(int fd, StreamerType type) +	: m_fd(fd), m_type(type) +{ + +} + +std::vector<string> VideoStreamer::get_ports() +{ +	vector<string> v; + +	switch (m_type) { +	case StreamerType::CaptureSingle: +	case StreamerType::CaptureMulti: +	{ +		struct v4l2_input input { }; + +		while (ioctl(m_fd, VIDIOC_ENUMINPUT, &input) == 0) { +			v.push_back(string((char*)&input.name)); +			input.index++; +		} + +		break; +	} + +	case StreamerType::OutputSingle: +	case StreamerType::OutputMulti: +	{ +		struct v4l2_output output { }; + +		while (ioctl(m_fd, VIDIOC_ENUMOUTPUT, &output) == 0) { +			v.push_back(string((char*)&output.name)); +			output.index++; +		} + +		break; +	} + +	default: +		FAIL("Bad StreamerType"); +	} + +	return v; +} + +void VideoStreamer::set_port(uint32_t index) +{ +	unsigned long req; + +	switch (m_type) { +	case StreamerType::CaptureSingle: +	case StreamerType::CaptureMulti: +		req = VIDIOC_S_INPUT; +		break; + +	case StreamerType::OutputSingle: +	case StreamerType::OutputMulti: +		req = VIDIOC_S_OUTPUT; +		break; + +	default: +		FAIL("Bad StreamerType"); +	} + +	int r = ioctl(m_fd, req, &index); +	ASSERT(r == 0); +} + +static v4l2_buf_type get_buf_type(VideoStreamer::StreamerType type) +{ +	switch (type) { +	case VideoStreamer::StreamerType::CaptureSingle: +		return V4L2_BUF_TYPE_VIDEO_CAPTURE; +	case VideoStreamer::StreamerType::CaptureMulti: +		return V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; +	case VideoStreamer::StreamerType::OutputSingle: +		return V4L2_BUF_TYPE_VIDEO_OUTPUT; +	case VideoStreamer::StreamerType::OutputMulti: +		return V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; +	default: +		FAIL("Bad StreamerType"); +	} +} + +std::vector<PixelFormat> VideoStreamer::get_formats() +{ +	return v4l2_get_formats(m_fd, get_buf_type(m_type)); +} + +void VideoStreamer::set_format(PixelFormat fmt, uint32_t width, uint32_t height) +{ +	v4l2_set_format(m_fd, fmt, width, height, get_buf_type(m_type)); +} + +void VideoStreamer::set_queue_size(uint32_t queue_size) +{ +	v4l2_request_bufs(m_fd, queue_size, get_buf_type(m_type)); +	m_fbs.resize(queue_size); +} + +void VideoStreamer::queue(DumbFramebuffer* fb) +{ +	uint32_t idx; + +	for (idx = 0; idx < m_fbs.size(); ++idx) { +		if (m_fbs[idx] == nullptr) +			break; +	} + +	FAIL_IF(idx == m_fbs.size(), "queue full"); + +	m_fbs[idx] = fb; + +	v4l2_queue_dmabuf(m_fd, idx, fb, get_buf_type(m_type)); +} + +DumbFramebuffer*VideoStreamer::dequeue() +{ +	uint32_t idx = v4l2_dequeue(m_fd, get_buf_type(m_type)); + +	auto fb = m_fbs[idx]; +	m_fbs[idx] = nullptr; + +	return fb; +} + +void VideoStreamer::stream_on() +{ +	uint32_t buf_type = get_buf_type(m_type); +	int r = ioctl(m_fd, VIDIOC_STREAMON, &buf_type); +	FAIL_IF(r, "Failed to enable stream: %d", r); +} + +void VideoStreamer::stream_off() +{ +	uint32_t buf_type = get_buf_type(m_type); +	int r = ioctl(m_fd, VIDIOC_STREAMOFF, &buf_type); +	FAIL_IF(r, "Failed to disable stream: %d", r); +} diff --git a/py/CMakeLists.txt b/py/CMakeLists.txt index 562a3cf..1349ea5 100644 --- a/py/CMakeLists.txt +++ b/py/CMakeLists.txt @@ -10,7 +10,7 @@ endif()  include_directories(${PROJECT_SOURCE_DIR}/ext/pybind11/include) -add_library(pykms SHARED pykms.cpp pykmsbase.cpp pykmsutil.cpp) +add_library(pykms SHARED pykms.cpp pykmsbase.cpp pykmsutil.cpp pyvid.cpp)  target_link_libraries(pykms kms++ kms++util ${LIBDRM_LIBRARIES})  # Don't add a 'lib' prefix to the shared library diff --git a/py/alpha-test.py b/py/alpha-test.py index 113fab0..c6ec8ee 100755 --- a/py/alpha-test.py +++ b/py/alpha-test.py @@ -9,10 +9,10 @@ card = pykms.Card()  card = 0  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode() -crtc = get_crtc_for_connector(conn)  planes = []  for p in card.planes: diff --git a/py/cam.py b/py/cam.py new file mode 100755 index 0000000..b44f8f9 --- /dev/null +++ b/py/cam.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 + +import sys +import selectors +import pykms +from helpers import * + + +w = 640 +h = 480 +fmt = pykms.PixelFormat.YUYV + + +# This hack makes drm initialize the fbcon, setting up the default connector +card = pykms.Card() +card = 0 + +card = pykms.Card() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn) +plane = res.reserve_overlay_plane(crtc, fmt) + +mode = conn.get_default_mode() + +NUM_BUFS = 5 + +fbs = [] +for i in range(NUM_BUFS): +    fb = pykms.DumbFramebuffer(card, w, h, fmt) +    fbs.append(fb) + +vidpath = pykms.VideoDevice.get_capture_devices()[0] + +vid = pykms.VideoDevice(vidpath) +cap = vid.capture_streamer +cap.set_port(0) +cap.set_format(fmt, w, h) +cap.set_queue_size(NUM_BUFS) + +for fb in fbs: +    cap.queue(fb) + +cap.stream_on() + + +def readvid(conn, mask): +    fb = cap.dequeue() + +    if card.has_atomic: +        set_props(plane, { +            "FB_ID": fb.id, +            "CRTC_ID": crtc.id, +            "SRC_W": fb.width << 16, +            "SRC_H": fb.height << 16, +            "CRTC_W": fb.width, +            "CRTC_H": fb.height, +        }) +    else: +        crtc.set_plane(plane, fb, 0, 0, fb.width, fb.height, +            0, 0, fb.width, fb.height) + +    cap.queue(fb) + +def readkey(conn, mask): +    #print("KEY EVENT"); +    sys.stdin.readline() +    exit(0) + +sel = selectors.DefaultSelector() +sel.register(cap.fd, selectors.EVENT_READ, readvid) +sel.register(sys.stdin, selectors.EVENT_READ, readkey) + +while True: +    events = sel.select() +    for key, mask in events: +        callback = key.data +        callback(key.fileobj, mask) @@ -41,9 +41,10 @@ class FlipHandler(pykms.PageFlipHandlerBase):  card = pykms.Card() -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode() -crtc = get_crtc_for_connector(conn)  fliphandler = FlipHandler() diff --git a/py/functest.py b/py/functest.py index c2548fa..44c29fb 100755 --- a/py/functest.py +++ b/py/functest.py @@ -4,16 +4,15 @@ import pykms  from helpers import *  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode()  fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");  pykms.draw_test_pattern(fb); -crtc = get_crtc_for_connector(conn) -  crtc.set_mode(conn, fb, mode)  print("OK") diff --git a/py/gamma.py b/py/gamma.py index e1daa43..a6b68cc 100755 --- a/py/gamma.py +++ b/py/gamma.py @@ -8,10 +8,10 @@ card = pykms.Card()  card = 0  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode() -crtc = get_crtc_for_connector(conn)  fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");  pykms.draw_test_pattern(fb); diff --git a/py/helpers.py b/py/helpers.py index e92163c..fd67d41 100644 --- a/py/helpers.py +++ b/py/helpers.py @@ -52,15 +52,3 @@ def disable_planes(card):      if areq.commit_sync() != 0:          print("disabling planes failed") - -def get_crtc_for_connector(conn): -    crtc = conn.get_current_crtc() - -    if crtc != None: -        return crtc - -    for crtc in conn.get_possible_crtcs(): -        if crtc.mode_valid == False: -            return crtc - -    raise RuntimeError("No free crtc found") @@ -9,16 +9,15 @@ from math import cos  from helpers import *  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode()  fb = pykms.DumbFramebuffer(card, 200, 200, "XR24");  pykms.draw_test_pattern(fb); -crtc = get_crtc_for_connector(conn) -  #crtc.set_mode(conn, fb, mode)  i = 0 diff --git a/py/pykms.cpp b/py/pykms.cpp index 57ca363..c759d23 100644 --- a/py/pykms.cpp +++ b/py/pykms.cpp @@ -9,6 +9,7 @@ using namespace std;  void init_pykmstest(py::module &m);  void init_pykmsbase(py::module &m); +void init_pyvid(py::module &m);  class PyPageFlipHandlerBase : PageFlipHandlerBase  { @@ -39,5 +40,7 @@ PYBIND11_PLUGIN(pykms) {  	init_pykmstest(m); +	init_pyvid(m); +  	return m.ptr();  } diff --git a/py/pykmsutil.cpp b/py/pykmsutil.cpp index 5ee1d4e..ab9f5a8 100644 --- a/py/pykmsutil.cpp +++ b/py/pykmsutil.cpp @@ -20,6 +20,24 @@ void init_pykmstest(py::module &m)  			.def_property_readonly("rgb565", &RGB::rgb565)  			; +	py::class_<ResourceManager>(m, "ResourceManager") +			.def(py::init<Card&>()) +			.def("reset", &ResourceManager::reset) +			.def("reserve_connector", &ResourceManager::reserve_connector, +			     py::arg("name") = string()) +			.def("reserve_crtc", &ResourceManager::reserve_crtc) +			.def("reserve_plane", &ResourceManager::reserve_plane, +			     py::arg("crtc"), +			     py::arg("type"), +			     py::arg("format") = PixelFormat::Undefined) +			.def("reserve_primary_plane", &ResourceManager::reserve_primary_plane, +			     py::arg("crtc"), +			     py::arg("format") = PixelFormat::Undefined) +			.def("reserve_overlay_plane", &ResourceManager::reserve_overlay_plane, +			     py::arg("crtc"), +			     py::arg("format") = PixelFormat::Undefined) +			; +  	// Use lambdas to handle IMappedFramebuffer  	m.def("draw_test_pattern", [](DumbFramebuffer& fb) { draw_test_pattern(fb); } );  	m.def("draw_color_bar", [](DumbFramebuffer& fb, int old_xpos, int xpos, int width) { diff --git a/py/pyvid.cpp b/py/pyvid.cpp new file mode 100644 index 0000000..01177d5 --- /dev/null +++ b/py/pyvid.cpp @@ -0,0 +1,38 @@ +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> +#include <kms++util/videodevice.h> + +namespace py = pybind11; + +using namespace kms; +using namespace std; + +void init_pyvid(py::module &m) +{ +	py::class_<VideoDevice, VideoDevice*>(m, "VideoDevice") +			.def(py::init<const string&>()) +			.def_property_readonly("fd", &VideoDevice::fd) +			.def_property_readonly("has_capture", &VideoDevice::has_capture) +			.def_property_readonly("has_output", &VideoDevice::has_output) +			.def_property_readonly("has_m2m", &VideoDevice::has_m2m) +			.def_property_readonly("capture_streamer", &VideoDevice::get_capture_streamer) +			.def_property_readonly("output_streamer", &VideoDevice::get_output_streamer) +			.def_property_readonly("discrete_frame_sizes", &VideoDevice::get_discrete_frame_sizes) +			.def_property_readonly("frame_sizes", &VideoDevice::get_frame_sizes) +			.def("get_capture_devices", &VideoDevice::get_capture_devices) +			; + +	py::class_<VideoStreamer, VideoStreamer*>(m, "VideoStreamer") +			.def_property_readonly("fd", &VideoStreamer::fd) +			.def_property_readonly("ports", &VideoStreamer::get_ports) +			.def("set_port", &VideoStreamer::set_port) +			.def_property_readonly("formats", &VideoStreamer::get_formats) +			.def("set_format", &VideoStreamer::set_format) +			.def("set_queue_size", &VideoStreamer::set_queue_size) +			.def("queue", &VideoStreamer::queue) +			.def("dequeue", &VideoStreamer::dequeue) +			.def("stream_on", &VideoStreamer::stream_on) +			; +} @@ -4,16 +4,15 @@ import pykms  from helpers import *  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode()  fb = pykms.DumbFramebuffer(card, mode.hdisplay, mode.vdisplay, "XR24");  pykms.draw_test_pattern(fb); -crtc = get_crtc_for_connector(conn) -  crtc.set_mode(conn, fb, mode)  input("press enter to exit\n") diff --git a/py/trans-test.py b/py/trans-test.py index e80802b..8c1f964 100755 --- a/py/trans-test.py +++ b/py/trans-test.py @@ -9,10 +9,10 @@ card = pykms.Card()  card = 0  card = pykms.Card() - -conn = card.get_first_connected_connector() +res = pykms.ResourceManager(card) +conn = res.reserve_connector() +crtc = res.reserve_crtc(conn)  mode = conn.get_default_mode() -crtc = get_crtc_for_connector(conn)  planes = []  for p in card.planes: diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 27e4bec..dd95f70 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -18,3 +18,9 @@ target_link_libraries(kmscapture kms++ kms++util ${LIBDRM_LIBRARIES})  add_executable (kmsblank kmsblank.cpp)  target_link_libraries(kmsblank kms++ kms++util ${LIBDRM_LIBRARIES}) + +add_executable (wbcap wbcap.cpp) +target_link_libraries(wbcap kms++ kms++util ${LIBDRM_LIBRARIES}) + +add_executable (wbm2m wbm2m.cpp) +target_link_libraries(wbm2m kms++ kms++util ${LIBDRM_LIBRARIES}) diff --git a/utils/kmsview.cpp b/utils/kmsview.cpp index b503f0a..6f236a1 100644 --- a/utils/kmsview.cpp +++ b/utils/kmsview.cpp @@ -79,27 +79,14 @@ int main(int argc, char** argv)  	Card card(dev_path); +	ResourceManager res(card); -	auto conn = card.get_first_connected_connector(); -	auto crtc = conn->get_current_crtc(); - -	auto fb = new DumbFramebuffer(card, w, h, pixfmt); - -	Plane* plane = 0; - -	for (Plane* p : crtc->get_possible_planes()) { -		if (p->plane_type() != PlaneType::Overlay) -			continue; - -		if (!p->supports_format(pixfmt)) -			continue; - -		plane = p; -		break; -	} - +	auto conn = res.reserve_connector(); +	auto crtc = res.reserve_crtc(conn); +	auto plane = res.reserve_overlay_plane(crtc, pixfmt);  	FAIL_IF(!plane, "available plane not found"); +	auto fb = new DumbFramebuffer(card, w, h, pixfmt);  	unsigned frame_size = 0;  	for (unsigned i = 0; i < fb->num_planes(); ++i) diff --git a/utils/wbcap.cpp b/utils/wbcap.cpp new file mode 100644 index 0000000..f4f2b71 --- /dev/null +++ b/utils/wbcap.cpp @@ -0,0 +1,362 @@ +#include <cstdio> +#include <poll.h> +#include <unistd.h> +#include <algorithm> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> +#include <kms++util/videodevice.h> + +#define CAMERA_BUF_QUEUE_SIZE 5 + +using namespace std; +using namespace kms; + +static vector<DumbFramebuffer*> s_fbs; +static vector<DumbFramebuffer*> s_free_fbs; +static vector<DumbFramebuffer*> s_wb_fbs; +static vector<DumbFramebuffer*> s_ready_fbs; + +class WBStreamer +{ +public: +	WBStreamer(VideoStreamer* streamer, Crtc* crtc, uint32_t width, uint32_t height, PixelFormat pixfmt) +		: m_capdev(*streamer) +	{ +		m_capdev.set_port(crtc->idx()); +		m_capdev.set_format(pixfmt, width, height); +		m_capdev.set_queue_size(s_fbs.size()); + +		for (auto fb : s_free_fbs) { +			m_capdev.queue(fb); +			s_wb_fbs.push_back(fb); +		} + +		s_free_fbs.clear(); +	} + +	~WBStreamer() +	{ +	} + +	WBStreamer(const WBStreamer& other) = delete; +	WBStreamer& operator=(const WBStreamer& other) = delete; + +	int fd() const { return m_capdev.fd(); } + +	void start_streaming() +	{ +		m_capdev.stream_on(); +	} + +	void stop_streaming() +	{ +		m_capdev.stream_off(); +	} + +	void Dequeue() +	{ +		auto fb = m_capdev.dequeue(); + +		auto iter = find(s_wb_fbs.begin(), s_wb_fbs.end(), fb); +		s_wb_fbs.erase(iter); + +		s_ready_fbs.insert(s_ready_fbs.begin(), fb); +	} + +	void Queue() +	{ +		if (s_free_fbs.size() == 0) +			return; + +		auto fb = s_free_fbs.back(); +		s_free_fbs.pop_back(); + +		m_capdev.queue(fb); + +		s_wb_fbs.insert(s_wb_fbs.begin(), fb); +	} + +private: +	VideoStreamer& m_capdev; +}; + +class WBFlipState : private PageFlipHandlerBase +{ +public: +	WBFlipState(Card& card, Crtc* crtc, Plane* plane) +		: m_card(card), m_crtc(crtc), m_plane(plane) +	{ +	} + +	void setup(uint32_t x, uint32_t y, uint32_t width, uint32_t height) +	{ +		auto fb = s_ready_fbs.back(); +		s_ready_fbs.pop_back(); + +		AtomicReq req(m_card); + +		req.add(m_plane, "CRTC_ID", m_crtc->id()); +		req.add(m_plane, "FB_ID", fb->id()); + +		req.add(m_plane, "CRTC_X", x); +		req.add(m_plane, "CRTC_Y", y); +		req.add(m_plane, "CRTC_W", width); +		req.add(m_plane, "CRTC_H", height); + +		req.add(m_plane, "SRC_X", 0); +		req.add(m_plane, "SRC_Y", 0); +		req.add(m_plane, "SRC_W", fb->width() << 16); +		req.add(m_plane, "SRC_H", fb->height() << 16); + +		int r = req.commit_sync(); +		FAIL_IF(r, "initial plane setup failed"); + +		m_current_fb = fb; +	} + +	void queue_next() +	{ +		if (m_queued_fb) +			return; + +		if (s_ready_fbs.size() == 0) +			return; + +		auto fb = s_ready_fbs.back(); +		s_ready_fbs.pop_back(); + +		AtomicReq req(m_card); +		req.add(m_plane, "FB_ID", fb->id()); + +		int r = req.commit(this); +		if (r) +			EXIT("Flip commit failed: %d\n", r); + +		m_queued_fb = fb; +	} + +private: +	void handle_page_flip(uint32_t frame, double time) +	{ +		if (m_queued_fb) { +			if (m_current_fb) +				s_free_fbs.insert(s_free_fbs.begin(), m_current_fb); + +			m_current_fb = m_queued_fb; +			m_queued_fb = nullptr; +		} + +		queue_next(); +	} + +	Card& m_card; +	Crtc* m_crtc; +	Plane* m_plane; + +	DumbFramebuffer* m_current_fb = nullptr; +	DumbFramebuffer* m_queued_fb = nullptr; +}; + +class BarFlipState : private PageFlipHandlerBase +{ +public: +	BarFlipState(Card& card, Crtc* crtc) +		: m_card(card), m_crtc(crtc) +	{ +		m_plane = m_crtc->get_primary_plane(); + +		uint32_t w = m_crtc->mode().hdisplay; +		uint32_t h = m_crtc->mode().vdisplay; + +		for (unsigned i = 0; i < s_num_buffers; ++i) +			m_fbs[i] = new DumbFramebuffer(card, w, h, PixelFormat::XRGB8888); +	} + +	~BarFlipState() +	{ +		for (unsigned i = 0; i < s_num_buffers; ++i) +			delete m_fbs[i]; +	} + +	void start_flipping() +	{ +		m_frame_num = 0; +		queue_next(); +	} + +private: +	void handle_page_flip(uint32_t frame, double time) +	{ +		m_frame_num++; +		queue_next(); +	} + +	static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num) +	{ +		return (frame_num * bar_speed) % (fb->width() - bar_width + 1); +	} + +	void draw_bar(DumbFramebuffer* fb, unsigned frame_num) +	{ +		int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers); +		int new_xpos = get_bar_pos(fb, frame_num); + +		draw_color_bar(*fb, old_xpos, new_xpos, bar_width); +		draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255)); +	} + +	void queue_next() +	{ +		AtomicReq req(m_card); + +		unsigned cur = m_frame_num % s_num_buffers; + +		auto fb = m_fbs[cur]; + +		draw_bar(fb, m_frame_num); + +		req.add(m_plane, { +				{ "FB_ID", fb->id() }, +			}); + +		int r = req.commit(this); +		if (r) +			EXIT("Flip commit failed: %d\n", r); +	} + +	static const unsigned s_num_buffers = 3; + +	DumbFramebuffer* m_fbs[s_num_buffers]; + +	Card& m_card; +	Crtc* m_crtc; +	Plane* m_plane; + +	unsigned m_frame_num; + +	static const unsigned bar_width = 20; +	static const unsigned bar_speed = 8; +}; + +static const char* usage_str = +		"Usage: wbcap [OPTIONS]\n\n" +		"Options:\n" +		"  -s, --src=CONN            Source connector\n" +		"  -d, --dst=CONN            Destination connector\n" +		"  -f, --format=4CC          Format" +		"  -h, --help                Print this help\n" +		; + +int main(int argc, char** argv) +{ +	string src_conn_name = "unknown"; +	string dst_conn_name = "hdmi"; +	PixelFormat pixfmt = PixelFormat::XRGB8888; + +	OptionSet optionset = { +		Option("s|src=", [&](string s) +		{ +			src_conn_name = s; +		}), +		Option("d|dst=", [&](string s) +		{ +			dst_conn_name = s; +		}), +		Option("f|format=", [&](string s) +		{ +			pixfmt = FourCCToPixelFormat(s); +		}), +		Option("h|help", [&]() +		{ +			puts(usage_str); +			exit(-1); +		}), +	}; + +	optionset.parse(argc, argv); + +	if (optionset.params().size() > 0) { +		puts(usage_str); +		exit(-1); +	} + +	VideoDevice vid("/dev/video11"); + +	Card card; +	ResourceManager resman(card); + +	auto src_conn = resman.reserve_connector(src_conn_name); +	auto src_crtc = resman.reserve_crtc(src_conn); + +	uint32_t src_width = src_crtc->mode().hdisplay; +	uint32_t src_height = src_crtc->mode().vdisplay; + +	printf("src %s, crtc %ux%u\n", src_conn->fullname().c_str(), src_width, src_height); + +	auto dst_conn = resman.reserve_connector(dst_conn_name); +	auto dst_crtc = resman.reserve_crtc(dst_conn); +	auto dst_plane = resman.reserve_overlay_plane(dst_crtc, pixfmt); +	FAIL_IF(!dst_plane, "Plane not found"); + +	uint32_t dst_width = min((uint32_t)dst_crtc->mode().hdisplay, src_width); +	uint32_t dst_height = min((uint32_t)dst_crtc->mode().vdisplay, src_height); + +	printf("dst %s, crtc %ux%u, plane %ux%u\n", dst_conn->fullname().c_str(), +	       dst_crtc->mode().hdisplay, dst_crtc->mode().vdisplay, +	       dst_width, dst_height); + +	for (int i = 0; i < CAMERA_BUF_QUEUE_SIZE; ++i) { +		auto fb = new DumbFramebuffer(card, src_width, src_height, pixfmt); +		s_fbs.push_back(fb); +		s_free_fbs.push_back(fb); +	} + +	// get one fb for initial setup +	s_ready_fbs.push_back(s_free_fbs.back()); +	s_free_fbs.pop_back(); + +	// This draws a moving bar to SRC display +	BarFlipState barflipper(card, src_crtc); +	barflipper.start_flipping(); + +	// This shows the captures SRC frames on DST display +	WBFlipState wbflipper(card, dst_crtc, dst_plane); +	wbflipper.setup(0, 0, dst_width, dst_height); + +	WBStreamer wb(vid.get_capture_streamer(), src_crtc, src_width, src_height, pixfmt); +	wb.start_streaming(); + +	vector<pollfd> fds(3); + +	fds[0].fd = 0; +	fds[0].events =  POLLIN; +	fds[1].fd = wb.fd(); +	fds[1].events =  POLLIN; +	fds[2].fd = card.fd(); +	fds[2].events =  POLLIN; + +	while (true) { +		int r = poll(fds.data(), fds.size(), -1); +		ASSERT(r > 0); + +		if (fds[0].revents != 0) +			break; + +		if (fds[1].revents) { +			fds[1].revents = 0; + +			wb.Dequeue(); +			wbflipper.queue_next(); +		} + +		if (fds[2].revents) { +			fds[2].revents = 0; + +			card.call_page_flip_handlers(); +			wb.Queue(); +		} +	} + +	printf("exiting...\n"); +} diff --git a/utils/wbm2m.cpp b/utils/wbm2m.cpp new file mode 100644 index 0000000..1f8dffa --- /dev/null +++ b/utils/wbm2m.cpp @@ -0,0 +1,168 @@ +#include <cstdio> +#include <poll.h> +#include <unistd.h> +#include <algorithm> +#include <fstream> +#include <map> + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> +#include <kms++util/videodevice.h> + +const uint32_t NUM_SRC_BUFS=2; +const uint32_t NUM_DST_BUFS=2; + +using namespace std; +using namespace kms; + +static const char* usage_str = +		"Usage: wbm2m [OPTIONS]\n\n" +		"Options:\n" +		"  -f, --format=4CC          Output format" +		"  -h, --help                Print this help\n" +		; + +const int bar_speed = 4; +const int bar_width = 10; + +static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num) +{ +	return (frame_num * bar_speed) % (fb->width() - bar_width + 1); +} + +static void read_frame(DumbFramebuffer* fb, unsigned frame_num) +{ +	static map<DumbFramebuffer*, int> s_bar_pos_map; + +	int old_pos = -1; +	if (s_bar_pos_map.find(fb) != s_bar_pos_map.end()) +		old_pos = s_bar_pos_map[fb]; + +	int pos = get_bar_pos(fb, frame_num); +	draw_color_bar(*fb, old_pos, pos, bar_width); +	draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255)); +	s_bar_pos_map[fb] = pos; +} + +int main(int argc, char** argv) +{ +	// XXX get from args +	const uint32_t src_width = 800; +	const uint32_t src_height = 480; +	const auto src_fmt = PixelFormat::XRGB8888; +	const uint32_t num_src_frames = 10; + +	const uint32_t dst_width = 800; +	const uint32_t dst_height = 480; +	auto dst_fmt = PixelFormat::XRGB8888; + +	const string filename = "wb-out.raw"; + +	OptionSet optionset = { +		Option("f|format=", [&](string s) +		{ +			dst_fmt = FourCCToPixelFormat(s); +		}), +		Option("h|help", [&]() +		{ +			puts(usage_str); +			exit(-1); +		}), +	}; + +	optionset.parse(argc, argv); + +	if (optionset.params().size() > 0) { +		puts(usage_str); +		exit(-1); +	} + +	VideoDevice vid("/dev/video10"); + +	Card card; + +	uint32_t src_frame_num = 0; +	uint32_t dst_frame_num = 0; + +	VideoStreamer* out = vid.get_output_streamer(); +	VideoStreamer* in = vid.get_capture_streamer(); + +	out->set_format(src_fmt, src_width, src_height); +	in->set_format(dst_fmt, dst_width, dst_height); + +	out->set_queue_size(NUM_SRC_BUFS); +	in->set_queue_size(NUM_DST_BUFS); + + +	for (unsigned i = 0; i < min(NUM_SRC_BUFS, num_src_frames); ++i) { +		auto fb = new DumbFramebuffer(card, src_width, src_height, src_fmt); + +		read_frame(fb, src_frame_num++); + +		out->queue(fb); +	} + +	for (unsigned i = 0; i < min(NUM_DST_BUFS, num_src_frames); ++i) { +		auto fb = new DumbFramebuffer(card, dst_width, dst_height, dst_fmt); +		in->queue(fb); +	} + +	vector<pollfd> fds(3); + +	fds[0].fd = 0; +	fds[0].events =  POLLIN; +	fds[1].fd = vid.fd(); +	fds[1].events =  POLLIN; +	fds[2].fd = card.fd(); +	fds[2].events =  POLLIN; + +	ofstream os(filename, ofstream::binary); + +	out->stream_on(); +	in->stream_on(); + +	while (true) { +		int r = poll(fds.data(), fds.size(), -1); +		ASSERT(r > 0); + +		if (fds[0].revents != 0) +			break; + +		if (fds[1].revents) { +			fds[1].revents = 0; + + +			try { +				DumbFramebuffer *dst_fb = in->dequeue(); +				printf("Writing frame %u\n", dst_frame_num); +				for (unsigned i = 0; i < dst_fb->num_planes(); ++i) +					os.write((char*)dst_fb->map(i), dst_fb->size(i)); +				in->queue(dst_fb); + +				dst_frame_num++; + +				if (dst_frame_num >= num_src_frames) +					break; + +			} catch (system_error& se) { +				if (se.code() != errc::resource_unavailable_try_again) +					FAIL("dequeue failed: %s", se.what()); + +				break; +			} + +			DumbFramebuffer *src_fb = out->dequeue(); + +			if (src_frame_num < num_src_frames) { +				read_frame(src_fb, src_frame_num++); +				out->queue(src_fb); +			} +		} + +		if (fds[2].revents) { +			fds[2].revents = 0; +		} +	} + +	printf("exiting...\n"); +} | 
