From 2b1a8f48f3a414e565cefb809f3e6a7c6aa5f8a7 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Wed, 6 Oct 2021 10:26:00 +0300 Subject: Split V4L2 code into separate libs Create v4l2++ library and pyv4l2, which are independent from the rest of the kms++. Signed-off-by: Tomi Valkeinen --- kms++util/inc/kms++util/videodevice.h | 88 ----- kms++util/meson.build | 2 - kms++util/src/videodevice.cpp | 552 ---------------------------- meson.build | 4 + meson_options.txt | 6 + py/meson.build | 4 + py/pykms/meson.build | 1 - py/pykms/pykms.cpp | 3 - py/pykms/pyvid.cpp | 46 --- py/pyv4l2/__init__.py | 5 + py/pyv4l2/meson.build | 38 ++ py/pyv4l2/pyv4l2.cpp | 153 ++++++++ utils/meson.build | 6 +- utils/omap-wbcap.cpp | 29 +- utils/omap-wbm2m.cpp | 50 ++- v4l2++/inc/v4l2++/helpers.h | 60 ++++ v4l2++/inc/v4l2++/pixelformats.h | 111 ++++++ v4l2++/inc/v4l2++/videodevice.h | 127 +++++++ v4l2++/meson.build | 32 ++ v4l2++/src/helpers.cpp | 16 + v4l2++/src/pixelformats.cpp | 301 ++++++++++++++++ v4l2++/src/videodevice.cpp | 653 ++++++++++++++++++++++++++++++++++ 22 files changed, 1569 insertions(+), 718 deletions(-) delete mode 100644 kms++util/inc/kms++util/videodevice.h delete mode 100644 kms++util/src/videodevice.cpp delete mode 100644 py/pykms/pyvid.cpp create mode 100644 py/pyv4l2/__init__.py create mode 100644 py/pyv4l2/meson.build create mode 100644 py/pyv4l2/pyv4l2.cpp create mode 100644 v4l2++/inc/v4l2++/helpers.h create mode 100644 v4l2++/inc/v4l2++/pixelformats.h create mode 100644 v4l2++/inc/v4l2++/videodevice.h create mode 100644 v4l2++/meson.build create mode 100644 v4l2++/src/helpers.cpp create mode 100644 v4l2++/src/pixelformats.cpp create mode 100644 v4l2++/src/videodevice.cpp diff --git a/kms++util/inc/kms++util/videodevice.h b/kms++util/inc/kms++util/videodevice.h deleted file mode 100644 index 3bce4a9..0000000 --- a/kms++util/inc/kms++util/videodevice.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -#include -#include -#include - -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> 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 get_capture_devices(); - static std::vector 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 m_capture_fbs; - std::vector m_output_fbs; - - std::unique_ptr m_capture_streamer; - std::unique_ptr m_output_streamer; -}; - -class VideoStreamer -{ -public: - enum class StreamerType { - CaptureSingle, - CaptureMulti, - OutputSingle, - OutputMulti, - }; - - VideoStreamer(int fd, StreamerType type); - - std::vector get_ports(); - void set_port(uint32_t index); - - std::vector get_formats(); - void set_format(kms::PixelFormat fmt, uint32_t width, uint32_t height); - void get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height); - void set_selection(uint32_t& left, uint32_t& top, 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 m_fbs; -}; diff --git a/kms++util/meson.build b/kms++util/meson.build index 9df75e1..1b226f3 100644 --- a/kms++util/meson.build +++ b/kms++util/meson.build @@ -8,7 +8,6 @@ libkmsxxutil_sources = files([ 'src/resourcemanager.cpp', 'src/strhelpers.cpp', 'src/testpat.cpp', - 'src/videodevice.cpp', ]) public_headers = [ @@ -20,7 +19,6 @@ public_headers = [ 'inc/kms++util/opts.h', 'inc/kms++util/extcpuframebuffer.h', 'inc/kms++util/resourcemanager.h', - 'inc/kms++util/videodevice.h', ] private_includes = include_directories('src', 'inc') diff --git a/kms++util/src/videodevice.cpp b/kms++util/src/videodevice.cpp deleted file mode 100644 index 9530d60..0000000 --- a/kms++util/src/videodevice.cpp +++ /dev/null @@ -1,552 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -using namespace std; -using namespace kms; - -/* - * V4L2 and DRM differ in their interpretation of YUV420::NV12 - * - * V4L2 NV12 is a Y and UV co-located planes in a single plane buffer. - * DRM NV12 is a Y and UV planes presented as dual plane buffer, - * which is known as NM12 in V4L2. - * - * Since here we have hybrid DRM/V4L2 user space helper functions - * we need to translate DRM::NV12 to V4L2:NM12 pixel format back - * and forth to keep the data view consistent. - */ - -/* V4L2 helper funcs */ -static vector v4l2_get_formats(int fd, uint32_t buf_type) -{ - vector v; - - v4l2_fmtdesc desc{}; - desc.type = buf_type; - - while (ioctl(fd, VIDIOC_ENUM_FMT, &desc) == 0) { - if (desc.pixelformat == V4L2_PIX_FMT_NV12M) - v.push_back(PixelFormat::NV12); - else if (desc.pixelformat != V4L2_PIX_FMT_NV12) - 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; - uint32_t used_fmt; - - if (fmt == PixelFormat::NV12) - used_fmt = V4L2_PIX_FMT_NV12M; - else - used_fmt = (uint32_t)fmt; - - mp.pixelformat = used_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 == used_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_get_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type) -{ - int r; - struct v4l2_selection selection; - - if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT || - buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - selection.target = V4L2_SEL_TGT_CROP; - } else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE || - buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - selection.target = V4L2_SEL_TGT_COMPOSE; - } else { - FAIL("buf_type (%d) is not valid\n", buf_type); - } - - r = ioctl(fd, VIDIOC_G_SELECTION, &selection); - ASSERT(r == 0); - - left = selection.r.left; - top = selection.r.top; - width = selection.r.width; - height = selection.r.height; -} - -static void v4l2_set_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type) -{ - int r; - struct v4l2_selection selection; - - if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT || - buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { - selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; - selection.target = V4L2_SEL_TGT_CROP; - } else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE || - buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { - selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - selection.target = V4L2_SEL_TGT_COMPOSE; - } else { - FAIL("buf_type (%d) is not valid\n", buf_type); - } - - selection.r.left = left; - selection.r.top = top; - selection.r.width = width; - selection.r.height = height; - - r = ioctl(fd, VIDIOC_S_SELECTION, &selection); - ASSERT(r == 0); - - left = selection.r.left; - top = selection.r.top; - width = selection.r.width; - height = selection.r.height; -} - -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) -{ - if (fd < 0) - throw runtime_error("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 = std::unique_ptr(new VideoStreamer(m_fd, type)); - } - - return m_capture_streamer.get(); -} - -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 = std::unique_ptr(new VideoStreamer(m_fd, type)); - } - - return m_output_streamer.get(); -} - -vector> VideoDevice::get_discrete_frame_sizes(PixelFormat fmt) -{ - vector> 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 VideoDevice::get_capture_devices() -{ - vector 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; - - try { - VideoDevice vid(name); - - if (vid.has_capture() && !vid.has_m2m()) - v.push_back(name); - } catch (...) { - } - } - - return v; -} - -vector VideoDevice::get_m2m_devices() -{ - vector 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; - - try { - VideoDevice vid(name); - - if (vid.has_m2m()) - v.push_back(name); - } catch (...) { - } - } - - return v; -} - -VideoStreamer::VideoStreamer(int fd, StreamerType type) - : m_fd(fd), m_type(type) -{ -} - -std::vector VideoStreamer::get_ports() -{ - vector 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 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::get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height) -{ - v4l2_get_selection(m_fd, left, top, width, height, get_buf_type(m_type)); -} - -void VideoStreamer::set_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height) -{ - v4l2_set_selection(m_fd, left, top, 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/meson.build b/meson.build index 1325a3f..3de7b6a 100644 --- a/meson.build +++ b/meson.build @@ -38,6 +38,10 @@ libdrmomap_dep = dependency('libdrm_omap', required : get_option('omap')) subdir('kms++') +if get_option('v4l2').enabled() + subdir('v4l2++') +endif + if get_option('libutils') subdir('kms++util') endif diff --git a/meson_options.txt b/meson_options.txt index 979753e..9e17c24 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,5 +13,11 @@ option('utils', type : 'boolean', value : true, option('pykms', type : 'feature', value : 'auto', description : 'Build python bindings') +option('v4l2', type : 'feature', value : 'disabled', + description : 'Build V4L2++ library (HACK)') + +option('pyv4l2', type : 'feature', value : 'disabled', + description : 'Build V4L2 python bindings (HACK)') + option('kmscube', type : 'boolean', value : false, description : 'Build kmscube test application') diff --git a/py/meson.build b/py/meson.build index 3130bf2..fcba3e5 100644 --- a/py/meson.build +++ b/py/meson.build @@ -1 +1,5 @@ subdir('pykms') + +if get_option('v4l2').enabled() + subdir('pyv4l2') +endif diff --git a/py/pykms/meson.build b/py/pykms/meson.build index e030ce8..db5cbd0 100644 --- a/py/pykms/meson.build +++ b/py/pykms/meson.build @@ -19,7 +19,6 @@ pykms_sources = files([ if get_option('utils') pykms_sources += files([ 'pykmsutil.cpp', - 'pyvid.cpp', ]) endif diff --git a/py/pykms/pykms.cpp b/py/pykms/pykms.cpp index b91a1a9..e9266a4 100644 --- a/py/pykms/pykms.cpp +++ b/py/pykms/pykms.cpp @@ -9,7 +9,6 @@ using namespace std; void init_pykmstest(py::module& m); void init_pykmsbase(py::module& m); -void init_pyvid(py::module& m); #if HAS_LIBDRM_OMAP void init_pykmsomap(py::module& m); @@ -21,8 +20,6 @@ PYBIND11_MODULE(pykms, m) init_pykmstest(m); - init_pyvid(m); - #if HAS_LIBDRM_OMAP init_pykmsomap(m); #endif diff --git a/py/pykms/pyvid.cpp b/py/pykms/pyvid.cpp deleted file mode 100644 index 54ad480..0000000 --- a/py/pykms/pyvid.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include -#include -#include - -namespace py = pybind11; - -using namespace kms; -using namespace std; - -void init_pyvid(py::module& m) -{ - py::class_(m, "VideoDevice") - .def(py::init()) - .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_(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("get_selection", [](VideoStreamer* self) { - uint32_t left, top, width, height; - self->get_selection(left, top, width, height); - return make_tuple(left, top, width, height); - }) - .def("set_selection", [](VideoStreamer* self, uint32_t left, uint32_t top, uint32_t width, uint32_t height) { - self->set_selection(left, top, width, height); - return make_tuple(left, top, width, height); - }) - .def("set_queue_size", &VideoStreamer::set_queue_size) - .def("queue", &VideoStreamer::queue) - .def("dequeue", &VideoStreamer::dequeue) - .def("stream_on", &VideoStreamer::stream_on) - .def("stream_off", &VideoStreamer::stream_off); -} diff --git a/py/pyv4l2/__init__.py b/py/pyv4l2/__init__.py new file mode 100644 index 0000000..02541c5 --- /dev/null +++ b/py/pyv4l2/__init__.py @@ -0,0 +1,5 @@ +from .pyv4l2 import * +from enum import Enum +import os +import struct + diff --git a/py/pyv4l2/meson.build b/py/pyv4l2/meson.build new file mode 100644 index 0000000..03b0dcc --- /dev/null +++ b/py/pyv4l2/meson.build @@ -0,0 +1,38 @@ +py3_dep = dependency('python3', required : get_option('pyv4l2')) + +if py3_dep.found() == false + subdir_done() +endif + +pybind11_dep = dependency('pybind11', fallback : ['pybind11', 'pybind11_dep'], + required : get_option('pyv4l2')) + +if pybind11_dep.found() == false + subdir_done() +endif + +pyv4l2_sources = files([ + 'pyv4l2.cpp', +]) + +pyv4l2_deps = [ + libv4l2xx_dep, + py3_dep, + pybind11_dep, +] + +pyv4l2_args = [ '-fvisibility=hidden' ] + +destdir = get_option('libdir') / 'python' + py3_dep.version() / 'site-packages/pyv4l2' + +pyv4l2 = shared_module('pyv4l2', + pyv4l2_sources, + install : true, + install_dir : destdir, + name_prefix : '', + dependencies : pyv4l2_deps, + cpp_args : pyv4l2_args) + +# Copy __init__.py to build dir so that we can run without installing +configure_file(input: '__init__.py', output: '__init__.py', copy: true, + install : true, install_dir : destdir) diff --git a/py/pyv4l2/pyv4l2.cpp b/py/pyv4l2/pyv4l2.cpp new file mode 100644 index 0000000..ec96835 --- /dev/null +++ b/py/pyv4l2/pyv4l2.cpp @@ -0,0 +1,153 @@ +#include +#include +#include +#include + +namespace py = pybind11; + +using namespace v4l2; +using namespace std; + +PYBIND11_MODULE(pyv4l2, m) +{ + py::class_(m, "VideoDevice") + .def(py::init()) + .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("meta_capture_streamer", &VideoDevice::get_meta_capture_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::enum_(m, "VideoMemoryType") + .value("MMAP", VideoMemoryType::MMAP) + .value("DMABUF", VideoMemoryType::DMABUF) + ; + + m.def("create_dmabuffer", [](int fd) { + VideoBuffer buf {}; + buf.m_mem_type = VideoMemoryType::DMABUF; + buf.m_fd = fd; + return buf; + }); + + m.def("create_mmapbuffer", []() { + VideoBuffer buf {}; + buf.m_mem_type = VideoMemoryType::MMAP; + return buf; + }); + + py::class_(m, "VideoBuffer") + .def_readonly("index", &VideoBuffer::m_index) + .def_readonly("offset", &VideoBuffer::m_offset) + .def_readonly("fd", &VideoBuffer::m_fd) + .def_readonly("length", &VideoBuffer::m_length) + ; + + py::class_(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("get_format", [](VideoStreamer* self) { + PixelFormat fmt; + uint32_t w, h; + + int r = self->get_format(fmt, w, h); + if (r) + throw std::system_error(errno, std::generic_category(), "get_format failed"); + + return make_tuple(w, h, fmt); + }) + .def("set_format", &VideoStreamer::set_format) + .def("get_selection", [](VideoStreamer* self) { + uint32_t left, top, width, height; + self->get_selection(left, top, width, height); + return make_tuple(left, top, width, height); + }) + .def("set_selection", [](VideoStreamer* self, uint32_t left, uint32_t top, uint32_t width, uint32_t height) { + self->set_selection(left, top, width, height); + return make_tuple(left, top, width, height); + }) + .def("set_queue_size", &VideoStreamer::set_queue_size) + .def("queue", &VideoStreamer::queue) + .def("dequeue", &VideoStreamer::dequeue) + .def("stream_on", &VideoStreamer::stream_on) + .def("stream_off", &VideoStreamer::stream_off); + + py::class_(m, "MetaStreamer") + .def_property_readonly("fd", &MetaStreamer::fd) + .def("set_format", &MetaStreamer::set_format) + .def("set_queue_size", &MetaStreamer::set_queue_size) + .def("queue", &MetaStreamer::queue) + .def("dequeue", &MetaStreamer::dequeue) + .def("stream_on", &MetaStreamer::stream_on) + .def("stream_off", &MetaStreamer::stream_off); + + py::enum_(m, "PixelFormat") + .value("Undefined", PixelFormat::Undefined) + + .value("NV12", PixelFormat::NV12) + .value("NV21", PixelFormat::NV21) + .value("NV16", PixelFormat::NV16) + .value("NV61", PixelFormat::NV61) + + .value("YUV420", PixelFormat::YUV420) + .value("YVU420", PixelFormat::YVU420) + .value("YUV422", PixelFormat::YUV422) + .value("YVU422", PixelFormat::YVU422) + .value("YUV444", PixelFormat::YUV444) + .value("YVU444", PixelFormat::YVU444) + + .value("UYVY", PixelFormat::UYVY) + .value("YUYV", PixelFormat::YUYV) + .value("YVYU", PixelFormat::YVYU) + .value("VYUY", PixelFormat::VYUY) + + .value("XRGB8888", PixelFormat::XRGB8888) + .value("XBGR8888", PixelFormat::XBGR8888) + .value("RGBX8888", PixelFormat::RGBX8888) + .value("BGRX8888", PixelFormat::BGRX8888) + + .value("ARGB8888", PixelFormat::ARGB8888) + .value("ABGR8888", PixelFormat::ABGR8888) + .value("RGBA8888", PixelFormat::RGBA8888) + .value("BGRA8888", PixelFormat::BGRA8888) + + .value("RGB888", PixelFormat::RGB888) + .value("BGR888", PixelFormat::BGR888) + + .value("RGB332", PixelFormat::RGB332) + + .value("RGB565", PixelFormat::RGB565) + .value("BGR565", PixelFormat::BGR565) + + .value("XRGB4444", PixelFormat::XRGB4444) + .value("XRGB1555", PixelFormat::XRGB1555) + + .value("ARGB4444", PixelFormat::ARGB4444) + .value("ARGB1555", PixelFormat::ARGB1555) + + .value("XRGB2101010", PixelFormat::XRGB2101010) + .value("XBGR2101010", PixelFormat::XBGR2101010) + .value("RGBX1010102", PixelFormat::RGBX1010102) + .value("BGRX1010102", PixelFormat::BGRX1010102) + + .value("ARGB2101010", PixelFormat::ARGB2101010) + .value("ABGR2101010", PixelFormat::ABGR2101010) + .value("RGBA1010102", PixelFormat::RGBA1010102) + .value("BGRA1010102", PixelFormat::BGRA1010102) + + .value("SBGGR12", PixelFormat::SBGGR12) + .value("SRGGB12", PixelFormat::SRGGB12) + + .value("META_8", PixelFormat::META_8) + .value("META_16", PixelFormat::META_16); + + + m.def("fourcc_to_pixelformat", &FourCCToPixelFormat); +} diff --git a/utils/meson.build b/utils/meson.build index b1e7918..ac73b5b 100644 --- a/utils/meson.build +++ b/utils/meson.build @@ -13,5 +13,7 @@ if libevdev_dep.found() executable('kmstouch', 'kmstouch.cpp', dependencies : [ common_deps, libevdev_dep ], install : false) endif -executable('omap-wbcap', 'omap-wbcap.cpp', dependencies : [ common_deps ], install : false) -executable('omap-wbm2m', 'omap-wbm2m.cpp', dependencies : [ common_deps ], install : false) +if get_option('v4l2').enabled() + executable('omap-wbcap', 'omap-wbcap.cpp', dependencies : [ common_deps, libv4l2xx_dep ], install : false) + executable('omap-wbm2m', 'omap-wbm2m.cpp', dependencies : [ common_deps, libv4l2xx_dep ], install : false) +endif diff --git a/utils/omap-wbcap.cpp b/utils/omap-wbcap.cpp index 8033869..5080159 100644 --- a/utils/omap-wbcap.cpp +++ b/utils/omap-wbcap.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #define CAMERA_BUF_QUEUE_SIZE 5 @@ -21,17 +21,21 @@ static vector s_ready_fbs; class WBStreamer { public: - WBStreamer(VideoStreamer* streamer, Crtc* crtc, PixelFormat pixfmt) + WBStreamer(v4l2::VideoStreamer* streamer, Crtc* crtc, PixelFormat pixfmt) : m_capdev(*streamer) { Videomode m = crtc->mode(); m_capdev.set_port(crtc->idx()); - m_capdev.set_format(pixfmt, m.hdisplay, m.vdisplay / (m.interlace() ? 2 : 1)); - m_capdev.set_queue_size(s_fbs.size()); + m_capdev.set_format((v4l2::PixelFormat)pixfmt, m.hdisplay, m.vdisplay / (m.interlace() ? 2 : 1)); + m_capdev.set_queue_size(s_fbs.size(), v4l2::VideoMemoryType::DMABUF); for (auto fb : s_free_fbs) { - m_capdev.queue(fb); + v4l2::VideoBuffer vbuf {}; + vbuf.m_mem_type = v4l2::VideoMemoryType::DMABUF; + vbuf.m_fd = fb->prime_fd(0); + + m_capdev.queue(vbuf); s_wb_fbs.push_back(fb); } @@ -59,9 +63,10 @@ public: DumbFramebuffer* Dequeue() { - auto fb = m_capdev.dequeue(); + auto vbuf = m_capdev.dequeue(); - auto iter = find(s_wb_fbs.begin(), s_wb_fbs.end(), fb); + auto iter = find_if(s_wb_fbs.begin(), s_wb_fbs.end(), [fd=vbuf.m_fd](auto& fb) { return fb->prime_fd(0) == fd; }); + auto fb = *iter; s_wb_fbs.erase(iter); s_ready_fbs.insert(s_ready_fbs.begin(), fb); @@ -77,13 +82,17 @@ public: auto fb = s_free_fbs.back(); s_free_fbs.pop_back(); - m_capdev.queue(fb); + v4l2::VideoBuffer vbuf {}; + vbuf.m_mem_type = v4l2::VideoMemoryType::DMABUF; + vbuf.m_fd = fb->prime_fd(0); + + m_capdev.queue(vbuf); s_wb_fbs.insert(s_wb_fbs.begin(), fb); } private: - VideoStreamer& m_capdev; + v4l2::VideoStreamer& m_capdev; }; class WBFlipState : private PageFlipHandlerBase @@ -304,7 +313,7 @@ int main(int argc, char** argv) if (dst_conn_name.empty()) EXIT("No destination connector defined"); - VideoDevice vid("/dev/video11"); + v4l2::VideoDevice vid("/dev/video11"); Card card; ResourceManager resman(card); diff --git a/utils/omap-wbm2m.cpp b/utils/omap-wbm2m.cpp index a00fab2..6ac08e0 100644 --- a/utils/omap-wbm2m.cpp +++ b/utils/omap-wbm2m.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include const uint32_t NUM_SRC_BUFS = 2; const uint32_t NUM_DST_BUFS = 2; @@ -106,38 +106,55 @@ int main(int argc, char** argv) printf("writing to %s\n", filename.c_str()); - VideoDevice vid("/dev/video10"); + v4l2::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(); + v4l2::VideoStreamer* out = vid.get_output_streamer(); + v4l2::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_format((v4l2::PixelFormat)src_fmt, src_width, src_height); + in->set_format((v4l2::PixelFormat)dst_fmt, dst_width, dst_height); if (use_selection) { out->set_selection(c_left, c_top, c_width, c_height); printf("crop -> %u,%u-%ux%u\n", c_left, c_top, c_width, c_height); } - out->set_queue_size(NUM_SRC_BUFS); - in->set_queue_size(NUM_DST_BUFS); + out->set_queue_size(NUM_SRC_BUFS, v4l2::VideoMemoryType::DMABUF); + in->set_queue_size(NUM_DST_BUFS, v4l2::VideoMemoryType::DMABUF); + + vector out_fbs; 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); + out_fbs.push_back(fb); + + v4l2::VideoBuffer vbuf {}; + vbuf.m_mem_type = v4l2::VideoMemoryType::DMABUF; + vbuf.m_fd = fb->prime_fd(0); + + out->queue(vbuf); } + vector in_fbs; + 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); + + in_fbs.push_back(fb); + + v4l2::VideoBuffer vbuf {}; + vbuf.m_mem_type = v4l2::VideoMemoryType::DMABUF; + vbuf.m_fd = fb->prime_fd(0); + + in->queue(vbuf); } vector fds(3); @@ -165,11 +182,14 @@ int main(int argc, char** argv) fds[1].revents = 0; try { - DumbFramebuffer* dst_fb = in->dequeue(); + auto dst_vbuf = in->dequeue(); + + auto dst_fb = *find_if(in_fbs.begin(), in_fbs.end(), [fd=dst_vbuf.m_fd](auto& fb) { return fb->prime_fd(0) == fd; }); + 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); + in->queue(dst_vbuf); dst_frame_num++; @@ -183,11 +203,13 @@ int main(int argc, char** argv) break; } - DumbFramebuffer* src_fb = out->dequeue(); + auto src_vbuf = out->dequeue(); + + auto src_fb = *find_if(out_fbs.begin(), out_fbs.end(), [fd=src_vbuf.m_fd](auto& fb) { return fb->prime_fd(0) == fd; }); if (src_frame_num < num_src_frames) { read_frame(src_fb, src_frame_num++); - out->queue(src_fb); + out->queue(src_vbuf); } } diff --git a/v4l2++/inc/v4l2++/helpers.h b/v4l2++/inc/v4l2++/helpers.h new file mode 100644 index 0000000..b5c3284 --- /dev/null +++ b/v4l2++/inc/v4l2++/helpers.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define unlikely(x) __builtin_expect(!!(x), 0) + +/* __STRING(x) is a glibcism (i.e. not standard), which happens to also + * be available in uClibc. However, musl does not define it. Do it here. + */ +#ifndef __STRING +#define __STRING(x) #x +#endif + +#define ASSERT(x) \ + if (unlikely(!(x))) { \ + fprintf(stderr, "%s:%d: %s: ASSERT(%s) failed\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, __STRING(x)); \ + abort(); \ + } + +#define FAIL(fmt, ...) \ + do { \ + fprintf(stderr, "%s:%d: %s:\n" fmt "\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__); \ + abort(); \ + } while (0) + +#define FAIL_IF(x, format, ...) \ + if (unlikely(x)) { \ + fprintf(stderr, "%s:%d: %s:\n" format "\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__); \ + abort(); \ + } + +#define EXIT(fmt, ...) \ + do { \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ + exit(-1); \ + } while (0) + +#define EXIT_IF(x, fmt, ...) \ + if (unlikely(x)) { \ + fprintf(stderr, fmt "\n", ##__VA_ARGS__); \ + exit(-1); \ + } + +void __my_throw(const char* file, int line, const char* funcname, const char* cond, fmt::string_view format, fmt::format_args args); + +template +void _my_throw(const char* file, int line, const char* funcname, const char* cond, const S& format, Args&&... args) +{ + __my_throw(file, line, funcname, cond, format, fmt::make_format_args(args...)); +} + +#define THROW(format, ...) \ + _my_throw(__FILE__, __LINE__, __PRETTY_FUNCTION__, nullptr, format, ##__VA_ARGS__); + +#define THROW_IF(x, format, ...) \ + if (unlikely(x)) { \ + _my_throw(__FILE__, __LINE__, __PRETTY_FUNCTION__, #x, format, ##__VA_ARGS__); \ + } diff --git a/v4l2++/inc/v4l2++/pixelformats.h b/v4l2++/inc/v4l2++/pixelformats.h new file mode 100644 index 0000000..609ff4f --- /dev/null +++ b/v4l2++/inc/v4l2++/pixelformats.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +namespace v4l2 +{ + +constexpr uint32_t MakeFourCC(const char* fourcc) +{ + return fourcc[0] | (fourcc[1] << 8) | (fourcc[2] << 16) | (fourcc[3] << 24); +} + +enum class PixelFormat : uint32_t { + Undefined = 0, + + NV12 = MakeFourCC("NV12"), + NV21 = MakeFourCC("NV21"), + NV16 = MakeFourCC("NV16"), + NV61 = MakeFourCC("NV61"), + + YUV420 = MakeFourCC("YU12"), + YVU420 = MakeFourCC("YV12"), + YUV422 = MakeFourCC("YU16"), + YVU422 = MakeFourCC("YV16"), + YUV444 = MakeFourCC("YU24"), + YVU444 = MakeFourCC("YV24"), + + UYVY = MakeFourCC("UYVY"), + YUYV = MakeFourCC("YUYV"), + YVYU = MakeFourCC("YVYU"), + VYUY = MakeFourCC("VYUY"), + + XRGB8888 = MakeFourCC("XR24"), + XBGR8888 = MakeFourCC("XB24"), + RGBX8888 = MakeFourCC("RX24"), + BGRX8888 = MakeFourCC("BX24"), + + ARGB8888 = MakeFourCC("AR24"), + ABGR8888 = MakeFourCC("AB24"), + RGBA8888 = MakeFourCC("RA24"), + BGRA8888 = MakeFourCC("BA24"), + + RGB888 = MakeFourCC("RG24"), + BGR888 = MakeFourCC("BG24"), + + RGB332 = MakeFourCC("RGB8"), + + RGB565 = MakeFourCC("RG16"), + BGR565 = MakeFourCC("BG16"), + + XRGB4444 = MakeFourCC("XR12"), + XRGB1555 = MakeFourCC("XR15"), + + ARGB4444 = MakeFourCC("AR12"), + ARGB1555 = MakeFourCC("AR15"), + + XRGB2101010 = MakeFourCC("XR30"), + XBGR2101010 = MakeFourCC("XB30"), + RGBX1010102 = MakeFourCC("RX30"), + BGRX1010102 = MakeFourCC("BX30"), + + ARGB2101010 = MakeFourCC("AR30"), + ABGR2101010 = MakeFourCC("AB30"), + RGBA1010102 = MakeFourCC("RA30"), + BGRA1010102 = MakeFourCC("BA30"), + + SBGGR12 = MakeFourCC("BG12"), + SRGGB12 = MakeFourCC("RG12"), + + META_8 = MakeFourCC("ME08"), + META_16 = MakeFourCC("ME16"), +}; + +static inline PixelFormat FourCCToPixelFormat(const std::string& fourcc) +{ + return (PixelFormat)MakeFourCC(fourcc.c_str()); +} + +static inline std::string PixelFormatToFourCC(PixelFormat f) +{ + char buf[5] = { (char)(((uint32_t)f >> 0) & 0xff), + (char)(((uint32_t)f >> 8) & 0xff), + (char)(((uint32_t)f >> 16) & 0xff), + (char)(((uint32_t)f >> 24) & 0xff), + 0 }; + return std::string(buf); +} + +enum class PixelColorType { + RGB, + YUV, + RAW, +}; + +struct PixelFormatPlaneInfo { + uint8_t bitspp; + uint8_t xsub; + uint8_t ysub; +}; + +struct PixelFormatInfo { + PixelColorType type; + uint8_t num_planes; + struct PixelFormatPlaneInfo planes[4]; +}; + +const struct PixelFormatInfo& get_pixel_format_info(PixelFormat format); + +} // namespace kms diff --git a/v4l2++/inc/v4l2++/videodevice.h b/v4l2++/inc/v4l2++/videodevice.h new file mode 100644 index 0000000..bdb290e --- /dev/null +++ b/v4l2++/inc/v4l2++/videodevice.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include + +namespace v4l2 +{ + +class VideoStreamer; +class MetaStreamer; + +enum class VideoMemoryType +{ + MMAP, + DMABUF, +}; + +class VideoBuffer +{ +public: + VideoMemoryType m_mem_type; + uint32_t m_index; + uint32_t m_length; + int m_fd; + uint32_t m_offset; + PixelFormat m_format; +}; + +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(); + MetaStreamer* get_meta_capture_streamer(); + + std::vector> get_discrete_frame_sizes(PixelFormat fmt); + VideoFrameSize get_frame_sizes(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 get_capture_devices(); + static std::vector get_m2m_devices(); + +private: + int m_fd; + + bool m_has_capture = false; + bool m_has_mplane_capture = false; + + bool m_has_output = false; + bool m_has_mplane_output = false; + + bool m_has_m2m = false; + bool m_has_mplane_m2m = false; + + bool m_has_meta_capture = false; + + std::unique_ptr m_capture_streamer; + std::unique_ptr m_output_streamer; + std::unique_ptr m_meta_capture_streamer; +}; + +class VideoStreamer +{ +public: + enum class StreamerType { + CaptureSingle, + CaptureMulti, + OutputSingle, + OutputMulti, + CaptureMeta, + OutputMeta, + }; + + VideoStreamer(int fd, StreamerType type); + virtual ~VideoStreamer() { } + + std::vector get_ports(); + void set_port(uint32_t index); + + std::vector get_formats(); + int get_format(PixelFormat& fmt, uint32_t& width, uint32_t& height); + void set_format(PixelFormat fmt, uint32_t width, uint32_t height); + void get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height); + void set_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height); + void set_queue_size(uint32_t queue_size, VideoMemoryType mem_type); + void queue(VideoBuffer& fb); + VideoBuffer dequeue(); + void stream_on(); + void stream_off(); + + int fd() const { return m_fd; } + +protected: + int m_fd; + StreamerType m_type; + VideoMemoryType m_mem_type; + std::vector m_fbs; +}; + + +class MetaStreamer : public VideoStreamer +{ +public: + MetaStreamer(int fd, VideoStreamer::StreamerType type); + + void set_format(PixelFormat fmt, uint32_t size); +}; + +} diff --git a/v4l2++/meson.build b/v4l2++/meson.build new file mode 100644 index 0000000..2f425d6 --- /dev/null +++ b/v4l2++/meson.build @@ -0,0 +1,32 @@ +libv4l2xx_sources = files([ + 'src/videodevice.cpp', + 'src/pixelformats.cpp', + 'src/helpers.cpp', +]) + +public_headers = [ + 'inc/v4l2++/videodevice.h', + 'inc/v4l2++/pixelformats.h', + 'inc/v4l2++/helpers.h', +] + +private_includes = include_directories('src', 'inc') +public_includes = include_directories('inc') + +libv4l2xx_deps = [ libfmt_dep ] + +libv4l2xx = library('v4l2++', + libv4l2xx_sources, + install : true, + include_directories : [ private_includes ], + dependencies : libv4l2xx_deps, + version : meson.project_version()) + + +libv4l2xx_dep = declare_dependency(include_directories : public_includes, + link_with : libv4l2xx) + +install_headers(public_headers, subdir : 'v4l2++') + +pkg = import('pkgconfig') +pkg.generate(libv4l2xx) diff --git a/v4l2++/src/helpers.cpp b/v4l2++/src/helpers.cpp new file mode 100644 index 0000000..db80408 --- /dev/null +++ b/v4l2++/src/helpers.cpp @@ -0,0 +1,16 @@ +#include + +void __my_throw(const char* file, int line, const char *funcname, const char *cond, fmt::string_view format, fmt::format_args args) +{ + std::string str = fmt::vformat(format, args); + + fmt::print(stderr, "{}:{}: {}:\n{}", file, line, funcname, str); + if (cond) + fmt::print(stderr, " ({})\n", cond); + else + fmt::print("\n"); + + fflush(stderr); + + throw std::runtime_error(str); +} diff --git a/v4l2++/src/pixelformats.cpp b/v4l2++/src/pixelformats.cpp new file mode 100644 index 0000000..1c8453f --- /dev/null +++ b/v4l2++/src/pixelformats.cpp @@ -0,0 +1,301 @@ +#include + +#include + +using namespace std; + +namespace v4l2 +{ +static const map format_info_array = { + /* YUV packed */ + { PixelFormat::UYVY, { + PixelColorType::YUV, + 1, + { { 16, 2, 1 } }, + } }, + { PixelFormat::YUYV, { + PixelColorType::YUV, + 1, + { { 16, 2, 1 } }, + } }, + { PixelFormat::YVYU, { + PixelColorType::YUV, + 1, + { { 16, 2, 1 } }, + } }, + { PixelFormat::VYUY, { + PixelColorType::YUV, + 1, + { { 16, 2, 1 } }, + } }, + /* YUV semi-planar */ + { PixelFormat::NV12, { + PixelColorType::YUV, + 2, + { { + 8, + 1, + 1, + }, + { 8, 2, 2 } }, + } }, + { PixelFormat::NV21, { + PixelColorType::YUV, + 2, + { { + 8, + 1, + 1, + }, + { 8, 2, 2 } }, + } }, + { PixelFormat::NV16, { + PixelColorType::YUV, + 2, + { { + 8, + 1, + 1, + }, + { 8, 2, 1 } }, + } }, + { PixelFormat::NV61, { + PixelColorType::YUV, + 2, + { { + 8, + 1, + 1, + }, + { 8, 2, 1 } }, + } }, + /* YUV planar */ + { PixelFormat::YUV420, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 2, 2 }, + { 8, 2, 2 } }, + } }, + { PixelFormat::YVU420, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 2, 2 }, + { 8, 2, 2 } }, + } }, + { PixelFormat::YUV422, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 2, 1 }, + { 8, 2, 1 } }, + } }, + { PixelFormat::YVU422, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 2, 1 }, + { 8, 2, 1 } }, + } }, + { PixelFormat::YUV444, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 1, 1 }, + { 8, 1, 1 } }, + } }, + { PixelFormat::YVU444, { + PixelColorType::YUV, + 3, + { { + 8, + 1, + 1, + }, + { 8, 1, 1 }, + { 8, 1, 1 } }, + } }, + /* RGB8 */ + { PixelFormat::RGB332, { + PixelColorType::RGB, + 1, + { { 8, 1, 1 } }, + } }, + /* RGB16 */ + { PixelFormat::RGB565, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::BGR565, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::XRGB4444, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::XRGB1555, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::ARGB4444, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::ARGB1555, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, + /* RGB24 */ + { PixelFormat::RGB888, { + PixelColorType::RGB, + 1, + { { 24, 1, 1 } }, + } }, + { PixelFormat::BGR888, { + PixelColorType::RGB, + 1, + { { 24, 1, 1 } }, + } }, + /* RGB32 */ + { PixelFormat::XRGB8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::XBGR8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::RGBX8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::BGRX8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + + { PixelFormat::ARGB8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::ABGR8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::RGBA8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::BGRA8888, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + + { PixelFormat::XRGB2101010, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::XBGR2101010, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::RGBX1010102, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::BGRX1010102, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + + { PixelFormat::ARGB2101010, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::ABGR2101010, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::RGBA1010102, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::BGRA1010102, { + PixelColorType::RGB, + 1, + { { 32, 1, 1 } }, + } }, + { PixelFormat::SBGGR12, { + PixelColorType::RAW, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::SRGGB12, { + PixelColorType::RAW, + 1, + { { 16, 1, 1 } }, + } }, + { PixelFormat::META_8, { + PixelColorType::RGB, + 1, + { { 8, 1, 1 } }, + } }, + { PixelFormat::META_16, { + PixelColorType::RGB, + 1, + { { 16, 1, 1 } }, + } }, +}; + +const struct PixelFormatInfo& get_pixel_format_info(PixelFormat format) +{ + if (!format_info_array.count(format)) + throw invalid_argument("get_pixel_format_info: Unsupported pixelformat"); + + return format_info_array.at(format); +} + +} diff --git a/v4l2++/src/videodevice.cpp b/v4l2++/src/videodevice.cpp new file mode 100644 index 0000000..5ab7099 --- /dev/null +++ b/v4l2++/src/videodevice.cpp @@ -0,0 +1,653 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace v4l2; + +/* + * V4L2 and DRM differ in their interpretation of YUV420::NV12 + * + * V4L2 NV12 is a Y and UV co-located planes in a single plane buffer. + * DRM NV12 is a Y and UV planes presented as dual plane buffer, + * which is known as NM12 in V4L2. + * + * Since here we have hybrid DRM/V4L2 user space helper functions + * we need to translate DRM::NV12 to V4L2:NM12 pixel format back + * and forth to keep the data view consistent. + */ + +static v4l2_memory get_mem_type(VideoMemoryType type) +{ + switch (type) { + case VideoMemoryType::MMAP: + return V4L2_MEMORY_MMAP; + case VideoMemoryType::DMABUF: + return V4L2_MEMORY_DMABUF; + default: + FAIL("Bad VideoMemoryType"); + } +} + +/* V4L2 helper funcs */ +static vector v4l2_get_formats(int fd, uint32_t buf_type) +{ + vector v; + + v4l2_fmtdesc desc{}; + desc.type = buf_type; + + while (ioctl(fd, VIDIOC_ENUM_FMT, &desc) == 0) { + if (desc.pixelformat == V4L2_PIX_FMT_NV12M) + v.push_back(PixelFormat::NV12); + else if (desc.pixelformat != V4L2_PIX_FMT_NV12) + v.push_back((PixelFormat)desc.pixelformat); + + desc.index++; + } + + return v; +} + +static int v4l2_get_format(int fd, uint32_t buf_type, PixelFormat& fmt, uint32_t& width, uint32_t& height) +{ + int r; + + v4l2_format v4lfmt{}; + + v4lfmt.type = buf_type; + r = ioctl(fd, VIDIOC_G_FMT, &v4lfmt); + ASSERT(r == 0); + + bool mplane = buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + + FAIL_IF(mplane, "mplane not supported"); + + fmt = (PixelFormat)v4lfmt.fmt.pix.pixelformat; + width = v4lfmt.fmt.pix.width; + height = v4lfmt.fmt.pix.height; + + return 0; +} + +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; + uint32_t used_fmt; + + if (fmt == PixelFormat::NV12) + used_fmt = V4L2_PIX_FMT_NV12M; + else + used_fmt = (uint32_t)fmt; + + mp.pixelformat = used_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 == used_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; + v4lfmt.fmt.pix.field = V4L2_FIELD_NONE; + + 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_get_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type) +{ + int r; + struct v4l2_selection selection; + + if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT || + buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + selection.target = V4L2_SEL_TGT_CROP; + } else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + selection.target = V4L2_SEL_TGT_COMPOSE; + } else { + FAIL("buf_type (%d) is not valid\n", buf_type); + } + + r = ioctl(fd, VIDIOC_G_SELECTION, &selection); + ASSERT(r == 0); + + left = selection.r.left; + top = selection.r.top; + width = selection.r.width; + height = selection.r.height; +} + +static void v4l2_set_selection(int fd, uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height, uint32_t buf_type) +{ + int r; + struct v4l2_selection selection; + + if (buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT || + buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + selection.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + selection.target = V4L2_SEL_TGT_CROP; + } else if (buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE || + buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + selection.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + selection.target = V4L2_SEL_TGT_COMPOSE; + } else { + FAIL("buf_type (%d) is not valid\n", buf_type); + } + + selection.r.left = left; + selection.r.top = top; + selection.r.width = width; + selection.r.height = height; + + r = ioctl(fd, VIDIOC_S_SELECTION, &selection); + ASSERT(r == 0); + + left = selection.r.left; + top = selection.r.top; + width = selection.r.width; + height = selection.r.height; +} + +static void v4l2_request_bufs(int fd, uint32_t queue_size, uint32_t buf_type, uint32_t mem_type) +{ + v4l2_requestbuffers v4lreqbuf{}; + v4lreqbuf.type = buf_type; + v4lreqbuf.memory = mem_type; + v4lreqbuf.count = queue_size; + int r = ioctl(fd, VIDIOC_REQBUFS, &v4lreqbuf); + FAIL_IF(r != 0, "VIDIOC_REQBUFS failed: %d", errno); + ASSERT(v4lreqbuf.count == queue_size); +} + +static void v4l2_queue(int fd, VideoBuffer& fb, uint32_t buf_type) +{ + v4l2_buffer buf{}; + buf.type = buf_type; + buf.memory = get_mem_type(fb.m_mem_type); + buf.index = fb.m_index; + + bool mplane = buf_type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + + if (mplane) { + ASSERT(false); + /* + const PixelFormatInfo& pfi = get_pixel_format_info(fb->m_format); + + 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 { + if (fb.m_mem_type == VideoMemoryType::DMABUF) + buf.m.fd = fb.m_fd; + + int r = ioctl(fd, VIDIOC_QBUF, &buf); + ASSERT(r == 0); + } +} + +static uint32_t v4l2_dequeue(int fd, VideoBuffer& fb, uint32_t buf_type) +{ + v4l2_buffer buf{}; + buf.type = buf_type; + buf.memory = get_mem_type(fb.m_mem_type); + + // 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()); + + fb.m_index = buf.index; + fb.m_length = buf.length; + + if (fb.m_mem_type == VideoMemoryType::DMABUF) + fb.m_fd = buf.m.fd; + else + fb.m_offset = buf.m.offset; + + return buf.index; +} + +VideoDevice::VideoDevice(const string& dev) + : VideoDevice(::open(dev.c_str(), O_RDWR | O_NONBLOCK)) +{ +} + +VideoDevice::VideoDevice(int fd) + : m_fd(fd) +{ + if (fd < 0) + throw runtime_error("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; + } + + if (cap.capabilities & V4L2_CAP_META_CAPTURE) { + m_has_meta_capture = true; + } +} + +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 = std::unique_ptr(new VideoStreamer(m_fd, type)); + } + + return m_capture_streamer.get(); +} + +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 = std::unique_ptr(new VideoStreamer(m_fd, type)); + } + + return m_output_streamer.get(); +} + +MetaStreamer* VideoDevice::get_meta_capture_streamer() +{ + ASSERT(m_has_meta_capture); + + if (!m_meta_capture_streamer) + m_meta_capture_streamer = make_unique(m_fd, MetaStreamer::StreamerType::CaptureMeta); + + return m_meta_capture_streamer.get(); +} + +vector> VideoDevice::get_discrete_frame_sizes(PixelFormat fmt) +{ + vector> 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 VideoDevice::get_capture_devices() +{ + vector 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; + + try { + VideoDevice vid(name); + + if (vid.has_capture() && !vid.has_m2m()) + v.push_back(name); + } catch (...) { + } + } + + return v; +} + +vector VideoDevice::get_m2m_devices() +{ + vector 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; + + try { + VideoDevice vid(name); + + if (vid.has_m2m()) + v.push_back(name); + } catch (...) { + } + } + + return v; +} + +VideoStreamer::VideoStreamer(int fd, StreamerType type) + : m_fd(fd), m_type(type) +{ +} + +std::vector VideoStreamer::get_ports() +{ + vector 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; + case MetaStreamer::StreamerType::CaptureMeta: + return V4L2_BUF_TYPE_META_CAPTURE; + case MetaStreamer::StreamerType::OutputMeta: + return (v4l2_buf_type)14; // XXX V4L2_BUF_TYPE_META_OUTPUT; + default: + FAIL("Bad StreamerType"); + } +} + +std::vector VideoStreamer::get_formats() +{ + return v4l2_get_formats(m_fd, get_buf_type(m_type)); +} + +int VideoStreamer::get_format(PixelFormat &fmt, uint32_t &width, uint32_t &height) +{ + return v4l2_get_format(m_fd, get_buf_type(m_type), fmt, width, height); +} + +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::get_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height) +{ + v4l2_get_selection(m_fd, left, top, width, height, get_buf_type(m_type)); +} + +void VideoStreamer::set_selection(uint32_t& left, uint32_t& top, uint32_t& width, uint32_t& height) +{ + v4l2_set_selection(m_fd, left, top, width, height, get_buf_type(m_type)); +} + +void VideoStreamer::set_queue_size(uint32_t queue_size, VideoMemoryType mem_type) +{ + m_mem_type = mem_type; + + v4l2_request_bufs(m_fd, queue_size, get_buf_type(m_type), get_mem_type(m_mem_type)); + + m_fbs.resize(queue_size); +} + +void VideoStreamer::queue(VideoBuffer &fb) +{ + uint32_t idx; + + for (idx = 0; idx < m_fbs.size(); ++idx) { + if (m_fbs[idx] == false) + break; + } + + FAIL_IF(idx == m_fbs.size(), "queue full"); + + fb.m_index = idx; + + m_fbs[idx] = true; + + v4l2_queue(m_fd, fb, get_buf_type(m_type)); +} + +VideoBuffer VideoStreamer::dequeue() +{ + VideoBuffer fb {}; + fb.m_mem_type = m_mem_type; + + uint32_t idx = v4l2_dequeue(m_fd, fb, get_buf_type(m_type)); + + m_fbs[idx] = false; + + 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); +} + + + + + +MetaStreamer::MetaStreamer(int fd, StreamerType type) + : VideoStreamer(fd, type) +{ +} + +void MetaStreamer::set_format(PixelFormat fmt, uint32_t size) +{ + int r; + + v4l2_format v4lfmt {}; + + v4lfmt.type = get_buf_type(m_type); + //r = ioctl(m_fd, VIDIOC_G_FMT, &v4lfmt); + //ASSERT(r == 0); + + v4lfmt.fmt.meta.dataformat = (uint32_t)fmt; + v4lfmt.fmt.meta.buffersize = size; + + r = ioctl(m_fd, VIDIOC_S_FMT, &v4lfmt); + ASSERT(r == 0); +} -- cgit v1.2.3