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.
4
5#include "media/video/capture/win/video_capture_device_factory_win.h"
6
7#include <mfapi.h>
8#include <mferror.h>
9
10#include "base/command_line.h"
11#include "base/lazy_instance.h"
12#include "base/strings/string_util.h"
13#include "base/strings/sys_string_conversions.h"
14#include "base/win/metro.h"
15#include "base/win/scoped_co_mem.h"
16#include "base/win/scoped_variant.h"
17#include "base/win/windows_version.h"
18#include "media/base/media_switches.h"
19#include "media/video/capture/win/video_capture_device_mf_win.h"
20#include "media/video/capture/win/video_capture_device_win.h"
21
22using base::win::ScopedCoMem;
23using base::win::ScopedComPtr;
24using base::win::ScopedVariant;
25using Name = media::VideoCaptureDevice::Name;
26using Names = media::VideoCaptureDevice::Names;
27
28namespace media {
29
30// Lazy Instance to initialize the MediaFoundation Library.
31class MFInitializerSingleton {
32 public:
33  MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); }
34  ~MFInitializerSingleton() { MFShutdown(); }
35};
36
37static base::LazyInstance<MFInitializerSingleton> g_mf_initialize =
38    LAZY_INSTANCE_INITIALIZER;
39
40static void EnsureMediaFoundationInit() {
41  g_mf_initialize.Get();
42}
43
44static bool LoadMediaFoundationDlls() {
45  static const wchar_t* const kMfDLLs[] = {
46    L"%WINDIR%\\system32\\mf.dll",
47    L"%WINDIR%\\system32\\mfplat.dll",
48    L"%WINDIR%\\system32\\mfreadwrite.dll",
49  };
50
51  for (int i = 0; i < arraysize(kMfDLLs); ++i) {
52    wchar_t path[MAX_PATH] = {0};
53    ExpandEnvironmentStringsW(kMfDLLs[i], path, arraysize(path));
54    if (!LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH))
55      return false;
56  }
57  return true;
58}
59
60static bool PrepareVideoCaptureAttributesMediaFoundation(
61    IMFAttributes** attributes,
62    int count) {
63  EnsureMediaFoundationInit();
64
65  if (FAILED(MFCreateAttributes(attributes, count)))
66    return false;
67
68  return SUCCEEDED((*attributes)->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
69      MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID));
70}
71
72static bool CreateVideoCaptureDeviceMediaFoundation(const char* sym_link,
73                                                    IMFMediaSource** source) {
74  ScopedComPtr<IMFAttributes> attributes;
75  if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 2))
76    return false;
77
78  attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
79                        base::SysUTF8ToWide(sym_link).c_str());
80
81  return SUCCEEDED(MFCreateDeviceSource(attributes, source));
82}
83
84static bool EnumerateVideoDevicesMediaFoundation(IMFActivate*** devices,
85                                                 UINT32* count) {
86  ScopedComPtr<IMFAttributes> attributes;
87  if (!PrepareVideoCaptureAttributesMediaFoundation(attributes.Receive(), 1))
88    return false;
89
90  return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count));
91}
92
93static void GetDeviceNamesDirectShow(
94    const CLSID& class_id,
95    const Name::CaptureApiType capture_api_type,
96    Names* device_names) {
97  DCHECK(device_names);
98  DVLOG(1) << " GetDeviceNamesDirectShow";
99
100  ScopedComPtr<ICreateDevEnum> dev_enum;
101  HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
102                                       CLSCTX_INPROC);
103  if (FAILED(hr))
104    return;
105
106  ScopedComPtr<IEnumMoniker> enum_moniker;
107  hr = dev_enum->CreateClassEnumerator(class_id, enum_moniker.Receive(), 0);
108  // CreateClassEnumerator returns S_FALSE on some Windows OS
109  // when no camera exist. Therefore the FAILED macro can't be used.
110  if (hr != S_OK)
111    return;
112
113  // Enumerate all video capture devices.
114  for (ScopedComPtr<IMoniker> moniker;
115       enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK;
116       moniker.Release()) {
117    ScopedComPtr<IPropertyBag> prop_bag;
118    hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
119    if (FAILED(hr))
120      continue;
121
122    // Find the description or friendly name.
123    ScopedVariant name;
124    hr = prop_bag->Read(L"Description", name.Receive(), 0);
125    if (FAILED(hr))
126      hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
127
128    if (FAILED(hr) || name.type() != VT_BSTR)
129      continue;
130
131    // Ignore all VFW drivers and the special Google Camera Adapter.
132    // Google Camera Adapter is not a real DirectShow camera device.
133    // VFW are very old Video for Windows drivers that can not be used.
134    const wchar_t* str_ptr = V_BSTR(&name);
135    // Name of a fake DirectShow filter that exist on computers with
136    // GTalk installed.
137    static const char kGoogleCameraAdapter[] = "google camera adapter";
138    if (wcsstr(str_ptr, L"(VFW)") != NULL ||
139        LowerCaseEqualsASCII(str_ptr,
140                             str_ptr + arraysize(kGoogleCameraAdapter) - 1,
141                             kGoogleCameraAdapter)) {
142      continue;
143    }
144
145    const std::string device_name(base::SysWideToUTF8(str_ptr));
146    name.Reset();
147    hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
148    std::string id;
149    if (FAILED(hr) || name.type() != VT_BSTR) {
150      id = device_name;
151    } else {
152      DCHECK_EQ(name.type(), VT_BSTR);
153      id = base::SysWideToUTF8(V_BSTR(&name));
154    }
155    device_names->push_back(Name(device_name, id, capture_api_type));
156  }
157}
158
159static void GetDeviceNamesMediaFoundation(Names* device_names) {
160  DVLOG(1) << " GetDeviceNamesMediaFoundation";
161  ScopedCoMem<IMFActivate*> devices;
162  UINT32 count;
163  if (!EnumerateVideoDevicesMediaFoundation(&devices, &count))
164    return;
165
166  for (UINT32 i = 0; i < count; ++i) {
167    ScopedCoMem<wchar_t> name;
168    UINT32 name_size;
169    HRESULT hr = devices[i]->GetAllocatedString(
170        MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size);
171    if (SUCCEEDED(hr)) {
172      ScopedCoMem<wchar_t> id;
173      UINT32 id_size;
174      hr = devices[i]->GetAllocatedString(
175          MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id,
176          &id_size);
177      if (SUCCEEDED(hr)) {
178        device_names->push_back(Name(
179            base::SysWideToUTF8(std::wstring(name, name_size)),
180            base::SysWideToUTF8(std::wstring(id, id_size)),
181            Name::MEDIA_FOUNDATION));
182      }
183    }
184    DLOG_IF(ERROR, FAILED(hr)) << "GetAllocatedString failed: "
185                               << logging::SystemErrorCodeToString(hr);
186    devices[i]->Release();
187  }
188}
189
190static void GetDeviceSupportedFormatsDirectShow(const Name& device,
191                                                VideoCaptureFormats* formats) {
192  DVLOG(1) << "GetDeviceSupportedFormatsDirectShow for " << device.name();
193  ScopedComPtr<ICreateDevEnum> dev_enum;
194  HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
195                                       CLSCTX_INPROC);
196  if (FAILED(hr))
197    return;
198
199  ScopedComPtr<IEnumMoniker> enum_moniker;
200  hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
201                                       enum_moniker.Receive(), 0);
202  // CreateClassEnumerator returns S_FALSE on some Windows OS when no camera
203  // exists. Therefore the FAILED macro can't be used.
204  if (hr != S_OK)
205    return;
206
207  // Walk the capture devices. No need to check for device presence again since
208  // that is anyway needed in GetDeviceFilter(). "google camera adapter" and old
209  // VFW devices are already skipped previously in GetDeviceNames() enumeration.
210  base::win::ScopedComPtr<IBaseFilter> capture_filter;
211  hr = VideoCaptureDeviceWin::GetDeviceFilter(device.capabilities_id(),
212                                              CLSID_VideoInputDeviceCategory,
213                                              capture_filter.Receive());
214  if (!capture_filter) {
215    DLOG(ERROR) << "Failed to create capture filter: "
216                << logging::SystemErrorCodeToString(hr);
217    return;
218  }
219
220  base::win::ScopedComPtr<IPin> output_capture_pin(
221      VideoCaptureDeviceWin::GetPin(capture_filter,
222                                    PINDIR_OUTPUT,
223                                    PIN_CATEGORY_CAPTURE,
224                                    GUID_NULL));
225  if (!output_capture_pin) {
226    DLOG(ERROR) << "Failed to get capture output pin";
227    return;
228  }
229
230  ScopedComPtr<IAMStreamConfig> stream_config;
231  hr = output_capture_pin.QueryInterface(stream_config.Receive());
232  if (FAILED(hr)) {
233    DLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
234                   "capture device: " << logging::SystemErrorCodeToString(hr);
235    return;
236  }
237
238  int count = 0, size = 0;
239  hr = stream_config->GetNumberOfCapabilities(&count, &size);
240  if (FAILED(hr)) {
241    DLOG(ERROR) << "GetNumberOfCapabilities failed: "
242                << logging::SystemErrorCodeToString(hr);
243    return;
244  }
245
246  scoped_ptr<BYTE[]> caps(new BYTE[size]);
247  for (int i = 0; i < count; ++i) {
248    VideoCaptureDeviceWin::ScopedMediaType media_type;
249    hr = stream_config->GetStreamCaps(i, media_type.Receive(), caps.get());
250    // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
251    // macros here since they'll trigger incorrectly.
252    if (hr != S_OK) {
253      DLOG(ERROR) << "GetStreamCaps failed: "
254                  << logging::SystemErrorCodeToString(hr);
255      return;
256    }
257
258    if (media_type->majortype == MEDIATYPE_Video &&
259        media_type->formattype == FORMAT_VideoInfo) {
260      VideoCaptureFormat format;
261      format.pixel_format =
262          VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
263              media_type->subtype);
264      if (format.pixel_format == PIXEL_FORMAT_UNKNOWN)
265        continue;
266      VIDEOINFOHEADER* h =
267          reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
268      format.frame_size.SetSize(h->bmiHeader.biWidth,
269                                h->bmiHeader.biHeight);
270      // Trust the frame rate from the VIDEOINFOHEADER.
271      format.frame_rate = (h->AvgTimePerFrame > 0) ?
272          kSecondsToReferenceTime / static_cast<float>(h->AvgTimePerFrame) :
273          0.0f;
274      formats->push_back(format);
275      DVLOG(1) << device.name() << " " << format.ToString();
276    }
277  }
278}
279
280static void GetDeviceSupportedFormatsMediaFoundation(
281    const Name& device,
282    VideoCaptureFormats* formats) {
283  DVLOG(1) << "GetDeviceSupportedFormatsMediaFoundation for " << device.name();
284  ScopedComPtr<IMFMediaSource> source;
285  if (!CreateVideoCaptureDeviceMediaFoundation(device.id().c_str(),
286                                               source.Receive())) {
287    return;
288  }
289
290  base::win::ScopedComPtr<IMFSourceReader> reader;
291  HRESULT hr =
292      MFCreateSourceReaderFromMediaSource(source, NULL, reader.Receive());
293  if (FAILED(hr)) {
294    DLOG(ERROR) << "MFCreateSourceReaderFromMediaSource failed: "
295                << logging::SystemErrorCodeToString(hr);
296    return;
297  }
298
299  DWORD stream_index = 0;
300  ScopedComPtr<IMFMediaType> type;
301  while (SUCCEEDED(reader->GetNativeMediaType(
302             kFirstVideoStream, stream_index, type.Receive()))) {
303    UINT32 width, height;
304    hr = MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width, &height);
305    if (FAILED(hr)) {
306      DLOG(ERROR) << "MFGetAttributeSize failed: "
307                  << logging::SystemErrorCodeToString(hr);
308      return;
309    }
310    VideoCaptureFormat capture_format;
311    capture_format.frame_size.SetSize(width, height);
312
313    UINT32 numerator, denominator;
314    hr = MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator, &denominator);
315    if (FAILED(hr)) {
316      DLOG(ERROR) << "MFGetAttributeSize failed: "
317                  << logging::SystemErrorCodeToString(hr);
318      return;
319    }
320    capture_format.frame_rate = denominator
321        ? static_cast<float>(numerator) / denominator : 0.0f;
322
323    GUID type_guid;
324    hr = type->GetGUID(MF_MT_SUBTYPE, &type_guid);
325    if (FAILED(hr)) {
326      DLOG(ERROR) << "GetGUID failed: "
327                  << logging::SystemErrorCodeToString(hr);
328      return;
329    }
330    VideoCaptureDeviceMFWin::FormatFromGuid(type_guid,
331                                            &capture_format.pixel_format);
332    type.Release();
333    formats->push_back(capture_format);
334    ++stream_index;
335
336    DVLOG(1) << device.name() << " " << capture_format.ToString();
337  }
338}
339
340// Returns true iff the current platform supports the Media Foundation API
341// and that the DLLs are available.  On Vista this API is an optional download
342// but the API is advertised as a part of Windows 7 and onwards.  However,
343// we've seen that the required DLLs are not available in some Win7
344// distributions such as Windows 7 N and Windows 7 KN.
345// static
346bool VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation() {
347  // Even though the DLLs might be available on Vista, we get crashes
348  // when running our tests on the build bots.
349  if (base::win::GetVersion() < base::win::VERSION_WIN7)
350    return false;
351
352  static bool g_dlls_available = LoadMediaFoundationDlls();
353  return g_dlls_available;
354}
355
356VideoCaptureDeviceFactoryWin::VideoCaptureDeviceFactoryWin() {
357  // Use Media Foundation for Metro processes (after and including Win8) and
358  // DirectShow for any other versions, unless forced via flag. Media Foundation
359  // can also be forced if appropriate flag is set and we are in Windows 7 or
360  // 8 in non-Metro mode.
361  const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
362  use_media_foundation_ = (base::win::IsMetroProcess() &&
363      !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) ||
364     (base::win::GetVersion() >= base::win::VERSION_WIN7 &&
365      cmd_line->HasSwitch(switches::kForceMediaFoundationVideoCapture));
366}
367
368
369scoped_ptr<VideoCaptureDevice> VideoCaptureDeviceFactoryWin::Create(
370    const Name& device_name) {
371  DCHECK(thread_checker_.CalledOnValidThread());
372  scoped_ptr<VideoCaptureDevice> device;
373  if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) {
374    DCHECK(PlatformSupportsMediaFoundation());
375    device.reset(new VideoCaptureDeviceMFWin(device_name));
376    DVLOG(1) << " MediaFoundation Device: " << device_name.name();
377    ScopedComPtr<IMFMediaSource> source;
378    if (!CreateVideoCaptureDeviceMediaFoundation(device_name.id().c_str(),
379                                                 source.Receive())) {
380      return scoped_ptr<VideoCaptureDevice>();
381    }
382    if (!static_cast<VideoCaptureDeviceMFWin*>(device.get())->Init(source))
383      device.reset();
384  } else {
385    DCHECK(device_name.capture_api_type() == Name::DIRECT_SHOW ||
386           device_name.capture_api_type() == Name::DIRECT_SHOW_WDM_CROSSBAR);
387    device.reset(new VideoCaptureDeviceWin(device_name));
388    DVLOG(1) << " DirectShow Device: " << device_name.name();
389    if (!static_cast<VideoCaptureDeviceWin*>(device.get())->Init())
390      device.reset();
391  }
392  return device.Pass();
393}
394
395void VideoCaptureDeviceFactoryWin::GetDeviceNames(Names* device_names) {
396  DCHECK(thread_checker_.CalledOnValidThread());
397  if (use_media_foundation_) {
398    GetDeviceNamesMediaFoundation(device_names);
399  } else {
400    GetDeviceNamesDirectShow(CLSID_VideoInputDeviceCategory,
401                             Name::DIRECT_SHOW,
402                             device_names);
403
404    Names crossbar_device_names;
405    GetDeviceNamesDirectShow(AM_KSCATEGORY_CROSSBAR,
406                             Name::DIRECT_SHOW_WDM_CROSSBAR,
407                             &crossbar_device_names);
408    // Search in the listed |device_names| to find a device with matching USB ID
409    // to each device in |crossbar_device_names|.
410    for (Names::iterator crossbar_device_it = crossbar_device_names.begin();
411         crossbar_device_it != crossbar_device_names.end();
412         ++crossbar_device_it) {
413      const std::string& crossbar_device_model = crossbar_device_it->GetModel();
414      for (Names::const_iterator device_it = device_names->begin();
415           device_it != device_names->end(); ++device_it) {
416        if (crossbar_device_model == device_it->GetModel()) {
417          crossbar_device_it->set_capabilities_id(device_it->id());
418          device_names->push_back(*crossbar_device_it);
419          break;
420        }
421      }
422    }
423  }
424}
425
426void VideoCaptureDeviceFactoryWin::GetDeviceSupportedFormats(
427    const Name& device,
428    VideoCaptureFormats* formats) {
429  DCHECK(thread_checker_.CalledOnValidThread());
430  if (use_media_foundation_)
431    GetDeviceSupportedFormatsMediaFoundation(device, formats);
432  else
433    GetDeviceSupportedFormatsDirectShow(device, formats);
434}
435
436}  // namespace media
437