1 // Copyright (c) 2012 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/video/capture/win/video_capture_device_win.h"
10 #include "base/command_line.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/win/metro.h"
14 #include "base/win/scoped_co_mem.h"
15 #include "base/win/scoped_variant.h"
16 #include "media/base/media_switches.h"
17 #include "media/video/capture/win/video_capture_device_mf_win.h"
19 using base::win::ScopedCoMem;
20 using base::win::ScopedComPtr;
21 using base::win::ScopedVariant;
26 // Finds and creates a DirectShow Video Capture filter matching the device_name.
27 HRESULT GetDeviceFilter(const VideoCaptureDevice::Name& device_name,
28 IBaseFilter** filter) {
31 ScopedComPtr<ICreateDevEnum> dev_enum;
32 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
37 ScopedComPtr<IEnumMoniker> enum_moniker;
38 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
39 enum_moniker.Receive(), 0);
40 // CreateClassEnumerator returns S_FALSE on some Windows OS
41 // when no camera exist. Therefore the FAILED macro can't be used.
45 ScopedComPtr<IMoniker> moniker;
46 ScopedComPtr<IBaseFilter> capture_filter;
48 while (enum_moniker->Next(1, moniker.Receive(), &fetched) == S_OK) {
49 ScopedComPtr<IPropertyBag> prop_bag;
50 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
56 // Find the description or friendly name.
57 static const wchar_t* kPropertyNames[] = {
58 L"DevicePath", L"Description", L"FriendlyName"
62 i < arraysize(kPropertyNames) && name.type() != VT_BSTR; ++i) {
63 prop_bag->Read(kPropertyNames[i], name.Receive(), 0);
65 if (name.type() == VT_BSTR) {
66 std::string device_path(base::SysWideToUTF8(V_BSTR(&name)));
67 if (device_path.compare(device_name.id()) == 0) {
68 // We have found the requested device
69 hr = moniker->BindToObject(0, 0, IID_IBaseFilter,
70 capture_filter.ReceiveVoid());
71 DVPLOG_IF(2, FAILED(hr)) << "Failed to bind camera filter.";
78 *filter = capture_filter.Detach();
79 if (!*filter && SUCCEEDED(hr))
80 hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
85 // Check if a Pin matches a category.
86 bool PinMatchesCategory(IPin* pin, REFGUID category) {
89 ScopedComPtr<IKsPropertySet> ks_property;
90 HRESULT hr = ks_property.QueryFrom(pin);
94 hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
95 &pin_category, sizeof(pin_category), &return_value);
96 if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) {
97 found = (pin_category == category);
103 // Finds a IPin on a IBaseFilter given the direction an category.
104 HRESULT GetPin(IBaseFilter* filter, PIN_DIRECTION pin_dir, REFGUID category,
107 ScopedComPtr<IEnumPins> pin_emum;
108 HRESULT hr = filter->EnumPins(pin_emum.Receive());
109 if (pin_emum == NULL)
112 // Get first unconnected pin.
113 hr = pin_emum->Reset(); // set to first pin
114 while ((hr = pin_emum->Next(1, pin, NULL)) == S_OK) {
115 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1);
116 hr = (*pin)->QueryDirection(&this_pin_dir);
117 if (pin_dir == this_pin_dir) {
118 if (category == GUID_NULL || PinMatchesCategory(*pin, category))
127 // Release the format block for a media type.
128 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
129 void FreeMediaType(AM_MEDIA_TYPE* mt) {
130 if (mt->cbFormat != 0) {
131 CoTaskMemFree(mt->pbFormat);
135 if (mt->pUnk != NULL) {
137 // pUnk should not be used.
143 // Delete a media type structure that was allocated on the heap.
144 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
145 void DeleteMediaType(AM_MEDIA_TYPE* mt) {
155 void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
156 const CommandLine* cmd_line = CommandLine::ForCurrentProcess();
157 // Use Media Foundation for Metro processes (after and including Win8)
158 // and DirectShow for any other platforms.
159 if (base::win::IsMetroProcess() &&
160 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) {
161 VideoCaptureDeviceMFWin::GetDeviceNames(device_names);
163 VideoCaptureDeviceWin::GetDeviceNames(device_names);
168 void VideoCaptureDevice::GetDeviceSupportedFormats(const Name& device,
169 VideoCaptureCapabilities* formats) {
174 VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
175 VideoCaptureDevice* ret = NULL;
176 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) {
177 DCHECK(VideoCaptureDeviceMFWin::PlatformSupported());
178 scoped_ptr<VideoCaptureDeviceMFWin> device(
179 new VideoCaptureDeviceMFWin(device_name));
180 DVLOG(1) << " MediaFoundation Device: " << device_name.name();
182 ret = device.release();
183 } else if (device_name.capture_api_type() == Name::DIRECT_SHOW) {
184 scoped_ptr<VideoCaptureDeviceWin> device(
185 new VideoCaptureDeviceWin(device_name));
186 DVLOG(1) << " DirectShow Device: " << device_name.name();
188 ret = device.release();
190 NOTREACHED() << " Couldn't recognize VideoCaptureDevice type";
197 void VideoCaptureDeviceWin::GetDeviceNames(Names* device_names) {
198 DCHECK(device_names);
200 ScopedComPtr<ICreateDevEnum> dev_enum;
201 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
206 ScopedComPtr<IEnumMoniker> enum_moniker;
207 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
208 enum_moniker.Receive(), 0);
209 // CreateClassEnumerator returns S_FALSE on some Windows OS
210 // when no camera exist. Therefore the FAILED macro can't be used.
214 device_names->clear();
216 // Name of a fake DirectShow filter that exist on computers with
218 static const char kGoogleCameraAdapter[] = "google camera adapter";
220 // Enumerate all video capture devices.
221 ScopedComPtr<IMoniker> moniker;
223 while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) {
224 ScopedComPtr<IPropertyBag> prop_bag;
225 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
231 // Find the description or friendly name.
233 hr = prop_bag->Read(L"Description", name.Receive(), 0);
235 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0);
237 if (SUCCEEDED(hr) && name.type() == VT_BSTR) {
238 // Ignore all VFW drivers and the special Google Camera Adapter.
239 // Google Camera Adapter is not a real DirectShow camera device.
240 // VFW is very old Video for Windows drivers that can not be used.
241 const wchar_t* str_ptr = V_BSTR(&name);
242 const int name_length = arraysize(kGoogleCameraAdapter) - 1;
244 if ((wcsstr(str_ptr, L"(VFW)") == NULL) &&
245 lstrlenW(str_ptr) < name_length ||
246 (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length,
247 kGoogleCameraAdapter)))) {
249 std::string device_name(base::SysWideToUTF8(str_ptr));
251 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0);
252 if (FAILED(hr) || name.type() != VT_BSTR) {
255 DCHECK_EQ(name.type(), VT_BSTR);
256 id = base::SysWideToUTF8(V_BSTR(&name));
259 device_names->push_back(Name(device_name, id, Name::DIRECT_SHOW));
266 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name)
267 : device_name_(device_name),
272 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
273 DCHECK(CalledOnValidThread());
275 media_control_->Stop();
277 if (graph_builder_) {
279 graph_builder_->RemoveFilter(sink_filter_);
284 graph_builder_->RemoveFilter(capture_filter_);
287 graph_builder_->RemoveFilter(mjpg_filter_);
291 bool VideoCaptureDeviceWin::Init() {
292 DCHECK(CalledOnValidThread());
293 HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive());
294 if (!capture_filter_) {
295 DVLOG(2) << "Failed to create capture filter.";
299 hr = GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE,
300 output_capture_pin_.Receive());
301 if (!output_capture_pin_) {
302 DVLOG(2) << "Failed to get capture output pin";
306 // Create the sink filter used for receiving Captured frames.
307 sink_filter_ = new SinkFilter(this);
308 if (sink_filter_ == NULL) {
309 DVLOG(2) << "Failed to create send filter";
313 input_sink_pin_ = sink_filter_->GetPin(0);
315 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
316 CLSCTX_INPROC_SERVER);
318 DVLOG(2) << "Failed to create graph builder.";
322 hr = graph_builder_.QueryInterface(media_control_.Receive());
324 DVLOG(2) << "Failed to create media control builder.";
328 hr = graph_builder_->AddFilter(capture_filter_, NULL);
330 DVLOG(2) << "Failed to add the capture device to the graph.";
334 hr = graph_builder_->AddFilter(sink_filter_, NULL);
336 DVLOG(2)<< "Failed to add the send filter to the graph.";
340 return CreateCapabilityMap();
343 void VideoCaptureDeviceWin::AllocateAndStart(
344 const VideoCaptureCapability& capture_format,
345 scoped_ptr<VideoCaptureDevice::Client> client) {
346 DCHECK(CalledOnValidThread());
350 client_ = client.Pass();
352 // Get the camera capability that best match the requested resolution.
353 const VideoCaptureCapabilityWin& found_capability =
354 capabilities_.GetBestMatchedCapability(capture_format.width,
355 capture_format.height,
356 capture_format.frame_rate);
357 VideoCaptureCapability capability = found_capability;
359 // Reduce the frame rate if the requested frame rate is lower
360 // than the capability.
361 if (capability.frame_rate > capture_format.frame_rate)
362 capability.frame_rate = capture_format.frame_rate;
364 AM_MEDIA_TYPE* pmt = NULL;
365 VIDEO_STREAM_CONFIG_CAPS caps;
367 ScopedComPtr<IAMStreamConfig> stream_config;
368 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
370 SetErrorState("Can't get the Capture format settings");
374 // Get the windows capability from the capture device.
375 hr = stream_config->GetStreamCaps(found_capability.stream_index, &pmt,
376 reinterpret_cast<BYTE*>(&caps));
378 if (pmt->formattype == FORMAT_VideoInfo) {
379 VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat);
380 if (capability.frame_rate > 0)
381 h->AvgTimePerFrame = kSecondsToReferenceTime / capability.frame_rate;
383 // Set the sink filter to request this capability.
384 sink_filter_->SetRequestedMediaCapability(capability);
385 // Order the capture device to use this capability.
386 hr = stream_config->SetFormat(pmt);
390 SetErrorState("Failed to set capture device output format");
392 if (capability.color == PIXEL_FORMAT_MJPEG &&
393 !mjpg_filter_.get()) {
394 // Create MJPG filter if we need it.
395 hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC);
398 GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL, input_mjpg_pin_.Receive());
399 GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL,
400 output_mjpg_pin_.Receive());
401 hr = graph_builder_->AddFilter(mjpg_filter_, NULL);
405 mjpg_filter_.Release();
406 input_mjpg_pin_.Release();
407 output_mjpg_pin_.Release();
411 if (capability.color == PIXEL_FORMAT_MJPEG &&
412 mjpg_filter_.get()) {
413 // Connect the camera to the MJPEG decoder.
414 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_,
416 // Connect the MJPEG filter to the Capture filter.
417 hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_,
420 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_,
425 SetErrorState("Failed to connect the Capture graph.");
429 hr = media_control_->Pause();
431 SetErrorState("Failed to Pause the Capture device. "
432 "Is it already occupied?");
436 // Get the capability back from the sink filter after the filter have been
438 const VideoCaptureCapability& used_capability
439 = sink_filter_->ResultingCapability();
440 client_->OnFrameInfo(used_capability);
443 hr = media_control_->Run();
445 SetErrorState("Failed to start the Capture device.");
452 void VideoCaptureDeviceWin::StopAndDeAllocate() {
453 DCHECK(CalledOnValidThread());
454 if (state_ != kCapturing)
457 HRESULT hr = media_control_->Stop();
459 SetErrorState("Failed to stop the capture graph.");
463 graph_builder_->Disconnect(output_capture_pin_);
464 graph_builder_->Disconnect(input_sink_pin_);
466 // If the _mjpg filter exist disconnect it even if it has not been used.
468 graph_builder_->Disconnect(input_mjpg_pin_);
469 graph_builder_->Disconnect(output_mjpg_pin_);
473 SetErrorState("Failed to Stop the Capture device");
480 // Implements SinkFilterObserver::SinkFilterObserver.
481 void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer,
483 client_->OnIncomingCapturedFrame(buffer, length, base::Time::Now(),
487 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
488 DCHECK(CalledOnValidThread());
489 ScopedComPtr<IAMStreamConfig> stream_config;
490 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
492 DVLOG(2) << "Failed to get IAMStreamConfig interface from "
497 // Get interface used for getting the frame rate.
498 ScopedComPtr<IAMVideoControl> video_control;
499 hr = capture_filter_.QueryInterface(video_control.Receive());
500 DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED";
502 AM_MEDIA_TYPE* media_type = NULL;
503 VIDEO_STREAM_CONFIG_CAPS caps;
506 hr = stream_config->GetNumberOfCapabilities(&count, &size);
508 DVLOG(2) << "Failed to GetNumberOfCapabilities";
512 for (int i = 0; i < count; ++i) {
513 hr = stream_config->GetStreamCaps(i, &media_type,
514 reinterpret_cast<BYTE*>(&caps));
515 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
516 // macros here since they'll trigger incorrectly.
518 DVLOG(2) << "Failed to GetStreamCaps";
522 if (media_type->majortype == MEDIATYPE_Video &&
523 media_type->formattype == FORMAT_VideoInfo) {
524 VideoCaptureCapabilityWin capability(i);
526 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
527 capability.width = h->bmiHeader.biWidth;
528 capability.height = h->bmiHeader.biHeight;
530 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use
531 // the value from VIDEOINFOHEADER.
532 REFERENCE_TIME time_per_frame = h->AvgTimePerFrame;
534 ScopedCoMem<LONGLONG> max_fps;
536 SIZE size = { capability.width, capability.height };
538 // GetFrameRateList doesn't return max frame rate always
539 // eg: Logitech Notebook. This may be due to a bug in that API
540 // because GetFrameRateList array is reversed in the above camera. So
541 // a util method written. Can't assume the first value will return
543 hr = video_control->GetFrameRateList(output_capture_pin_, i, size,
544 &list_size, &max_fps);
545 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some
546 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates
547 // into success, so explicitly check S_OK. See http://crbug.com/306237.
548 if (hr == S_OK && list_size > 0 && max_fps) {
549 time_per_frame = *std::min_element(max_fps.get(),
550 max_fps.get() + list_size);
554 capability.frame_rate = (time_per_frame > 0) ?
555 static_cast<int>(kSecondsToReferenceTime / time_per_frame) : 0;
557 // DirectShow works at the moment only on integer frame_rate but the
558 // best capability matching class works on rational frame rates.
559 capability.frame_rate_numerator = capability.frame_rate;
560 capability.frame_rate_denominator = 1;
562 // We can't switch MEDIATYPE :~(.
563 if (media_type->subtype == kMediaSubTypeI420) {
564 capability.color = PIXEL_FORMAT_I420;
565 } else if (media_type->subtype == MEDIASUBTYPE_IYUV) {
566 // This is identical to PIXEL_FORMAT_I420.
567 capability.color = PIXEL_FORMAT_I420;
568 } else if (media_type->subtype == MEDIASUBTYPE_RGB24) {
569 capability.color = PIXEL_FORMAT_RGB24;
570 } else if (media_type->subtype == MEDIASUBTYPE_YUY2) {
571 capability.color = PIXEL_FORMAT_YUY2;
572 } else if (media_type->subtype == MEDIASUBTYPE_MJPG) {
573 capability.color = PIXEL_FORMAT_MJPEG;
574 } else if (media_type->subtype == MEDIASUBTYPE_UYVY) {
575 capability.color = PIXEL_FORMAT_UYVY;
576 } else if (media_type->subtype == MEDIASUBTYPE_ARGB32) {
577 capability.color = PIXEL_FORMAT_ARGB;
580 StringFromGUID2(media_type->subtype, guid_str, arraysize(guid_str));
581 DVLOG(2) << "Device supports (also) an unknown media type " << guid_str;
584 capabilities_.Add(capability);
586 DeleteMediaType(media_type);
590 return !capabilities_.empty();
593 void VideoCaptureDeviceWin::SetErrorState(const char* reason) {
594 DCHECK(CalledOnValidThread());