Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / capture / video / win / video_capture_device_factory_win.cc
blob5a626f8e56167e6db01bf47992bd8f7b4d70359d
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"
7 #include <mfapi.h>
8 #include <mferror.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;
30 namespace media {
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",
53 "EpocCam",
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))
70 return false;
72 return true;
75 static bool PrepareVideoCaptureAttributesMediaFoundation(
76 IMFAttributes** attributes,
77 int count) {
78 InitializeMediaFoundation();
80 if (FAILED(MFCreateAttributes(attributes, count)))
81 return false;
83 return SUCCEEDED(
84 (*attributes)
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))
93 return false;
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,
102 UINT32* count) {
103 ScopedComPtr<IMFAttributes> attributes;
104 if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1))
105 return false;
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);
119 return true;
122 return false;
125 static void GetDeviceNamesDirectShow(Names* device_names) {
126 DCHECK(device_names);
127 DVLOG(1) << " GetDeviceNamesDirectShow";
129 ScopedComPtr<ICreateDevEnum> dev_enum;
130 HRESULT hr =
131 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
132 if (FAILED(hr))
133 return;
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.
140 if (hr != S_OK)
141 return;
143 // Enumerate all video capture devices.
144 for (ScopedComPtr<IMoniker> moniker;
145 enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK;
146 moniker.Release()) {
147 ScopedComPtr<IPropertyBag> prop_bag;
148 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
149 if (FAILED(hr))
150 continue;
152 // Find the description or friendly name.
153 ScopedVariant name;
154 hr = prop_bag->Read(L"Description", name.Receive(), 0);
155 if (FAILED(hr))
156 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
158 if (FAILED(hr) || name.type() != VT_BSTR)
159 continue;
161 const std::string device_name(base::SysWideToUTF8(V_BSTR(name.ptr())));
162 if (IsDeviceBlackListed(device_name))
163 continue;
165 name.Reset();
166 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
167 std::string id;
168 if (FAILED(hr) || name.type() != VT_BSTR) {
169 id = device_name;
170 } else {
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;
181 UINT32 count;
182 if (!EnumerateVideoDevicesMediaFoundation(&devices, &count))
183 return;
185 for (UINT32 i = 0; i < count; ++i) {
186 ScopedCoMem<wchar_t> name;
187 UINT32 name_size;
188 HRESULT hr = devices[i]->GetAllocatedString(
189 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size);
190 if (SUCCEEDED(hr)) {
191 ScopedCoMem<wchar_t> id;
192 UINT32 id_size;
193 hr = devices[i]->GetAllocatedString(
194 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id,
195 &id_size);
196 if (SUCCEEDED(hr)) {
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;
213 HRESULT hr =
214 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
215 if (FAILED(hr))
216 return;
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.
223 if (hr != S_OK)
224 return;
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);
235 return;
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";
243 return;
246 ScopedComPtr<IAMStreamConfig> stream_config;
247 hr = output_capture_pin.QueryInterface(stream_config.Receive());
248 if (FAILED(hr)) {
249 DLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
250 "capture device: " << logging::SystemErrorCodeToString(hr);
251 return;
254 int count = 0, size = 0;
255 hr = stream_config->GetNumberOfCapabilities(&count, &size);
256 if (FAILED(hr)) {
257 DLOG(ERROR) << "GetNumberOfCapabilities failed: "
258 << logging::SystemErrorCodeToString(hr);
259 return;
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);
271 return;
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)
281 continue;
282 VIDEOINFOHEADER* h =
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.
286 format.frame_rate =
287 (h->AvgTimePerFrame > 0)
288 ? kSecondsToReferenceTime / static_cast<float>(h->AvgTimePerFrame)
289 : 0.0f;
290 formats->push_back(format);
291 DVLOG(1) << device.name() << " " << VideoCaptureFormat::ToString(format);
296 static void GetDeviceSupportedFormatsMediaFoundation(
297 const Name& device,
298 VideoCaptureFormats* formats) {
299 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name();
300 ScopedComPtr<IMFMediaSource> source;
301 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(),
302 source.Receive())) {
303 return;
306 base::win::ScopedComPtr<IMFSourceReader> reader;
307 HRESULT hr =
308 MFCreateSourceReaderFromMediaSource(source.get(), NULL, reader.Receive());
309 if (FAILED(hr)) {
310 DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: "
311 << logging::SystemErrorCodeToString(hr);
312 return;
315 DWORD stream_index = 0;
316 ScopedComPtr<IMFMediaType> type;
317 while (SUCCEEDED(reader->GetNativeMediaType(kFirstVideoStream, stream_index,
318 type.Receive()))) {
319 UINT32 width, height;
320 hr = MFGetAttributeSize(type.get(), MF_MT_FRAME_SIZE, &width, &height);
321 if (FAILED(hr)) {
322 DLOG(ERROR) << "MFGetAttributeSize failed: "
323 << logging::SystemErrorCodeToString(hr);
324 return;
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,
331 &denominator);
332 if (FAILED(hr)) {
333 DLOG(ERROR) << "MFGetAttributeSize failed: "
334 << logging::SystemErrorCodeToString(hr);
335 return;
337 capture_format.frame_rate =
338 denominator ? static_cast<float>(numerator) / denominator : 0.0f;
340 GUID type_guid;
341 hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid);
342 if (FAILED(hr)) {
343 DLOG(ERROR) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr);
344 return;
346 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid,
347 &capture_format.pixel_format);
348 type.Release();
349 formats->push_back(capture_format);
350 ++stream_index;
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.
362 // static
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)
367 return false;
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(),
396 source.Receive())) {
397 return scoped_ptr<VideoCaptureDevice>();
399 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source))
400 device.reset();
401 } else {
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())
406 device.reset();
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);
415 else
416 GetDeviceNamesDirectShow(device_names);
419 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
420 const Name& device,
421 VideoCaptureFormats* formats) {
422 DCHECK(thread_checker_.CalledOnValidThread());
423 if (use_media_foundation_)
424 GetDeviceSupportedFormatsMediaFoundation(device, formats);
425 else
426 GetDeviceSupportedFormatsDirectShow(device, formats);
429 // static
430 VideoCaptureDeviceFactory*
431 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
432 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
433 return new VideoCaptureDeviceFactoryWin();
436 } // namespace media