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 capture_filter
.Receive());
232 if (!capture_filter
.get()) {
233 DLOG(ERROR
) << "Failed to create capture filter: "
234 << logging::SystemErrorCodeToString(hr
);
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";
246 ScopedComPtr
<IAMStreamConfig
> stream_config
;
247 hr
= output_capture_pin
.QueryInterface(stream_config
.Receive());
249 DLOG(ERROR
) << "Failed to get IAMStreamConfig interface from "
250 "capture device: " << logging::SystemErrorCodeToString(hr
);
254 int count
= 0, size
= 0;
255 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
257 DLOG(ERROR
) << "GetNumberOfCapabilities failed: "
258 << logging::SystemErrorCodeToString(hr
);
262 scoped_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
);
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
)
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.
287 (h
->AvgTimePerFrame
> 0)
288 ? kSecondsToReferenceTime
/ static_cast<float>(h
->AvgTimePerFrame
)
290 formats
->push_back(format
);
291 DVLOG(1) << device
.name() << " " << VideoCaptureFormat::ToString(format
);
296 static void GetDeviceSupportedFormatsMediaFoundation(
298 VideoCaptureFormats
* formats
) {
299 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device
.name();
300 ScopedComPtr
<IMFMediaSource
> source
;
301 if (!CreateVideoCaptureDeviceMediaFoundation(device
.id().c_str(),
306 base::win::ScopedComPtr
<IMFSourceReader
> reader
;
308 MFCreateSourceReaderFromMediaSource(source
.get(), NULL
, reader
.Receive());
310 DLOG(ERROR
) << "MFCreateSourceReaderFromMediaSource failed: "
311 << logging::SystemErrorCodeToString(hr
);
315 DWORD stream_index
= 0;
316 ScopedComPtr
<IMFMediaType
> type
;
317 while (SUCCEEDED(reader
->GetNativeMediaType(kFirstVideoStream
, stream_index
,
319 UINT32 width
, height
;
320 hr
= MFGetAttributeSize(type
.get(), MF_MT_FRAME_SIZE
, &width
, &height
);
322 DLOG(ERROR
) << "MFGetAttributeSize failed: "
323 << logging::SystemErrorCodeToString(hr
);
326 VideoCaptureFormat capture_format
;
327 capture_format
.frame_size
.SetSize(width
, height
);
329 UINT32 numerator
, denominator
;
330 hr
= MFGetAttributeRatio(type
.get(), MF_MT_FRAME_RATE
, &numerator
,
333 DLOG(ERROR
) << "MFGetAttributeSize failed: "
334 << logging::SystemErrorCodeToString(hr
);
337 capture_format
.frame_rate
=
338 denominator
? static_cast<float>(numerator
) / denominator
: 0.0f
;
341 hr
= type
->GetGUID(MF_MT_SUBTYPE
, &type_guid
);
343 DLOG(ERROR
) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr
);
346 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid
,
347 &capture_format
.pixel_format
);
349 formats
->push_back(capture_format
);
352 DVLOG(1) << device
.name() << " "
353 << VideoCaptureFormat::ToString(capture_format
);
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.
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
)
369 static bool g_dlls_available
= LoadMediaFoundationDlls();
370 return g_dlls_available
;
373 VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin() {
374 // Use Media Foundation for Metro processes (after and including Win8) and
375 // DirectShow for any other versions, unless forced via flag. Media Foundation
376 // can also be forced if appropriate flag is set and we are in Windows 7 or
377 // 8 in non-Metro mode.
378 const base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
379 use_media_foundation_
=
380 (base::win::IsMetroProcess() &&
381 !cmd_line
->HasSwitch(switches::kForceDirectShowVideoCapture
)) ||
382 (base::win::GetVersion() >= base::win::VERSION_WIN7
&&
383 cmd_line
->HasSwitch(switches::kForceMediaFoundationVideoCapture
));
386 scoped_ptr
<VideoCaptureDevice
> VideoCaptureDeviceFactoryWin::Create(
387 const Name
& device_name
) {
388 DCHECK(thread_checker_
.CalledOnValidThread());
389 scoped_ptr
<VideoCaptureDevice
> device
;
390 if (device_name
.capture_api_type() == Name::MEDIA_FOUNDATION
) {
391 DCHECK(PlatformSupportsMediaFoundation());
392 device
.reset(new VideoCaptureDeviceMFWin(device_name
));
393 DVLOG(1) << " MediaFoundation Device: " << device_name
.name();
394 ScopedComPtr
<IMFMediaSource
> source
;
395 if (!CreateVideoCaptureDeviceMediaFoundation(device_name
.id().c_str(),
397 return scoped_ptr
<VideoCaptureDevice
>();
399 if (!static_cast<VideoCaptureDeviceMFWin
*>(device
.get())->Init(source
))
402 DCHECK(device_name
.capture_api_type() == Name::DIRECT_SHOW
);
403 device
.reset(new VideoCaptureDeviceWin(device_name
));
404 DVLOG(1) << " DirectShow Device: " << device_name
.name();
405 if (!static_cast<VideoCaptureDeviceWin
*>(device
.get())->Init())
408 return device
.Pass();
411 void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names
* device_names
) {
412 DCHECK(thread_checker_
.CalledOnValidThread());
413 if (use_media_foundation_
)
414 GetDeviceNamesMediaFoundation(device_names
);
416 GetDeviceNamesDirectShow(device_names
);
419 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
421 VideoCaptureFormats
* formats
) {
422 DCHECK(thread_checker_
.CalledOnValidThread());
423 if (use_media_foundation_
)
424 GetDeviceSupportedFormatsMediaFoundation(device
, formats
);
426 GetDeviceSupportedFormatsDirectShow(device
, formats
);
430 VideoCaptureDeviceFactory
*
431 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
432 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
433 return new VideoCaptureDeviceFactoryWin();