win-dshow.cpp 29 KB

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