v4l2-output.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. #define _GNU_SOURCE
  2. #include <obs-module.h>
  3. #include <util/dstr.h>
  4. #include <util/platform.h>
  5. #include <linux/videodev2.h>
  6. #include <sys/ioctl.h>
  7. #include <fcntl.h>
  8. #include <unistd.h>
  9. #include <dirent.h>
  10. #include <errno.h>
  11. #include <string.h>
  12. struct virtualcam_data {
  13. obs_output_t *output;
  14. int device;
  15. uint32_t frame_size;
  16. bool use_caps_workaround;
  17. };
  18. static const char *virtualcam_name(void *unused)
  19. {
  20. UNUSED_PARAMETER(unused);
  21. return "Virtual Camera Output";
  22. }
  23. static void virtualcam_destroy(void *data)
  24. {
  25. struct virtualcam_data *vcam = (struct virtualcam_data *)data;
  26. close(vcam->device);
  27. bfree(data);
  28. }
  29. static bool is_flatpak_sandbox(void)
  30. {
  31. static bool flatpak_info_exists = false;
  32. static bool initialized = false;
  33. if (!initialized) {
  34. flatpak_info_exists = access("/.flatpak-info", F_OK) == 0;
  35. initialized = true;
  36. }
  37. return flatpak_info_exists;
  38. }
  39. static int run_command(const char *command)
  40. {
  41. struct dstr str;
  42. int result;
  43. dstr_init_copy(&str, "PATH=\"$PATH:/sbin\" ");
  44. if (is_flatpak_sandbox())
  45. dstr_cat(&str, "flatpak-spawn --host ");
  46. dstr_cat(&str, command);
  47. result = system(str.array);
  48. dstr_free(&str);
  49. return result;
  50. }
  51. static bool loopback_module_loaded()
  52. {
  53. bool loaded = false;
  54. char temp[512];
  55. FILE *fp = fopen("/proc/modules", "r");
  56. if (!fp)
  57. return false;
  58. while (fgets(temp, sizeof(temp), fp)) {
  59. if (strstr(temp, "v4l2loopback")) {
  60. loaded = true;
  61. break;
  62. }
  63. }
  64. fclose(fp);
  65. return loaded;
  66. }
  67. bool loopback_module_available()
  68. {
  69. if (loopback_module_loaded()) {
  70. return true;
  71. }
  72. if (run_command("modinfo v4l2loopback >/dev/null 2>&1") == 0) {
  73. return true;
  74. }
  75. return false;
  76. }
  77. static int loopback_module_load()
  78. {
  79. return run_command(
  80. "pkexec modprobe v4l2loopback exclusive_caps=1 card_label='OBS Virtual Camera' && sleep 0.5");
  81. }
  82. static void *virtualcam_create(obs_data_t *settings, obs_output_t *output)
  83. {
  84. struct virtualcam_data *vcam = (struct virtualcam_data *)bzalloc(sizeof(*vcam));
  85. vcam->output = output;
  86. UNUSED_PARAMETER(settings);
  87. return vcam;
  88. }
  89. bool try_reset_output_caps(const char *device)
  90. {
  91. struct v4l2_capability capability;
  92. struct v4l2_format format;
  93. int fd;
  94. blog(LOG_INFO, "Attempting to reset output capability of '%s'", device);
  95. fd = open(device, O_RDWR);
  96. if (fd < 0)
  97. return false;
  98. format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
  99. if (ioctl(fd, VIDIOC_G_FMT, &format) < 0)
  100. goto fail_close_reset_caps_fd;
  101. if (ioctl(fd, VIDIOC_S_FMT, &format) < 0)
  102. goto fail_close_reset_caps_fd;
  103. if (ioctl(fd, VIDIOC_STREAMON, &format.type) < 0)
  104. goto fail_close_reset_caps_fd;
  105. if (ioctl(fd, VIDIOC_STREAMOFF, &format.type) < 0)
  106. goto fail_close_reset_caps_fd;
  107. close(fd);
  108. fd = open(device, O_RDWR);
  109. if (fd < 0)
  110. return false;
  111. if (ioctl(fd, VIDIOC_QUERYCAP, &capability) < 0)
  112. goto fail_close_reset_caps_fd;
  113. close(fd);
  114. return (capability.device_caps & V4L2_CAP_VIDEO_OUTPUT) != 0;
  115. fail_close_reset_caps_fd:
  116. close(fd);
  117. return false;
  118. }
  119. static bool try_connect(void *data, const char *device)
  120. {
  121. static bool use_caps_workaround = false;
  122. struct virtualcam_data *vcam = (struct virtualcam_data *)data;
  123. struct v4l2_capability capability;
  124. struct v4l2_format format;
  125. struct v4l2_streamparm parm;
  126. uint32_t width = obs_output_get_width(vcam->output);
  127. uint32_t height = obs_output_get_height(vcam->output);
  128. vcam->frame_size = width * height * 2;
  129. vcam->device = open(device, O_RDWR);
  130. if (vcam->device < 0)
  131. return false;
  132. if (ioctl(vcam->device, VIDIOC_QUERYCAP, &capability) < 0)
  133. goto fail_close_device;
  134. if (!use_caps_workaround && !(capability.device_caps & V4L2_CAP_VIDEO_OUTPUT)) {
  135. if (!try_reset_output_caps(device))
  136. goto fail_close_device;
  137. use_caps_workaround = true;
  138. }
  139. vcam->use_caps_workaround = use_caps_workaround;
  140. format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
  141. if (ioctl(vcam->device, VIDIOC_G_FMT, &format) < 0)
  142. goto fail_close_device;
  143. struct obs_video_info ovi;
  144. obs_get_video_info(&ovi);
  145. memset(&parm, 0, sizeof(parm));
  146. parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
  147. parm.parm.output.capability = V4L2_CAP_TIMEPERFRAME;
  148. parm.parm.output.timeperframe.numerator = ovi.fps_den;
  149. parm.parm.output.timeperframe.denominator = ovi.fps_num;
  150. if (ioctl(vcam->device, VIDIOC_S_PARM, &parm) < 0)
  151. goto fail_close_device;
  152. format.fmt.pix.width = width;
  153. format.fmt.pix.height = height;
  154. format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
  155. format.fmt.pix.sizeimage = vcam->frame_size;
  156. if (ioctl(vcam->device, VIDIOC_S_FMT, &format) < 0)
  157. goto fail_close_device;
  158. struct video_scale_info vsi = {0};
  159. vsi.format = VIDEO_FORMAT_YUY2;
  160. vsi.width = width;
  161. vsi.height = height;
  162. obs_output_set_video_conversion(vcam->output, &vsi);
  163. memset(&parm, 0, sizeof(parm));
  164. parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
  165. if (vcam->use_caps_workaround && ioctl(vcam->device, VIDIOC_STREAMON, &format.type) < 0) {
  166. blog(LOG_ERROR, "Failed to start streaming on '%s' (%s)", device, strerror(errno));
  167. goto fail_close_device;
  168. }
  169. blog(LOG_INFO, "Virtual camera started");
  170. obs_output_begin_data_capture(vcam->output, 0);
  171. return true;
  172. fail_close_device:
  173. close(vcam->device);
  174. return false;
  175. }
  176. static int scanfilter(const struct dirent *entry)
  177. {
  178. return !astrcmp_n(entry->d_name, "video", 5);
  179. }
  180. static bool virtualcam_start(void *data)
  181. {
  182. struct virtualcam_data *vcam = (struct virtualcam_data *)data;
  183. struct dirent **list;
  184. bool success = false;
  185. int n;
  186. if (!loopback_module_loaded()) {
  187. if (loopback_module_load() != 0)
  188. return false;
  189. }
  190. n = scandir("/dev", &list, scanfilter,
  191. #if defined(__linux__)
  192. versionsort
  193. #else
  194. alphasort
  195. #endif
  196. );
  197. if (n == -1)
  198. return false;
  199. for (int i = 0; i < n; i++) {
  200. char device[32] = {0};
  201. // Use the return value of snprintf to prevent truncation warning.
  202. int written = snprintf(device, 32, "/dev/%s", list[i]->d_name);
  203. if (written >= 32)
  204. blog(LOG_DEBUG, "v4l2-output: A format truncation may have occurred."
  205. " This can be ignored since it is quite improbable.");
  206. if (try_connect(vcam, device)) {
  207. success = true;
  208. break;
  209. }
  210. }
  211. while (n--)
  212. free(list[n]);
  213. free(list);
  214. if (!success)
  215. blog(LOG_WARNING, "Failed to start virtual camera");
  216. return success;
  217. }
  218. static void virtualcam_stop(void *data, uint64_t ts)
  219. {
  220. struct virtualcam_data *vcam = (struct virtualcam_data *)data;
  221. obs_output_end_data_capture(vcam->output);
  222. uint32_t buf_type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
  223. if (vcam->use_caps_workaround && ioctl(vcam->device, VIDIOC_STREAMOFF, buf_type) < 0) {
  224. blog(LOG_WARNING, "Failed to stop streaming on video device %d (%s)", vcam->device, strerror(errno));
  225. }
  226. close(vcam->device);
  227. blog(LOG_INFO, "Virtual camera stopped");
  228. UNUSED_PARAMETER(ts);
  229. }
  230. static void virtual_video(void *param, struct video_data *frame)
  231. {
  232. struct virtualcam_data *vcam = (struct virtualcam_data *)param;
  233. uint32_t frame_size = vcam->frame_size;
  234. while (frame_size > 0) {
  235. ssize_t written = write(vcam->device, frame->data[0], vcam->frame_size);
  236. if (written == -1)
  237. break;
  238. frame_size -= written;
  239. }
  240. }
  241. struct obs_output_info virtualcam_info = {
  242. .id = "virtualcam_output",
  243. .flags = OBS_OUTPUT_VIDEO,
  244. .get_name = virtualcam_name,
  245. .create = virtualcam_create,
  246. .destroy = virtualcam_destroy,
  247. .start = virtualcam_start,
  248. .stop = virtualcam_stop,
  249. .raw_video = virtual_video,
  250. };