uvc_camera.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #include "uvc_camera_impl.h"
  2. #include "core/utility.hpp"
  3. #include "third_party/scope_guard.hpp"
  4. #include "image_process_v5/sp_image.h"
  5. #include "image_process_v5/image_process.h"
  6. #include <opencv2/imgcodecs.hpp>
  7. #include <opencv2/imgproc.hpp>
  8. #include <libusb.h>
  9. #include <fmt/format.h>
  10. #include <spdlog/spdlog.h>
  11. #include <boost/asio/post.hpp>
  12. namespace uvc_camera_impl {
  13. bool check_api_call(uvc_error_t api_ret, unsigned int line_number,
  14. const char *file_name, const char *api_call_str) {
  15. if (api_ret == UVC_SUCCESS) [[likely]] return true;
  16. SPDLOG_ERROR("libuvc api call {} failed at {}:{} with error 0x{:x}",
  17. api_call_str, file_name, line_number, (int) api_ret);
  18. return false;
  19. }
  20. #define API_CHECK(api_call) \
  21. check_api_call( \
  22. api_call, __LINE__, __FILE__, #api_call)
  23. #define API_CHECK_P(api_call) \
  24. if (!check_api_call( \
  25. api_call, __LINE__, __FILE__, #api_call)) [[unlikely]] \
  26. return nullptr
  27. uvc_context_t *uvc_ctx = nullptr;
  28. void free_device_list(dev_info_list_type::element_type *data) {
  29. for (auto &item: *data) {
  30. uvc_unref_device((uvc_device_t *) item.h_dev);
  31. }
  32. delete data;
  33. }
  34. dev_info_list_type get_device_list() {
  35. if (uvc_ctx == nullptr) [[unlikely]] {
  36. API_CHECK_P(uvc_init(&uvc_ctx, nullptr));
  37. std::atexit([] { uvc_exit(uvc_ctx); });
  38. }
  39. assert(uvc_ctx != nullptr);
  40. uvc_device_t **dev_list = nullptr;
  41. auto dev_list_closer = sg::make_scope_guard([&] {
  42. if (dev_list == nullptr) return;
  43. uvc_free_device_list(dev_list, 1);
  44. });
  45. API_CHECK_P(uvc_find_devices(uvc_ctx, &dev_list, 0, 0, nullptr));
  46. auto dev_info_list =
  47. dev_info_list_type(new dev_info_list_type::element_type(),
  48. [](auto ptr) { free_device_list(ptr); });
  49. for (auto p_dev = dev_list; *p_dev != nullptr; ++p_dev) {
  50. auto dev = *p_dev;
  51. uvc_device_descriptor_t *dev_desc = nullptr;
  52. auto dev_desc_closer = sg::make_scope_guard([&] {
  53. if (dev_desc == nullptr) return;
  54. uvc_free_device_descriptor(dev_desc);
  55. });
  56. auto safe_str = [](const char *str) -> auto {
  57. return (str == nullptr || *str == 0) ? "??" : str;
  58. };
  59. API_CHECK_P(uvc_get_device_descriptor(dev, &dev_desc));
  60. assert(dev_desc != nullptr);
  61. std::string dev_name;
  62. if (dev_desc->serialNumber != nullptr) {
  63. dev_name = fmt::format("{}: {}, {} ({})",
  64. p_dev - dev_list,
  65. safe_str(dev_desc->manufacturer),
  66. safe_str(dev_desc->product),
  67. safe_str(dev_desc->serialNumber));
  68. } else {
  69. dev_name = fmt::format("{:x}:{:x}",
  70. dev_desc->idVendor, dev_desc->idProduct);
  71. }
  72. uvc_ref_device(dev);
  73. dev_info_list->emplace_back(dev, dev_name);
  74. }
  75. return dev_info_list;
  76. }
  77. sp_image mjpeg_frame_to_image(uvc_frame_t *frame) { // TODO: accelerate with CUDA
  78. assert(frame->frame_format == UVC_FRAME_FORMAT_MJPEG);
  79. auto img_data = cv::_InputArray((uchar *) frame->data, frame->data_bytes);
  80. auto img_bgr = cv::imdecode(img_data, cv::IMREAD_UNCHANGED);
  81. if (img_bgr.empty()) return {};
  82. const auto img_rgb = sp_image::create(CV_8UC3, img_bgr.size());
  83. const auto helper = write_access_helper(img_rgb.host());
  84. auto img_rgb_mat = img_rgb.cv_mat(helper.ptr());
  85. cv::cvtColor(img_bgr, img_rgb_mat, cv::COLOR_BGR2RGB);
  86. return img_rgb;
  87. }
  88. sp_image yuyv_frame_to_image(uvc_frame_t *frame) {
  89. assert(frame->frame_format == UVC_FRAME_FORMAT_YUYV);
  90. auto img_size = cv::Size(frame->width, frame->height);
  91. if (img_size.area() * sizeof(uchar2) != frame->data_bytes) return {};
  92. return sp_image::create<uchar2>(img_size, frame->data);
  93. }
  94. }
  95. uvc_camera::impl::~impl() {
  96. if (is_capturing) {
  97. stop();
  98. }
  99. if (dev_h != nullptr) {
  100. uvc_close(dev_h);
  101. }
  102. if (dev != nullptr) {
  103. uvc_unref_device(dev);
  104. }
  105. }
  106. std::unique_ptr<uvc_camera::impl>
  107. uvc_camera::impl::create(create_config conf) {
  108. assert(uvc_ctx != nullptr);
  109. auto ret = std::make_unique<impl>();
  110. ret->conf = conf;
  111. ret->dev = (uvc_device_t *) conf.h_dev;
  112. uvc_ref_device(ret->dev);
  113. API_CHECK_P(uvc_open(ret->dev, &ret->dev_h));
  114. #ifndef NDEBUG
  115. uvc_print_diag(ret->dev_h, stdout);
  116. #endif
  117. // create stream list
  118. ret->stream_list =
  119. std::make_shared<stream_info_list_type::element_type>();
  120. for (auto fmt_desc = uvc_get_format_descs(ret->dev_h);
  121. fmt_desc != nullptr; fmt_desc = fmt_desc->next) {
  122. auto fmt_name = std::string((char *) fmt_desc->fourccFormat, 4);
  123. for (auto f_desc = fmt_desc->frame_descs;
  124. f_desc != nullptr; f_desc = f_desc->next) {
  125. auto fps = 1e7 / f_desc->dwDefaultFrameInterval;
  126. ret->stream_list->emplace_back(
  127. fmt_name, f_desc->wWidth, f_desc->wHeight, fps, f_desc);
  128. }
  129. }
  130. return ret;
  131. }
  132. void uvc_camera::impl::frame_callback(uvc_frame *frame) {
  133. switch (frame->frame_format) {
  134. case UVC_FRAME_FORMAT_MJPEG: {
  135. auto img = mjpeg_frame_to_image(frame);
  136. OBJ_SAVE(img_name, img);
  137. break;
  138. }
  139. case UVC_FRAME_FORMAT_YUYV: {
  140. auto img_yuyv = yuyv_frame_to_image(frame);
  141. if (img_yuyv.empty()) break;
  142. boost::asio::post(*conf.ctx, [=, this] { // handle CUDA context problem
  143. // TODO: check why CUDA commands cannot be used in this thread
  144. auto img_rgb = image_yuyv_to_rgb(img_yuyv);
  145. OBJ_SAVE(img_name, img_rgb);
  146. });
  147. break;
  148. }
  149. default: {
  150. RET_ERROR;
  151. }
  152. }
  153. }
  154. void uvc_camera::impl::start(start_config conf) {
  155. assert(!is_capturing);
  156. // reset usb device to make it work
  157. auto usb_dev_h = uvc_get_libusb_handle(dev_h);
  158. auto usb_ret = libusb_reset_device(usb_dev_h);
  159. assert(usb_ret == LIBUSB_SUCCESS);
  160. auto f_desc = (uvc_frame_desc_t *) conf.h_desc;
  161. auto fmt_desc = f_desc->parent;
  162. uvc_frame_format fmt = UVC_FRAME_FORMAT_ANY;
  163. switch (fmt_desc->bDescriptorSubtype) {
  164. // @formatter:off
  165. case UVC_VS_FORMAT_MJPEG: { fmt = UVC_FRAME_FORMAT_MJPEG; break; }
  166. case UVC_VS_FORMAT_UNCOMPRESSED: { fmt = UVC_FRAME_FORMAT_YUYV; break; }
  167. // @formatter:on
  168. default: {
  169. RET_ERROR;
  170. }
  171. }
  172. auto fps = 1e7 / f_desc->dwDefaultFrameInterval;
  173. API_CHECK(uvc_get_stream_ctrl_format_size(dev_h, &stream_ctrl, fmt,
  174. f_desc->wWidth, f_desc->wHeight, fps));
  175. #ifndef NDEBUG
  176. uvc_print_stream_ctrl(&stream_ctrl, stdout);
  177. #endif
  178. img_name = conf.img_name;
  179. API_CHECK(uvc_start_streaming(dev_h, &stream_ctrl, [](auto frame, void *user) {
  180. ((impl *) user)->frame_callback(frame);
  181. }, this, 0));
  182. is_capturing = true;
  183. }
  184. void uvc_camera::impl::stop() {
  185. assert(is_capturing);
  186. uvc_stop_streaming(dev_h);
  187. is_capturing = false;
  188. }
  189. dev_info_list_type uvc_camera::get_device_list() {
  190. return ::get_device_list();
  191. }
  192. uvc_camera::pointer uvc_camera::create(create_config conf) {
  193. auto pimpl = impl::create(conf);
  194. if (pimpl == nullptr) return nullptr;
  195. auto ret = std::make_unique<this_type>();
  196. ret->pimpl = std::move(pimpl);
  197. return ret;
  198. }
  199. uvc_camera::~uvc_camera() = default;
  200. uvc_camera::stream_info_list_type
  201. uvc_camera::get_stream_list() {
  202. return pimpl->stream_list;
  203. }
  204. void uvc_camera::start(start_config conf) {
  205. pimpl->start(conf);
  206. }
  207. bool uvc_camera::is_capturing() const {
  208. return pimpl->is_capturing;
  209. }
  210. void uvc_camera::stop() {
  211. pimpl->stop();
  212. }
  213. uvc_camera::stream_info_type::operator std::string() {
  214. return fmt::format("{}x{} @ {}fps ({})", width, height, fps, fmt_name);
  215. }