1 /*
2     Copyright © 2010 Ramiro Polla
3     Copyright © 2015-2019 by The qTox Project Contributors
4 
5     This file is part of qTox, a Qt-based graphical interface for Tox.
6 
7     qTox is libre software: you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation, either version 3 of the License, or
10     (at your option) any later version.
11 
12     qTox is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 
22 #include "directshow.h"
23 
24 // Because of replacing to incorrect order, which leads to building failing,
25 // this region is ignored for clang-format
26 // clang-format off
27 #include <cstdint>
28 #include <objbase.h>
29 #include <strmif.h>
30 #include <amvideo.h>
31 #include <dvdmedia.h>
32 #include <uuids.h>
33 #include <cassert>
34 #include <QDebug>
35 // clang-format on
36 
37 /**
38  * Most of this file is adapted from libavdevice's dshow.c,
39  * which retrieves useful information but only exposes it to
40  * stdout and is not part of the public API for some reason.
41  */
42 
wcharToUtf8(wchar_t * w)43 static char* wcharToUtf8(wchar_t* w)
44 {
45     int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
46     char* s = new char[l];
47     if (s)
48         WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
49     return s;
50 }
51 
getDeviceList()52 QVector<QPair<QString, QString>> DirectShow::getDeviceList()
53 {
54     IMoniker* m = nullptr;
55     QVector<QPair<QString, QString>> devices;
56 
57     ICreateDevEnum* devenum = nullptr;
58     if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
59                          (void**)&devenum)
60         != S_OK)
61         return devices;
62 
63     IEnumMoniker* classenum = nullptr;
64     if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0)
65         != S_OK)
66         return devices;
67 
68     while (classenum->Next(1, &m, nullptr) == S_OK) {
69         VARIANT var;
70         IPropertyBag* bag = nullptr;
71         LPMALLOC coMalloc = nullptr;
72         IBindCtx* bindCtx = nullptr;
73         LPOLESTR olestr = nullptr;
74         char *devIdString = nullptr, *devHumanName = nullptr;
75 
76         if (CoGetMalloc(1, &coMalloc) != S_OK)
77             goto fail;
78         if (CreateBindCtx(0, &bindCtx) != S_OK)
79             goto fail;
80 
81         // Get an uuid for the device that we can pass to ffmpeg directly
82         if (m->GetDisplayName(bindCtx, nullptr, &olestr) != S_OK)
83             goto fail;
84         devIdString = wcharToUtf8(olestr);
85 
86         // replace ':' with '_' since FFmpeg uses : to delimitate sources
87         for (size_t i = 0; i < strlen(devIdString); ++i)
88             if (devIdString[i] == ':')
89                 devIdString[i] = '_';
90 
91         // Get a human friendly name/description
92         if (m->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void**)&bag) != S_OK)
93             goto fail;
94 
95         var.vt = VT_BSTR;
96         if (bag->Read(L"FriendlyName", &var, nullptr) != S_OK)
97             goto fail;
98         devHumanName = wcharToUtf8(var.bstrVal);
99 
100         devices += {QString("video=") + devIdString, devHumanName};
101 
102     fail:
103         if (olestr && coMalloc)
104             coMalloc->Free(olestr);
105         if (bindCtx)
106             bindCtx->Release();
107         delete[] devIdString;
108         delete[] devHumanName;
109         if (bag)
110             bag->Release();
111         m->Release();
112     }
113     classenum->Release();
114 
115     return devices;
116 }
117 
118 // Used (by getDeviceModes) to select a device
119 // so we can list its properties
getDevFilter(QString devName)120 static IBaseFilter* getDevFilter(QString devName)
121 {
122     IBaseFilter* devFilter = nullptr;
123     devName = devName.mid(6); // Remove the "video="
124     IMoniker* m = nullptr;
125 
126     ICreateDevEnum* devenum = nullptr;
127     if (CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
128                          (void**)&devenum)
129         != S_OK)
130         return devFilter;
131 
132     IEnumMoniker* classenum = nullptr;
133     if (devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, (IEnumMoniker**)&classenum, 0)
134         != S_OK)
135         return devFilter;
136 
137     while (classenum->Next(1, &m, nullptr) == S_OK) {
138         LPMALLOC coMalloc = nullptr;
139         IBindCtx* bindCtx = nullptr;
140         LPOLESTR olestr = nullptr;
141         char* devIdString;
142 
143         if (CoGetMalloc(1, &coMalloc) != S_OK)
144             goto fail;
145         if (CreateBindCtx(0, &bindCtx) != S_OK)
146             goto fail;
147 
148         if (m->GetDisplayName(bindCtx, nullptr, &olestr) != S_OK)
149             goto fail;
150         devIdString = wcharToUtf8(olestr);
151 
152         // replace ':' with '_' since FFmpeg uses : to delimitate sources
153         for (size_t i = 0; i < strlen(devIdString); ++i)
154             if (devIdString[i] == ':')
155                 devIdString[i] = '_';
156 
157         if (devName != devIdString)
158             goto fail;
159 
160         if (m->BindToObject(0, 0, IID_IBaseFilter, (void**)&devFilter) != S_OK)
161             goto fail;
162 
163     fail:
164         if (olestr && coMalloc)
165             coMalloc->Free(olestr);
166         if (bindCtx)
167             bindCtx->Release();
168         delete[] devIdString;
169         m->Release();
170     }
171     classenum->Release();
172 
173     if (!devFilter)
174         qWarning() << "Could't find the device " << devName;
175 
176     return devFilter;
177 }
178 
getDeviceModes(QString devName)179 QVector<VideoMode> DirectShow::getDeviceModes(QString devName)
180 {
181     QVector<VideoMode> modes;
182 
183     IBaseFilter* devFilter = getDevFilter(devName);
184     if (!devFilter)
185         return modes;
186 
187     // The outter loop tries to find a valid output pin
188     GUID category;
189     DWORD r2;
190     IEnumPins* pins = nullptr;
191     IPin* pin;
192     if (devFilter->EnumPins(&pins) != S_OK)
193         return modes;
194 
195     while (pins->Next(1, &pin, nullptr) == S_OK) {
196         IKsPropertySet* p = nullptr;
197         PIN_INFO info;
198 
199         pin->QueryPinInfo(&info);
200         info.pFilter->Release();
201         if (info.dir != PINDIR_OUTPUT)
202             goto next;
203         if (pin->QueryInterface(IID_IKsPropertySet, (void**)&p) != S_OK)
204             goto next;
205         if (p->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, nullptr, 0, &category, sizeof(GUID), &r2)
206             != S_OK)
207             goto next;
208         if (!IsEqualGUID(category, PIN_CATEGORY_CAPTURE))
209             goto next;
210 
211         // Now we can list the video modes for the current pin
212         // Prepare for another wall of spaghetti DIRECT SHOW QUALITY code
213         {
214             IAMStreamConfig* config = nullptr;
215             VIDEO_STREAM_CONFIG_CAPS* vcaps = nullptr;
216             int size, n;
217             if (pin->QueryInterface(IID_IAMStreamConfig, (void**)&config) != S_OK)
218                 goto next;
219             if (config->GetNumberOfCapabilities(&n, &size) != S_OK)
220                 goto pinend;
221 
222             assert(size == sizeof(VIDEO_STREAM_CONFIG_CAPS));
223             vcaps = new VIDEO_STREAM_CONFIG_CAPS;
224 
225             for (int i = 0; i < n; ++i) {
226                 AM_MEDIA_TYPE* type = nullptr;
227                 VideoMode mode;
228                 if (config->GetStreamCaps(i, &type, (BYTE*)vcaps) != S_OK)
229                     goto nextformat;
230 
231                 if (!IsEqualGUID(type->formattype, FORMAT_VideoInfo)
232                     && !IsEqualGUID(type->formattype, FORMAT_VideoInfo2))
233                     goto nextformat;
234 
235                 mode.width = vcaps->MaxOutputSize.cx;
236                 mode.height = vcaps->MaxOutputSize.cy;
237                 mode.FPS = 1e7 / vcaps->MinFrameInterval;
238                 if (!modes.contains(mode))
239                     modes.append(std::move(mode));
240 
241             nextformat:
242                 if (type->pbFormat)
243                     CoTaskMemFree(type->pbFormat);
244                 CoTaskMemFree(type);
245             }
246         pinend:
247             config->Release();
248             delete vcaps;
249         }
250     next:
251         if (p)
252             p->Release();
253         pin->Release();
254     }
255 
256     return modes;
257 }
258