| /* |
| * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #include "modules/video_capture/windows/device_info_ds.h" |
| |
| #include "modules/video_capture/video_capture_config.h" |
| #include "modules/video_capture/windows/help_functions_ds.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/string_utils.h" |
| |
| #include <dvdmedia.h> |
| |
| namespace webrtc { |
| namespace videocapturemodule { |
| |
| // static |
| DeviceInfoDS* DeviceInfoDS::Create() { |
| DeviceInfoDS* dsInfo = new DeviceInfoDS(); |
| if (!dsInfo || dsInfo->Init() != 0) { |
| delete dsInfo; |
| dsInfo = NULL; |
| } |
| return dsInfo; |
| } |
| |
| DeviceInfoDS::DeviceInfoDS() |
| : _dsDevEnum(NULL), |
| _dsMonikerDevEnum(NULL), |
| _CoUninitializeIsRequired(true) { |
| // 1) Initialize the COM library (make Windows load the DLLs). |
| // |
| // CoInitializeEx must be called at least once, and is usually called only |
| // once, for each thread that uses the COM library. Multiple calls to |
| // CoInitializeEx by the same thread are allowed as long as they pass the same |
| // concurrency flag, but subsequent valid calls return S_FALSE. To close the |
| // COM library gracefully on a thread, each successful call to CoInitializeEx, |
| // including any call that returns S_FALSE, must be balanced by a |
| // corresponding call to CoUninitialize. |
| // |
| |
| /*Apartment-threading, while allowing for multiple threads of execution, |
| serializes all incoming calls by requiring that calls to methods of objects |
| created by this thread always run on the same thread the apartment/thread |
| that created them. In addition, calls can arrive only at message-queue |
| boundaries (i.e., only during a PeekMessage, SendMessage, DispatchMessage, |
| etc.). Because of this serialization, it is not typically necessary to write |
| concurrency control into the code for the object, other than to avoid calls |
| to PeekMessage and SendMessage during processing that must not be interrupted |
| by other method invocations or calls to other objects in the same |
| apartment/thread.*/ |
| |
| /// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED ); //| |
| /// COINIT_SPEED_OVER_MEMORY |
| HRESULT hr = CoInitializeEx( |
| NULL, COINIT_MULTITHREADED); // Use COINIT_MULTITHREADED since Voice |
| // Engine uses COINIT_MULTITHREADED |
| if (FAILED(hr)) { |
| // Avoid calling CoUninitialize() since CoInitializeEx() failed. |
| _CoUninitializeIsRequired = FALSE; |
| |
| if (hr == RPC_E_CHANGED_MODE) { |
| // Calling thread has already initialized COM to be used in a |
| // single-threaded apartment (STA). We are then prevented from using STA. |
| // Details: hr = 0x80010106 <=> "Cannot change thread mode after it is |
| // set". |
| // |
| RTC_LOG(LS_INFO) << __FUNCTION__ |
| << ": CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)" |
| << " => RPC_E_CHANGED_MODE, error 0x" << rtc::ToHex(hr); |
| } |
| } |
| } |
| |
| DeviceInfoDS::~DeviceInfoDS() { |
| RELEASE_AND_CLEAR(_dsMonikerDevEnum); |
| RELEASE_AND_CLEAR(_dsDevEnum); |
| if (_CoUninitializeIsRequired) { |
| CoUninitialize(); |
| } |
| } |
| |
| int32_t DeviceInfoDS::Init() { |
| HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, |
| IID_ICreateDevEnum, (void**)&_dsDevEnum); |
| if (hr != NOERROR) { |
| RTC_LOG(LS_INFO) << "Failed to create CLSID_SystemDeviceEnum, error 0x" |
| << rtc::ToHex(hr); |
| return -1; |
| } |
| return 0; |
| } |
| uint32_t DeviceInfoDS::NumberOfDevices() { |
| ReadLockScoped cs(_apiLock); |
| return GetDeviceInfo(0, 0, 0, 0, 0, 0, 0); |
| } |
| |
| int32_t DeviceInfoDS::GetDeviceName(uint32_t deviceNumber, |
| char* deviceNameUTF8, |
| uint32_t deviceNameLength, |
| char* deviceUniqueIdUTF8, |
| uint32_t deviceUniqueIdUTF8Length, |
| char* productUniqueIdUTF8, |
| uint32_t productUniqueIdUTF8Length) { |
| ReadLockScoped cs(_apiLock); |
| const int32_t result = GetDeviceInfo( |
| deviceNumber, deviceNameUTF8, deviceNameLength, deviceUniqueIdUTF8, |
| deviceUniqueIdUTF8Length, productUniqueIdUTF8, productUniqueIdUTF8Length); |
| return result > (int32_t)deviceNumber ? 0 : -1; |
| } |
| |
| int32_t DeviceInfoDS::GetDeviceInfo(uint32_t deviceNumber, |
| char* deviceNameUTF8, |
| uint32_t deviceNameLength, |
| char* deviceUniqueIdUTF8, |
| uint32_t deviceUniqueIdUTF8Length, |
| char* productUniqueIdUTF8, |
| uint32_t productUniqueIdUTF8Length) |
| |
| { |
| // enumerate all video capture devices |
| RELEASE_AND_CLEAR(_dsMonikerDevEnum); |
| HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, |
| &_dsMonikerDevEnum, 0); |
| if (hr != NOERROR) { |
| RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x" |
| << rtc::ToHex(hr) << ". No webcam exist?"; |
| return 0; |
| } |
| |
| _dsMonikerDevEnum->Reset(); |
| ULONG cFetched; |
| IMoniker* pM; |
| int index = 0; |
| while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched)) { |
| IPropertyBag* pBag; |
| hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag); |
| if (S_OK == hr) { |
| // Find the description or friendly name. |
| VARIANT varName; |
| VariantInit(&varName); |
| hr = pBag->Read(L"Description", &varName, 0); |
| if (FAILED(hr)) { |
| hr = pBag->Read(L"FriendlyName", &varName, 0); |
| } |
| if (SUCCEEDED(hr)) { |
| // ignore all VFW drivers |
| if ((wcsstr(varName.bstrVal, (L"(VFW)")) == NULL) && |
| (_wcsnicmp(varName.bstrVal, (L"Google Camera Adapter"), 21) != 0)) { |
| // Found a valid device. |
| if (index == static_cast<int>(deviceNumber)) { |
| int convResult = 0; |
| if (deviceNameLength > 0) { |
| convResult = WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1, |
| (char*)deviceNameUTF8, |
| deviceNameLength, NULL, NULL); |
| if (convResult == 0) { |
| RTC_LOG(LS_INFO) << "Failed to convert device name to UTF8, " |
| << "error = " << GetLastError(); |
| return -1; |
| } |
| } |
| if (deviceUniqueIdUTF8Length > 0) { |
| hr = pBag->Read(L"DevicePath", &varName, 0); |
| if (FAILED(hr)) { |
| strncpy_s((char*)deviceUniqueIdUTF8, deviceUniqueIdUTF8Length, |
| (char*)deviceNameUTF8, convResult); |
| RTC_LOG(LS_INFO) << "Failed to get " |
| << "deviceUniqueIdUTF8 using " |
| << "deviceNameUTF8"; |
| } else { |
| convResult = WideCharToMultiByte( |
| CP_UTF8, 0, varName.bstrVal, -1, (char*)deviceUniqueIdUTF8, |
| deviceUniqueIdUTF8Length, NULL, NULL); |
| if (convResult == 0) { |
| RTC_LOG(LS_INFO) |
| << "Failed to convert device " |
| << "name to UTF8, error = " << GetLastError(); |
| return -1; |
| } |
| if (productUniqueIdUTF8 && productUniqueIdUTF8Length > 0) { |
| GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8, |
| productUniqueIdUTF8Length); |
| } |
| } |
| } |
| } |
| ++index; // increase the number of valid devices |
| } |
| } |
| VariantClear(&varName); |
| pBag->Release(); |
| pM->Release(); |
| } |
| } |
| if (deviceNameLength) { |
| RTC_LOG(LS_INFO) << __FUNCTION__ << " " << deviceNameUTF8; |
| } |
| return index; |
| } |
| |
| IBaseFilter* DeviceInfoDS::GetDeviceFilter(const char* deviceUniqueIdUTF8, |
| char* productUniqueIdUTF8, |
| uint32_t productUniqueIdUTF8Length) { |
| const int32_t deviceUniqueIdUTF8Length = (int32_t)strlen( |
| (char*)deviceUniqueIdUTF8); // UTF8 is also NULL terminated |
| if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) { |
| RTC_LOG(LS_INFO) << "Device name too long"; |
| return NULL; |
| } |
| |
| // enumerate all video capture devices |
| RELEASE_AND_CLEAR(_dsMonikerDevEnum); |
| HRESULT hr = _dsDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, |
| &_dsMonikerDevEnum, 0); |
| if (hr != NOERROR) { |
| RTC_LOG(LS_INFO) << "Failed to enumerate CLSID_SystemDeviceEnum, error 0x" |
| << rtc::ToHex(hr) << ". No webcam exist?"; |
| return 0; |
| } |
| _dsMonikerDevEnum->Reset(); |
| ULONG cFetched; |
| IMoniker* pM; |
| |
| IBaseFilter* captureFilter = NULL; |
| bool deviceFound = false; |
| while (S_OK == _dsMonikerDevEnum->Next(1, &pM, &cFetched) && !deviceFound) { |
| IPropertyBag* pBag; |
| hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pBag); |
| if (S_OK == hr) { |
| // Find the description or friendly name. |
| VARIANT varName; |
| VariantInit(&varName); |
| if (deviceUniqueIdUTF8Length > 0) { |
| hr = pBag->Read(L"DevicePath", &varName, 0); |
| if (FAILED(hr)) { |
| hr = pBag->Read(L"Description", &varName, 0); |
| if (FAILED(hr)) { |
| hr = pBag->Read(L"FriendlyName", &varName, 0); |
| } |
| } |
| if (SUCCEEDED(hr)) { |
| char tempDevicePathUTF8[256]; |
| tempDevicePathUTF8[0] = 0; |
| WideCharToMultiByte(CP_UTF8, 0, varName.bstrVal, -1, |
| tempDevicePathUTF8, sizeof(tempDevicePathUTF8), |
| NULL, NULL); |
| if (strncmp(tempDevicePathUTF8, (const char*)deviceUniqueIdUTF8, |
| deviceUniqueIdUTF8Length) == 0) { |
| // We have found the requested device |
| deviceFound = true; |
| hr = |
| pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&captureFilter); |
| if |
| FAILED(hr) { |
| RTC_LOG(LS_ERROR) << "Failed to bind to the selected " |
| << "capture device " << hr; |
| } |
| |
| if (productUniqueIdUTF8 && |
| productUniqueIdUTF8Length > 0) // Get the device name |
| { |
| GetProductId(deviceUniqueIdUTF8, productUniqueIdUTF8, |
| productUniqueIdUTF8Length); |
| } |
| } |
| } |
| } |
| VariantClear(&varName); |
| pBag->Release(); |
| pM->Release(); |
| } |
| } |
| return captureFilter; |
| } |
| |
| int32_t DeviceInfoDS::GetWindowsCapability( |
| const int32_t capabilityIndex, |
| VideoCaptureCapabilityWindows& windowsCapability) { |
| ReadLockScoped cs(_apiLock); |
| |
| if (capabilityIndex < 0 || static_cast<size_t>(capabilityIndex) >= |
| _captureCapabilitiesWindows.size()) { |
| return -1; |
| } |
| |
| windowsCapability = _captureCapabilitiesWindows[capabilityIndex]; |
| return 0; |
| } |
| |
| int32_t DeviceInfoDS::CreateCapabilityMap(const char* deviceUniqueIdUTF8) |
| |
| { |
| // Reset old capability list |
| _captureCapabilities.clear(); |
| |
| const int32_t deviceUniqueIdUTF8Length = |
| (int32_t)strlen((char*)deviceUniqueIdUTF8); |
| if (deviceUniqueIdUTF8Length > kVideoCaptureUniqueNameLength) { |
| RTC_LOG(LS_INFO) << "Device name too long"; |
| return -1; |
| } |
| RTC_LOG(LS_INFO) << "CreateCapabilityMap called for device " |
| << deviceUniqueIdUTF8; |
| |
| char productId[kVideoCaptureProductIdLength]; |
| IBaseFilter* captureDevice = DeviceInfoDS::GetDeviceFilter( |
| deviceUniqueIdUTF8, productId, kVideoCaptureProductIdLength); |
| if (!captureDevice) |
| return -1; |
| IPin* outputCapturePin = GetOutputPin(captureDevice, GUID_NULL); |
| if (!outputCapturePin) { |
| RTC_LOG(LS_INFO) << "Failed to get capture device output pin"; |
| RELEASE_AND_CLEAR(captureDevice); |
| return -1; |
| } |
| IAMExtDevice* extDevice = NULL; |
| HRESULT hr = |
| captureDevice->QueryInterface(IID_IAMExtDevice, (void**)&extDevice); |
| if (SUCCEEDED(hr) && extDevice) { |
| RTC_LOG(LS_INFO) << "This is an external device"; |
| extDevice->Release(); |
| } |
| |
| IAMStreamConfig* streamConfig = NULL; |
| hr = outputCapturePin->QueryInterface(IID_IAMStreamConfig, |
| (void**)&streamConfig); |
| if (FAILED(hr)) { |
| RTC_LOG(LS_INFO) << "Failed to get IID_IAMStreamConfig interface " |
| << "from capture device"; |
| return -1; |
| } |
| |
| // this gets the FPS |
| IAMVideoControl* videoControlConfig = NULL; |
| HRESULT hrVC = captureDevice->QueryInterface(IID_IAMVideoControl, |
| (void**)&videoControlConfig); |
| if (FAILED(hrVC)) { |
| RTC_LOG(LS_INFO) << "IID_IAMVideoControl Interface NOT SUPPORTED"; |
| } |
| |
| AM_MEDIA_TYPE* pmt = NULL; |
| VIDEO_STREAM_CONFIG_CAPS caps; |
| int count, size; |
| |
| hr = streamConfig->GetNumberOfCapabilities(&count, &size); |
| if (FAILED(hr)) { |
| RTC_LOG(LS_INFO) << "Failed to GetNumberOfCapabilities"; |
| RELEASE_AND_CLEAR(videoControlConfig); |
| RELEASE_AND_CLEAR(streamConfig); |
| RELEASE_AND_CLEAR(outputCapturePin); |
| RELEASE_AND_CLEAR(captureDevice); |
| return -1; |
| } |
| |
| // Check if the device support formattype == FORMAT_VideoInfo2 and |
| // FORMAT_VideoInfo. Prefer FORMAT_VideoInfo since some cameras (ZureCam) has |
| // been seen having problem with MJPEG and FORMAT_VideoInfo2 Interlace flag is |
| // only supported in FORMAT_VideoInfo2 |
| bool supportFORMAT_VideoInfo2 = false; |
| bool supportFORMAT_VideoInfo = false; |
| bool foundInterlacedFormat = false; |
| GUID preferedVideoFormat = FORMAT_VideoInfo; |
| for (int32_t tmp = 0; tmp < count; ++tmp) { |
| hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps)); |
| if (hr == S_OK) { |
| if (pmt->majortype == MEDIATYPE_Video && |
| pmt->formattype == FORMAT_VideoInfo2) { |
| RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2"; |
| supportFORMAT_VideoInfo2 = true; |
| VIDEOINFOHEADER2* h = |
| reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat); |
| assert(h); |
| foundInterlacedFormat |= |
| h->dwInterlaceFlags & |
| (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly); |
| } |
| if (pmt->majortype == MEDIATYPE_Video && |
| pmt->formattype == FORMAT_VideoInfo) { |
| RTC_LOG(LS_INFO) << "Device support FORMAT_VideoInfo2"; |
| supportFORMAT_VideoInfo = true; |
| } |
| } |
| } |
| if (supportFORMAT_VideoInfo2) { |
| if (supportFORMAT_VideoInfo && !foundInterlacedFormat) { |
| preferedVideoFormat = FORMAT_VideoInfo; |
| } else { |
| preferedVideoFormat = FORMAT_VideoInfo2; |
| } |
| } |
| |
| for (int32_t tmp = 0; tmp < count; ++tmp) { |
| hr = streamConfig->GetStreamCaps(tmp, &pmt, reinterpret_cast<BYTE*>(&caps)); |
| if (hr != S_OK) { |
| RTC_LOG(LS_INFO) << "Failed to GetStreamCaps"; |
| RELEASE_AND_CLEAR(videoControlConfig); |
| RELEASE_AND_CLEAR(streamConfig); |
| RELEASE_AND_CLEAR(outputCapturePin); |
| RELEASE_AND_CLEAR(captureDevice); |
| return -1; |
| } |
| |
| if (pmt->majortype == MEDIATYPE_Video && |
| pmt->formattype == preferedVideoFormat) { |
| VideoCaptureCapabilityWindows capability; |
| int64_t avgTimePerFrame = 0; |
| |
| if (pmt->formattype == FORMAT_VideoInfo) { |
| VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat); |
| assert(h); |
| capability.directShowCapabilityIndex = tmp; |
| capability.width = h->bmiHeader.biWidth; |
| capability.height = h->bmiHeader.biHeight; |
| avgTimePerFrame = h->AvgTimePerFrame; |
| } |
| if (pmt->formattype == FORMAT_VideoInfo2) { |
| VIDEOINFOHEADER2* h = |
| reinterpret_cast<VIDEOINFOHEADER2*>(pmt->pbFormat); |
| assert(h); |
| capability.directShowCapabilityIndex = tmp; |
| capability.width = h->bmiHeader.biWidth; |
| capability.height = h->bmiHeader.biHeight; |
| capability.interlaced = |
| h->dwInterlaceFlags & |
| (AMINTERLACE_IsInterlaced | AMINTERLACE_DisplayModeBobOnly); |
| avgTimePerFrame = h->AvgTimePerFrame; |
| } |
| |
| if (hrVC == S_OK) { |
| LONGLONG* frameDurationList; |
| LONGLONG maxFPS; |
| long listSize; |
| SIZE size; |
| size.cx = capability.width; |
| size.cy = capability.height; |
| |
| // GetMaxAvailableFrameRate doesn't return max frame rate always |
| // eg: Logitech Notebook. This may be due to a bug in that API |
| // because GetFrameRateList array is reversed in the above camera. So |
| // a util method written. Can't assume the first value will return |
| // the max fps. |
| hrVC = videoControlConfig->GetFrameRateList( |
| outputCapturePin, tmp, size, &listSize, &frameDurationList); |
| |
| // On some odd cameras, you may get a 0 for duration. |
| // GetMaxOfFrameArray returns the lowest duration (highest FPS) |
| if (hrVC == S_OK && listSize > 0 && |
| 0 != (maxFPS = GetMaxOfFrameArray(frameDurationList, listSize))) { |
| capability.maxFPS = static_cast<int>(10000000 / maxFPS); |
| capability.supportFrameRateControl = true; |
| } else // use existing method |
| { |
| RTC_LOG(LS_INFO) << "GetMaxAvailableFrameRate NOT SUPPORTED"; |
| if (avgTimePerFrame > 0) |
| capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame); |
| else |
| capability.maxFPS = 0; |
| } |
| } else // use existing method in case IAMVideoControl is not supported |
| { |
| if (avgTimePerFrame > 0) |
| capability.maxFPS = static_cast<int>(10000000 / avgTimePerFrame); |
| else |
| capability.maxFPS = 0; |
| } |
| |
| // can't switch MEDIATYPE :~( |
| if (pmt->subtype == MEDIASUBTYPE_I420) { |
| capability.videoType = VideoType::kI420; |
| } else if (pmt->subtype == MEDIASUBTYPE_IYUV) { |
| capability.videoType = VideoType::kIYUV; |
| } else if (pmt->subtype == MEDIASUBTYPE_RGB24) { |
| capability.videoType = VideoType::kRGB24; |
| } else if (pmt->subtype == MEDIASUBTYPE_YUY2) { |
| capability.videoType = VideoType::kYUY2; |
| } else if (pmt->subtype == MEDIASUBTYPE_RGB565) { |
| capability.videoType = VideoType::kRGB565; |
| } else if (pmt->subtype == MEDIASUBTYPE_MJPG) { |
| capability.videoType = VideoType::kMJPEG; |
| } else if (pmt->subtype == MEDIASUBTYPE_dvsl || |
| pmt->subtype == MEDIASUBTYPE_dvsd || |
| pmt->subtype == |
| MEDIASUBTYPE_dvhd) // If this is an external DV camera |
| { |
| capability.videoType = |
| VideoType::kYUY2; // MS DV filter seems to create this type |
| } else if (pmt->subtype == |
| MEDIASUBTYPE_UYVY) // Seen used by Declink capture cards |
| { |
| capability.videoType = VideoType::kUYVY; |
| } else if (pmt->subtype == |
| MEDIASUBTYPE_HDYC) // Seen used by Declink capture cards. Uses |
| // BT. 709 color. Not entiry correct to use |
| // UYVY. http://en.wikipedia.org/wiki/YCbCr |
| { |
| RTC_LOG(LS_INFO) << "Device support HDYC."; |
| capability.videoType = VideoType::kUYVY; |
| } else { |
| WCHAR strGuid[39]; |
| StringFromGUID2(pmt->subtype, strGuid, 39); |
| RTC_LOG(LS_WARNING) |
| << "Device support unknown media type " << strGuid << ", width " |
| << capability.width << ", height " << capability.height; |
| continue; |
| } |
| |
| _captureCapabilities.push_back(capability); |
| _captureCapabilitiesWindows.push_back(capability); |
| RTC_LOG(LS_INFO) << "Camera capability, width:" << capability.width |
| << " height:" << capability.height |
| << " type:" << static_cast<int>(capability.videoType) |
| << " fps:" << capability.maxFPS; |
| } |
| FreeMediaType(pmt); |
| pmt = NULL; |
| } |
| RELEASE_AND_CLEAR(streamConfig); |
| RELEASE_AND_CLEAR(videoControlConfig); |
| RELEASE_AND_CLEAR(outputCapturePin); |
| RELEASE_AND_CLEAR(captureDevice); // Release the capture device |
| |
| // Store the new used device name |
| _lastUsedDeviceNameLength = deviceUniqueIdUTF8Length; |
| _lastUsedDeviceName = |
| (char*)realloc(_lastUsedDeviceName, _lastUsedDeviceNameLength + 1); |
| memcpy(_lastUsedDeviceName, deviceUniqueIdUTF8, |
| _lastUsedDeviceNameLength + 1); |
| RTC_LOG(LS_INFO) << "CreateCapabilityMap " << _captureCapabilities.size(); |
| |
| return static_cast<int32_t>(_captureCapabilities.size()); |
| } |
| |
| // Constructs a product ID from the Windows DevicePath. on a USB device the |
| // devicePath contains product id and vendor id. This seems to work for firewire |
| // as well. |
| // Example of device path: |
| // "\\?\usb#vid_0408&pid_2010&mi_00#7&258e7aaf&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" |
| // "\\?\avc#sony&dv-vcr&camcorder&dv#65b2d50301460008#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" |
| void DeviceInfoDS::GetProductId(const char* devicePath, |
| char* productUniqueIdUTF8, |
| uint32_t productUniqueIdUTF8Length) { |
| *productUniqueIdUTF8 = '\0'; |
| char* startPos = strstr((char*)devicePath, "\\\\?\\"); |
| if (!startPos) { |
| strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1); |
| RTC_LOG(LS_INFO) << "Failed to get the product Id"; |
| return; |
| } |
| startPos += 4; |
| |
| char* pos = strchr(startPos, '&'); |
| if (!pos || pos >= (char*)devicePath + strlen((char*)devicePath)) { |
| strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1); |
| RTC_LOG(LS_INFO) << "Failed to get the product Id"; |
| return; |
| } |
| // Find the second occurrence. |
| pos = strchr(pos + 1, '&'); |
| uint32_t bytesToCopy = (uint32_t)(pos - startPos); |
| if (pos && (bytesToCopy <= productUniqueIdUTF8Length) && |
| bytesToCopy <= kVideoCaptureProductIdLength) { |
| strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, |
| (char*)startPos, bytesToCopy); |
| } else { |
| strncpy_s((char*)productUniqueIdUTF8, productUniqueIdUTF8Length, "", 1); |
| RTC_LOG(LS_INFO) << "Failed to get the product Id"; |
| } |
| } |
| |
| int32_t DeviceInfoDS::DisplayCaptureSettingsDialogBox( |
| const char* deviceUniqueIdUTF8, |
| const char* dialogTitleUTF8, |
| void* parentWindow, |
| uint32_t positionX, |
| uint32_t positionY) { |
| ReadLockScoped cs(_apiLock); |
| HWND window = (HWND)parentWindow; |
| |
| IBaseFilter* filter = GetDeviceFilter(deviceUniqueIdUTF8, NULL, 0); |
| if (!filter) |
| return -1; |
| |
| ISpecifyPropertyPages* pPages = NULL; |
| CAUUID uuid; |
| HRESULT hr = S_OK; |
| |
| hr = filter->QueryInterface(IID_ISpecifyPropertyPages, (LPVOID*)&pPages); |
| if (!SUCCEEDED(hr)) { |
| filter->Release(); |
| return -1; |
| } |
| hr = pPages->GetPages(&uuid); |
| if (!SUCCEEDED(hr)) { |
| filter->Release(); |
| return -1; |
| } |
| |
| WCHAR tempDialogTitleWide[256]; |
| tempDialogTitleWide[0] = 0; |
| int size = 255; |
| |
| // UTF-8 to wide char |
| MultiByteToWideChar(CP_UTF8, 0, (char*)dialogTitleUTF8, -1, |
| tempDialogTitleWide, size); |
| |
| // Invoke a dialog box to display. |
| |
| hr = OleCreatePropertyFrame( |
| window, // You must create the parent window. |
| positionX, // Horizontal position for the dialog box. |
| positionY, // Vertical position for the dialog box. |
| tempDialogTitleWide, // String used for the dialog box caption. |
| 1, // Number of pointers passed in pPlugin. |
| (LPUNKNOWN*)&filter, // Pointer to the filter. |
| uuid.cElems, // Number of property pages. |
| uuid.pElems, // Array of property page CLSIDs. |
| LOCALE_USER_DEFAULT, // Locale ID for the dialog box. |
| 0, NULL); // Reserved |
| // Release memory. |
| if (uuid.pElems) { |
| CoTaskMemFree(uuid.pElems); |
| } |
| filter->Release(); |
| return 0; |
| } |
| } // namespace videocapturemodule |
| } // namespace webrtc |