Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / media / capture / video / win / video_capture_device_factory_win.cc
blob8f1ea11f9ab105373d0446f5bfa8d5d7500c6ca6
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 CLSID_VideoInputDeviceCategory,
232 capture_filter.Receive());
233 if (!capture_filter.get()) {
234 DLOG(ERROR) << "Failed to create capture filter: "
235 << logging::SystemErrorCodeToString(hr);
236 return;
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";
244 return;
247 ScopedComPtr<IAMStreamConfig> stream_config;
248 hr = output_capture_pin.QueryInterface(stream_config.Receive());
249 if (FAILED(hr)) {
250 DLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
251 "capture device: " << logging::SystemErrorCodeToString(hr);
252 return;
255 int count = 0, size = 0;
256 hr = stream_config->GetNumberOfCapabilities(&count, &size);
257 if (FAILED(hr)) {
258 DLOG(ERROR) << "GetNumberOfCapabilities failed: "
259 << logging::SystemErrorCodeToString(hr);
260 return;
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);
272 return;
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)
282 continue;
283 VIDEOINFOHEADER* h =
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.
287 format.frame_rate =
288 (h->AvgTimePerFrame > 0)
289 ? kSecondsToReferenceTime / static_cast<float>(h->AvgTimePerFrame)
290 : 0.0f;
291 formats->push_back(format);
292 DVLOG(1) << device.name() << " " << VideoCaptureFormat::ToString(format);
297 static void GetDeviceSupportedFormatsMediaFoundation(
298 const Name& device,
299 VideoCaptureFormats* formats) {
300 DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name();
301 ScopedComPtr<IMFMediaSource> source;
302 if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(),
303 source.Receive())) {
304 return;
307 base::win::ScopedComPtr<IMFSourceReader> reader;
308 HRESULT hr =
309 MFCreateSourceReaderFromMediaSource(source.get(), NULL, reader.Receive());
310 if (FAILED(hr)) {
311 DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: "
312 << logging::SystemErrorCodeToString(hr);
313 return;
316 DWORD stream_index = 0;
317 ScopedComPtr<IMFMediaType> type;
318 while (SUCCEEDED(reader->GetNativeMediaType(kFirstVideoStream, stream_index,
319 type.Receive()))) {
320 UINT32 width, height;
321 hr = MFGetAttributeSize(type.get(), MF_MT_FRAME_SIZE, &width, &height);
322 if (FAILED(hr)) {
323 DLOG(ERROR) << "MFGetAttributeSize failed: "
324 << logging::SystemErrorCodeToString(hr);
325 return;
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,
332 &denominator);
333 if (FAILED(hr)) {
334 DLOG(ERROR) << "MFGetAttributeSize failed: "
335 << logging::SystemErrorCodeToString(hr);
336 return;
338 capture_format.frame_rate =
339 denominator ? static_cast<float>(numerator) / denominator : 0.0f;
341 GUID type_guid;
342 hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid);
343 if (FAILED(hr)) {
344 DLOG(ERROR) << "GetGUID failed: " << logging::SystemErrorCodeToString(hr);
345 return;
347 VideoCaptureDeviceMFWin::FormatFromGuid(type_guid,
348 &capture_format.pixel_format);
349 type.Release();
350 formats->push_back(capture_format);
351 ++stream_index;
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.
363 // static
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)
368 return false;
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(),
397 source.Receive())) {
398 return scoped_ptr<VideoCaptureDevice>();
400 if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source))
401 device.reset();
402 } else {
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())
407 device.reset();
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);
416 } else {
417 GetDeviceNamesDirectShow(device_names);
421 void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
422 const Name& device,
423 VideoCaptureFormats* formats) {
424 DCHECK(thread_checker_.CalledOnValidThread());
425 if (use_media_foundation_)
426 GetDeviceSupportedFormatsMediaFoundation(device, formats);
427 else
428 GetDeviceSupportedFormatsDirectShow(device, formats);
431 // static
432 VideoCaptureDeviceFactory*
433 VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory(
434 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
435 return new VideoCaptureDeviceFactoryWin();
438 } // namespace media