blob: 5018f520e6d890f6893f3f81bfba611e9d0efb7d [file] [log] [blame]
niklase@google.com470e71d2011-07-07 08:21:25 +00001/*
xians@webrtc.org843c8c72012-02-20 08:45:02 +00002 * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
niklase@google.com470e71d2011-07-07 08:21:25 +00003 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "modules/video_capture/windows/device_info_ds.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000012
Jerome Humbert39bab5a2019-10-28 18:12:51 +000013#include <assert.h>
Jonas Olssona4d87372019-07-05 19:08:33 +020014#include <dvdmedia.h>
15
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020016#include "modules/video_capture/video_capture_config.h"
17#include "modules/video_capture/windows/help_functions_ds.h"
18#include "rtc_base/logging.h"
Steve Anton10542f22019-01-11 09:11:00 -080019#include "rtc_base/string_utils.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000020
Mirko Bonadei72c42502017-11-09 09:33:23 +010021namespace webrtc {
22namespace videocapturemodule {
tommi@webrtc.org951b6c42012-11-20 12:17:05 +000023
tommi@webrtc.org81878772012-11-20 13:35:33 +000024// static
Mirko Bonadei72c42502017-11-09 09:33:23 +010025DeviceInfoDS* DeviceInfoDS::Create() {
26 DeviceInfoDS* dsInfo = new DeviceInfoDS();
27 if (!dsInfo || dsInfo->Init() != 0) {
28 delete dsInfo;
29 dsInfo = NULL;
30 }
31 return dsInfo;
niklase@google.com470e71d2011-07-07 08:21:25 +000032}
33
nisseb29b9c82016-12-12 00:22:56 -080034DeviceInfoDS::DeviceInfoDS()
Mirko Bonadei72c42502017-11-09 09:33:23 +010035 : _dsDevEnum(NULL),
36 _dsMonikerDevEnum(NULL),
37 _CoUninitializeIsRequired(true) {
38 // 1) Initialize the COM library (make Windows load the DLLs).
39 //
40 // CoInitializeEx must be called at least once, and is usually called only
41 // once, for each thread that uses the COM library. Multiple calls to
42 // CoInitializeEx by the same thread are allowed as long as they pass the same
43 // concurrency flag, but subsequent valid calls return S_FALSE. To close the
44 // COM library gracefully on a thread, each successful call to CoInitializeEx,
45 // including any call that returns S_FALSE, must be balanced by a
46 // corresponding call to CoUninitialize.
47 //
niklase@google.com470e71d2011-07-07 08:21:25 +000048
Mirko Bonadei72c42502017-11-09 09:33:23 +010049 /*Apartment-threading, while allowing for multiple threads of execution,
50 serializes all incoming calls by requiring that calls to methods of objects
51 created by this thread always run on the same thread the apartment/thread
52 that created them. In addition, calls can arrive only at message-queue
53 boundaries (i.e., only during a PeekMessage, SendMessage, DispatchMessage,
54 etc.). Because of this serialization, it is not typically necessary to write
55 concurrency control into the code for the object, other than to avoid calls
56 to PeekMessage and SendMessage during processing that must not be interrupted
57 by other method invocations or calls to other objects in the same
58 apartment/thread.*/
niklase@google.com470e71d2011-07-07 08:21:25 +000059
Mirko Bonadei72c42502017-11-09 09:33:23 +010060 /// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED ); //|
61 /// COINIT_SPEED_OVER_MEMORY
62 HRESULT hr = CoInitializeEx(
63 NULL, COINIT_MULTITHREADED); // Use COINIT_MULTITHREADED since Voice
64 // Engine uses COINIT_MULTITHREADED
65 if (FAILED(hr)) {
66 // Avoid calling CoUninitialize() since CoInitializeEx() failed.
67 _CoUninitializeIsRequired = FALSE;
niklase@google.com470e71d2011-07-07 08:21:25 +000068
Mirko Bonadei72c42502017-11-09 09:33:23 +010069 if (hr == RPC_E_CHANGED_MODE) {
70 // Calling thread has already initialized COM to be used in a
71 // single-threaded apartment (STA). We are then prevented from using STA.
72 // Details: hr = 0x80010106 <=> "Cannot change thread mode after it is
73 // set".
74 //
Mirko Bonadei675513b2017-11-09 11:09:25 +010075 RTC_LOG(LS_INFO) << __FUNCTION__
76 << ": CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)"
Karl Wiberg43432732018-05-23 11:13:31 +020077 << " => RPC_E_CHANGED_MODE, error 0x" << rtc::ToHex(hr);
niklase@google.com470e71d2011-07-07 08:21:25 +000078 }
Mirko Bonadei72c42502017-11-09 09:33:23 +010079 }
niklase@google.com470e71d2011-07-07 08:21:25 +000080}
81
Mirko Bonadei72c42502017-11-09 09:33:23 +010082DeviceInfoDS::~DeviceInfoDS() {
83 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
84 RELEASE_AND_CLEAR(_dsDevEnum);
85 if (_CoUninitializeIsRequired) {
86 CoUninitialize();
87 }
niklase@google.com470e71d2011-07-07 08:21:25 +000088}
89
Mirko Bonadei72c42502017-11-09 09:33:23 +010090int32_t DeviceInfoDS::Init() {
91 HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
92 IID_ICreateDevEnum, (void**)&_dsDevEnum);
93 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +010094 RTC_LOG(LS_INFO) << "Failed to create CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +020095 << rtc::ToHex(hr);
Mirko Bonadei72c42502017-11-09 09:33:23 +010096 return -1;
97 }
98 return 0;
99}
100uint32_t DeviceInfoDS::NumberOfDevices() {
101 ReadLockScoped cs(_apiLock);
102 return GetDeviceInfo(0, 0, 0, 0, 0, 0, 0);
103}
104
105int32_t DeviceInfoDS::GetDeviceName(uint32_t deviceNumber,
106 char* deviceNameUTF8,
107 uint32_t deviceNameLength,
108 char* deviceUniqueIdUTF8,
109 uint32_t deviceUniqueIdUTF8Length,
110 char* productUniqueIdUTF8,
111 uint32_t productUniqueIdUTF8Length) {
112 ReadLockScoped cs(_apiLock);
113 const int32_t result = GetDeviceInfo(
114 deviceNumber, deviceNameUTF8, deviceNameLength, deviceUniqueIdUTF8,
115 deviceUniqueIdUTF8Length, productUniqueIdUTF8, productUniqueIdUTF8Length);
116 return result > (int32_t)deviceNumber ? 0 : -1;
117}
118
119int32_t DeviceInfoDS::GetDeviceInfo(uint32_t deviceNumber,
120 char* deviceNameUTF8,
121 uint32_t deviceNameLength,
122 char* deviceUniqueIdUTF8,
123 uint32_t deviceUniqueIdUTF8Length,
124 char* productUniqueIdUTF8,
125 uint32_t productUniqueIdUTF8Length)
126
niklase@google.com470e71d2011-07-07 08:21:25 +0000127{
Mirko Bonadei72c42502017-11-09 09:33:23 +0100128 // enumerate all video capture devices
129 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
130 HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
131 &_dsMonikerDevEnum, 0);
132 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100133 RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +0200134 << rtc::ToHex(hr) << ". No webcam exist?";
niklase@google.com470e71d2011-07-07 08:21:25 +0000135 return 0;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100136 }
tommi@webrtc.org81878772012-11-20 13:35:33 +0000137
Mirko Bonadei72c42502017-11-09 09:33:23 +0100138 _dsMonikerDevEnum->Reset();
139 ULONG cFetched;
140 IMoniker* pM;
141 int index = 0;
142 while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched)) {
143 IPropertyBag* pBag;
144 hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag);
145 if (S_OK == hr) {
146 // Find the description or friendly name.
147 VARIANT varName;
148 VariantInit(&varName);
149 hr = pBag->Read(L"Description", &varName, 0);
150 if (FAILED(hr)) {
151 hr = pBag->Read(L"FriendlyName", &varName, 0);
152 }
153 if (SUCCEEDED(hr)) {
154 // ignore all VFW drivers
155 if ((wcsstr(varName.bstrVal, (L"(VFW)")) == NULL) &&
156 (_wcsnicmp(varName.bstrVal, (L"Google Camera Adapter"), 21) != 0)) {
157 // Found a valid device.
158 if (index == static_cast<int>(deviceNumber)) {
159 int convResult = 0;
160 if (deviceNameLength > 0) {
161 convResult = WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1,
162 (char*)deviceNameUTF8,
163 deviceNameLength, NULL, NULL);
164 if (convResult == 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100165 RTC_LOG(LS_INFO) << "Failed to convert device name to UTF8, "
166 << "error = " << GetLastError();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100167 return -1;
168 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000169 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100170 if (deviceUniqueIdUTF8Length > 0) {
171 hr = pBag->Read(L"DevicePath", &varName, 0);
172 if (FAILED(hr)) {
173 strncpy_s((char*)deviceUniqueIdUTF8, deviceUniqueIdUTF8Length,
174 (char*)deviceNameUTF8, convResult);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100175 RTC_LOG(LS_INFO) << "Failed to get "
176 << "deviceUniqueIdUTF8 using "
177 << "deviceNameUTF8";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100178 } else {
179 convResult = WideCharToMultiByte(
180 CP_UTF8, 0, varName.bstrVal, -1, (char*)deviceUniqueIdUTF8,
181 deviceUniqueIdUTF8Length, NULL, NULL);
182 if (convResult == 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100183 RTC_LOG(LS_INFO)
184 << "Failed to convert device "
185 << "name to UTF8, error = " << GetLastError();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100186 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000187 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100188 if (productUniqueIdUTF8 && productUniqueIdUTF8Length > 0) {
189 GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8,
190 productUniqueIdUTF8Length);
191 }
192 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000193 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100194 }
195 ++index; // increase the number of valid devices
niklase@google.com470e71d2011-07-07 08:21:25 +0000196 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100197 }
198 VariantClear(&varName);
199 pBag->Release();
200 pM->Release();
niklase@google.com470e71d2011-07-07 08:21:25 +0000201 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100202 }
203 if (deviceNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100204 RTC_LOG(LS_INFO) << __FUNCTION__ << " " << deviceNameUTF8;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100205 }
206 return index;
niklase@google.com470e71d2011-07-07 08:21:25 +0000207}
208
Mirko Bonadei72c42502017-11-09 09:33:23 +0100209IBaseFilter* DeviceInfoDS::GetDeviceFilter(const char* deviceUniqueIdUTF8,
210 char* productUniqueIdUTF8,
211 uint32_t productUniqueIdUTF8Length) {
212 const int32_t deviceUniqueIdUTF8Length = (int32_t)strlen(
213 (char*)deviceUniqueIdUTF8); // UTF8 is also NULL terminated
214 if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100215 RTC_LOG(LS_INFO) << "Device name too long";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100216 return NULL;
217 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000218
Mirko Bonadei72c42502017-11-09 09:33:23 +0100219 // enumerate all video capture devices
220 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
221 HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
222 &_dsMonikerDevEnum, 0);
223 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100224 RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +0200225 << rtc::ToHex(hr) << ". No webcam exist?";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100226 return 0;
227 }
228 _dsMonikerDevEnum->Reset();
229 ULONG cFetched;
230 IMoniker* pM;
niklase@google.com470e71d2011-07-07 08:21:25 +0000231
Mirko Bonadei72c42502017-11-09 09:33:23 +0100232 IBaseFilter* captureFilter = NULL;
233 bool deviceFound = false;
234 while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched) && !deviceFound) {
235 IPropertyBag* pBag;
236 hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag);
237 if (S_OK == hr) {
238 // Find the description or friendly name.
239 VARIANT varName;
240 VariantInit(&varName);
241 if (deviceUniqueIdUTF8Length > 0) {
242 hr = pBag->Read(L"DevicePath", &varName, 0);
243 if (FAILED(hr)) {
244 hr = pBag->Read(L"Description", &varName, 0);
245 if (FAILED(hr)) {
246 hr = pBag->Read(L"FriendlyName", &varName, 0);
247 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000248 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100249 if (SUCCEEDED(hr)) {
250 char tempDevicePathUTF8[256];
251 tempDevicePathUTF8[0] = 0;
252 WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1,
253 tempDevicePathUTF8, sizeof(tempDevicePathUTF8),
254 NULL, NULL);
255 if (strncmp(tempDevicePathUTF8, (const char*)deviceUniqueIdUTF8,
256 deviceUniqueIdUTF8Length) == 0) {
257 // We have found the requested device
258 deviceFound = true;
259 hr =
260 pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&captureFilter);
261 if
262 FAILED(hr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100263 RTC_LOG(LS_ERROR) << "Failed to bind to the selected "
264 << "capture device " << hr;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100265 }
266
267 if (productUniqueIdUTF8 &&
268 productUniqueIdUTF8Length > 0) // Get the device name
269 {
270 GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8,
271 productUniqueIdUTF8Length);
272 }
273 }
274 }
275 }
276 VariantClear(&varName);
277 pBag->Release();
niklase@google.com470e71d2011-07-07 08:21:25 +0000278 }
Tim Haloun059daa42019-11-11 16:06:03 -0800279 pM->Release();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100280 }
281 return captureFilter;
niklase@google.com470e71d2011-07-07 08:21:25 +0000282}
283
pbos@webrtc.orgdfc5bb92013-04-10 08:23:13 +0000284int32_t DeviceInfoDS::GetWindowsCapability(
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000285 const int32_t capabilityIndex,
286 VideoCaptureCapabilityWindows& windowsCapability) {
287 ReadLockScoped cs(_apiLock);
niklase@google.com470e71d2011-07-07 08:21:25 +0000288
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000289 if (capabilityIndex < 0 || static_cast<size_t>(capabilityIndex) >=
290 _captureCapabilitiesWindows.size()) {
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000291 return -1;
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000292 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000293
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000294 windowsCapability = _captureCapabilitiesWindows[capabilityIndex];
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000295 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000296}
297
Mirko Bonadei72c42502017-11-09 09:33:23 +0100298int32_t DeviceInfoDS::CreateCapabilityMap(const char* deviceUniqueIdUTF8)
niklase@google.com470e71d2011-07-07 08:21:25 +0000299
300{
Mirko Bonadei72c42502017-11-09 09:33:23 +0100301 // Reset old capability list
302 _captureCapabilities.clear();
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000303
Mirko Bonadei72c42502017-11-09 09:33:23 +0100304 const int32_t deviceUniqueIdUTF8Length =
305 (int32_t)strlen((char*)deviceUniqueIdUTF8);
306 if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100307 RTC_LOG(LS_INFO) << "Device name too long";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100308 return -1;
309 }
Mirko Bonadei675513b2017-11-09 11:09:25 +0100310 RTC_LOG(LS_INFO) << "CreateCapabilityMap called for device "
311 << deviceUniqueIdUTF8;
niklase@google.com470e71d2011-07-07 08:21:25 +0000312
Mirko Bonadei72c42502017-11-09 09:33:23 +0100313 char productId[kVideoCaptureProductIdLength];
314 IBaseFilter* captureDevice = DeviceInfoDS::GetDeviceFilter(
315 deviceUniqueIdUTF8, productId, kVideoCaptureProductIdLength);
316 if (!captureDevice)
317 return -1;
318 IPin* outputCapturePin = GetOutputPin(captureDevice, GUID_NULL);
319 if (!outputCapturePin) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100320 RTC_LOG(LS_INFO) << "Failed to get capture device output pin";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100321 RELEASE_AND_CLEAR(captureDevice);
322 return -1;
323 }
324 IAMExtDevice* extDevice = NULL;
325 HRESULT hr =
326 captureDevice->QueryInterface(IID_IAMExtDevice, (void**)&extDevice);
327 if (SUCCEEDED(hr) && extDevice) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100328 RTC_LOG(LS_INFO) << "This is an external device";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100329 extDevice->Release();
330 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000331
Mirko Bonadei72c42502017-11-09 09:33:23 +0100332 IAMStreamConfig* streamConfig = NULL;
333 hr = outputCapturePin->QueryInterface(IID_IAMStreamConfig,
334 (void**)&streamConfig);
335 if (FAILED(hr)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100336 RTC_LOG(LS_INFO) << "Failed to get IID_IAMStreamConfig interface "
337 << "from capture device";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100338 return -1;
339 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000340
Mirko Bonadei72c42502017-11-09 09:33:23 +0100341 // this gets the FPS
342 IAMVideoControl* videoControlConfig = NULL;
343 HRESULT hrVC = captureDevice->QueryInterface(IID_IAMVideoControl,
344 (void**)&videoControlConfig);
345 if (FAILED(hrVC)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100346 RTC_LOG(LS_INFO) << "IID_IAMVideoControl Interface NOT SUPPORTED";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100347 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000348
Mirko Bonadei72c42502017-11-09 09:33:23 +0100349 AM_MEDIA_TYPE* pmt = NULL;
350 VIDEO_STREAM_CONFIG_CAPS caps;
351 int count, size;
niklase@google.com470e71d2011-07-07 08:21:25 +0000352
Mirko Bonadei72c42502017-11-09 09:33:23 +0100353 hr = streamConfig->GetNumberOfCapabilities(&count, &size);
354 if (FAILED(hr)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100355 RTC_LOG(LS_INFO) << "Failed to GetNumberOfCapabilities";
niklase@google.com470e71d2011-07-07 08:21:25 +0000356 RELEASE_AND_CLEAR(videoControlConfig);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100357 RELEASE_AND_CLEAR(streamConfig);
niklase@google.com470e71d2011-07-07 08:21:25 +0000358 RELEASE_AND_CLEAR(outputCapturePin);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100359 RELEASE_AND_CLEAR(captureDevice);
360 return -1;
361 }
henrika@webrtc.org5ba3dec2012-11-26 09:12:02 +0000362
Mirko Bonadei72c42502017-11-09 09:33:23 +0100363 // Check if the device support formattype == FORMAT_VideoInfo2 and
364 // FORMAT_VideoInfo. Prefer FORMAT_VideoInfo since some cameras (ZureCam) has
365 // been seen having problem with MJPEG and FORMAT_VideoInfo2 Interlace flag is
366 // only supported in FORMAT_VideoInfo2
367 bool supportFORMAT_VideoInfo2 = false;
368 bool supportFORMAT_VideoInfo = false;
369 bool foundInterlacedFormat = false;
370 GUID preferedVideoFormat = FORMAT_VideoInfo;
371 for (int32_t tmp = 0; tmp < count; ++tmp) {
372 hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));
Dan Minore22498c2019-02-04 12:47:48 -0500373 if (hr == S_OK) {
Mirko Bonadei72c42502017-11-09 09:33:23 +0100374 if (pmt->majortype == MEDIATYPE_Video &&
375 pmt->formattype == FORMAT_VideoInfo2) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100376 RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100377 supportFORMAT_VideoInfo2 = true;
378 VIDEOINFOHEADER2* h =
379 reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);
380 assert(h);
381 foundInterlacedFormat |=
382 h->dwInterlaceFlags &
383 (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);
384 }
385 if (pmt->majortype == MEDIATYPE_Video &&
386 pmt->formattype == FORMAT_VideoInfo) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100387 RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100388 supportFORMAT_VideoInfo = true;
389 }
390 }
391 }
392 if (supportFORMAT_VideoInfo2) {
393 if (supportFORMAT_VideoInfo && !foundInterlacedFormat) {
394 preferedVideoFormat = FORMAT_VideoInfo;
395 } else {
396 preferedVideoFormat = FORMAT_VideoInfo2;
397 }
398 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000399
Mirko Bonadei72c42502017-11-09 09:33:23 +0100400 for (int32_t tmp = 0; tmp < count; ++tmp) {
401 hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));
Dan Minore22498c2019-02-04 12:47:48 -0500402 if (hr != S_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100403 RTC_LOG(LS_INFO) << "Failed to GetStreamCaps";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100404 RELEASE_AND_CLEAR(videoControlConfig);
405 RELEASE_AND_CLEAR(streamConfig);
406 RELEASE_AND_CLEAR(outputCapturePin);
407 RELEASE_AND_CLEAR(captureDevice);
408 return -1;
409 }
410
411 if (pmt->majortype == MEDIATYPE_Video &&
412 pmt->formattype == preferedVideoFormat) {
413 VideoCaptureCapabilityWindows capability;
414 int64_t avgTimePerFrame = 0;
415
416 if (pmt->formattype == FORMAT_VideoInfo) {
417 VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);
418 assert(h);
419 capability.directShowCapabilityIndex = tmp;
420 capability.width = h->bmiHeader.biWidth;
421 capability.height = h->bmiHeader.biHeight;
422 avgTimePerFrame = h->AvgTimePerFrame;
423 }
424 if (pmt->formattype == FORMAT_VideoInfo2) {
425 VIDEOINFOHEADER2* h =
426 reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);
427 assert(h);
428 capability.directShowCapabilityIndex = tmp;
429 capability.width = h->bmiHeader.biWidth;
430 capability.height = h->bmiHeader.biHeight;
431 capability.interlaced =
432 h->dwInterlaceFlags &
433 (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);
434 avgTimePerFrame = h->AvgTimePerFrame;
435 }
436
437 if (hrVC == S_OK) {
438 LONGLONG* frameDurationList;
439 LONGLONG maxFPS;
440 long listSize;
441 SIZE size;
442 size.cx = capability.width;
443 size.cy = capability.height;
444
445 // GetMaxAvailableFrameRate doesn't return max frame rate always
446 // eg: Logitech Notebook. This may be due to a bug in that API
447 // because GetFrameRateList array is reversed in the above camera. So
448 // a util method written. Can't assume the first value will return
449 // the max fps.
450 hrVC = videoControlConfig->GetFrameRateList(
451 outputCapturePin, tmp, size, &listSize, &frameDurationList);
452
453 // On some odd cameras, you may get a 0 for duration.
454 // GetMaxOfFrameArray returns the lowest duration (highest FPS)
455 if (hrVC == S_OK && listSize > 0 &&
456 0 != (maxFPS = GetMaxOfFrameArray(frameDurationList, listSize))) {
457 capability.maxFPS = static_cast<int>(10000000 / maxFPS);
458 capability.supportFrameRateControl = true;
459 } else // use existing method
460 {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100461 RTC_LOG(LS_INFO) << "GetMaxAvailableFrameRate NOT SUPPORTED";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100462 if (avgTimePerFrame > 0)
463 capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);
464 else
465 capability.maxFPS = 0;
466 }
467 } else // use existing method in case IAMVideoControl is not supported
468 {
469 if (avgTimePerFrame > 0)
470 capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);
471 else
472 capability.maxFPS = 0;
473 }
474
475 // can't switch MEDIATYPE :~(
476 if (pmt->subtype == MEDIASUBTYPE_I420) {
477 capability.videoType = VideoType::kI420;
478 } else if (pmt->subtype == MEDIASUBTYPE_IYUV) {
479 capability.videoType = VideoType::kIYUV;
480 } else if (pmt->subtype == MEDIASUBTYPE_RGB24) {
481 capability.videoType = VideoType::kRGB24;
482 } else if (pmt->subtype == MEDIASUBTYPE_YUY2) {
483 capability.videoType = VideoType::kYUY2;
484 } else if (pmt->subtype == MEDIASUBTYPE_RGB565) {
485 capability.videoType = VideoType::kRGB565;
486 } else if (pmt->subtype == MEDIASUBTYPE_MJPG) {
487 capability.videoType = VideoType::kMJPEG;
488 } else if (pmt->subtype == MEDIASUBTYPE_dvsl ||
489 pmt->subtype == MEDIASUBTYPE_dvsd ||
490 pmt->subtype ==
491 MEDIASUBTYPE_dvhd) // If this is an external DV camera
492 {
493 capability.videoType =
494 VideoType::kYUY2; // MS DV filter seems to create this type
495 } else if (pmt->subtype ==
496 MEDIASUBTYPE_UYVY) // Seen used by Declink capture cards
497 {
498 capability.videoType = VideoType::kUYVY;
499 } else if (pmt->subtype ==
500 MEDIASUBTYPE_HDYC) // Seen used by Declink capture cards. Uses
501 // BT. 709 color. Not entiry correct to use
502 // UYVY. http://en.wikipedia.org/wiki/YCbCr
503 {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100504 RTC_LOG(LS_INFO) << "Device support HDYC.";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100505 capability.videoType = VideoType::kUYVY;
506 } else {
507 WCHAR strGuid[39];
508 StringFromGUID2(pmt->subtype, strGuid, 39);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100509 RTC_LOG(LS_WARNING)
510 << "Device support unknown media type " << strGuid << ", width "
511 << capability.width << ", height " << capability.height;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100512 continue;
513 }
514
515 _captureCapabilities.push_back(capability);
516 _captureCapabilitiesWindows.push_back(capability);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100517 RTC_LOG(LS_INFO) << "Camera capability, width:" << capability.width
518 << " height:" << capability.height
519 << " type:" << static_cast<int>(capability.videoType)
520 << " fps:" << capability.maxFPS;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100521 }
Tommi30e60d62019-03-12 09:59:05 +0100522 FreeMediaType(pmt);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100523 pmt = NULL;
524 }
525 RELEASE_AND_CLEAR(streamConfig);
526 RELEASE_AND_CLEAR(videoControlConfig);
527 RELEASE_AND_CLEAR(outputCapturePin);
528 RELEASE_AND_CLEAR(captureDevice); // Release the capture device
529
530 // Store the new used device name
531 _lastUsedDeviceNameLength = deviceUniqueIdUTF8Length;
532 _lastUsedDeviceName =
533 (char*)realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1);
534 memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8,
535 _lastUsedDeviceNameLength + 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100536 RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100537
538 return static_cast<int32_t>(_captureCapabilities.size());
niklase@google.com470e71d2011-07-07 08:21:25 +0000539}
540
Mirko Bonadeic710ac12018-05-08 16:52:55 +0200541// Constructs a product ID from the Windows DevicePath. on a USB device the
542// devicePath contains product id and vendor id. This seems to work for firewire
543// as well.
544// Example of device path:
545// "\\?\usb#vid_0408&pid_2010&mi_00#7&258e7aaf&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
546// "\\?\avc#sony&dv-vcr&camcorder&dv#65b2d50301460008#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
tommi@webrtc.org81878772012-11-20 13:35:33 +0000547void DeviceInfoDS::GetProductId(const char* devicePath,
Mirko Bonadei72c42502017-11-09 09:33:23 +0100548 char* productUniqueIdUTF8,
549 uint32_t productUniqueIdUTF8Length) {
550 *productUniqueIdUTF8 = '\0';
551 char* startPos = strstr((char*)devicePath, "\\\\?\\");
552 if (!startPos) {
553 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100554 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100555 return;
556 }
557 startPos += 4;
niklase@google.com470e71d2011-07-07 08:21:25 +0000558
Mirko Bonadei72c42502017-11-09 09:33:23 +0100559 char* pos = strchr(startPos, '&');
560 if (!pos || pos >= (char*)devicePath + strlen((char*)devicePath)) {
561 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100562 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100563 return;
564 }
565 // Find the second occurrence.
566 pos = strchr(pos + 1, '&');
567 uint32_t bytesToCopy = (uint32_t)(pos - startPos);
568 if (pos && (bytesToCopy <= productUniqueIdUTF8Length) &&
569 bytesToCopy <= kVideoCaptureProductIdLength) {
570 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length,
571 (char*)startPos, bytesToCopy);
572 } else {
573 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100574 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100575 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000576}
577
pbos@webrtc.orgdfc5bb92013-04-10 08:23:13 +0000578int32_t DeviceInfoDS::DisplayCaptureSettingsDialogBox(
Mirko Bonadei72c42502017-11-09 09:33:23 +0100579 const char* deviceUniqueIdUTF8,
580 const char* dialogTitleUTF8,
581 void* parentWindow,
582 uint32_t positionX,
583 uint32_t positionY) {
584 ReadLockScoped cs(_apiLock);
585 HWND window = (HWND)parentWindow;
niklase@google.com470e71d2011-07-07 08:21:25 +0000586
Mirko Bonadei72c42502017-11-09 09:33:23 +0100587 IBaseFilter* filter = GetDeviceFilter(deviceUniqueIdUTF8, NULL, 0);
588 if (!filter)
589 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000590
Mirko Bonadei72c42502017-11-09 09:33:23 +0100591 ISpecifyPropertyPages* pPages = NULL;
592 CAUUID uuid;
593 HRESULT hr = S_OK;
niklase@google.com470e71d2011-07-07 08:21:25 +0000594
Mirko Bonadei72c42502017-11-09 09:33:23 +0100595 hr = filter->QueryInterface(IID_ISpecifyPropertyPages, (LPVOID*)&pPages);
596 if (!SUCCEEDED(hr)) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000597 filter->Release();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100598 return -1;
599 }
600 hr = pPages->GetPages(&uuid);
601 if (!SUCCEEDED(hr)) {
602 filter->Release();
603 return -1;
604 }
605
606 WCHAR tempDialogTitleWide[256];
607 tempDialogTitleWide[0] = 0;
608 int size = 255;
609
610 // UTF-8 to wide char
611 MultiByteToWideChar(CP_UTF8, 0, (char*)dialogTitleUTF8, -1,
612 tempDialogTitleWide, size);
613
614 // Invoke a dialog box to display.
615
616 hr = OleCreatePropertyFrame(
617 window, // You must create the parent window.
618 positionX, // Horizontal position for the dialog box.
619 positionY, // Vertical position for the dialog box.
620 tempDialogTitleWide, // String used for the dialog box caption.
621 1, // Number of pointers passed in pPlugin.
622 (LPUNKNOWN*)&filter, // Pointer to the filter.
623 uuid.cElems, // Number of property pages.
624 uuid.pElems, // Array of property page CLSIDs.
625 LOCALE_USER_DEFAULT, // Locale ID for the dialog box.
626 0, NULL); // Reserved
627 // Release memory.
628 if (uuid.pElems) {
629 CoTaskMemFree(uuid.pElems);
630 }
631 filter->Release();
632 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000633}
pbos@webrtc.orgd900e8b2013-07-03 15:12:26 +0000634} // namespace videocapturemodule
635} // namespace webrtc