| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "media/capture/video/win/video_capture_device_factory_win.h" | |
| 6 | |
| 7 #include <mfapi.h> | |
| 8 #include <mferror.h> | |
| 9 #include <stddef.h> | |
| 10 | |
| 11 #include "base/command_line.h" | |
| 12 #include "base/macros.h" | |
| 13 #include "base/metrics/histogram.h" | |
| 14 #include "base/strings/string_util.h" | |
| 15 #include "base/strings/sys_string_conversions.h" | |
| 16 #include "base/win/scoped_co_mem.h" | |
| 17 #include "base/win/scoped_variant.h" | |
| 18 #include "base/win/windows_version.h" | |
| 19 #include "media/base/media_switches.h" | |
| 20 #include "media/base/win/mf_initializer.h" | |
| 21 #include "media/capture/video/win/video_capture_device_mf_win.h" | |
| 22 #include "media/capture/video/win/video_capture_device_win.h" | |
| 23 | |
| 24 using base::win::ScopedCoMem; | |
| 25 using base::win::ScopedComPtr; | |
| 26 using base::win::ScopedVariant; | |
| 27 using Name = media::VideoCaptureDevice::Name; | |
| 28 using Names = media::VideoCaptureDevice::Names; | |
| 29 | |
| 30 namespace media { | |
| 31 | |
| 32 // Avoid enumerating and/or using certain devices due to they provoking crashes | |
| 33 // or any other reason (http://crbug.com/378494). This enum is defined for the | |
| 34 // purposes of UMA collection. Existing entries cannot be removed. | |
| 35 enum BlacklistedCameraNames { | |
| 36 BLACKLISTED_CAMERA_GOOGLE_CAMERA_ADAPTER = 0, | |
| 37 BLACKLISTED_CAMERA_IP_CAMERA = 1, | |
| 38 BLACKLISTED_CAMERA_CYBERLINK_WEBCAM_SPLITTER = 2, | |
| 39 BLACKLISTED_CAMERA_EPOCCAM = 3, | |
| 40 // This one must be last, and equal to the previous enumerated value. | |
| 41 BLACKLISTED_CAMERA_MAX = BLACKLISTED_CAMERA_EPOCCAM, | |
| 42 }; | |
| 43 | |
| 44 // Blacklisted devices are identified by a characteristic prefix of the name. | |
| 45 // This prefix is used case-insensitively. This list must be kept in sync with | |
| 46 // |BlacklistedCameraNames|. | |
| 47 static const char* const kBlacklistedCameraNames[] = { | |
| 48 // Name of a fake DirectShow filter on computers with GTalk installed. | |
| 49 "Google Camera Adapter", | |
| 50 // The following software WebCams cause crashes. | |
| 51 "IP Camera [JPEG/MJPEG]", | |
| 52 "CyberLink Webcam Splitter", | |
| 53 "EpocCam", | |
| 54 }; | |
| 55 static_assert(arraysize(kBlacklistedCameraNames) == BLACKLISTED_CAMERA_MAX + 1, | |
| 56 "kBlacklistedCameraNames should be same size as " | |
| 57 "BlacklistedCameraNames enum"); | |
| 58 | |
| 59 static bool LoadMediaFoundationDlls() { | |
| 60 static const wchar_t* const kMfDLLs[] = { | |
| 61 L"%WINDIR%\\system32\\mf.dll", | |
| 62 L"%WINDIR%\\system32\\mfplat.dll", | |
| 63 L"%WINDIR%\\system32\\mfreadwrite.dll", | |
| 64 }; | |
| 65 | |
| 66 for (const wchar_t* kMfDLL : kMfDLLs) { | |
| 67 wchar_t path[MAX_PATH] = {0}; | |
| 68 ExpandEnvironmentStringsW(kMfDLL, path, arraysize(path)); | |
| 69 if (!LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH)) | |
| 70 return false; | |
| 71 } | |
| 72 return true; | |
| 73 } | |
| 74 | |
| 75 static bool PrepareVideoCaptureAttributesMediaFoundation( | |
| 76 IMFAttributes** attributes, | |
| 77 int count) { | |
| 78 InitializeMediaFoundation(); | |
| 79 | |
| 80 if (FAILED(MFCreateAttributes(attributes, count))) | |
| 81 return false; | |
| 82 | |
| 83 return SUCCEEDED( | |
| 84 (*attributes) | |
| 85 ->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, | |
| 86 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)); | |
| 87 } | |
| 88 | |
| 89 static bool CreateVideoCaptureDeviceMediaFoundation(const char* sym_link, | |
| 90 IMFMediaSource** source) { | |
| 91 ScopedComPtr<IMFAttributes> attributes; | |
| 92 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 2)) | |
| 93 return false; | |
| 94 | |
| 95 attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, | |
| 96 base::SysUTF8ToWide(sym_link).c_str()); | |
| 97 | |
| 98 return SUCCEEDED(MFCreateDeviceSource(attributes.get(), source)); | |
| 99 } | |
| 100 | |
| 101 static bool EnumerateVideoDevicesMediaFoundation(IMFActivate*** devices, | |
| 102 UINT32* count) { | |
| 103 ScopedComPtr<IMFAttributes> attributes; | |
| 104 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1)) | |
| 105 return false; | |
| 106 | |
| 107 return SUCCEEDED(MFEnumDeviceSources(attributes.get(), devices, count)); | |
| 108 } | |
| 109 | |
| 110 static bool IsDeviceBlackListed(const std::string& name) { | |
| 111 DCHECK_EQ(BLACKLISTED_CAMERA_MAX + 1, | |
| 112 static_cast<int>(arraysize(kBlacklistedCameraNames))); | |
| 113 for (size_t i = 0; i < arraysize(kBlacklistedCameraNames); ++i) { | |
| 114 if (base::StartsWith(name, kBlacklistedCameraNames[i], | |
| 115 base::CompareCase::INSENSITIVE_ASCII)) { | |
| 116 DVLOG(1) << "Enumerated blacklisted device: " << name; | |
| 117 UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.BlacklistedDevice", i, | |
| 118 BLACKLISTED_CAMERA_MAX + 1); | |
| 119 return true; | |
| 120 } | |
| 121 } | |
| 122 return false; | |
| 123 } | |
| 124 | |
| 125 static void GetDeviceNamesDirectShow(Names* device_names) { | |
| 126 DCHECK(device_names); | |
| 127 DVLOG(1) << " GetDeviceNamesDirectShow"; | |
| 128 | |
| 129 ScopedComPtr<ICreateDevEnum> dev_enum; | |
| 130 HRESULT hr = | |
| 131 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC); | |
| 132 if (FAILED(hr)) | |
| 133 return; | |
| 134 | |
| 135 ScopedComPtr<IEnumMoniker> enum_moniker; | |
| 136 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, | |
| 137 enum_moniker.Receive(), 0); | |
| 138 // CreateClassEnumerator returns S_FALSE on some Windows OS | |
| 139 // when no camera exist. Therefore the FAILED macro can't be used. | |
| 140 if (hr != S_OK) | |
| 141 return; | |
| 142 | |
| 143 // Enumerate all video capture devices. | |
| 144 for (ScopedComPtr<IMoniker> moniker; | |
| 145 enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK; | |
| 146 moniker.Release()) { | |
| 147 ScopedComPtr<IPropertyBag> prop_bag; | |
| 148 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); | |
| 149 if (FAILED(hr)) | |
| 150 continue; | |
| 151 | |
| 152 // Find the description or friendly name. | |
| 153 ScopedVariant name; | |
| 154 hr = prop_bag->Read(L"Description", name.Receive(), 0); | |
| 155 if (FAILED(hr)) | |
| 156 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); | |
| 157 | |
| 158 if (FAILED(hr) || name.type() != VT_BSTR) | |
| 159 continue; | |
| 160 | |
| 161 const std::string device_name(base::SysWideToUTF8(V_BSTR(name.ptr()))); | |
| 162 if (IsDeviceBlackListed(device_name)) | |
| 163 continue; | |
| 164 | |
| 165 name.Reset(); | |
| 166 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); | |
| 167 std::string id; | |
| 168 if (FAILED(hr) || name.type() != VT_BSTR) { | |
| 169 id = device_name; | |
| 170 } else { | |
| 171 DCHECK_EQ(name.type(), VT_BSTR); | |
| 172 id = base::SysWideToUTF8(V_BSTR(name.ptr())); | |
| 173 } | |
| 174 device_names->push_back(Name(device_name, id, Name::DIRECT_SHOW)); | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 static void GetDeviceNamesMediaFoundation(Names* device_names) { | |
| 179 DVLOG(1) << " GetDeviceNamesMediaFoundation"; | |
| 180 ScopedCoMem<IMFActivate*> devices; | |
| 181 UINT32 count; | |
| 182 if (!EnumerateVideoDevicesMediaFoundation(&devices, &count)) | |
| 183 return; | |
| 184 | |
| 185 for (UINT32 i = 0; i < count; ++i) { | |
| 186 ScopedCoMem<wchar_t> name; | |
| 187 UINT32 name_size; | |
| 188 HRESULT hr = devices[i]->GetAllocatedString( | |
| 189 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size); | |
| 190 if (SUCCEEDED(hr)) { | |
| 191 ScopedCoMem<wchar_t> id; | |
| 192 UINT32 id_size; | |
| 193 hr = devices[i]->GetAllocatedString( | |
| 194 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, | |
| 195 &id_size); | |
| 196 if (SUCCEEDED(hr)) { | |
| 197 device_names->push_back( | |
| 198 Name(base::SysWideToUTF8(std::wstring(name, name_size)), | |
| 199 base::SysWideToUTF8(std::wstring(id, id_size)), | |
| 200 Name::MEDIA_FOUNDATION)); | |
| 201 } | |
| 202 } | |
| 203 DLOG_IF(ERROR, FAILED(hr)) << "GetAllocatedString failed: " | |
| 204 << logging::SystemErrorCodeToString(hr); | |
| 205 devices[i]->Release(); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 static void GetDeviceSupportedFormatsDirectShow(const Name& device, | |
| 210 VideoCaptureFormats* formats) { | |
| 211 DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device.name(); | |
| 212 ScopedComPtr<ICreateDevEnum> dev_enum; | |
| 213 HRESULT hr = | |
| 214 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC); | |
| 215 if (FAILED(hr)) | |
| 216 return; | |
| 217 | |
| 218 ScopedComPtr<IEnumMoniker> enum_moniker; | |
| 219 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, | |
| 220 enum_moniker.Receive(), 0); | |
| 221 // CreateClassEnumerator returns S_FALSE on some Windows OS when no camera | |
| 222 // exists. Therefore the FAILED macro can't be used. | |
| 223 if (hr != S_OK) | |
| 224 return; | |
| 225 | |
| 226 // Walk the capture devices. No need to check for device presence again since | |
| 227 // that is anyway needed in GetDeviceFilter(). "google camera adapter" and old | |
| 228 // VFW devices are already skipped previously in GetDeviceNames() enumeration. | |
| 229 base::win::ScopedComPtr<IBaseFilter> capture_filter; | |
| 230 hr = VideoCaptureDeviceWin::GetDeviceFilter(device.capabilities_id(), | |
| 231 capture_filter.Receive()); | |
| 232 if (!capture_filter.get()) { | |
| 233 DLOG(ERROR) << "Failed to create capture filter: " | |
| 234 << logging::SystemErrorCodeToString(hr); | |
| 235 return; | |
| 236 } | |
| 237 | |
| 238 base::win::ScopedComPtr<IPin> output_capture_pin( | |
| 239 VideoCaptureDeviceWin::GetPin(capture_filter.get(), PINDIR_OUTPUT, | |
| 240 PIN_CATEGORY_CAPTURE, GUID_NULL)); | |
| 241 if (!output_capture_pin.get()) { | |
| 242 DLOG(ERROR) << "Failed to get capture output pin"; | |
| 243 return; | |
| 244 } | |
| 245 | |
| 246 ScopedComPtr<IAMStreamConfig> stream_config; | |
| 247 hr = output_capture_pin.QueryInterface(stream_config.Receive()); | |
| 248 if (FAILED(hr)) { | |
| 249 DLOG(ERROR) << "Failed to get IAMStreamConfig interface from " | |
| 250 "capture device: " << logging::SystemErrorCodeToString(hr); | |
| 251 return; | |
| 252 } | |
| 253 | |
| 254 int count = 0, size = 0; | |
| 255 hr = stream_config->GetNumberOfCapabilities(&count, &size); | |
| 256 if (FAILED(hr)) { | |
| 257 DLOG(ERROR) << "GetNumberOfCapabilities failed: " | |
| 258 << logging::SystemErrorCodeToString(hr); | |
| 259 return; | |
| 260 } | |
| 261 | |
| 262 std::unique_ptr<BYTE[]> caps(new BYTE[size]); | |
| 263 for (int i = 0; i < count; ++i) { | |
| 264 VideoCaptureDeviceWin::ScopedMediaType media_type; | |
| 265 hr = stream_config->GetStreamCaps(i, media_type.Receive(), caps.get()); | |
| 266 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED() | |
| 267 // macros here since they'll trigger incorrectly. | |
| 268 if (hr != S_OK || !media_type.get()) { | |
| 269 DLOG(ERROR) << "GetStreamCaps failed: " | |
| 270 << logging::SystemErrorCodeToString(hr); | |
| 271 return; | |
| 272 } | |
| 273 | |
| 274 if (media_type->majortype == MEDIATYPE_Video && | |
| 275 media_type->formattype == FORMAT_VideoInfo) { | |
| 276 VideoCaptureFormat format; | |
| 277 format.pixel_format = | |
| 278 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat( | |
| 279 media_type->subtype); | |
| 280 if (format.pixel_format == PIXEL_FORMAT_UNKNOWN) | |
| 281 continue; | |
| 282 VIDEOINFOHEADER* h = | |
| 283 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); | |
| 284 format.frame_size.SetSize(h->bmiHeader.biWidth, h->bmiHeader.biHeight); | |
| 285 // Trust the frame rate from the VIDEOINFOHEADER. | |
| 286 format.frame_rate = | |
| 287 (h->AvgTimePerFrame > 0) | |
| 288 ? kSecondsToReferenceTime / static_cast<float>(h->AvgTimePerFrame) | |
| 289 : 0.0f; | |
| 290 formats->push_back(format); | |
| 291 DVLOG(1) << device.name() << " " << VideoCaptureFormat::ToString(format); | |
| 292 } | |
| 293 } | |
| 294 } | |
| 295 | |
| 296 static void GetDeviceSupportedFormatsMediaFoundation( | |
| 297 const Name& device, | |
| 298 VideoCaptureFormats* formats) { | |
| 299 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name(); | |
| 300 ScopedComPtr<IMFMediaSource> source; | |
| 301 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(), | |
| 302 source.Receive())) { | |
| 303 return; | |
| 304 } | |
| 305 | |
| 306 base::win::ScopedComPtr<IMFSourceReader> reader; | |
| 307 HRESULT hr = | |
| 308 MFCreateSourceReaderFromMediaSource(source.get(), NULL, reader.Receive()); | |
| 309 if (FAILED(hr)) { | |
| 310 DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: " | |
| 311 << logging::SystemErrorCodeToString(hr); | |
| 312 return; | |
| 313 } | |
| 314 | |
| 315 DWORD stream_index = 0; | |
| 316 ScopedComPtr<IMFMediaType> type; | |
| 317 while (SUCCEEDED(reader->GetNativeMediaType(kFirstVideoStream, stream_index, | |
| 318 type.Receive()))) { | |
| 319 UINT32 width, height; | |
| 320 hr = MFGetAttributeSize(type.get(), MF_MT_FRAME_SIZE, &width, &height); | |
| 321 if (FAILED(hr)) { | |
| 322 DLOG(ERROR) << "MFGetAttributeSize failed: " | |
| 323 << logging::SystemErrorCodeToString(hr); | |
| 324 return; | |
| 325 } | |
| 326 VideoCaptureFormat capture_format; | |
| 327 capture_format.frame_size.SetSize(width, height); | |
| 328 | |
| 329 UINT32 numerator, denominator; | |
| 330 hr = MFGetAttributeRatio(type.get(), MF_MT_FRAME_RATE, &numerator, | |
| 331 &denominator); | |
| 332 if (FAILED(hr)) { | |
| 333 DLOG(ERROR) << "MFGetAttributeSize failed: " | |
| 334 << logging::SystemErrorCodeToString(hr); | |
| 335 return; | |
| 336 } | |
| 337 capture_format.frame_rate = | |
| 338 denominator ? static_cast<float>(numerator) / denominator : 0.0f; | |
| 339 | |
| 340 GUID type_guid; | |
| 341 hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid); | |
| 342 if (FAILED(hr)) { | |
| 343 DLOG(ERROR) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr); | |
| 344 return; | |
| 345 } | |
| 346 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid, | |
| 347 &capture_format.pixel_format); | |
| 348 type.Release(); | |
| 349 formats->push_back(capture_format); | |
| 350 ++stream_index; | |
| 351 | |
| 352 DVLOG(1) << device.name() << " " | |
| 353 << VideoCaptureFormat::ToString(capture_format); | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 // Returns true iff the current platform supports the Media Foundation API | |
| 358 // and that the DLLs are available. On Vista this API is an optional download | |
| 359 // but the API is advertised as a part of Windows 7 and onwards. However, | |
| 360 // we've seen that the required DLLs are not available in some Win7 | |
| 361 // distributions such as Windows 7 N and Windows 7 KN. | |
| 362 // static | |
| 363 bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() { | |
| 364 // Even though the DLLs might be available on Vista, we get crashes | |
| 365 // when running our tests on the build bots. | |
| 366 if (base::win::GetVersion() < base::win::VERSION_WIN7) | |
| 367 return false; | |
| 368 | |
| 369 static bool g_dlls_available = LoadMediaFoundationDlls(); | |
| 370 return g_dlls_available; | |
| 371 } | |
| 372 | |
| 373 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin() | |
| 374 : use_media_foundation_(base::win::GetVersion() >= | |
| 375 base::win::VERSION_WIN7 && | |
| 376 base::CommandLine::ForCurrentProcess()->HasSwitch( | |
| 377 switches::kForceMediaFoundationVideoCapture)) {} | |
| 378 | |
| 379 std::unique_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::Create( | |
| 380 const Name& device_name) { | |
| 381 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 382 std::unique_ptr<VideoCaptureDevice> device; | |
| 383 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) { | |
| 384 DCHECK(PlatformSupportsMediaFoundation()); | |
| 385 device.reset(new VideoCaptureDeviceMFWin(device_name)); | |
| 386 DVLOG(1) << " MediaFoundation Device: " << device_name.name(); | |
| 387 ScopedComPtr<IMFMediaSource> source; | |
| 388 if (!CreateVideoCaptureDeviceMediaFoundation(device_name.id().c_str(), | |
| 389 source.Receive())) { | |
| 390 return std::unique_ptr<VideoCaptureDevice>(); | |
| 391 } | |
| 392 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source)) | |
| 393 device.reset(); | |
| 394 } else { | |
| 395 DCHECK(device_name.capture_api_type() == Name::DIRECT_SHOW); | |
| 396 device.reset(new VideoCaptureDeviceWin(device_name)); | |
| 397 DVLOG(1) << " DirectShow Device: " << device_name.name(); | |
| 398 if (!static_cast<VideoCaptureDeviceWin*>(device.get())->Init()) | |
| 399 device.reset(); | |
| 400 } | |
| 401 return device; | |
| 402 } | |
| 403 | |
| 404 void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names* device_names) { | |
| 405 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 406 if (use_media_foundation_) | |
| 407 GetDeviceNamesMediaFoundation(device_names); | |
| 408 else | |
| 409 GetDeviceNamesDirectShow(device_names); | |
| 410 } | |
| 411 | |
| 412 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats( | |
| 413 const Name& device, | |
| 414 VideoCaptureFormats* formats) { | |
| 415 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 416 if (use_media_foundation_) | |
| 417 GetDeviceSupportedFormatsMediaFoundation(device, formats); | |
| 418 else | |
| 419 GetDeviceSupportedFormatsDirectShow(device, formats); | |
| 420 } | |
| 421 | |
| 422 // static | |
| 423 VideoCaptureDeviceFactory* | |
| 424 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory( | |
| 425 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { | |
| 426 return new VideoCaptureDeviceFactoryWin(); | |
| 427 } | |
| 428 | |
| 429 } // namespace media | |
| OLD | NEW |