void GetUSBCamera::onSchedule()

in extensions/usb-camera/GetUSBCamera.cpp [95:313]


void GetUSBCamera::onSchedule(core::ProcessContext *context, core::ProcessSessionFactory *session_factory) {
  std::lock_guard<std::recursive_mutex> lock(*dev_access_mtx_);

  double default_fps = 1;
  double target_fps = default_fps;
  std::string conf_fps_str;
  context->getProperty("FPS", conf_fps_str);

  if (conf_fps_str.empty()) {
    logger_->log_info("FPS property was not set; using default %f", default_fps);
  } else {
    try {
      target_fps = std::stod(conf_fps_str);
    } catch (std::invalid_argument &e) {
      logger_->log_error("Could not parse configured FPS value (will use default %f): %s", default_fps, conf_fps_str);
    }
  }

  uint16_t target_width = 0;
  uint16_t target_height = 0;
  std::string conf_width_str;
  context->getProperty("Width", conf_width_str);

  if (!conf_width_str.empty()) {
    auto target_width_ul = std::stoul(conf_width_str);
    if (target_width_ul > UINT16_MAX) {
      logger_->log_error("Configured target width %s is out of range", conf_width_str);
    } else {
      target_width = static_cast<uint16_t>(target_width_ul);
    }
    logger_->log_info("Using configured target width: %i", target_width);
  }

  std::string conf_height_str;
  context->getProperty("Height", conf_height_str);

  if (!conf_height_str.empty()) {
    auto target_height_ul = std::stoul(conf_height_str);
    if (target_height_ul > UINT16_MAX) {
      logger_->log_error("Configured target height %s is out of range", conf_height_str);
    } else {
      target_height = static_cast<uint16_t>(target_height_ul);
    }
    logger_->log_info("Using configured target height: %i", target_height);
  }

  std::string conf_format_str;
  context->getProperty("Format", conf_format_str);

  int usb_vendor_id;
  std::string conf_vendor_id;
  context->getProperty("USB Vendor ID", conf_vendor_id);
  std::stringstream(conf_vendor_id) >> std::hex >> usb_vendor_id;
  logger_->log_info("Using USB Vendor ID: %x", usb_vendor_id);

  int usb_product_id;
  std::string conf_product_id;
  context->getProperty("USB Product ID", conf_product_id);
  std::stringstream(conf_product_id) >> std::hex >> usb_product_id;
  logger_->log_info("Using USB Product ID: %x", usb_product_id);

  const char *usb_serial_no = nullptr;
  std::string conf_serial;
  context->getProperty("USB Serial No.", conf_serial);

  if (!conf_serial.empty()) {
    usb_serial_no = conf_serial.c_str();
    logger_->log_info("Using USB Serial No.: %s", conf_serial);
  }

  cleanupUvc();
  logger_->log_info("Beginning to capture frames from USB camera");

  uvc_stream_ctrl_t ctrl{};
  uvc_error_t res;
  res = uvc_init(&ctx_, nullptr);

  if (res < 0) {
    logger_->log_error("Failed to initialize UVC service context");
    ctx_ = nullptr;
    return;
  }

  logger_->log_debug("UVC initialized");

  // Locate device
  res = uvc_find_device(ctx_, &dev_, usb_vendor_id, usb_product_id, usb_serial_no);

  if (res < 0) {
    logger_->log_error("Unable to find device: %s", uvc_strerror(res));
    dev_ = nullptr;
  } else {
    logger_->log_info("Device found");

    // Open the device
    res = uvc_open(dev_, &devh_);

    if (res < 0) {
      logger_->log_error("Unable to open device: %s", uvc_strerror(res));
      devh_ = nullptr;
    } else {
      logger_->log_info("Device opened");

      // Iterate resolutions & framerates >= context fps, or nearest
      uint16_t width = 0;
      uint16_t height = 0;
      uint32_t max_size = 0;
      uint32_t fps = 0;

      double min_diff = -1;
      double current_diff = -1;
      double current_width_diff = -1;
      double current_height_diff = -1;

      for (auto fmt_desc = uvc_get_format_descs(devh_); fmt_desc; fmt_desc = fmt_desc->next) {
        uvc_frame_desc_t *frame_desc;
        switch (fmt_desc->bDescriptorSubtype) {
          case UVC_VS_FORMAT_UNCOMPRESSED:
          case UVC_VS_FORMAT_FRAME_BASED:
            for (frame_desc = fmt_desc->frame_descs; frame_desc; frame_desc = frame_desc->next) {
              uint32_t frame_fps = 10000000 / frame_desc->dwDefaultFrameInterval;
              logger_->log_info("Discovered supported format %ix%i @ %i",
                                frame_desc->wWidth,
                                frame_desc->wHeight,
                                frame_fps);
              if (target_height > 0 && target_width > 0) {
                if (frame_fps >= target_fps) {
                  current_width_diff = abs(frame_desc->wWidth - target_width) / static_cast<double>(target_width);
                  logger_->log_debug("Current frame format width difference is %f", current_width_diff);
                  current_height_diff = abs(frame_desc->wHeight - target_height) / static_cast<double>(target_height);
                  logger_->log_debug("Current frame format height difference is %f", current_height_diff);
                  current_diff = (current_width_diff + current_height_diff) / 2;
                  logger_->log_debug("Current frame format difference is %f", current_diff);

                  if (min_diff < 0 || current_diff < min_diff) {
                    logger_->log_info("Format %ix%i @ %i is now closest to target",
                                      frame_desc->wWidth,
                                      frame_desc->wHeight,
                                      frame_fps);
                    width = frame_desc->wWidth;
                    height = frame_desc->wHeight;
                    max_size = frame_desc->dwMaxVideoFrameBufferSize;
                    fps = frame_fps;
                    min_diff = current_diff;
                  }
                }

              } else {
                if (frame_desc->dwMaxVideoFrameBufferSize > max_size && frame_fps >= target_fps) {
                  width = frame_desc->wWidth;
                  height = frame_desc->wHeight;
                  max_size = frame_desc->dwMaxVideoFrameBufferSize;
                  fps = frame_fps;
                }
              }
            }
            break;

          case UVC_VS_FORMAT_MJPEG:
            logger_->log_info("Skipping MJPEG frame formats");
            break;

          default:logger_->log_warn("Found unknown format");
        }
      }

      if (fps == 0) {
        logger_->log_error("Could not find suitable frame format from device. "
                           "Try changing configuration (lower FPS) or device.");
        return;
      }

      logger_->log_info("Negotiating stream profile (looking for %dx%d @ %d)", width, height, fps);

      res = uvc_get_stream_ctrl_format_size(devh_, &ctrl, UVC_FRAME_FORMAT_UNCOMPRESSED, width, height, fps);

      if (res < 0) {
        logger_->log_error("Failed to find a matching stream profile: %s", uvc_strerror(res));
      } else {
        cb_data_.session_factory = session_factory;

        if (frame_buffer_ != nullptr) {
          uvc_free_frame(frame_buffer_);
        }

        frame_buffer_ = uvc_allocate_frame(width * height * 3);

        if (!frame_buffer_) {
          printf("unable to allocate bgr frame!");
          logger_->log_error("Unable to allocate RGB frame");
          return;
        }

        cb_data_.frame_buffer = frame_buffer_;
        cb_data_.context = context;
        cb_data_.png_write_mtx = png_write_mtx_;
        cb_data_.dev_access_mtx = dev_access_mtx_;
        cb_data_.logger = logger_;
        cb_data_.format = conf_format_str;
        cb_data_.device_width = width;
        cb_data_.device_height = height;
        cb_data_.device_fps = fps;
        cb_data_.target_fps = target_fps;
        cb_data_.last_frame_time = std::chrono::steady_clock::time_point();

        res = uvc_start_streaming(devh_, &ctrl, onFrame, &cb_data_, 0);

        if (res < 0) {
          logger_->log_error("Unable to start streaming: %s", uvc_strerror(res));
        } else {
          logger_->log_info("Streaming...");

          // Enable auto-exposure
          uvc_set_ae_mode(devh_, 1);
        }
      }
    }
  }
}