UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
DirectShowGrabber.cpp
Go to the documentation of this file.
1
9#include <cassert>
10#include <cstdio>
11#include <iostream>
12#include <string>
13
14#include <tchar.h>
15#include <dshow.h>
16#include <dvdmedia.h>
17#include <qedit.h>
18#include <oleauto.h>
19//#include <Streams.h>
20#include <windows.h>
21
22#include "debug.h"
23#include "lib_common.h"
24#include "tv.h"
25#include "utils/color_out.h"
26#include "utils/macros.h"
27#include "utils/string.h"
28#include "utils/windows.h"
29#include "video.h"
30#include "video_capture.h"
31
32#define MOD_NAME "[dshow] "
33
36
37typedef void (*convert_t)(int width, int height, const unsigned char *in,
38 unsigned char *out);
39
40using namespace std;
41
42static void DeleteMediaType(AM_MEDIA_TYPE *mediaType);
43static const CHAR * GetSubtypeName(const GUID *pSubtype);
44static codec_t get_ug_codec(const GUID *pSubtype);
45static convert_t get_conversion(const GUID *pSubtype);
46static codec_t get_ug_from_subtype_name(const char *subtype_name);
47static void vidcap_dshow_probe_internal(device_info **available_cards,
48 int *count, void (**deleter)(void *),
49 bool include_unsupported);
50
51static void ErrorDescription(HRESULT hr)
52{
53 if(FACILITY_WINDOWS == HRESULT_FACILITY(hr))
54 hr = HRESULT_CODE(hr);
55
56 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error: %s\n", get_win32_error(hr));
57}
58
59#define DEFAULT_DEVNUM 1
60#define MAX_STRING_LEN 1000
61#define DEFAULT_VIDEO_WIDTH 640
62#define DEFAULT_VIDEO_HEIGHT 480
63#define DEFAULT_FPS 15
64
65#define MAX_YUV_CLAMP 535
66#define MIN_YUV_CLAMP -276
67
68static int *yuv_clamp;
69
71
81
86 //BYTE *buffer;
90
91 unsigned long frames;
93
94 CRITICAL_SECTION returnBufferCS;
95 CONDITION_VARIABLE grabWaitCV;
96
98 ICaptureGraphBuilder2 *graphBuilder;
99 IGraphBuilder *filterGraph;
100 IBaseFilter *captureFilter;
102 IBaseFilter *nullRenderer;
103 ISampleGrabber *sampleGrabber;
104 ICreateDevEnum *devEnumerator;
105 IEnumMoniker *videoInputEnumerator;
106 IMoniker *moniker;
107 IAMStreamConfig *streamConfig;
108 IMediaControl *mediaControl;
109
110 struct module *parent;
111};
112
113class SampleGrabberCallback : public ISampleGrabberCB {
114public:
115 unsigned long *frameNumber;
117
118 // We have to implement IUnknown COM interface, but can make it a little easier
119 STDMETHODIMP_(unsigned long) AddRef() { return 2; }
120 STDMETHODIMP_(unsigned long) Release() { return 1; }
121 STDMETHODIMP QueryInterface(REFIID iface, void **pointer) {
122 if (pointer == NULL) return E_POINTER;
123 if (iface == IID_IUnknown || iface == IID_ISampleGrabberCB) {
124 (*pointer) = this;
125 return S_OK;
126 }
127 *pointer = NULL;
128 return E_NOINTERFACE;
129 }
130
133 s(s)
134 {
135 if (frameNumber != NULL) *frameNumber = 0;
136 }
138
139 STDMETHODIMP SampleCB(double sampleTime, IMediaSample *ms) {
140 UNUSED(sampleTime);
141 UNUSED(ms);
142
143 return S_OK;
144 }
145
146 STDMETHODIMP BufferCB(double sampleTime, BYTE *buffer, long len) {
147 UNUSED(sampleTime);
148
149 if (len <= 0) return S_OK;
150
151 EnterCriticalSection(&s->returnBufferCS);
152 const long req_len = vc_get_datalen(
154 if (s->grabBufferLen != req_len) {
155 s->grabBuffer = (BYTE *) realloc((void *) s->grabBuffer, req_len);
156 if (s->grabBuffer == NULL) {
157 s->grabBufferLen = 0;
158 return S_OK;
159 }
161 }
162
163 // We need to make a copy, DirectShow will do something with the data
164 if (s->convert != nullptr) {
165 s->convert(s->desc.width, s->desc.height, buffer, s->grabBuffer);
166 } else {
167 memcpy((char *) s->grabBuffer, (char *) buffer, len);
168 }
169
170 bool grabMightWait = false;
171 if (!s->haveNewReturnBuffer) {
172 grabMightWait = true;
173 s->haveNewReturnBuffer = true;
174 }
175 LeaveCriticalSection(&s->returnBufferCS);
176 if (grabMightWait) {
177 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << "WakeConditionVariable s->grabWaitCV\n";
178 WakeConditionVariable(&s->grabWaitCV);
179 }
180
181 return S_OK;
182 }
183};
184
185static bool cleanup(struct vidcap_dshow_state *s) {
186 if (s->mediaControl != NULL) s->mediaControl->Release();
187 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Released: mediaControl\n");
188 if (s->nullRenderer != NULL) s->nullRenderer->Release();
189 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Released: nullRenderer\n");
190 if (s->captureFilter != NULL) s->captureFilter->Release();
191 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Released: captureFilter\n");
192 if (s->sampleGrabberFilter != NULL) s->sampleGrabberFilter->Release();
193 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Released: sampleGrabberFilter\n");
194 if (s->moniker != NULL) s->moniker->Release();
195 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Released: moniker\n");
196 /*
197 if (s->videoInputEnumerator != NULL) s->videoInputEnumerator->Release();
198 fprintf(stderr, "[dshow] Released: videoInputEnum\n");
199 if (s->devEnumerator != NULL) s->devEnumerator->Release();
200 fprintf(stderr, "[dshow] Released: devEnum\n");
201 if (s->filterGraph != NULL) s->filterGraph->Release();
202 fprintf(stderr, "[dshow] Released: filterGraph\n");
203 if (s->graphBuilder != NULL) s->graphBuilder->Release();
204 fprintf(stderr, "[dshow] Released: graphBuilder\n");
205 */
206
207 // COM library uninitialization
209
210 if (s->frame != NULL) vf_free(s->frame);
211 if (s->grabBuffer != NULL) free(s->grabBuffer);
212 if (s->returnBuffer != NULL) free(s->returnBuffer);
213 if (s->convert_buffer != NULL) free(s->convert_buffer);
214 if (yuv_clamp != NULL) free(yuv_clamp);
215 free(s->deviceName);
216
217 DeleteCriticalSection(&s->returnBufferCS);
218
219 free(s);
220
221 return true;
222}
223
224#define HANDLE_ERR_ACTION(res, action, msg, ...) \
225 if (res != S_OK) { \
226 MSG(ERROR, msg ": %s\n", \
227 __VA_ARGS__ __VA_OPT__(, ) hresult_to_str(res)); \
228 action; \
229 } else
230
231static bool common_init(struct vidcap_dshow_state *s) {
232#define HANDLE_ERR(msg, ...) \
233 HANDLE_ERR_ACTION(res, goto error, \
234 "vidcap_dshow_init: " msg __VA_OPT__(, ) \
235 __VA_ARGS__)
236 // set defaults
237 s->deviceNumber = 1;
238 s->modeNumber = 0;
241 s->desc.fps = DEFAULT_FPS;
242 s->desc.tile_count = 1;
244 s->com_initialized = false;
245
246 s->frame = NULL;
247 s->grabBufferLen = 0;
248 s->returnBufferLen = 0;
249 s->grabBuffer = NULL;
250 s->returnBuffer = NULL;
251 s->convert_buffer = NULL;
252 s->convert_YUYV_RGB = false;
253 s->no_sync_source = false;
254 yuv_clamp = NULL;
255
256 s->graphBuilder = NULL;
257 s->filterGraph = NULL;
258 s->devEnumerator = NULL;
259 s->videoInputEnumerator = NULL;
260 s->moniker = NULL;
261 s->captureFilter = NULL;
262 s->nullRenderer = NULL;
263 s->sampleGrabberFilter = NULL;
264 s->streamConfig = NULL;
265 s->mediaControl = NULL;
266
267 HRESULT res;
268
269 // Initialize COM library
270 // COINIT_APARTMENTTHREADED is used because we do not expect any other thread to work with the object
271 if (!com_initialize(&s->com_initialized, "widcap_dshow_init: ")) {
272 return false;
273 }
274
275 // Create non-inheritable mutex without a name, owned by this thread
276 InitializeCriticalSectionAndSpinCount(&s->returnBufferCS, 0x40);
277 s->haveNewReturnBuffer = false;
278
279 s->frames = 0;
280
281 // create device enumerator
282 res = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&s->devEnumerator));
283 HANDLE_ERR("Cannot create System Device Enumerator");
284
285 res = s->devEnumerator->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &s->videoInputEnumerator, 0);
286 if (res == S_FALSE) {
287 MSG(ERROR, "no devices found\n");
288 goto error;
289 }
290 HANDLE_ERR("Cannot create Video Input Device enumerator");
291
292 // Media processing classes (filters) are conected to a graph.
293 // Create graph builder -- helper class for connecting of the graph
294 res = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER,
295 IID_ICaptureGraphBuilder2, (void **) &s->graphBuilder);
296 HANDLE_ERR("Cannot create instance of Capture Graph Builder 2");
297
298 // create the filter graph
299 res = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
300 IID_IGraphBuilder, (void **) &s->filterGraph);
301 HANDLE_ERR("Cannot create instance of Filter Graph");
302
303 // specify the graph builder a graph to be built
304 res = s->graphBuilder->SetFiltergraph(s->filterGraph);
305 HANDLE_ERR("Cannot attach Filter Graph to Graph Builder");
306
307 return true;
308
309error:
310 cleanup(s);
311 return false;
312#undef HANDLE_ERR
313}
314
315static struct video_desc vidcap_dshow_get_video_desc(AM_MEDIA_TYPE *mediaType)
316{
317 BITMAPINFOHEADER *bmiHeader;
318 struct video_desc desc{};
319 if (mediaType->formattype != FORMAT_VideoInfo && mediaType->formattype != FORMAT_VideoInfo2) {
320 LOG(LOG_LEVEL_WARNING) << MOD_NAME "Unsupported format type!\n";
321 return desc;
322 }
323
324 desc.color_spec = get_ug_codec(&mediaType->subtype);
325 desc.tile_count = 1;
327 if (mediaType->formattype == FORMAT_VideoInfo) {
328 VIDEOINFOHEADER *infoHeader = reinterpret_cast<VIDEOINFOHEADER*>(mediaType->pbFormat);
329 bmiHeader = &infoHeader->bmiHeader;
330 desc.fps = 10000000.0/infoHeader->AvgTimePerFrame;
331 } else {
332 VIDEOINFOHEADER2 *infoHeader = reinterpret_cast<VIDEOINFOHEADER2*>(mediaType->pbFormat);
333 bmiHeader = &infoHeader->bmiHeader;
334 desc.fps = 10000000.0/infoHeader->AvgTimePerFrame;
335 if (infoHeader->dwInterlaceFlags & AMINTERLACE_IsInterlaced) {
336 if (infoHeader->dwInterlaceFlags & AMINTERLACE_1FieldPerSample) {
338 "1 Field Per Sample is not supported! ");
339 } else {
341 }
342 }
343 }
344 desc.width = bmiHeader->biWidth;
345 desc.height = bmiHeader->biHeight;
346
347 return desc;
348}
349
350static void
351show_help(struct vidcap_dshow_state *s, const char *help_str)
352{
353 const bool short_h = strcmp(help_str, "shorthelp") == 0;
354 const bool full_h = strcmp(help_str, "fullhelp") == 0;
355 printf("dshow grabber options:\n");
356 col() << SBOLD("\t-t dshow:[full|short]help") << "\n";
357 col() << SBOLD(SRED("\t-t dshow") << "[:device=<DeviceNumber>|<DeviceName>][:mode=<ModeNumber>][:RGB]") "\n";
358 col() << "\t Flag " << SBOLD("RGB") << " forces use of RGB codec, otherwise native is used if possible.\n";
359 printf("\tor\n");
360 col() << SBOLD(SRED("\t-t dshow:[Device]<DeviceNumber>:<codec>:<width>:<height>:<fps>")) "\n\n";
361
362 if (!common_init(s)) return;
363
364 color_printf("Devices:\n");
365 device_info *cards = nullptr;
366 int count = 0;
367 void (*deleter)(void *) = NULL;
368 vidcap_dshow_probe_internal(&cards, &count, &deleter, full_h);
369
370 // Enumerate all capture devices
371 for (int n = 0; n < count; ++n) {
372 color_printf("Device %d) " TERM_BOLD "%s\n" TERM_RESET, n + 1, cards[n].name);
373
374 if (short_h) {
375 continue;
376 }
377 int i = 0;
378 // iterate over all capabilities
379 while (strlen(cards[n].modes[i].id) > 0) {
380 fputs(i == 0 ? "" : i % 2 == 0 ? "\n" : "\t", stdout);
381 int mode_idx = -1;
382 sscanf(cards[n].modes[i].id, "{\"mode\":\"%d", &mode_idx);
383 UG_ASSERT_NO_FATAL(mode_idx != -1);
384 printf(" Mode %2d: %s", mode_idx, cards[n].modes[i].name);
385 ++i;
386 }
387
388 printf("\n\n");
389 }
390 deleter = IF_NOT_NULL_ELSE(deleter, (void (*)(void *)) free);
391 deleter(cards);
392
393 printf("%sMode flags:\n", short_h ? "\n" : "");
394 printf("C - codec not natively supported by UG%s\n\n",
395 full_h ? "; F - video format is "
396 "not supported"
397 : "\n(use ':fullhelp' to see also unsupported modes)");
398}
399
400static string
401get_friendly_name(IMoniker *moniker, int idx = -1)
402{
403#define HANDLE_ERR(msg, ...) \
404 HANDLE_ERR_ACTION(res, return {}, "%s: " msg, \
405 __func__ __VA_OPT__(, ) __VA_ARGS__)
406 IPropertyBag *properties = nullptr;
407 const string device_id = idx == -1 ? "" : to_string(idx) + " ";
408 // Attach structure for reading basic device properties
409 HRESULT res = moniker->BindToStorage(0, 0, IID_PPV_ARGS(&properties));
410 HANDLE_ERR("Failed to read device %s properties", device_id.c_str());
411
412 VARIANT var;
413 VariantInit(&var);
414 res = properties->Read(L"FriendlyName", &var, NULL);
415 HANDLE_ERR("Failed to read device %s FriendlyName", device_id.c_str());
416
417 char buf[MAX_STRING_LEN];
418 // convert to standard C string
419 wcstombs(buf, var.bstrVal, sizeof buf);
420
421 // clean up structures
422 VariantClear(&var);
423 properties->Release();
424
425 return buf;
426#undef HANDLE_ERR
427}
428
429static void
431 void (**deleter)(void *), bool include_unsupported)
432{
433 *deleter = free;
434 struct vidcap_dshow_state *s = (struct vidcap_dshow_state *) calloc(1, sizeof(struct vidcap_dshow_state));
435
436 if (!common_init(s))
437 return;
438
439 device_info *cards = nullptr;
440 int card_count = 0;
441
442 HRESULT res;
443 int n = 0;
444 // Enumerate all capture devices
445 while ((res = s->videoInputEnumerator->Next(1, &s->moniker, NULL)) == S_OK) {
446 n++;
447 card_count = n;
448 cards = (struct device_info *) realloc(cards, n * sizeof(struct device_info));
449 memset(&cards[n - 1], 0, sizeof(struct device_info));
450 snprintf(cards[n-1].dev, sizeof cards[n-1].dev - 1, ":device=%d", n);
451 snprintf(cards[n-1].name, sizeof cards[n-1].name - 1, "_DSHOW_FAILED_TO_READ_NAME_%d_", n);
452
453 string friendly_name = get_friendly_name(s->moniker, n);
454 if (friendly_name.empty()) {
455 log_msg(LOG_LEVEL_WARNING, MOD_NAME "vidcap_dshow_help: Failed to get device %d name.\n", n);
456 // Ignore the device
457 continue;
458 }
459 const char *name = friendly_name.c_str();;
460 snprintf(cards[n - 1].name, sizeof cards[n - 1].name, "%s",
461 name);
462
463#define HANDLE_ERR(msg, ...) \
464 HANDLE_ERR_ACTION(res, continue, "vidcap_dshow_help: %s: " msg, \
465 name __VA_OPT__(, ) __VA_ARGS__)
466 // bind the selected device to the capture filter
467 IBaseFilter *captureFilter;
468 res = s->moniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **) &captureFilter);
469 HANDLE_ERR("Cannot bind capture filter to device");
470
471 // add the capture filter to the filter graph
472 res = s->filterGraph->AddFilter(captureFilter, L"Capture filter");
473 HANDLE_ERR("Cannot add capture filter to filter graph");
474
475 // connect stream config interface to the capture filter
476 res = s->graphBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, captureFilter,
477 IID_IAMStreamConfig, (void **) &s->streamConfig);
478 HANDLE_ERR("Cannot find interface for reading capture capabilites");
479
480 int capCount, capSize;
481 // read number of capture device capabilities
482 res = s->streamConfig->GetNumberOfCapabilities(&capCount, &capSize);
483 HANDLE_ERR("Cannot read number of capture capabilites");
484 // check if the format of capture capabilities is the right one
485 if (capSize != sizeof(VIDEO_STREAM_CONFIG_CAPS)) {
486 log_msg(LOG_LEVEL_WARNING, MOD_NAME "vidcap_dshow_help: %s: Unknown format of capture capabilites.\n", name);
487 continue;
488 }
489
490 int mode_idx = 0;
491 // iterate over all capabilities
492 for (int i = 0; i < capCount; i++) {
493 if (mode_idx >=
494 (int) (sizeof cards[card_count - 1].modes /
495 sizeof cards[card_count - 1].modes[0]) -
496 1) { // no space
497 break;
498 }
499
500 AM_MEDIA_TYPE *mediaType;
501 VIDEO_STREAM_CONFIG_CAPS streamCaps;
502
503 res = s->streamConfig->GetStreamCaps(i, &mediaType, (BYTE*) &streamCaps);
504 HANDLE_ERR("Cannot read stream capabilities #%d", i);
505
506 struct video_desc desc = vidcap_dshow_get_video_desc(mediaType);
507 if (desc.width == 0) {
508 DeleteMediaType(mediaType);
509 continue;
510 }
511 if (mediaType->formattype != FORMAT_VideoInfo && !include_unsupported) {
512 DeleteMediaType(mediaType);
513 continue;
514 }
515
516 codec_t ug_codec = get_ug_codec(&mediaType->subtype);
518
519 snprintf(cards[card_count - 1].modes[mode_idx].id,
520 sizeof cards[card_count - 1].modes[mode_idx].id,
521 "{\"mode\":\"%d\", "
522 "\"force_rgb\":\"%c\"}",
523 i, force_rgb ? 't' : 'f');
524 snprintf(cards[card_count - 1].modes[mode_idx].name,
525 sizeof cards[card_count - 1].modes[mode_idx].name,
526 "%s %ux%u @%0.2lf%s %s%s", GetSubtypeName(&mediaType->subtype),
528 desc.color_spec ? "" : "C",
529 mediaType->formattype == FORMAT_VideoInfo ? "" : "F");
530 mode_idx += 1;
531
532 DeleteMediaType(mediaType);
533 }
534
535 s->streamConfig->Release();
536 res = s ->filterGraph->RemoveFilter(captureFilter);
537 HANDLE_ERR("Cannot remove capture filter from filter graph");
538 captureFilter->Release();
539 s->moniker->Release();
540 dev_add_option(&cards[card_count - 1], "Force no sync source", "Only use if capture is stuck", "no_sync_source", ":no_sync_source", true);
541 }
542 cleanup(s);
543 *available_cards = cards;
544 *count = card_count;
545#undef HANDLE_ERR
546}
547
548static void
549vidcap_dshow_probe(device_info **available_cards, int *count,
550 void (**deleter)(void *)) {
551 vidcap_dshow_probe_internal(available_cards, count, deleter, false);
552}
553
554static bool process_args(struct vidcap_dshow_state *s, char *init_fmt) {
555 char *token;
556 char *strtok_context;
557 int i = 1;
558
559 if (strchr(init_fmt, '=') == NULL) { // positional arguments
560 while ((token = strtok_s(init_fmt, ":", &strtok_context)) != NULL) {
561 init_fmt = NULL;
562 switch (i) {
563 case 1 :
564 if (strstr(token, "Device") == token) token = token + strlen("Device");
565 if (isdigit(token[0])) { // device specified by number
566 s->deviceNumber = atoi(token);
567 } else { // device specified by name
568 s->deviceName = (char *) malloc(sizeof(char) * (strlen(token) + 100));
569 if (s->deviceName == NULL) return false;
570 strcpy_s(s->deviceName, strlen(token) + 1, token);
571 s->deviceNumber = -1;
572 }
573 break;
574 case 2 :
575 if (strstr(token, "Mode") == token) token = token + strlen("Mode");
576 if (isdigit(token[0])) {
577 s->modeNumber = atoi(token);
578 } else {
579 s->modeNumber = -1;
581 if (s->desc.color_spec == VIDEO_CODEC_NONE) { // try Win subtype name
583 }
584 if (s->desc.color_spec == VIDEO_CODEC_NONE) {
587 "Unsupported video "
588 "format: %s. ",
589 token);
590 return false;
591 }
592 }
593 break;
594 case 3 :
595 if (s->modeNumber != -1) {
596 if (strcmp(token, "RGB") == 0) s->desc.color_spec = BGR;
597 else {
598 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown parameter: %s.\n", token);
599 return false;
600 }
601 break;
602 }
603 s->desc.width = atoi(token);
604 if (s->desc.width <= 0) {
605 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Invalid video width parameter: %s.\n", token);
606 return false;
607 }
608 break;
609 case 4 :
610 if (s->modeNumber != -1) break;
611 s->desc.height = atoi(token);
612 if (s->desc.height <= 0) {
613 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Invalid video height parameter: %s.\n", token);
614 return false;
615 }
616 break;
617 case 5 :
618 if (s->modeNumber != -1) break;
619 s->desc.fps = atoi(token);
620 if (s->desc.fps <= 0) {
621 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Invalid video fps parameter: %s.\n", token);
622 return false;
623 }
624 break;
625 default :
626 log_msg(LOG_LEVEL_ERROR, MOD_NAME "More arguments than expected, ignoring.\n");
627 break;
628 }
629 i++;
630 }
631 } else {
632 while ((token = strtok_s(init_fmt, ":", &strtok_context)) != NULL) {
633 init_fmt = NULL;
634 if (IS_KEY_PREFIX(token, "device")) {
635 token = strchr(token, '=') + 1;
636 if (isdigit(token[0])) { // device specified by number
637 s->deviceNumber = atoi(token);
638 } else { // device specified by name
639 s->deviceName = (char *) malloc(sizeof(char) * (strlen(token) + 100));
640 strcpy_s(s->deviceName, strlen(token) + 1, token);
641 s->deviceNumber = -1;
642 }
643 } else if (IS_KEY_PREFIX(token, "mode")) {
644 token = strchr(token, '=') + 1;
645 s->modeNumber = atoi(token);
646 } else if (strcmp(token, "RGB") == 0) {
647 s->desc.color_spec = BGR;
648 } else if (strcmp(token, "no_sync_source") == 0) {
649 s->no_sync_source = true;
650 } else {
651 MSG(ERROR, "Unknown argument: %s\n", token);
652 return false;
653 }
654 }
655 }
656
657 return true;
658}
659
660static HRESULT PinIsConnected(IPin *pin, bool *result) {
661 IPin *connectedPin;
662
663 HRESULT res = pin->ConnectedTo(&connectedPin);
664 if (res == S_OK) {
665 *result = true;
666 } else if (res == VFW_E_NOT_CONNECTED) {
667 res = S_OK;
668 *result = false;
669 }
670
671 if (connectedPin != NULL) connectedPin->Release();
672 return res;
673}
674
675static HRESULT PinHasDirection(IPin *pin, PIN_DIRECTION direction, bool *result) {
676 PIN_DIRECTION pinDir;
677
678 HRESULT res = pin->QueryDirection(&pinDir);
679 if (res == S_OK) {
680 *result = (pinDir == direction);
681 }
682
683 return res;
684}
685
686static HRESULT FindUnconnectedPin(IBaseFilter *filter, PIN_DIRECTION direction, IPin **pin) {
687 IEnumPins *pinEnum = NULL;
688 IPin *filterPin = NULL;
689 bool pinFound;
690
691 HRESULT res = filter->EnumPins(&pinEnum);
692 if (res != S_OK) {
693 goto error;
694 }
695
696 pinFound = false;
697 while ((res = pinEnum->Next(1, &filterPin, NULL)) == S_OK) {
698 bool isConnected;
699 res = PinIsConnected(filterPin, &isConnected);
700 if (res == S_OK) {
701 if (isConnected) {
702 continue;
703 }
704 bool hasDirection;
705 res = PinHasDirection(filterPin, direction, &hasDirection);
706 if (res == S_OK) {
707 if (!hasDirection) continue;
708
709 // We found the pin!
710 *pin = filterPin;
711 (*pin)->AddRef();
712 pinFound = true;
713 break;
714 } else {
715 goto error;
716 }
717 } else {
718 goto error;
719 }
720 if (filterPin != NULL) filterPin->Release();
721 }
722
723 if (!pinFound) {
724 res = VFW_E_NOT_FOUND;
725 }
726
727error:
728 if (pinEnum != NULL) pinEnum->Release();
729 if (filterPin != NULL) filterPin->Release();
730 return res;
731}
732
733static HRESULT ConnectFilters(IGraphBuilder *g, IPin *fromPin, IBaseFilter *toFilter) {
734 IPin *toPin = NULL;
735
736 HRESULT res = FindUnconnectedPin(toFilter, PINDIR_INPUT, &toPin);
737 if (res == S_OK) {
738 res = g->Connect(fromPin, toPin);
739 toPin->Release();
740 }
741
742 return res;
743}
744
745static HRESULT ConnectFilters(IGraphBuilder *g, IBaseFilter *fromFilter, IBaseFilter *toFilter) {
746 IPin *fromPin = NULL;
747
748 HRESULT res = FindUnconnectedPin(fromFilter, PINDIR_OUTPUT, &fromPin);
749 if (res == S_OK) {
750 res = ConnectFilters(g, fromPin, toFilter);
751 fromPin->Release();
752 }
753
754 return res;
755}
756
757[[maybe_unused]] static HRESULT GraphRun(IMediaControl *mc) {
758 HRESULT res;
759
760 if ((res = mc->Run()) == S_FALSE) {
761 FILTER_STATE fs;
762
763 while ((res = mc->GetState(500, (OAFilterState*) &fs)) != VFW_S_CANT_CUE && !(res == S_OK && fs == State_Running)) {
764 if (res != VFW_S_STATE_INTERMEDIATE && res != S_OK) {
765 break;
766 }
767 }
768 }
769
770 if (res != S_OK) {
771 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot run filter graph.\n");
772 ErrorDescription(res);
773 }
774
775 return res;
776}
777
778[[maybe_unused]] static HRESULT GraphPause(IMediaControl *mc) {
779 HRESULT res;
780
781 if ((res = mc->Pause()) == S_FALSE) {
782 FILTER_STATE fs;
783
784 while ((res = mc->GetState(500, (OAFilterState*) &fs)) != VFW_S_CANT_CUE && !(res == S_OK && fs == State_Paused)) {
785 if (res != VFW_S_STATE_INTERMEDIATE && res != S_OK) break;
786 }
787 }
788
789 if (res != S_OK) {
790 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot pause filter graph.\n");
791 ErrorDescription(res);
792 }
793
794 return res;
795}
796
797[[maybe_unused]] static HRESULT GetPinCategory(IPin *pPin, GUID *pPinCategory)
798{
799 IKsPropertySet *pKs = NULL;
800
801 HRESULT hr = pPin->QueryInterface(IID_PPV_ARGS(&pKs));
802 if (FAILED(hr))
803 {
804 return hr;
805 }
806
807 // Try to retrieve the pin category.
808 DWORD cbReturned = 0;
809 hr = pKs->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
810 pPinCategory, sizeof(GUID), &cbReturned);
811
812 // If this succeeded, pPinCategory now contains the category GUID.
813
814 if (pKs != NULL) pKs->Release();
815 return hr;
816}
817
818static void vidcap_dshow_should_exit(void *state) {
819 auto *s = static_cast<vidcap_dshow_state *>(state);
820 EnterCriticalSection(&s->returnBufferCS);
821 s->should_exit = true;
822 LeaveCriticalSection(&s->returnBufferCS);
823 WakeConditionVariable(&s->grabWaitCV);
824}
825
826static int vidcap_dshow_init(struct vidcap_params *params, void **state) {
827#define HANDLE_ERR(msg, ...) \
828 HANDLE_ERR_ACTION(res, goto error, \
829 "vidcap_dshow_init: " msg __VA_OPT__(, ) __VA_ARGS__)
830 struct vidcap_dshow_state *s;
831 HRESULT res;
832
835 }
836
837 s = (struct vidcap_dshow_state *) calloc(1, sizeof(struct vidcap_dshow_state));
838 if (s == NULL) {
839 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: memory allocation error\n");
840 return VIDCAP_INIT_FAIL;
841 }
842
843 InitializeConditionVariable(&s->grabWaitCV);
844 InitializeCriticalSection(&s->returnBufferCS);
845
846 if (strstr(vidcap_params_get_fmt(params), "help") != nullptr) {
848 cleanup(s);
849 return VIDCAP_INIT_NOERR;
850 }
851
852 if (!common_init(s)) {
853 goto error;
854 }
855
856 if (vidcap_params_get_fmt(params) != NULL) {
857 char *init_fmt = strdup(vidcap_params_get_fmt(params));
858 if (!process_args(s, init_fmt)) goto error;
859 free(init_fmt);
860 }
861
862 // Select video capture device
863 if (s->deviceNumber != -1) { // Device was specified by number
864 res = E_FAIL;
865 for (int i = 1; i <= s->deviceNumber; i++) {
866 // Take one device. We could take more at once, but it would require allocation of more moniker objects
867 res = s->videoInputEnumerator->Next(1, &s->moniker, NULL);
868 if(i != s->deviceNumber) {
869 if (s->moniker)
870 s->moniker->Release();
871 s->moniker = NULL;
872 }
873 if (res != S_OK) break;
874 }
875 if (res != S_OK) {
876 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: Device number %d was not found.\n", s->deviceNumber);
877 goto error;
878 }
879 } else { // device specified by name
880 while ((res = s->videoInputEnumerator->Next(1, &s->moniker, NULL)) == S_OK) {
881 string friendly_name = get_friendly_name(s->moniker);
882 if (strcasecmp(s->deviceName, friendly_name.c_str()) == 0) {
883 break;
884 }
885 s->moniker->Release();
886 }
887
888 if (res != S_OK) {
889 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: Device named %s was not found.\n", s->deviceName);
890 goto error;
891 }
892 }
893
894 if(get_friendly_name(s->moniker).find("OBS Virtual") != std::string::npos){
895 log_msg(LOG_LEVEL_WARNING, MOD_NAME "OBS virtual camera detected. Force setting sync source to NULL!\n");
896 s->no_sync_source = true;
897 }
898
899 LOG(LOG_LEVEL_NOTICE) << MOD_NAME "Capturing from device: "
900 << get_friendly_name(s->moniker) << "\n";
901
902 res = s->moniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **) &s->captureFilter);
903 HANDLE_ERR("Cannot bind capture filter to device");
904
905 res = s->filterGraph->AddFilter(s->captureFilter, L"Capture filter");
906 HANDLE_ERR("Cannot add capture filter to filter graph");
907
908 res = s->graphBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, s->captureFilter,
909 IID_IAMStreamConfig, (void **) &s->streamConfig);
910 HANDLE_ERR("Cannot find interface for reading capture capabilites");
911
912 // create instance of sample grabber
913 res = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&s->sampleGrabberFilter));
914 HANDLE_ERR("Cannot create instance of sample grabber");
915
916 // add the sample grabber to filter graph
917 res = s->filterGraph->AddFilter(s->sampleGrabberFilter, L"Sample Grabber");
918 HANDLE_ERR("Cannot add sample grabber to filter graph");
919
920 // query the sample grabber filter for control interface
921 res = s->sampleGrabberFilter->QueryInterface(IID_PPV_ARGS(&s->sampleGrabber));
922 HANDLE_ERR("Cannot query sample grabber filter for control interface");
923
924 // make the sample grabber buffer frames
925 res = s->sampleGrabber->SetBufferSamples(TRUE);
926 HANDLE_ERR("Cannot set sample grabber to buffer samples");
927
928 // set media type for sample grabber; this is not done a very detailed setup, because it would be unneccessarily complicated to do so
929 AM_MEDIA_TYPE sampleGrabberMT;
930 ZeroMemory(&sampleGrabberMT, sizeof(sampleGrabberMT));
931 sampleGrabberMT.majortype = MEDIATYPE_Video;
932 sampleGrabberMT.subtype = MEDIASUBTYPE_RGB24;
933 res = s->sampleGrabber->SetMediaType(&sampleGrabberMT);
934 HANDLE_ERR("Cannot setup media type of grabber filter");
935
936 int capCount, capSize;
937 res = s->streamConfig->GetNumberOfCapabilities(&capCount, &capSize);
938 HANDLE_ERR("Cannot read number of capture capabilites");
939 if (capSize != sizeof(VIDEO_STREAM_CONFIG_CAPS)) {
940 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: Unknown format of capture capabilites.\n");
941 goto error;
942 }
943
944 bool format_found;
945 format_found = false;
946 AM_MEDIA_TYPE *mediaType;
947 VIDEO_STREAM_CONFIG_CAPS streamCaps;
948
949 if (s->modeNumber >= 0) { // mode number was set by user directly
950 res = s->streamConfig->GetStreamCaps(s->modeNumber, &mediaType, (BYTE*) &streamCaps);
951 // Invalid mode selected
952 if (res == S_FALSE || res == E_INVALIDARG) {
953 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Invalid mode index!\n");
954 goto error;
955 }
956 // Some other error occured
958 "Cannot read stream capabilities #%d (index is correct)",
959 s->modeNumber);
960
961 assert(s->desc.color_spec == VC_NONE || s->desc.color_spec == BGR);
962 if (s->desc.color_spec == VC_NONE) {
963 s->desc.color_spec = get_ug_codec(&mediaType->subtype);
964 if (s->desc.color_spec == VC_NONE) {
965 MSG(WARNING,
966 "Pixel format %.4s not supported directly, "
967 "capturing BGR.\n",
968 (char *) &mediaType->subtype.Data1);
969 s->desc.color_spec = BGR;
970 } else {
971 res = s->sampleGrabber->SetMediaType(mediaType);
972 HANDLE_ERR("Cannot setup media type "
973 "of grabber filter");
974 }
975 }
976
977 struct video_desc desc = vidcap_dshow_get_video_desc(mediaType);
978 s->desc.width = desc.width;
979 s->desc.height = desc.height;
980 s->desc.fps = desc.fps;
982
983 format_found = true;
984 } else {
985 for (int i = 0; i < capCount; i++) {
986 res = s->streamConfig->GetStreamCaps(i, &mediaType, (BYTE*) &streamCaps);
987 if (res != S_OK) {
988 log_msg(LOG_LEVEL_WARNING, MOD_NAME "vidcap_dshow_help: Cannot read stream capabilities #%d.\n", i);
989 continue;
990 }
991 if (s->desc.color_spec !=
992 get_ug_codec(&mediaType->subtype)) {
993 continue;
994 }
995
996 struct video_desc desc = vidcap_dshow_get_video_desc(mediaType);
997 if (desc.height == s->desc.height && desc.width == s->desc.width) {
998 format_found = true;
999 res = s->sampleGrabber->SetMediaType(mediaType);
1000 HANDLE_ERR("Cannot setup media type "
1001 "of grabber filter");
1002 break;
1003 }
1004
1005 DeleteMediaType(mediaType);
1006 }
1007 }
1008
1009 if (!format_found) {
1010 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: Requested format not supported by the device. Quitting.\n");
1011 goto error;
1012 }
1013
1015 MSG(WARNING,
1016 "Capturing a compressed format directly. You may consider "
1017 "enforcing conversion to RGB (':RGB' option), eg. to "
1018 "use another compression.\n");
1019 }
1020
1021#if 0
1022 if (s->modeNumber < 0) { // mode number was not set by user directly
1023 s->streamConfig->GetFormat(&mediaType);
1024 HANDLE_ERR("Cannot get current capture format");
1025 switch (s->desc.color_spec) {
1026 case BGR : mediaType->subtype = MEDIASUBTYPE_RGB24;
1027 break;
1028 case YUYV : mediaType->subtype = MEDIASUBTYPE_YUY2;
1029 break;
1030 default:
1031 // this is directly from parse_fmt, where, only 2 formats above are permissible
1032 abort();
1033 }
1034 VIDEOINFOHEADER *infoHeader;
1035 infoHeader = reinterpret_cast<VIDEOINFOHEADER*>(mediaType->pbFormat);
1036 infoHeader->rcSource.bottom = s->desc.height;
1037 infoHeader->rcSource.right = s->desc.width;
1038 infoHeader->AvgTimePerFrame = (REFERENCE_TIME) (1e7 / s->desc.fps);
1039 }
1040#endif
1041 res = s->streamConfig->SetFormat(mediaType);
1042 HANDLE_ERR("Cannot set capture format");
1043
1044 if (s->convert_YUYV_RGB) {
1045 s->convert_buffer = (BYTE *) malloc(s->desc.height * s->desc.width * 3);
1046 if (s->convert_buffer == NULL) {
1047 log_msg(LOG_LEVEL_ERROR, MOD_NAME "vidcap_dshow_init: memory allocation error\n");
1048 goto error;
1049 }
1050
1051 yuv_clamp = (int *) malloc((MAX_YUV_CLAMP - MIN_YUV_CLAMP + 1) * sizeof(int));
1052 int i;
1053 for (i = 0; i + MIN_YUV_CLAMP <= 0; i++) {
1054 yuv_clamp[i] = 0;
1055 }
1056 for (int j = 1; j <= 254; j++, i++) {
1057 yuv_clamp[i] = j;
1058 }
1059 for ( ; i < MAX_YUV_CLAMP - MIN_YUV_CLAMP; i++) {
1060 yuv_clamp[i] = 255;
1061 }
1062 }
1063
1064 // Create null renderer discarding all incoming frames
1065 res = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter,
1066 (void **) &s->nullRenderer);
1067 HANDLE_ERR("Cannot create NullRenderer");
1068
1069 res = s->filterGraph->AddFilter(s->nullRenderer, L"NullRenderer");
1070 HANDLE_ERR("Cannot add null renderer to filter graph");
1071
1072 IEnumPins *pinEnum;
1073 IPin *pin;
1074
1075 res = s->captureFilter->EnumPins(&pinEnum);
1076 HANDLE_ERR("Error enumerating pins of capture filter");
1077
1078 while (pinEnum->Next(1, &pin, NULL) == S_OK) {
1080 if (pin != NULL) pin->Release();
1081 if (res == S_OK) {
1082 break;
1083 }
1084 }
1085 HANDLE_ERR("Cannot connect capture filter to sample grabber");
1086
1087 res = s->sampleGrabber->GetConnectedMediaType(&sampleGrabberMT);
1088 HANDLE_ERR("Cannot get current grabber format");
1089 MSG(INFO, "streaming type: %s, grabber type: %s, output: %s\n",
1090 GetSubtypeName(&mediaType->subtype),
1091 GetSubtypeName(&sampleGrabberMT.subtype),
1092 ((string) vidcap_dshow_get_video_desc(&sampleGrabberMT)).c_str());
1093 s->convert = get_conversion(&sampleGrabberMT.subtype);
1094 DeleteMediaType(mediaType);
1095
1097 HANDLE_ERR("Cannot connect sample grabber to null renderer");
1098
1099 s->SGCallback = new SampleGrabberCallback(s, &s->frames);
1100 s->sampleGrabber->SetCallback(s->SGCallback, 1);
1101
1102 /*
1103 res = s->graphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, s->captureFilter,
1104 NULL, s->nullRenderer);
1105 if (res != S_OK) {
1106 fprintf(stderr, "[dshow] vidcap_dshow_init: Cannot render stream.\n");
1107 goto error;
1108 }
1109 */
1110
1111 res = s->filterGraph->QueryInterface(IID_IMediaControl, (void **) &s->mediaControl);
1112 HANDLE_ERR("Cannot find media control interface");
1113
1114 if(s->no_sync_source){
1115 IMediaFilter *pMediaFilter;
1116 res = s->filterGraph->QueryInterface(IID_IMediaFilter, (void **) &pMediaFilter);
1117 HANDLE_ERR("Cannot find media filter interface");
1118 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Setting sync source to NULL\n");
1119 pMediaFilter->SetSyncSource(NULL);
1120 }
1121
1122 FILTER_STATE fs;
1123 res = s->mediaControl->Run();
1124 while ((res = s->mediaControl->GetState(500, (OAFilterState*) &fs)) != VFW_S_CANT_CUE && !(res == S_OK && fs == State_Running)) {
1125 if (res != VFW_S_STATE_INTERMEDIATE && res != S_OK) {
1126 break;
1127 }
1128 }
1129 HANDLE_ERR("Cannot run filter graph");
1130 res = s->sampleGrabberFilter->GetState(INFINITE, &fs);
1131 HANDLE_ERR("filter getstate error");
1132
1133 s->frame = vf_alloc_desc(s->desc);
1136
1137 *state = s;
1138 return VIDCAP_INIT_OK;
1139
1140error:
1141 cleanup(s);
1142 return VIDCAP_INIT_FAIL;
1143#undef HANDLE_ERR
1144}
1145
1146static void vidcap_dshow_done(void *state) {
1147 struct vidcap_dshow_state *s = (struct vidcap_dshow_state *) state;
1148
1150
1151 HRESULT res = s->mediaControl->Stop();
1152 if (res != S_OK) {
1153 log_msg(LOG_LEVEL_WARNING, MOD_NAME "vidcap_dshow_init: Failed to stop filter graph.\n");
1154 }
1155
1156 cleanup(s);
1157}
1158
1159static inline void convert_yuv_rgb(BYTE y, BYTE u, BYTE v, BYTE *dst) {
1160 int yy = (y - 16) * 298;
1161 int uu = u - 128;
1162 int vv = v - 128;
1163
1164 dst[0] = yuv_clamp[(yy + uu * 516 + 128) >> 8];
1165 dst[1] = yuv_clamp[(yy - uu * 100 - vv * 208 + 128) >> 8];
1166 dst[2] = yuv_clamp[(yy + 409 * vv + 128) >> 8];
1167}
1168
1169static void convert_yuyv_rgb(BYTE *src, BYTE *dst, int input_len) {
1170 BYTE *s = src;
1171 //BYTE *d = dst;
1172
1173 for (int i = 0; i < input_len; i += 4) {
1174 convert_yuv_rgb(s[0], s[1], s[3], dst);
1175 convert_yuv_rgb(s[2], s[1], s[3], dst + 3);
1176 s += 4;
1177 dst += 6;
1178 }
1179}
1180
1181static struct video_frame * vidcap_dshow_grab(void *state, struct audio_frame **audio) {
1182 struct vidcap_dshow_state *s = (struct vidcap_dshow_state *) state;
1183 *audio = NULL;
1184
1185 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << "GRAB: enter: " << s->deviceNumber << "\n";
1186 EnterCriticalSection(&s->returnBufferCS);
1187 //fprintf(stderr, "[dshow] s: %p\n", s);
1188 while (!s->haveNewReturnBuffer && !s->should_exit) {
1189 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << "Wait CV\n";
1190 SleepConditionVariableCS(&s->grabWaitCV, &s->returnBufferCS, INFINITE);
1191 //fprintf(stderr, "[dshow] s: %p\n", s);
1192 }
1193
1194 if (s->should_exit) {
1195 LeaveCriticalSection(&s->returnBufferCS);
1196 return nullptr;
1197 }
1198
1199 LOG(LOG_LEVEL_DEBUG) << MOD_NAME << "Swap buffers\n";
1200 //fprintf(stderr, "[dshow] s: %p\n", s);
1201 // switch the buffers
1202 BYTE *tmp = s->returnBuffer;
1203 s->returnBuffer = s->grabBuffer;
1204 s->grabBuffer = tmp;
1205 unsigned long tmplen = s->returnBufferLen;
1207 s->grabBufferLen = tmplen;
1208 s->haveNewReturnBuffer = false;
1209 //fprintf(stderr, "[dshow] s: %p\n", s);
1210
1211 LeaveCriticalSection(&s->returnBufferCS);
1212
1213 //fprintf(stderr, "[dshow] GRAB: CS leave: %d\n", s->deviceNumber);
1214 //fprintf(stderr, "[dshow] s: %p\n", s);
1215
1216
1217 if (s->convert_YUYV_RGB) {
1219 }
1220 //fprintf(stderr, "[dshow] s: %p\n", s);
1221
1222 s->frame->tiles[0].data = (char *) s->returnBuffer;
1223 //fprintf(stderr, "[dshow] s: %p\n", s);
1224 //s->tile->data_len = s->width * s->height * 3;
1227
1228/*
1229 fprintf(stderr, "[dshow] s5: %p\n", s);
1230 fprintf(stderr, "[dshow] s6: %p\n", s);
1231 fprintf(stderr, "[dshow] s7: %p\n", s);
1232 fprintf(stderr, "[dshow] s: %p\n", s);
1233 fprintf(stderr, "[dshow] %lf\n", seconds);
1234 */
1235
1236 s->frames++;
1237
1238 return s->frame;
1239}
1240
1241static const CHAR * GetSubtypeNameA(const GUID *pSubtype);
1242static int LocateSubtype(const GUID *pSubtype);
1243static void FreeMediaType(AM_MEDIA_TYPE& mt);
1244
1245static void FreeMediaType(AM_MEDIA_TYPE& mt)
1246{
1247 if (mt.cbFormat != 0) {
1248 CoTaskMemFree((PVOID)mt.pbFormat);
1249
1250 // Strictly unnecessary but tidier
1251 mt.cbFormat = 0;
1252 mt.pbFormat = NULL;
1253 }
1254 if (mt.pUnk != NULL) {
1255 mt.pUnk->Release();
1256 mt.pUnk = NULL;
1257 }
1258}
1259
1260static void DeleteMediaType(AM_MEDIA_TYPE *pmt) {
1261 // allow NULL pointers for coding simplicity
1262
1263 if (pmt == NULL) {
1264 return;
1265 }
1266
1267 FreeMediaType(*pmt);
1268 CoTaskMemFree((PVOID)pmt);
1269}
1270
1271static void
1272nv12_to_uyvy(int width, int height, const unsigned char *in, unsigned char *out)
1273{
1274 const int uyvy_linesize = vc_get_linesize(width, UYVY);
1275 for (ptrdiff_t y = 0; y < height; ++y) {
1276 const unsigned char *src_y = in + width * y;
1277 const unsigned char *src_cbcr =
1278 in + (ptrdiff_t) width * height + width * (y / 2);
1279 unsigned char *dst = out + y * uyvy_linesize;
1280
1281 OPTIMIZED_FOR(int x = 0; x < width / 2; ++x)
1282 {
1283 *dst++ = *src_cbcr++;
1284 *dst++ = *src_y++;
1285 *dst++ = *src_cbcr++;
1286 *dst++ = *src_y++;
1287 }
1288 }
1289}
1290
1293static void
1294bgr_flip_lines(int width, int height, const unsigned char *in,
1295 unsigned char *out)
1296{
1297 const size_t linesize = vc_get_linesize(width, BGR);
1298 for (int i = 0; i < height; ++i) {
1299 memcpy(out + i * linesize, in + (height - i - 1) * linesize,
1300 linesize);
1301 }
1302}
1303
1305static void
1306abgr_flip_and_swap(int width, int height, const unsigned char *in,
1307 unsigned char *out)
1308{
1309 const size_t linesize = vc_get_linesize(width, RGBA);
1310 for (int i = 0; i < height; ++i) {
1311 vc_copylineRGBA(out + i * linesize,
1312 in + (height - i - 1) * linesize, linesize, 16,
1313 8, 0);
1314 }
1315}
1316
1317// <https://learn.microsoft.com/en-us/windows/win32/medfound/video-subtype-guids>
1318// <https://gix.github.io/media-types>
1319#define GUID_FROM_FOURCC(fourcc) {fourcc, 0x0000, 0x10, {0x80,0x0,0x0,0xAA,0x0,0x38,0x9B,0x71}}
1320static const GUID GUID_R210 = GUID_FROM_FOURCC(0x30313272);
1321static const GUID GUID_v210 = GUID_FROM_FOURCC(0x30313276);
1322static const GUID GUID_V210 = GUID_FROM_FOURCC(0x30313256);
1323static const GUID GUID_HDYC = GUID_FROM_FOURCC(0x43594448);
1324static const GUID GUID_I420 = GUID_FROM_FOURCC(0x30323449);
1325static const GUID GUID_H264 = GUID_FROM_FOURCC(to_fourcc('H', '2', '6', '4')); // 0x34363248
1326static const GUID GUID_HEVC = GUID_FROM_FOURCC(to_fourcc('H', 'E', 'V', 'C'));
1327static const GUID GUID_AV01 = GUID_FROM_FOURCC(to_fourcc('A', 'V', '0', '1'));
1328
1329static const struct {
1330 const GUID *pSubtype;
1331 const CHAR *pName;
1334} BitCountMap[] = {
1335 {&MEDIASUBTYPE_RGB1, "RGB Monochrome", VC_NONE, nullptr },
1336 { &MEDIASUBTYPE_RGB4, "RGB VGA", VC_NONE, nullptr },
1337 { &MEDIASUBTYPE_RGB8, "RGB 8", VC_NONE, nullptr },
1338 { &MEDIASUBTYPE_RGB565, "RGB 565 (16 bit)", VC_NONE, nullptr },
1339 { &MEDIASUBTYPE_RGB555, "RGB 555 (16 bit)", VC_NONE, nullptr },
1340 { &MEDIASUBTYPE_RGB24, "RGB 24", BGR, bgr_flip_lines },
1341 { &MEDIASUBTYPE_RGB32, "RGB 32", RGBA, abgr_flip_and_swap},
1342 { &MEDIASUBTYPE_ARGB32, "ARGB 32", VC_NONE, nullptr },
1343 { &MEDIASUBTYPE_Overlay, "Overlay", VC_NONE, nullptr },
1344 { &GUID_I420, "I420", UYVY, i420_8_to_uyvy },
1345 { &MEDIASUBTYPE_YUY2, "YUY2", YUYV, nullptr },
1346 { &GUID_R210, "r210", VC_NONE, nullptr },
1347 { &GUID_v210, "v210", v210, nullptr },
1348 { &GUID_V210, "V210", v210, nullptr },
1349 { &MEDIASUBTYPE_UYVY, "UYVY", UYVY, nullptr },
1350 { &GUID_HDYC, "HDYC", UYVY, nullptr },
1351 { &MEDIASUBTYPE_MJPG, "MJPG", MJPG, nullptr },
1352 { &GUID_H264, "H264", H264, nullptr },
1353 { &GUID_HEVC, "HEVC", H265, nullptr },
1354 { &GUID_AV01, "AV1", AV1, nullptr },
1355 { &MEDIASUBTYPE_NV12, "NV12", UYVY, nv12_to_uyvy },
1356 { &GUID_NULL, "UNKNOWN", VC_NONE, nullptr },
1358
1359static codec_t get_ug_codec(const GUID *pSubtype)
1360{
1361 return BitCountMap[LocateSubtype(pSubtype)].ug_codec;
1362}
1363
1364static convert_t
1366{
1367 return BitCountMap[LocateSubtype(pSubtype)].convert;
1368}
1369
1370static codec_t get_ug_from_subtype_name(const char *subtype_name) {
1371 for (unsigned int i = 0; i < sizeof BitCountMap / sizeof BitCountMap[0]; ++i) {
1372 if (strcmp(BitCountMap[i].pName, subtype_name) == 0) {
1373 return BitCountMap[i].ug_codec;
1374 }
1375 }
1376 return VIDEO_CODEC_NONE;
1377}
1378
1379static int LocateSubtype(const GUID *pSubtype)
1380{
1381 assert(pSubtype);
1382 const GUID *pMediaSubtype;
1383 INT iPosition = 0;
1384
1385/* this code is only to optain type data if UNKNOWN
1386 fprintf(stderr, "%s ", &pSubtype->Data1);
1387 fprintf(stderr, "%X ", pSubtype->Data1);
1388 fprintf(stderr, "%hX ", pSubtype->Data2);
1389 fprintf(stderr, "%hX ", pSubtype->Data3);
1390 for (int i = 0;i < 8; ++i)
1391 fprintf(stderr, "%.2hhX", pSubtype->Data4[i]);
1392*/
1393
1394 // Scan the mapping list seeing if the source GUID matches any known
1395 // bitmap subtypes, the list is terminated by a GUID_NULL entry
1396
1397 while (TRUE) {
1398 pMediaSubtype = BitCountMap[iPosition].pSubtype;
1399 if (IsEqualGUID(*pMediaSubtype,*pSubtype) ||
1400 IsEqualGUID(*pMediaSubtype,GUID_NULL)
1401 )
1402 {
1403 break;
1404 }
1405
1406 iPosition++;
1407 }
1408
1409 return iPosition;
1410}
1411
1412static const CHAR * GetSubtypeNameA(const GUID *pSubtype)
1413{
1414 return BitCountMap[LocateSubtype(pSubtype)].pName;
1415}
1416
1417// this is here for people that linked to it directly; most people
1418// would use the header file that picks the A or W version.
1419static const CHAR * GetSubtypeName(const GUID *pSubtype)
1420{
1421 // the type is unknown to us, so print FourCC
1422 if (LocateSubtype(pSubtype) == sizeof BitCountMap / sizeof BitCountMap[0] - 1) {
1423 return pretty_print_fourcc(&pSubtype->Data1);
1424 }
1425 return GetSubtypeNameA(pSubtype);
1426}
1427
1435
1437
1438/* vim: set noexpandtab: */
1439
static void vidcap_dshow_probe_internal(device_info **available_cards, int *count, void(**deleter)(void *), bool include_unsupported)
static const GUID GUID_H264
codec_t ug_codec
static HRESULT GetPinCategory(IPin *pPin, GUID *pPinCategory)
static HRESULT PinIsConnected(IPin *pin, bool *result)
static void abgr_flip_and_swap(int width, int height, const unsigned char *in, unsigned char *out)
convert from ABGR and bottom-to-top
static const GUID GUID_HDYC
const struct video_capture_info vidcap_dshow_info
static int vidcap_dshow_init(struct vidcap_params *params, void **state)
static convert_t get_conversion(const GUID *pSubtype)
static const GUID GUID_v210
static const GUID GUID_V210
#define MAX_YUV_CLAMP
static codec_t get_ug_from_subtype_name(const char *subtype_name)
static void convert_yuv_rgb(BYTE y, BYTE u, BYTE v, BYTE *dst)
#define DEFAULT_VIDEO_HEIGHT
static int * yuv_clamp
static HRESULT PinHasDirection(IPin *pin, PIN_DIRECTION direction, bool *result)
EXTERN_C const CLSID CLSID_NullRenderer
static const GUID GUID_HEVC
static const CHAR * GetSubtypeNameA(const GUID *pSubtype)
#define MAX_STRING_LEN
static HRESULT GraphPause(IMediaControl *mc)
const GUID * pSubtype
static const GUID GUID_I420
#define HANDLE_ERR(msg,...)
static const GUID GUID_AV01
#define MIN_YUV_CLAMP
static const struct @69 BitCountMap[]
static int LocateSubtype(const GUID *pSubtype)
static void vidcap_dshow_should_exit(void *state)
static const CHAR * GetSubtypeName(const GUID *pSubtype)
static void DeleteMediaType(AM_MEDIA_TYPE *mediaType)
const CHAR * pName
static string get_friendly_name(IMoniker *moniker, int idx=-1)
static HRESULT ConnectFilters(IGraphBuilder *g, IPin *fromPin, IBaseFilter *toFilter)
static void vidcap_dshow_done(void *state)
static void convert_yuyv_rgb(BYTE *src, BYTE *dst, int input_len)
static bool process_args(struct vidcap_dshow_state *s, char *init_fmt)
static void ErrorDescription(HRESULT hr)
#define DEFAULT_VIDEO_WIDTH
#define DEFAULT_FPS
#define MOD_NAME
static struct video_frame * vidcap_dshow_grab(void *state, struct audio_frame **audio)
static bool common_init(struct vidcap_dshow_state *s)
static void nv12_to_uyvy(int width, int height, const unsigned char *in, unsigned char *out)
static void FreeMediaType(AM_MEDIA_TYPE &mt)
static codec_t get_ug_codec(const GUID *pSubtype)
EXTERN_C const CLSID CLSID_SampleGrabber
static HRESULT GraphRun(IMediaControl *mc)
static void bgr_flip_lines(int width, int height, const unsigned char *in, unsigned char *out)
static HRESULT FindUnconnectedPin(IBaseFilter *filter, PIN_DIRECTION direction, IPin **pin)
static void vidcap_dshow_probe(device_info **available_cards, int *count, void(**deleter)(void *))
static struct video_desc vidcap_dshow_get_video_desc(AM_MEDIA_TYPE *mediaType)
#define GUID_FROM_FOURCC(fourcc)
static const GUID GUID_R210
#define UNUSED(x)
void ** state
struct video_desc desc
int void size_t * len
struct audio_frame ** audio
int void(** deleter)(void *))
void vf_free(struct video_frame *buf)
Frees video_frame structure.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc(struct video_desc desc)
Allocates video frame accordig given desc.
codec_t get_codec_from_name(const char *name)
static void show_help()
Definition wasapi.cpp:193
static af_result_code filter(void *state, struct audio_frame **frame)
STDMETHODIMP SampleCB(double sampleTime, IMediaSample *ms)
SampleGrabberCallback(struct vidcap_dshow_state *s, unsigned long *frameNumber)
STDMETHODIMP_(unsigned long) AddRef()
STDMETHODIMP_(unsigned long) Release()
STDMETHODIMP BufferCB(double sampleTime, BYTE *buffer, long len)
STDMETHODIMP QueryInterface(REFIID iface, void **pointer)
struct vidcap_dshow_state * s
int color_printf(const char *format,...)
#define TERM_BOLD
Definition color_out.h:48
#define SRED(x)
Definition color_out.h:85
#define TERM_RESET
Definition color_out.h:47
#define SBOLD(x)
Definition color_out.h:80
#define TRUE
unsigned char BYTE
Definition crypt_aes.h:71
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
void bug_msg(int level, const char *format,...)
prints the error with the advice to report the bug
Definition debug.cpp:379
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define MSG(l, fmt,...)
Definition debug.h:96
#define LOG(level)
Definition debug.h:313
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define VIDCAP_INIT_NOERR
state not initialized, other action performed (typically help)
#define VIDCAP_INIT_OK
initialization successful
#define VIDEO_CAPTURE_ABI_VERSION
#define VIDCAP_INIT_FAIL
error ocured
#define VIDCAP_INIT_AUDIO_NOT_SUPPORTED
card does not support audio
int count
Definition hd-rum.c:44
void register_should_exit_callback(struct module *mod, void(*callback)(void *), void *udata)
Definition host.cpp:1144
void unregister_should_exit_callback(struct module *mod, void(*callback)(void *), void *udata)
Definition host.cpp:1158
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
@ LIBRARY_CLASS_VIDEO_CAPTURE
Definition lib_common.h:78
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
#define UG_ASSERT_NO_FATAL(cond)
Definition macros.h:90
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
#define EXTERN_C
Definition macros.h:71
#define IF_NOT_NULL_ELSE(val, default_val)
Definition macros.h:51
#define OPTIMIZED_FOR
Definition macros.h:127
#define to_fourcc(a, b, c, d)
Creates FourCC word.
Definition macros.h:114
void(*)(struct video_frame *, const uint8_t *, int in_stride, int field_idx, int total_fields) convert_t
Definition ndi.cpp:357
decoder_func_t vc_copylineRGBA
static void cleanup()
Definition sdp.c:195
const char * name
Definition settings.cpp:193
const char * pretty_print_fourcc(const void *fcc)
struct used to comunicate with audio capture/playback device
Definition types.h:99
struct device_info::mode modes[512]
char dev[1024]
Definition types.h:383
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281
IBaseFilter * sampleGrabberFilter
IEnumMoniker * videoInputEnumerator
SampleGrabberCallback * SGCallback
CONDITION_VARIABLE grabWaitCV
struct video_desc desc
ICreateDevEnum * devEnumerator
IMediaControl * mediaControl
IGraphBuilder * filterGraph
struct video_frame * frame
IAMStreamConfig * streamConfig
ICaptureGraphBuilder2 * graphBuilder
CRITICAL_SECTION returnBufferCS
ISampleGrabber * sampleGrabber
unsigned int tile_count
Definition types.h:185
codec_t color_spec
Definition types.h:182
enum interlacing_t interlacing
Definition types.h:184
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
codec_t color_spec
Definition types.h:306
void dev_add_option(struct device_info *dev, const char *name, const char *desc, const char *key, const char *opt_str, bool is_boolean)
@ PROGRESSIVE
progressive frame
Definition types.h:164
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
Definition types.h:167
codec_t
Definition types.h:65
@ YUYV
YCbCr 422 8-bit - Y0 Cb Y1 Cr.
Definition types.h:72
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ VIDEO_CODEC_NONE
dummy color spec
Definition types.h:66
@ BGR
8-bit BGR
Definition types.h:88
@ AV1
AOMedia Video 1.
Definition types.h:96
@ VC_NONE
Definition types.h:67
@ MJPG
JPEG image, without restart intervals.
Definition types.h:85
@ H264
H.264 frame.
Definition types.h:83
@ H265
H.264 frame.
Definition types.h:84
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
@ v210
YCbCr 422 10-bit - xVY0U|xY2UY1|xUY3V|xY5VY4, little-endian (2 bit padding /'x'/),...
Definition types.h:75
This is an umbrella header for video functions.
struct module * vidcap_params_get_parent(const struct vidcap_params *params)
const char * vidcap_params_get_fmt(const struct vidcap_params *params)
unsigned int vidcap_params_get_flags(const struct vidcap_params *params)
#define VIDCAP_FLAG_AUDIO_ANY
void i420_8_to_uyvy(int width, int height, const unsigned char *in, unsigned char *out)
bool is_codec_opaque(codec_t codec)
bool codec_is_planar(codec_t codec)
size_t vc_get_datalen(unsigned int width, unsigned int height, codec_t codec)
const char * get_interlacing_suffix(enum interlacing_t interlacing)
Returns suffix describing interlacing Eg. p, i or psf.
@ FAILED
bool com_initialize(bool *com_initialized, const char *err_prefix)
Definition windows.c:65
void com_uninitialize(bool *com_initialized)
Definition windows.c:94