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