win-dshow.cpp 24 KB

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