win-dshow.cpp 31 KB

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