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.
5 #include "media/video/capture/win/video_capture_device_factory_win.h"
10 #include "base/command_line.h"
11 #include "base/lazy_instance.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/win/metro.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/video/capture/win/video_capture_device_mf_win.h"
21 #include "media/video/capture/win/video_capture_device_win.h"
23 using base::win::ScopedCoMem;
24 using base::win::ScopedComPtr;
25 using base::win::ScopedVariant;
26 using Name = media::VideoCaptureDevice::Name;
27 using Names = media::VideoCaptureDevice::Names;
31 // We would like to avoid enumerating and/or using certain devices due to they
32 // provoking crashes or any other reason. This enum is defined for the purposes
33 // of UMA collection. Existing entries cannot be removed.
34 enum BlacklistedCameraNames {
35 BLACKLISTED_CAMERA_GOOGLE_CAMERA_ADAPTER,
36 BLACKLISTED_CAMERA_IP_CAMERA,
37 BLACKLISTED_CAMERA_CYBERLINK_WEBCAM_SPLITTER,
38 // This one must be last, and equal to the previous enumerated value.
39 BLACKLISTED_CAMERA_MAX = BLACKLISTED_CAMERA_CYBERLINK_WEBCAM_SPLITTER
42 // Lazy Instance to initialize the MediaFoundation Library.
43 class MFInitializerSingleton {
45 MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); }
46 ~MFInitializerSingleton() { MFShutdown(); }
49 static base::LazyInstance<MFInitializerSingleton> g_mf_initialize =
50 LAZY_INSTANCE_INITIALIZER;
52 // Blacklisted devices are identified by a characteristic prefix of the name.
53 // This prefix is used case-insensitively. This list must be kept in sync with
54 // |BlacklistedCameraNames|.
55 static const char* const kBlacklistedCameraNames[] = {
56 // Name of a fake DirectShow filter on computers with GTalk installed.
57 "Google Camera Adapter",
58 // The following two software WebCams cause crashes.
59 "IP Camera [JPEG/MJPEG]",
60 "CyberLink Webcam Splitter",
63 static void EnsureMediaFoundationInit() {
64 g_mf_initialize.Get();
67 static bool LoadMediaFoundationDlls() {
68 static const wchar_t* const kMfDLLs[] = {
69 L"%WINDIR%\\system32\\mf.dll",
70 L"%WINDIR%\\system32\\mfplat.dll",
71 L"%WINDIR%\\system32\\mfreadwrite.dll",
74 for (int i = 0; i < arraysize(kMfDLLs); ++i) {
75 wchar_t path[MAX_PATH] = {0};
76 ExpandEnvironmentStringsW(kMfDLLs[i], path, arraysize(path));
77 if (!LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH))
83 static bool PrepareVideoCaptureAttributesMediaFoundation(
84 IMFAttributes** attributes,
86 EnsureMediaFoundationInit();
88 if (FAILED(MFCreateAttributes(attributes, count)))
91 return SUCCEEDED((*attributes)->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
92 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID));
95 static bool CreateVideoCaptureDeviceMediaFoundation(const char* sym_link,
96 IMFMediaSource** source) {
97 ScopedComPtr<IMFAttributes> attributes;
98 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 2))
101 attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
102 base::SysUTF8ToWide(sym_link).c_str());
104 return SUCCEEDED(MFCreateDeviceSource(attributes, source));
107 static bool EnumerateVideoDevicesMediaFoundation(IMFActivate*** devices,
109 ScopedComPtr<IMFAttributes> attributes;
110 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1))
113 return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count));
116 static bool IsDeviceBlackListed(const std::string& name) {
117 DCHECK_EQ(BLACKLISTED_CAMERA_MAX + 1,
118 static_cast<int>(arraysize(kBlacklistedCameraNames)));
119 for (size_t i = 0; i < arraysize(kBlacklistedCameraNames); ++i) {
120 if (StartsWithASCII(name, kBlacklistedCameraNames[i], false)) {
121 DVLOG(1) << "Enumerated blacklisted device: " << name;
122 UMA_HISTOGRAM_ENUMERATION("Media.VideoCapture.BlacklistedDevice",
123 i, BLACKLISTED_CAMERA_MAX + 1);
130 static void GetDeviceNamesDirectShow(
131 const CLSID& class_id,
132 const Name::CaptureApiType capture_api_type,
133 Names* device_names) {
134 DCHECK(device_names);
135 DVLOG(1) << " GetDeviceNamesDirectShow";
137 ScopedComPtr<ICreateDevEnum> dev_enum;
138 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
143 ScopedComPtr<IEnumMoniker> enum_moniker;
144 hr = dev_enum->CreateClassEnumerator(class_id, enum_moniker.Receive(), 0);
145 // CreateClassEnumerator returns S_FALSE on some Windows OS
146 // when no camera exist. Therefore the FAILED macro can't be used.
150 // Enumerate all video capture devices.
151 for (ScopedComPtr<IMoniker> moniker;
152 enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK;
154 ScopedComPtr<IPropertyBag> prop_bag;
155 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
159 // Find the description or friendly name.
161 hr = prop_bag->Read(L"Description", name.Receive(), 0);
163 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
165 if (FAILED(hr) || name.type() != VT_BSTR)
168 const std::string device_name(base::SysWideToUTF8(V_BSTR(&name)));
169 if (IsDeviceBlackListed(device_name))
173 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
175 if (FAILED(hr) || name.type() != VT_BSTR) {
178 DCHECK_EQ(name.type(), VT_BSTR);
179 id = base::SysWideToUTF8(V_BSTR(&name));
181 device_names->push_back(Name(device_name, id, capture_api_type));
185 static void GetDeviceNamesMediaFoundation(Names* device_names) {
186 DVLOG(1) << " GetDeviceNamesMediaFoundation";
187 ScopedCoMem<IMFActivate*> devices;
189 if (!EnumerateVideoDevicesMediaFoundation(&devices, &count))
192 for (UINT32 i = 0; i < count; ++i) {
193 ScopedCoMem<wchar_t> name;
195 HRESULT hr = devices[i]->GetAllocatedString(
196 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size);
198 ScopedCoMem<wchar_t> id;
200 hr = devices[i]->GetAllocatedString(
201 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id,
204 device_names->push_back(Name(
205 base::SysWideToUTF8(std::wstring(name, name_size)),
206 base::SysWideToUTF8(std::wstring(id, id_size)),
207 Name::MEDIA_FOUNDATION));
210 DLOG_IF(ERROR, FAILED(hr)) << "GetAllocatedString failed: "
211 << logging::SystemErrorCodeToString(hr);
212 devices[i]->Release();
216 static void GetDeviceSupportedFormatsDirectShow(const Name& device,
217 VideoCaptureFormats* formats) {
218 DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device.name();
219 ScopedComPtr<ICreateDevEnum> dev_enum;
220 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
225 ScopedComPtr<IEnumMoniker> enum_moniker;
226 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
227 enum_moniker.Receive(), 0);
228 // CreateClassEnumerator returns S_FALSE on some Windows OS when no camera
229 // exists. Therefore the FAILED macro can't be used.
233 // Walk the capture devices. No need to check for device presence again since
234 // that is anyway needed in GetDeviceFilter(). "google camera adapter" and old
235 // VFW devices are already skipped previously in GetDeviceNames() enumeration.
236 base::win::ScopedComPtr<IBaseFilter> capture_filter;
237 hr = VideoCaptureDeviceWin::GetDeviceFilter(device.capabilities_id(),
238 CLSID_VideoInputDeviceCategory,
239 capture_filter.Receive());
240 if (!capture_filter) {
241 DLOG(ERROR) << "Failed to create capture filter: "
242 << logging::SystemErrorCodeToString(hr);
246 base::win::ScopedComPtr<IPin> output_capture_pin(
247 VideoCaptureDeviceWin::GetPin(capture_filter,
249 PIN_CATEGORY_CAPTURE,
251 if (!output_capture_pin) {
252 DLOG(ERROR) << "Failed to get capture output pin";
256 ScopedComPtr<IAMStreamConfig> stream_config;
257 hr = output_capture_pin.QueryInterface(stream_config.Receive());
259 DLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
260 "capture device: " << logging::SystemErrorCodeToString(hr);
264 int count = 0, size = 0;
265 hr = stream_config->GetNumberOfCapabilities(&count, &size);
267 DLOG(ERROR) << "GetNumberOfCapabilities failed: "
268 << logging::SystemErrorCodeToString(hr);
272 scoped_ptr<BYTE[]> caps(new BYTE[size]);
273 for (int i = 0; i < count; ++i) {
274 VideoCaptureDeviceWin::ScopedMediaType media_type;
275 hr = stream_config->GetStreamCaps(i, media_type.Receive(), caps.get());
276 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
277 // macros here since they'll trigger incorrectly.
278 if (hr != S_OK || !media_type.get()) {
279 DLOG(ERROR) << "GetStreamCaps failed: "
280 << logging::SystemErrorCodeToString(hr);
284 if (media_type->majortype == MEDIATYPE_Video &&
285 media_type->formattype == FORMAT_VideoInfo) {
286 VideoCaptureFormat format;
287 format.pixel_format =
288 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
289 media_type->subtype);
290 if (format.pixel_format == PIXEL_FORMAT_UNKNOWN)
293 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
294 format.frame_size.SetSize(h->bmiHeader.biWidth,
295 h->bmiHeader.biHeight);
296 // Trust the frame rate from the VIDEOINFOHEADER.
297 format.frame_rate = (h->AvgTimePerFrame > 0) ?
298 kSecondsToReferenceTime / static_cast<float>(h->AvgTimePerFrame) :
300 formats->push_back(format);
301 DVLOG(1) << device.name() << " " << format.ToString();
306 static void GetDeviceSupportedFormatsMediaFoundation(
308 VideoCaptureFormats* formats) {
309 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name();
310 ScopedComPtr<IMFMediaSource> source;
311 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(),
316 base::win::ScopedComPtr<IMFSourceReader> reader;
318 MFCreateSourceReaderFromMediaSource(source, NULL, reader.Receive());
320 DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: "
321 << logging::SystemErrorCodeToString(hr);
325 DWORD stream_index = 0;
326 ScopedComPtr<IMFMediaType> type;
327 while (SUCCEEDED(reader->GetNativeMediaType(
328 kFirstVideoStream, stream_index, type.Receive()))) {
329 UINT32 width, height;
330 hr = MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width, &height);
332 DLOG(ERROR) << "MFGetAttributeSize failed: "
333 << logging::SystemErrorCodeToString(hr);
336 VideoCaptureFormat capture_format;
337 capture_format.frame_size.SetSize(width, height);
339 UINT32 numerator, denominator;
340 hr = MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator, &denominator);
342 DLOG(ERROR) << "MFGetAttributeSize failed: "
343 << logging::SystemErrorCodeToString(hr);
346 capture_format.frame_rate = denominator
347 ? static_cast<float>(numerator) / denominator : 0.0f;
350 hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid);
352 DLOG(ERROR) << "GetGUID failed: "
353 << logging::SystemErrorCodeToString(hr);
356 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid,
357 &capture_format.pixel_format);
359 formats->push_back(capture_format);
362 DVLOG(1) << device.name() << " " << capture_format.ToString();
366 // Returns true iff the current platform supports the Media Foundation API
367 // and that the DLLs are available. On Vista this API is an optional download
368 // but the API is advertised as a part of Windows 7 and onwards. However,
369 // we've seen that the required DLLs are not available in some Win7
370 // distributions such as Windows 7 N and Windows 7 KN.
372 bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
373 // Even though the DLLs might be available on Vista, we get crashes
374 // when running our tests on the build bots.
375 if (base::win::GetVersion() < base::win::VERSION_WIN7)
378 static bool g_dlls_available = LoadMediaFoundationDlls();
379 return g_dlls_available;
382 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin() {
383 // Use Media Foundation for Metro processes (after and including Win8) and
384 // DirectShow for any other versions, unless forced via flag. Media Foundation
385 // can also be forced if appropriate flag is set and we are in Windows 7 or
386 // 8 in non-Metro mode.
387 const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
388 use_media_foundation_ = (base::win::IsMetroProcess() &&
389 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) ||
390 (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
391 cmd_line->HasSwitch(switches::kForceMediaFoundationVideoCapture));
395 scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::Create(
396 const Name& device_name) {
397 DCHECK(thread_checker_.CalledOnValidThread());
398 scoped_ptr<VideoCaptureDevice> device;
399 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) {
400 DCHECK(PlatformSupportsMediaFoundation());
401 device.reset(new VideoCaptureDeviceMFWin(device_name));
402 DVLOG(1) << " MediaFoundation Device: " << device_name.name();
403 ScopedComPtr<IMFMediaSource> source;
404 if (!CreateVideoCaptureDeviceMediaFoundation(device_name.id().c_str(),
406 return scoped_ptr<VideoCaptureDevice>();
408 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source))
411 DCHECK(device_name.capture_api_type() == Name::DIRECT_SHOW ||
412 device_name.capture_api_type() == Name::DIRECT_SHOW_WDM_CROSSBAR);
413 device.reset(new VideoCaptureDeviceWin(device_name));
414 DVLOG(1) << " DirectShow Device: " << device_name.name();
415 if (!static_cast<VideoCaptureDeviceWin*>(device.get())->Init())
418 return device.Pass();
421 void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names* device_names) {
422 DCHECK(thread_checker_.CalledOnValidThread());
423 if (use_media_foundation_) {
424 GetDeviceNamesMediaFoundation(device_names);
426 GetDeviceNamesDirectShow(CLSID_VideoInputDeviceCategory,
430 Names crossbar_device_names;
431 GetDeviceNamesDirectShow(AM_KSCATEGORY_CROSSBAR,
432 Name::DIRECT_SHOW_WDM_CROSSBAR,
433 &crossbar_device_names);
434 // Search in the listed |device_names| to find a device with matching USB ID
435 // to each device in |crossbar_device_names|.
436 for (Names::iterator crossbar_device_it = crossbar_device_names.begin();
437 crossbar_device_it != crossbar_device_names.end();
438 ++crossbar_device_it) {
439 const std::string& crossbar_device_model = crossbar_device_it->GetModel();
440 for (Names::const_iterator device_it = device_names->begin();
441 device_it != device_names->end(); ++device_it) {
442 if (crossbar_device_model == device_it->GetModel()) {
443 crossbar_device_it->set_capabilities_id(device_it->id());
444 device_names->push_back(*crossbar_device_it);
452 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
454 VideoCaptureFormats* formats) {
455 DCHECK(thread_checker_.CalledOnValidThread());
456 if (use_media_foundation_)
457 GetDeviceSupportedFormatsMediaFoundation(device, formats);
459 GetDeviceSupportedFormatsDirectShow(device, formats);