win-dshow.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. #include <objbase.h>
  2. #include <obs-module.h>
  3. #include <obs.hpp>
  4. #include <util/dstr.hpp>
  5. #include <util/util.hpp>
  6. #include <util/platform.h>
  7. #include "libdshowcapture/dshowcapture.hpp"
  8. #include <algorithm>
  9. #include <limits>
  10. #include <set>
  11. #include <string>
  12. #include <vector>
  13. /*
  14. * TODO:
  15. * - handle disconnections and reconnections
  16. * - if device not present, wait for device to be plugged in
  17. */
  18. #undef min
  19. #undef max
  20. using namespace std;
  21. using namespace DShow;
  22. /* settings defines that will cause errors if there are typos */
  23. #define VIDEO_DEVICE_ID "video_device_id"
  24. #define RES_TYPE "res_type"
  25. #define RESOLUTION "resolution"
  26. #define FRAME_INTERVAL "frame_interval"
  27. #define VIDEO_FORMAT "video_format"
  28. #define LAST_VIDEO_DEV_ID "last_video_device_id"
  29. #define LAST_RESOLUTION "last_resolution"
  30. enum ResType {
  31. ResType_Preferred,
  32. ResType_Custom
  33. };
  34. struct DShowInput {
  35. obs_source_t source;
  36. Device device;
  37. bool comInitialized;
  38. VideoConfig videoConfig;
  39. AudioConfig audioConfig;
  40. source_frame frame;
  41. inline DShowInput(obs_source_t source_)
  42. : source (source_),
  43. device (InitGraph::False),
  44. comInitialized (false)
  45. {}
  46. static void OnVideoData(DShowInput *input, unsigned char *data,
  47. size_t size, long long startTime, long long endTime);
  48. void Update(obs_data_t settings);
  49. };
  50. template <typename T, typename U, typename V>
  51. static bool between(T &&lower, U &&value, V &&upper)
  52. {
  53. return value >= lower && value <= upper;
  54. }
  55. static bool ResolutionAvailable(const VideoInfo &cap, int cx, int cy)
  56. {
  57. return between(cap.minCX, cx, cap.maxCX) &&
  58. between(cap.minCY, cy, cap.maxCY);
  59. }
  60. #define DEVICE_INTERVAL_DIFF_LIMIT 20
  61. static bool FrameRateAvailable(const VideoInfo &cap, long long interval)
  62. {
  63. return between(cap.minInterval - DEVICE_INTERVAL_DIFF_LIMIT,
  64. interval,
  65. cap.maxInterval + DEVICE_INTERVAL_DIFF_LIMIT);
  66. }
  67. static long long FrameRateInterval(const VideoInfo &cap,
  68. long long desired_interval)
  69. {
  70. return desired_interval < cap.minInterval ?
  71. cap.minInterval :
  72. min(desired_interval, cap.maxInterval);
  73. }
  74. void encode_dstr(struct dstr *str)
  75. {
  76. dstr_replace(str, "#", "#22");
  77. dstr_replace(str, ":", "#3A");
  78. }
  79. void decode_dstr(struct dstr *str)
  80. {
  81. dstr_replace(str, "#3A", ":");
  82. dstr_replace(str, "#22", "#");
  83. }
  84. static inline video_format ConvertVideoFormat(VideoFormat format)
  85. {
  86. switch (format) {
  87. case VideoFormat::ARGB: return VIDEO_FORMAT_BGRA;
  88. case VideoFormat::XRGB: return VIDEO_FORMAT_BGRX;
  89. case VideoFormat::I420: return VIDEO_FORMAT_I420;
  90. case VideoFormat::NV12: return VIDEO_FORMAT_NV12;
  91. case VideoFormat::YVYU: return VIDEO_FORMAT_UYVY;
  92. case VideoFormat::YUY2: return VIDEO_FORMAT_YUY2;
  93. case VideoFormat::UYVY: return VIDEO_FORMAT_YVYU;
  94. case VideoFormat::MJPEG: return VIDEO_FORMAT_YUY2;
  95. default: return VIDEO_FORMAT_NONE;
  96. }
  97. }
  98. void DShowInput::OnVideoData(DShowInput *input, unsigned char *data,
  99. size_t size, long long startTime, long long endTime)
  100. {
  101. const int cx = input->videoConfig.cx;
  102. const int cy = input->videoConfig.cy;
  103. input->frame.timestamp = (uint64_t)startTime * 100;
  104. if (input->videoConfig.format == VideoFormat::XRGB ||
  105. input->videoConfig.format == VideoFormat::ARGB) {
  106. input->frame.data[0] = data;
  107. input->frame.linesize[0] = cx * 4;
  108. } else if (input->videoConfig.format == VideoFormat::YVYU ||
  109. input->videoConfig.format == VideoFormat::YUY2 ||
  110. input->videoConfig.format == VideoFormat::UYVY) {
  111. input->frame.data[0] = data;
  112. input->frame.linesize[0] = cx * 2;
  113. } else if (input->videoConfig.format == VideoFormat::I420) {
  114. input->frame.data[0] = data;
  115. input->frame.data[1] = input->frame.data[0] + (cx * cy);
  116. input->frame.data[2] = input->frame.data[1] + (cx * cy / 4);
  117. input->frame.linesize[0] = cx;
  118. input->frame.linesize[1] = cx / 2;
  119. input->frame.linesize[2] = cx / 2;
  120. } else {
  121. /* TODO: other formats */
  122. return;
  123. }
  124. obs_source_output_video(input->source, &input->frame);
  125. UNUSED_PARAMETER(endTime); /* it's the enndd tiimmes! */
  126. UNUSED_PARAMETER(size);
  127. }
  128. static bool DecodeDeviceId(DStr &name, DStr &path, const char *device_id)
  129. {
  130. const char *path_str;
  131. if (!device_id || !*device_id)
  132. return false;
  133. path_str = strchr(device_id, ':');
  134. if (!path_str)
  135. return false;
  136. dstr_copy(path, path_str+1);
  137. dstr_copy(name, device_id);
  138. size_t len = path_str - device_id;
  139. name->array[len] = 0;
  140. name->len = len;
  141. decode_dstr(name);
  142. decode_dstr(path);
  143. return true;
  144. }
  145. static bool DecodeDeviceId(DeviceId &out, const char *device_id)
  146. {
  147. DStr name, path;
  148. if (!DecodeDeviceId(name, path, device_id))
  149. return false;
  150. BPtr<wchar_t> wname = dstr_to_wcs(name);
  151. out.name = wname;
  152. if (!dstr_isempty(path)) {
  153. BPtr<wchar_t> wpath = dstr_to_wcs(path);
  154. out.path = wpath;
  155. }
  156. return true;
  157. }
  158. struct PropertiesData {
  159. vector<VideoDevice> devices;
  160. bool GetDevice(VideoDevice &device, const char *encoded_id) const
  161. {
  162. DeviceId deviceId;
  163. DecodeDeviceId(deviceId, encoded_id);
  164. for (const VideoDevice &curDevice : devices) {
  165. if (deviceId.name == curDevice.name &&
  166. deviceId.path == curDevice.path) {
  167. device = curDevice;
  168. return true;
  169. }
  170. }
  171. return false;
  172. }
  173. };
  174. static inline bool ConvertRes(int &cx, int &cy, const char *res)
  175. {
  176. return sscanf(res, "%dx%d", &cx, &cy) == 2;
  177. }
  178. static bool FormatMatches(VideoFormat left, VideoFormat right)
  179. {
  180. return left == VideoFormat::Any || right == VideoFormat::Any ||
  181. left == right;
  182. }
  183. static bool ResolutionValid(string res, int &cx, int &cy)
  184. {
  185. if (!res.size())
  186. return false;
  187. return ConvertRes(cx, cy, res.c_str());
  188. }
  189. template <typename F, typename ... Fs>
  190. static bool CapsMatch(const VideoInfo &info, F&& f, Fs ... fs)
  191. {
  192. return f(info) && CapsMatch(info, fs ...);
  193. }
  194. static bool CapsMatch(const VideoInfo&)
  195. {
  196. return true;
  197. }
  198. template <typename ... F>
  199. static bool CapsMatch(const VideoDevice &dev, F ... fs)
  200. {
  201. auto matcher = [&](const VideoInfo &info)
  202. {
  203. return CapsMatch(info, fs ...);
  204. };
  205. return any_of(begin(dev.caps), end(dev.caps), matcher);
  206. }
  207. bool MatcherMatchVideoFormat(VideoFormat format, bool &did_match,
  208. const VideoInfo &info)
  209. {
  210. bool match = FormatMatches(format, info.format);
  211. did_match = did_match || match;
  212. return match;
  213. }
  214. bool MatcherClosestFrameRateSelector(long long interval, long long &best_match,
  215. const VideoInfo &info)
  216. {
  217. long long current = FrameRateInterval(info, interval);
  218. if (llabs(interval - best_match) > llabs(interval - current))
  219. best_match = current;
  220. return true;
  221. }
  222. #if 0
  223. auto ResolutionMatcher = [](int cx, int cy)
  224. {
  225. return [cx, cy](const VideoInfo &info)
  226. {
  227. return ResolutionAvailable(info, cx, cy);
  228. };
  229. };
  230. auto FrameRateMatcher = [](long long interval)
  231. {
  232. return [interval](const VideoInfo &info)
  233. {
  234. return FrameRateAvailable(info, interval);
  235. };
  236. };
  237. auto VideoFormatMatcher = [](VideoFormat format, bool &did_match)
  238. {
  239. return [format, &did_match](const VideoInfo &info)
  240. {
  241. return MatcherMatchVideoFormat(format, did_match, info);
  242. };
  243. };
  244. auto ClosestFrameRateSelector = [](long long interval, long long &best_match)
  245. {
  246. return [interval, &best_match](const VideoInfo &info) mutable -> bool
  247. {
  248. MatcherClosestFrameRateSelector(interval, best_match, info);
  249. };
  250. }
  251. #else
  252. #define ResolutionMatcher(cx, cy) \
  253. [cx, cy](const VideoInfo &info) -> bool \
  254. { return ResolutionAvailable(info, cx, cy); }
  255. #define FrameRateMatcher(interval) \
  256. [interval](const VideoInfo &info) -> bool \
  257. { return FrameRateAvailable(info, interval); }
  258. #define VideoFormatMatcher(format, did_match) \
  259. [format, &did_match](const VideoInfo &info) mutable -> bool \
  260. { return MatcherMatchVideoFormat(format, did_match, info); }
  261. #define ClosestFrameRateSelector(interval, best_match) \
  262. [interval, &best_match](const VideoInfo &info) mutable -> bool \
  263. { return MatcherClosestFrameRateSelector(interval, best_match, info); }
  264. #endif
  265. static bool ResolutionAvailable(const VideoDevice &dev, int cx, int cy)
  266. {
  267. return CapsMatch(dev, ResolutionMatcher(cx, cy));
  268. }
  269. static bool DetermineResolution(int &cx, int &cy, obs_data_t settings,
  270. VideoDevice dev)
  271. {
  272. const char *res = obs_data_get_autoselect_string(settings, RESOLUTION);
  273. if (obs_data_has_autoselect(settings, RESOLUTION) &&
  274. ConvertRes(cx, cy, res) &&
  275. ResolutionAvailable(dev, cx, cy))
  276. return true;
  277. res = obs_data_getstring(settings, RESOLUTION);
  278. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  279. return true;
  280. res = obs_data_getstring(settings, LAST_RESOLUTION);
  281. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  282. return true;
  283. return false;
  284. }
  285. void DShowInput::Update(obs_data_t settings)
  286. {
  287. string video_device_id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
  288. if (!comInitialized) {
  289. CoInitialize(nullptr);
  290. comInitialized = true;
  291. }
  292. if (!device.ResetGraph())
  293. return;
  294. DeviceId id;
  295. if (!DecodeDeviceId(id, video_device_id.c_str()))
  296. return;
  297. PropertiesData data;
  298. Device::EnumVideoDevices(data.devices);
  299. VideoDevice dev;
  300. if (!data.GetDevice(dev, video_device_id.c_str()))
  301. return;
  302. int resType = (int)obs_data_getint(settings, RES_TYPE);
  303. int cx = 0, cy = 0;
  304. long long interval = 0;
  305. VideoFormat format = VideoFormat::Any;
  306. if (resType == ResType_Custom) {
  307. string resolution = obs_data_getstring(settings, RESOLUTION);
  308. if (!ResolutionValid(resolution, cx, cy))
  309. return;
  310. interval = obs_data_has_autoselect(settings, FRAME_INTERVAL) ?
  311. obs_data_get_autoselect_int(settings, FRAME_INTERVAL) :
  312. obs_data_getint(settings, FRAME_INTERVAL);
  313. format = (VideoFormat)obs_data_getint(settings, VIDEO_FORMAT);
  314. long long best_interval = numeric_limits<long long>::max();
  315. bool video_format_match = false;
  316. if (!CapsMatch(dev,
  317. ResolutionMatcher(cx, cy),
  318. VideoFormatMatcher(format, video_format_match),
  319. ClosestFrameRateSelector(interval, best_interval),
  320. FrameRateMatcher(interval)) && !video_format_match)
  321. return;
  322. interval = best_interval;
  323. blog(LOG_INFO, "%s: Using interval %lld",
  324. obs_source_getname(source), interval);
  325. }
  326. videoConfig.name = id.name.c_str();
  327. videoConfig.path = id.path.c_str();
  328. videoConfig.callback = CaptureProc(DShowInput::OnVideoData);
  329. videoConfig.param = this;
  330. videoConfig.useDefaultConfig = resType == ResType_Preferred;
  331. videoConfig.cx = cx;
  332. videoConfig.cy = cy;
  333. videoConfig.frameInterval = interval;
  334. videoConfig.internalFormat = format;
  335. if (videoConfig.internalFormat != VideoFormat::MJPEG)
  336. videoConfig.format = videoConfig.internalFormat;
  337. device.SetVideoConfig(&videoConfig);
  338. if (videoConfig.internalFormat == VideoFormat::MJPEG) {
  339. videoConfig.format = VideoFormat::XRGB;
  340. device.SetVideoConfig(&videoConfig);
  341. }
  342. if (!device.ConnectFilters())
  343. return;
  344. if (device.Start() != Result::Success)
  345. return;
  346. frame.width = videoConfig.cx;
  347. frame.height = videoConfig.cy;
  348. frame.format = ConvertVideoFormat(videoConfig.format);
  349. frame.full_range = false;
  350. frame.flip = (videoConfig.format == VideoFormat::XRGB ||
  351. videoConfig.format == VideoFormat::ARGB);
  352. if (!video_format_get_parameters(VIDEO_CS_601, VIDEO_RANGE_PARTIAL,
  353. frame.color_matrix,
  354. frame.color_range_min,
  355. frame.color_range_max)) {
  356. blog(LOG_ERROR, "Failed to get video format parameters for " \
  357. "video format %u", VIDEO_CS_601);
  358. }
  359. }
  360. /* ------------------------------------------------------------------------- */
  361. static const char *GetDShowInputName(void)
  362. {
  363. /* TODO: locale */
  364. return "Video Capture Device";
  365. }
  366. static void *CreateDShowInput(obs_data_t settings, obs_source_t source)
  367. {
  368. DShowInput *dshow = new DShowInput(source);
  369. /* causes a deferred update in the video thread */
  370. obs_source_update(source, nullptr);
  371. UNUSED_PARAMETER(settings);
  372. return dshow;
  373. }
  374. static void DestroyDShowInput(void *data)
  375. {
  376. delete reinterpret_cast<DShowInput*>(data);
  377. }
  378. static uint32_t GetDShowWidth(void *data)
  379. {
  380. return reinterpret_cast<DShowInput*>(data)->videoConfig.cx;
  381. }
  382. static uint32_t GetDShowHeight(void *data)
  383. {
  384. return reinterpret_cast<DShowInput*>(data)->videoConfig.cy;
  385. }
  386. static void UpdateDShowInput(void *data, obs_data_t settings)
  387. {
  388. reinterpret_cast<DShowInput*>(data)->Update(settings);
  389. }
  390. static void GetDShowDefaults(obs_data_t settings)
  391. {
  392. obs_data_set_default_int(settings, RES_TYPE, ResType_Preferred);
  393. obs_data_set_default_int(settings, VIDEO_FORMAT, (int)VideoFormat::Any);
  394. }
  395. struct Resolution {
  396. int cx, cy;
  397. inline Resolution(int cx, int cy) : cx(cx), cy(cy) {}
  398. };
  399. static void InsertResolution(vector<Resolution> &resolutions, int cx, int cy)
  400. {
  401. int bestCY = 0;
  402. size_t idx = 0;
  403. for (; idx < resolutions.size(); idx++) {
  404. const Resolution &res = resolutions[idx];
  405. if (res.cx > cx)
  406. break;
  407. if (res.cx == cx) {
  408. if (res.cy == cy)
  409. return;
  410. if (!bestCY)
  411. bestCY = res.cy;
  412. else if (res.cy > bestCY)
  413. break;
  414. }
  415. }
  416. resolutions.insert(resolutions.begin() + idx, Resolution(cx, cy));
  417. }
  418. static inline void AddCap(vector<Resolution> &resolutions, const VideoInfo &cap)
  419. {
  420. InsertResolution(resolutions, cap.minCX, cap.minCY);
  421. InsertResolution(resolutions, cap.maxCX, cap.maxCY);
  422. }
  423. #define MAKE_DSHOW_FPS(fps) (10000000LL/(fps))
  424. #define MAKE_DSHOW_FRACTIONAL_FPS(den, num) ((num)*10000000LL/(den))
  425. struct FPSFormat {
  426. const char *text;
  427. long long interval;
  428. };
  429. static const FPSFormat validFPSFormats[] = {
  430. {"60", MAKE_DSHOW_FPS(60)},
  431. {"59.94 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(60000, 1001)},
  432. {"50", MAKE_DSHOW_FPS(50)},
  433. {"48 film", MAKE_DSHOW_FRACTIONAL_FPS(48000, 1001)},
  434. {"40", MAKE_DSHOW_FPS(40)},
  435. {"30", MAKE_DSHOW_FPS(30)},
  436. {"29.97 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(30000, 1001)},
  437. {"25", MAKE_DSHOW_FPS(25)},
  438. {"24 film", MAKE_DSHOW_FRACTIONAL_FPS(24000, 1001)},
  439. {"20", MAKE_DSHOW_FPS(20)},
  440. {"15", MAKE_DSHOW_FPS(15)},
  441. {"10", MAKE_DSHOW_FPS(10)},
  442. {"5", MAKE_DSHOW_FPS(5)},
  443. {"4", MAKE_DSHOW_FPS(4)},
  444. {"3", MAKE_DSHOW_FPS(3)},
  445. {"2", MAKE_DSHOW_FPS(2)},
  446. {"1", MAKE_DSHOW_FPS(1)},
  447. };
  448. static bool DeviceIntervalChanged(obs_properties_t props, obs_property_t p,
  449. obs_data_t settings);
  450. static bool TryResolution(VideoDevice &dev, string res)
  451. {
  452. int cx, cy;
  453. if (!ConvertRes(cx, cy, res.c_str()))
  454. return false;
  455. return ResolutionAvailable(dev, cx, cy);
  456. }
  457. static bool SetResolution(obs_properties_t props, obs_data_t settings,
  458. string res, bool autoselect=false)
  459. {
  460. if (autoselect)
  461. obs_data_set_autoselect_string(settings, RESOLUTION,
  462. res.c_str());
  463. else
  464. obs_data_unset_autoselect_value(settings, RESOLUTION);
  465. DeviceIntervalChanged(props, obs_properties_get(props, FRAME_INTERVAL),
  466. settings);
  467. if (!autoselect)
  468. obs_data_setstring(settings, LAST_RESOLUTION, res.c_str());
  469. return true;
  470. }
  471. static bool DeviceResolutionChanged(obs_properties_t props, obs_property_t p,
  472. obs_data_t settings)
  473. {
  474. UNUSED_PARAMETER(p);
  475. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  476. const char *id;
  477. VideoDevice device;
  478. id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
  479. string res = obs_data_getstring(settings, RESOLUTION);
  480. string last_res = obs_data_getstring(settings, LAST_RESOLUTION);
  481. if (!data->GetDevice(device, id))
  482. return false;
  483. if (TryResolution(device, res))
  484. return SetResolution(props, settings, res);
  485. if (TryResolution(device, last_res))
  486. return SetResolution(props, settings, last_res, true);
  487. return false;
  488. }
  489. struct VideoFormatName {
  490. VideoFormat format;
  491. const char *name;
  492. };
  493. static const VideoFormatName videoFormatNames[] = {
  494. /* autoselect format*/
  495. {VideoFormat::Any, "Any"},
  496. /* raw formats */
  497. {VideoFormat::ARGB, "ARGB"},
  498. {VideoFormat::XRGB, "XRGB"},
  499. /* planar YUV formats */
  500. {VideoFormat::I420, "I420"},
  501. {VideoFormat::NV12, "NV12"},
  502. /* packed YUV formats */
  503. {VideoFormat::YVYU, "YVYU"},
  504. {VideoFormat::YUY2, "YUY2"},
  505. {VideoFormat::UYVY, "UYVY"},
  506. {VideoFormat::HDYC, "HDYV"},
  507. /* encoded formats */
  508. {VideoFormat::MPEG2, "MPEG2"},
  509. {VideoFormat::MJPEG, "MJPEG"},
  510. {VideoFormat::H264, "H264"}
  511. };
  512. static bool ResTypeChanged(obs_properties_t props, obs_property_t p,
  513. obs_data_t settings);
  514. static size_t AddDevice(obs_property_t device_list, const string &id)
  515. {
  516. DStr name, path;
  517. if (!DecodeDeviceId(name, path, id.c_str()))
  518. return numeric_limits<size_t>::max();
  519. return obs_property_list_add_string(device_list, name, id.c_str());
  520. }
  521. static bool UpdateDeviceList(obs_property_t list, const string &id)
  522. {
  523. size_t size = obs_property_list_item_count(list);
  524. bool found = false;
  525. bool disabled_unknown_found = false;
  526. for (size_t i = 0; i < size; i++) {
  527. if (obs_property_list_item_string(list, i) == id) {
  528. found = true;
  529. continue;
  530. }
  531. if (obs_property_list_item_disabled(list, i))
  532. disabled_unknown_found = true;
  533. }
  534. if (!found && !disabled_unknown_found) {
  535. size_t idx = AddDevice(list, id);
  536. obs_property_list_item_disable(list, idx, true);
  537. return true;
  538. }
  539. if (found && !disabled_unknown_found)
  540. return false;
  541. for (size_t i = 0; i < size;) {
  542. if (obs_property_list_item_disabled(list, i)) {
  543. obs_property_list_item_remove(list, i);
  544. continue;
  545. }
  546. i += 1;
  547. }
  548. return true;
  549. }
  550. static bool DeviceSelectionChanged(obs_properties_t props, obs_property_t p,
  551. obs_data_t settings)
  552. {
  553. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  554. VideoDevice device;
  555. string id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
  556. string old_id = obs_data_getstring(settings, LAST_VIDEO_DEV_ID);
  557. bool device_list_updated = UpdateDeviceList(p, id);
  558. if (!data->GetDevice(device, id.c_str()))
  559. return !device_list_updated;
  560. vector<Resolution> resolutions;
  561. for (const VideoInfo &cap : device.caps)
  562. AddCap(resolutions, cap);
  563. p = obs_properties_get(props, RESOLUTION);
  564. obs_property_list_clear(p);
  565. for (size_t idx = resolutions.size(); idx > 0; idx--) {
  566. const Resolution &res = resolutions[idx-1];
  567. string strRes;
  568. strRes += to_string(res.cx);
  569. strRes += "x";
  570. strRes += to_string(res.cy);
  571. obs_property_list_add_string(p, strRes.c_str(), strRes.c_str());
  572. }
  573. /* only refresh properties if device legitimately changed */
  574. if (!id.size() || !old_id.size() || id != old_id) {
  575. p = obs_properties_get(props, RES_TYPE);
  576. ResTypeChanged(props, p, settings);
  577. obs_data_setstring(settings, LAST_VIDEO_DEV_ID, id.c_str());
  578. }
  579. return true;
  580. }
  581. static bool VideoConfigClicked(obs_properties_t props, obs_property_t p,
  582. void *data)
  583. {
  584. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  585. input->device.OpenDialog(nullptr, DialogType::ConfigVideo);
  586. UNUSED_PARAMETER(props);
  587. UNUSED_PARAMETER(p);
  588. return false;
  589. }
  590. /*static bool AudioConfigClicked(obs_properties_t props, obs_property_t p,
  591. void *data)
  592. {
  593. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  594. input->device.OpenDialog(nullptr, DialogType::ConfigAudio);
  595. UNUSED_PARAMETER(props);
  596. UNUSED_PARAMETER(p);
  597. return false;
  598. }*/
  599. static bool CrossbarConfigClicked(obs_properties_t props, obs_property_t p,
  600. void *data)
  601. {
  602. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  603. input->device.OpenDialog(nullptr, DialogType::ConfigCrossbar);
  604. UNUSED_PARAMETER(props);
  605. UNUSED_PARAMETER(p);
  606. return false;
  607. }
  608. /*static bool Crossbar2ConfigClicked(obs_properties_t props, obs_property_t p,
  609. void *data)
  610. {
  611. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  612. input->device.OpenDialog(nullptr, DialogType::ConfigCrossbar2);
  613. UNUSED_PARAMETER(props);
  614. UNUSED_PARAMETER(p);
  615. return false;
  616. }*/
  617. static bool AddDevice(obs_property_t device_list, const VideoDevice &device)
  618. {
  619. DStr name, path, device_id;
  620. dstr_from_wcs(name, device.name.c_str());
  621. dstr_from_wcs(path, device.path.c_str());
  622. encode_dstr(path);
  623. dstr_copy_dstr(device_id, name);
  624. encode_dstr(device_id);
  625. dstr_cat(device_id, ":");
  626. dstr_cat_dstr(device_id, path);
  627. obs_property_list_add_string(device_list, name, device_id);
  628. return true;
  629. }
  630. static void PropertiesDataDestroy(void *data)
  631. {
  632. delete reinterpret_cast<PropertiesData*>(data);
  633. }
  634. static bool ResTypeChanged(obs_properties_t props, obs_property_t p,
  635. obs_data_t settings)
  636. {
  637. int val = (int)obs_data_getint(settings, RES_TYPE);
  638. bool enabled = (val != ResType_Preferred);
  639. p = obs_properties_get(props, RESOLUTION);
  640. obs_property_set_enabled(p, enabled);
  641. p = obs_properties_get(props, FRAME_INTERVAL);
  642. obs_property_set_enabled(p, enabled);
  643. p = obs_properties_get(props, VIDEO_FORMAT);
  644. obs_property_set_enabled(p, enabled);
  645. if (val == ResType_Custom) {
  646. p = obs_properties_get(props, RESOLUTION);
  647. DeviceResolutionChanged(props, p, settings);
  648. } else {
  649. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  650. }
  651. return true;
  652. }
  653. static DStr GetFPSName(long long interval)
  654. {
  655. DStr name;
  656. for (const FPSFormat &format : validFPSFormats) {
  657. if (format.interval != interval)
  658. continue;
  659. dstr_cat(name, format.text);
  660. return name;
  661. }
  662. dstr_cat(name, to_string(10000000. / interval).c_str());
  663. return name;
  664. }
  665. static void UpdateFPS(VideoDevice &device, VideoFormat format,
  666. long long interval, int cx, int cy, obs_properties_t props)
  667. {
  668. obs_property_t list = obs_properties_get(props, FRAME_INTERVAL);
  669. obs_property_list_clear(list);
  670. bool interval_added = false;
  671. for (const FPSFormat &fps_format : validFPSFormats) {
  672. bool video_format_match = false;
  673. long long format_interval = fps_format.interval;
  674. bool available = CapsMatch(device,
  675. ResolutionMatcher(cx, cy),
  676. VideoFormatMatcher(format, video_format_match),
  677. FrameRateMatcher(format_interval));
  678. if (!available && interval != fps_format.interval)
  679. continue;
  680. if (interval == fps_format.interval)
  681. interval_added = true;
  682. size_t idx = obs_property_list_add_int(list, fps_format.text,
  683. fps_format.interval);
  684. obs_property_list_item_disable(list, idx, !available);
  685. }
  686. if (interval_added)
  687. return;
  688. size_t idx = obs_property_list_add_int(list, GetFPSName(interval),
  689. interval);
  690. obs_property_list_item_disable(list, idx, true);
  691. }
  692. static DStr GetVideoFormatName(VideoFormat format)
  693. {
  694. DStr name;
  695. for (const VideoFormatName &format_ : videoFormatNames) {
  696. if (format_.format == format) {
  697. dstr_cat(name, format_.name);
  698. return name;
  699. }
  700. }
  701. dstr_catf(name, "Unknown (%lld)", (long long)format);
  702. return name;
  703. }
  704. static void UpdateVideoFormats(VideoDevice &device, VideoFormat format_,
  705. int cx, int cy, long long interval, obs_properties_t props)
  706. {
  707. set<VideoFormat> formats = { VideoFormat::Any };
  708. auto format_gatherer = [&formats](const VideoInfo &info) mutable -> bool
  709. {
  710. formats.insert(info.format);
  711. return false;
  712. };
  713. CapsMatch(device,
  714. ResolutionMatcher(cx, cy),
  715. FrameRateMatcher(interval),
  716. format_gatherer);
  717. obs_property_t list = obs_properties_get(props, VIDEO_FORMAT);
  718. obs_property_list_clear(list);
  719. bool format_added = false;
  720. for (const VideoFormatName &format : videoFormatNames) {
  721. bool available = formats.find(format.format) != end(formats);
  722. if (!available && format.format != format_)
  723. continue;
  724. if (format.format == format_)
  725. format_added = true;
  726. size_t idx = obs_property_list_add_int(list, format.name,
  727. (long long)format.format);
  728. obs_property_list_item_disable(list, idx, !available);
  729. }
  730. if (format_added)
  731. return;
  732. size_t idx = obs_property_list_add_int(list,
  733. GetVideoFormatName(format_), (long long)format_);
  734. obs_property_list_item_disable(list, idx, true);
  735. }
  736. static bool UpdateFPS(long long interval, obs_property_t list)
  737. {
  738. size_t size = obs_property_list_item_count(list);
  739. bool fps_found = false;
  740. DStr name;
  741. for (size_t i = 0; i < size; i++) {
  742. if (obs_property_list_item_int(list, i) != interval)
  743. continue;
  744. obs_property_list_item_disable(list, i, true);
  745. if (size == 1)
  746. return false;
  747. dstr_cat(name, obs_property_list_item_name(list, i));
  748. fps_found = true;
  749. break;
  750. }
  751. obs_property_list_clear(list);
  752. if (!name->len)
  753. name = GetFPSName(interval);
  754. obs_property_list_add_int(list, name, interval);
  755. obs_property_list_item_disable(list, 0, true);
  756. return true;
  757. }
  758. static bool DeviceIntervalChanged(obs_properties_t props, obs_property_t p,
  759. obs_data_t settings)
  760. {
  761. long long val = obs_data_getint(settings, FRAME_INTERVAL);
  762. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  763. const char *id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
  764. VideoDevice device;
  765. if (!data->GetDevice(device, id))
  766. return UpdateFPS(val, p);
  767. int cx = 0, cy = 0;
  768. if (!DetermineResolution(cx, cy, settings, device)) {
  769. UpdateVideoFormats(device, VideoFormat::Any, 0, 0, 0, props);
  770. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  771. return true;
  772. }
  773. int resType = (int)obs_data_getint(settings, RES_TYPE);
  774. if (resType != ResType_Custom)
  775. return true;
  776. VideoFormat format = (VideoFormat)obs_data_getint(settings,
  777. VIDEO_FORMAT);
  778. bool video_format_matches = false;
  779. long long best_interval = numeric_limits<long long>::max();
  780. bool frameRateSupported = CapsMatch(device,
  781. ResolutionMatcher(cx, cy),
  782. VideoFormatMatcher(format, video_format_matches),
  783. ClosestFrameRateSelector(val, best_interval),
  784. FrameRateMatcher(val));
  785. if (video_format_matches &&
  786. !frameRateSupported &&
  787. best_interval != val) {
  788. long long listed_val = 0;
  789. for (const FPSFormat &format : validFPSFormats) {
  790. long long diff = llabs(format.interval - best_interval);
  791. if (diff < DEVICE_INTERVAL_DIFF_LIMIT) {
  792. listed_val = format.interval;
  793. break;
  794. }
  795. }
  796. if (listed_val != val)
  797. obs_data_set_autoselect_int(settings, FRAME_INTERVAL,
  798. listed_val);
  799. } else {
  800. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  801. }
  802. UpdateVideoFormats(device, format, cx, cy, val, props);
  803. UpdateFPS(device, format, val, cx, cy, props);
  804. UNUSED_PARAMETER(p);
  805. return true;
  806. }
  807. static bool UpdateVideoFormats(VideoFormat format, obs_property_t list)
  808. {
  809. size_t size = obs_property_list_item_count(list);
  810. DStr name;
  811. for (size_t i = 0; i < size; i++) {
  812. if ((VideoFormat)obs_property_list_item_int(list, i) != format)
  813. continue;
  814. if (size == 1)
  815. return false;
  816. dstr_cat(name, obs_property_list_item_name(list, i));
  817. break;
  818. }
  819. obs_property_list_clear(list);
  820. if (!name->len)
  821. name = GetVideoFormatName(format);
  822. obs_property_list_add_int(list, name, (long long)format);
  823. obs_property_list_item_disable(list, 0, true);
  824. return true;
  825. }
  826. static bool VideoFormatChanged(obs_properties_t props, obs_property_t p,
  827. obs_data_t settings)
  828. {
  829. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  830. const char *id = obs_data_getstring(settings, VIDEO_DEVICE_ID);
  831. VideoDevice device;
  832. VideoFormat curFormat =
  833. (VideoFormat)obs_data_getint(settings, VIDEO_FORMAT);
  834. if (!data->GetDevice(device, id))
  835. return UpdateVideoFormats(curFormat, p);
  836. int cx, cy;
  837. if (!DetermineResolution(cx, cy, settings, device)) {
  838. UpdateVideoFormats(device, VideoFormat::Any, cx, cy, 0, props);
  839. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  840. return true;
  841. }
  842. long long interval = obs_data_getint(settings, FRAME_INTERVAL);
  843. UpdateVideoFormats(device, curFormat, cx, cy, interval, props);
  844. UpdateFPS(device, curFormat, interval, cx, cy, props);
  845. return true;
  846. }
  847. static obs_properties_t GetDShowProperties(void)
  848. {
  849. obs_properties_t ppts = obs_properties_create();
  850. PropertiesData *data = new PropertiesData;
  851. obs_properties_set_param(ppts, data, PropertiesDataDestroy);
  852. /* TODO: locale */
  853. obs_property_t p = obs_properties_add_list(ppts,
  854. VIDEO_DEVICE_ID, "Device",
  855. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  856. obs_property_set_modified_callback(p, DeviceSelectionChanged);
  857. Device::EnumVideoDevices(data->devices);
  858. for (const VideoDevice &device : data->devices)
  859. AddDevice(p, device);
  860. obs_properties_add_button(ppts, "video_config", "Configure Video",
  861. VideoConfigClicked);
  862. obs_properties_add_button(ppts, "xbar_config", "Configure Crossbar",
  863. CrossbarConfigClicked);
  864. /* ------------------------------------- */
  865. p = obs_properties_add_list(ppts, RES_TYPE, "Resolution/FPS Type",
  866. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  867. obs_property_set_modified_callback(p, ResTypeChanged);
  868. obs_property_list_add_int(p, "Device Preferred", ResType_Preferred);
  869. obs_property_list_add_int(p, "Custom", ResType_Custom);
  870. p = obs_properties_add_list(ppts, RESOLUTION, "Resolution",
  871. OBS_COMBO_TYPE_EDITABLE, OBS_COMBO_FORMAT_STRING);
  872. obs_property_set_modified_callback(p, DeviceResolutionChanged);
  873. p = obs_properties_add_list(ppts, FRAME_INTERVAL, "FPS",
  874. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  875. obs_property_set_modified_callback(p, DeviceIntervalChanged);
  876. p = obs_properties_add_list(ppts, VIDEO_FORMAT, "Video Format",
  877. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  878. obs_property_set_modified_callback(p, VideoFormatChanged);
  879. return ppts;
  880. }
  881. OBS_DECLARE_MODULE()
  882. void DShowModuleLogCallback(LogType type, const wchar_t *msg, void *param)
  883. {
  884. int obs_type = LOG_DEBUG;
  885. switch (type) {
  886. case LogType::Error: obs_type = LOG_ERROR; break;
  887. case LogType::Warning: obs_type = LOG_WARNING; break;
  888. case LogType::Info: obs_type = LOG_INFO; break;
  889. case LogType::Debug: obs_type = LOG_DEBUG; break;
  890. }
  891. DStr dmsg;
  892. dstr_from_wcs(dmsg, msg);
  893. blog(obs_type, "DShow: %s", dmsg->array);
  894. UNUSED_PARAMETER(param);
  895. }
  896. bool obs_module_load(uint32_t libobs_ver)
  897. {
  898. UNUSED_PARAMETER(libobs_ver);
  899. SetLogCallback(DShowModuleLogCallback, nullptr);
  900. obs_source_info info = {};
  901. info.id = "dshow_input";
  902. info.type = OBS_SOURCE_TYPE_INPUT;
  903. info.output_flags = OBS_SOURCE_VIDEO |
  904. OBS_SOURCE_ASYNC;
  905. info.getname = GetDShowInputName;
  906. info.create = CreateDShowInput;
  907. info.destroy = DestroyDShowInput;
  908. info.getwidth = GetDShowWidth;
  909. info.getheight = GetDShowHeight;
  910. info.update = UpdateDShowInput;
  911. info.defaults = GetDShowDefaults;
  912. info.properties = GetDShowProperties;
  913. obs_register_source(&info);
  914. return true;
  915. }