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