win-dshow.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632
  1. #include <objbase.h>
  2. #include <obs-module.h>
  3. #include <obs.hpp>
  4. #include <util/dstr.hpp>
  5. #include <util/platform.h>
  6. #include <util/windows/WinHandle.hpp>
  7. #include <util/threading.h>
  8. #include "libdshowcapture/dshowcapture.hpp"
  9. #include "ffmpeg-decode.h"
  10. #include "encode-dstr.hpp"
  11. #include <algorithm>
  12. #include <limits>
  13. #include <set>
  14. #include <string>
  15. #include <vector>
  16. /*
  17. * TODO:
  18. * - handle disconnections and reconnections
  19. * - if device not present, wait for device to be plugged in
  20. */
  21. #undef min
  22. #undef max
  23. using namespace std;
  24. using namespace DShow;
  25. /* settings defines that will cause errors if there are typos */
  26. #define VIDEO_DEVICE_ID "video_device_id"
  27. #define RES_TYPE "res_type"
  28. #define RESOLUTION "resolution"
  29. #define FRAME_INTERVAL "frame_interval"
  30. #define VIDEO_FORMAT "video_format"
  31. #define LAST_VIDEO_DEV_ID "last_video_device_id"
  32. #define LAST_RESOLUTION "last_resolution"
  33. #define BUFFERING_VAL "buffering"
  34. #define USE_CUSTOM_AUDIO "use_custom_audio_device"
  35. #define AUDIO_DEVICE_ID "audio_device_id"
  36. #define TEXT_INPUT_NAME obs_module_text("VideoCaptureDevice")
  37. #define TEXT_DEVICE obs_module_text("Device")
  38. #define TEXT_CONFIG_VIDEO obs_module_text("ConfigureVideo")
  39. #define TEXT_CONFIG_XBAR obs_module_text("ConfigureCrossbar")
  40. #define TEXT_RES_FPS_TYPE obs_module_text("ResFPSType")
  41. #define TEXT_CUSTOM_RES obs_module_text("ResFPSType.Custom")
  42. #define TEXT_PREFERRED_RES obs_module_text("ResFPSType.DevPreferred")
  43. #define TEXT_FPS_MATCHING obs_module_text("FPS.Matching")
  44. #define TEXT_FPS_HIGHEST obs_module_text("FPS.Highest")
  45. #define TEXT_RESOLUTION obs_module_text("Resolution")
  46. #define TEXT_VIDEO_FORMAT obs_module_text("VideoFormat")
  47. #define TEXT_FORMAT_UNKNOWN obs_module_text("VideoFormat.Unknown")
  48. #define TEXT_BUFFERING obs_module_text("Buffering")
  49. #define TEXT_BUFFERING_AUTO obs_module_text("Buffering.AutoDetect")
  50. #define TEXT_BUFFERING_ON obs_module_text("Buffering.Enable")
  51. #define TEXT_BUFFERING_OFF obs_module_text("Buffering.Disable")
  52. #define TEXT_CUSTOM_AUDIO obs_module_text("UseCustomAudioDevice")
  53. #define TEXT_AUDIO_DEVICE obs_module_text("AudioDevice")
  54. enum ResType {
  55. ResType_Preferred,
  56. ResType_Custom
  57. };
  58. enum class BufferingType : int64_t {
  59. Auto,
  60. On,
  61. Off
  62. };
  63. void ffmpeg_log(void *bla, int level, const char *msg, va_list args)
  64. {
  65. DStr str;
  66. if (level == AV_LOG_WARNING)
  67. dstr_copy(str, "warning: ");
  68. else if (level == AV_LOG_ERROR)
  69. dstr_copy(str, "error: ");
  70. else if (level < AV_LOG_ERROR)
  71. dstr_copy(str, "fatal: ");
  72. else
  73. return;
  74. dstr_cat(str, msg);
  75. if (dstr_end(str) == '\n')
  76. dstr_resize(str, str->len - 1);
  77. blogva(LOG_WARNING, str, args);
  78. av_log_default_callback(bla, level, msg, args);
  79. }
  80. class Decoder {
  81. struct ffmpeg_decode decode;
  82. public:
  83. inline Decoder() {memset(&decode, 0, sizeof(decode));}
  84. inline ~Decoder() {ffmpeg_decode_free(&decode);}
  85. inline operator ffmpeg_decode*() {return &decode;}
  86. };
  87. class CriticalSection {
  88. CRITICAL_SECTION mutex;
  89. public:
  90. inline CriticalSection() {InitializeCriticalSection(&mutex);}
  91. inline ~CriticalSection() {DeleteCriticalSection(&mutex);}
  92. inline operator CRITICAL_SECTION*() {return &mutex;}
  93. };
  94. class CriticalScope {
  95. CriticalSection &mutex;
  96. CriticalScope() = delete;
  97. CriticalScope& operator=(CriticalScope &cs) = delete;
  98. public:
  99. inline CriticalScope(CriticalSection &mutex_) : mutex(mutex_)
  100. {
  101. EnterCriticalSection(mutex);
  102. }
  103. inline ~CriticalScope()
  104. {
  105. LeaveCriticalSection(mutex);
  106. }
  107. };
  108. enum class Action {
  109. None,
  110. Update,
  111. Shutdown,
  112. ConfigVideo,
  113. ConfigAudio,
  114. ConfigCrossbar1,
  115. ConfigCrossbar2,
  116. };
  117. static DWORD CALLBACK DShowThread(LPVOID ptr);
  118. struct DShowInput {
  119. obs_source_t *source;
  120. Device device;
  121. bool deviceHasAudio;
  122. Decoder audio_decoder;
  123. Decoder video_decoder;
  124. VideoConfig videoConfig;
  125. AudioConfig audioConfig;
  126. obs_source_frame frame;
  127. obs_source_audio audio;
  128. WinHandle semaphore;
  129. WinHandle thread;
  130. CriticalSection mutex;
  131. vector<Action> actions;
  132. inline void QueueAction(Action action)
  133. {
  134. CriticalScope scope(mutex);
  135. actions.push_back(action);
  136. ReleaseSemaphore(semaphore, 1, nullptr);
  137. }
  138. inline DShowInput(obs_source_t *source_)
  139. : source (source_),
  140. device (InitGraph::False)
  141. {
  142. memset(&audio, 0, sizeof(audio));
  143. memset(&frame, 0, sizeof(frame));
  144. av_log_set_level(AV_LOG_WARNING);
  145. av_log_set_callback(ffmpeg_log);
  146. semaphore = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr);
  147. if (!semaphore)
  148. throw "Failed to create semaphore";
  149. thread = CreateThread(nullptr, 0, DShowThread, this, 0,
  150. nullptr);
  151. if (!thread)
  152. throw "Failed to create thread";
  153. QueueAction(Action::Update);
  154. }
  155. inline ~DShowInput()
  156. {
  157. {
  158. CriticalScope scope(mutex);
  159. actions.resize(1);
  160. actions[0] = Action::Shutdown;
  161. }
  162. ReleaseSemaphore(semaphore, 1, nullptr);
  163. WaitForSingleObject(thread, INFINITE);
  164. }
  165. void OnEncodedVideoData(enum AVCodecID id,
  166. unsigned char *data, size_t size, long long ts);
  167. void OnEncodedAudioData(enum AVCodecID id,
  168. unsigned char *data, size_t size, long long ts);
  169. void OnVideoData(const VideoConfig &config,
  170. unsigned char *data, size_t size,
  171. long long startTime, long long endTime);
  172. void OnAudioData(const AudioConfig &config,
  173. unsigned char *data, size_t size,
  174. long long startTime, long long endTime);
  175. bool UpdateVideoConfig(obs_data_t *settings);
  176. bool UpdateAudioConfig(obs_data_t *settings);
  177. void Update(obs_data_t *settings);
  178. inline void SetupBuffering(obs_data_t *settings);
  179. void DShowLoop();
  180. };
  181. static DWORD CALLBACK DShowThread(LPVOID ptr)
  182. {
  183. DShowInput *dshowInput = (DShowInput*)ptr;
  184. os_set_thread_name("win-dshow: DShowThread");
  185. CoInitialize(nullptr);
  186. dshowInput->DShowLoop();
  187. CoUninitialize();
  188. return 0;
  189. }
  190. static inline void ProcessMessages()
  191. {
  192. MSG msg;
  193. while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
  194. TranslateMessage(&msg);
  195. DispatchMessage(&msg);
  196. }
  197. }
  198. /* Always keep directshow in a single thread for a given device */
  199. void DShowInput::DShowLoop()
  200. {
  201. while (true) {
  202. DWORD ret = MsgWaitForMultipleObjects(1, &semaphore, false,
  203. INFINITE, QS_ALLINPUT);
  204. if (ret == (WAIT_OBJECT_0 + 1)) {
  205. ProcessMessages();
  206. continue;
  207. } else if (ret != WAIT_OBJECT_0) {
  208. break;
  209. }
  210. Action action = Action::None;
  211. {
  212. CriticalScope scope(mutex);
  213. if (actions.size()) {
  214. action = actions.front();
  215. actions.erase(actions.begin());
  216. }
  217. }
  218. switch (action) {
  219. case Action::Update:
  220. {
  221. obs_data_t *settings;
  222. settings = obs_source_get_settings(source);
  223. Update(settings);
  224. obs_data_release(settings);
  225. break;
  226. }
  227. case Action::Shutdown:
  228. device.ShutdownGraph();
  229. return;
  230. case Action::ConfigVideo:
  231. device.OpenDialog(nullptr, DialogType::ConfigVideo);
  232. break;
  233. case Action::ConfigAudio:
  234. device.OpenDialog(nullptr, DialogType::ConfigAudio);
  235. break;
  236. case Action::ConfigCrossbar1:
  237. device.OpenDialog(nullptr, DialogType::ConfigCrossbar);
  238. break;
  239. case Action::ConfigCrossbar2:
  240. device.OpenDialog(nullptr, DialogType::ConfigCrossbar2);
  241. break;
  242. case Action::None:;
  243. }
  244. }
  245. }
  246. #define FPS_HIGHEST 0LL
  247. #define FPS_MATCHING -1LL
  248. template <typename T, typename U, typename V>
  249. static bool between(T &&lower, U &&value, V &&upper)
  250. {
  251. return value >= lower && value <= upper;
  252. }
  253. static bool ResolutionAvailable(const VideoInfo &cap, int cx, int cy)
  254. {
  255. return between(cap.minCX, cx, cap.maxCX) &&
  256. between(cap.minCY, cy, cap.maxCY);
  257. }
  258. #define DEVICE_INTERVAL_DIFF_LIMIT 20
  259. static bool FrameRateAvailable(const VideoInfo &cap, long long interval)
  260. {
  261. return interval == FPS_HIGHEST || interval == FPS_MATCHING ||
  262. between(cap.minInterval - DEVICE_INTERVAL_DIFF_LIMIT,
  263. interval,
  264. cap.maxInterval + DEVICE_INTERVAL_DIFF_LIMIT);
  265. }
  266. static long long FrameRateInterval(const VideoInfo &cap,
  267. long long desired_interval)
  268. {
  269. return desired_interval < cap.minInterval ?
  270. cap.minInterval :
  271. min(desired_interval, cap.maxInterval);
  272. }
  273. static inline video_format ConvertVideoFormat(VideoFormat format)
  274. {
  275. switch (format) {
  276. case VideoFormat::ARGB: return VIDEO_FORMAT_BGRA;
  277. case VideoFormat::XRGB: return VIDEO_FORMAT_BGRX;
  278. case VideoFormat::I420: return VIDEO_FORMAT_I420;
  279. case VideoFormat::YV12: return VIDEO_FORMAT_I420;
  280. case VideoFormat::NV12: return VIDEO_FORMAT_NV12;
  281. case VideoFormat::YVYU: return VIDEO_FORMAT_YVYU;
  282. case VideoFormat::YUY2: return VIDEO_FORMAT_YUY2;
  283. case VideoFormat::UYVY: return VIDEO_FORMAT_UYVY;
  284. case VideoFormat::HDYC: return VIDEO_FORMAT_UYVY;
  285. case VideoFormat::MJPEG: return VIDEO_FORMAT_YUY2;
  286. default: return VIDEO_FORMAT_NONE;
  287. }
  288. }
  289. static inline audio_format ConvertAudioFormat(AudioFormat format)
  290. {
  291. switch (format) {
  292. case AudioFormat::Wave16bit: return AUDIO_FORMAT_16BIT;
  293. case AudioFormat::WaveFloat: return AUDIO_FORMAT_FLOAT;
  294. default: return AUDIO_FORMAT_UNKNOWN;
  295. }
  296. }
  297. //#define LOG_ENCODED_VIDEO_TS 1
  298. //#define LOG_ENCODED_AUDIO_TS 1
  299. void DShowInput::OnEncodedVideoData(enum AVCodecID id,
  300. unsigned char *data, size_t size, long long ts)
  301. {
  302. if (!ffmpeg_decode_valid(video_decoder)) {
  303. if (ffmpeg_decode_init(video_decoder, id) < 0) {
  304. blog(LOG_WARNING, "Could not initialize video decoder");
  305. return;
  306. }
  307. }
  308. bool got_output;
  309. int len = ffmpeg_decode_video(video_decoder, data, size, &ts,
  310. &frame, &got_output);
  311. if (len < 0) {
  312. blog(LOG_WARNING, "Error decoding video");
  313. return;
  314. }
  315. if (got_output) {
  316. frame.timestamp = (uint64_t)ts * 100;
  317. #if LOG_ENCODED_VIDEO_TS
  318. blog(LOG_DEBUG, "video ts: %llu", frame.timestamp);
  319. #endif
  320. obs_source_output_video(source, &frame);
  321. }
  322. }
  323. void DShowInput::OnVideoData(const VideoConfig &config,
  324. unsigned char *data, size_t size,
  325. long long startTime, long long endTime)
  326. {
  327. if (videoConfig.format == VideoFormat::H264) {
  328. OnEncodedVideoData(AV_CODEC_ID_H264, data, size, startTime);
  329. return;
  330. }
  331. const int cx = config.cx;
  332. const int cy = config.cy;
  333. frame.timestamp = (uint64_t)startTime * 100;
  334. frame.width = config.cx;
  335. frame.height = config.cy;
  336. frame.format = ConvertVideoFormat(config.format);
  337. frame.full_range = false;
  338. frame.flip = (config.format == VideoFormat::XRGB ||
  339. config.format == VideoFormat::ARGB);
  340. if (videoConfig.format == VideoFormat::XRGB ||
  341. videoConfig.format == VideoFormat::ARGB) {
  342. frame.data[0] = data;
  343. frame.linesize[0] = cx * 4;
  344. } else if (videoConfig.format == VideoFormat::YVYU ||
  345. videoConfig.format == VideoFormat::YUY2 ||
  346. videoConfig.format == VideoFormat::HDYC ||
  347. videoConfig.format == VideoFormat::UYVY) {
  348. frame.data[0] = data;
  349. frame.linesize[0] = cx * 2;
  350. } else if (videoConfig.format == VideoFormat::I420) {
  351. frame.data[0] = data;
  352. frame.data[1] = frame.data[0] + (cx * cy);
  353. frame.data[2] = frame.data[1] + (cx * cy / 4);
  354. frame.linesize[0] = cx;
  355. frame.linesize[1] = cx / 2;
  356. frame.linesize[2] = cx / 2;
  357. } else if (videoConfig.format == VideoFormat::YV12) {
  358. frame.data[0] = data;
  359. frame.data[2] = frame.data[0] + (cx * cy);
  360. frame.data[1] = frame.data[2] + (cx * cy / 4);
  361. frame.linesize[0] = cx;
  362. frame.linesize[1] = cx / 2;
  363. frame.linesize[2] = cx / 2;
  364. } else if (videoConfig.format == VideoFormat::NV12) {
  365. frame.data[0] = data;
  366. frame.data[1] = frame.data[0] + (cx * cy);
  367. frame.linesize[0] = cx;
  368. frame.linesize[1] = cx;
  369. } else {
  370. /* TODO: other formats */
  371. return;
  372. }
  373. obs_source_output_video(source, &frame);
  374. UNUSED_PARAMETER(endTime); /* it's the enndd tiimmes! */
  375. UNUSED_PARAMETER(size);
  376. }
  377. void DShowInput::OnEncodedAudioData(enum AVCodecID id,
  378. unsigned char *data, size_t size, long long ts)
  379. {
  380. if (!ffmpeg_decode_valid(audio_decoder)) {
  381. if (ffmpeg_decode_init(audio_decoder, id) < 0) {
  382. blog(LOG_WARNING, "Could not initialize audio decoder");
  383. return;
  384. }
  385. }
  386. bool got_output;
  387. int len = ffmpeg_decode_audio(audio_decoder, data, size,
  388. &audio, &got_output);
  389. if (len < 0) {
  390. blog(LOG_WARNING, "Error decoding audio");
  391. return;
  392. }
  393. if (got_output) {
  394. audio.timestamp = (uint64_t)ts * 100;
  395. #if LOG_ENCODED_AUDIO_TS
  396. blog(LOG_DEBUG, "audio ts: %llu", audio.timestamp);
  397. #endif
  398. obs_source_output_audio(source, &audio);
  399. }
  400. }
  401. void DShowInput::OnAudioData(const AudioConfig &config,
  402. unsigned char *data, size_t size,
  403. long long startTime, long long endTime)
  404. {
  405. size_t block_size;
  406. if (config.format == AudioFormat::AAC) {
  407. OnEncodedAudioData(AV_CODEC_ID_AAC, data, size, startTime);
  408. return;
  409. } else if (config.format == AudioFormat::AC3) {
  410. OnEncodedAudioData(AV_CODEC_ID_AC3, data, size, startTime);
  411. return;
  412. } else if (config.format == AudioFormat::MPGA) {
  413. OnEncodedAudioData(AV_CODEC_ID_MP2, data, size, startTime);
  414. return;
  415. }
  416. audio.speakers = (enum speaker_layout)config.channels;
  417. audio.format = ConvertAudioFormat(config.format);
  418. audio.samples_per_sec = (uint32_t)config.sampleRate;
  419. audio.data[0] = data;
  420. block_size = get_audio_bytes_per_channel(audio.format) *
  421. get_audio_channels(audio.speakers);
  422. audio.frames = (uint32_t)(size / block_size);
  423. audio.timestamp = (uint64_t)startTime * 100;
  424. if (audio.format != AUDIO_FORMAT_UNKNOWN)
  425. obs_source_output_audio(source, &audio);
  426. UNUSED_PARAMETER(endTime);
  427. }
  428. struct PropertiesData {
  429. vector<VideoDevice> devices;
  430. vector<AudioDevice> audioDevices;
  431. bool GetDevice(VideoDevice &device, const char *encoded_id) const
  432. {
  433. DeviceId deviceId;
  434. DecodeDeviceId(deviceId, encoded_id);
  435. for (const VideoDevice &curDevice : devices) {
  436. if (deviceId.name == curDevice.name &&
  437. deviceId.path == curDevice.path) {
  438. device = curDevice;
  439. return true;
  440. }
  441. }
  442. return false;
  443. }
  444. };
  445. static inline bool ConvertRes(int &cx, int &cy, const char *res)
  446. {
  447. return sscanf(res, "%dx%d", &cx, &cy) == 2;
  448. }
  449. static inline bool FormatMatches(VideoFormat left, VideoFormat right)
  450. {
  451. return left == VideoFormat::Any || right == VideoFormat::Any ||
  452. left == right;
  453. }
  454. static inline bool ResolutionValid(string res, int &cx, int &cy)
  455. {
  456. if (!res.size())
  457. return false;
  458. return ConvertRes(cx, cy, res.c_str());
  459. }
  460. template <typename F, typename ... Fs>
  461. static inline bool CapsMatch(const VideoInfo &info, F&& f, Fs ... fs)
  462. {
  463. return f(info) && CapsMatch(info, fs ...);
  464. }
  465. static inline bool CapsMatch(const VideoInfo&)
  466. {
  467. return true;
  468. }
  469. template <typename ... F>
  470. static bool CapsMatch(const VideoDevice &dev, F ... fs)
  471. {
  472. auto matcher = [&](const VideoInfo &info)
  473. {
  474. return CapsMatch(info, fs ...);
  475. };
  476. return any_of(begin(dev.caps), end(dev.caps), matcher);
  477. }
  478. static inline bool MatcherMatchVideoFormat(VideoFormat format,
  479. bool &did_match, const VideoInfo &info)
  480. {
  481. bool match = FormatMatches(format, info.format);
  482. did_match = did_match || match;
  483. return match;
  484. }
  485. static inline bool MatcherClosestFrameRateSelector(long long interval,
  486. long long &best_match, const VideoInfo &info)
  487. {
  488. long long current = FrameRateInterval(info, interval);
  489. if (llabs(interval - best_match) > llabs(interval - current))
  490. best_match = current;
  491. return true;
  492. }
  493. #if 0
  494. auto ResolutionMatcher = [](int cx, int cy)
  495. {
  496. return [cx, cy](const VideoInfo &info)
  497. {
  498. return ResolutionAvailable(info, cx, cy);
  499. };
  500. };
  501. auto FrameRateMatcher = [](long long interval)
  502. {
  503. return [interval](const VideoInfo &info)
  504. {
  505. return FrameRateAvailable(info, interval);
  506. };
  507. };
  508. auto VideoFormatMatcher = [](VideoFormat format, bool &did_match)
  509. {
  510. return [format, &did_match](const VideoInfo &info)
  511. {
  512. return MatcherMatchVideoFormat(format, did_match, info);
  513. };
  514. };
  515. auto ClosestFrameRateSelector = [](long long interval, long long &best_match)
  516. {
  517. return [interval, &best_match](const VideoInfo &info) mutable -> bool
  518. {
  519. MatcherClosestFrameRateSelector(interval, best_match, info);
  520. };
  521. }
  522. #else
  523. #define ResolutionMatcher(cx, cy) \
  524. [cx, cy](const VideoInfo &info) -> bool \
  525. { return ResolutionAvailable(info, cx, cy); }
  526. #define FrameRateMatcher(interval) \
  527. [interval](const VideoInfo &info) -> bool \
  528. { return FrameRateAvailable(info, interval); }
  529. #define VideoFormatMatcher(format, did_match) \
  530. [format, &did_match](const VideoInfo &info) mutable -> bool \
  531. { return MatcherMatchVideoFormat(format, did_match, info); }
  532. #define ClosestFrameRateSelector(interval, best_match) \
  533. [interval, &best_match](const VideoInfo &info) mutable -> bool \
  534. { return MatcherClosestFrameRateSelector(interval, best_match, info); }
  535. #endif
  536. static bool ResolutionAvailable(const VideoDevice &dev, int cx, int cy)
  537. {
  538. return CapsMatch(dev, ResolutionMatcher(cx, cy));
  539. }
  540. static bool DetermineResolution(int &cx, int &cy, obs_data_t *settings,
  541. VideoDevice dev)
  542. {
  543. const char *res = obs_data_get_autoselect_string(settings, RESOLUTION);
  544. if (obs_data_has_autoselect_value(settings, RESOLUTION) &&
  545. ConvertRes(cx, cy, res) &&
  546. ResolutionAvailable(dev, cx, cy))
  547. return true;
  548. res = obs_data_get_string(settings, RESOLUTION);
  549. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  550. return true;
  551. res = obs_data_get_string(settings, LAST_RESOLUTION);
  552. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  553. return true;
  554. return false;
  555. }
  556. static long long GetOBSFPS();
  557. static inline bool IsEncoded(const VideoConfig &config)
  558. {
  559. return config.format >= VideoFormat::MJPEG ||
  560. wstrstri(config.name.c_str(), L"elgato") != NULL ||
  561. wstrstri(config.name.c_str(), L"stream engine") != NULL;
  562. }
  563. inline void DShowInput::SetupBuffering(obs_data_t *settings)
  564. {
  565. BufferingType bufType;
  566. uint32_t flags = obs_source_get_flags(source);
  567. bool useBuffering;
  568. bufType = (BufferingType)obs_data_get_int(settings, BUFFERING_VAL);
  569. if (bufType == BufferingType::Auto)
  570. useBuffering = IsEncoded(videoConfig);
  571. else
  572. useBuffering = bufType == BufferingType::On;
  573. if (useBuffering)
  574. flags &= ~OBS_SOURCE_FLAG_UNBUFFERED;
  575. else
  576. flags |= OBS_SOURCE_FLAG_UNBUFFERED;
  577. obs_source_set_flags(source, flags);
  578. }
  579. bool DShowInput::UpdateVideoConfig(obs_data_t *settings)
  580. {
  581. string video_device_id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  582. DeviceId id;
  583. if (!DecodeDeviceId(id, video_device_id.c_str()))
  584. return false;
  585. PropertiesData data;
  586. Device::EnumVideoDevices(data.devices);
  587. VideoDevice dev;
  588. if (!data.GetDevice(dev, video_device_id.c_str()))
  589. return false;
  590. int resType = (int)obs_data_get_int(settings, RES_TYPE);
  591. int cx = 0, cy = 0;
  592. long long interval = 0;
  593. VideoFormat format = VideoFormat::Any;
  594. if (resType == ResType_Custom) {
  595. bool has_autosel_val;
  596. string resolution = obs_data_get_string(settings, RESOLUTION);
  597. if (!ResolutionValid(resolution, cx, cy))
  598. return false;
  599. has_autosel_val = obs_data_has_autoselect_value(settings,
  600. FRAME_INTERVAL);
  601. interval = has_autosel_val ?
  602. obs_data_get_autoselect_int(settings, FRAME_INTERVAL) :
  603. obs_data_get_int(settings, FRAME_INTERVAL);
  604. if (interval == FPS_MATCHING)
  605. interval = GetOBSFPS();
  606. format = (VideoFormat)obs_data_get_int(settings, VIDEO_FORMAT);
  607. long long best_interval = numeric_limits<long long>::max();
  608. bool video_format_match = false;
  609. if (!CapsMatch(dev,
  610. ResolutionMatcher(cx, cy),
  611. VideoFormatMatcher(format, video_format_match),
  612. ClosestFrameRateSelector(interval, best_interval),
  613. FrameRateMatcher(interval)) && !video_format_match)
  614. return false;
  615. interval = best_interval;
  616. blog(LOG_INFO, "%s: Using interval %lld",
  617. obs_source_get_name(source), interval);
  618. }
  619. videoConfig.name = id.name.c_str();
  620. videoConfig.path = id.path.c_str();
  621. videoConfig.useDefaultConfig = resType == ResType_Preferred;
  622. videoConfig.cx = cx;
  623. videoConfig.cy = cy;
  624. videoConfig.frameInterval = interval;
  625. videoConfig.internalFormat = format;
  626. deviceHasAudio = dev.audioAttached;
  627. videoConfig.callback = std::bind(&DShowInput::OnVideoData, this,
  628. placeholders::_1, placeholders::_2,
  629. placeholders::_3, placeholders::_4,
  630. placeholders::_5);
  631. if (videoConfig.internalFormat != VideoFormat::MJPEG)
  632. videoConfig.format = videoConfig.internalFormat;
  633. if (!device.SetVideoConfig(&videoConfig))
  634. return false;
  635. if (videoConfig.internalFormat == VideoFormat::MJPEG) {
  636. videoConfig.format = VideoFormat::XRGB;
  637. if (!device.SetVideoConfig(&videoConfig))
  638. return false;
  639. }
  640. SetupBuffering(settings);
  641. return true;
  642. }
  643. bool DShowInput::UpdateAudioConfig(obs_data_t *settings)
  644. {
  645. string audio_device_id = obs_data_get_string(settings, AUDIO_DEVICE_ID);
  646. bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO);
  647. if (useCustomAudio) {
  648. DeviceId id;
  649. if (!DecodeDeviceId(id, audio_device_id.c_str()))
  650. return false;
  651. audioConfig.name = id.name.c_str();
  652. audioConfig.path = id.path.c_str();
  653. } else if (!deviceHasAudio) {
  654. return true;
  655. }
  656. audioConfig.useVideoDevice = !useCustomAudio;
  657. audioConfig.callback = std::bind(&DShowInput::OnAudioData, this,
  658. placeholders::_1, placeholders::_2,
  659. placeholders::_3, placeholders::_4,
  660. placeholders::_5);
  661. return device.SetAudioConfig(&audioConfig);
  662. }
  663. void DShowInput::Update(obs_data_t *settings)
  664. {
  665. if (!device.ResetGraph())
  666. return;
  667. if (!UpdateVideoConfig(settings)) {
  668. blog(LOG_WARNING, "%s: Video configuration failed",
  669. obs_source_get_name(source));
  670. return;
  671. }
  672. if (!UpdateAudioConfig(settings))
  673. blog(LOG_WARNING, "%s: Audio configuration failed, ignoring "
  674. "audio", obs_source_get_name(source));
  675. if (!device.ConnectFilters())
  676. return;
  677. if (device.Start() != Result::Success)
  678. return;
  679. enum video_colorspace cs = (videoConfig.format == VideoFormat::HDYC) ?
  680. VIDEO_CS_709 : VIDEO_CS_601;
  681. if (!video_format_get_parameters(cs, VIDEO_RANGE_PARTIAL,
  682. frame.color_matrix,
  683. frame.color_range_min,
  684. frame.color_range_max)) {
  685. blog(LOG_ERROR, "Failed to get video format parameters for " \
  686. "video format %u", cs);
  687. }
  688. }
  689. /* ------------------------------------------------------------------------- */
  690. static const char *GetDShowInputName(void)
  691. {
  692. return TEXT_INPUT_NAME;
  693. }
  694. static void *CreateDShowInput(obs_data_t *settings, obs_source_t *source)
  695. {
  696. DShowInput *dshow = nullptr;
  697. try {
  698. dshow = new DShowInput(source);
  699. } catch (const char *error) {
  700. blog(LOG_ERROR, "Could not create device '%s': %s",
  701. obs_source_get_name(source), error);
  702. }
  703. UNUSED_PARAMETER(settings);
  704. return dshow;
  705. }
  706. static void DestroyDShowInput(void *data)
  707. {
  708. delete reinterpret_cast<DShowInput*>(data);
  709. }
  710. static void UpdateDShowInput(void *data, obs_data_t *settings)
  711. {
  712. UNUSED_PARAMETER(settings);
  713. reinterpret_cast<DShowInput*>(data)->QueueAction(Action::Update);
  714. }
  715. static void GetDShowDefaults(obs_data_t *settings)
  716. {
  717. obs_data_set_default_int(settings, FRAME_INTERVAL, FPS_MATCHING);
  718. obs_data_set_default_int(settings, RES_TYPE, ResType_Preferred);
  719. obs_data_set_default_int(settings, VIDEO_FORMAT, (int)VideoFormat::Any);
  720. }
  721. struct Resolution {
  722. int cx, cy;
  723. inline Resolution(int cx, int cy) : cx(cx), cy(cy) {}
  724. };
  725. static void InsertResolution(vector<Resolution> &resolutions, int cx, int cy)
  726. {
  727. int bestCY = 0;
  728. size_t idx = 0;
  729. for (; idx < resolutions.size(); idx++) {
  730. const Resolution &res = resolutions[idx];
  731. if (res.cx > cx)
  732. break;
  733. if (res.cx == cx) {
  734. if (res.cy == cy)
  735. return;
  736. if (!bestCY)
  737. bestCY = res.cy;
  738. else if (res.cy > bestCY)
  739. break;
  740. }
  741. }
  742. resolutions.insert(resolutions.begin() + idx, Resolution(cx, cy));
  743. }
  744. static inline void AddCap(vector<Resolution> &resolutions, const VideoInfo &cap)
  745. {
  746. InsertResolution(resolutions, cap.minCX, cap.minCY);
  747. InsertResolution(resolutions, cap.maxCX, cap.maxCY);
  748. }
  749. #define MAKE_DSHOW_FPS(fps) (10000000LL/(fps))
  750. #define MAKE_DSHOW_FRACTIONAL_FPS(den, num) ((num)*10000000LL/(den))
  751. static long long GetOBSFPS()
  752. {
  753. obs_video_info ovi;
  754. if (!obs_get_video_info(&ovi))
  755. return 0;
  756. return MAKE_DSHOW_FRACTIONAL_FPS(ovi.fps_num, ovi.fps_den);
  757. }
  758. struct FPSFormat {
  759. const char *text;
  760. long long interval;
  761. };
  762. static const FPSFormat validFPSFormats[] = {
  763. {"60", MAKE_DSHOW_FPS(60)},
  764. {"59.94 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(60000, 1001)},
  765. {"50", MAKE_DSHOW_FPS(50)},
  766. {"48 film", MAKE_DSHOW_FRACTIONAL_FPS(48000, 1001)},
  767. {"40", MAKE_DSHOW_FPS(40)},
  768. {"30", MAKE_DSHOW_FPS(30)},
  769. {"29.97 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(30000, 1001)},
  770. {"25", MAKE_DSHOW_FPS(25)},
  771. {"24 film", MAKE_DSHOW_FRACTIONAL_FPS(24000, 1001)},
  772. {"20", MAKE_DSHOW_FPS(20)},
  773. {"15", MAKE_DSHOW_FPS(15)},
  774. {"10", MAKE_DSHOW_FPS(10)},
  775. {"5", MAKE_DSHOW_FPS(5)},
  776. {"4", MAKE_DSHOW_FPS(4)},
  777. {"3", MAKE_DSHOW_FPS(3)},
  778. {"2", MAKE_DSHOW_FPS(2)},
  779. {"1", MAKE_DSHOW_FPS(1)},
  780. };
  781. static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p,
  782. obs_data_t *settings);
  783. static bool TryResolution(VideoDevice &dev, string res)
  784. {
  785. int cx, cy;
  786. if (!ConvertRes(cx, cy, res.c_str()))
  787. return false;
  788. return ResolutionAvailable(dev, cx, cy);
  789. }
  790. static bool SetResolution(obs_properties_t *props, obs_data_t *settings,
  791. string res, bool autoselect=false)
  792. {
  793. if (autoselect)
  794. obs_data_set_autoselect_string(settings, RESOLUTION,
  795. res.c_str());
  796. else
  797. obs_data_unset_autoselect_value(settings, RESOLUTION);
  798. DeviceIntervalChanged(props, obs_properties_get(props, FRAME_INTERVAL),
  799. settings);
  800. if (!autoselect)
  801. obs_data_set_string(settings, LAST_RESOLUTION, res.c_str());
  802. return true;
  803. }
  804. static bool DeviceResolutionChanged(obs_properties_t *props, obs_property_t *p,
  805. obs_data_t *settings)
  806. {
  807. UNUSED_PARAMETER(p);
  808. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  809. const char *id;
  810. VideoDevice device;
  811. id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  812. string res = obs_data_get_string(settings, RESOLUTION);
  813. string last_res = obs_data_get_string(settings, LAST_RESOLUTION);
  814. if (!data->GetDevice(device, id))
  815. return false;
  816. if (TryResolution(device, res))
  817. return SetResolution(props, settings, res);
  818. if (TryResolution(device, last_res))
  819. return SetResolution(props, settings, last_res, true);
  820. return false;
  821. }
  822. struct VideoFormatName {
  823. VideoFormat format;
  824. const char *name;
  825. };
  826. static const VideoFormatName videoFormatNames[] = {
  827. /* autoselect format*/
  828. {VideoFormat::Any, "VideoFormat.Any"},
  829. /* raw formats */
  830. {VideoFormat::ARGB, "ARGB"},
  831. {VideoFormat::XRGB, "XRGB"},
  832. /* planar YUV formats */
  833. {VideoFormat::I420, "I420"},
  834. {VideoFormat::NV12, "NV12"},
  835. {VideoFormat::YV12, "YV12"},
  836. /* packed YUV formats */
  837. {VideoFormat::YVYU, "YVYU"},
  838. {VideoFormat::YUY2, "YUY2"},
  839. {VideoFormat::UYVY, "UYVY"},
  840. {VideoFormat::HDYC, "HDYC"},
  841. /* encoded formats */
  842. {VideoFormat::MJPEG, "MJPEG"},
  843. {VideoFormat::H264, "H264"}
  844. };
  845. static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p,
  846. obs_data_t *settings);
  847. static size_t AddDevice(obs_property_t *device_list, const string &id)
  848. {
  849. DStr name, path;
  850. if (!DecodeDeviceDStr(name, path, id.c_str()))
  851. return numeric_limits<size_t>::max();
  852. return obs_property_list_add_string(device_list, name, id.c_str());
  853. }
  854. static bool UpdateDeviceList(obs_property_t *list, const string &id)
  855. {
  856. size_t size = obs_property_list_item_count(list);
  857. bool found = false;
  858. bool disabled_unknown_found = false;
  859. for (size_t i = 0; i < size; i++) {
  860. if (obs_property_list_item_string(list, i) == id) {
  861. found = true;
  862. continue;
  863. }
  864. if (obs_property_list_item_disabled(list, i))
  865. disabled_unknown_found = true;
  866. }
  867. if (!found && !disabled_unknown_found) {
  868. size_t idx = AddDevice(list, id);
  869. obs_property_list_item_disable(list, idx, true);
  870. return true;
  871. }
  872. if (found && !disabled_unknown_found)
  873. return false;
  874. for (size_t i = 0; i < size;) {
  875. if (obs_property_list_item_disabled(list, i)) {
  876. obs_property_list_item_remove(list, i);
  877. continue;
  878. }
  879. i += 1;
  880. }
  881. return true;
  882. }
  883. static bool DeviceSelectionChanged(obs_properties_t *props, obs_property_t *p,
  884. obs_data_t *settings)
  885. {
  886. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  887. VideoDevice device;
  888. string id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  889. string old_id = obs_data_get_string(settings, LAST_VIDEO_DEV_ID);
  890. bool device_list_updated = UpdateDeviceList(p, id);
  891. if (!data->GetDevice(device, id.c_str()))
  892. return !device_list_updated;
  893. vector<Resolution> resolutions;
  894. for (const VideoInfo &cap : device.caps)
  895. AddCap(resolutions, cap);
  896. p = obs_properties_get(props, RESOLUTION);
  897. obs_property_list_clear(p);
  898. for (size_t idx = resolutions.size(); idx > 0; idx--) {
  899. const Resolution &res = resolutions[idx-1];
  900. string strRes;
  901. strRes += to_string(res.cx);
  902. strRes += "x";
  903. strRes += to_string(res.cy);
  904. obs_property_list_add_string(p, strRes.c_str(), strRes.c_str());
  905. }
  906. /* only refresh properties if device legitimately changed */
  907. if (!id.size() || !old_id.size() || id != old_id) {
  908. p = obs_properties_get(props, RES_TYPE);
  909. ResTypeChanged(props, p, settings);
  910. obs_data_set_string(settings, LAST_VIDEO_DEV_ID, id.c_str());
  911. }
  912. return true;
  913. }
  914. static bool VideoConfigClicked(obs_properties_t *props, obs_property_t *p,
  915. void *data)
  916. {
  917. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  918. input->QueueAction(Action::ConfigVideo);
  919. UNUSED_PARAMETER(props);
  920. UNUSED_PARAMETER(p);
  921. return false;
  922. }
  923. /*static bool AudioConfigClicked(obs_properties_t *props, obs_property_t *p,
  924. void *data)
  925. {
  926. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  927. input->QueueAction(Action::ConfigAudio);
  928. UNUSED_PARAMETER(props);
  929. UNUSED_PARAMETER(p);
  930. return false;
  931. }*/
  932. static bool CrossbarConfigClicked(obs_properties_t *props, obs_property_t *p,
  933. void *data)
  934. {
  935. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  936. input->QueueAction(Action::ConfigCrossbar1);
  937. UNUSED_PARAMETER(props);
  938. UNUSED_PARAMETER(p);
  939. return false;
  940. }
  941. /*static bool Crossbar2ConfigClicked(obs_properties_t *props, obs_property_t *p,
  942. void *data)
  943. {
  944. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  945. input->QueueAction(Action::ConfigCrossbar2);
  946. UNUSED_PARAMETER(props);
  947. UNUSED_PARAMETER(p);
  948. return false;
  949. }*/
  950. static bool AddDevice(obs_property_t *device_list, const VideoDevice &device)
  951. {
  952. DStr name, path, device_id;
  953. dstr_from_wcs(name, device.name.c_str());
  954. dstr_from_wcs(path, device.path.c_str());
  955. encode_dstr(path);
  956. dstr_copy_dstr(device_id, name);
  957. encode_dstr(device_id);
  958. dstr_cat(device_id, ":");
  959. dstr_cat_dstr(device_id, path);
  960. obs_property_list_add_string(device_list, name, device_id);
  961. return true;
  962. }
  963. static bool AddAudioDevice(obs_property_t *device_list,
  964. const AudioDevice &device)
  965. {
  966. DStr name, path, device_id;
  967. dstr_from_wcs(name, device.name.c_str());
  968. dstr_from_wcs(path, device.path.c_str());
  969. encode_dstr(path);
  970. dstr_copy_dstr(device_id, name);
  971. encode_dstr(device_id);
  972. dstr_cat(device_id, ":");
  973. dstr_cat_dstr(device_id, path);
  974. obs_property_list_add_string(device_list, name, device_id);
  975. return true;
  976. }
  977. static void PropertiesDataDestroy(void *data)
  978. {
  979. delete reinterpret_cast<PropertiesData*>(data);
  980. }
  981. static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p,
  982. obs_data_t *settings)
  983. {
  984. int val = (int)obs_data_get_int(settings, RES_TYPE);
  985. bool enabled = (val != ResType_Preferred);
  986. p = obs_properties_get(props, RESOLUTION);
  987. obs_property_set_enabled(p, enabled);
  988. p = obs_properties_get(props, FRAME_INTERVAL);
  989. obs_property_set_enabled(p, enabled);
  990. p = obs_properties_get(props, VIDEO_FORMAT);
  991. obs_property_set_enabled(p, enabled);
  992. if (val == ResType_Custom) {
  993. p = obs_properties_get(props, RESOLUTION);
  994. DeviceResolutionChanged(props, p, settings);
  995. } else {
  996. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  997. }
  998. return true;
  999. }
  1000. static DStr GetFPSName(long long interval)
  1001. {
  1002. DStr name;
  1003. if (interval == FPS_MATCHING) {
  1004. dstr_cat(name, TEXT_FPS_MATCHING);
  1005. return name;
  1006. }
  1007. if (interval == FPS_HIGHEST) {
  1008. dstr_cat(name, TEXT_FPS_HIGHEST);
  1009. return name;
  1010. }
  1011. for (const FPSFormat &format : validFPSFormats) {
  1012. if (format.interval != interval)
  1013. continue;
  1014. dstr_cat(name, format.text);
  1015. return name;
  1016. }
  1017. dstr_cat(name, to_string(10000000. / interval).c_str());
  1018. return name;
  1019. }
  1020. static void UpdateFPS(VideoDevice &device, VideoFormat format,
  1021. long long interval, int cx, int cy, obs_properties_t *props)
  1022. {
  1023. obs_property_t *list = obs_properties_get(props, FRAME_INTERVAL);
  1024. obs_property_list_clear(list);
  1025. obs_property_list_add_int(list, TEXT_FPS_MATCHING, FPS_MATCHING);
  1026. obs_property_list_add_int(list, TEXT_FPS_HIGHEST, FPS_HIGHEST);
  1027. bool interval_added = interval == FPS_HIGHEST ||
  1028. interval == FPS_MATCHING;
  1029. for (const FPSFormat &fps_format : validFPSFormats) {
  1030. bool video_format_match = false;
  1031. long long format_interval = fps_format.interval;
  1032. bool available = CapsMatch(device,
  1033. ResolutionMatcher(cx, cy),
  1034. VideoFormatMatcher(format, video_format_match),
  1035. FrameRateMatcher(format_interval));
  1036. if (!available && interval != fps_format.interval)
  1037. continue;
  1038. if (interval == fps_format.interval)
  1039. interval_added = true;
  1040. size_t idx = obs_property_list_add_int(list, fps_format.text,
  1041. fps_format.interval);
  1042. obs_property_list_item_disable(list, idx, !available);
  1043. }
  1044. if (interval_added)
  1045. return;
  1046. size_t idx = obs_property_list_add_int(list, GetFPSName(interval),
  1047. interval);
  1048. obs_property_list_item_disable(list, idx, true);
  1049. }
  1050. static DStr GetVideoFormatName(VideoFormat format)
  1051. {
  1052. DStr name;
  1053. for (const VideoFormatName &format_ : videoFormatNames) {
  1054. if (format_.format == format) {
  1055. dstr_cat(name, obs_module_text(format_.name));
  1056. return name;
  1057. }
  1058. }
  1059. dstr_cat(name, TEXT_FORMAT_UNKNOWN);
  1060. dstr_replace(name, "%1", std::to_string((long long)format).c_str());
  1061. return name;
  1062. }
  1063. static void UpdateVideoFormats(VideoDevice &device, VideoFormat format_,
  1064. int cx, int cy, long long interval, obs_properties_t *props)
  1065. {
  1066. set<VideoFormat> formats = { VideoFormat::Any };
  1067. auto format_gatherer = [&formats](const VideoInfo &info) mutable -> bool
  1068. {
  1069. formats.insert(info.format);
  1070. return false;
  1071. };
  1072. CapsMatch(device,
  1073. ResolutionMatcher(cx, cy),
  1074. FrameRateMatcher(interval),
  1075. format_gatherer);
  1076. obs_property_t *list = obs_properties_get(props, VIDEO_FORMAT);
  1077. obs_property_list_clear(list);
  1078. bool format_added = false;
  1079. for (const VideoFormatName &format : videoFormatNames) {
  1080. bool available = formats.find(format.format) != end(formats);
  1081. if (!available && format.format != format_)
  1082. continue;
  1083. if (format.format == format_)
  1084. format_added = true;
  1085. size_t idx = obs_property_list_add_int(list,
  1086. obs_module_text(format.name),
  1087. (long long)format.format);
  1088. obs_property_list_item_disable(list, idx, !available);
  1089. }
  1090. if (format_added)
  1091. return;
  1092. size_t idx = obs_property_list_add_int(list,
  1093. GetVideoFormatName(format_), (long long)format_);
  1094. obs_property_list_item_disable(list, idx, true);
  1095. }
  1096. static bool UpdateFPS(long long interval, obs_property_t *list)
  1097. {
  1098. size_t size = obs_property_list_item_count(list);
  1099. bool fps_found = false;
  1100. DStr name;
  1101. for (size_t i = 0; i < size; i++) {
  1102. if (obs_property_list_item_int(list, i) != interval)
  1103. continue;
  1104. obs_property_list_item_disable(list, i, true);
  1105. if (size == 1)
  1106. return false;
  1107. dstr_cat(name, obs_property_list_item_name(list, i));
  1108. fps_found = true;
  1109. break;
  1110. }
  1111. obs_property_list_clear(list);
  1112. if (!name->len)
  1113. name = GetFPSName(interval);
  1114. obs_property_list_add_int(list, name, interval);
  1115. obs_property_list_item_disable(list, 0, true);
  1116. return true;
  1117. }
  1118. static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p,
  1119. obs_data_t *settings)
  1120. {
  1121. long long val = obs_data_get_int(settings, FRAME_INTERVAL);
  1122. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  1123. const char *id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1124. VideoDevice device;
  1125. if (!data->GetDevice(device, id))
  1126. return UpdateFPS(val, p);
  1127. int cx = 0, cy = 0;
  1128. if (!DetermineResolution(cx, cy, settings, device)) {
  1129. UpdateVideoFormats(device, VideoFormat::Any, 0, 0, 0, props);
  1130. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  1131. return true;
  1132. }
  1133. int resType = (int)obs_data_get_int(settings, RES_TYPE);
  1134. if (resType != ResType_Custom)
  1135. return true;
  1136. if (val == FPS_MATCHING)
  1137. val = GetOBSFPS();
  1138. VideoFormat format = (VideoFormat)obs_data_get_int(settings,
  1139. VIDEO_FORMAT);
  1140. bool video_format_matches = false;
  1141. long long best_interval = numeric_limits<long long>::max();
  1142. bool frameRateSupported = CapsMatch(device,
  1143. ResolutionMatcher(cx, cy),
  1144. VideoFormatMatcher(format, video_format_matches),
  1145. ClosestFrameRateSelector(val, best_interval),
  1146. FrameRateMatcher(val));
  1147. if (video_format_matches &&
  1148. !frameRateSupported &&
  1149. best_interval != val) {
  1150. long long listed_val = 0;
  1151. for (const FPSFormat &format : validFPSFormats) {
  1152. long long diff = llabs(format.interval - best_interval);
  1153. if (diff < DEVICE_INTERVAL_DIFF_LIMIT) {
  1154. listed_val = format.interval;
  1155. break;
  1156. }
  1157. }
  1158. if (listed_val != val)
  1159. obs_data_set_autoselect_int(settings, FRAME_INTERVAL,
  1160. listed_val);
  1161. } else {
  1162. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  1163. }
  1164. UpdateVideoFormats(device, format, cx, cy, val, props);
  1165. UpdateFPS(device, format, val, cx, cy, props);
  1166. UNUSED_PARAMETER(p);
  1167. return true;
  1168. }
  1169. static bool UpdateVideoFormats(VideoFormat format, obs_property_t *list)
  1170. {
  1171. size_t size = obs_property_list_item_count(list);
  1172. DStr name;
  1173. for (size_t i = 0; i < size; i++) {
  1174. if ((VideoFormat)obs_property_list_item_int(list, i) != format)
  1175. continue;
  1176. if (size == 1)
  1177. return false;
  1178. dstr_cat(name, obs_property_list_item_name(list, i));
  1179. break;
  1180. }
  1181. obs_property_list_clear(list);
  1182. if (!name->len)
  1183. name = GetVideoFormatName(format);
  1184. obs_property_list_add_int(list, name, (long long)format);
  1185. obs_property_list_item_disable(list, 0, true);
  1186. return true;
  1187. }
  1188. static bool VideoFormatChanged(obs_properties_t *props, obs_property_t *p,
  1189. obs_data_t *settings)
  1190. {
  1191. PropertiesData *data = (PropertiesData*)obs_properties_get_param(props);
  1192. const char *id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1193. VideoDevice device;
  1194. VideoFormat curFormat =
  1195. (VideoFormat)obs_data_get_int(settings, VIDEO_FORMAT);
  1196. if (!data->GetDevice(device, id))
  1197. return UpdateVideoFormats(curFormat, p);
  1198. int cx, cy;
  1199. if (!DetermineResolution(cx, cy, settings, device)) {
  1200. UpdateVideoFormats(device, VideoFormat::Any, cx, cy, 0, props);
  1201. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  1202. return true;
  1203. }
  1204. long long interval = obs_data_get_int(settings, FRAME_INTERVAL);
  1205. UpdateVideoFormats(device, curFormat, cx, cy, interval, props);
  1206. UpdateFPS(device, curFormat, interval, cx, cy, props);
  1207. return true;
  1208. }
  1209. static bool CustomAudioClicked(obs_properties_t *props, obs_property_t *p,
  1210. obs_data_t *settings)
  1211. {
  1212. bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO);
  1213. p = obs_properties_get(props, AUDIO_DEVICE_ID);
  1214. obs_property_set_visible(p, useCustomAudio);
  1215. return true;
  1216. }
  1217. static obs_properties_t *GetDShowProperties(void *)
  1218. {
  1219. obs_properties_t *ppts = obs_properties_create();
  1220. PropertiesData *data = new PropertiesData;
  1221. obs_properties_set_param(ppts, data, PropertiesDataDestroy);
  1222. obs_property_t *p = obs_properties_add_list(ppts,
  1223. VIDEO_DEVICE_ID, TEXT_DEVICE,
  1224. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1225. obs_property_set_modified_callback(p, DeviceSelectionChanged);
  1226. Device::EnumVideoDevices(data->devices);
  1227. for (const VideoDevice &device : data->devices)
  1228. AddDevice(p, device);
  1229. obs_properties_add_button(ppts, "video_config", TEXT_CONFIG_VIDEO,
  1230. VideoConfigClicked);
  1231. obs_properties_add_button(ppts, "xbar_config", TEXT_CONFIG_XBAR,
  1232. CrossbarConfigClicked);
  1233. /* ------------------------------------- */
  1234. /* video settings */
  1235. p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE,
  1236. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1237. obs_property_set_modified_callback(p, ResTypeChanged);
  1238. obs_property_list_add_int(p, TEXT_PREFERRED_RES, ResType_Preferred);
  1239. obs_property_list_add_int(p, TEXT_CUSTOM_RES, ResType_Custom);
  1240. p = obs_properties_add_list(ppts, RESOLUTION, TEXT_RESOLUTION,
  1241. OBS_COMBO_TYPE_EDITABLE, OBS_COMBO_FORMAT_STRING);
  1242. obs_property_set_modified_callback(p, DeviceResolutionChanged);
  1243. p = obs_properties_add_list(ppts, FRAME_INTERVAL, "FPS",
  1244. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1245. obs_property_set_modified_callback(p, DeviceIntervalChanged);
  1246. p = obs_properties_add_list(ppts, VIDEO_FORMAT, TEXT_VIDEO_FORMAT,
  1247. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1248. obs_property_set_modified_callback(p, VideoFormatChanged);
  1249. p = obs_properties_add_list(ppts, BUFFERING_VAL, TEXT_BUFFERING,
  1250. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1251. obs_property_list_add_int(p, TEXT_BUFFERING_AUTO,
  1252. (int64_t)BufferingType::Auto);
  1253. obs_property_list_add_int(p, TEXT_BUFFERING_ON,
  1254. (int64_t)BufferingType::On);
  1255. obs_property_list_add_int(p, TEXT_BUFFERING_OFF,
  1256. (int64_t)BufferingType::Off);
  1257. /* ------------------------------------- */
  1258. /* audio settings */
  1259. Device::EnumAudioDevices(data->audioDevices);
  1260. if (!data->audioDevices.size())
  1261. return ppts;
  1262. p = obs_properties_add_bool(ppts, USE_CUSTOM_AUDIO, TEXT_CUSTOM_AUDIO);
  1263. obs_property_set_modified_callback(p, CustomAudioClicked);
  1264. p = obs_properties_add_list(ppts, AUDIO_DEVICE_ID, TEXT_AUDIO_DEVICE,
  1265. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1266. for (const AudioDevice &device : data->audioDevices)
  1267. AddAudioDevice(p, device);
  1268. return ppts;
  1269. }
  1270. void DShowModuleLogCallback(LogType type, const wchar_t *msg, void *param)
  1271. {
  1272. int obs_type = LOG_DEBUG;
  1273. switch (type) {
  1274. case LogType::Error: obs_type = LOG_ERROR; break;
  1275. case LogType::Warning: obs_type = LOG_WARNING; break;
  1276. case LogType::Info: obs_type = LOG_INFO; break;
  1277. case LogType::Debug: obs_type = LOG_DEBUG; break;
  1278. }
  1279. DStr dmsg;
  1280. dstr_from_wcs(dmsg, msg);
  1281. blog(obs_type, "DShow: %s", dmsg->array);
  1282. UNUSED_PARAMETER(param);
  1283. }
  1284. void RegisterDShowSource()
  1285. {
  1286. SetLogCallback(DShowModuleLogCallback, nullptr);
  1287. obs_source_info info = {};
  1288. info.id = "dshow_input";
  1289. info.type = OBS_SOURCE_TYPE_INPUT;
  1290. info.output_flags = OBS_SOURCE_VIDEO |
  1291. OBS_SOURCE_AUDIO |
  1292. OBS_SOURCE_ASYNC;
  1293. info.get_name = GetDShowInputName;
  1294. info.create = CreateDShowInput;
  1295. info.destroy = DestroyDShowInput;
  1296. info.update = UpdateDShowInput;
  1297. info.get_defaults = GetDShowDefaults;
  1298. info.get_properties = GetDShowProperties;
  1299. obs_register_source(&info);
  1300. }