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