Merge V4L2 related work
authorTomi Valkeinen <tomi.valkeinen@ti.com>
Mon, 20 Jun 2016 05:53:36 +0000 (08:53 +0300)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Mon, 20 Jun 2016 05:53:36 +0000 (08:53 +0300)
22 files changed:
kms++util/inc/kms++util/kms++util.h
kms++util/inc/kms++util/resourcemanager.h [new file with mode: 0644]
kms++util/inc/kms++util/videodevice.h [new file with mode: 0644]
kms++util/src/resourcemanager.cpp [new file with mode: 0644]
kms++util/src/videodevice.cpp [new file with mode: 0644]
py/CMakeLists.txt
py/alpha-test.py
py/cam.py [new file with mode: 0755]
py/db.py
py/functest.py
py/gamma.py
py/helpers.py
py/iact.py
py/pykms.cpp
py/pykmsutil.cpp
py/pyvid.cpp [new file with mode: 0644]
py/test.py
py/trans-test.py
utils/CMakeLists.txt
utils/kmsview.cpp
utils/wbcap.cpp [new file with mode: 0644]
utils/wbm2m.cpp [new file with mode: 0644]

index ca3c4069f855b34990ff987e56f45beb80f0a4b2..10a1f0af5a13badb0e27db55abe946d23dcc17a3 100644 (file)
@@ -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 (file)
index 0000000..92e7b93
--- /dev/null
@@ -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 (file)
index 0000000..68e2b01
--- /dev/null
@@ -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 (file)
index 0000000..cdd3e40
--- /dev/null
@@ -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 (file)
index 0000000..8cc18dc
--- /dev/null
@@ -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);
+}
index 562a3cff4572225a298d996d54a44b5ddc86c312..1349ea57200341c107183385f0e48cc0d27692c6 100644 (file)
@@ -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
index 113fab0ad73cdd597e55cc986280cd8a273b97d4..c6ec8ee0eae8f4e5476992bf62e44d650b97ac44 100755 (executable)
@@ -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 (executable)
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)
index 60737650a95b9096f57fa3cf0e4711673dbf50ac..3ffb716b02792270fdfadef3d3138629afdc9d16 100755 (executable)
--- 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()
 
index c2548fa044f45defbed698a81c586a20e93fe33d..44c29fb9a18ce778317bf55688ee2bb1371583e8 100755 (executable)
@@ -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")
index e1daa43929beef7c745c5a01491a0a1768adfa59..a6b68cccd2af82b45ad560407f1cb0b938a7706e 100755 (executable)
@@ -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);
index e92163ceaac9c92015a8300da0ef859a0542d0fe..fd67d4160cb9214d77c9a8e43db0a392b0f879d2 100644 (file)
@@ -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")
index 518dbfa18ca407dfd99d4513c6a827fd24ec5e3d..fecd899f056e58ac8b53dad5bc7c497c8b719efd 100755 (executable)
@@ -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
index 57ca363c5fc274c213790b63ace94ded2edd2114..c759d236cc966f5281436705fc886519700a095b 100644 (file)
@@ -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();
 }
index 5ee1d4e7445311bb01dd4d7b18303de666583ce9..ab9f5a8d0be8e58cabdc4860f2255509a3971563 100644 (file)
@@ -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 (file)
index 0000000..01177d5
--- /dev/null
@@ -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)
+                       ;
+}
index 7625f101213e10aa3e07f9605464638eeaf239b1..9c23b5b4b92950667ba69456549ed199ca0231c2 100755 (executable)
@@ -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")
index e80802bbd80794f2d76b091b3f75bbd4e95b3ac8..8c1f9640ff7766ebf16b3016349d220bd5f7cbf6 100755 (executable)
@@ -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:
index 27e4beccb23b5a3e7d16e0b621d6884099ad4ca8..dd95f70a345a8dcbba6f6cebe8b33ab92540871f 100644 (file)
@@ -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})
index b503f0a0ecfc6fe91ae9533712993fda07952502..6f236a11254e446bc20a5ceeffccf83bcaf9dc1b 100644 (file)
@@ -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 (file)
index 0000000..f4f2b71
--- /dev/null
@@ -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 (file)
index 0000000..1f8dffa
--- /dev/null
@@ -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");
+}