639 lines
24 KiB
C++
639 lines
24 KiB
C++
/*
|
|
* 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 <assert.h>
|
|
#include <dvdmedia.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"
|
|
|
|
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
|