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