win-dshow.cpp 24 KB

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