blob: 9ad07595da608ab06cb3f44a172ae14798f5f28e [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
Jonas Olssona4d87372019-07-05 19:08:33 +020013#include <dvdmedia.h>
14
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020015#include "modules/video_capture/video_capture_config.h"
16#include "modules/video_capture/windows/help_functions_ds.h"
17#include "rtc_base/logging.h"
Steve Anton10542f22019-01-11 09:11:00 -080018#include "rtc_base/string_utils.h"
niklase@google.com470e71d2011-07-07 08:21:25 +000019
Mirko Bonadei72c42502017-11-09 09:33:23 +010020namespace webrtc {
21namespace videocapturemodule {
tommi@webrtc.org951b6c42012-11-20 12:17:05 +000022
tommi@webrtc.org81878772012-11-20 13:35:33 +000023// static
Mirko Bonadei72c42502017-11-09 09:33:23 +010024DeviceInfoDS* DeviceInfoDS::Create() {
25 DeviceInfoDS* dsInfo = new DeviceInfoDS();
26 if (!dsInfo || dsInfo->Init() != 0) {
27 delete dsInfo;
28 dsInfo = NULL;
29 }
30 return dsInfo;
niklase@google.com470e71d2011-07-07 08:21:25 +000031}
32
nisseb29b9c82016-12-12 00:22:56 -080033DeviceInfoDS::DeviceInfoDS()
Mirko Bonadei72c42502017-11-09 09:33:23 +010034 : _dsDevEnum(NULL),
35 _dsMonikerDevEnum(NULL),
36 _CoUninitializeIsRequired(true) {
37 // 1) Initialize the COM library (make Windows load the DLLs).
38 //
39 // CoInitializeEx must be called at least once, and is usually called only
40 // once, for each thread that uses the COM library. Multiple calls to
41 // CoInitializeEx by the same thread are allowed as long as they pass the same
42 // concurrency flag, but subsequent valid calls return S_FALSE. To close the
43 // COM library gracefully on a thread, each successful call to CoInitializeEx,
44 // including any call that returns S_FALSE, must be balanced by a
45 // corresponding call to CoUninitialize.
46 //
niklase@google.com470e71d2011-07-07 08:21:25 +000047
Mirko Bonadei72c42502017-11-09 09:33:23 +010048 /*Apartment-threading, while allowing for multiple threads of execution,
49 serializes all incoming calls by requiring that calls to methods of objects
50 created by this thread always run on the same thread the apartment/thread
51 that created them. In addition, calls can arrive only at message-queue
52 boundaries (i.e., only during a PeekMessage, SendMessage, DispatchMessage,
53 etc.). Because of this serialization, it is not typically necessary to write
54 concurrency control into the code for the object, other than to avoid calls
55 to PeekMessage and SendMessage during processing that must not be interrupted
56 by other method invocations or calls to other objects in the same
57 apartment/thread.*/
niklase@google.com470e71d2011-07-07 08:21:25 +000058
Mirko Bonadei72c42502017-11-09 09:33:23 +010059 /// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED ); //|
60 /// COINIT_SPEED_OVER_MEMORY
61 HRESULT hr = CoInitializeEx(
62 NULL, COINIT_MULTITHREADED); // Use COINIT_MULTITHREADED since Voice
63 // Engine uses COINIT_MULTITHREADED
64 if (FAILED(hr)) {
65 // Avoid calling CoUninitialize() since CoInitializeEx() failed.
66 _CoUninitializeIsRequired = FALSE;
niklase@google.com470e71d2011-07-07 08:21:25 +000067
Mirko Bonadei72c42502017-11-09 09:33:23 +010068 if (hr == RPC_E_CHANGED_MODE) {
69 // Calling thread has already initialized COM to be used in a
70 // single-threaded apartment (STA). We are then prevented from using STA.
71 // Details: hr = 0x80010106 <=> "Cannot change thread mode after it is
72 // set".
73 //
Mirko Bonadei675513b2017-11-09 11:09:25 +010074 RTC_LOG(LS_INFO) << __FUNCTION__
75 << ": CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)"
Karl Wiberg43432732018-05-23 11:13:31 +020076 << " => RPC_E_CHANGED_MODE, error 0x" << rtc::ToHex(hr);
niklase@google.com470e71d2011-07-07 08:21:25 +000077 }
Mirko Bonadei72c42502017-11-09 09:33:23 +010078 }
niklase@google.com470e71d2011-07-07 08:21:25 +000079}
80
Mirko Bonadei72c42502017-11-09 09:33:23 +010081DeviceInfoDS::~DeviceInfoDS() {
82 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
83 RELEASE_AND_CLEAR(_dsDevEnum);
84 if (_CoUninitializeIsRequired) {
85 CoUninitialize();
86 }
niklase@google.com470e71d2011-07-07 08:21:25 +000087}
88
Mirko Bonadei72c42502017-11-09 09:33:23 +010089int32_t DeviceInfoDS::Init() {
90 HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
91 IID_ICreateDevEnum, (void**)&_dsDevEnum);
92 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +010093 RTC_LOG(LS_INFO) << "Failed to create CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +020094 << rtc::ToHex(hr);
Mirko Bonadei72c42502017-11-09 09:33:23 +010095 return -1;
96 }
97 return 0;
98}
99uint32_t DeviceInfoDS::NumberOfDevices() {
100 ReadLockScoped cs(_apiLock);
101 return GetDeviceInfo(0, 0, 0, 0, 0, 0, 0);
102}
103
104int32_t DeviceInfoDS::GetDeviceName(uint32_t deviceNumber,
105 char* deviceNameUTF8,
106 uint32_t deviceNameLength,
107 char* deviceUniqueIdUTF8,
108 uint32_t deviceUniqueIdUTF8Length,
109 char* productUniqueIdUTF8,
110 uint32_t productUniqueIdUTF8Length) {
111 ReadLockScoped cs(_apiLock);
112 const int32_t result = GetDeviceInfo(
113 deviceNumber, deviceNameUTF8, deviceNameLength, deviceUniqueIdUTF8,
114 deviceUniqueIdUTF8Length, productUniqueIdUTF8, productUniqueIdUTF8Length);
115 return result > (int32_t)deviceNumber ? 0 : -1;
116}
117
118int32_t DeviceInfoDS::GetDeviceInfo(uint32_t deviceNumber,
119 char* deviceNameUTF8,
120 uint32_t deviceNameLength,
121 char* deviceUniqueIdUTF8,
122 uint32_t deviceUniqueIdUTF8Length,
123 char* productUniqueIdUTF8,
124 uint32_t productUniqueIdUTF8Length)
125
niklase@google.com470e71d2011-07-07 08:21:25 +0000126{
Mirko Bonadei72c42502017-11-09 09:33:23 +0100127 // enumerate all video capture devices
128 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
129 HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
130 &_dsMonikerDevEnum, 0);
131 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100132 RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +0200133 << rtc::ToHex(hr) << ". No webcam exist?";
niklase@google.com470e71d2011-07-07 08:21:25 +0000134 return 0;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100135 }
tommi@webrtc.org81878772012-11-20 13:35:33 +0000136
Mirko Bonadei72c42502017-11-09 09:33:23 +0100137 _dsMonikerDevEnum->Reset();
138 ULONG cFetched;
139 IMoniker* pM;
140 int index = 0;
141 while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched)) {
142 IPropertyBag* pBag;
143 hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag);
144 if (S_OK == hr) {
145 // Find the description or friendly name.
146 VARIANT varName;
147 VariantInit(&varName);
148 hr = pBag->Read(L"Description", &varName, 0);
149 if (FAILED(hr)) {
150 hr = pBag->Read(L"FriendlyName", &varName, 0);
151 }
152 if (SUCCEEDED(hr)) {
153 // ignore all VFW drivers
154 if ((wcsstr(varName.bstrVal, (L"(VFW)")) == NULL) &&
155 (_wcsnicmp(varName.bstrVal, (L"Google Camera Adapter"), 21) != 0)) {
156 // Found a valid device.
157 if (index == static_cast<int>(deviceNumber)) {
158 int convResult = 0;
159 if (deviceNameLength > 0) {
160 convResult = WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1,
161 (char*)deviceNameUTF8,
162 deviceNameLength, NULL, NULL);
163 if (convResult == 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100164 RTC_LOG(LS_INFO) << "Failed to convert device name to UTF8, "
165 << "error = " << GetLastError();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100166 return -1;
167 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000168 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100169 if (deviceUniqueIdUTF8Length > 0) {
170 hr = pBag->Read(L"DevicePath", &varName, 0);
171 if (FAILED(hr)) {
172 strncpy_s((char*)deviceUniqueIdUTF8, deviceUniqueIdUTF8Length,
173 (char*)deviceNameUTF8, convResult);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100174 RTC_LOG(LS_INFO) << "Failed to get "
175 << "deviceUniqueIdUTF8 using "
176 << "deviceNameUTF8";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100177 } else {
178 convResult = WideCharToMultiByte(
179 CP_UTF8, 0, varName.bstrVal, -1, (char*)deviceUniqueIdUTF8,
180 deviceUniqueIdUTF8Length, NULL, NULL);
181 if (convResult == 0) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100182 RTC_LOG(LS_INFO)
183 << "Failed to convert device "
184 << "name to UTF8, error = " << GetLastError();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100185 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000186 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100187 if (productUniqueIdUTF8 && productUniqueIdUTF8Length > 0) {
188 GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8,
189 productUniqueIdUTF8Length);
190 }
191 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000192 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100193 }
194 ++index; // increase the number of valid devices
niklase@google.com470e71d2011-07-07 08:21:25 +0000195 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100196 }
197 VariantClear(&varName);
198 pBag->Release();
199 pM->Release();
niklase@google.com470e71d2011-07-07 08:21:25 +0000200 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100201 }
202 if (deviceNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100203 RTC_LOG(LS_INFO) << __FUNCTION__ << " " << deviceNameUTF8;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100204 }
205 return index;
niklase@google.com470e71d2011-07-07 08:21:25 +0000206}
207
Mirko Bonadei72c42502017-11-09 09:33:23 +0100208IBaseFilter* DeviceInfoDS::GetDeviceFilter(const char* deviceUniqueIdUTF8,
209 char* productUniqueIdUTF8,
210 uint32_t productUniqueIdUTF8Length) {
211 const int32_t deviceUniqueIdUTF8Length = (int32_t)strlen(
212 (char*)deviceUniqueIdUTF8); // UTF8 is also NULL terminated
213 if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100214 RTC_LOG(LS_INFO) << "Device name too long";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100215 return NULL;
216 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000217
Mirko Bonadei72c42502017-11-09 09:33:23 +0100218 // enumerate all video capture devices
219 RELEASE_AND_CLEAR(_dsMonikerDevEnum);
220 HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
221 &_dsMonikerDevEnum, 0);
222 if (hr != NOERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100223 RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x"
Karl Wiberg43432732018-05-23 11:13:31 +0200224 << rtc::ToHex(hr) << ". No webcam exist?";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100225 return 0;
226 }
227 _dsMonikerDevEnum->Reset();
228 ULONG cFetched;
229 IMoniker* pM;
niklase@google.com470e71d2011-07-07 08:21:25 +0000230
Mirko Bonadei72c42502017-11-09 09:33:23 +0100231 IBaseFilter* captureFilter = NULL;
232 bool deviceFound = false;
233 while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched) && !deviceFound) {
234 IPropertyBag* pBag;
235 hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag);
236 if (S_OK == hr) {
237 // Find the description or friendly name.
238 VARIANT varName;
239 VariantInit(&varName);
240 if (deviceUniqueIdUTF8Length > 0) {
241 hr = pBag->Read(L"DevicePath", &varName, 0);
242 if (FAILED(hr)) {
243 hr = pBag->Read(L"Description", &varName, 0);
244 if (FAILED(hr)) {
245 hr = pBag->Read(L"FriendlyName", &varName, 0);
246 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000247 }
Mirko Bonadei72c42502017-11-09 09:33:23 +0100248 if (SUCCEEDED(hr)) {
249 char tempDevicePathUTF8[256];
250 tempDevicePathUTF8[0] = 0;
251 WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1,
252 tempDevicePathUTF8, sizeof(tempDevicePathUTF8),
253 NULL, NULL);
254 if (strncmp(tempDevicePathUTF8, (const char*)deviceUniqueIdUTF8,
255 deviceUniqueIdUTF8Length) == 0) {
256 // We have found the requested device
257 deviceFound = true;
258 hr =
259 pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&captureFilter);
260 if
261 FAILED(hr) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100262 RTC_LOG(LS_ERROR) << "Failed to bind to the selected "
263 << "capture device " << hr;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100264 }
265
266 if (productUniqueIdUTF8 &&
267 productUniqueIdUTF8Length > 0) // Get the device name
268 {
269 GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8,
270 productUniqueIdUTF8Length);
271 }
272 }
273 }
274 }
275 VariantClear(&varName);
276 pBag->Release();
niklase@google.com470e71d2011-07-07 08:21:25 +0000277 }
Tim Haloun059daa42019-11-11 16:06:03 -0800278 pM->Release();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100279 }
280 return captureFilter;
niklase@google.com470e71d2011-07-07 08:21:25 +0000281}
282
pbos@webrtc.orgdfc5bb92013-04-10 08:23:13 +0000283int32_t DeviceInfoDS::GetWindowsCapability(
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000284 const int32_t capabilityIndex,
285 VideoCaptureCapabilityWindows& windowsCapability) {
286 ReadLockScoped cs(_apiLock);
niklase@google.com470e71d2011-07-07 08:21:25 +0000287
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000288 if (capabilityIndex < 0 || static_cast<size_t>(capabilityIndex) >=
289 _captureCapabilitiesWindows.size()) {
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000290 return -1;
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000291 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000292
fischman@webrtc.org69fc3152013-09-25 17:01:42 +0000293 windowsCapability = _captureCapabilitiesWindows[capabilityIndex];
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000294 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000295}
296
Mirko Bonadei72c42502017-11-09 09:33:23 +0100297int32_t DeviceInfoDS::CreateCapabilityMap(const char* deviceUniqueIdUTF8)
niklase@google.com470e71d2011-07-07 08:21:25 +0000298
299{
Mirko Bonadei72c42502017-11-09 09:33:23 +0100300 // Reset old capability list
301 _captureCapabilities.clear();
pbos@webrtc.org4ca7d3f2013-08-12 19:51:57 +0000302
Mirko Bonadei72c42502017-11-09 09:33:23 +0100303 const int32_t deviceUniqueIdUTF8Length =
304 (int32_t)strlen((char*)deviceUniqueIdUTF8);
305 if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100306 RTC_LOG(LS_INFO) << "Device name too long";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100307 return -1;
308 }
Mirko Bonadei675513b2017-11-09 11:09:25 +0100309 RTC_LOG(LS_INFO) << "CreateCapabilityMap called for device "
310 << deviceUniqueIdUTF8;
niklase@google.com470e71d2011-07-07 08:21:25 +0000311
Mirko Bonadei72c42502017-11-09 09:33:23 +0100312 char productId[kVideoCaptureProductIdLength];
313 IBaseFilter* captureDevice = DeviceInfoDS::GetDeviceFilter(
314 deviceUniqueIdUTF8, productId, kVideoCaptureProductIdLength);
315 if (!captureDevice)
316 return -1;
317 IPin* outputCapturePin = GetOutputPin(captureDevice, GUID_NULL);
318 if (!outputCapturePin) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100319 RTC_LOG(LS_INFO) << "Failed to get capture device output pin";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100320 RELEASE_AND_CLEAR(captureDevice);
321 return -1;
322 }
323 IAMExtDevice* extDevice = NULL;
324 HRESULT hr =
325 captureDevice->QueryInterface(IID_IAMExtDevice, (void**)&extDevice);
326 if (SUCCEEDED(hr) && extDevice) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100327 RTC_LOG(LS_INFO) << "This is an external device";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100328 extDevice->Release();
329 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000330
Mirko Bonadei72c42502017-11-09 09:33:23 +0100331 IAMStreamConfig* streamConfig = NULL;
332 hr = outputCapturePin->QueryInterface(IID_IAMStreamConfig,
333 (void**)&streamConfig);
334 if (FAILED(hr)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100335 RTC_LOG(LS_INFO) << "Failed to get IID_IAMStreamConfig interface "
336 << "from capture device";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100337 return -1;
338 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000339
Mirko Bonadei72c42502017-11-09 09:33:23 +0100340 // this gets the FPS
341 IAMVideoControl* videoControlConfig = NULL;
342 HRESULT hrVC = captureDevice->QueryInterface(IID_IAMVideoControl,
343 (void**)&videoControlConfig);
344 if (FAILED(hrVC)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100345 RTC_LOG(LS_INFO) << "IID_IAMVideoControl Interface NOT SUPPORTED";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100346 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000347
Mirko Bonadei72c42502017-11-09 09:33:23 +0100348 AM_MEDIA_TYPE* pmt = NULL;
349 VIDEO_STREAM_CONFIG_CAPS caps;
350 int count, size;
niklase@google.com470e71d2011-07-07 08:21:25 +0000351
Mirko Bonadei72c42502017-11-09 09:33:23 +0100352 hr = streamConfig->GetNumberOfCapabilities(&count, &size);
353 if (FAILED(hr)) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100354 RTC_LOG(LS_INFO) << "Failed to GetNumberOfCapabilities";
niklase@google.com470e71d2011-07-07 08:21:25 +0000355 RELEASE_AND_CLEAR(videoControlConfig);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100356 RELEASE_AND_CLEAR(streamConfig);
niklase@google.com470e71d2011-07-07 08:21:25 +0000357 RELEASE_AND_CLEAR(outputCapturePin);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100358 RELEASE_AND_CLEAR(captureDevice);
359 return -1;
360 }
henrika@webrtc.org5ba3dec2012-11-26 09:12:02 +0000361
Mirko Bonadei72c42502017-11-09 09:33:23 +0100362 // Check if the device support formattype == FORMAT_VideoInfo2 and
363 // FORMAT_VideoInfo. Prefer FORMAT_VideoInfo since some cameras (ZureCam) has
364 // been seen having problem with MJPEG and FORMAT_VideoInfo2 Interlace flag is
365 // only supported in FORMAT_VideoInfo2
366 bool supportFORMAT_VideoInfo2 = false;
367 bool supportFORMAT_VideoInfo = false;
368 bool foundInterlacedFormat = false;
369 GUID preferedVideoFormat = FORMAT_VideoInfo;
370 for (int32_t tmp = 0; tmp < count; ++tmp) {
371 hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));
Dan Minore22498c2019-02-04 12:47:48 -0500372 if (hr == S_OK) {
Mirko Bonadei72c42502017-11-09 09:33:23 +0100373 if (pmt->majortype == MEDIATYPE_Video &&
374 pmt->formattype == FORMAT_VideoInfo2) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100375 RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100376 supportFORMAT_VideoInfo2 = true;
377 VIDEOINFOHEADER2* h =
378 reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);
379 assert(h);
380 foundInterlacedFormat |=
381 h->dwInterlaceFlags &
382 (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);
383 }
384 if (pmt->majortype == MEDIATYPE_Video &&
385 pmt->formattype == FORMAT_VideoInfo) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100386 RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100387 supportFORMAT_VideoInfo = true;
388 }
389 }
390 }
391 if (supportFORMAT_VideoInfo2) {
392 if (supportFORMAT_VideoInfo && !foundInterlacedFormat) {
393 preferedVideoFormat = FORMAT_VideoInfo;
394 } else {
395 preferedVideoFormat = FORMAT_VideoInfo2;
396 }
397 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000398
Mirko Bonadei72c42502017-11-09 09:33:23 +0100399 for (int32_t tmp = 0; tmp < count; ++tmp) {
400 hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps));
Dan Minore22498c2019-02-04 12:47:48 -0500401 if (hr != S_OK) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100402 RTC_LOG(LS_INFO) << "Failed to GetStreamCaps";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100403 RELEASE_AND_CLEAR(videoControlConfig);
404 RELEASE_AND_CLEAR(streamConfig);
405 RELEASE_AND_CLEAR(outputCapturePin);
406 RELEASE_AND_CLEAR(captureDevice);
407 return -1;
408 }
409
410 if (pmt->majortype == MEDIATYPE_Video &&
411 pmt->formattype == preferedVideoFormat) {
412 VideoCaptureCapabilityWindows capability;
413 int64_t avgTimePerFrame = 0;
414
415 if (pmt->formattype == FORMAT_VideoInfo) {
416 VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);
417 assert(h);
418 capability.directShowCapabilityIndex = tmp;
419 capability.width = h->bmiHeader.biWidth;
420 capability.height = h->bmiHeader.biHeight;
421 avgTimePerFrame = h->AvgTimePerFrame;
422 }
423 if (pmt->formattype == FORMAT_VideoInfo2) {
424 VIDEOINFOHEADER2* h =
425 reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat);
426 assert(h);
427 capability.directShowCapabilityIndex = tmp;
428 capability.width = h->bmiHeader.biWidth;
429 capability.height = h->bmiHeader.biHeight;
430 capability.interlaced =
431 h->dwInterlaceFlags &
432 (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly);
433 avgTimePerFrame = h->AvgTimePerFrame;
434 }
435
436 if (hrVC == S_OK) {
437 LONGLONG* frameDurationList;
438 LONGLONG maxFPS;
439 long listSize;
440 SIZE size;
441 size.cx = capability.width;
442 size.cy = capability.height;
443
444 // GetMaxAvailableFrameRate doesn't return max frame rate always
445 // eg: Logitech Notebook. This may be due to a bug in that API
446 // because GetFrameRateList array is reversed in the above camera. So
447 // a util method written. Can't assume the first value will return
448 // the max fps.
449 hrVC = videoControlConfig->GetFrameRateList(
450 outputCapturePin, tmp, size, &listSize, &frameDurationList);
451
452 // On some odd cameras, you may get a 0 for duration.
453 // GetMaxOfFrameArray returns the lowest duration (highest FPS)
454 if (hrVC == S_OK && listSize > 0 &&
455 0 != (maxFPS = GetMaxOfFrameArray(frameDurationList, listSize))) {
456 capability.maxFPS = static_cast<int>(10000000 / maxFPS);
457 capability.supportFrameRateControl = true;
458 } else // use existing method
459 {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100460 RTC_LOG(LS_INFO) << "GetMaxAvailableFrameRate NOT SUPPORTED";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100461 if (avgTimePerFrame > 0)
462 capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);
463 else
464 capability.maxFPS = 0;
465 }
466 } else // use existing method in case IAMVideoControl is not supported
467 {
468 if (avgTimePerFrame > 0)
469 capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame);
470 else
471 capability.maxFPS = 0;
472 }
473
474 // can't switch MEDIATYPE :~(
475 if (pmt->subtype == MEDIASUBTYPE_I420) {
476 capability.videoType = VideoType::kI420;
477 } else if (pmt->subtype == MEDIASUBTYPE_IYUV) {
478 capability.videoType = VideoType::kIYUV;
479 } else if (pmt->subtype == MEDIASUBTYPE_RGB24) {
480 capability.videoType = VideoType::kRGB24;
481 } else if (pmt->subtype == MEDIASUBTYPE_YUY2) {
482 capability.videoType = VideoType::kYUY2;
483 } else if (pmt->subtype == MEDIASUBTYPE_RGB565) {
484 capability.videoType = VideoType::kRGB565;
485 } else if (pmt->subtype == MEDIASUBTYPE_MJPG) {
486 capability.videoType = VideoType::kMJPEG;
487 } else if (pmt->subtype == MEDIASUBTYPE_dvsl ||
488 pmt->subtype == MEDIASUBTYPE_dvsd ||
489 pmt->subtype ==
490 MEDIASUBTYPE_dvhd) // If this is an external DV camera
491 {
492 capability.videoType =
493 VideoType::kYUY2; // MS DV filter seems to create this type
494 } else if (pmt->subtype ==
495 MEDIASUBTYPE_UYVY) // Seen used by Declink capture cards
496 {
497 capability.videoType = VideoType::kUYVY;
498 } else if (pmt->subtype ==
499 MEDIASUBTYPE_HDYC) // Seen used by Declink capture cards. Uses
500 // BT. 709 color. Not entiry correct to use
501 // UYVY. http://en.wikipedia.org/wiki/YCbCr
502 {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100503 RTC_LOG(LS_INFO) << "Device support HDYC.";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100504 capability.videoType = VideoType::kUYVY;
505 } else {
506 WCHAR strGuid[39];
507 StringFromGUID2(pmt->subtype, strGuid, 39);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100508 RTC_LOG(LS_WARNING)
509 << "Device support unknown media type " << strGuid << ", width "
510 << capability.width << ", height " << capability.height;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100511 continue;
512 }
513
514 _captureCapabilities.push_back(capability);
515 _captureCapabilitiesWindows.push_back(capability);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100516 RTC_LOG(LS_INFO) << "Camera capability, width:" << capability.width
517 << " height:" << capability.height
518 << " type:" << static_cast<int>(capability.videoType)
519 << " fps:" << capability.maxFPS;
Mirko Bonadei72c42502017-11-09 09:33:23 +0100520 }
Tommi30e60d62019-03-12 09:59:05 +0100521 FreeMediaType(pmt);
Mirko Bonadei72c42502017-11-09 09:33:23 +0100522 pmt = NULL;
523 }
524 RELEASE_AND_CLEAR(streamConfig);
525 RELEASE_AND_CLEAR(videoControlConfig);
526 RELEASE_AND_CLEAR(outputCapturePin);
527 RELEASE_AND_CLEAR(captureDevice); // Release the capture device
528
529 // Store the new used device name
530 _lastUsedDeviceNameLength = deviceUniqueIdUTF8Length;
531 _lastUsedDeviceName =
532 (char*)realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1);
533 memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8,
534 _lastUsedDeviceNameLength + 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100535 RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100536
537 return static_cast<int32_t>(_captureCapabilities.size());
niklase@google.com470e71d2011-07-07 08:21:25 +0000538}
539
Mirko Bonadeic710ac12018-05-08 16:52:55 +0200540// Constructs a product ID from the Windows DevicePath. on a USB device the
541// devicePath contains product id and vendor id. This seems to work for firewire
542// as well.
543// Example of device path:
544// "\\?\usb#vid_0408&pid_2010&mi_00#7&258e7aaf&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
545// "\\?\avc#sony&dv-vcr&camcorder&dv#65b2d50301460008#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
tommi@webrtc.org81878772012-11-20 13:35:33 +0000546void DeviceInfoDS::GetProductId(const char* devicePath,
Mirko Bonadei72c42502017-11-09 09:33:23 +0100547 char* productUniqueIdUTF8,
548 uint32_t productUniqueIdUTF8Length) {
549 *productUniqueIdUTF8 = '\0';
550 char* startPos = strstr((char*)devicePath, "\\\\?\\");
551 if (!startPos) {
552 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100553 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100554 return;
555 }
556 startPos += 4;
niklase@google.com470e71d2011-07-07 08:21:25 +0000557
Mirko Bonadei72c42502017-11-09 09:33:23 +0100558 char* pos = strchr(startPos, '&');
559 if (!pos || pos >= (char*)devicePath + strlen((char*)devicePath)) {
560 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100561 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100562 return;
563 }
564 // Find the second occurrence.
565 pos = strchr(pos + 1, '&');
566 uint32_t bytesToCopy = (uint32_t)(pos - startPos);
567 if (pos && (bytesToCopy <= productUniqueIdUTF8Length) &&
568 bytesToCopy <= kVideoCaptureProductIdLength) {
569 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length,
570 (char*)startPos, bytesToCopy);
571 } else {
572 strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1);
Mirko Bonadei675513b2017-11-09 11:09:25 +0100573 RTC_LOG(LS_INFO) << "Failed to get the product Id";
Mirko Bonadei72c42502017-11-09 09:33:23 +0100574 }
niklase@google.com470e71d2011-07-07 08:21:25 +0000575}
576
pbos@webrtc.orgdfc5bb92013-04-10 08:23:13 +0000577int32_t DeviceInfoDS::DisplayCaptureSettingsDialogBox(
Mirko Bonadei72c42502017-11-09 09:33:23 +0100578 const char* deviceUniqueIdUTF8,
579 const char* dialogTitleUTF8,
580 void* parentWindow,
581 uint32_t positionX,
582 uint32_t positionY) {
583 ReadLockScoped cs(_apiLock);
584 HWND window = (HWND)parentWindow;
niklase@google.com470e71d2011-07-07 08:21:25 +0000585
Mirko Bonadei72c42502017-11-09 09:33:23 +0100586 IBaseFilter* filter = GetDeviceFilter(deviceUniqueIdUTF8, NULL, 0);
587 if (!filter)
588 return -1;
niklase@google.com470e71d2011-07-07 08:21:25 +0000589
Mirko Bonadei72c42502017-11-09 09:33:23 +0100590 ISpecifyPropertyPages* pPages = NULL;
591 CAUUID uuid;
592 HRESULT hr = S_OK;
niklase@google.com470e71d2011-07-07 08:21:25 +0000593
Mirko Bonadei72c42502017-11-09 09:33:23 +0100594 hr = filter->QueryInterface(IID_ISpecifyPropertyPages, (LPVOID*)&pPages);
595 if (!SUCCEEDED(hr)) {
niklase@google.com470e71d2011-07-07 08:21:25 +0000596 filter->Release();
Mirko Bonadei72c42502017-11-09 09:33:23 +0100597 return -1;
598 }
599 hr = pPages->GetPages(&uuid);
600 if (!SUCCEEDED(hr)) {
601 filter->Release();
602 return -1;
603 }
604
605 WCHAR tempDialogTitleWide[256];
606 tempDialogTitleWide[0] = 0;
607 int size = 255;
608
609 // UTF-8 to wide char
610 MultiByteToWideChar(CP_UTF8, 0, (char*)dialogTitleUTF8, -1,
611 tempDialogTitleWide, size);
612
613 // Invoke a dialog box to display.
614
615 hr = OleCreatePropertyFrame(
616 window, // You must create the parent window.
617 positionX, // Horizontal position for the dialog box.
618 positionY, // Vertical position for the dialog box.
619 tempDialogTitleWide, // String used for the dialog box caption.
620 1, // Number of pointers passed in pPlugin.
621 (LPUNKNOWN*)&filter, // Pointer to the filter.
622 uuid.cElems, // Number of property pages.
623 uuid.pElems, // Array of property page CLSIDs.
624 LOCALE_USER_DEFAULT, // Locale ID for the dialog box.
625 0, NULL); // Reserved
626 // Release memory.
627 if (uuid.pElems) {
628 CoTaskMemFree(uuid.pElems);
629 }
630 filter->Release();
631 return 0;
niklase@google.com470e71d2011-07-07 08:21:25 +0000632}
pbos@webrtc.orgd900e8b2013-07-03 15:12:26 +0000633} // namespace videocapturemodule
634} // namespace webrtc