win-dshow.cpp 22 KB

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