1 /* Webcamoid, webcam capture application.
2 * Copyright (C) 2018 Gonzalo Exequiel Pedone
3 *
4 * Webcamoid is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * Webcamoid is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Web-Site: http://webcamoid.github.io/
18 */
19
20 #include <algorithm>
21 #include <cwchar>
22 #include <map>
23 #include <sstream>
24 #include <dshow.h>
25 #include <dvdmedia.h>
26 #include <comdef.h>
27 #include <shlobj.h>
28
29 #include "utils.h"
30 #include "VCamUtils/src/utils.h"
31 #include "VCamUtils/src/image/videoformat.h"
32
33 #define TIME_BASE 1.0e7
34
35 namespace AkVCam
36 {
37 class VideoFormatSpecsPrivate
38 {
39 public:
40 FourCC pixelFormat;
41 DWORD compression;
42 GUID guid;
43 const DWORD *masks;
44
formats()45 inline static const std::vector<VideoFormatSpecsPrivate> &formats()
46 {
47 static const DWORD bits555[] = {0x007c00, 0x0003e0, 0x00001f};
48 static const DWORD bits565[] = {0x00f800, 0x0007e0, 0x00001f};
49
50 static const std::vector<VideoFormatSpecsPrivate> formats {
51 {PixelFormatRGB32, BI_RGB , MEDIASUBTYPE_RGB32 , nullptr},
52 {PixelFormatRGB24, BI_RGB , MEDIASUBTYPE_RGB24 , nullptr},
53 {PixelFormatRGB16, BI_BITFIELDS , MEDIASUBTYPE_RGB565, bits565},
54 {PixelFormatRGB15, BI_BITFIELDS , MEDIASUBTYPE_RGB555, bits555},
55 {PixelFormatUYVY , MAKEFOURCC('U', 'Y', 'V', 'Y'), MEDIASUBTYPE_UYVY , nullptr},
56 {PixelFormatYUY2 , MAKEFOURCC('Y', 'U', 'Y', '2'), MEDIASUBTYPE_YUY2 , nullptr},
57 {PixelFormatNV12 , MAKEFOURCC('N', 'V', '1', '2'), MEDIASUBTYPE_NV12 , nullptr}
58 };
59
60 return formats;
61 }
62
byGuid(const GUID & guid)63 static inline const VideoFormatSpecsPrivate *byGuid(const GUID &guid)
64 {
65 for (auto &format: formats())
66 if (IsEqualGUID(format.guid, guid))
67 return &format;
68
69 return nullptr;
70 }
71
byPixelFormat(FourCC pixelFormat)72 static inline const VideoFormatSpecsPrivate *byPixelFormat(FourCC pixelFormat)
73 {
74 for (auto &format: formats())
75 if (format.pixelFormat == pixelFormat)
76 return &format;
77
78 return nullptr;
79 }
80 };
81 }
82
operator <(const CLSID & a,const CLSID & b)83 bool operator <(const CLSID &a, const CLSID &b)
84 {
85 return AkVCam::stringFromIid(a) < AkVCam::stringFromIid(b);
86 }
87
isWow64()88 BOOL AkVCam::isWow64()
89 {
90 BOOL isWow64 = FALSE;
91
92 if (!IsWow64Process(GetCurrentProcess(), &isWow64))
93 return false;
94
95 return isWow64;
96 }
97
tempPath()98 std::wstring AkVCam::tempPath()
99 {
100 WCHAR tempPath[MAX_PATH];
101 memset(tempPath, 0, MAX_PATH * sizeof(WCHAR));
102 GetTempPath(MAX_PATH, tempPath);
103
104 return std::wstring(tempPath);
105 }
106
programFilesPath()107 std::wstring AkVCam::programFilesPath()
108 {
109 WCHAR programFiles[MAX_PATH];
110 DWORD programFilesSize = MAX_PATH * sizeof(WCHAR);
111 memset(programFiles, 0, programFilesSize);
112 bool ok = false;
113
114 if (isWow64()
115 && regGetValue(HKEY_LOCAL_MACHINE,
116 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion",
117 L"ProgramFilesDir",
118 RRF_RT_REG_SZ,
119 nullptr,
120 &programFiles,
121 &programFilesSize) == ERROR_SUCCESS)
122 ok = true;
123
124 if (!ok)
125 SHGetSpecialFolderPath(nullptr,
126 programFiles,
127 CSIDL_PROGRAM_FILES,
128 FALSE);
129
130 return std::wstring(programFiles);
131 }
132
moduleFileNameW(HINSTANCE hinstDLL)133 std::wstring AkVCam::moduleFileNameW(HINSTANCE hinstDLL)
134 {
135 WCHAR fileName[MAX_PATH];
136 memset(fileName, 0, MAX_PATH * sizeof(WCHAR));
137 GetModuleFileName(hinstDLL, fileName, MAX_PATH);
138
139 return std::wstring(fileName);
140 }
141
moduleFileName(HINSTANCE hinstDLL)142 std::string AkVCam::moduleFileName(HINSTANCE hinstDLL)
143 {
144 auto fileName = moduleFileNameW(hinstDLL);
145
146 return std::string(fileName.begin(), fileName.end());
147 }
148
errorToStringW(DWORD errorCode)149 std::wstring AkVCam::errorToStringW(DWORD errorCode)
150 {
151 WCHAR *errorStr = nullptr;
152 auto size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
153 | FORMAT_MESSAGE_FROM_SYSTEM
154 | FORMAT_MESSAGE_IGNORE_INSERTS,
155 nullptr,
156 errorCode,
157 MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
158 reinterpret_cast<LPWSTR>(&errorStr),
159 0,
160 nullptr);
161 std::wstring error(errorStr, size);
162 LocalFree(errorStr);
163
164 return error;
165 }
166
errorToString(DWORD errorCode)167 std::string AkVCam::errorToString(DWORD errorCode)
168 {
169 auto errorStr = errorToStringW(errorCode);
170
171 return std::string(errorStr.begin(), errorStr.end());
172 }
173
174 // Converts a human redable string to a CLSID using MD5 hash.
createClsidFromStr(const std::string & str)175 CLSID AkVCam::createClsidFromStr(const std::string &str)
176 {
177 return createClsidFromStr(std::wstring(str.begin(), str.end()));
178 }
179
createClsidFromStr(const std::wstring & str)180 CLSID AkVCam::createClsidFromStr(const std::wstring &str)
181 {
182 HCRYPTPROV provider = 0;
183 HCRYPTHASH hash = 0;
184 CLSID clsid;
185 DWORD clsidLen = sizeof(CLSID);
186 memset(&clsid, 0, sizeof(CLSID));
187
188 if (!CryptAcquireContext(&provider,
189 nullptr,
190 nullptr,
191 PROV_RSA_FULL,
192 CRYPT_VERIFYCONTEXT))
193 goto clsidFromStr_failed;
194
195 if (!CryptCreateHash(provider, CALG_MD5, 0, 0, &hash))
196 goto clsidFromStr_failed;
197
198 if (!CryptHashData(hash,
199 reinterpret_cast<const BYTE *>(str.c_str()),
200 DWORD(str.size() * sizeof(wchar_t)),
201 0))
202 goto clsidFromStr_failed;
203
204 CryptGetHashParam(hash,
205 HP_HASHVAL,
206 reinterpret_cast<BYTE *>(&clsid),
207 &clsidLen,
208 0);
209
210 clsidFromStr_failed:
211 if (hash)
212 CryptDestroyHash(hash);
213
214 if (provider)
215 CryptReleaseContext(provider, 0);
216
217 return clsid;
218 }
219
createClsidWStrFromStr(const std::string & str)220 std::wstring AkVCam::createClsidWStrFromStr(const std::string &str)
221 {
222 return createClsidWStrFromStr(std::wstring(str.begin(), str.end()));
223 }
224
createClsidWStrFromStr(const std::wstring & str)225 std::wstring AkVCam::createClsidWStrFromStr(const std::wstring &str)
226 {
227 auto clsid = createClsidFromStr(str);
228 OLECHAR *clsidWStr = nullptr;
229
230 if (StringFromCLSID(clsid, &clsidWStr) != S_OK)
231 return std::wstring();
232
233 std::wstring wstr(clsidWStr);
234 CoTaskMemFree(clsidWStr);
235
236 return wstr;
237 }
238
stringFromIid(const IID & iid)239 std::string AkVCam::stringFromIid(const IID &iid)
240 {
241 auto wstr = wstringFromIid(iid);
242
243 return std::string(wstr.begin(), wstr.end());
244 }
245
wstringFromIid(const IID & iid)246 std::wstring AkVCam::wstringFromIid(const IID &iid)
247 {
248 WCHAR *strIID = nullptr;
249 StringFromIID(iid, &strIID);
250 std::wstring wstr(strIID);
251 CoTaskMemFree(strIID);
252
253 return wstr;
254 }
255
stringFromResult(HRESULT result)256 std::string AkVCam::stringFromResult(HRESULT result)
257 {
258 auto msg = std::wstring(_com_error(result).ErrorMessage());
259
260 return std::string(msg.begin(), msg.end());
261 }
262
stringFromClsid(const CLSID & clsid)263 std::string AkVCam::stringFromClsid(const CLSID &clsid)
264 {
265 static const std::map<CLSID, std::string> clsidToString {
266 {IID_IAgileObject , "IAgileObject" },
267 {IID_IAMAnalogVideoDecoder, "IAMAnalogVideoDecoder"},
268 {IID_IAMAudioInputMixer , "IAMAudioInputMixer" },
269 {IID_IAMAudioRendererStats, "IAMAudioRendererStats"},
270 {IID_IAMBufferNegotiation , "IAMBufferNegotiation" },
271 {IID_IAMCameraControl , "IAMCameraControl" },
272 {IID_IAMClockAdjust , "IAMClockAdjust" },
273 {IID_IAMCrossbar , "IAMCrossbar" },
274 {IID_IAMDeviceRemoval , "IAMDeviceRemoval" },
275 {IID_IAMExtDevice , "IAMExtDevice" },
276 {IID_IAMFilterMiscFlags , "IAMFilterMiscFlags" },
277 {IID_IAMOpenProgress , "IAMOpenProgress" },
278 {IID_IAMPushSource , "IAMPushSource" },
279 {IID_IAMStreamConfig , "IAMStreamConfig" },
280 {IID_IAMTVTuner , "IAMTVTuner" },
281 {IID_IAMVfwCaptureDialogs , "IAMVfwCaptureDialogs" },
282 {IID_IAMVfwCompressDialogs, "IAMVfwCompressDialogs"},
283 {IID_IAMVideoCompression , "IAMVideoCompression" },
284 {IID_IAMVideoControl , "IAMVideoControl" },
285 {IID_IAMVideoProcAmp , "IAMVideoProcAmp" },
286 {IID_IBaseFilter , "IBaseFilter" },
287 {IID_IBasicAudio , "IBasicAudio" },
288 {IID_IBasicVideo , "IBasicVideo" },
289 {IID_IClassFactory , "IClassFactory" },
290 {IID_IEnumMediaTypes , "IEnumMediaTypes" },
291 {IID_IEnumPins , "IEnumPins" },
292 {IID_IFileSinkFilter , "IFileSinkFilter" },
293 {IID_IFileSinkFilter2 , "IFileSinkFilter2" },
294 {IID_IFileSourceFilter , "IFileSourceFilter" },
295 {IID_IKsPropertySet , "IKsPropertySet" },
296 {IID_IMarshal , "IMarshal" },
297 {IID_IMediaControl , "IMediaControl" },
298 {IID_IMediaFilter , "IMediaFilter" },
299 {IID_IMediaPosition , "IMediaPosition" },
300 {IID_IMediaSample , "IMediaSample" },
301 {IID_IMediaSample2 , "IMediaSample2" },
302 {IID_IMediaSeeking , "IMediaSeeking" },
303 {IID_IMediaEventSink , "IMediaEventSink" },
304 {IID_IMemAllocator , "IMemAllocator" },
305 {IID_INoMarshal , "INoMarshal" },
306 {IID_IPersist , "IPersist" },
307 {IID_IPersistPropertyBag , "IPersistPropertyBag" },
308 {IID_IPin , "IPin" },
309 {IID_IProvideClassInfo , "IProvideClassInfo" },
310 {IID_IQualityControl , "IQualityControl" },
311 {IID_IReferenceClock , "IReferenceClock" },
312 {IID_IRpcOptions , "IRpcOptions" },
313 {IID_ISpecifyPropertyPages, "ISpecifyPropertyPages"},
314 {IID_IVideoWindow , "IVideoWindow" },
315 {IID_IUnknown , "IUnknown" },
316 };
317
318 for (auto &id: clsidToString)
319 if (IsEqualCLSID(id.first, clsid))
320 return id.second;
321
322 return stringFromIid(clsid);
323 }
324
wcharStrFromWStr(const std::wstring & wstr)325 wchar_t *AkVCam::wcharStrFromWStr(const std::wstring &wstr)
326 {
327 if (wstr.size() < 1)
328 return nullptr;
329
330 auto wcstrSize = wstr.size() * sizeof(wchar_t);
331 auto wcstr = reinterpret_cast<wchar_t *>(CoTaskMemAlloc(wcstrSize + 1));
332 wcstr[wstr.size()] = 0;
333 memcpy(wcstr, wstr.data(), wcstrSize);
334
335 return wcstr;
336 }
337
formatFromGuid(const GUID & guid)338 AkVCam::FourCC AkVCam::formatFromGuid(const GUID &guid)
339 {
340 auto formatSpec = VideoFormatSpecsPrivate::byGuid(guid);
341
342 if (!formatSpec)
343 return 0;
344
345 return formatSpec->pixelFormat;
346 }
347
guidFromFormat(FourCC format)348 const GUID &AkVCam::guidFromFormat(FourCC format)
349 {
350 auto formatSpec = VideoFormatSpecsPrivate::byPixelFormat(format);
351
352 if (!formatSpec)
353 return GUID_NULL;
354
355 return formatSpec->guid;
356 }
357
compressionFromFormat(FourCC format)358 DWORD AkVCam::compressionFromFormat(FourCC format)
359 {
360 auto formatSpec = VideoFormatSpecsPrivate::byPixelFormat(format);
361
362 if (!formatSpec)
363 return 0;
364
365 return formatSpec->compression;
366 }
367
isSubTypeSupported(const GUID & subType)368 bool AkVCam::isSubTypeSupported(const GUID &subType)
369 {
370 for (auto &format: VideoFormatSpecsPrivate::formats())
371 if (IsEqualGUID(format.guid, subType))
372 return true;
373
374 return false;
375 }
376
mediaTypeFromFormat(const AkVCam::VideoFormat & format)377 AM_MEDIA_TYPE *AkVCam::mediaTypeFromFormat(const AkVCam::VideoFormat &format)
378 {
379 auto subtype = guidFromFormat(format.fourcc());
380
381 if (IsEqualGUID(subtype, GUID_NULL))
382 return nullptr;
383
384 auto frameSize = format.size();
385
386 if (!frameSize)
387 return nullptr;
388
389 auto videoInfo =
390 reinterpret_cast<VIDEOINFO *>(CoTaskMemAlloc(sizeof(VIDEOINFO)));
391 memset(videoInfo, 0, sizeof(VIDEOINFO));
392 auto fps = format.minimumFrameRate();
393
394 // Initialize info header.
395 videoInfo->rcSource = {0, 0, 0, 0};
396 videoInfo->rcTarget = videoInfo->rcSource;
397 videoInfo->dwBitRate = DWORD(8
398 * frameSize
399 * fps.num()
400 / fps.den());
401 videoInfo->AvgTimePerFrame = REFERENCE_TIME(TIME_BASE
402 * fps.den()
403 / fps.num());
404
405 // Initialize bitmap header.
406 videoInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
407 videoInfo->bmiHeader.biWidth = format.width();
408 videoInfo->bmiHeader.biHeight = format.height();
409 videoInfo->bmiHeader.biPlanes = 1;
410 videoInfo->bmiHeader.biBitCount = WORD(format.bpp());
411 videoInfo->bmiHeader.biCompression = compressionFromFormat(format.fourcc());
412 videoInfo->bmiHeader.biSizeImage = DWORD(format.size());
413
414 switch (videoInfo->bmiHeader.biCompression) {
415 case BI_RGB:
416 if (videoInfo->bmiHeader.biBitCount == 8) {
417 videoInfo->bmiHeader.biClrUsed = iPALETTE_COLORS;
418
419 if (HDC hdc = GetDC(nullptr)) {
420 PALETTEENTRY palette[iPALETTE_COLORS];
421
422 if (GetSystemPaletteEntries(hdc,
423 0,
424 iPALETTE_COLORS,
425 palette))
426 for (int i = 0; i < iPALETTE_COLORS; i++) {
427 videoInfo->TrueColorInfo.bmiColors[i].rgbRed = palette[i].peRed;
428 videoInfo->TrueColorInfo.bmiColors[i].rgbBlue = palette[i].peBlue;
429 videoInfo->TrueColorInfo.bmiColors[i].rgbGreen = palette[i].peGreen;
430 videoInfo->TrueColorInfo.bmiColors[i].rgbReserved = 0;
431 }
432
433 ReleaseDC(nullptr, hdc);
434 }
435 }
436
437 break;
438
439 case BI_BITFIELDS: {
440 auto masks = VideoFormatSpecsPrivate::byPixelFormat(format.fourcc())->masks;
441
442 if (masks)
443 memcpy(videoInfo->TrueColorInfo.dwBitMasks, masks, 3);
444 }
445
446 break;
447
448 default:
449 break;
450 }
451
452 auto mediaType =
453 reinterpret_cast<AM_MEDIA_TYPE *>(CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)));
454 memset(mediaType, 0, sizeof(AM_MEDIA_TYPE));
455
456 // Initialize media type.
457 mediaType->majortype = MEDIATYPE_Video;
458 mediaType->subtype = subtype;
459 mediaType->bFixedSizeSamples = TRUE;
460 mediaType->bTemporalCompression = FALSE;
461 mediaType->lSampleSize = ULONG(frameSize);
462 mediaType->formattype = FORMAT_VideoInfo;
463 mediaType->cbFormat = sizeof(VIDEOINFO);
464 mediaType->pbFormat = reinterpret_cast<BYTE *>(videoInfo);
465
466 return mediaType;
467 }
468
formatFromMediaType(const AM_MEDIA_TYPE * mediaType)469 AkVCam::VideoFormat AkVCam::formatFromMediaType(const AM_MEDIA_TYPE *mediaType)
470 {
471 if (!mediaType)
472 return VideoFormat();
473
474 if (!IsEqualGUID(mediaType->majortype, MEDIATYPE_Video))
475 return VideoFormat();
476
477 if (!isSubTypeSupported(mediaType->subtype))
478 return VideoFormat();
479
480 if (!mediaType->pbFormat)
481 return VideoFormat();
482
483 if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) {
484 auto format = reinterpret_cast<VIDEOINFOHEADER *>(mediaType->pbFormat);
485 auto fps = Fraction {uint32_t(TIME_BASE),
486 uint32_t(format->AvgTimePerFrame)};
487
488 return VideoFormat(formatFromGuid(mediaType->subtype),
489 format->bmiHeader.biWidth,
490 std::abs(format->bmiHeader.biHeight),
491 {fps});
492 } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) {
493 auto format = reinterpret_cast<VIDEOINFOHEADER2 *>(mediaType->pbFormat);
494 auto fps = Fraction {uint32_t(TIME_BASE),
495 uint32_t(format->AvgTimePerFrame)};
496
497 return VideoFormat(formatFromGuid(mediaType->subtype),
498 format->bmiHeader.biWidth,
499 std::abs(format->bmiHeader.biHeight),
500 {fps});
501 }
502
503 return VideoFormat();
504 }
505
isEqualMediaType(const AM_MEDIA_TYPE * mediaType1,const AM_MEDIA_TYPE * mediaType2,bool exact)506 bool AkVCam::isEqualMediaType(const AM_MEDIA_TYPE *mediaType1,
507 const AM_MEDIA_TYPE *mediaType2,
508 bool exact)
509 {
510 if (mediaType1 == mediaType2)
511 return true;
512
513 if (!mediaType1 || !mediaType2)
514 return false;
515
516 if (!IsEqualGUID(mediaType1->majortype, mediaType2->majortype)
517 || !IsEqualGUID(mediaType1->subtype, mediaType2->subtype)
518 || !IsEqualGUID(mediaType1->formattype, mediaType2->formattype))
519 return false;
520
521 if (mediaType1->pbFormat == mediaType2->pbFormat)
522 return true;
523
524 if (exact)
525 return memcmp(mediaType1->pbFormat,
526 mediaType2->pbFormat,
527 mediaType1->cbFormat) == 0;
528
529 if (IsEqualGUID(mediaType1->formattype, FORMAT_VideoInfo)) {
530 auto format1 = reinterpret_cast<VIDEOINFOHEADER *>(mediaType1->pbFormat);
531 auto format2 = reinterpret_cast<VIDEOINFOHEADER *>(mediaType2->pbFormat);
532
533 if (format1->bmiHeader.biWidth == format2->bmiHeader.biWidth
534 && format1->bmiHeader.biHeight == format2->bmiHeader.biHeight)
535 return true;
536 } else if (IsEqualGUID(mediaType1->formattype, FORMAT_VideoInfo2)) {
537 auto format1 = reinterpret_cast<VIDEOINFOHEADER2 *>(mediaType1->pbFormat);
538 auto format2 = reinterpret_cast<VIDEOINFOHEADER2 *>(mediaType2->pbFormat);
539
540 if (format1->bmiHeader.biWidth == format2->bmiHeader.biWidth
541 && format1->bmiHeader.biHeight == format2->bmiHeader.biHeight)
542 return true;
543 }
544
545 return false;
546 }
547
copyMediaType(AM_MEDIA_TYPE * dstMediaType,const AM_MEDIA_TYPE * srcMediaType)548 bool AkVCam::copyMediaType(AM_MEDIA_TYPE *dstMediaType,
549 const AM_MEDIA_TYPE *srcMediaType)
550 {
551 if (!dstMediaType)
552 return false;
553
554 if (!srcMediaType) {
555 memset(dstMediaType, 0, sizeof(AM_MEDIA_TYPE));
556
557 return false;
558 }
559
560 memcpy(dstMediaType, srcMediaType, sizeof(AM_MEDIA_TYPE));
561
562 if (dstMediaType->cbFormat && dstMediaType->pbFormat) {
563 dstMediaType->pbFormat =
564 reinterpret_cast<BYTE *>(CoTaskMemAlloc(dstMediaType->cbFormat));
565 memcpy(dstMediaType->pbFormat,
566 srcMediaType->pbFormat,
567 dstMediaType->cbFormat);
568 }
569
570 return true;
571 }
572
createMediaType(const AM_MEDIA_TYPE * mediaType)573 AM_MEDIA_TYPE *AkVCam::createMediaType(const AM_MEDIA_TYPE *mediaType)
574 {
575 if (!mediaType)
576 return nullptr;
577
578 auto newMediaType =
579 reinterpret_cast<AM_MEDIA_TYPE *>(CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)));
580 memcpy(newMediaType, mediaType, sizeof(AM_MEDIA_TYPE));
581
582 if (newMediaType->cbFormat && newMediaType->pbFormat) {
583 newMediaType->pbFormat =
584 reinterpret_cast<BYTE *>(CoTaskMemAlloc(newMediaType->cbFormat));
585 memcpy(newMediaType->pbFormat,
586 mediaType->pbFormat,
587 newMediaType->cbFormat);
588 }
589
590 return newMediaType;
591 }
592
deleteMediaType(AM_MEDIA_TYPE ** mediaType)593 void AkVCam::deleteMediaType(AM_MEDIA_TYPE **mediaType)
594 {
595 if (!mediaType || !*mediaType)
596 return;
597
598 auto format = (*mediaType)->pbFormat;
599
600 if (format && (*mediaType)->cbFormat)
601 CoTaskMemFree(format);
602
603 CoTaskMemFree(*mediaType);
604 *mediaType = nullptr;
605 }
606
containsMediaType(const AM_MEDIA_TYPE * mediaType,IEnumMediaTypes * mediaTypes)607 bool AkVCam::containsMediaType(const AM_MEDIA_TYPE *mediaType,
608 IEnumMediaTypes *mediaTypes)
609 {
610 AM_MEDIA_TYPE *mt = nullptr;
611 mediaTypes->Reset();
612 auto isEqual = false;
613
614 while (mediaTypes->Next(1, &mt, nullptr) == S_OK) {
615 isEqual = isEqualMediaType(mt, mediaType);
616 deleteMediaType(&mt);
617
618 if (isEqual)
619 break;
620 }
621
622 return isEqual;
623 }
624
stringFromMajorType(const GUID & majorType)625 std::string AkVCam::stringFromMajorType(const GUID &majorType)
626 {
627 static const std::map<GUID, std::string> mtToStr {
628 {GUID_NULL , "GUID_NULL" },
629 {MEDIATYPE_AnalogAudio , "MEDIATYPE_AnalogAudio" },
630 {MEDIATYPE_AnalogVideo , "MEDIATYPE_AnalogVideo" },
631 {MEDIATYPE_Audio , "MEDIATYPE_Audio" },
632 {MEDIATYPE_AUXLine21Data, "MEDIATYPE_AUXLine21Data"},
633 {MEDIATYPE_File , "MEDIATYPE_File" },
634 {MEDIATYPE_Interleaved , "MEDIATYPE_Interleaved" },
635 {MEDIATYPE_LMRT , "MEDIATYPE_LMRT" },
636 {MEDIATYPE_Midi , "MEDIATYPE_Midi" },
637 {MEDIATYPE_MPEG2_PES , "MEDIATYPE_MPEG2_PES" },
638 {MEDIATYPE_ScriptCommand, "MEDIATYPE_ScriptCommand"},
639 {MEDIATYPE_Stream , "MEDIATYPE_Stream" },
640 {MEDIATYPE_Text , "MEDIATYPE_Text" },
641 {MEDIATYPE_Timecode , "MEDIATYPE_Timecode" },
642 {MEDIATYPE_URL_STREAM , "MEDIATYPE_URL_STREAM" },
643 {MEDIATYPE_VBI , "MEDIATYPE_VBI" },
644 {MEDIATYPE_Video , "MEDIATYPE_Video" }
645 };
646
647 for (auto &mediaType: mtToStr)
648 if (IsEqualGUID(mediaType.first, majorType))
649 return mediaType.second;
650
651 return stringFromIid(majorType);
652 }
653
stringFromSubType(const GUID & subType)654 std::string AkVCam::stringFromSubType(const GUID &subType)
655 {
656 static const std::map<GUID, std::string> mstToStr {
657 {GUID_NULL , "GUID_NULL" },
658 {MEDIASUBTYPE_RGB1 , "MEDIASUBTYPE_RGB1" },
659 {MEDIASUBTYPE_RGB4 , "MEDIASUBTYPE_RGB4" },
660 {MEDIASUBTYPE_RGB8 , "MEDIASUBTYPE_RGB8" },
661 {MEDIASUBTYPE_RGB555 , "MEDIASUBTYPE_RGB555" },
662 {MEDIASUBTYPE_RGB565 , "MEDIASUBTYPE_RGB565" },
663 {MEDIASUBTYPE_RGB24 , "MEDIASUBTYPE_RGB24" },
664 {MEDIASUBTYPE_RGB32 , "MEDIASUBTYPE_RGB32" },
665 {MEDIASUBTYPE_ARGB1555 , "MEDIASUBTYPE_ARGB1555" },
666 {MEDIASUBTYPE_ARGB32 , "MEDIASUBTYPE_ARGB32" },
667 {MEDIASUBTYPE_ARGB4444 , "MEDIASUBTYPE_ARGB4444" },
668 {MEDIASUBTYPE_A2R10G10B10, "MEDIASUBTYPE_A2R10G10B10"},
669 {MEDIASUBTYPE_A2B10G10R10, "MEDIASUBTYPE_A2B10G10R10"},
670 {MEDIASUBTYPE_AYUV , "MEDIASUBTYPE_AYUV" },
671 {MEDIASUBTYPE_YUY2 , "MEDIASUBTYPE_YUY2" },
672 {MEDIASUBTYPE_UYVY , "MEDIASUBTYPE_UYVY" },
673 {MEDIASUBTYPE_IMC1 , "MEDIASUBTYPE_IMC1" },
674 {MEDIASUBTYPE_IMC3 , "MEDIASUBTYPE_IMC3" },
675 {MEDIASUBTYPE_IMC2 , "MEDIASUBTYPE_IMC2" },
676 {MEDIASUBTYPE_IMC4 , "MEDIASUBTYPE_IMC4" },
677 {MEDIASUBTYPE_YV12 , "MEDIASUBTYPE_YV12" },
678 {MEDIASUBTYPE_NV12 , "MEDIASUBTYPE_NV12" },
679 {MEDIASUBTYPE_IF09 , "MEDIASUBTYPE_IF09" },
680 {MEDIASUBTYPE_IYUV , "MEDIASUBTYPE_IYUV" },
681 {MEDIASUBTYPE_Y211 , "MEDIASUBTYPE_Y211" },
682 {MEDIASUBTYPE_Y411 , "MEDIASUBTYPE_Y411" },
683 {MEDIASUBTYPE_Y41P , "MEDIASUBTYPE_Y41P" },
684 {MEDIASUBTYPE_YVU9 , "MEDIASUBTYPE_YVU9" },
685 {MEDIASUBTYPE_YVYU , "MEDIASUBTYPE_YVYU" }
686 };
687
688 for (auto &mediaType: mstToStr)
689 if (IsEqualGUID(mediaType.first, subType))
690 return mediaType.second;
691
692 return stringFromIid(subType);
693 }
694
stringFromFormatType(const GUID & formatType)695 std::string AkVCam::stringFromFormatType(const GUID &formatType)
696 {
697 static const std::map<GUID, std::string> ftToStr {
698 {GUID_NULL , "GUID_NULL" },
699 {FORMAT_DvInfo , "FORMAT_DvInfo" },
700 {FORMAT_MPEG2Video , "FORMAT_MPEG2Video" },
701 {FORMAT_MPEGStreams , "FORMAT_MPEGStreams" },
702 {FORMAT_MPEGVideo , "FORMAT_MPEGVideo" },
703 {FORMAT_None , "FORMAT_None" },
704 {FORMAT_VideoInfo , "FORMAT_VideoInfo" },
705 {FORMAT_VideoInfo2 , "FORMAT_VideoInfo2" },
706 {FORMAT_WaveFormatEx, "FORMAT_WaveFormatEx"}
707 };
708
709 for (auto &mediaType: ftToStr)
710 if (IsEqualGUID(mediaType.first, formatType))
711 return mediaType.second;
712
713 return stringFromIid(formatType);
714 }
715
stringFromMediaType(const AM_MEDIA_TYPE * mediaType)716 std::string AkVCam::stringFromMediaType(const AM_MEDIA_TYPE *mediaType)
717 {
718 if (!mediaType)
719 return std::string("MediaType(NULL)");
720
721 std::stringstream ss;
722 ss << "MediaType("
723 << stringFromMajorType(mediaType->majortype)
724 << ", "
725 << stringFromSubType(mediaType->subtype)
726 << ", "
727 << stringFromFormatType(mediaType->formattype);
728
729 if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo)) {
730 auto format = reinterpret_cast<VIDEOINFOHEADER *>(mediaType->pbFormat);
731 ss << ", "
732 << format->bmiHeader.biWidth
733 << ", "
734 << format->bmiHeader.biHeight;
735 } else if (IsEqualGUID(mediaType->formattype, FORMAT_VideoInfo2)) {
736 auto format = reinterpret_cast<VIDEOINFOHEADER2 *>(mediaType->pbFormat);
737 ss << ", "
738 << format->bmiHeader.biWidth
739 << ", "
740 << format->bmiHeader.biHeight;
741 }
742
743 ss << ")";
744
745 return ss.str();
746 }
747
stringFromMediaSample(IMediaSample * mediaSample)748 std::string AkVCam::stringFromMediaSample(IMediaSample *mediaSample)
749 {
750 if (!mediaSample)
751 return std::string("MediaSample(NULL)");
752
753 BYTE *buffer = nullptr;
754 mediaSample->GetPointer(&buffer);
755 auto bufferSize = mediaSample->GetSize();
756 AM_MEDIA_TYPE *mediaType = nullptr;
757 mediaSample->GetMediaType(&mediaType);
758 REFERENCE_TIME timeStart = 0;
759 REFERENCE_TIME timeEnd = 0;
760 mediaSample->GetTime(&timeStart, &timeEnd);
761 REFERENCE_TIME mediaTimeStart = 0;
762 REFERENCE_TIME mediaTimeEnd = 0;
763 mediaSample->GetMediaTime(&mediaTimeStart, &mediaTimeEnd);
764 auto discontinuity = mediaSample->IsDiscontinuity() == S_OK;
765 auto preroll = mediaSample->IsPreroll() == S_OK;
766 auto syncPoint = mediaSample->IsSyncPoint() == S_OK;
767 auto dataLength = mediaSample->GetActualDataLength();
768
769 std::stringstream ss;
770 ss << "MediaSample(" << std::endl
771 << " Buffer: " << size_t(buffer) << std::endl
772 << " Buffer Size: " << bufferSize << std::endl
773 << " Media Type: " << stringFromMediaType(mediaType) << std::endl
774 << " Time: (" << timeStart << ", " << timeEnd << ")" << std::endl
775 << " Media Time: (" << mediaTimeStart << ", " << mediaTimeEnd << ")" << std::endl
776 << " Discontinuity: " << discontinuity << std::endl
777 << " Preroll: " << preroll << std::endl
778 << " Sync Point: " << syncPoint << std::endl
779 << " Data Length: " << dataLength << std::endl
780 << ")";
781
782 deleteMediaType(&mediaType);
783
784 return ss.str();
785 }
786
regGetValue(HKEY hkey,LPCWSTR lpSubKey,LPCWSTR lpValue,DWORD dwFlags,LPDWORD pdwType,PVOID pvData,LPDWORD pcbData)787 LONG AkVCam::regGetValue(HKEY hkey,
788 LPCWSTR lpSubKey,
789 LPCWSTR lpValue,
790 DWORD dwFlags,
791 LPDWORD pdwType,
792 PVOID pvData,
793 LPDWORD pcbData)
794 {
795 HKEY key = nullptr;
796 auto result = RegOpenKeyEx(hkey,
797 lpSubKey,
798 0,
799 KEY_READ | KEY_WOW64_64KEY,
800 &key);
801
802 if (result != ERROR_SUCCESS)
803 return result;
804
805 result = RegGetValue(key,
806 nullptr,
807 lpValue,
808 dwFlags,
809 pdwType,
810 pvData,
811 pcbData);
812 RegCloseKey(key);
813
814 return result;
815 }
816
listRegisteredCameras(HINSTANCE hinstDLL)817 std::vector<CLSID> AkVCam::listRegisteredCameras(HINSTANCE hinstDLL)
818 {
819 WCHAR *strIID = nullptr;
820 StringFromIID(CLSID_VideoInputDeviceCategory, &strIID);
821
822 std::wstringstream ss;
823 ss << L"CLSID\\"
824 << strIID
825 << L"\\Instance";
826 CoTaskMemFree(strIID);
827
828 HKEY key = nullptr;
829 auto result = RegOpenKeyEx(HKEY_CLASSES_ROOT,
830 ss.str().c_str(),
831 0,
832 MAXIMUM_ALLOWED,
833 &key);
834
835 if (result != ERROR_SUCCESS)
836 return {};
837
838 DWORD subkeys = 0;
839
840 result = RegQueryInfoKey(key,
841 nullptr,
842 nullptr,
843 nullptr,
844 &subkeys,
845 nullptr,
846 nullptr,
847 nullptr,
848 nullptr,
849 nullptr,
850 nullptr,
851 nullptr);
852
853 if (result != ERROR_SUCCESS) {
854 RegCloseKey(key);
855
856 return {};
857 }
858
859 std::vector<CLSID> cameras;
860 FILETIME lastWrite;
861
862 for (DWORD i = 0; i < subkeys; i++) {
863 TCHAR subKey[MAX_PATH];
864 memset(subKey, 0, MAX_PATH * sizeof(TCHAR));
865 DWORD subKeyLen = MAX_PATH;
866 result = RegEnumKeyEx(key,
867 i,
868 subKey,
869 &subKeyLen,
870 nullptr,
871 nullptr,
872 nullptr,
873 &lastWrite);
874
875 if (result != ERROR_SUCCESS)
876 continue;
877
878 std::wstringstream ss;
879 ss << L"CLSID\\" << subKey << L"\\InprocServer32";
880 WCHAR path[MAX_PATH];
881 memset(path, 0, MAX_PATH * sizeof(WCHAR));
882 DWORD pathSize = MAX_PATH;
883
884 if (RegGetValue(HKEY_CLASSES_ROOT,
885 ss.str().c_str(),
886 nullptr,
887 RRF_RT_REG_SZ,
888 nullptr,
889 path,
890 &pathSize) == ERROR_SUCCESS) {
891 WCHAR modulePath[MAX_PATH];
892 memset(modulePath, 0, MAX_PATH * sizeof(WCHAR));
893 GetModuleFileName(hinstDLL, modulePath, MAX_PATH);
894
895 if (!lstrcmpi(path, modulePath)) {
896 CLSID clsid;
897 memset(&clsid, 0, sizeof(CLSID));
898 CLSIDFromString(subKey, &clsid);
899 cameras.push_back(clsid);
900 }
901 }
902 }
903
904 RegCloseKey(key);
905
906 return cameras;
907 }
908
camerasCount()909 DWORD AkVCam::camerasCount()
910 {
911 DWORD nCameras = 0;
912 DWORD nCamerasSize = sizeof(DWORD);
913
914 regGetValue(HKEY_LOCAL_MACHINE,
915 L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras",
916 L"size",
917 RRF_RT_REG_DWORD,
918 nullptr,
919 &nCameras,
920 &nCamerasSize);
921
922 return nCameras;
923 }
924
createDevicePath()925 std::wstring AkVCam::createDevicePath()
926 {
927 // List device paths in use.
928 std::vector<std::wstring> cameraPaths;
929
930 for (DWORD i = 0; i < camerasCount(); i++)
931 cameraPaths.push_back(cameraPath(i));
932
933 const int maxId = 64;
934
935 for (int i = 0; i < maxId; i++) {
936 /* There are no rules for device paths in Windows. Just append an
937 * incremental index to a common prefix.
938 */
939 auto path = DSHOW_PLUGIN_DEVICE_PREFIX_L + std::to_wstring(i);
940
941 // Check if the path is being used, if not return it.
942 if (std::find(cameraPaths.begin(),
943 cameraPaths.end(),
944 path) == cameraPaths.end())
945 return path;
946 }
947
948 return {};
949 }
950
cameraFromId(const std::wstring & path)951 int AkVCam::cameraFromId(const std::wstring &path)
952 {
953 auto clsid = createClsidFromStr(path);
954
955 return cameraFromId(clsid);
956 }
957
cameraFromId(const CLSID & clsid)958 int AkVCam::cameraFromId(const CLSID &clsid)
959 {
960 for (DWORD i = 0; i < camerasCount(); i++) {
961 auto cameraClsid = createClsidFromStr(cameraPath(i));
962
963 if (IsEqualCLSID(cameraClsid, clsid) && !cameraFormats(i).empty())
964 return int(i);
965 }
966
967 return -1;
968 }
969
cameraExists(const std::string & path)970 bool AkVCam::cameraExists(const std::string &path)
971 {
972 return cameraExists(std::wstring(path.begin(), path.end()));
973 }
974
cameraExists(const std::wstring & path)975 bool AkVCam::cameraExists(const std::wstring &path)
976 {
977 for (DWORD i = 0; i < camerasCount(); i++)
978 if (cameraPath(i) == path)
979 return true;
980
981 return false;
982 }
983
cameraDescription(DWORD cameraIndex)984 std::wstring AkVCam::cameraDescription(DWORD cameraIndex)
985 {
986 std::wstringstream ss;
987 ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\"
988 << cameraIndex + 1;
989
990 WCHAR description[1024];
991 DWORD descriptionSize = 1024 * sizeof(WCHAR);
992 memset(description, 0, descriptionSize);
993
994 if (regGetValue(HKEY_LOCAL_MACHINE,
995 ss.str().c_str(),
996 L"description",
997 RRF_RT_REG_SZ,
998 nullptr,
999 &description,
1000 &descriptionSize) != ERROR_SUCCESS)
1001 return std::wstring();
1002
1003 return std::wstring(description);
1004 }
1005
cameraPath(DWORD cameraIndex)1006 std::wstring AkVCam::cameraPath(DWORD cameraIndex)
1007 {
1008 std::wstringstream ss;
1009 ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\"
1010 << cameraIndex + 1;
1011
1012 WCHAR path[1024];
1013 DWORD pathSize = 1024 * sizeof(WCHAR);
1014 memset(path, 0, pathSize);
1015
1016 if (regGetValue(HKEY_LOCAL_MACHINE,
1017 ss.str().c_str(),
1018 L"path",
1019 RRF_RT_REG_SZ,
1020 nullptr,
1021 &path,
1022 &pathSize) != ERROR_SUCCESS)
1023 return std::wstring();
1024
1025 return std::wstring(path);
1026 }
1027
cameraPath(const CLSID & clsid)1028 std::wstring AkVCam::cameraPath(const CLSID &clsid)
1029 {
1030 auto camera = cameraFromId(clsid);
1031
1032 if (camera < 0)
1033 return {};
1034
1035 return cameraPath(DWORD(camera));
1036 }
1037
formatsCount(DWORD cameraIndex)1038 DWORD AkVCam::formatsCount(DWORD cameraIndex)
1039 {
1040 std::wstringstream ss;
1041 ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\"
1042 << cameraIndex + 1
1043 << L"\\Formats";
1044
1045 DWORD nFormats;
1046 DWORD nFormatsSize = sizeof(DWORD);
1047 memset(&nFormats, 0, nFormatsSize);
1048
1049 regGetValue(HKEY_LOCAL_MACHINE,
1050 ss.str().c_str(),
1051 L"size",
1052 RRF_RT_REG_DWORD,
1053 nullptr,
1054 &nFormats,
1055 &nFormatsSize);
1056
1057 return nFormats;
1058 }
1059
cameraFormat(DWORD cameraIndex,DWORD formatIndex)1060 AkVCam::VideoFormat AkVCam::cameraFormat(DWORD cameraIndex, DWORD formatIndex)
1061 {
1062 std::wstringstream ss;
1063 ss << L"SOFTWARE\\Webcamoid\\VirtualCamera\\Cameras\\"
1064 << cameraIndex + 1
1065 << L"\\Formats\\"
1066 << formatIndex + 1;
1067
1068 WCHAR formatStr[1024];
1069 DWORD variableSize = 1024 * sizeof(WCHAR);
1070 memset(formatStr, 0, variableSize);
1071
1072 if (regGetValue(HKEY_LOCAL_MACHINE,
1073 ss.str().c_str(),
1074 L"format",
1075 RRF_RT_REG_SZ,
1076 nullptr,
1077 &formatStr,
1078 &variableSize) != ERROR_SUCCESS)
1079 return {};
1080
1081 DWORD width = 0;
1082 variableSize = sizeof(DWORD);
1083
1084 if (regGetValue(HKEY_LOCAL_MACHINE,
1085 ss.str().c_str(),
1086 L"width",
1087 RRF_RT_REG_DWORD,
1088 nullptr,
1089 &width,
1090 &variableSize) != ERROR_SUCCESS)
1091 return {};
1092
1093 DWORD height = 0;
1094 variableSize = sizeof(DWORD);
1095
1096 if (regGetValue(HKEY_LOCAL_MACHINE,
1097 ss.str().c_str(),
1098 L"height",
1099 RRF_RT_REG_DWORD,
1100 nullptr,
1101 &height,
1102 &variableSize) != ERROR_SUCCESS)
1103 return {};
1104
1105 WCHAR fpsStr[1024];
1106 variableSize = 1024 * sizeof(WCHAR);
1107 memset(fpsStr, 0, variableSize);
1108
1109 if (regGetValue(HKEY_LOCAL_MACHINE,
1110 ss.str().c_str(),
1111 L"fps",
1112 RRF_RT_REG_SZ,
1113 nullptr,
1114 &fpsStr,
1115 &variableSize) != ERROR_SUCCESS)
1116 return {};
1117
1118 std::wstring format(formatStr);
1119 auto fourcc = VideoFormat::fourccFromString(std::string(format.begin(),
1120 format.end()));
1121
1122 return VideoFormat(fourcc,
1123 int(width),
1124 int(height),
1125 {Fraction(fpsStr)});
1126 }
1127
cameraFormats(DWORD cameraIndex)1128 std::vector<AkVCam::VideoFormat> AkVCam::cameraFormats(DWORD cameraIndex)
1129 {
1130 std::vector<AkVCam::VideoFormat> formats;
1131
1132 for (DWORD i = 0; i < formatsCount(cameraIndex); i++) {
1133 auto videoFormat = cameraFormat(cameraIndex, i);
1134
1135 if (videoFormat)
1136 formats.push_back(videoFormat);
1137 }
1138
1139 return formats;
1140 }
1141