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