From a5c28bcb2ead34e921617711ebf94ffcb5d72878 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Mon, 23 May 2016 09:54:08 +0300 Subject: File/dir renames --- utils/kmscapture.cpp | 435 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 utils/kmscapture.cpp (limited to 'utils/kmscapture.cpp') diff --git a/utils/kmscapture.cpp b/utils/kmscapture.cpp new file mode 100644 index 0000000..ee700b7 --- /dev/null +++ b/utils/kmscapture.cpp @@ -0,0 +1,435 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kms++.h" +#include "test.h" +#include "opts.h" + +#define CAMERA_BUF_QUEUE_SIZE 3 +#define MAX_CAMERA 9 + +using namespace std; +using namespace kms; + +enum class BufferProvider { + DRM, + V4L2, +}; + +class CameraPipeline +{ +public: + CameraPipeline(int cam_fd, Card& card, Crtc* crtc, Plane* plane, uint32_t x, uint32_t y, + uint32_t iw, uint32_t ih, PixelFormat pixfmt, + BufferProvider buffer_provider); + ~CameraPipeline(); + + CameraPipeline(const CameraPipeline& other) = delete; + CameraPipeline& operator=(const CameraPipeline& other) = delete; + + void show_next_frame(AtomicReq &req); + int fd() const { return m_fd; } + void start_streaming(); +private: + ExtFramebuffer* GetExtFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt); + int m_fd; /* camera file descriptor */ + Crtc* m_crtc; + Plane* m_plane; + BufferProvider m_buffer_provider; + vector m_fb; /* framebuffers for DRM buffers */ + vector m_extfb; /* framebuffers for V4L2 buffers */ + int m_prev_fb_index; + uint32_t m_in_width, m_in_height; /* camera capture resolution */ + /* image properties for display */ + uint32_t m_out_width, m_out_height; + uint32_t m_out_x, m_out_y; +}; + +static int buffer_export(int v4lfd, enum v4l2_buf_type bt, uint32_t index, int *dmafd) +{ + struct v4l2_exportbuffer expbuf; + + memset(&expbuf, 0, sizeof(expbuf)); + expbuf.type = bt; + expbuf.index = index; + if (ioctl(v4lfd, VIDIOC_EXPBUF, &expbuf) == -1) { + perror("VIDIOC_EXPBUF"); + return -1; + } + + *dmafd = expbuf.fd; + + return 0; +} + +ExtFramebuffer* CameraPipeline::GetExtFrameBuffer(Card& card, uint32_t i, PixelFormat pixfmt) +{ + int r, dmafd; + + r = buffer_export(m_fd, V4L2_BUF_TYPE_VIDEO_CAPTURE, i, &dmafd); + ASSERT(r == 0); + + uint32_t handle; + r = drmPrimeFDToHandle(card.fd(), dmafd, &handle); + ASSERT(r == 0); + + const PixelFormatInfo& format_info = get_pixel_format_info(pixfmt); + ASSERT(format_info.num_planes == 1); + + uint32_t handles[4] { handle }; + uint32_t pitches[4] { m_in_width * (format_info.planes[0].bitspp / 8) }; + uint32_t offsets[4] { }; + + return new ExtFramebuffer(card, m_in_width, m_in_height, pixfmt, + handles, pitches, offsets); +} + +bool inline better_size(struct v4l2_frmsize_discrete* v4ldisc, + uint32_t iw, uint32_t ih, + uint32_t best_w, uint32_t best_h) +{ + if (v4ldisc->width <= iw && v4ldisc->height <= ih && + (v4ldisc->width >= best_w || v4ldisc->height >= best_h)) + return true; + + return false; +} + +CameraPipeline::CameraPipeline(int cam_fd, Card& card, Crtc *crtc, Plane* plane, uint32_t x, uint32_t y, + uint32_t iw, uint32_t ih, PixelFormat pixfmt, + BufferProvider buffer_provider) + : m_fd(cam_fd), m_crtc(crtc), m_buffer_provider(buffer_provider), m_prev_fb_index(-1) +{ + + int r; + uint32_t best_w = 320; + uint32_t best_h = 240; + + struct v4l2_frmsizeenum v4lfrms = { }; + v4lfrms.pixel_format = (uint32_t)pixfmt; + while (ioctl(m_fd, VIDIOC_ENUM_FRAMESIZES, &v4lfrms) == 0) { + if (v4lfrms.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + if (better_size(&v4lfrms.discrete, iw, ih, + best_w, best_h)) { + best_w = v4lfrms.discrete.width; + best_h = v4lfrms.discrete.height; + } + } else { + break; + } + v4lfrms.index++; + }; + + m_out_width = m_in_width = best_w; + m_out_height = m_in_height = best_h; + /* Move it to the middle of the requested area */ + m_out_x = x + iw / 2 - m_out_width / 2; + m_out_y = y + ih / 2 - m_out_height / 2; + + struct v4l2_format v4lfmt = { }; + v4lfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + r = ioctl(m_fd, VIDIOC_G_FMT, &v4lfmt); + ASSERT(r == 0); + + v4lfmt.fmt.pix.pixelformat = (uint32_t)pixfmt; + v4lfmt.fmt.pix.width = m_in_width; + v4lfmt.fmt.pix.height = m_in_height; + + r = ioctl(m_fd, VIDIOC_S_FMT, &v4lfmt); + ASSERT(r == 0); + + uint32_t v4l_mem; + + if (m_buffer_provider == BufferProvider::V4L2) + v4l_mem = V4L2_MEMORY_MMAP; + else + v4l_mem = V4L2_MEMORY_DMABUF; + + struct v4l2_requestbuffers v4lreqbuf = { }; + v4lreqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4lreqbuf.memory = v4l_mem; + v4lreqbuf.count = CAMERA_BUF_QUEUE_SIZE; + r = ioctl(m_fd, VIDIOC_REQBUFS, &v4lreqbuf); + ASSERT(r == 0); + ASSERT(v4lreqbuf.count == CAMERA_BUF_QUEUE_SIZE); + + struct v4l2_buffer v4lbuf = { }; + v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4lbuf.memory = v4l_mem; + + for (unsigned i = 0; i < CAMERA_BUF_QUEUE_SIZE; i++) { + DumbFramebuffer *fb = NULL; + ExtFramebuffer *extfb = NULL; + + if (m_buffer_provider == BufferProvider::V4L2) + extfb = GetExtFrameBuffer(card, i, pixfmt); + else + fb = new DumbFramebuffer(card, m_in_width, + m_in_height, pixfmt); + + v4lbuf.index = i; + if (m_buffer_provider == BufferProvider::DRM) + v4lbuf.m.fd = fb->prime_fd(0); + r = ioctl(m_fd, VIDIOC_QBUF, &v4lbuf); + ASSERT(r == 0); + + if (m_buffer_provider == BufferProvider::V4L2) + m_extfb.push_back(extfb); + else + m_fb.push_back(fb); + } + + m_plane = plane; + + // Do initial plane setup with first fb, so that we only need to + // set the FB when page flipping + AtomicReq req(card); + + Framebuffer *fb; + if (m_buffer_provider == BufferProvider::V4L2) + fb = m_extfb[0]; + else + fb = m_fb[0]; + + req.add(m_plane, "CRTC_ID", m_crtc->id()); + req.add(m_plane, "FB_ID", fb->id()); + + req.add(m_plane, "CRTC_X", m_out_x); + req.add(m_plane, "CRTC_Y", m_out_y); + req.add(m_plane, "CRTC_W", m_out_width); + req.add(m_plane, "CRTC_H", m_out_height); + + req.add(m_plane, "SRC_X", 0); + req.add(m_plane, "SRC_Y", 0); + req.add(m_plane, "SRC_W", m_in_width << 16); + req.add(m_plane, "SRC_H", m_in_height << 16); + + r = req.commit_sync(); + FAIL_IF(r, "initial plane setup failed"); +} + +CameraPipeline::~CameraPipeline() +{ + for (unsigned i = 0; i < m_fb.size(); i++) + delete m_fb[i]; + + for (unsigned i = 0; i < m_extfb.size(); i++) + delete m_extfb[i]; + + ::close(m_fd); +} + +void CameraPipeline::start_streaming() +{ + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + int r = ioctl(m_fd, VIDIOC_STREAMON, &type); + FAIL_IF(r, "Failed to enable camera stream: %d", r); +} + +void CameraPipeline::show_next_frame(AtomicReq& req) +{ + int r; + uint32_t v4l_mem; + + if (m_buffer_provider == BufferProvider::V4L2) + v4l_mem = V4L2_MEMORY_MMAP; + else + v4l_mem = V4L2_MEMORY_DMABUF; + + struct v4l2_buffer v4l2buf = { }; + v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buf.memory = v4l_mem; + r = ioctl(m_fd, VIDIOC_DQBUF, &v4l2buf); + if (r != 0) { + printf("VIDIOC_DQBUF ioctl failed with %d\n", errno); + return; + } + + unsigned fb_index = v4l2buf.index; + + Framebuffer *fb; + if (m_buffer_provider == BufferProvider::V4L2) + fb = m_extfb[fb_index]; + else + fb = m_fb[fb_index]; + + req.add(m_plane, "FB_ID", fb->id()); + + if (m_prev_fb_index >= 0) { + memset(&v4l2buf, 0, sizeof(v4l2buf)); + v4l2buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2buf.memory = v4l_mem; + v4l2buf.index = m_prev_fb_index; + if (m_buffer_provider == BufferProvider::DRM) + v4l2buf.m.fd = m_fb[m_prev_fb_index]->prime_fd(0); + r = ioctl(m_fd, VIDIOC_QBUF, &v4l2buf); + ASSERT(r == 0); + + } + + m_prev_fb_index = fb_index; +} + +static bool is_capture_dev(int fd) +{ + struct v4l2_capability cap = { }; + int r = ioctl(fd, VIDIOC_QUERYCAP, &cap); + ASSERT(r == 0); + return cap.capabilities & V4L2_CAP_VIDEO_CAPTURE; +} + +std::vector glob(const std::string& pat) +{ + glob_t glob_result; + glob(pat.c_str(), 0, NULL, &glob_result); + vector ret; + for(unsigned i = 0; i < glob_result.gl_pathc; ++i) + ret.push_back(string(glob_result.gl_pathv[i])); + globfree(&glob_result); + return ret; +} + +static const char* usage_str = + "Usage: kmscapture [OPTIONS]\n\n" + "Options:\n" + " -s, --single Single camera mode. Open only /dev/video0\n" + " --buffer-type= Use DRM or V4L provided buffers. Default: DRM\n" + " -h, --help Print this help\n" + ; + +int main(int argc, char** argv) +{ + BufferProvider buffer_provider = BufferProvider::DRM; + bool single_cam = false; + + OptionSet optionset = { + Option("s|single", [&]() + { + single_cam = true; + }), + Option("|buffer-type=", [&](string s) + { + if (s == "v4l") + buffer_provider = BufferProvider::V4L2; + else if (s == "drm") + buffer_provider = BufferProvider::DRM; + else + FAIL("Invalid buffer provider: %s", s.c_str()); + }), + Option("h|help", [&]() + { + puts(usage_str); + exit(-1); + }), + }; + + optionset.parse(argc, argv); + + if (optionset.params().size() > 0) { + puts(usage_str); + exit(-1); + } + + auto pixfmt = PixelFormat::YUYV; + + Card card; + + auto conn = card.get_first_connected_connector(); + auto crtc = conn->get_current_crtc(); + printf("Display: %dx%d\n", crtc->width(), crtc->height()); + printf("Buffer provider: %s\n", buffer_provider == BufferProvider::V4L2? "V4L" : "DRM"); + + vector camera_fds; + + for (string vidpath : glob("/dev/video*")) { + int fd = ::open(vidpath.c_str(), O_RDWR | O_NONBLOCK); + + if (fd < 0) + continue; + + if (!is_capture_dev(fd)) { + close(fd); + continue; + } + + camera_fds.push_back(fd); + + if (single_cam) + break; + } + + FAIL_IF(camera_fds.size() == 0, "No cameras found"); + + vector available_planes; + for (Plane* p : crtc->get_possible_planes()) { + if (p->plane_type() != PlaneType::Overlay) + continue; + + if (!p->supports_format(pixfmt)) + continue; + + available_planes.push_back(p); + } + + FAIL_IF(available_planes.size() < camera_fds.size(), "Not enough video planes for cameras"); + + uint32_t plane_w = crtc->width() / camera_fds.size(); + vector cameras; + + for (unsigned i = 0; i < camera_fds.size(); ++i) { + int cam_fd = camera_fds[i]; + Plane* plane = available_planes[i]; + + auto cam = new CameraPipeline(cam_fd, card, crtc, plane, i * plane_w, 0, + plane_w, crtc->height(), pixfmt, buffer_provider); + cameras.push_back(cam); + } + + unsigned nr_cameras = cameras.size(); + + vector fds(nr_cameras + 1); + + for (unsigned i = 0; i < nr_cameras; i++) { + fds[i].fd = cameras[i]->fd(); + fds[i].events = POLLIN; + } + fds[nr_cameras].fd = 0; + fds[nr_cameras].events = POLLIN; + + for (auto cam : cameras) + cam->start_streaming(); + + while (true) { + int r = poll(fds.data(), nr_cameras + 1, -1); + ASSERT(r > 0); + + if (fds[nr_cameras].revents != 0) + break; + + AtomicReq req(card); + + for (unsigned i = 0; i < nr_cameras; i++) { + if (!fds[i].revents) + continue; + cameras[i]->show_next_frame(req); + fds[i].revents = 0; + } + + r = req.test(); + FAIL_IF(r, "Atomic commit failed: %d", r); + + req.commit_sync(); + } + + for (auto cam : cameras) + delete cam; +} -- cgit v1.2.3