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