#include "ikvm_video.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ikvm { const int Video::bitsPerSample(8); const int Video::bytesPerPixel(4); const int Video::samplesPerPixel(3); using namespace phosphor::logging; using namespace sdbusplus::xyz::openbmc_project::Common::File::Error; using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; Video::Video(const std::string& p, Input& input, int fr, int sub) : resizeAfterOpen(false), timingsError(false), fd(-1), frameRate(fr), lastFrameIndex(-1), height(600), width(800), subSampling(sub), input(input), path(p), pixelformat(V4L2_PIX_FMT_JPEG) {} Video::~Video() { stop(); } char* Video::getData() { if (lastFrameIndex >= 0) { return (char*)buffers[lastFrameIndex].data; } return nullptr; } void Video::getFrame() { int rc(0); int fd_flags; v4l2_buffer buf; fd_set fds; timeval tv; if (fd < 0) { return; } FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec = 1; tv.tv_usec = 0; memset(&buf, 0, sizeof(v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; // Switch to non-blocking in order to safely dequeue all buffers; if the // video signal is lost while blocking to dequeue, the video driver may // wait forever if signal is not re-acquired fd_flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK); rc = select(fd + 1, &fds, NULL, NULL, &tv); if (rc > 0) { do { rc = ioctl(fd, VIDIOC_DQBUF, &buf); if (rc >= 0) { buffers[buf.index].queued = false; if (!(buf.flags & V4L2_BUF_FLAG_ERROR)) { lastFrameIndex = buf.index; buffers[lastFrameIndex].payload = buf.bytesused; break; } else { buffers[buf.index].payload = 0; } } } while (rc >= 0); } fcntl(fd, F_SETFL, fd_flags); for (unsigned int i = 0; i < buffers.size(); ++i) { if (i == (unsigned int)lastFrameIndex) { continue; } if (!buffers[i].queued) { memset(&buf, 0, sizeof(v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; rc = ioctl(fd, VIDIOC_QBUF, &buf); if (rc) { log("Failed to queue buffer", entry("ERROR=%s", strerror(errno))); } else { buffers[i].queued = true; } } } } bool Video::needsResize() { int rc; v4l2_dv_timings timings; if (fd < 0) { return false; } if (resizeAfterOpen) { return true; } memset(&timings, 0, sizeof(v4l2_dv_timings)); rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings); if (rc < 0) { if (!timingsError) { log("Failed to query timings", entry("ERROR=%s", strerror(errno))); timingsError = true; } restart(); return false; } else { timingsError = false; } if (timings.bt.width != width || timings.bt.height != height) { width = timings.bt.width; height = timings.bt.height; if (!width || !height) { log("Failed to get new resolution", entry("WIDTH=%d", width), entry("HEIGHT=%d", height)); elog( xyz::openbmc_project::Common::File::Open::ERRNO(-EPROTO), xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); } lastFrameIndex = -1; return true; } return false; } void Video::resize() { int rc; unsigned int i; bool needsResizeCall(false); v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE); v4l2_requestbuffers req; if (fd < 0) { return; } if (resizeAfterOpen) { resizeAfterOpen = false; return; } for (i = 0; i < buffers.size(); ++i) { if (buffers[i].data) { needsResizeCall = true; break; } } if (needsResizeCall) { rc = ioctl(fd, VIDIOC_STREAMOFF, &type); if (rc) { log("Failed to stop streaming", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } } for (i = 0; i < buffers.size(); ++i) { if (buffers[i].data) { munmap(buffers[i].data, buffers[i].size); buffers[i].data = nullptr; buffers[i].queued = false; } } if (needsResizeCall) { v4l2_dv_timings timings; memset(&req, 0, sizeof(v4l2_requestbuffers)); req.count = 0; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; rc = ioctl(fd, VIDIOC_REQBUFS, &req); if (rc < 0) { log("Failed to zero streaming buffers", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } memset(&timings, 0, sizeof(v4l2_dv_timings)); rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings); if (rc < 0) { log("Failed to query timings, restart", entry("ERROR=%s", strerror(errno))); restart(); return; } rc = ioctl(fd, VIDIOC_S_DV_TIMINGS, &timings); if (rc < 0) { log("Failed to set timings", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } buffers.clear(); } memset(&req, 0, sizeof(v4l2_requestbuffers)); req.count = 3; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; rc = ioctl(fd, VIDIOC_REQBUFS, &req); if (rc < 0 || req.count < 2) { log("Failed to request streaming buffers", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } buffers.resize(req.count); for (i = 0; i < buffers.size(); ++i) { v4l2_buffer buf; memset(&buf, 0, sizeof(v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; rc = ioctl(fd, VIDIOC_QUERYBUF, &buf); if (rc < 0) { log("Failed to query buffer", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } buffers[i].data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[i].data == MAP_FAILED) { log("Failed to mmap buffer", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } buffers[i].size = buf.length; rc = ioctl(fd, VIDIOC_QBUF, &buf); if (rc < 0) { log("Failed to queue buffer", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_ERRNO(errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } buffers[i].queued = true; } rc = ioctl(fd, VIDIOC_STREAMON, &type); if (rc) { log("Failed to start streaming", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } } void Video::start() { int rc; size_t oldHeight = height; size_t oldWidth = width; v4l2_capability cap; v4l2_format fmt; v4l2_streamparm sparm; v4l2_control ctrl; if (fd >= 0) { return; } input.sendWakeupPacket(); fd = open(path.c_str(), O_RDWR); if (fd < 0) { log("Failed to open video device", entry("PATH=%s", path.c_str()), entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::File::Open::ERRNO(errno), xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); } memset(&cap, 0, sizeof(v4l2_capability)); rc = ioctl(fd, VIDIOC_QUERYCAP, &cap); if (rc < 0) { log("Failed to query video device capabilities", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || !(cap.capabilities & V4L2_CAP_STREAMING)) { log("Video device doesn't support this application"); elog( xyz::openbmc_project::Common::File::Open::ERRNO(errno), xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); } memset(&fmt, 0, sizeof(v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rc = ioctl(fd, VIDIOC_G_FMT, &fmt); if (rc < 0) { log("Failed to query video device format", entry("ERROR=%s", strerror(errno))); elog( xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( errno), xyz::openbmc_project::Common::Device::ReadFailure:: CALLOUT_DEVICE_PATH(path.c_str())); } memset(&sparm, 0, sizeof(v4l2_streamparm)); sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; sparm.parm.capture.timeperframe.numerator = 1; sparm.parm.capture.timeperframe.denominator = frameRate; rc = ioctl(fd, VIDIOC_S_PARM, &sparm); if (rc < 0) { log("Failed to set video device frame rate", entry("ERROR=%s", strerror(errno))); } ctrl.id = V4L2_CID_JPEG_CHROMA_SUBSAMPLING; ctrl.value = subSampling ? V4L2_JPEG_CHROMA_SUBSAMPLING_420 : V4L2_JPEG_CHROMA_SUBSAMPLING_444; rc = ioctl(fd, VIDIOC_S_CTRL, &ctrl); if (rc < 0) { log("Failed to set video jpeg subsampling", entry("ERROR=%s", strerror(errno))); } height = fmt.fmt.pix.height; width = fmt.fmt.pix.width; pixelformat = fmt.fmt.pix.pixelformat; if (pixelformat != V4L2_PIX_FMT_RGB24 && pixelformat != V4L2_PIX_FMT_JPEG) { log("Pixel Format not supported", entry("PIXELFORMAT=%d", pixelformat)); } resize(); if (oldHeight != height || oldWidth != width) { resizeAfterOpen = true; } } void Video::stop() { int rc; unsigned int i; v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE); if (fd < 0) { return; } lastFrameIndex = -1; rc = ioctl(fd, VIDIOC_STREAMOFF, &type); if (rc) { log("Failed to stop streaming", entry("ERROR=%s", strerror(errno))); } for (i = 0; i < buffers.size(); ++i) { if (buffers[i].data) { munmap(buffers[i].data, buffers[i].size); buffers[i].data = nullptr; buffers[i].queued = false; } } close(fd); fd = -1; } } // namespace ikvm