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