win-dshow.cpp 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992
  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. /* clang-format off */
  26. /* settings defines that will cause errors if there are typos */
  27. #define VIDEO_DEVICE_ID "video_device_id"
  28. #define RES_TYPE "res_type"
  29. #define RESOLUTION "resolution"
  30. #define FRAME_INTERVAL "frame_interval"
  31. #define VIDEO_FORMAT "video_format"
  32. #define LAST_VIDEO_DEV_ID "last_video_device_id"
  33. #define LAST_RESOLUTION "last_resolution"
  34. #define BUFFERING_VAL "buffering"
  35. #define FLIP_IMAGE "flip_vertically"
  36. #define AUDIO_OUTPUT_MODE "audio_output_mode"
  37. #define USE_CUSTOM_AUDIO "use_custom_audio_device"
  38. #define AUDIO_DEVICE_ID "audio_device_id"
  39. #define COLOR_SPACE "color_space"
  40. #define COLOR_RANGE "color_range"
  41. #define DEACTIVATE_WNS "deactivate_when_not_showing"
  42. #define TEXT_INPUT_NAME obs_module_text("VideoCaptureDevice")
  43. #define TEXT_DEVICE obs_module_text("Device")
  44. #define TEXT_CONFIG_VIDEO obs_module_text("ConfigureVideo")
  45. #define TEXT_CONFIG_XBAR obs_module_text("ConfigureCrossbar")
  46. #define TEXT_RES_FPS_TYPE obs_module_text("ResFPSType")
  47. #define TEXT_CUSTOM_RES obs_module_text("ResFPSType.Custom")
  48. #define TEXT_PREFERRED_RES obs_module_text("ResFPSType.DevPreferred")
  49. #define TEXT_FPS_MATCHING obs_module_text("FPS.Matching")
  50. #define TEXT_FPS_HIGHEST obs_module_text("FPS.Highest")
  51. #define TEXT_RESOLUTION obs_module_text("Resolution")
  52. #define TEXT_VIDEO_FORMAT obs_module_text("VideoFormat")
  53. #define TEXT_FORMAT_UNKNOWN obs_module_text("VideoFormat.Unknown")
  54. #define TEXT_BUFFERING obs_module_text("Buffering")
  55. #define TEXT_BUFFERING_AUTO obs_module_text("Buffering.AutoDetect")
  56. #define TEXT_BUFFERING_ON obs_module_text("Buffering.Enable")
  57. #define TEXT_BUFFERING_OFF obs_module_text("Buffering.Disable")
  58. #define TEXT_FLIP_IMAGE obs_module_text("FlipVertically")
  59. #define TEXT_AUDIO_MODE obs_module_text("AudioOutputMode")
  60. #define TEXT_MODE_CAPTURE obs_module_text("AudioOutputMode.Capture")
  61. #define TEXT_MODE_DSOUND obs_module_text("AudioOutputMode.DirectSound")
  62. #define TEXT_MODE_WAVEOUT obs_module_text("AudioOutputMode.WaveOut")
  63. #define TEXT_CUSTOM_AUDIO obs_module_text("UseCustomAudioDevice")
  64. #define TEXT_AUDIO_DEVICE obs_module_text("AudioDevice")
  65. #define TEXT_ACTIVATE obs_module_text("Activate")
  66. #define TEXT_DEACTIVATE obs_module_text("Deactivate")
  67. #define TEXT_COLOR_SPACE obs_module_text("ColorSpace")
  68. #define TEXT_COLOR_DEFAULT obs_module_text("ColorSpace.Default")
  69. #define TEXT_COLOR_RANGE obs_module_text("ColorRange")
  70. #define TEXT_RANGE_DEFAULT obs_module_text("ColorRange.Default")
  71. #define TEXT_RANGE_PARTIAL obs_module_text("ColorRange.Partial")
  72. #define TEXT_RANGE_FULL obs_module_text("ColorRange.Full")
  73. #define TEXT_DWNS obs_module_text("DeactivateWhenNotShowing")
  74. /* clang-format on */
  75. enum ResType {
  76. ResType_Preferred,
  77. ResType_Custom,
  78. };
  79. enum class BufferingType : int64_t {
  80. Auto,
  81. On,
  82. Off,
  83. };
  84. void ffmpeg_log(void *bla, int level, const char *msg, va_list args)
  85. {
  86. DStr str;
  87. if (level == AV_LOG_WARNING)
  88. dstr_copy(str, "warning: ");
  89. else if (level == AV_LOG_ERROR)
  90. dstr_copy(str, "error: ");
  91. else if (level < AV_LOG_ERROR)
  92. dstr_copy(str, "fatal: ");
  93. else
  94. return;
  95. dstr_cat(str, msg);
  96. if (dstr_end(str) == '\n')
  97. dstr_resize(str, str->len - 1);
  98. blogva(LOG_WARNING, str, args);
  99. av_log_default_callback(bla, level, msg, args);
  100. }
  101. class Decoder {
  102. struct ffmpeg_decode decode;
  103. public:
  104. inline Decoder() { memset(&decode, 0, sizeof(decode)); }
  105. inline ~Decoder() { ffmpeg_decode_free(&decode); }
  106. inline operator ffmpeg_decode *() { return &decode; }
  107. inline ffmpeg_decode *operator->() { return &decode; }
  108. };
  109. class CriticalSection {
  110. CRITICAL_SECTION mutex;
  111. public:
  112. inline CriticalSection() { InitializeCriticalSection(&mutex); }
  113. inline ~CriticalSection() { DeleteCriticalSection(&mutex); }
  114. inline operator CRITICAL_SECTION *() { return &mutex; }
  115. };
  116. class CriticalScope {
  117. CriticalSection &mutex;
  118. CriticalScope() = delete;
  119. CriticalScope &operator=(CriticalScope &cs) = delete;
  120. public:
  121. inline CriticalScope(CriticalSection &mutex_) : mutex(mutex_)
  122. {
  123. EnterCriticalSection(mutex);
  124. }
  125. inline ~CriticalScope() { LeaveCriticalSection(mutex); }
  126. };
  127. enum class Action {
  128. None,
  129. Activate,
  130. ActivateBlock,
  131. Deactivate,
  132. Shutdown,
  133. ConfigVideo,
  134. ConfigAudio,
  135. ConfigCrossbar1,
  136. ConfigCrossbar2,
  137. };
  138. static DWORD CALLBACK DShowThread(LPVOID ptr);
  139. struct DShowInput {
  140. obs_source_t *source;
  141. Device device;
  142. bool deactivateWhenNotShowing = false;
  143. bool deviceHasAudio = false;
  144. bool deviceHasSeparateAudioFilter = false;
  145. bool flip = false;
  146. bool active = false;
  147. Decoder audio_decoder;
  148. Decoder video_decoder;
  149. VideoConfig videoConfig;
  150. AudioConfig audioConfig;
  151. obs_source_frame2 frame;
  152. obs_source_audio audio;
  153. WinHandle semaphore;
  154. WinHandle activated_event;
  155. WinHandle thread;
  156. CriticalSection mutex;
  157. vector<Action> actions;
  158. inline void QueueAction(Action action)
  159. {
  160. CriticalScope scope(mutex);
  161. actions.push_back(action);
  162. ReleaseSemaphore(semaphore, 1, nullptr);
  163. }
  164. inline void QueueActivate(obs_data_t *settings)
  165. {
  166. bool block =
  167. obs_data_get_bool(settings, "synchronous_activate");
  168. QueueAction(block ? Action::ActivateBlock : Action::Activate);
  169. if (block) {
  170. obs_data_erase(settings, "synchronous_activate");
  171. WaitForSingleObject(activated_event, INFINITE);
  172. }
  173. }
  174. inline DShowInput(obs_source_t *source_, obs_data_t *settings)
  175. : source(source_), device(InitGraph::False)
  176. {
  177. memset(&audio, 0, sizeof(audio));
  178. memset(&frame, 0, sizeof(frame));
  179. av_log_set_level(AV_LOG_WARNING);
  180. av_log_set_callback(ffmpeg_log);
  181. semaphore = CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr);
  182. if (!semaphore)
  183. throw "Failed to create semaphore";
  184. activated_event = CreateEvent(nullptr, false, false, nullptr);
  185. if (!activated_event)
  186. throw "Failed to create activated_event";
  187. thread =
  188. CreateThread(nullptr, 0, DShowThread, this, 0, nullptr);
  189. if (!thread)
  190. throw "Failed to create thread";
  191. deactivateWhenNotShowing =
  192. obs_data_get_bool(settings, DEACTIVATE_WNS);
  193. if (obs_data_get_bool(settings, "active")) {
  194. bool showing = obs_source_showing(source);
  195. if (!deactivateWhenNotShowing || showing)
  196. QueueActivate(settings);
  197. active = true;
  198. }
  199. }
  200. inline ~DShowInput()
  201. {
  202. {
  203. CriticalScope scope(mutex);
  204. actions.resize(1);
  205. actions[0] = Action::Shutdown;
  206. }
  207. ReleaseSemaphore(semaphore, 1, nullptr);
  208. WaitForSingleObject(thread, INFINITE);
  209. }
  210. void OnEncodedVideoData(enum AVCodecID id, unsigned char *data,
  211. size_t size, long long ts);
  212. void OnEncodedAudioData(enum AVCodecID id, unsigned char *data,
  213. size_t size, long long ts);
  214. void OnVideoData(const VideoConfig &config, unsigned char *data,
  215. size_t size, long long startTime, long long endTime);
  216. void OnAudioData(const AudioConfig &config, unsigned char *data,
  217. size_t size, long long startTime, long long endTime);
  218. bool UpdateVideoConfig(obs_data_t *settings);
  219. bool UpdateAudioConfig(obs_data_t *settings);
  220. void SetActive(bool active);
  221. inline enum video_colorspace GetColorSpace(obs_data_t *settings) const;
  222. inline enum video_range_type GetColorRange(obs_data_t *settings) const;
  223. inline bool Activate(obs_data_t *settings);
  224. inline void Deactivate();
  225. inline void SetupBuffering(obs_data_t *settings);
  226. void DShowLoop();
  227. };
  228. static DWORD CALLBACK DShowThread(LPVOID ptr)
  229. {
  230. DShowInput *dshowInput = (DShowInput *)ptr;
  231. os_set_thread_name("win-dshow: DShowThread");
  232. CoInitialize(nullptr);
  233. dshowInput->DShowLoop();
  234. CoUninitialize();
  235. return 0;
  236. }
  237. static inline void ProcessMessages()
  238. {
  239. MSG msg;
  240. while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
  241. TranslateMessage(&msg);
  242. DispatchMessage(&msg);
  243. }
  244. }
  245. /* Always keep directshow in a single thread for a given device */
  246. void DShowInput::DShowLoop()
  247. {
  248. while (true) {
  249. DWORD ret = MsgWaitForMultipleObjects(1, &semaphore, false,
  250. INFINITE, QS_ALLINPUT);
  251. if (ret == (WAIT_OBJECT_0 + 1)) {
  252. ProcessMessages();
  253. continue;
  254. } else if (ret != WAIT_OBJECT_0) {
  255. break;
  256. }
  257. Action action = Action::None;
  258. {
  259. CriticalScope scope(mutex);
  260. if (actions.size()) {
  261. action = actions.front();
  262. actions.erase(actions.begin());
  263. }
  264. }
  265. switch (action) {
  266. case Action::Activate:
  267. case Action::ActivateBlock: {
  268. bool block = action == Action::ActivateBlock;
  269. obs_data_t *settings;
  270. settings = obs_source_get_settings(source);
  271. if (!Activate(settings)) {
  272. obs_source_output_video2(source, nullptr);
  273. }
  274. if (block)
  275. SetEvent(activated_event);
  276. obs_data_release(settings);
  277. break;
  278. }
  279. case Action::Deactivate:
  280. Deactivate();
  281. break;
  282. case Action::Shutdown:
  283. device.ShutdownGraph();
  284. return;
  285. case Action::ConfigVideo:
  286. device.OpenDialog(nullptr, DialogType::ConfigVideo);
  287. break;
  288. case Action::ConfigAudio:
  289. device.OpenDialog(nullptr, DialogType::ConfigAudio);
  290. break;
  291. case Action::ConfigCrossbar1:
  292. device.OpenDialog(nullptr, DialogType::ConfigCrossbar);
  293. break;
  294. case Action::ConfigCrossbar2:
  295. device.OpenDialog(nullptr, DialogType::ConfigCrossbar2);
  296. break;
  297. case Action::None:;
  298. }
  299. }
  300. }
  301. #define FPS_HIGHEST 0LL
  302. #define FPS_MATCHING -1LL
  303. template<typename T, typename U, typename V>
  304. static bool between(T &&lower, U &&value, V &&upper)
  305. {
  306. return value >= lower && value <= upper;
  307. }
  308. static bool ResolutionAvailable(const VideoInfo &cap, int cx, int cy)
  309. {
  310. return between(cap.minCX, cx, cap.maxCX) &&
  311. between(cap.minCY, cy, cap.maxCY);
  312. }
  313. #define DEVICE_INTERVAL_DIFF_LIMIT 20
  314. static bool FrameRateAvailable(const VideoInfo &cap, long long interval)
  315. {
  316. return interval == FPS_HIGHEST || interval == FPS_MATCHING ||
  317. between(cap.minInterval - DEVICE_INTERVAL_DIFF_LIMIT, interval,
  318. cap.maxInterval + DEVICE_INTERVAL_DIFF_LIMIT);
  319. }
  320. static long long FrameRateInterval(const VideoInfo &cap,
  321. long long desired_interval)
  322. {
  323. return desired_interval < cap.minInterval
  324. ? cap.minInterval
  325. : min(desired_interval, cap.maxInterval);
  326. }
  327. static inline video_format ConvertVideoFormat(VideoFormat format)
  328. {
  329. switch (format) {
  330. case VideoFormat::ARGB:
  331. return VIDEO_FORMAT_BGRA;
  332. case VideoFormat::XRGB:
  333. return VIDEO_FORMAT_BGRX;
  334. case VideoFormat::I420:
  335. return VIDEO_FORMAT_I420;
  336. case VideoFormat::YV12:
  337. return VIDEO_FORMAT_I420;
  338. case VideoFormat::NV12:
  339. return VIDEO_FORMAT_NV12;
  340. case VideoFormat::Y800:
  341. return VIDEO_FORMAT_Y800;
  342. case VideoFormat::YVYU:
  343. return VIDEO_FORMAT_YVYU;
  344. case VideoFormat::YUY2:
  345. return VIDEO_FORMAT_YUY2;
  346. case VideoFormat::UYVY:
  347. return VIDEO_FORMAT_UYVY;
  348. case VideoFormat::HDYC:
  349. return VIDEO_FORMAT_UYVY;
  350. default:
  351. return VIDEO_FORMAT_NONE;
  352. }
  353. }
  354. static inline audio_format ConvertAudioFormat(AudioFormat format)
  355. {
  356. switch (format) {
  357. case AudioFormat::Wave16bit:
  358. return AUDIO_FORMAT_16BIT;
  359. case AudioFormat::WaveFloat:
  360. return AUDIO_FORMAT_FLOAT;
  361. default:
  362. return AUDIO_FORMAT_UNKNOWN;
  363. }
  364. }
  365. static inline enum speaker_layout convert_speaker_layout(uint8_t channels)
  366. {
  367. switch (channels) {
  368. case 0:
  369. return SPEAKERS_UNKNOWN;
  370. case 1:
  371. return SPEAKERS_MONO;
  372. case 2:
  373. return SPEAKERS_STEREO;
  374. case 3:
  375. return SPEAKERS_2POINT1;
  376. case 4:
  377. return SPEAKERS_4POINT0;
  378. case 5:
  379. return SPEAKERS_4POINT1;
  380. case 6:
  381. return SPEAKERS_5POINT1;
  382. case 8:
  383. return SPEAKERS_7POINT1;
  384. default:
  385. return SPEAKERS_UNKNOWN;
  386. }
  387. }
  388. //#define LOG_ENCODED_VIDEO_TS 1
  389. //#define LOG_ENCODED_AUDIO_TS 1
  390. #define MAX_SW_RES_INT (1920 * 1080)
  391. void DShowInput::OnEncodedVideoData(enum AVCodecID id, unsigned char *data,
  392. size_t size, long long ts)
  393. {
  394. if (!ffmpeg_decode_valid(video_decoder)) {
  395. /* Only use MJPEG hardware decoding on resolutions higher
  396. * than 1920x1080. The reason why is because we want to strike
  397. * a reasonable balance between hardware and CPU usage. */
  398. bool useHW = videoConfig.format != VideoFormat::MJPEG ||
  399. (videoConfig.cx * videoConfig.cy) > MAX_SW_RES_INT;
  400. if (ffmpeg_decode_init(video_decoder, id, useHW) < 0) {
  401. blog(LOG_WARNING, "Could not initialize video decoder");
  402. return;
  403. }
  404. }
  405. bool got_output;
  406. bool success = ffmpeg_decode_video(video_decoder, data, size, &ts,
  407. &frame, &got_output);
  408. if (!success) {
  409. blog(LOG_WARNING, "Error decoding video");
  410. return;
  411. }
  412. if (got_output) {
  413. frame.timestamp = (uint64_t)ts * 100;
  414. if (flip)
  415. frame.flip = !frame.flip;
  416. #if LOG_ENCODED_VIDEO_TS
  417. blog(LOG_DEBUG, "video ts: %llu", frame.timestamp);
  418. #endif
  419. obs_source_output_video2(source, &frame);
  420. }
  421. }
  422. void DShowInput::OnVideoData(const VideoConfig &config, unsigned char *data,
  423. size_t size, long long startTime,
  424. long long endTime)
  425. {
  426. if (videoConfig.format == VideoFormat::H264) {
  427. OnEncodedVideoData(AV_CODEC_ID_H264, data, size, startTime);
  428. return;
  429. }
  430. if (videoConfig.format == VideoFormat::MJPEG) {
  431. OnEncodedVideoData(AV_CODEC_ID_MJPEG, data, size, startTime);
  432. return;
  433. }
  434. const int cx = config.cx;
  435. const int cy = config.cy;
  436. frame.timestamp = (uint64_t)startTime * 100;
  437. frame.width = config.cx;
  438. frame.height = config.cy;
  439. frame.format = ConvertVideoFormat(config.format);
  440. frame.flip = (config.format == VideoFormat::XRGB ||
  441. config.format == VideoFormat::ARGB);
  442. if (flip)
  443. frame.flip = !frame.flip;
  444. if (videoConfig.format == VideoFormat::XRGB ||
  445. videoConfig.format == VideoFormat::ARGB) {
  446. frame.data[0] = data;
  447. frame.linesize[0] = cx * 4;
  448. } else if (videoConfig.format == VideoFormat::YVYU ||
  449. videoConfig.format == VideoFormat::YUY2 ||
  450. videoConfig.format == VideoFormat::HDYC ||
  451. videoConfig.format == VideoFormat::UYVY) {
  452. frame.data[0] = data;
  453. frame.linesize[0] = cx * 2;
  454. } else if (videoConfig.format == VideoFormat::I420) {
  455. frame.data[0] = data;
  456. frame.data[1] = frame.data[0] + (cx * cy);
  457. frame.data[2] = frame.data[1] + (cx * cy / 4);
  458. frame.linesize[0] = cx;
  459. frame.linesize[1] = cx / 2;
  460. frame.linesize[2] = cx / 2;
  461. } else if (videoConfig.format == VideoFormat::YV12) {
  462. frame.data[0] = data;
  463. frame.data[2] = frame.data[0] + (cx * cy);
  464. frame.data[1] = frame.data[2] + (cx * cy / 4);
  465. frame.linesize[0] = cx;
  466. frame.linesize[1] = cx / 2;
  467. frame.linesize[2] = cx / 2;
  468. } else if (videoConfig.format == VideoFormat::NV12) {
  469. frame.data[0] = data;
  470. frame.data[1] = frame.data[0] + (cx * cy);
  471. frame.linesize[0] = cx;
  472. frame.linesize[1] = cx;
  473. } else if (videoConfig.format == VideoFormat::Y800) {
  474. frame.data[0] = data;
  475. frame.linesize[0] = cx;
  476. } else {
  477. /* TODO: other formats */
  478. return;
  479. }
  480. obs_source_output_video2(source, &frame);
  481. UNUSED_PARAMETER(endTime); /* it's the enndd tiimmes! */
  482. UNUSED_PARAMETER(size);
  483. }
  484. void DShowInput::OnEncodedAudioData(enum AVCodecID id, unsigned char *data,
  485. size_t size, long long ts)
  486. {
  487. if (!ffmpeg_decode_valid(audio_decoder)) {
  488. if (ffmpeg_decode_init(audio_decoder, id, false) < 0) {
  489. blog(LOG_WARNING, "Could not initialize audio decoder");
  490. return;
  491. }
  492. }
  493. bool got_output = false;
  494. do {
  495. bool success = ffmpeg_decode_audio(audio_decoder, data, size,
  496. &audio, &got_output);
  497. if (!success) {
  498. blog(LOG_WARNING, "Error decoding audio");
  499. return;
  500. }
  501. if (got_output) {
  502. audio.timestamp = (uint64_t)ts * 100;
  503. #if LOG_ENCODED_AUDIO_TS
  504. blog(LOG_DEBUG, "audio ts: %llu", audio.timestamp);
  505. #endif
  506. obs_source_output_audio(source, &audio);
  507. } else {
  508. break;
  509. }
  510. ts += int64_t(audio_decoder->frame->nb_samples) * 10000000LL /
  511. int64_t(audio_decoder->frame->sample_rate);
  512. size = 0;
  513. data = nullptr;
  514. } while (got_output);
  515. }
  516. void DShowInput::OnAudioData(const AudioConfig &config, unsigned char *data,
  517. size_t size, long long startTime,
  518. long long endTime)
  519. {
  520. size_t block_size;
  521. if (config.format == AudioFormat::AAC) {
  522. OnEncodedAudioData(AV_CODEC_ID_AAC, data, size, startTime);
  523. return;
  524. } else if (config.format == AudioFormat::AC3) {
  525. OnEncodedAudioData(AV_CODEC_ID_AC3, data, size, startTime);
  526. return;
  527. } else if (config.format == AudioFormat::MPGA) {
  528. OnEncodedAudioData(AV_CODEC_ID_MP2, data, size, startTime);
  529. return;
  530. }
  531. audio.speakers = convert_speaker_layout((uint8_t)config.channels);
  532. audio.format = ConvertAudioFormat(config.format);
  533. audio.samples_per_sec = (uint32_t)config.sampleRate;
  534. audio.data[0] = data;
  535. block_size = get_audio_bytes_per_channel(audio.format) *
  536. get_audio_channels(audio.speakers);
  537. audio.frames = (uint32_t)(size / block_size);
  538. audio.timestamp = (uint64_t)startTime * 100;
  539. if (audio.format != AUDIO_FORMAT_UNKNOWN)
  540. obs_source_output_audio(source, &audio);
  541. UNUSED_PARAMETER(endTime);
  542. }
  543. struct PropertiesData {
  544. DShowInput *input;
  545. vector<VideoDevice> devices;
  546. vector<AudioDevice> audioDevices;
  547. bool GetDevice(VideoDevice &device, const char *encoded_id) const
  548. {
  549. DeviceId deviceId;
  550. DecodeDeviceId(deviceId, encoded_id);
  551. for (const VideoDevice &curDevice : devices) {
  552. if (deviceId.name == curDevice.name &&
  553. deviceId.path == curDevice.path) {
  554. device = curDevice;
  555. return true;
  556. }
  557. }
  558. return false;
  559. }
  560. };
  561. static inline bool ConvertRes(int &cx, int &cy, const char *res)
  562. {
  563. return sscanf(res, "%dx%d", &cx, &cy) == 2;
  564. }
  565. static inline bool FormatMatches(VideoFormat left, VideoFormat right)
  566. {
  567. return left == VideoFormat::Any || right == VideoFormat::Any ||
  568. left == right;
  569. }
  570. static inline bool ResolutionValid(string res, int &cx, int &cy)
  571. {
  572. if (!res.size())
  573. return false;
  574. return ConvertRes(cx, cy, res.c_str());
  575. }
  576. static inline bool CapsMatch(const VideoInfo &)
  577. {
  578. return true;
  579. }
  580. template<typename... F> static bool CapsMatch(const VideoDevice &dev, F... fs);
  581. template<typename F, typename... Fs>
  582. static inline bool CapsMatch(const VideoInfo &info, F &&f, Fs... fs)
  583. {
  584. return f(info) && CapsMatch(info, fs...);
  585. }
  586. template<typename... F> static bool CapsMatch(const VideoDevice &dev, F... fs)
  587. {
  588. // no early exit, trigger all side effects.
  589. bool match = false;
  590. for (const VideoInfo &info : dev.caps)
  591. if (CapsMatch(info, fs...))
  592. match = true;
  593. return match;
  594. }
  595. static inline bool MatcherMatchVideoFormat(VideoFormat format, bool &did_match,
  596. const VideoInfo &info)
  597. {
  598. bool match = FormatMatches(format, info.format);
  599. did_match = did_match || match;
  600. return match;
  601. }
  602. static inline bool MatcherClosestFrameRateSelector(long long interval,
  603. long long &best_match,
  604. const VideoInfo &info)
  605. {
  606. long long current = FrameRateInterval(info, interval);
  607. if (llabs(interval - best_match) > llabs(interval - current))
  608. best_match = current;
  609. return true;
  610. }
  611. #if 0
  612. auto ResolutionMatcher = [](int cx, int cy)
  613. {
  614. return [cx, cy](const VideoInfo &info)
  615. {
  616. return ResolutionAvailable(info, cx, cy);
  617. };
  618. };
  619. auto FrameRateMatcher = [](long long interval)
  620. {
  621. return [interval](const VideoInfo &info)
  622. {
  623. return FrameRateAvailable(info, interval);
  624. };
  625. };
  626. auto VideoFormatMatcher = [](VideoFormat format, bool &did_match)
  627. {
  628. return [format, &did_match](const VideoInfo &info)
  629. {
  630. return MatcherMatchVideoFormat(format, did_match, info);
  631. };
  632. };
  633. auto ClosestFrameRateSelector = [](long long interval, long long &best_match)
  634. {
  635. return [interval, &best_match](const VideoInfo &info) mutable -> bool
  636. {
  637. MatcherClosestFrameRateSelector(interval, best_match, info);
  638. };
  639. }
  640. #else
  641. #define ResolutionMatcher(cx, cy) \
  642. [cx, cy](const VideoInfo &info) -> bool { \
  643. return ResolutionAvailable(info, cx, cy); \
  644. }
  645. #define FrameRateMatcher(interval) \
  646. [interval](const VideoInfo &info) -> bool { \
  647. return FrameRateAvailable(info, interval); \
  648. }
  649. #define VideoFormatMatcher(format, did_match) \
  650. [format, &did_match](const VideoInfo &info) mutable -> bool { \
  651. return MatcherMatchVideoFormat(format, did_match, info); \
  652. }
  653. #define ClosestFrameRateSelector(interval, best_match) \
  654. [interval, &best_match](const VideoInfo &info) mutable -> bool { \
  655. return MatcherClosestFrameRateSelector(interval, best_match, \
  656. info); \
  657. }
  658. #endif
  659. static bool ResolutionAvailable(const VideoDevice &dev, int cx, int cy)
  660. {
  661. return CapsMatch(dev, ResolutionMatcher(cx, cy));
  662. }
  663. static bool DetermineResolution(int &cx, int &cy, obs_data_t *settings,
  664. VideoDevice dev)
  665. {
  666. const char *res = obs_data_get_autoselect_string(settings, RESOLUTION);
  667. if (obs_data_has_autoselect_value(settings, RESOLUTION) &&
  668. ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  669. return true;
  670. res = obs_data_get_string(settings, RESOLUTION);
  671. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  672. return true;
  673. res = obs_data_get_string(settings, LAST_RESOLUTION);
  674. if (ConvertRes(cx, cy, res) && ResolutionAvailable(dev, cx, cy))
  675. return true;
  676. return false;
  677. }
  678. static long long GetOBSFPS();
  679. static inline bool IsDelayedDevice(const VideoConfig &config)
  680. {
  681. return config.format >= VideoFormat::MJPEG ||
  682. wstrstri(config.name.c_str(), L"elgato") != NULL ||
  683. wstrstri(config.name.c_str(), L"stream engine") != NULL;
  684. }
  685. static inline bool IsDecoupled(const VideoConfig &config)
  686. {
  687. return wstrstri(config.name.c_str(), L"GV-USB2") != NULL;
  688. }
  689. inline void DShowInput::SetupBuffering(obs_data_t *settings)
  690. {
  691. BufferingType bufType;
  692. bool useBuffering;
  693. bufType = (BufferingType)obs_data_get_int(settings, BUFFERING_VAL);
  694. if (bufType == BufferingType::Auto)
  695. useBuffering = IsDelayedDevice(videoConfig);
  696. else
  697. useBuffering = bufType == BufferingType::On;
  698. obs_source_set_async_unbuffered(source, !useBuffering);
  699. obs_source_set_async_decoupled(source, IsDecoupled(videoConfig));
  700. }
  701. static DStr GetVideoFormatName(VideoFormat format);
  702. bool DShowInput::UpdateVideoConfig(obs_data_t *settings)
  703. {
  704. string video_device_id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  705. deactivateWhenNotShowing = obs_data_get_bool(settings, DEACTIVATE_WNS);
  706. flip = obs_data_get_bool(settings, FLIP_IMAGE);
  707. DeviceId id;
  708. if (!DecodeDeviceId(id, video_device_id.c_str())) {
  709. blog(LOG_WARNING, "%s: DecodeDeviceId failed",
  710. obs_source_get_name(source));
  711. return false;
  712. }
  713. PropertiesData data;
  714. Device::EnumVideoDevices(data.devices);
  715. VideoDevice dev;
  716. if (!data.GetDevice(dev, video_device_id.c_str())) {
  717. blog(LOG_WARNING, "%s: data.GetDevice failed",
  718. obs_source_get_name(source));
  719. return false;
  720. }
  721. int resType = (int)obs_data_get_int(settings, RES_TYPE);
  722. int cx = 0, cy = 0;
  723. long long interval = 0;
  724. VideoFormat format = VideoFormat::Any;
  725. if (resType == ResType_Custom) {
  726. bool has_autosel_val;
  727. string resolution = obs_data_get_string(settings, RESOLUTION);
  728. if (!ResolutionValid(resolution, cx, cy)) {
  729. blog(LOG_WARNING, "%s: ResolutionValid failed",
  730. obs_source_get_name(source));
  731. return false;
  732. }
  733. has_autosel_val =
  734. obs_data_has_autoselect_value(settings, FRAME_INTERVAL);
  735. interval = has_autosel_val
  736. ? obs_data_get_autoselect_int(settings,
  737. FRAME_INTERVAL)
  738. : obs_data_get_int(settings, FRAME_INTERVAL);
  739. if (interval == FPS_MATCHING)
  740. interval = GetOBSFPS();
  741. format = (VideoFormat)obs_data_get_int(settings, VIDEO_FORMAT);
  742. long long best_interval = numeric_limits<long long>::max();
  743. bool video_format_match = false;
  744. bool caps_match = CapsMatch(
  745. dev, ResolutionMatcher(cx, cy),
  746. VideoFormatMatcher(format, video_format_match),
  747. ClosestFrameRateSelector(interval, best_interval),
  748. FrameRateMatcher(interval));
  749. if (!caps_match && !video_format_match) {
  750. blog(LOG_WARNING, "%s: Video format match failed",
  751. obs_source_get_name(source));
  752. return false;
  753. }
  754. interval = best_interval;
  755. }
  756. videoConfig.name = id.name.c_str();
  757. videoConfig.path = id.path.c_str();
  758. videoConfig.useDefaultConfig = resType == ResType_Preferred;
  759. videoConfig.cx = cx;
  760. videoConfig.cy = cy;
  761. videoConfig.frameInterval = interval;
  762. videoConfig.internalFormat = format;
  763. deviceHasAudio = dev.audioAttached;
  764. deviceHasSeparateAudioFilter = dev.separateAudioFilter;
  765. videoConfig.callback = std::bind(&DShowInput::OnVideoData, this,
  766. placeholders::_1, placeholders::_2,
  767. placeholders::_3, placeholders::_4,
  768. placeholders::_5);
  769. videoConfig.format = videoConfig.internalFormat;
  770. if (!device.SetVideoConfig(&videoConfig)) {
  771. blog(LOG_WARNING, "%s: device.SetVideoConfig failed",
  772. obs_source_get_name(source));
  773. return false;
  774. }
  775. DStr formatName = GetVideoFormatName(videoConfig.internalFormat);
  776. double fps = 0.0;
  777. if (videoConfig.frameInterval)
  778. fps = 10000000.0 / double(videoConfig.frameInterval);
  779. BPtr<char> name_utf8;
  780. BPtr<char> path_utf8;
  781. os_wcs_to_utf8_ptr(videoConfig.name.c_str(), videoConfig.name.size(),
  782. &name_utf8);
  783. os_wcs_to_utf8_ptr(videoConfig.path.c_str(), videoConfig.path.size(),
  784. &path_utf8);
  785. blog(LOG_INFO, "---------------------------------");
  786. blog(LOG_INFO,
  787. "[DShow Device: '%s'] settings updated: \n"
  788. "\tvideo device: %s\n"
  789. "\tvideo path: %s\n"
  790. "\tresolution: %dx%d\n"
  791. "\tfps: %0.2f (interval: %lld)\n"
  792. "\tformat: %s",
  793. obs_source_get_name(source), (const char *)name_utf8,
  794. (const char *)path_utf8, videoConfig.cx, videoConfig.cy, fps,
  795. videoConfig.frameInterval, formatName->array);
  796. SetupBuffering(settings);
  797. return true;
  798. }
  799. bool DShowInput::UpdateAudioConfig(obs_data_t *settings)
  800. {
  801. string audio_device_id = obs_data_get_string(settings, AUDIO_DEVICE_ID);
  802. bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO);
  803. if (useCustomAudio) {
  804. DeviceId id;
  805. if (!DecodeDeviceId(id, audio_device_id.c_str()))
  806. return false;
  807. audioConfig.name = id.name.c_str();
  808. audioConfig.path = id.path.c_str();
  809. } else if (!deviceHasAudio) {
  810. return true;
  811. }
  812. audioConfig.useVideoDevice = !useCustomAudio &&
  813. !deviceHasSeparateAudioFilter;
  814. audioConfig.useSeparateAudioFilter = deviceHasSeparateAudioFilter;
  815. audioConfig.callback = std::bind(&DShowInput::OnAudioData, this,
  816. placeholders::_1, placeholders::_2,
  817. placeholders::_3, placeholders::_4,
  818. placeholders::_5);
  819. audioConfig.mode =
  820. (AudioMode)obs_data_get_int(settings, AUDIO_OUTPUT_MODE);
  821. bool success = device.SetAudioConfig(&audioConfig);
  822. if (!success)
  823. return false;
  824. BPtr<char> name_utf8;
  825. os_wcs_to_utf8_ptr(audioConfig.name.c_str(), audioConfig.name.size(),
  826. &name_utf8);
  827. blog(LOG_INFO, "\tusing video device audio: %s",
  828. audioConfig.useVideoDevice ? "yes" : "no");
  829. if (!audioConfig.useVideoDevice) {
  830. if (audioConfig.useSeparateAudioFilter)
  831. blog(LOG_INFO, "\tseparate audio filter");
  832. else
  833. blog(LOG_INFO, "\taudio device: %s",
  834. (const char *)name_utf8);
  835. }
  836. const char *mode = "";
  837. switch (audioConfig.mode) {
  838. case AudioMode::Capture:
  839. mode = "Capture";
  840. break;
  841. case AudioMode::DirectSound:
  842. mode = "DirectSound";
  843. break;
  844. case AudioMode::WaveOut:
  845. mode = "WaveOut";
  846. break;
  847. }
  848. blog(LOG_INFO,
  849. "\tsample rate: %d\n"
  850. "\tchannels: %d\n"
  851. "\taudio type: %s",
  852. audioConfig.sampleRate, audioConfig.channels, mode);
  853. return true;
  854. }
  855. void DShowInput::SetActive(bool active_)
  856. {
  857. obs_data_t *settings = obs_source_get_settings(source);
  858. QueueAction(active_ ? Action::Activate : Action::Deactivate);
  859. obs_data_set_bool(settings, "active", active_);
  860. active = active_;
  861. obs_data_release(settings);
  862. }
  863. inline enum video_colorspace
  864. DShowInput::GetColorSpace(obs_data_t *settings) const
  865. {
  866. const char *space = obs_data_get_string(settings, COLOR_SPACE);
  867. if (astrcmpi(space, "709") == 0)
  868. return VIDEO_CS_709;
  869. else if (astrcmpi(space, "601") == 0)
  870. return VIDEO_CS_601;
  871. else
  872. return (videoConfig.format == VideoFormat::HDYC) ? VIDEO_CS_709
  873. : VIDEO_CS_601;
  874. }
  875. inline enum video_range_type
  876. DShowInput::GetColorRange(obs_data_t *settings) const
  877. {
  878. const char *range = obs_data_get_string(settings, COLOR_RANGE);
  879. if (astrcmpi(range, "full") == 0)
  880. return VIDEO_RANGE_FULL;
  881. if (astrcmpi(range, "partial") == 0)
  882. return VIDEO_RANGE_PARTIAL;
  883. return VIDEO_RANGE_DEFAULT;
  884. }
  885. inline bool DShowInput::Activate(obs_data_t *settings)
  886. {
  887. if (!device.ResetGraph())
  888. return false;
  889. if (!UpdateVideoConfig(settings)) {
  890. blog(LOG_WARNING, "%s: Video configuration failed",
  891. obs_source_get_name(source));
  892. return false;
  893. }
  894. if (!UpdateAudioConfig(settings))
  895. blog(LOG_WARNING,
  896. "%s: Audio configuration failed, ignoring "
  897. "audio",
  898. obs_source_get_name(source));
  899. if (!device.ConnectFilters())
  900. return false;
  901. enum video_colorspace cs = GetColorSpace(settings);
  902. frame.range = GetColorRange(settings);
  903. if (device.Start() != Result::Success)
  904. return false;
  905. bool success = video_format_get_parameters(cs, frame.range,
  906. frame.color_matrix,
  907. frame.color_range_min,
  908. frame.color_range_max);
  909. if (!success) {
  910. blog(LOG_ERROR,
  911. "Failed to get video format parameters for "
  912. "video format %u",
  913. cs);
  914. }
  915. return true;
  916. }
  917. inline void DShowInput::Deactivate()
  918. {
  919. device.ResetGraph();
  920. obs_source_output_video2(source, nullptr);
  921. }
  922. /* ------------------------------------------------------------------------- */
  923. static const char *GetDShowInputName(void *)
  924. {
  925. return TEXT_INPUT_NAME;
  926. }
  927. static void *CreateDShowInput(obs_data_t *settings, obs_source_t *source)
  928. {
  929. DShowInput *dshow = nullptr;
  930. try {
  931. dshow = new DShowInput(source, settings);
  932. } catch (const char *error) {
  933. blog(LOG_ERROR, "Could not create device '%s': %s",
  934. obs_source_get_name(source), error);
  935. }
  936. return dshow;
  937. }
  938. static void DestroyDShowInput(void *data)
  939. {
  940. delete reinterpret_cast<DShowInput *>(data);
  941. }
  942. static void UpdateDShowInput(void *data, obs_data_t *settings)
  943. {
  944. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  945. if (input->active)
  946. input->QueueActivate(settings);
  947. }
  948. static void GetDShowDefaults(obs_data_t *settings)
  949. {
  950. obs_data_set_default_int(settings, FRAME_INTERVAL, FPS_MATCHING);
  951. obs_data_set_default_int(settings, RES_TYPE, ResType_Preferred);
  952. obs_data_set_default_int(settings, VIDEO_FORMAT, (int)VideoFormat::Any);
  953. obs_data_set_default_bool(settings, "active", true);
  954. obs_data_set_default_string(settings, COLOR_SPACE, "default");
  955. obs_data_set_default_string(settings, COLOR_RANGE, "default");
  956. obs_data_set_default_int(settings, AUDIO_OUTPUT_MODE,
  957. (int)AudioMode::Capture);
  958. }
  959. struct Resolution {
  960. int cx, cy;
  961. inline Resolution(int cx, int cy) : cx(cx), cy(cy) {}
  962. };
  963. static void InsertResolution(vector<Resolution> &resolutions, int cx, int cy)
  964. {
  965. int bestCY = 0;
  966. size_t idx = 0;
  967. for (; idx < resolutions.size(); idx++) {
  968. const Resolution &res = resolutions[idx];
  969. if (res.cx > cx)
  970. break;
  971. if (res.cx == cx) {
  972. if (res.cy == cy)
  973. return;
  974. if (!bestCY)
  975. bestCY = res.cy;
  976. else if (res.cy > bestCY)
  977. break;
  978. }
  979. }
  980. resolutions.insert(resolutions.begin() + idx, Resolution(cx, cy));
  981. }
  982. static inline void AddCap(vector<Resolution> &resolutions, const VideoInfo &cap)
  983. {
  984. InsertResolution(resolutions, cap.minCX, cap.minCY);
  985. InsertResolution(resolutions, cap.maxCX, cap.maxCY);
  986. }
  987. #define MAKE_DSHOW_FPS(fps) (10000000LL / (fps))
  988. #define MAKE_DSHOW_FRACTIONAL_FPS(den, num) ((num)*10000000LL / (den))
  989. static long long GetOBSFPS()
  990. {
  991. obs_video_info ovi;
  992. if (!obs_get_video_info(&ovi))
  993. return 0;
  994. return MAKE_DSHOW_FRACTIONAL_FPS(ovi.fps_num, ovi.fps_den);
  995. }
  996. struct FPSFormat {
  997. const char *text;
  998. long long interval;
  999. };
  1000. static const FPSFormat validFPSFormats[] = {
  1001. {"60", MAKE_DSHOW_FPS(60)},
  1002. {"59.94 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(60000, 1001)},
  1003. {"50", MAKE_DSHOW_FPS(50)},
  1004. {"48 film", MAKE_DSHOW_FRACTIONAL_FPS(48000, 1001)},
  1005. {"40", MAKE_DSHOW_FPS(40)},
  1006. {"30", MAKE_DSHOW_FPS(30)},
  1007. {"29.97 NTSC", MAKE_DSHOW_FRACTIONAL_FPS(30000, 1001)},
  1008. {"25", MAKE_DSHOW_FPS(25)},
  1009. {"24 film", MAKE_DSHOW_FRACTIONAL_FPS(24000, 1001)},
  1010. {"20", MAKE_DSHOW_FPS(20)},
  1011. {"15", MAKE_DSHOW_FPS(15)},
  1012. {"10", MAKE_DSHOW_FPS(10)},
  1013. {"5", MAKE_DSHOW_FPS(5)},
  1014. {"4", MAKE_DSHOW_FPS(4)},
  1015. {"3", MAKE_DSHOW_FPS(3)},
  1016. {"2", MAKE_DSHOW_FPS(2)},
  1017. {"1", MAKE_DSHOW_FPS(1)},
  1018. };
  1019. static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p,
  1020. obs_data_t *settings);
  1021. static bool TryResolution(VideoDevice &dev, string res)
  1022. {
  1023. int cx, cy;
  1024. if (!ConvertRes(cx, cy, res.c_str()))
  1025. return false;
  1026. return ResolutionAvailable(dev, cx, cy);
  1027. }
  1028. static bool SetResolution(obs_properties_t *props, obs_data_t *settings,
  1029. string res, bool autoselect = false)
  1030. {
  1031. if (autoselect)
  1032. obs_data_set_autoselect_string(settings, RESOLUTION,
  1033. res.c_str());
  1034. else
  1035. obs_data_unset_autoselect_value(settings, RESOLUTION);
  1036. DeviceIntervalChanged(props, obs_properties_get(props, FRAME_INTERVAL),
  1037. settings);
  1038. if (!autoselect)
  1039. obs_data_set_string(settings, LAST_RESOLUTION, res.c_str());
  1040. return true;
  1041. }
  1042. static bool DeviceResolutionChanged(obs_properties_t *props, obs_property_t *p,
  1043. obs_data_t *settings)
  1044. {
  1045. UNUSED_PARAMETER(p);
  1046. PropertiesData *data =
  1047. (PropertiesData *)obs_properties_get_param(props);
  1048. const char *id;
  1049. VideoDevice device;
  1050. id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1051. string res = obs_data_get_string(settings, RESOLUTION);
  1052. string last_res = obs_data_get_string(settings, LAST_RESOLUTION);
  1053. if (!data->GetDevice(device, id))
  1054. return false;
  1055. if (TryResolution(device, res))
  1056. return SetResolution(props, settings, res);
  1057. if (TryResolution(device, last_res))
  1058. return SetResolution(props, settings, last_res, true);
  1059. return false;
  1060. }
  1061. struct VideoFormatName {
  1062. VideoFormat format;
  1063. const char *name;
  1064. };
  1065. static const VideoFormatName videoFormatNames[] = {
  1066. /* autoselect format*/
  1067. {VideoFormat::Any, "VideoFormat.Any"},
  1068. /* raw formats */
  1069. {VideoFormat::ARGB, "ARGB"},
  1070. {VideoFormat::XRGB, "XRGB"},
  1071. /* planar YUV formats */
  1072. {VideoFormat::I420, "I420"},
  1073. {VideoFormat::NV12, "NV12"},
  1074. {VideoFormat::YV12, "YV12"},
  1075. {VideoFormat::Y800, "Y800"},
  1076. /* packed YUV formats */
  1077. {VideoFormat::YVYU, "YVYU"},
  1078. {VideoFormat::YUY2, "YUY2"},
  1079. {VideoFormat::UYVY, "UYVY"},
  1080. {VideoFormat::HDYC, "HDYC"},
  1081. /* encoded formats */
  1082. {VideoFormat::MJPEG, "MJPEG"},
  1083. {VideoFormat::H264, "H264"}};
  1084. static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p,
  1085. obs_data_t *settings);
  1086. static size_t AddDevice(obs_property_t *device_list, const string &id)
  1087. {
  1088. DStr name, path;
  1089. if (!DecodeDeviceDStr(name, path, id.c_str()))
  1090. return numeric_limits<size_t>::max();
  1091. return obs_property_list_add_string(device_list, name, id.c_str());
  1092. }
  1093. static bool UpdateDeviceList(obs_property_t *list, const string &id)
  1094. {
  1095. size_t size = obs_property_list_item_count(list);
  1096. bool found = false;
  1097. bool disabled_unknown_found = false;
  1098. for (size_t i = 0; i < size; i++) {
  1099. if (obs_property_list_item_string(list, i) == id) {
  1100. found = true;
  1101. continue;
  1102. }
  1103. if (obs_property_list_item_disabled(list, i))
  1104. disabled_unknown_found = true;
  1105. }
  1106. if (!found && !disabled_unknown_found) {
  1107. size_t idx = AddDevice(list, id);
  1108. obs_property_list_item_disable(list, idx, true);
  1109. return true;
  1110. }
  1111. if (found && !disabled_unknown_found)
  1112. return false;
  1113. for (size_t i = 0; i < size;) {
  1114. if (obs_property_list_item_disabled(list, i)) {
  1115. obs_property_list_item_remove(list, i);
  1116. continue;
  1117. }
  1118. i += 1;
  1119. }
  1120. return true;
  1121. }
  1122. static bool DeviceSelectionChanged(obs_properties_t *props, obs_property_t *p,
  1123. obs_data_t *settings)
  1124. {
  1125. PropertiesData *data =
  1126. (PropertiesData *)obs_properties_get_param(props);
  1127. VideoDevice device;
  1128. string id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1129. string old_id = obs_data_get_string(settings, LAST_VIDEO_DEV_ID);
  1130. bool device_list_updated = UpdateDeviceList(p, id);
  1131. if (!data->GetDevice(device, id.c_str()))
  1132. return !device_list_updated;
  1133. vector<Resolution> resolutions;
  1134. for (const VideoInfo &cap : device.caps)
  1135. AddCap(resolutions, cap);
  1136. p = obs_properties_get(props, RESOLUTION);
  1137. obs_property_list_clear(p);
  1138. for (size_t idx = resolutions.size(); idx > 0; idx--) {
  1139. const Resolution &res = resolutions[idx - 1];
  1140. string strRes;
  1141. strRes += to_string(res.cx);
  1142. strRes += "x";
  1143. strRes += to_string(res.cy);
  1144. obs_property_list_add_string(p, strRes.c_str(), strRes.c_str());
  1145. }
  1146. /* only refresh properties if device legitimately changed */
  1147. if (!id.size() || !old_id.size() || id != old_id) {
  1148. p = obs_properties_get(props, RES_TYPE);
  1149. ResTypeChanged(props, p, settings);
  1150. obs_data_set_string(settings, LAST_VIDEO_DEV_ID, id.c_str());
  1151. }
  1152. return true;
  1153. }
  1154. static bool VideoConfigClicked(obs_properties_t *props, obs_property_t *p,
  1155. void *data)
  1156. {
  1157. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  1158. input->QueueAction(Action::ConfigVideo);
  1159. UNUSED_PARAMETER(props);
  1160. UNUSED_PARAMETER(p);
  1161. return false;
  1162. }
  1163. /*static bool AudioConfigClicked(obs_properties_t *props, obs_property_t *p,
  1164. void *data)
  1165. {
  1166. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  1167. input->QueueAction(Action::ConfigAudio);
  1168. UNUSED_PARAMETER(props);
  1169. UNUSED_PARAMETER(p);
  1170. return false;
  1171. }*/
  1172. static bool CrossbarConfigClicked(obs_properties_t *props, obs_property_t *p,
  1173. void *data)
  1174. {
  1175. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  1176. input->QueueAction(Action::ConfigCrossbar1);
  1177. UNUSED_PARAMETER(props);
  1178. UNUSED_PARAMETER(p);
  1179. return false;
  1180. }
  1181. /*static bool Crossbar2ConfigClicked(obs_properties_t *props, obs_property_t *p,
  1182. void *data)
  1183. {
  1184. DShowInput *input = reinterpret_cast<DShowInput*>(data);
  1185. input->QueueAction(Action::ConfigCrossbar2);
  1186. UNUSED_PARAMETER(props);
  1187. UNUSED_PARAMETER(p);
  1188. return false;
  1189. }*/
  1190. static bool AddDevice(obs_property_t *device_list, const VideoDevice &device)
  1191. {
  1192. DStr name, path, device_id;
  1193. dstr_from_wcs(name, device.name.c_str());
  1194. dstr_from_wcs(path, device.path.c_str());
  1195. encode_dstr(path);
  1196. dstr_copy_dstr(device_id, name);
  1197. encode_dstr(device_id);
  1198. dstr_cat(device_id, ":");
  1199. dstr_cat_dstr(device_id, path);
  1200. obs_property_list_add_string(device_list, name, device_id);
  1201. return true;
  1202. }
  1203. static bool AddAudioDevice(obs_property_t *device_list,
  1204. const AudioDevice &device)
  1205. {
  1206. DStr name, path, device_id;
  1207. dstr_from_wcs(name, device.name.c_str());
  1208. dstr_from_wcs(path, device.path.c_str());
  1209. encode_dstr(path);
  1210. dstr_copy_dstr(device_id, name);
  1211. encode_dstr(device_id);
  1212. dstr_cat(device_id, ":");
  1213. dstr_cat_dstr(device_id, path);
  1214. obs_property_list_add_string(device_list, name, device_id);
  1215. return true;
  1216. }
  1217. static void PropertiesDataDestroy(void *data)
  1218. {
  1219. delete reinterpret_cast<PropertiesData *>(data);
  1220. }
  1221. static bool ResTypeChanged(obs_properties_t *props, obs_property_t *p,
  1222. obs_data_t *settings)
  1223. {
  1224. int val = (int)obs_data_get_int(settings, RES_TYPE);
  1225. bool enabled = (val != ResType_Preferred);
  1226. p = obs_properties_get(props, RESOLUTION);
  1227. obs_property_set_enabled(p, enabled);
  1228. p = obs_properties_get(props, FRAME_INTERVAL);
  1229. obs_property_set_enabled(p, enabled);
  1230. p = obs_properties_get(props, VIDEO_FORMAT);
  1231. obs_property_set_enabled(p, enabled);
  1232. if (val == ResType_Custom) {
  1233. p = obs_properties_get(props, RESOLUTION);
  1234. DeviceResolutionChanged(props, p, settings);
  1235. } else {
  1236. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  1237. }
  1238. return true;
  1239. }
  1240. static DStr GetFPSName(long long interval)
  1241. {
  1242. DStr name;
  1243. if (interval == FPS_MATCHING) {
  1244. dstr_cat(name, TEXT_FPS_MATCHING);
  1245. return name;
  1246. }
  1247. if (interval == FPS_HIGHEST) {
  1248. dstr_cat(name, TEXT_FPS_HIGHEST);
  1249. return name;
  1250. }
  1251. for (const FPSFormat &format : validFPSFormats) {
  1252. if (format.interval != interval)
  1253. continue;
  1254. dstr_cat(name, format.text);
  1255. return name;
  1256. }
  1257. dstr_cat(name, to_string(10000000. / interval).c_str());
  1258. return name;
  1259. }
  1260. static void UpdateFPS(VideoDevice &device, VideoFormat format,
  1261. long long interval, int cx, int cy,
  1262. obs_properties_t *props)
  1263. {
  1264. obs_property_t *list = obs_properties_get(props, FRAME_INTERVAL);
  1265. obs_property_list_clear(list);
  1266. obs_property_list_add_int(list, TEXT_FPS_MATCHING, FPS_MATCHING);
  1267. obs_property_list_add_int(list, TEXT_FPS_HIGHEST, FPS_HIGHEST);
  1268. bool interval_added = interval == FPS_HIGHEST ||
  1269. interval == FPS_MATCHING;
  1270. for (const FPSFormat &fps_format : validFPSFormats) {
  1271. bool video_format_match = false;
  1272. long long format_interval = fps_format.interval;
  1273. bool available = CapsMatch(
  1274. device, ResolutionMatcher(cx, cy),
  1275. VideoFormatMatcher(format, video_format_match),
  1276. FrameRateMatcher(format_interval));
  1277. if (!available && interval != fps_format.interval)
  1278. continue;
  1279. if (interval == fps_format.interval)
  1280. interval_added = true;
  1281. size_t idx = obs_property_list_add_int(list, fps_format.text,
  1282. fps_format.interval);
  1283. obs_property_list_item_disable(list, idx, !available);
  1284. }
  1285. if (interval_added)
  1286. return;
  1287. size_t idx =
  1288. obs_property_list_add_int(list, GetFPSName(interval), interval);
  1289. obs_property_list_item_disable(list, idx, true);
  1290. }
  1291. static DStr GetVideoFormatName(VideoFormat format)
  1292. {
  1293. DStr name;
  1294. for (const VideoFormatName &format_ : videoFormatNames) {
  1295. if (format_.format == format) {
  1296. dstr_cat(name, obs_module_text(format_.name));
  1297. return name;
  1298. }
  1299. }
  1300. dstr_cat(name, TEXT_FORMAT_UNKNOWN);
  1301. dstr_replace(name, "%1", std::to_string((long long)format).c_str());
  1302. return name;
  1303. }
  1304. static void UpdateVideoFormats(VideoDevice &device, VideoFormat format_, int cx,
  1305. int cy, long long interval,
  1306. obs_properties_t *props)
  1307. {
  1308. set<VideoFormat> formats = {VideoFormat::Any};
  1309. auto format_gatherer =
  1310. [&formats](const VideoInfo &info) mutable -> bool {
  1311. formats.insert(info.format);
  1312. return false;
  1313. };
  1314. CapsMatch(device, ResolutionMatcher(cx, cy), FrameRateMatcher(interval),
  1315. format_gatherer);
  1316. obs_property_t *list = obs_properties_get(props, VIDEO_FORMAT);
  1317. obs_property_list_clear(list);
  1318. bool format_added = false;
  1319. for (const VideoFormatName &format : videoFormatNames) {
  1320. bool available = formats.find(format.format) != end(formats);
  1321. if (!available && format.format != format_)
  1322. continue;
  1323. if (format.format == format_)
  1324. format_added = true;
  1325. size_t idx = obs_property_list_add_int(
  1326. list, obs_module_text(format.name),
  1327. (long long)format.format);
  1328. obs_property_list_item_disable(list, idx, !available);
  1329. }
  1330. if (format_added)
  1331. return;
  1332. size_t idx = obs_property_list_add_int(
  1333. list, GetVideoFormatName(format_), (long long)format_);
  1334. obs_property_list_item_disable(list, idx, true);
  1335. }
  1336. static bool UpdateFPS(long long interval, obs_property_t *list)
  1337. {
  1338. size_t size = obs_property_list_item_count(list);
  1339. DStr name;
  1340. for (size_t i = 0; i < size; i++) {
  1341. if (obs_property_list_item_int(list, i) != interval)
  1342. continue;
  1343. obs_property_list_item_disable(list, i, true);
  1344. if (size == 1)
  1345. return false;
  1346. dstr_cat(name, obs_property_list_item_name(list, i));
  1347. break;
  1348. }
  1349. obs_property_list_clear(list);
  1350. if (!name->len)
  1351. name = GetFPSName(interval);
  1352. obs_property_list_add_int(list, name, interval);
  1353. obs_property_list_item_disable(list, 0, true);
  1354. return true;
  1355. }
  1356. static bool DeviceIntervalChanged(obs_properties_t *props, obs_property_t *p,
  1357. obs_data_t *settings)
  1358. {
  1359. long long val = obs_data_get_int(settings, FRAME_INTERVAL);
  1360. PropertiesData *data =
  1361. (PropertiesData *)obs_properties_get_param(props);
  1362. const char *id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1363. VideoDevice device;
  1364. if (!data->GetDevice(device, id))
  1365. return UpdateFPS(val, p);
  1366. int cx = 0, cy = 0;
  1367. if (!DetermineResolution(cx, cy, settings, device)) {
  1368. UpdateVideoFormats(device, VideoFormat::Any, 0, 0, 0, props);
  1369. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  1370. return true;
  1371. }
  1372. int resType = (int)obs_data_get_int(settings, RES_TYPE);
  1373. if (resType != ResType_Custom)
  1374. return true;
  1375. if (val == FPS_MATCHING)
  1376. val = GetOBSFPS();
  1377. VideoFormat format =
  1378. (VideoFormat)obs_data_get_int(settings, VIDEO_FORMAT);
  1379. bool video_format_matches = false;
  1380. long long best_interval = numeric_limits<long long>::max();
  1381. bool frameRateSupported =
  1382. CapsMatch(device, ResolutionMatcher(cx, cy),
  1383. VideoFormatMatcher(format, video_format_matches),
  1384. ClosestFrameRateSelector(val, best_interval),
  1385. FrameRateMatcher(val));
  1386. if (video_format_matches && !frameRateSupported &&
  1387. best_interval != val) {
  1388. long long listed_val = 0;
  1389. for (const FPSFormat &format : validFPSFormats) {
  1390. long long diff = llabs(format.interval - best_interval);
  1391. if (diff < DEVICE_INTERVAL_DIFF_LIMIT) {
  1392. listed_val = format.interval;
  1393. break;
  1394. }
  1395. }
  1396. if (listed_val != val) {
  1397. obs_data_set_autoselect_int(settings, FRAME_INTERVAL,
  1398. listed_val);
  1399. val = listed_val;
  1400. }
  1401. } else {
  1402. obs_data_unset_autoselect_value(settings, FRAME_INTERVAL);
  1403. }
  1404. UpdateVideoFormats(device, format, cx, cy, val, props);
  1405. UpdateFPS(device, format, val, cx, cy, props);
  1406. UNUSED_PARAMETER(p);
  1407. return true;
  1408. }
  1409. static bool UpdateVideoFormats(VideoFormat format, obs_property_t *list)
  1410. {
  1411. size_t size = obs_property_list_item_count(list);
  1412. DStr name;
  1413. for (size_t i = 0; i < size; i++) {
  1414. if ((VideoFormat)obs_property_list_item_int(list, i) != format)
  1415. continue;
  1416. if (size == 1)
  1417. return false;
  1418. dstr_cat(name, obs_property_list_item_name(list, i));
  1419. break;
  1420. }
  1421. obs_property_list_clear(list);
  1422. if (!name->len)
  1423. name = GetVideoFormatName(format);
  1424. obs_property_list_add_int(list, name, (long long)format);
  1425. obs_property_list_item_disable(list, 0, true);
  1426. return true;
  1427. }
  1428. static bool VideoFormatChanged(obs_properties_t *props, obs_property_t *p,
  1429. obs_data_t *settings)
  1430. {
  1431. PropertiesData *data =
  1432. (PropertiesData *)obs_properties_get_param(props);
  1433. const char *id = obs_data_get_string(settings, VIDEO_DEVICE_ID);
  1434. VideoDevice device;
  1435. VideoFormat curFormat =
  1436. (VideoFormat)obs_data_get_int(settings, VIDEO_FORMAT);
  1437. if (!data->GetDevice(device, id))
  1438. return UpdateVideoFormats(curFormat, p);
  1439. int cx, cy;
  1440. if (!DetermineResolution(cx, cy, settings, device)) {
  1441. UpdateVideoFormats(device, VideoFormat::Any, cx, cy, 0, props);
  1442. UpdateFPS(device, VideoFormat::Any, 0, 0, 0, props);
  1443. return true;
  1444. }
  1445. long long interval = obs_data_get_int(settings, FRAME_INTERVAL);
  1446. UpdateVideoFormats(device, curFormat, cx, cy, interval, props);
  1447. UpdateFPS(device, curFormat, interval, cx, cy, props);
  1448. return true;
  1449. }
  1450. static bool CustomAudioClicked(obs_properties_t *props, obs_property_t *p,
  1451. obs_data_t *settings)
  1452. {
  1453. bool useCustomAudio = obs_data_get_bool(settings, USE_CUSTOM_AUDIO);
  1454. p = obs_properties_get(props, AUDIO_DEVICE_ID);
  1455. obs_property_set_visible(p, useCustomAudio);
  1456. return true;
  1457. }
  1458. static bool ActivateClicked(obs_properties_t *, obs_property_t *p, void *data)
  1459. {
  1460. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  1461. if (input->active) {
  1462. input->SetActive(false);
  1463. obs_property_set_description(p, TEXT_ACTIVATE);
  1464. } else {
  1465. input->SetActive(true);
  1466. obs_property_set_description(p, TEXT_DEACTIVATE);
  1467. }
  1468. return true;
  1469. }
  1470. static obs_properties_t *GetDShowProperties(void *obj)
  1471. {
  1472. DShowInput *input = reinterpret_cast<DShowInput *>(obj);
  1473. obs_properties_t *ppts = obs_properties_create();
  1474. PropertiesData *data = new PropertiesData;
  1475. data->input = input;
  1476. obs_properties_set_param(ppts, data, PropertiesDataDestroy);
  1477. obs_property_t *p = obs_properties_add_list(ppts, VIDEO_DEVICE_ID,
  1478. TEXT_DEVICE,
  1479. OBS_COMBO_TYPE_LIST,
  1480. OBS_COMBO_FORMAT_STRING);
  1481. obs_property_set_modified_callback(p, DeviceSelectionChanged);
  1482. Device::EnumVideoDevices(data->devices);
  1483. for (const VideoDevice &device : data->devices)
  1484. AddDevice(p, device);
  1485. const char *activateText = TEXT_ACTIVATE;
  1486. if (input) {
  1487. if (input->active)
  1488. activateText = TEXT_DEACTIVATE;
  1489. }
  1490. obs_properties_add_button(ppts, "activate", activateText,
  1491. ActivateClicked);
  1492. obs_properties_add_button(ppts, "video_config", TEXT_CONFIG_VIDEO,
  1493. VideoConfigClicked);
  1494. obs_properties_add_button(ppts, "xbar_config", TEXT_CONFIG_XBAR,
  1495. CrossbarConfigClicked);
  1496. obs_properties_add_bool(ppts, DEACTIVATE_WNS, TEXT_DWNS);
  1497. /* ------------------------------------- */
  1498. /* video settings */
  1499. p = obs_properties_add_list(ppts, RES_TYPE, TEXT_RES_FPS_TYPE,
  1500. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1501. obs_property_set_modified_callback(p, ResTypeChanged);
  1502. obs_property_list_add_int(p, TEXT_PREFERRED_RES, ResType_Preferred);
  1503. obs_property_list_add_int(p, TEXT_CUSTOM_RES, ResType_Custom);
  1504. p = obs_properties_add_list(ppts, RESOLUTION, TEXT_RESOLUTION,
  1505. OBS_COMBO_TYPE_EDITABLE,
  1506. OBS_COMBO_FORMAT_STRING);
  1507. obs_property_set_modified_callback(p, DeviceResolutionChanged);
  1508. p = obs_properties_add_list(ppts, FRAME_INTERVAL, "FPS",
  1509. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1510. obs_property_set_modified_callback(p, DeviceIntervalChanged);
  1511. p = obs_properties_add_list(ppts, VIDEO_FORMAT, TEXT_VIDEO_FORMAT,
  1512. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1513. obs_property_set_modified_callback(p, VideoFormatChanged);
  1514. p = obs_properties_add_list(ppts, COLOR_SPACE, TEXT_COLOR_SPACE,
  1515. OBS_COMBO_TYPE_LIST,
  1516. OBS_COMBO_FORMAT_STRING);
  1517. obs_property_list_add_string(p, TEXT_COLOR_DEFAULT, "default");
  1518. obs_property_list_add_string(p, "709", "709");
  1519. obs_property_list_add_string(p, "601", "601");
  1520. p = obs_properties_add_list(ppts, COLOR_RANGE, TEXT_COLOR_RANGE,
  1521. OBS_COMBO_TYPE_LIST,
  1522. OBS_COMBO_FORMAT_STRING);
  1523. obs_property_list_add_string(p, TEXT_RANGE_DEFAULT, "default");
  1524. obs_property_list_add_string(p, TEXT_RANGE_PARTIAL, "partial");
  1525. obs_property_list_add_string(p, TEXT_RANGE_FULL, "full");
  1526. p = obs_properties_add_list(ppts, BUFFERING_VAL, TEXT_BUFFERING,
  1527. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1528. obs_property_list_add_int(p, TEXT_BUFFERING_AUTO,
  1529. (int64_t)BufferingType::Auto);
  1530. obs_property_list_add_int(p, TEXT_BUFFERING_ON,
  1531. (int64_t)BufferingType::On);
  1532. obs_property_list_add_int(p, TEXT_BUFFERING_OFF,
  1533. (int64_t)BufferingType::Off);
  1534. obs_property_set_long_description(p,
  1535. obs_module_text("Buffering.ToolTip"));
  1536. obs_properties_add_bool(ppts, FLIP_IMAGE, TEXT_FLIP_IMAGE);
  1537. /* ------------------------------------- */
  1538. /* audio settings */
  1539. Device::EnumAudioDevices(data->audioDevices);
  1540. p = obs_properties_add_list(ppts, AUDIO_OUTPUT_MODE, TEXT_AUDIO_MODE,
  1541. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1542. obs_property_list_add_int(p, TEXT_MODE_CAPTURE,
  1543. (int64_t)AudioMode::Capture);
  1544. obs_property_list_add_int(p, TEXT_MODE_DSOUND,
  1545. (int64_t)AudioMode::DirectSound);
  1546. obs_property_list_add_int(p, TEXT_MODE_WAVEOUT,
  1547. (int64_t)AudioMode::WaveOut);
  1548. if (!data->audioDevices.size())
  1549. return ppts;
  1550. p = obs_properties_add_bool(ppts, USE_CUSTOM_AUDIO, TEXT_CUSTOM_AUDIO);
  1551. obs_property_set_modified_callback(p, CustomAudioClicked);
  1552. p = obs_properties_add_list(ppts, AUDIO_DEVICE_ID, TEXT_AUDIO_DEVICE,
  1553. OBS_COMBO_TYPE_LIST,
  1554. OBS_COMBO_FORMAT_STRING);
  1555. for (const AudioDevice &device : data->audioDevices)
  1556. AddAudioDevice(p, device);
  1557. return ppts;
  1558. }
  1559. void DShowModuleLogCallback(LogType type, const wchar_t *msg, void *param)
  1560. {
  1561. int obs_type = LOG_DEBUG;
  1562. switch (type) {
  1563. case LogType::Error:
  1564. obs_type = LOG_ERROR;
  1565. break;
  1566. case LogType::Warning:
  1567. obs_type = LOG_WARNING;
  1568. break;
  1569. case LogType::Info:
  1570. obs_type = LOG_INFO;
  1571. break;
  1572. case LogType::Debug:
  1573. obs_type = LOG_DEBUG;
  1574. break;
  1575. }
  1576. DStr dmsg;
  1577. dstr_from_wcs(dmsg, msg);
  1578. blog(obs_type, "DShow: %s", dmsg->array);
  1579. UNUSED_PARAMETER(param);
  1580. }
  1581. static void HideDShowInput(void *data)
  1582. {
  1583. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  1584. if (input->deactivateWhenNotShowing && input->active)
  1585. input->QueueAction(Action::Deactivate);
  1586. }
  1587. static void ShowDShowInput(void *data)
  1588. {
  1589. DShowInput *input = reinterpret_cast<DShowInput *>(data);
  1590. if (input->deactivateWhenNotShowing && input->active)
  1591. input->QueueAction(Action::Activate);
  1592. }
  1593. void RegisterDShowSource()
  1594. {
  1595. SetLogCallback(DShowModuleLogCallback, nullptr);
  1596. obs_source_info info = {};
  1597. info.id = "dshow_input";
  1598. info.type = OBS_SOURCE_TYPE_INPUT;
  1599. info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_AUDIO |
  1600. OBS_SOURCE_ASYNC | OBS_SOURCE_DO_NOT_DUPLICATE;
  1601. info.show = ShowDShowInput;
  1602. info.hide = HideDShowInput;
  1603. info.get_name = GetDShowInputName;
  1604. info.create = CreateDShowInput;
  1605. info.destroy = DestroyDShowInput;
  1606. info.update = UpdateDShowInput;
  1607. info.get_defaults = GetDShowDefaults;
  1608. info.get_properties = GetDShowProperties;
  1609. obs_register_source(&info);
  1610. }