1 #include "ikvm_video.hpp" 2 3 #include <err.h> 4 #include <errno.h> 5 #include <fcntl.h> 6 #include <linux/videodev2.h> 7 #include <poll.h> 8 #include <sys/ioctl.h> 9 #include <sys/mman.h> 10 #include <sys/select.h> 11 #include <sys/stat.h> 12 #include <sys/time.h> 13 #include <sys/types.h> 14 #include <unistd.h> 15 16 #include <phosphor-logging/elog-errors.hpp> 17 #include <phosphor-logging/elog.hpp> 18 #include <phosphor-logging/log.hpp> 19 #include <xyz/openbmc_project/Common/Device/error.hpp> 20 #include <xyz/openbmc_project/Common/File/error.hpp> 21 22 namespace ikvm 23 { 24 25 const int Video::bitsPerSample(8); 26 const int Video::bytesPerPixel(4); 27 const int Video::samplesPerPixel(3); 28 29 using namespace phosphor::logging; 30 using namespace sdbusplus::xyz::openbmc_project::Common::File::Error; 31 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 32 33 Video::Video(const std::string& p, Input& input, int fr, int sub) : 34 resizeAfterOpen(false), timingsError(false), fd(-1), frameRate(fr), 35 lastFrameIndex(-1), height(600), width(800), subSampling(sub), input(input), 36 path(p), pixelformat(V4L2_PIX_FMT_JPEG) 37 {} 38 39 Video::~Video() 40 { 41 stop(); 42 } 43 44 char* Video::getData() 45 { 46 if (lastFrameIndex >= 0) 47 { 48 return (char*)buffers[lastFrameIndex].data; 49 } 50 51 return nullptr; 52 } 53 54 void Video::getFrame() 55 { 56 int rc(0); 57 int fd_flags; 58 v4l2_buffer buf; 59 fd_set fds; 60 timeval tv; 61 62 if (fd < 0) 63 { 64 return; 65 } 66 67 FD_ZERO(&fds); 68 FD_SET(fd, &fds); 69 70 tv.tv_sec = 1; 71 tv.tv_usec = 0; 72 73 memset(&buf, 0, sizeof(v4l2_buffer)); 74 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 75 buf.memory = V4L2_MEMORY_MMAP; 76 77 // Switch to non-blocking in order to safely dequeue all buffers; if the 78 // video signal is lost while blocking to dequeue, the video driver may 79 // wait forever if signal is not re-acquired 80 fd_flags = fcntl(fd, F_GETFL); 81 fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK); 82 83 rc = select(fd + 1, &fds, NULL, NULL, &tv); 84 if (rc > 0) 85 { 86 do 87 { 88 rc = ioctl(fd, VIDIOC_DQBUF, &buf); 89 if (rc >= 0) 90 { 91 buffers[buf.index].queued = false; 92 93 if (!(buf.flags & V4L2_BUF_FLAG_ERROR)) 94 { 95 lastFrameIndex = buf.index; 96 buffers[lastFrameIndex].payload = buf.bytesused; 97 break; 98 } 99 else 100 { 101 buffers[buf.index].payload = 0; 102 } 103 } 104 } while (rc >= 0); 105 } 106 107 fcntl(fd, F_SETFL, fd_flags); 108 109 for (unsigned int i = 0; i < buffers.size(); ++i) 110 { 111 if (i == (unsigned int)lastFrameIndex) 112 { 113 continue; 114 } 115 116 if (!buffers[i].queued) 117 { 118 memset(&buf, 0, sizeof(v4l2_buffer)); 119 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 120 buf.memory = V4L2_MEMORY_MMAP; 121 buf.index = i; 122 123 rc = ioctl(fd, VIDIOC_QBUF, &buf); 124 if (rc) 125 { 126 log<level::ERR>("Failed to queue buffer", 127 entry("ERROR=%s", strerror(errno))); 128 } 129 else 130 { 131 buffers[i].queued = true; 132 } 133 } 134 } 135 } 136 137 bool Video::needsResize() 138 { 139 int rc; 140 v4l2_dv_timings timings; 141 142 if (fd < 0) 143 { 144 return false; 145 } 146 147 if (resizeAfterOpen) 148 { 149 return true; 150 } 151 152 memset(&timings, 0, sizeof(v4l2_dv_timings)); 153 rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings); 154 if (rc < 0) 155 { 156 if (!timingsError) 157 { 158 log<level::ERR>("Failed to query timings", 159 entry("ERROR=%s", strerror(errno))); 160 timingsError = true; 161 } 162 163 restart(); 164 return false; 165 } 166 else 167 { 168 timingsError = false; 169 } 170 171 if (timings.bt.width != width || timings.bt.height != height) 172 { 173 width = timings.bt.width; 174 height = timings.bt.height; 175 176 if (!width || !height) 177 { 178 log<level::ERR>("Failed to get new resolution", 179 entry("WIDTH=%d", width), 180 entry("HEIGHT=%d", height)); 181 elog<Open>( 182 xyz::openbmc_project::Common::File::Open::ERRNO(-EPROTO), 183 xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); 184 } 185 186 lastFrameIndex = -1; 187 return true; 188 } 189 190 return false; 191 } 192 193 void Video::resize() 194 { 195 int rc; 196 unsigned int i; 197 bool needsResizeCall(false); 198 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE); 199 v4l2_requestbuffers req; 200 201 if (fd < 0) 202 { 203 return; 204 } 205 206 if (resizeAfterOpen) 207 { 208 resizeAfterOpen = false; 209 return; 210 } 211 212 for (i = 0; i < buffers.size(); ++i) 213 { 214 if (buffers[i].data) 215 { 216 needsResizeCall = true; 217 break; 218 } 219 } 220 221 if (needsResizeCall) 222 { 223 rc = ioctl(fd, VIDIOC_STREAMOFF, &type); 224 if (rc) 225 { 226 log<level::ERR>("Failed to stop streaming", 227 entry("ERROR=%s", strerror(errno))); 228 elog<ReadFailure>( 229 xyz::openbmc_project::Common::Device::ReadFailure:: 230 CALLOUT_ERRNO(errno), 231 xyz::openbmc_project::Common::Device::ReadFailure:: 232 CALLOUT_DEVICE_PATH(path.c_str())); 233 } 234 } 235 236 for (i = 0; i < buffers.size(); ++i) 237 { 238 if (buffers[i].data) 239 { 240 munmap(buffers[i].data, buffers[i].size); 241 buffers[i].data = nullptr; 242 buffers[i].queued = false; 243 } 244 } 245 246 if (needsResizeCall) 247 { 248 v4l2_dv_timings timings; 249 250 memset(&req, 0, sizeof(v4l2_requestbuffers)); 251 req.count = 0; 252 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 253 req.memory = V4L2_MEMORY_MMAP; 254 rc = ioctl(fd, VIDIOC_REQBUFS, &req); 255 if (rc < 0) 256 { 257 log<level::ERR>("Failed to zero streaming buffers", 258 entry("ERROR=%s", strerror(errno))); 259 elog<ReadFailure>( 260 xyz::openbmc_project::Common::Device::ReadFailure:: 261 CALLOUT_ERRNO(errno), 262 xyz::openbmc_project::Common::Device::ReadFailure:: 263 CALLOUT_DEVICE_PATH(path.c_str())); 264 } 265 266 memset(&timings, 0, sizeof(v4l2_dv_timings)); 267 rc = ioctl(fd, VIDIOC_QUERY_DV_TIMINGS, &timings); 268 if (rc < 0) 269 { 270 log<level::ERR>("Failed to query timings, restart", 271 entry("ERROR=%s", strerror(errno))); 272 restart(); 273 return; 274 } 275 276 rc = ioctl(fd, VIDIOC_S_DV_TIMINGS, &timings); 277 if (rc < 0) 278 { 279 log<level::ERR>("Failed to set timings", 280 entry("ERROR=%s", strerror(errno))); 281 elog<ReadFailure>( 282 xyz::openbmc_project::Common::Device::ReadFailure:: 283 CALLOUT_ERRNO(errno), 284 xyz::openbmc_project::Common::Device::ReadFailure:: 285 CALLOUT_DEVICE_PATH(path.c_str())); 286 } 287 288 buffers.clear(); 289 } 290 291 memset(&req, 0, sizeof(v4l2_requestbuffers)); 292 req.count = 3; 293 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 294 req.memory = V4L2_MEMORY_MMAP; 295 rc = ioctl(fd, VIDIOC_REQBUFS, &req); 296 if (rc < 0 || req.count < 2) 297 { 298 log<level::ERR>("Failed to request streaming buffers", 299 entry("ERROR=%s", strerror(errno))); 300 elog<ReadFailure>( 301 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( 302 errno), 303 xyz::openbmc_project::Common::Device::ReadFailure:: 304 CALLOUT_DEVICE_PATH(path.c_str())); 305 } 306 307 buffers.resize(req.count); 308 309 for (i = 0; i < buffers.size(); ++i) 310 { 311 v4l2_buffer buf; 312 313 memset(&buf, 0, sizeof(v4l2_buffer)); 314 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 315 buf.memory = V4L2_MEMORY_MMAP; 316 buf.index = i; 317 318 rc = ioctl(fd, VIDIOC_QUERYBUF, &buf); 319 if (rc < 0) 320 { 321 log<level::ERR>("Failed to query buffer", 322 entry("ERROR=%s", strerror(errno))); 323 elog<ReadFailure>( 324 xyz::openbmc_project::Common::Device::ReadFailure:: 325 CALLOUT_ERRNO(errno), 326 xyz::openbmc_project::Common::Device::ReadFailure:: 327 CALLOUT_DEVICE_PATH(path.c_str())); 328 } 329 330 buffers[i].data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, 331 MAP_SHARED, fd, buf.m.offset); 332 if (buffers[i].data == MAP_FAILED) 333 { 334 log<level::ERR>("Failed to mmap buffer", 335 entry("ERROR=%s", strerror(errno))); 336 elog<ReadFailure>( 337 xyz::openbmc_project::Common::Device::ReadFailure:: 338 CALLOUT_ERRNO(errno), 339 xyz::openbmc_project::Common::Device::ReadFailure:: 340 CALLOUT_DEVICE_PATH(path.c_str())); 341 } 342 343 buffers[i].size = buf.length; 344 345 rc = ioctl(fd, VIDIOC_QBUF, &buf); 346 if (rc < 0) 347 { 348 log<level::ERR>("Failed to queue buffer", 349 entry("ERROR=%s", strerror(errno))); 350 elog<ReadFailure>( 351 xyz::openbmc_project::Common::Device::ReadFailure:: 352 CALLOUT_ERRNO(errno), 353 xyz::openbmc_project::Common::Device::ReadFailure:: 354 CALLOUT_DEVICE_PATH(path.c_str())); 355 } 356 357 buffers[i].queued = true; 358 } 359 360 rc = ioctl(fd, VIDIOC_STREAMON, &type); 361 if (rc) 362 { 363 log<level::ERR>("Failed to start streaming", 364 entry("ERROR=%s", strerror(errno))); 365 elog<ReadFailure>( 366 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( 367 errno), 368 xyz::openbmc_project::Common::Device::ReadFailure:: 369 CALLOUT_DEVICE_PATH(path.c_str())); 370 } 371 } 372 373 void Video::start() 374 { 375 int rc; 376 size_t oldHeight = height; 377 size_t oldWidth = width; 378 v4l2_capability cap; 379 v4l2_format fmt; 380 v4l2_streamparm sparm; 381 v4l2_control ctrl; 382 383 if (fd >= 0) 384 { 385 return; 386 } 387 388 input.sendWakeupPacket(); 389 390 fd = open(path.c_str(), O_RDWR); 391 if (fd < 0) 392 { 393 log<level::ERR>("Failed to open video device", 394 entry("PATH=%s", path.c_str()), 395 entry("ERROR=%s", strerror(errno))); 396 elog<Open>( 397 xyz::openbmc_project::Common::File::Open::ERRNO(errno), 398 xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); 399 } 400 401 memset(&cap, 0, sizeof(v4l2_capability)); 402 rc = ioctl(fd, VIDIOC_QUERYCAP, &cap); 403 if (rc < 0) 404 { 405 log<level::ERR>("Failed to query video device capabilities", 406 entry("ERROR=%s", strerror(errno))); 407 elog<ReadFailure>( 408 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( 409 errno), 410 xyz::openbmc_project::Common::Device::ReadFailure:: 411 CALLOUT_DEVICE_PATH(path.c_str())); 412 } 413 414 if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || 415 !(cap.capabilities & V4L2_CAP_STREAMING)) 416 { 417 log<level::ERR>("Video device doesn't support this application"); 418 elog<Open>( 419 xyz::openbmc_project::Common::File::Open::ERRNO(errno), 420 xyz::openbmc_project::Common::File::Open::PATH(path.c_str())); 421 } 422 423 memset(&fmt, 0, sizeof(v4l2_format)); 424 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 425 rc = ioctl(fd, VIDIOC_G_FMT, &fmt); 426 if (rc < 0) 427 { 428 log<level::ERR>("Failed to query video device format", 429 entry("ERROR=%s", strerror(errno))); 430 elog<ReadFailure>( 431 xyz::openbmc_project::Common::Device::ReadFailure::CALLOUT_ERRNO( 432 errno), 433 xyz::openbmc_project::Common::Device::ReadFailure:: 434 CALLOUT_DEVICE_PATH(path.c_str())); 435 } 436 437 memset(&sparm, 0, sizeof(v4l2_streamparm)); 438 sparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 439 sparm.parm.capture.timeperframe.numerator = 1; 440 sparm.parm.capture.timeperframe.denominator = frameRate; 441 rc = ioctl(fd, VIDIOC_S_PARM, &sparm); 442 if (rc < 0) 443 { 444 log<level::WARNING>("Failed to set video device frame rate", 445 entry("ERROR=%s", strerror(errno))); 446 } 447 448 ctrl.id = V4L2_CID_JPEG_CHROMA_SUBSAMPLING; 449 ctrl.value = subSampling ? V4L2_JPEG_CHROMA_SUBSAMPLING_420 450 : V4L2_JPEG_CHROMA_SUBSAMPLING_444; 451 rc = ioctl(fd, VIDIOC_S_CTRL, &ctrl); 452 if (rc < 0) 453 { 454 log<level::WARNING>("Failed to set video jpeg subsampling", 455 entry("ERROR=%s", strerror(errno))); 456 } 457 458 height = fmt.fmt.pix.height; 459 width = fmt.fmt.pix.width; 460 pixelformat = fmt.fmt.pix.pixelformat; 461 462 if (pixelformat != V4L2_PIX_FMT_RGB24 && pixelformat != V4L2_PIX_FMT_JPEG) 463 { 464 log<level::ERR>("Pixel Format not supported", 465 entry("PIXELFORMAT=%d", pixelformat)); 466 } 467 468 resize(); 469 470 if (oldHeight != height || oldWidth != width) 471 { 472 resizeAfterOpen = true; 473 } 474 } 475 476 void Video::stop() 477 { 478 int rc; 479 unsigned int i; 480 v4l2_buf_type type(V4L2_BUF_TYPE_VIDEO_CAPTURE); 481 482 if (fd < 0) 483 { 484 return; 485 } 486 487 lastFrameIndex = -1; 488 489 rc = ioctl(fd, VIDIOC_STREAMOFF, &type); 490 if (rc) 491 { 492 log<level::ERR>("Failed to stop streaming", 493 entry("ERROR=%s", strerror(errno))); 494 } 495 496 for (i = 0; i < buffers.size(); ++i) 497 { 498 if (buffers[i].data) 499 { 500 munmap(buffers[i].data, buffers[i].size); 501 buffers[i].data = nullptr; 502 buffers[i].queued = false; 503 } 504 } 505 506 close(fd); 507 fd = -1; 508 } 509 510 } // namespace ikvm 511