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/string_util.h"
11 #include "base/sys_string_conversions.h"
12 #include "base/win/scoped_variant.h"
14 using base::win::ScopedComPtr
;
15 using base::win::ScopedVariant
;
19 // Finds and creates a DirectShow Video Capture filter matching the device_name.
20 HRESULT
GetDeviceFilter(const media::VideoCaptureDevice::Name
& device_name
,
21 IBaseFilter
** filter
) {
24 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
25 HRESULT hr
= dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
,
30 ScopedComPtr
<IEnumMoniker
> enum_moniker
;
31 hr
= dev_enum
->CreateClassEnumerator(CLSID_VideoInputDeviceCategory
,
32 enum_moniker
.Receive(), 0);
33 // CreateClassEnumerator returns S_FALSE on some Windows OS
34 // when no camera exist. Therefore the FAILED macro can't be used.
38 ScopedComPtr
<IMoniker
> moniker
;
39 ScopedComPtr
<IBaseFilter
> capture_filter
;
41 while (enum_moniker
->Next(1, moniker
.Receive(), &fetched
) == S_OK
) {
42 ScopedComPtr
<IPropertyBag
> prop_bag
;
43 hr
= moniker
->BindToStorage(0, 0, IID_IPropertyBag
, prop_bag
.ReceiveVoid());
49 // Find the description or friendly name.
50 static const wchar_t* kPropertyNames
[] = {
51 L
"DevicePath", L
"Description", L
"FriendlyName"
55 i
< arraysize(kPropertyNames
) && name
.type() != VT_BSTR
; ++i
) {
56 prop_bag
->Read(kPropertyNames
[i
], name
.Receive(), 0);
58 if (name
.type() == VT_BSTR
) {
59 std::string
device_path(base::SysWideToUTF8(V_BSTR(&name
)));
60 if (device_path
.compare(device_name
.unique_id
) == 0) {
61 // We have found the requested device
62 hr
= moniker
->BindToObject(0, 0, IID_IBaseFilter
,
63 capture_filter
.ReceiveVoid());
64 DVPLOG_IF(2, FAILED(hr
)) << "Failed to bind camera filter.";
71 *filter
= capture_filter
.Detach();
72 if (!*filter
&& SUCCEEDED(hr
))
73 hr
= HRESULT_FROM_WIN32(ERROR_NOT_FOUND
);
78 // Check if a Pin matches a category.
79 bool PinMatchesCategory(IPin
* pin
, REFGUID category
) {
82 ScopedComPtr
<IKsPropertySet
> ks_property
;
83 HRESULT hr
= ks_property
.QueryFrom(pin
);
87 hr
= ks_property
->Get(AMPROPSETID_Pin
, AMPROPERTY_PIN_CATEGORY
, NULL
, 0,
88 &pin_category
, sizeof(pin_category
), &return_value
);
89 if (SUCCEEDED(hr
) && (return_value
== sizeof(pin_category
))) {
90 found
= (pin_category
== category
) ? true : false;
96 // Finds a IPin on a IBaseFilter given the direction an category.
97 HRESULT
GetPin(IBaseFilter
* filter
, PIN_DIRECTION pin_dir
, REFGUID category
,
100 ScopedComPtr
<IEnumPins
> pin_emum
;
101 HRESULT hr
= filter
->EnumPins(pin_emum
.Receive());
102 if (pin_emum
== NULL
)
105 // Get first unconnected pin.
106 hr
= pin_emum
->Reset(); // set to first pin
107 while ((hr
= pin_emum
->Next(1, pin
, NULL
)) == S_OK
) {
108 PIN_DIRECTION this_pin_dir
= static_cast<PIN_DIRECTION
>(-1);
109 hr
= (*pin
)->QueryDirection(&this_pin_dir
);
110 if (pin_dir
== this_pin_dir
) {
111 if (category
== GUID_NULL
|| PinMatchesCategory(*pin
, category
))
120 // Release the format block for a media type.
121 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
122 void FreeMediaType(AM_MEDIA_TYPE
* mt
) {
123 if (mt
->cbFormat
!= 0) {
124 CoTaskMemFree(mt
->pbFormat
);
128 if (mt
->pUnk
!= NULL
) {
130 // pUnk should not be used.
136 // Delete a media type structure that was allocated on the heap.
137 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
138 void DeleteMediaType(AM_MEDIA_TYPE
* mt
) {
145 // Help structure used for comparing video capture capabilities.
146 struct ResolutionDiff
{
147 int capability_index
;
151 media::VideoCaptureDevice::Format color
;
154 bool CompareHeight(const ResolutionDiff
& item1
, const ResolutionDiff
& item2
) {
155 return abs(item1
.diff_height
) < abs(item2
.diff_height
);
158 bool CompareWidth(const ResolutionDiff
& item1
, const ResolutionDiff
& item2
) {
159 return abs(item1
.diff_width
) < abs(item2
.diff_width
);
162 bool CompareFrameRate(const ResolutionDiff
& item1
,
163 const ResolutionDiff
& item2
) {
164 return abs(item1
.diff_frame_rate
) < abs(item2
.diff_frame_rate
);
167 bool CompareColor(const ResolutionDiff
& item1
, const ResolutionDiff
& item2
) {
168 return (item1
.color
< item2
.color
);
175 // Name of a fake DirectShow filter that exist on computers with
177 static const char kGoogleCameraAdapter
[] = "google camera adapter";
179 // Gets the names of all video capture devices connected to this computer.
180 void VideoCaptureDevice::GetDeviceNames(Names
* device_names
) {
181 DCHECK(device_names
);
183 base::win::ScopedCOMInitializer coinit
;
184 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
185 HRESULT hr
= dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
,
190 ScopedComPtr
<IEnumMoniker
> enum_moniker
;
191 hr
= dev_enum
->CreateClassEnumerator(CLSID_VideoInputDeviceCategory
,
192 enum_moniker
.Receive(), 0);
193 // CreateClassEnumerator returns S_FALSE on some Windows OS
194 // when no camera exist. Therefore the FAILED macro can't be used.
198 device_names
->clear();
200 // Enumerate all video capture devices.
201 ScopedComPtr
<IMoniker
> moniker
;
203 while (enum_moniker
->Next(1, moniker
.Receive(), NULL
) == S_OK
) {
205 ScopedComPtr
<IPropertyBag
> prop_bag
;
206 hr
= moniker
->BindToStorage(0, 0, IID_IPropertyBag
, prop_bag
.ReceiveVoid());
212 // Find the description or friendly name.
214 hr
= prop_bag
->Read(L
"Description", name
.Receive(), 0);
216 hr
= prop_bag
->Read(L
"FriendlyName", name
.Receive(), 0);
218 if (SUCCEEDED(hr
) && name
.type() == VT_BSTR
) {
219 // Ignore all VFW drivers and the special Google Camera Adapter.
220 // Google Camera Adapter is not a real DirectShow camera device.
221 // VFW is very old Video for Windows drivers that can not be used.
222 const wchar_t* str_ptr
= V_BSTR(&name
);
223 const int name_length
= arraysize(kGoogleCameraAdapter
) - 1;
225 if ((wcsstr(str_ptr
, L
"(VFW)") == NULL
) &&
226 lstrlenW(str_ptr
) < name_length
||
227 (!(LowerCaseEqualsASCII(str_ptr
, str_ptr
+ name_length
,
228 kGoogleCameraAdapter
)))) {
229 device
.device_name
= base::SysWideToUTF8(str_ptr
);
231 hr
= prop_bag
->Read(L
"DevicePath", name
.Receive(), 0);
233 device
.unique_id
= device
.device_name
;
234 } else if (name
.type() == VT_BSTR
) {
235 device
.unique_id
= base::SysWideToUTF8(V_BSTR(&name
));
238 device_names
->push_back(device
);
245 VideoCaptureDevice
* VideoCaptureDevice::Create(const Name
& device_name
) {
246 VideoCaptureDeviceWin
* self
= new VideoCaptureDeviceWin(device_name
);
247 if (self
&& self
->Init())
254 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name
& device_name
)
255 : device_name_(device_name
),
260 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
262 media_control_
->Stop();
264 if (graph_builder_
) {
266 graph_builder_
->RemoveFilter(sink_filter_
);
271 graph_builder_
->RemoveFilter(capture_filter_
);
274 graph_builder_
->RemoveFilter(mjpg_filter_
);
278 bool VideoCaptureDeviceWin::Init() {
279 HRESULT hr
= GetDeviceFilter(device_name_
, capture_filter_
.Receive());
280 if (!capture_filter_
) {
281 DVLOG(2) << "Failed to create capture filter.";
285 hr
= GetPin(capture_filter_
, PINDIR_OUTPUT
, PIN_CATEGORY_CAPTURE
,
286 output_capture_pin_
.Receive());
287 if (!output_capture_pin_
) {
288 DVLOG(2) << "Failed to get capture output pin";
292 // Create the sink filter used for receiving Captured frames.
293 sink_filter_
= new SinkFilter(this);
294 if (sink_filter_
== NULL
) {
295 DVLOG(2) << "Failed to create send filter";
299 input_sink_pin_
= sink_filter_
->GetPin(0);
301 hr
= graph_builder_
.CreateInstance(CLSID_FilterGraph
, NULL
,
302 CLSCTX_INPROC_SERVER
);
304 DVLOG(2) << "Failed to create graph builder.";
308 hr
= graph_builder_
.QueryInterface(media_control_
.Receive());
310 DVLOG(2) << "Failed to create media control builder.";
314 hr
= graph_builder_
->AddFilter(capture_filter_
, NULL
);
316 DVLOG(2) << "Failed to add the capture device to the graph.";
320 hr
= graph_builder_
->AddFilter(sink_filter_
, NULL
);
322 DVLOG(2)<< "Failed to add the send filter to the graph.";
326 return CreateCapabilityMap();
329 void VideoCaptureDeviceWin::Allocate(
333 VideoCaptureDevice::EventHandler
* observer
) {
337 observer_
= observer
;
338 // Get the camera capability that best match the requested resolution.
339 const int capability_index
= GetBestMatchedCapability(width
, height
,
341 Capability capability
= capabilities_
[capability_index
];
343 // Reduce the frame rate if the requested frame rate is lower
344 // than the capability.
345 if (capability
.frame_rate
> frame_rate
)
346 capability
.frame_rate
= frame_rate
;
348 AM_MEDIA_TYPE
* pmt
= NULL
;
349 VIDEO_STREAM_CONFIG_CAPS caps
;
351 ScopedComPtr
<IAMStreamConfig
> stream_config
;
352 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
354 SetErrorState("Can't get the Capture format settings");
358 // Get the windows capability from the capture device.
359 hr
= stream_config
->GetStreamCaps(capability_index
, &pmt
,
360 reinterpret_cast<BYTE
*>(&caps
));
362 if (pmt
->formattype
== FORMAT_VideoInfo
) {
363 VIDEOINFOHEADER
* h
= reinterpret_cast<VIDEOINFOHEADER
*>(pmt
->pbFormat
);
364 if (capability
.frame_rate
> 0)
365 h
->AvgTimePerFrame
= kSecondsToReferenceTime
/ capability
.frame_rate
;
367 // Set the sink filter to request this capability.
368 sink_filter_
->SetRequestedMediaCapability(capability
);
369 // Order the capture device to use this capability.
370 hr
= stream_config
->SetFormat(pmt
);
374 SetErrorState("Failed to set capture device output format");
376 if (capability
.color
== VideoCaptureDevice::kMJPEG
&& !mjpg_filter_
.get()) {
377 // Create MJPG filter if we need it.
378 hr
= mjpg_filter_
.CreateInstance(CLSID_MjpegDec
, NULL
, CLSCTX_INPROC
);
381 GetPin(mjpg_filter_
, PINDIR_INPUT
, GUID_NULL
, input_mjpg_pin_
.Receive());
382 GetPin(mjpg_filter_
, PINDIR_OUTPUT
, GUID_NULL
,
383 output_mjpg_pin_
.Receive());
384 hr
= graph_builder_
->AddFilter(mjpg_filter_
, NULL
);
388 mjpg_filter_
.Release();
389 input_mjpg_pin_
.Release();
390 output_mjpg_pin_
.Release();
394 if (capability
.color
== VideoCaptureDevice::kMJPEG
&& mjpg_filter_
.get()) {
395 // Connect the camera to the MJPEG decoder.
396 hr
= graph_builder_
->ConnectDirect(output_capture_pin_
, input_mjpg_pin_
,
398 // Connect the MJPEG filter to the Capture filter.
399 hr
+= graph_builder_
->ConnectDirect(output_mjpg_pin_
, input_sink_pin_
,
402 hr
= graph_builder_
->ConnectDirect(output_capture_pin_
, input_sink_pin_
,
407 SetErrorState("Failed to connect the Capture graph.");
411 hr
= media_control_
->Pause();
413 SetErrorState("Failed to Pause the Capture device. "
414 "Is it already occupied?");
418 // Get the capability back from the sink filter after the filter have been
420 const Capability
& used_capability
= sink_filter_
->ResultingCapability();
421 observer_
->OnFrameInfo(used_capability
);
426 void VideoCaptureDeviceWin::Start() {
427 if (state_
!= kAllocated
)
430 HRESULT hr
= media_control_
->Run();
432 SetErrorState("Failed to start the Capture device.");
439 void VideoCaptureDeviceWin::Stop() {
440 if (state_
!= kCapturing
)
443 HRESULT hr
= media_control_
->Stop();
445 SetErrorState("Failed to stop the capture graph.");
452 void VideoCaptureDeviceWin::DeAllocate() {
456 HRESULT hr
= media_control_
->Stop();
457 graph_builder_
->Disconnect(output_capture_pin_
);
458 graph_builder_
->Disconnect(input_sink_pin_
);
460 // If the _mjpg filter exist disconnect it even if it has not been used.
462 graph_builder_
->Disconnect(input_mjpg_pin_
);
463 graph_builder_
->Disconnect(output_mjpg_pin_
);
467 SetErrorState("Failed to Stop the Capture device");
474 const VideoCaptureDevice::Name
& VideoCaptureDeviceWin::device_name() {
478 // Implements SinkFilterObserver::SinkFilterObserver.
479 void VideoCaptureDeviceWin::FrameReceived(const uint8
* buffer
,
481 observer_
->OnIncomingCapturedFrame(buffer
, length
, base::Time::Now());
484 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
485 ScopedComPtr
<IAMStreamConfig
> stream_config
;
486 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
488 DVLOG(2) << "Failed to get IAMStreamConfig interface from "
493 // Get interface used for getting the frame rate.
494 ScopedComPtr
<IAMVideoControl
> video_control
;
495 hr
= capture_filter_
.QueryInterface(video_control
.Receive());
496 DVLOG_IF(2, FAILED(hr
)) << "IAMVideoControl Interface NOT SUPPORTED";
498 AM_MEDIA_TYPE
* media_type
= NULL
;
499 VIDEO_STREAM_CONFIG_CAPS caps
;
502 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
504 DVLOG(2) << "Failed to GetNumberOfCapabilities";
508 for (int i
= 0; i
< count
; ++i
) {
509 hr
= stream_config
->GetStreamCaps(i
, &media_type
,
510 reinterpret_cast<BYTE
*>(&caps
));
512 DVLOG(2) << "Failed to GetStreamCaps";
516 if (media_type
->majortype
== MEDIATYPE_Video
&&
517 media_type
->formattype
== FORMAT_VideoInfo
) {
518 Capability capability
;
519 REFERENCE_TIME time_per_frame
= 0;
522 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
523 capability
.width
= h
->bmiHeader
.biWidth
;
524 capability
.height
= h
->bmiHeader
.biHeight
;
525 time_per_frame
= h
->AvgTimePerFrame
;
527 // Try to get the max frame rate from IAMVideoControl.
528 if (video_control
.get()) {
529 LONGLONG
* max_fps_ptr
;
532 size
.cx
= capability
.width
;
533 size
.cy
= capability
.height
;
535 // GetFrameRateList doesn't return max frame rate always
536 // eg: Logitech Notebook. This may be due to a bug in that API
537 // because GetFrameRateList array is reversed in the above camera. So
538 // a util method written. Can't assume the first value will return
540 hr
= video_control
->GetFrameRateList(output_capture_pin_
, i
, size
,
541 &list_size
, &max_fps_ptr
);
543 if (SUCCEEDED(hr
) && list_size
> 0) {
544 int min_time
= *std::min_element(max_fps_ptr
,
545 max_fps_ptr
+ list_size
);
546 capability
.frame_rate
= (min_time
> 0) ?
547 kSecondsToReferenceTime
/ min_time
: 0;
549 // Get frame rate from VIDEOINFOHEADER.
550 capability
.frame_rate
= (time_per_frame
> 0) ?
551 static_cast<int>(kSecondsToReferenceTime
/ time_per_frame
) : 0;
554 // Get frame rate from VIDEOINFOHEADER since IAMVideoControl is
556 capability
.frame_rate
= (time_per_frame
> 0) ?
557 static_cast<int>(kSecondsToReferenceTime
/ time_per_frame
) : 0;
560 // We can't switch MEDIATYPE :~(.
561 if (media_type
->subtype
== kMediaSubTypeI420
) {
562 capability
.color
= VideoCaptureDevice::kI420
;
563 } else if (media_type
->subtype
== MEDIASUBTYPE_IYUV
) {
564 // This is identical to kI420.
565 capability
.color
= VideoCaptureDevice::kI420
;
566 } else if (media_type
->subtype
== MEDIASUBTYPE_RGB24
) {
567 capability
.color
= VideoCaptureDevice::kRGB24
;
568 } else if (media_type
->subtype
== MEDIASUBTYPE_YUY2
) {
569 capability
.color
= VideoCaptureDevice::kYUY2
;
570 } else if (media_type
->subtype
== MEDIASUBTYPE_MJPG
) {
571 capability
.color
= VideoCaptureDevice::kMJPEG
;
574 StringFromGUID2(media_type
->subtype
, guid_str
, arraysize(guid_str
));
575 DVLOG(2) << "Device support unknown media type " << guid_str
;
578 capabilities_
[i
] = capability
;
580 DeleteMediaType(media_type
);
584 return capabilities_
.size() > 0;
587 // Loops through the list of capabilities and returns an index of the best
588 // matching capability.
589 // The algorithm prioritize height, width, frame rate and color format in that
591 int VideoCaptureDeviceWin::GetBestMatchedCapability(int requested_width
,
592 int requested_height
,
593 int requested_frame_rate
) {
594 std::list
<ResolutionDiff
> diff_list
;
596 // Loop through the candidates to create a list of differentials between the
597 // requested resolution and the camera capability.
598 for (CapabilityMap::iterator iterator
= capabilities_
.begin();
599 iterator
!= capabilities_
.end();
601 Capability capability
= iterator
->second
;
604 diff
.capability_index
= iterator
->first
;
605 diff
.diff_width
= capability
.width
- requested_width
;
606 diff
.diff_height
= capability
.height
- requested_height
;
607 diff
.diff_frame_rate
= capability
.frame_rate
- requested_frame_rate
;
608 diff
.color
= capability
.color
;
609 diff_list
.push_back(diff
);
612 // Sort the best height candidates.
613 diff_list
.sort(&CompareHeight
);
614 int best_diff
= diff_list
.front().diff_height
;
615 for (std::list
<ResolutionDiff
>::iterator it
= diff_list
.begin();
616 it
!= diff_list
.end(); ++it
) {
617 if (it
->diff_height
!= best_diff
) {
618 // Remove all candidates but the best.
619 diff_list
.erase(it
, diff_list
.end());
624 // Sort the best width candidates.
625 diff_list
.sort(&CompareWidth
);
626 best_diff
= diff_list
.front().diff_width
;
627 for (std::list
<ResolutionDiff
>::iterator it
= diff_list
.begin();
628 it
!= diff_list
.end(); ++it
) {
629 if (it
->diff_width
!= best_diff
) {
630 // Remove all candidates but the best.
631 diff_list
.erase(it
, diff_list
.end());
636 // Sort the best frame rate candidates.
637 diff_list
.sort(&CompareFrameRate
);
638 best_diff
= diff_list
.front().diff_frame_rate
;
639 for (std::list
<ResolutionDiff
>::iterator it
= diff_list
.begin();
640 it
!= diff_list
.end(); ++it
) {
641 if (it
->diff_frame_rate
!= best_diff
) {
642 diff_list
.erase(it
, diff_list
.end());
647 // Decide the best color format.
648 diff_list
.sort(&CompareColor
);
649 return diff_list
.front().capability_index
;
652 void VideoCaptureDeviceWin::SetErrorState(const char* reason
) {
653 DLOG(ERROR
) << reason
;
655 observer_
->OnError();