win-dshow.cpp 22 KB

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