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/capture/video/win/video_capture_device_factory_win.h"
10 #include "base/command_line.h"
11 #include "base/macros.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/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"
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
;
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
,
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",
55 static_assert(arraysize(kBlacklistedCameraNames
) == BLACKLISTED_CAMERA_MAX
+ 1,
56 "kBlacklistedCameraNames should be same size as "
57 "BlacklistedCameraNames enum");
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",
66 for (int i
= 0; i
< arraysize(kMfDLLs
); ++i
) {
67 wchar_t path
[MAX_PATH
] = {0};
68 ExpandEnvironmentStringsW(kMfDLLs
[i
], path
, arraysize(path
));
69 if (!LoadLibraryExW(path
, NULL
, LOAD_WITH_ALTERED_SEARCH_PATH
))
75 static bool PrepareVideoCaptureAttributesMediaFoundation(
76 IMFAttributes
** attributes
,
78 InitializeMediaFoundation();
80 if (FAILED(MFCreateAttributes(attributes
, count
)))
85 ->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE
,
86 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID
));
89 static bool CreateVideoCaptureDeviceMediaFoundation(const char* sym_link
,
90 IMFMediaSource
** source
) {
91 ScopedComPtr
<IMFAttributes
> attributes
;
92 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes
.Receive(), 2))
95 attributes
->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK
,
96 base::SysUTF8ToWide(sym_link
).c_str());
98 return SUCCEEDED(MFCreateDeviceSource(attributes
.get(), source
));
101 static bool EnumerateVideoDevicesMediaFoundation(IMFActivate
*** devices
,
103 ScopedComPtr
<IMFAttributes
> attributes
;
104 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes
.Receive(), 1))
107 return SUCCEEDED(MFEnumDeviceSources(attributes
.get(), devices
, count
));
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);
125 static void GetDeviceNamesDirectShow(Names
* device_names
) {
126 DCHECK(device_names
);
127 DVLOG(1) << " GetDeviceNamesDirectShow";
129 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
131 dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
, CLSCTX_INPROC
);
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.
143 // Enumerate all video capture devices.
144 for (ScopedComPtr
<IMoniker
> moniker
;
145 enum_moniker
->Next(1, moniker
.Receive(), NULL
) == S_OK
;
147 ScopedComPtr
<IPropertyBag
> prop_bag
;
148 hr
= moniker
->BindToStorage(0, 0, IID_IPropertyBag
, prop_bag
.ReceiveVoid());
152 // Find the description or friendly name.
154 hr
= prop_bag
->Read(L
"Description", name
.Receive(), 0);
156 hr
= prop_bag
->Read(L
"FriendlyName", name
.Receive(), 0);
158 if (FAILED(hr
) || name
.type() != VT_BSTR
)
161 const std::string
device_name(base::SysWideToUTF8(V_BSTR(name
.ptr())));
162 if (IsDeviceBlackListed(device_name
))
166 hr
= prop_bag
->Read(L
"DevicePath", name
.Receive(), 0);
168 if (FAILED(hr
) || name
.type() != VT_BSTR
) {
171 DCHECK_EQ(name
.type(), VT_BSTR
);
172 id
= base::SysWideToUTF8(V_BSTR(name
.ptr()));
174 device_names
->push_back(Name(device_name
, id
, Name::DIRECT_SHOW
));
178 static void GetDeviceNamesMediaFoundation(Names
* device_names
) {
179 DVLOG(1) << " GetDeviceNamesMediaFoundation";
180 ScopedCoMem
<IMFActivate
*> devices
;
182 if (!EnumerateVideoDevicesMediaFoundation(&devices
, &count
))
185 for (UINT32 i
= 0; i
< count
; ++i
) {
186 ScopedCoMem
<wchar_t> name
;
188 HRESULT hr
= devices
[i
]->GetAllocatedString(
189 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME
, &name
, &name_size
);
191 ScopedCoMem
<wchar_t> id
;
193 hr
= devices
[i
]->GetAllocatedString(
194 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK
, &id
,
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
));
203 DLOG_IF(ERROR
, FAILED(hr
)) << "GetAllocatedString failed: "
204 << logging::SystemErrorCodeToString(hr
);
205 devices
[i
]->Release();
209 static void GetDeviceSupportedFormatsDirectShow(const Name
& device
,
210 VideoCaptureFormats
* formats
) {
211 DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device
.name();
212 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
214 dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
, CLSCTX_INPROC
);
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.
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 CLSID_VideoInputDeviceCategory
,
232 capture_filter
.Receive());
233 if (!capture_filter
.get()) {
234 DLOG(ERROR
) << "Failed to create capture filter: "
235 << logging::SystemErrorCodeToString(hr
);
239 base::win::ScopedComPtr
<IPin
> output_capture_pin(
240 VideoCaptureDeviceWin::GetPin(capture_filter
.get(), PINDIR_OUTPUT
,
241 PIN_CATEGORY_CAPTURE
, GUID_NULL
));
242 if (!output_capture_pin
.get()) {
243 DLOG(ERROR
) << "Failed to get capture output pin";
247 ScopedComPtr
<IAMStreamConfig
> stream_config
;
248 hr
= output_capture_pin
.QueryInterface(stream_config
.Receive());
250 DLOG(ERROR
) << "Failed to get IAMStreamConfig interface from "
251 "capture device: " << logging::SystemErrorCodeToString(hr
);
255 int count
= 0, size
= 0;
256 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
258 DLOG(ERROR
) << "GetNumberOfCapabilities failed: "
259 << logging::SystemErrorCodeToString(hr
);
263 scoped_ptr
<BYTE
[]> caps(new BYTE
[size
]);
264 for (int i
= 0; i
< count
; ++i
) {
265 VideoCaptureDeviceWin::ScopedMediaType media_type
;
266 hr
= stream_config
->GetStreamCaps(i
, media_type
.Receive(), caps
.get());
267 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
268 // macros here since they'll trigger incorrectly.
269 if (hr
!= S_OK
|| !media_type
.get()) {
270 DLOG(ERROR
) << "GetStreamCaps failed: "
271 << logging::SystemErrorCodeToString(hr
);
275 if (media_type
->majortype
== MEDIATYPE_Video
&&
276 media_type
->formattype
== FORMAT_VideoInfo
) {
277 VideoCaptureFormat format
;
278 format
.pixel_format
=
279 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
280 media_type
->subtype
);
281 if (format
.pixel_format
== VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN
)
284 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
285 format
.frame_size
.SetSize(h
->bmiHeader
.biWidth
, h
->bmiHeader
.biHeight
);
286 // Trust the frame rate from the VIDEOINFOHEADER.
288 (h
->AvgTimePerFrame
> 0)
289 ? kSecondsToReferenceTime
/ static_cast<float>(h
->AvgTimePerFrame
)
291 formats
->push_back(format
);
292 DVLOG(1) << device
.name() << " " << VideoCaptureFormat::ToString(format
);
297 static void GetDeviceSupportedFormatsMediaFoundation(
299 VideoCaptureFormats
* formats
) {
300 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device
.name();
301 ScopedComPtr
<IMFMediaSource
> source
;
302 if (!CreateVideoCaptureDeviceMediaFoundation(device
.id().c_str(),
307 base::win::ScopedComPtr
<IMFSourceReader
> reader
;
309 MFCreateSourceReaderFromMediaSource(source
.get(), NULL
, reader
.Receive());
311 DLOG(ERROR
) << "MFCreateSourceReaderFromMediaSource failed: "
312 << logging::SystemErrorCodeToString(hr
);
316 DWORD stream_index
= 0;
317 ScopedComPtr
<IMFMediaType
> type
;
318 while (SUCCEEDED(reader
->GetNativeMediaType(kFirstVideoStream
, stream_index
,
320 UINT32 width
, height
;
321 hr
= MFGetAttributeSize(type
.get(), MF_MT_FRAME_SIZE
, &width
, &height
);
323 DLOG(ERROR
) << "MFGetAttributeSize failed: "
324 << logging::SystemErrorCodeToString(hr
);
327 VideoCaptureFormat capture_format
;
328 capture_format
.frame_size
.SetSize(width
, height
);
330 UINT32 numerator
, denominator
;
331 hr
= MFGetAttributeRatio(type
.get(), MF_MT_FRAME_RATE
, &numerator
,
334 DLOG(ERROR
) << "MFGetAttributeSize failed: "
335 << logging::SystemErrorCodeToString(hr
);
338 capture_format
.frame_rate
=
339 denominator
? static_cast<float>(numerator
) / denominator
: 0.0f
;
342 hr
= type
->GetGUID(MF_MT_SUBTYPE
, &type_guid
);
344 DLOG(ERROR
) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr
);
347 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid
,
348 &capture_format
.pixel_format
);
350 formats
->push_back(capture_format
);
353 DVLOG(1) << device
.name() << " "
354 << VideoCaptureFormat::ToString(capture_format
);
358 // Returns true iff the current platform supports the Media Foundation API
359 // and that the DLLs are available. On Vista this API is an optional download
360 // but the API is advertised as a part of Windows 7 and onwards. However,
361 // we've seen that the required DLLs are not available in some Win7
362 // distributions such as Windows 7 N and Windows 7 KN.
364 bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
365 // Even though the DLLs might be available on Vista, we get crashes
366 // when running our tests on the build bots.
367 if (base::win::GetVersion() < base::win::VERSION_WIN7
)
370 static bool g_dlls_available
= LoadMediaFoundationDlls();
371 return g_dlls_available
;
374 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin() {
375 // Use Media Foundation for Metro processes (after and including Win8) and
376 // DirectShow for any other versions, unless forced via flag. Media Foundation
377 // can also be forced if appropriate flag is set and we are in Windows 7 or
378 // 8 in non-Metro mode.
379 const base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
380 use_media_foundation_
=
381 (base::win::IsMetroProcess() &&
382 !cmd_line
->HasSwitch(switches::kForceDirectShowVideoCapture
)) ||
383 (base::win::GetVersion() >= base::win::VERSION_WIN7
&&
384 cmd_line
->HasSwitch(switches::kForceMediaFoundationVideoCapture
));
387 scoped_ptr
<VideoCaptureDevice
> VideoCaptureDeviceFactoryWin::Create(
388 const Name
& device_name
) {
389 DCHECK(thread_checker_
.CalledOnValidThread());
390 scoped_ptr
<VideoCaptureDevice
> device
;
391 if (device_name
.capture_api_type() == Name::MEDIA_FOUNDATION
) {
392 DCHECK(PlatformSupportsMediaFoundation());
393 device
.reset(new VideoCaptureDeviceMFWin(device_name
));
394 DVLOG(1) << " MediaFoundation Device: " << device_name
.name();
395 ScopedComPtr
<IMFMediaSource
> source
;
396 if (!CreateVideoCaptureDeviceMediaFoundation(device_name
.id().c_str(),
398 return scoped_ptr
<VideoCaptureDevice
>();
400 if (!static_cast<VideoCaptureDeviceMFWin
*>(device
.get())->Init(source
))
403 DCHECK(device_name
.capture_api_type() == Name::DIRECT_SHOW
);
404 device
.reset(new VideoCaptureDeviceWin(device_name
));
405 DVLOG(1) << " DirectShow Device: " << device_name
.name();
406 if (!static_cast<VideoCaptureDeviceWin
*>(device
.get())->Init())
409 return device
.Pass();
412 void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names
* device_names
) {
413 DCHECK(thread_checker_
.CalledOnValidThread());
414 if (use_media_foundation_
) {
415 GetDeviceNamesMediaFoundation(device_names
);
417 GetDeviceNamesDirectShow(device_names
);
421 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
423 VideoCaptureFormats
* formats
) {
424 DCHECK(thread_checker_
.CalledOnValidThread());
425 if (use_media_foundation_
)
426 GetDeviceSupportedFormatsMediaFoundation(device
, formats
);
428 GetDeviceSupportedFormatsDirectShow(device
, formats
);
432 VideoCaptureDeviceFactory
*
433 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
434 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
435 return new VideoCaptureDeviceFactoryWin();