blob: c63a835fbc7169f0b249e1465e3a6fe2f221110e [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/capture/video/win/video_capture_device_factory_win.h"
#include <mfapi.h>
#include <mferror.h>
#include <objbase.h>
#include <stddef.h>
#include <windows.devices.enumeration.h>
#include <windows.foundation.collections.h>
#include <wrl.h>
#include <wrl/client.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/cxx17_backports.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/win/core_winrt_util.h"
#include "base/win/scoped_co_mem.h"
#include "base/win/scoped_variant.h"
#include "base/win/windows_version.h"
#include "media/base/media_switches.h"
#include "media/base/win/mf_initializer.h"
#include "media/capture/capture_switches.h"
#include "media/capture/video/win/metrics.h"
#include "media/capture/video/win/video_capture_device_mf_win.h"
#include "media/capture/video/win/video_capture_device_win.h"
using DevicesInfo = std::vector<media::VideoCaptureDeviceInfo>;
using base::win::GetActivationFactory;
using base::win::ScopedCoMem;
using base::win::ScopedHString;
using base::win::ScopedVariant;
using Microsoft::WRL::ComPtr;
namespace media {
namespace {
// In Windows device identifiers, the USB VID and PID are preceded by the string
// "vid_" or "pid_". The identifiers are each 4 bytes long.
const char kVidPrefix[] = "vid_"; // Also contains '\0'.
const char kPidPrefix[] = "pid_"; // Also contains '\0'.
const size_t kVidPidSize = 4;
// AQS device selector string to filter enumerated DeviceInformation objects to
// KSCATEGORY_SENSOR_CAMERA (Class GUID 24e552d7-6523-47F7-a647-d3465bf1f5ca)
// OR KSCATEGORY_VIDEO_CAMERA (Class GUID e5323777-f976-4f5b-9b55-b94699c46e44).
const wchar_t* kVideoAndSensorCamerasAqsString =
L"(System.Devices.InterfaceClassGuid:="
L"\"{e5323777-f976-4f5b-9b55-b94699c46e44}\" AND "
L"(System.Devices.WinPhone8CameraFlags:=[] OR "
L"System.Devices.WinPhone8CameraFlags:<4096)) OR "
L"System.Devices.InterfaceClassGuid:="
L"\"{24e552d7-6523-47f7-a647-d3465bf1f5ca}\" AND "
L"System.Devices.InterfaceEnabled:=System.StructuredQueryType.Boolean#True";
// Class GUID for KSCATEGORY_VIDEO_CAMERA. Only devices from that category will
// contain this GUID in their |device_id|.
const char kVideoCameraGuid[] = "e5323777-f976-4f5b-9b55-b94699c46e44";
// Avoid enumerating and/or using certain devices due to they provoking crashes
// or any other reason (http://crbug.com/378494). This enum is defined for the
// purposes of UMA collection. Existing entries cannot be removed.
enum BlockedCameraNames {
BLOCKED_CAMERA_GOOGLE_CAMERA_ADAPTER = 0,
BLOCKED_CAMERA_IP_CAMERA = 1,
BLOCKED_CAMERA_CYBERLINK_WEBCAM_SPLITTER = 2,
BLOCKED_CAMERA_EPOCCAM = 3,
// This one must be last, and equal to the previous enumerated value.
BLOCKED_CAMERA_MAX = BLOCKED_CAMERA_EPOCCAM,
};
#define UWP_ENUM_ERROR_HANDLER(hr, err_log) \
DLOG(WARNING) << err_log << logging::SystemErrorCodeToString(hr); \
origin_task_runner_->PostTask(FROM_HERE, \
base::BindOnce(device_info_callback, nullptr))
// Blocked devices are identified by a characteristic prefix of the name.
// This prefix is used case-insensitively. This list must be kept in sync with
// |BlockedCameraNames|.
const char* const kBlockedCameraNames[] = {
// Name of a fake DirectShow filter on computers with GTalk installed.
"Google Camera Adapter",
// The following software WebCams cause crashes.
"IP Camera [JPEG/MJPEG]",
"CyberLink Webcam Splitter",
"EpocCam",
};
static_assert(base::size(kBlockedCameraNames) == BLOCKED_CAMERA_MAX + 1,
"kBlockedCameraNames should be same size as "
"BlockedCameraNames enum");
// Use this list only for USB webcams.
const char* const kModelIdsBlockedForMediaFoundation[] = {
// Devices using Empia 2860 or 2820 chips, see https://crbug.com/849636.
"eb1a:2860", "eb1a:2820", "1ce6:2820",
// Elgato HD60 Pro
"12ab:0380",
// Sensoray 2253
"1943:2253",
// Dell E5440
"0c45:64d0", "0c45:64d2",
// Dell E7440
"1bcf:2985",
// Lenovo Thinkpad Model 20CG0006FMZ front and rear cameras, see
// also https://crbug.com/924528.
"04ca:7047", "04ca:7048",
// HP Elitebook 840 G1
"04f2:b3ed", "04f2:b3ca", "05c8:035d", "05c8:0369",
// HP HD Camera. See https://crbug.com/1011888.
"04ca:7095",
// RBG/IR camera for Windows Hello Face Auth. See https://crbug.com/984864.
"13d3:5257",
// Acer Aspire f5-573g. See https://crbug.com/1034644.
"0bda:57f2",
// Elgato Camlink 4k
"0fd9:0066"};
// Use this list only for non-USB webcams.
const char* const kDisplayNamesBlockedForMediaFoundation[] = {
// VMware Virtual Webcams cause hangs when there is no physical Webcam.
// See https://crbug.com/1044974.
"VMware Virtual Webcam"};
const std::vector<
std::pair<VideoCaptureApi, std::vector<std::pair<GUID, GUID>>>>&
GetMFAttributes() {
if (base::FeatureList::IsEnabled(
media::kIncludeIRCamerasInDeviceEnumeration)) {
static const base::NoDestructor<std::vector<
std::pair<VideoCaptureApi, std::vector<std::pair<GUID, GUID>>>>>
mf_attributes({{{VideoCaptureApi::WIN_MEDIA_FOUNDATION,
{
{MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID},
}},
{VideoCaptureApi::WIN_MEDIA_FOUNDATION_SENSOR,
{{MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID},
{MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY,
KSCATEGORY_SENSOR_CAMERA}}}}});
return *mf_attributes;
}
static const base::NoDestructor<std::vector<
std::pair<VideoCaptureApi, std::vector<std::pair<GUID, GUID>>>>>
mf_attributes({{VideoCaptureApi::WIN_MEDIA_FOUNDATION,
{
{MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID},
}}});
return *mf_attributes;
}
bool IsDeviceBlockedForQueryingDetailedFrameRates(
const std::string& display_name) {
return display_name.find("WebcamMax") != std::string::npos;
}
bool IsDeviceBlockedForMediaFoundationByModelId(const std::string& model_id) {
return base::Contains(kModelIdsBlockedForMediaFoundation, model_id);
}
bool IsDeviceBlockedForMediaFoundationByDisplayName(
const std::string& display_name) {
return base::Contains(kDisplayNamesBlockedForMediaFoundation, display_name);
}
bool LoadMediaFoundationDlls() {
static const wchar_t* const kMfDLLs[] = {
L"%WINDIR%\\system32\\mf.dll", L"%WINDIR%\\system32\\mfplat.dll",
L"%WINDIR%\\system32\\mfreadwrite.dll",
L"%WINDIR%\\system32\\MFCaptureEngine.dll"};
// Mitigate the issues caused by loading DLLs on a background thread
// (http://crbug/973868).
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
for (const wchar_t* kMfDLL : kMfDLLs) {
wchar_t path[MAX_PATH] = {0};
ExpandEnvironmentStringsW(kMfDLL, path, base::size(path));
if (!LoadLibraryExW(path, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH))
return false;
}
return true;
}
bool PrepareVideoCaptureAttributesMediaFoundation(
const std::vector<std::pair<GUID, GUID>>& attributes_data,
int count,
IMFAttributes** attributes) {
DCHECK(attributes);
DCHECK(!*attributes);
// Once https://bugs.chromium.org/p/chromium/issues/detail?id=791615 is fixed,
// we must make sure that this method succeeds in capture_unittests context
// when MediaFoundation is enabled.
if (!VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation())
return false;
if (FAILED(MFCreateAttributes(attributes, count)))
return false;
for (const auto& value : attributes_data) {
if (!SUCCEEDED((*attributes)->SetGUID(value.first, value.second)))
return false;
}
return true;
}
bool IsDeviceBlocked(const std::string& name) {
DCHECK_EQ(BLOCKED_CAMERA_MAX + 1,
static_cast<int>(base::size(kBlockedCameraNames)));
for (size_t i = 0; i < base::size(kBlockedCameraNames); ++i) {
if (base::StartsWith(name, kBlockedCameraNames[i],
base::CompareCase::INSENSITIVE_ASCII)) {
DVLOG(1) << "Enumerated blocked device: " << name;
UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.BlacklistedDevice", i,
BLOCKED_CAMERA_MAX + 1);
return true;
}
}
return false;
}
std::string GetDeviceModelId(const std::string& device_id) {
const size_t vid_prefix_size = sizeof(kVidPrefix) - 1;
const size_t pid_prefix_size = sizeof(kPidPrefix) - 1;
const size_t vid_location = device_id.find(kVidPrefix);
if (vid_location == std::string::npos ||
vid_location + vid_prefix_size + kVidPidSize > device_id.size()) {
return std::string();
}
const size_t pid_location = device_id.find(kPidPrefix);
if (pid_location == std::string::npos ||
pid_location + pid_prefix_size + kVidPidSize > device_id.size()) {
return std::string();
}
const std::string id_vendor =
device_id.substr(vid_location + vid_prefix_size, kVidPidSize);
const std::string id_product =
device_id.substr(pid_location + pid_prefix_size, kVidPidSize);
return id_vendor + ":" + id_product;
}
bool DevicesInfoContainsDeviceId(const DevicesInfo& devices_info,
const std::string& device_id) {
return std::find_if(devices_info.begin(), devices_info.end(),
[device_id](const VideoCaptureDeviceInfo& device_info) {
return device_id == device_info.descriptor.device_id;
}) != devices_info.end();
}
// Returns a non DirectShow descriptor DevicesInfo with the provided name and
// model.
DevicesInfo::const_iterator FindNonDirectShowDeviceInfoByNameAndModel(
const DevicesInfo& devices_info,
const std::string& name_and_model) {
return std::find_if(
devices_info.begin(), devices_info.end(),
[name_and_model](const VideoCaptureDeviceInfo& device_info) {
return device_info.descriptor.capture_api !=
VideoCaptureApi::WIN_DIRECT_SHOW &&
name_and_model == device_info.descriptor.GetNameAndModel();
});
}
bool IsEnclosureLocationSupported() {
// DeviceInformation class is only available in Win10 onwards (v10.0.10240.0).
if (base::win::GetVersion() < base::win::Version::WIN10) {
DVLOG(1) << "DeviceInformation not supported before Windows 10";
return false;
}
if (!(base::win::ResolveCoreWinRTDelayload() &&
ScopedHString::ResolveCoreWinRTStringDelayload())) {
DLOG(ERROR) << "Failed loading functions from combase.dll";
return false;
}
return true;
}
void FindAndSetDefaultVideoCamera(
std::vector<VideoCaptureDeviceInfo>* devices_info) {
// When available, the default video camera should be external with
// MEDIA_VIDEO_FACING_NONE. Otherwise, it should be internal with
// MEDIA_VIDEO_FACING_USER. It occupies the first index in |devices_info|.
for (auto it = devices_info->begin(); it != devices_info->end(); ++it) {
// Default video camera belongs to KSCATEGORY_VIDEO_CAMERA.
if (it->descriptor.device_id.find(kVideoCameraGuid) != std::string::npos) {
if (it->descriptor.facing == VideoFacingMode::MEDIA_VIDEO_FACING_NONE) {
std::iter_swap(devices_info->begin(), it);
break; // Stop iterating once an external video camera is found.
} else if (it->descriptor.facing ==
VideoFacingMode::MEDIA_VIDEO_FACING_USER) {
std::iter_swap(devices_info->begin(), it);
}
}
}
}
} // namespace
// Returns true if the current platform supports the Media Foundation API
// and that the DLLs are available. On Vista this API is an optional download
// but the API is advertised as a part of Windows 7 and onwards. However,
// we've seen that the required DLLs are not available in some Win7
// distributions such as Windows 7 N and Windows 7 KN.
// static
bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
static const bool has_media_foundation =
LoadMediaFoundationDlls() && InitializeMediaFoundation();
return has_media_foundation;
}
VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin()
: use_media_foundation_(
base::FeatureList::IsEnabled(media::kMediaFoundationVideoCapture)),
use_d3d11_with_media_foundation_(
base::FeatureList::IsEnabled(
media::kMediaFoundationD3D11VideoCapture) &&
switches::IsVideoCaptureUseGpuMemoryBufferEnabled()),
com_thread_("Windows Video Capture COM Thread") {
if (use_media_foundation_ && !PlatformSupportsMediaFoundation()) {
use_media_foundation_ = false;
LogVideoCaptureWinBackendUsed(
VideoCaptureWinBackendUsed::kUsingDirectShowAsFallback);
} else if (use_media_foundation_) {
LogVideoCaptureWinBackendUsed(
VideoCaptureWinBackendUsed::kUsingMediaFoundationAsDefault);
} else {
LogVideoCaptureWinBackendUsed(
VideoCaptureWinBackendUsed::kUsingDirectShowAsDefault);
}
}
VideoCaptureDeviceFactoryWin::~VideoCaptureDeviceFactoryWin() {
if (com_thread_.IsRunning()) {
com_thread_.Stop();
}
}
std::unique_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::CreateDevice(
const VideoCaptureDeviceDescriptor& device_descriptor) {
DCHECK(thread_checker_.CalledOnValidThread());
switch (device_descriptor.capture_api) {
case VideoCaptureApi::WIN_MEDIA_FOUNDATION:
FALLTHROUGH;
case VideoCaptureApi::WIN_MEDIA_FOUNDATION_SENSOR: {
DCHECK(PlatformSupportsMediaFoundation());
ComPtr<IMFMediaSource> source;
if (!CreateDeviceSourceMediaFoundation(device_descriptor.device_id,
device_descriptor.capture_api,
&source)) {
break;
}
auto device = std::make_unique<VideoCaptureDeviceMFWin>(
device_descriptor, std::move(source), dxgi_device_manager_);
DVLOG(1) << " MediaFoundation Device: "
<< device_descriptor.display_name();
if (device->Init())
return device;
break;
}
case VideoCaptureApi::WIN_DIRECT_SHOW: {
ComPtr<IBaseFilter> capture_filter;
if (!CreateDeviceFilterDirectShow(device_descriptor.device_id,
&capture_filter)) {
break;
}
auto device = std::make_unique<VideoCaptureDeviceWin>(
device_descriptor, std::move(capture_filter));
DVLOG(1) << " DirectShow Device: " << device_descriptor.display_name();
if (device->Init())
return device;
break;
}
default:
NOTREACHED();
break;
}
return nullptr;
}
bool VideoCaptureDeviceFactoryWin::CreateDeviceEnumMonikerDirectShow(
IEnumMoniker** enum_moniker) {
DCHECK(enum_moniker);
DCHECK(!*enum_moniker);
// Mitigate the issues caused by loading DLLs on a background thread
// (http://crbug/973868).
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
ComPtr<ICreateDevEnum> dev_enum;
HRESULT hr = ::CoCreateInstance(CLSID_SystemDeviceEnum, nullptr,
CLSCTX_INPROC, IID_PPV_ARGS(&dev_enum));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to create system device enumerator: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
enum_moniker, 0);
// CreateClassEnumerator returns S_FALSE on some Windows OS
// when no camera exist. Therefore the FAILED macro can't be used.
if (hr != S_OK) {
DLOG(ERROR) << "Failed to create video input device enum moniker: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
return true;
}
bool VideoCaptureDeviceFactoryWin::CreateDeviceFilterDirectShow(
const std::string& device_id,
IBaseFilter** capture_filter) {
DCHECK(capture_filter);
DCHECK(!*capture_filter);
ComPtr<IEnumMoniker> enum_moniker;
if (!CreateDeviceEnumMonikerDirectShow(&enum_moniker))
return false;
HRESULT hr = S_OK;
for (ComPtr<IMoniker> moniker;
enum_moniker->Next(1, &moniker, nullptr) == S_OK; moniker.Reset()) {
ComPtr<IPropertyBag> prop_bag;
hr = moniker->BindToStorage(0, 0, IID_PPV_ARGS(&prop_bag));
if (FAILED(hr))
continue;
// Find |device_id| via DevicePath, Description or FriendlyName, whichever
// is available first and is a VT_BSTR (i.e. String) type.
static const wchar_t* kPropertyNames[] = {L"DevicePath", L"Description",
L"FriendlyName"};
ScopedVariant name;
for (const auto* property_name : kPropertyNames) {
prop_bag->Read(property_name, name.Receive(), 0);
if (name.type() != VT_BSTR)
continue; // Continue to the next property.
const std::string device_path(base::SysWideToUTF8(V_BSTR(name.ptr())));
if (device_path.compare(device_id) != 0)
break; // Continue to the next moniker.
// We have found the requested device.
return CreateDeviceFilterDirectShow(std::move(moniker), capture_filter);
}
}
if (SUCCEEDED(hr))
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
DLOG(ERROR) << "Failed to find camera filter: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
bool VideoCaptureDeviceFactoryWin::CreateDeviceFilterDirectShow(
ComPtr<IMoniker> moniker,
IBaseFilter** capture_filter) {
DCHECK(capture_filter);
DCHECK(!*capture_filter);
// Mitigate the issues caused by loading DLLs on a background thread
// (http://crbug/973868).
SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
HRESULT hr = moniker->BindToObject(0, 0, IID_PPV_ARGS(capture_filter));
if (FAILED(hr)) {
DLOG(ERROR) << "Failed to bind camera filter: "
<< logging::SystemErrorCodeToString(hr);
return false;
}
return true;
}
bool VideoCaptureDeviceFactoryWin::CreateDeviceSourceMediaFoundation(
const std::string& device_id,
VideoCaptureApi capture_api,
IMFMediaSource** source) {
DCHECK(source);
DCHECK(!*source);
ComPtr<IMFAttributes> attributes;
DCHECK_EQ(GetMFAttributes()[0].first, VideoCaptureApi::WIN_MEDIA_FOUNDATION);
const auto& attributes_data =
capture_api == VideoCaptureApi::WIN_MEDIA_FOUNDATION
? GetMFAttributes()[0].second
: GetMFAttributes()[1].second;
// We allocate attributes_data.size() + 1 (+1 is for sym_link below) elements
// in attributes store.
if (!PrepareVideoCaptureAttributesMediaFoundation(
attributes_data, attributes_data.size() + 1, &attributes)) {
return false;
}
attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
base::SysUTF8ToWide(device_id).c_str());
return CreateDeviceSourceMediaFoundation(std::move(attributes), source);
}
bool VideoCaptureDeviceFactoryWin::CreateDeviceSourceMediaFoundation(
ComPtr<IMFAttributes> attributes,
IMFMediaSource** source_out) {
ComPtr<IMFMediaSource> source;
HRESULT hr = MFCreateDeviceSource(attributes.Get(), &source);
DLOG_IF(ERROR, FAILED(hr)) << "MFCreateDeviceSource failed: "
<< logging::SystemErrorCodeToString(hr);
if (SUCCEEDED(hr) && use_d3d11_with_media_foundation_ &&
dxgi_device_manager_) {
dxgi_device_manager_->RegisterWithMediaSource(source);
}
*source_out = source.Detach();
return SUCCEEDED(hr);
}
bool VideoCaptureDeviceFactoryWin::EnumerateDeviceSourcesMediaFoundation(
Microsoft::WRL::ComPtr<IMFAttributes> attributes,
IMFActivate*** devices,
UINT32* count) {
HRESULT hr = MFEnumDeviceSources(attributes.Get(), devices, count);
DLOG_IF(ERROR, FAILED(hr))
<< "MFEnumDeviceSources failed: " << logging::SystemErrorCodeToString(hr);
return SUCCEEDED(hr);
}
void VideoCaptureDeviceFactoryWin::GetDevicesInfo(
GetDevicesInfoCallback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<VideoCaptureDeviceInfo> devices_info;
if (use_media_foundation_) {
DCHECK(PlatformSupportsMediaFoundation());
devices_info = GetDevicesInfoMediaFoundation();
AugmentDevicesListWithDirectShowOnlyDevices(&devices_info);
} else {
devices_info = GetDevicesInfoDirectShow();
}
if (IsEnclosureLocationSupported()) {
origin_task_runner_ = base::ThreadTaskRunnerHandle::Get();
com_thread_.init_com_with_mta(true);
com_thread_.Start();
com_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VideoCaptureDeviceFactoryWin::EnumerateDevicesUWP,
base::Unretained(this), std::move(devices_info),
std::move(callback)));
} else {
DeviceInfoReady(std::move(devices_info), std::move(callback));
}
}
void VideoCaptureDeviceFactoryWin::EnumerateDevicesUWP(
std::vector<VideoCaptureDeviceInfo> devices_info,
GetDevicesInfoCallback result_callback) {
DCHECK_GE(base::win::OSInfo::GetInstance()->version_number().build, 10240u);
VideoCaptureDeviceFactoryWin* factory = this;
scoped_refptr<base::SingleThreadTaskRunner> com_thread_runner =
com_thread_.task_runner();
// The |device_info_callback| created by base::BindRepeating() is copyable,
// which is necessary for the below lambda function of |callback| for the
// asynchronous operation. The reason is to permanently capture anything in a
// lambda, it must be copyable, merely movable is insufficient.
auto device_info_callback = base::BindRepeating(
&VideoCaptureDeviceFactoryWin::FoundAllDevicesUWP,
base::Unretained(factory), base::Passed(&devices_info),
base::Passed(&result_callback));
auto callback =
Microsoft::WRL::Callback<
ABI::Windows::Foundation::IAsyncOperationCompletedHandler<
DeviceInformationCollection*>>(
[com_thread_runner, device_info_callback](
IAsyncOperation<DeviceInformationCollection*>* operation,
AsyncStatus status) -> HRESULT {
com_thread_runner->PostTask(
FROM_HERE, base::BindOnce(device_info_callback,
base::Unretained(operation)));
return S_OK;
});
ComPtr<ABI::Windows::Devices::Enumeration::IDeviceInformationStatics>
dev_info_statics;
HRESULT hr = GetActivationFactory<
ABI::Windows::Devices::Enumeration::IDeviceInformationStatics,
RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(
&dev_info_statics);
if (FAILED(hr)) {
UWP_ENUM_ERROR_HANDLER(hr, "DeviceInformation factory failed: ");
return;
}
IAsyncOperation<DeviceInformationCollection*>* async_op;
ScopedHString aqs_filter =
ScopedHString::Create(kVideoAndSensorCamerasAqsString);
hr = dev_info_statics->FindAllAsyncAqsFilter(aqs_filter.get(), &async_op);
if (FAILED(hr)) {
UWP_ENUM_ERROR_HANDLER(hr, "Find all devices asynchronously failed: ");
return;
}
hr = async_op->put_Completed(callback.Get());
if (FAILED(hr)) {
UWP_ENUM_ERROR_HANDLER(hr, "Register async operation callback failed: ");
return;
}
// Keep a reference to incomplete |asyn_op| for releasing later.
async_ops_.insert(async_op);
}
void VideoCaptureDeviceFactoryWin::FoundAllDevicesUWP(
std::vector<VideoCaptureDeviceInfo> devices_info,
GetDevicesInfoCallback result_callback,
IAsyncOperation<DeviceInformationCollection*>* operation) {
if (!operation) {
origin_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VideoCaptureDeviceFactoryWin::DeviceInfoReady,
weak_ptr_factory_.GetWeakPtr(), std::move(devices_info),
std::move(result_callback)));
return;
}
ComPtr<ABI::Windows::Foundation::Collections::IVectorView<
ABI::Windows::Devices::Enumeration::DeviceInformation*>>
devices;
operation->GetResults(&devices);
unsigned int count = 0;
if (devices) {
devices->get_Size(&count);
}
for (unsigned int j = 0; j < count; ++j) {
ComPtr<ABI::Windows::Devices::Enumeration::IDeviceInformation> device_info;
HRESULT hr = devices->GetAt(j, &device_info);
if (SUCCEEDED(hr)) {
HSTRING id;
device_info->get_Id(&id);
std::string device_id = ScopedHString(id).GetAsUTF8();
transform(device_id.begin(), device_id.end(), device_id.begin(),
::tolower);
ComPtr<ABI::Windows::Devices::Enumeration::IEnclosureLocation>
enclosure_location;
hr = device_info->get_EnclosureLocation(&enclosure_location);
if (FAILED(hr)) {
break;
}
VideoFacingMode facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
if (enclosure_location) {
ABI::Windows::Devices::Enumeration::Panel panel;
enclosure_location->get_Panel(&panel);
switch (panel) {
case ABI::Windows::Devices::Enumeration::Panel_Unknown:
facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
break;
case ABI::Windows::Devices::Enumeration::Panel_Front:
facing = VideoFacingMode::MEDIA_VIDEO_FACING_USER;
break;
case ABI::Windows::Devices::Enumeration::Panel_Back:
facing = VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT;
break;
default:
facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
}
}
for (auto& device : devices_info) {
if (!device.descriptor.device_id.compare(device_id)) {
device.descriptor.facing = facing;
break;
}
}
}
}
FindAndSetDefaultVideoCamera(&devices_info);
origin_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureDeviceFactoryWin::DeviceInfoReady,
base::Unretained(this), std::move(devices_info),
std::move(result_callback)));
auto it = async_ops_.find(operation);
DCHECK(it != async_ops_.end());
(*it)->Release();
async_ops_.erase(it);
}
void VideoCaptureDeviceFactoryWin::DeviceInfoReady(
std::vector<VideoCaptureDeviceInfo> devices_info,
GetDevicesInfoCallback result_callback) {
if (com_thread_.IsRunning()) {
com_thread_.Stop();
}
std::move(result_callback).Run(std::move(devices_info));
}
DevicesInfo VideoCaptureDeviceFactoryWin::GetDevicesInfoMediaFoundation() {
DVLOG(1) << " GetDevicesInfoMediaFoundation";
DevicesInfo devices_info;
if (use_d3d11_with_media_foundation_ && !dxgi_device_manager_) {
dxgi_device_manager_ = DXGIDeviceManager::Create();
}
// Recent non-RGB (depth, IR) cameras could be marked as sensor cameras in
// driver inf file and MFEnumDeviceSources enumerates them only if attribute
// KSCATEGORY_SENSOR_CAMERA is supplied. We enumerate twice. As it is possible
// that SENSOR_CAMERA is also in VIDEO_CAMERA category, we prevent duplicate
// entries. https://crbug.com/807293
for (const auto& api_attributes : GetMFAttributes()) {
ComPtr<IMFAttributes> attributes;
if (!PrepareVideoCaptureAttributesMediaFoundation(
api_attributes.second, api_attributes.second.size(), &attributes)) {
return {};
}
ScopedCoMem<IMFActivate*> devices;
UINT32 count;
if (!EnumerateDeviceSourcesMediaFoundation(std::move(attributes), &devices,
&count)) {
return {};
}
const bool list_was_empty = devices_info.empty();
for (UINT32 i = 0; i < count; ++i) {
ScopedCoMem<wchar_t> name;
UINT32 name_size;
HRESULT hr = devices[i]->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size);
if (SUCCEEDED(hr)) {
ScopedCoMem<wchar_t> id;
UINT32 id_size;
hr = devices[i]->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id,
&id_size);
if (SUCCEEDED(hr)) {
const std::string device_id =
base::SysWideToUTF8(std::wstring(id, id_size));
const std::string model_id = GetDeviceModelId(device_id);
const std::string display_name =
base::SysWideToUTF8(std::wstring(name, name_size));
if (IsDeviceBlockedForMediaFoundationByModelId(model_id) ||
IsDeviceBlockedForMediaFoundationByDisplayName(display_name)) {
continue;
}
if (list_was_empty ||
!DevicesInfoContainsDeviceId(devices_info, device_id)) {
ComPtr<IMFMediaSource> source;
VideoCaptureControlSupport control_support;
VideoCaptureFormats supported_formats;
if (CreateDeviceSourceMediaFoundation(
device_id, api_attributes.first, &source)) {
control_support =
VideoCaptureDeviceMFWin::GetControlSupport(source);
supported_formats =
GetSupportedFormatsMediaFoundation(source, display_name);
}
devices_info.emplace_back(VideoCaptureDeviceDescriptor(
display_name, device_id, model_id, api_attributes.first,
control_support));
devices_info.back().supported_formats =
std::move(supported_formats);
}
}
}
DLOG_IF(ERROR, FAILED(hr)) << "GetAllocatedString failed: "
<< logging::SystemErrorCodeToString(hr);
devices[i]->Release();
}
}
return devices_info;
}
// Adds descriptors that are only reported by the DirectShow API.
// Replaces MediaFoundation descriptors with corresponding DirectShow
// ones if the MediaFoundation one has no supported formats,
// but the DirectShow one does.
void VideoCaptureDeviceFactoryWin::AugmentDevicesListWithDirectShowOnlyDevices(
DevicesInfo* devices_info) {
// DirectShow virtual cameras are not supported by MediaFoundation.
// To overcome this, based on device name and model, we append
// missing DirectShow device descriptor to full devices list.
DevicesInfo direct_show_devices_info = GetDevicesInfoDirectShow();
for (const auto& direct_show_device_info : direct_show_devices_info) {
// DirectShow can produce two descriptors with same name and model.
// If those descriptors are missing from MediaFoundation, we want them both
// appended to the full descriptors list.
// Therefore, we prevent duplication by always comparing a DirectShow
// descriptor with a MediaFoundation one.
DevicesInfo::const_iterator matching_non_direct_show_device =
FindNonDirectShowDeviceInfoByNameAndModel(
*devices_info,
direct_show_device_info.descriptor.GetNameAndModel());
// Devices like the Pinnacle Dazzle, appear both in DirectShow and
// MediaFoundation. In MediaFoundation, they will have no supported video
// format while in DirectShow they will have at least one video format.
// Therefore, we must prioritize the MediaFoundation descriptor if it has at
// least one supported format
if (matching_non_direct_show_device != devices_info->end()) {
if (matching_non_direct_show_device->supported_formats.size() > 0)
continue;
if (direct_show_device_info.supported_formats.size() == 0)
continue;
devices_info->erase(matching_non_direct_show_device);
}
devices_info->emplace_back(direct_show_device_info);
}
}
DevicesInfo VideoCaptureDeviceFactoryWin::GetDevicesInfoDirectShow() {
DVLOG(1) << __func__;
ComPtr<IEnumMoniker> enum_moniker;
if (!CreateDeviceEnumMonikerDirectShow(&enum_moniker))
return {};
DevicesInfo devices_info;
// Enumerate all video capture devices.
for (ComPtr<IMoniker> moniker;
enum_moniker->Next(1, &moniker, nullptr) == S_OK; moniker.Reset()) {
ComPtr<IPropertyBag> prop_bag;
HRESULT hr = moniker->BindToStorage(0, 0, IID_PPV_ARGS(&prop_bag));
if (FAILED(hr))
continue;
// Find the description or friendly name.
ScopedVariant name;
hr = prop_bag->Read(L"Description", name.Receive(), 0);
if (FAILED(hr))
hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
if (FAILED(hr) || name.type() != VT_BSTR)
continue;
const std::string device_name(base::SysWideToUTF8(V_BSTR(name.ptr())));
if (IsDeviceBlocked(device_name))
continue;
name.Reset();
hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
std::string id;
if (FAILED(hr) || name.type() != VT_BSTR) {
id = device_name;
} else {
DCHECK_EQ(name.type(), VT_BSTR);
id = base::SysWideToUTF8(V_BSTR(name.ptr()));
}
const std::string model_id = GetDeviceModelId(id);
VideoCaptureControlSupport control_support;
VideoCaptureFormats supported_formats;
ComPtr<IBaseFilter> capture_filter;
if (CreateDeviceFilterDirectShow(std::move(moniker), &capture_filter)) {
control_support =
VideoCaptureDeviceWin::GetControlSupport(capture_filter);
supported_formats =
GetSupportedFormatsDirectShow(capture_filter, device_name);
}
devices_info.emplace_back(VideoCaptureDeviceDescriptor(
device_name, id, model_id, VideoCaptureApi::WIN_DIRECT_SHOW,
control_support));
devices_info.back().supported_formats = std::move(supported_formats);
}
return devices_info;
}
VideoCaptureFormats VideoCaptureDeviceFactoryWin::GetSupportedFormatsDirectShow(
ComPtr<IBaseFilter> capture_filter,
const std::string& display_name) {
VideoCaptureFormats formats;
bool query_detailed_frame_rates =
!IsDeviceBlockedForQueryingDetailedFrameRates(display_name);
CapabilityList capability_list;
VideoCaptureDeviceWin::GetDeviceCapabilityList(
capture_filter, query_detailed_frame_rates, &capability_list);
for (const auto& entry : capability_list) {
formats.emplace_back(entry.supported_format);
DVLOG(1) << display_name << " "
<< VideoCaptureFormat::ToString(entry.supported_format);
}
return formats;
}
VideoCaptureFormats
VideoCaptureDeviceFactoryWin::GetSupportedFormatsMediaFoundation(
ComPtr<IMFMediaSource> source,
const std::string& display_name) {
ComPtr<IMFAttributes> source_reader_attributes;
if (dxgi_device_manager_) {
dxgi_device_manager_->RegisterWithMediaSource(source);
HRESULT hr = MFCreateAttributes(&source_reader_attributes, 1);
if (SUCCEEDED(hr)) {
dxgi_device_manager_->RegisterInSourceReaderAttributes(
source_reader_attributes.Get());
} else {
DLOG(ERROR) << "MFCreateAttributes failed: "
<< logging::SystemErrorCodeToString(hr);
}
}
ComPtr<IMFSourceReader> reader;
HRESULT hr = MFCreateSourceReaderFromMediaSource(
source.Get(), source_reader_attributes.Get(), &reader);
if (FAILED(hr)) {
DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: "
<< logging::SystemErrorCodeToString(hr);
return {};
}
VideoCaptureFormats formats;
DWORD stream_index = 0;
ComPtr<IMFMediaType> type;
const bool dxgi_device_manager_available = dxgi_device_manager_ != nullptr;
while (SUCCEEDED(hr = reader->GetNativeMediaType(
static_cast<DWORD>(MF_SOURCE_READER_FIRST_VIDEO_STREAM),
stream_index, &type))) {
UINT32 width, height;
hr = MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE, &width, &height);
if (FAILED(hr)) {
DLOG(ERROR) << "MFGetAttributeSize failed: "
<< logging::SystemErrorCodeToString(hr);
return {};
}
VideoCaptureFormat capture_format;
capture_format.frame_size.SetSize(width, height);
UINT32 numerator, denominator;
hr = MFGetAttributeRatio(type.Get(), MF_MT_FRAME_RATE, &numerator,
&denominator);
if (FAILED(hr)) {
DLOG(ERROR) << "MFGetAttributeSize failed: "
<< logging::SystemErrorCodeToString(hr);
return {};
}
capture_format.frame_rate =
denominator ? static_cast<float>(numerator) / denominator : 0.0f;
GUID type_guid;
hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid);
if (FAILED(hr)) {
DLOG(ERROR) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr);
return {};
}
VideoCaptureDeviceMFWin::GetPixelFormatFromMFSourceMediaSubtype(
type_guid, /*use_hardware_format=*/dxgi_device_manager_available,
&capture_format.pixel_format);
type.Reset();
++stream_index;
if (capture_format.pixel_format == PIXEL_FORMAT_UNKNOWN)
continue;
formats.push_back(capture_format);
DVLOG(1) << display_name << " "
<< VideoCaptureFormat::ToString(capture_format);
}
return formats;
}
} // namespace media