| // 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 |