diff options
Diffstat (limited to 'kms++util')
| -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 | 
5 files changed, 721 insertions, 0 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); +} | 
