summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kms++util/inc/kms++util/kms++util.h1
-rw-r--r--kms++util/inc/kms++util/resourcemanager.h27
-rw-r--r--kms++util/inc/kms++util/videodevice.h86
-rw-r--r--kms++util/src/resourcemanager.cpp147
-rw-r--r--kms++util/src/videodevice.cpp460
-rw-r--r--py/CMakeLists.txt2
-rwxr-xr-xpy/alpha-test.py6
-rwxr-xr-xpy/cam.py78
-rwxr-xr-xpy/db.py5
-rwxr-xr-xpy/functest.py7
-rwxr-xr-xpy/gamma.py6
-rw-r--r--py/helpers.py12
-rwxr-xr-xpy/iact.py7
-rw-r--r--py/pykms.cpp3
-rw-r--r--py/pykmsutil.cpp18
-rw-r--r--py/pyvid.cpp38
-rwxr-xr-xpy/test.py7
-rwxr-xr-xpy/trans-test.py6
-rw-r--r--utils/CMakeLists.txt6
-rw-r--r--utils/kmsview.cpp23
-rw-r--r--utils/wbcap.cpp362
-rw-r--r--utils/wbm2m.cpp168
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)
diff --git a/py/db.py b/py/db.py
index 6073765..3ffb716 100755
--- a/py/db.py
+++ b/py/db.py
@@ -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")
diff --git a/py/iact.py b/py/iact.py
index 518dbfa..fecd899 100755
--- a/py/iact.py
+++ b/py/iact.py
@@ -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)
+ ;
+}
diff --git a/py/test.py b/py/test.py
index 7625f10..9c23b5b 100755
--- a/py/test.py
+++ b/py/test.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)
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");
+}