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/capture/video/win/video_capture_device_win.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/win/scoped_co_mem.h"
15 #include "base/win/scoped_variant.h"
16 #include "media/capture/video/win/video_capture_device_mf_win.h"
18 using base::win::ScopedCoMem
;
19 using base::win::ScopedComPtr
;
20 using base::win::ScopedVariant
;
24 // Check if a Pin matches a category.
25 bool PinMatchesCategory(IPin
* pin
, REFGUID category
) {
28 ScopedComPtr
<IKsPropertySet
> ks_property
;
29 HRESULT hr
= ks_property
.QueryFrom(pin
);
33 hr
= ks_property
->Get(AMPROPSETID_Pin
, AMPROPERTY_PIN_CATEGORY
, NULL
, 0,
34 &pin_category
, sizeof(pin_category
), &return_value
);
35 if (SUCCEEDED(hr
) && (return_value
== sizeof(pin_category
))) {
36 found
= (pin_category
== category
);
42 // Check if a Pin's MediaType matches a given |major_type|.
43 bool PinMatchesMajorType(IPin
* pin
, REFGUID major_type
) {
45 AM_MEDIA_TYPE connection_media_type
;
46 HRESULT hr
= pin
->ConnectionMediaType(&connection_media_type
);
47 return SUCCEEDED(hr
) && connection_media_type
.majortype
== major_type
;
50 // Finds and creates a DirectShow Video Capture filter matching the |device_id|.
51 // |class_id| is usually CLSID_VideoInputDeviceCategory for standard DirectShow
52 // devices but might also be AM_KSCATEGORY_CAPTURE or AM_KSCATEGORY_CROSSBAR, to
53 // enumerate WDM capture devices or WDM crossbars, respectively.
55 HRESULT
VideoCaptureDeviceWin::GetDeviceFilter(const std::string
& device_id
,
56 const CLSID device_class_id
,
57 IBaseFilter
** filter
) {
60 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
62 dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
, CLSCTX_INPROC
);
66 ScopedComPtr
<IEnumMoniker
> enum_moniker
;
67 hr
= dev_enum
->CreateClassEnumerator(device_class_id
, enum_moniker
.Receive(),
69 // CreateClassEnumerator returns S_FALSE on some Windows OS
70 // when no camera exist. Therefore the FAILED macro can't be used.
74 ScopedComPtr
<IMoniker
> moniker
;
75 ScopedComPtr
<IBaseFilter
> capture_filter
;
77 while (enum_moniker
->Next(1, moniker
.Receive(), &fetched
) == S_OK
) {
78 ScopedComPtr
<IPropertyBag
> prop_bag
;
79 hr
= moniker
->BindToStorage(0, 0, IID_IPropertyBag
, prop_bag
.ReceiveVoid());
85 // Find the device via DevicePath, Description or FriendlyName, whichever is
87 static const wchar_t* kPropertyNames
[] = {
88 L
"DevicePath", L
"Description", L
"FriendlyName"};
91 for (const auto* property_name
: kPropertyNames
) {
92 if (name
.type() != VT_BSTR
)
93 prop_bag
->Read(property_name
, name
.Receive(), 0);
96 if (name
.type() == VT_BSTR
) {
97 std::string
device_path(base::SysWideToUTF8(V_BSTR(name
.ptr())));
98 if (device_path
.compare(device_id
) == 0) {
99 // We have found the requested device
100 hr
= moniker
->BindToObject(0, 0, IID_IBaseFilter
,
101 capture_filter
.ReceiveVoid());
102 DLOG_IF(ERROR
, FAILED(hr
)) << "Failed to bind camera filter: "
103 << logging::SystemErrorCodeToString(hr
);
110 *filter
= capture_filter
.Detach();
111 if (!*filter
&& SUCCEEDED(hr
))
112 hr
= HRESULT_FROM_WIN32(ERROR_NOT_FOUND
);
117 // Finds an IPin on an IBaseFilter given the direction, Category and/or Major
118 // Type. If either |category| or |major_type| are GUID_NULL, they are ignored.
120 ScopedComPtr
<IPin
> VideoCaptureDeviceWin::GetPin(IBaseFilter
* filter
,
121 PIN_DIRECTION pin_dir
,
123 REFGUID major_type
) {
124 ScopedComPtr
<IPin
> pin
;
125 ScopedComPtr
<IEnumPins
> pin_enum
;
126 HRESULT hr
= filter
->EnumPins(pin_enum
.Receive());
127 if (pin_enum
.get() == NULL
)
130 // Get first unconnected pin.
131 hr
= pin_enum
->Reset(); // set to first pin
132 while ((hr
= pin_enum
->Next(1, pin
.Receive(), NULL
)) == S_OK
) {
133 PIN_DIRECTION this_pin_dir
= static_cast<PIN_DIRECTION
>(-1);
134 hr
= pin
->QueryDirection(&this_pin_dir
);
135 if (pin_dir
== this_pin_dir
) {
136 if ((category
== GUID_NULL
|| PinMatchesCategory(pin
.get(), category
)) &&
137 (major_type
== GUID_NULL
||
138 PinMatchesMajorType(pin
.get(), major_type
))) {
150 VideoCapturePixelFormat
151 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
152 const GUID
& sub_type
) {
154 const GUID
& sub_type
;
155 VideoCapturePixelFormat format
;
156 } pixel_formats
[] = {
157 {kMediaSubTypeI420
, VIDEO_CAPTURE_PIXEL_FORMAT_I420
},
158 {MEDIASUBTYPE_IYUV
, VIDEO_CAPTURE_PIXEL_FORMAT_I420
},
159 {MEDIASUBTYPE_RGB24
, VIDEO_CAPTURE_PIXEL_FORMAT_RGB24
},
160 {MEDIASUBTYPE_YUY2
, VIDEO_CAPTURE_PIXEL_FORMAT_YUY2
},
161 {MEDIASUBTYPE_MJPG
, VIDEO_CAPTURE_PIXEL_FORMAT_MJPEG
},
162 {MEDIASUBTYPE_UYVY
, VIDEO_CAPTURE_PIXEL_FORMAT_UYVY
},
163 {MEDIASUBTYPE_ARGB32
, VIDEO_CAPTURE_PIXEL_FORMAT_ARGB
},
164 {kMediaSubTypeHDYC
, VIDEO_CAPTURE_PIXEL_FORMAT_UYVY
},
166 for (size_t i
= 0; i
< arraysize(pixel_formats
); ++i
) {
167 if (sub_type
== pixel_formats
[i
].sub_type
)
168 return pixel_formats
[i
].format
;
172 StringFromGUID2(sub_type
, guid_str
, arraysize(guid_str
));
173 DVLOG(2) << "Device (also) supports an unknown media type " << guid_str
;
175 return VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN
;
178 void VideoCaptureDeviceWin::ScopedMediaType::Free() {
182 DeleteMediaType(media_type_
);
186 AM_MEDIA_TYPE
** VideoCaptureDeviceWin::ScopedMediaType::Receive() {
187 DCHECK(!media_type_
);
191 // Release the format block for a media type.
192 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
193 void VideoCaptureDeviceWin::ScopedMediaType::FreeMediaType(AM_MEDIA_TYPE
* mt
) {
194 if (mt
->cbFormat
!= 0) {
195 CoTaskMemFree(mt
->pbFormat
);
199 if (mt
->pUnk
!= NULL
) {
201 // pUnk should not be used.
207 // Delete a media type structure that was allocated on the heap.
208 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
209 void VideoCaptureDeviceWin::ScopedMediaType::DeleteMediaType(
217 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name
& device_name
)
218 : device_name_(device_name
), state_(kIdle
) {
222 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
223 DCHECK(CalledOnValidThread());
224 if (media_control_
.get())
225 media_control_
->Stop();
227 if (graph_builder_
.get()) {
228 if (sink_filter_
.get()) {
229 graph_builder_
->RemoveFilter(sink_filter_
.get());
233 if (capture_filter_
.get())
234 graph_builder_
->RemoveFilter(capture_filter_
.get());
237 if (capture_graph_builder_
.get())
238 capture_graph_builder_
.Release();
241 bool VideoCaptureDeviceWin::Init() {
242 DCHECK(CalledOnValidThread());
245 hr
= GetDeviceFilter(device_name_
.id(), CLSID_VideoInputDeviceCategory
,
246 capture_filter_
.Receive());
248 if (!capture_filter_
.get()) {
249 DLOG(ERROR
) << "Failed to create capture filter: "
250 << logging::SystemErrorCodeToString(hr
);
254 output_capture_pin_
= GetPin(capture_filter_
.get(), PINDIR_OUTPUT
,
255 PIN_CATEGORY_CAPTURE
, GUID_NULL
);
256 if (!output_capture_pin_
.get()) {
257 DLOG(ERROR
) << "Failed to get capture output pin";
261 // Create the sink filter used for receiving Captured frames.
262 sink_filter_
= new SinkFilter(this);
263 if (sink_filter_
.get() == NULL
) {
264 DLOG(ERROR
) << "Failed to create send filter";
268 input_sink_pin_
= sink_filter_
->GetPin(0);
270 hr
= graph_builder_
.CreateInstance(CLSID_FilterGraph
, NULL
,
271 CLSCTX_INPROC_SERVER
);
273 DLOG(ERROR
) << "Failed to create graph builder: "
274 << logging::SystemErrorCodeToString(hr
);
278 hr
= capture_graph_builder_
.CreateInstance(CLSID_CaptureGraphBuilder2
, NULL
,
281 DLOG(ERROR
) << "Failed to create the Capture Graph Builder: "
282 << logging::SystemErrorCodeToString(hr
);
286 hr
= capture_graph_builder_
->SetFiltergraph(graph_builder_
.get());
288 DLOG(ERROR
) << "Failed to give graph to capture graph builder: "
289 << logging::SystemErrorCodeToString(hr
);
293 hr
= graph_builder_
.QueryInterface(media_control_
.Receive());
295 DLOG(ERROR
) << "Failed to create media control builder: "
296 << logging::SystemErrorCodeToString(hr
);
300 hr
= graph_builder_
->AddFilter(capture_filter_
.get(), NULL
);
302 DLOG(ERROR
) << "Failed to add the capture device to the graph: "
303 << logging::SystemErrorCodeToString(hr
);
307 hr
= graph_builder_
->AddFilter(sink_filter_
.get(), NULL
);
309 DLOG(ERROR
) << "Failed to add the send filter to the graph: "
310 << logging::SystemErrorCodeToString(hr
);
314 // The following code builds the upstream portions of the graph,
315 // for example if a capture device uses a Windows Driver Model (WDM)
316 // driver, the graph may require certain filters upstream from the
317 // WDM Video Capture filter, such as a TV Tuner filter or an Analog
318 // Video Crossbar filter. We try using the more prevalent
319 // MEDIATYPE_Interleaved first.
320 base::win::ScopedComPtr
<IAMStreamConfig
> stream_config
;
322 hr
= capture_graph_builder_
->FindInterface(
323 &PIN_CATEGORY_CAPTURE
, &MEDIATYPE_Interleaved
, capture_filter_
.get(),
324 IID_IAMStreamConfig
, (void**)stream_config
.Receive());
326 hr
= capture_graph_builder_
->FindInterface(
327 &PIN_CATEGORY_CAPTURE
, &MEDIATYPE_Video
, capture_filter_
.get(),
328 IID_IAMStreamConfig
, (void**)stream_config
.Receive());
329 DLOG_IF(ERROR
, FAILED(hr
)) << "Failed to find CapFilter:IAMStreamConfig: "
330 << logging::SystemErrorCodeToString(hr
);
333 return CreateCapabilityMap();
336 void VideoCaptureDeviceWin::AllocateAndStart(
337 const VideoCaptureParams
& params
,
338 scoped_ptr
<VideoCaptureDevice::Client
> client
) {
339 DCHECK(CalledOnValidThread());
343 client_
= client
.Pass();
345 // Get the camera capability that best match the requested format.
346 const CapabilityWin found_capability
=
347 GetBestMatchedCapability(params
.requested_format
, capabilities_
);
349 // Reduce the frame rate if the requested frame rate is lower
350 // than the capability.
351 float frame_rate
= std::min(found_capability
.supported_format
.frame_rate
,
352 params
.requested_format
.frame_rate
);
354 ScopedComPtr
<IAMStreamConfig
> stream_config
;
355 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
357 SetErrorState("Can't get the Capture format settings");
361 int count
= 0, size
= 0;
362 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
364 SetErrorState("Failed to GetNumberOfCapabilities");
368 scoped_ptr
<BYTE
[]> caps(new BYTE
[size
]);
369 ScopedMediaType media_type
;
371 // Get the windows capability from the capture device.
372 // GetStreamCaps can return S_FALSE which we consider an error. Therefore the
373 // FAILED macro can't be used.
374 hr
= stream_config
->GetStreamCaps(found_capability
.stream_index
,
375 media_type
.Receive(), caps
.get());
377 SetErrorState("Failed to get capture device capabilities");
380 if (media_type
->formattype
== FORMAT_VideoInfo
) {
382 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
384 h
->AvgTimePerFrame
= kSecondsToReferenceTime
/ frame_rate
;
386 // Set the sink filter to request this format.
387 sink_filter_
->SetRequestedMediaFormat(
388 found_capability
.supported_format
.pixel_format
, frame_rate
,
389 found_capability
.info_header
);
390 // Order the capture device to use this format.
391 hr
= stream_config
->SetFormat(media_type
.get());
393 // TODO(grunell): Log the error. http://crbug.com/405016.
394 SetErrorState("Failed to set capture device output format");
398 SetAntiFlickerInCaptureFilter();
400 if (media_type
->subtype
== kMediaSubTypeHDYC
) {
401 // HDYC pixel format, used by the DeckLink capture card, needs an AVI
402 // decompressor filter after source, let |graph_builder_| add it.
403 hr
= graph_builder_
->Connect(output_capture_pin_
.get(),
404 input_sink_pin_
.get());
406 hr
= graph_builder_
->ConnectDirect(output_capture_pin_
.get(),
407 input_sink_pin_
.get(), NULL
);
411 SetErrorState("Failed to connect the Capture graph.");
415 hr
= media_control_
->Pause();
418 "Failed to Pause the Capture device. "
419 "Is it already occupied?");
423 // Get the format back from the sink filter after the filter have been
425 capture_format_
= sink_filter_
->ResultingFormat();
428 hr
= media_control_
->Run();
430 SetErrorState("Failed to start the Capture device.");
437 void VideoCaptureDeviceWin::StopAndDeAllocate() {
438 DCHECK(CalledOnValidThread());
439 if (state_
!= kCapturing
)
442 HRESULT hr
= media_control_
->Stop();
444 SetErrorState("Failed to stop the capture graph.");
448 graph_builder_
->Disconnect(output_capture_pin_
.get());
449 graph_builder_
->Disconnect(input_sink_pin_
.get());
455 // Implements SinkFilterObserver::SinkFilterObserver.
456 void VideoCaptureDeviceWin::FrameReceived(const uint8
* buffer
, int length
) {
457 client_
->OnIncomingCapturedData(buffer
, length
, capture_format_
, 0,
458 base::TimeTicks::Now());
461 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
462 DCHECK(CalledOnValidThread());
463 ScopedComPtr
<IAMStreamConfig
> stream_config
;
464 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
466 DPLOG(ERROR
) << "Failed to get IAMStreamConfig interface from "
467 "capture device: " << logging::SystemErrorCodeToString(hr
);
471 // Get interface used for getting the frame rate.
472 ScopedComPtr
<IAMVideoControl
> video_control
;
473 hr
= capture_filter_
.QueryInterface(video_control
.Receive());
474 DLOG_IF(WARNING
, FAILED(hr
)) << "IAMVideoControl Interface NOT SUPPORTED: "
475 << logging::SystemErrorCodeToString(hr
);
477 int count
= 0, size
= 0;
478 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
480 DLOG(ERROR
) << "Failed to GetNumberOfCapabilities: "
481 << logging::SystemErrorCodeToString(hr
);
485 scoped_ptr
<BYTE
[]> caps(new BYTE
[size
]);
486 for (int stream_index
= 0; stream_index
< count
; ++stream_index
) {
487 ScopedMediaType media_type
;
488 hr
= stream_config
->GetStreamCaps(stream_index
, media_type
.Receive(),
490 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
491 // macros here since they'll trigger incorrectly.
493 DLOG(ERROR
) << "Failed to GetStreamCaps: "
494 << logging::SystemErrorCodeToString(hr
);
498 if (media_type
->majortype
== MEDIATYPE_Video
&&
499 media_type
->formattype
== FORMAT_VideoInfo
) {
500 VideoCaptureFormat format
;
501 format
.pixel_format
=
502 TranslateMediaSubtypeToPixelFormat(media_type
->subtype
);
503 if (format
.pixel_format
== VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN
)
507 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
508 format
.frame_size
.SetSize(h
->bmiHeader
.biWidth
, h
->bmiHeader
.biHeight
);
510 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use
511 // the value from VIDEOINFOHEADER.
512 REFERENCE_TIME time_per_frame
= h
->AvgTimePerFrame
;
513 if (video_control
.get()) {
514 ScopedCoMem
<LONGLONG
> max_fps
;
516 const SIZE size
= {format
.frame_size
.width(),
517 format
.frame_size
.height()};
518 hr
= video_control
->GetFrameRateList(output_capture_pin_
.get(),
519 stream_index
, size
, &list_size
,
521 // Can't assume the first value will return the max fps.
522 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some
523 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates
524 // into success, so explicitly check S_OK. See http://crbug.com/306237.
525 if (hr
== S_OK
&& list_size
> 0 && max_fps
) {
527 *std::min_element(max_fps
.get(), max_fps
.get() + list_size
);
533 ? (kSecondsToReferenceTime
/ static_cast<float>(time_per_frame
))
536 capabilities_
.emplace_back(stream_index
, format
, h
->bmiHeader
);
540 return !capabilities_
.empty();
543 // Set the power line frequency removal in |capture_filter_| if available.
544 void VideoCaptureDeviceWin::SetAntiFlickerInCaptureFilter() {
545 const int power_line_frequency
= GetPowerLineFrequencyForLocation();
546 if (power_line_frequency
!= kPowerLine50Hz
&&
547 power_line_frequency
!= kPowerLine60Hz
) {
550 ScopedComPtr
<IKsPropertySet
> ks_propset
;
551 DWORD type_support
= 0;
553 if (SUCCEEDED(hr
= ks_propset
.QueryFrom(capture_filter_
.get())) &&
554 SUCCEEDED(hr
= ks_propset
->QuerySupported(
555 PROPSETID_VIDCAP_VIDEOPROCAMP
,
556 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
,
558 (type_support
& KSPROPERTY_SUPPORT_SET
)) {
559 KSPROPERTY_VIDEOPROCAMP_S data
= {};
560 data
.Property
.Set
= PROPSETID_VIDCAP_VIDEOPROCAMP
;
561 data
.Property
.Id
= KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
;
562 data
.Property
.Flags
= KSPROPERTY_TYPE_SET
;
563 data
.Value
= (power_line_frequency
== kPowerLine50Hz
) ? 1 : 2;
564 data
.Flags
= KSPROPERTY_VIDEOPROCAMP_FLAGS_MANUAL
;
565 hr
= ks_propset
->Set(PROPSETID_VIDCAP_VIDEOPROCAMP
,
566 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
, &data
,
567 sizeof(data
), &data
, sizeof(data
));
568 DLOG_IF(ERROR
, FAILED(hr
)) << "Anti-flicker setting failed: "
569 << logging::SystemErrorCodeToString(hr
);
570 DVLOG_IF(2, SUCCEEDED(hr
)) << "Anti-flicker set correctly.";
572 DVLOG(2) << "Anti-flicker setting not supported.";
576 void VideoCaptureDeviceWin::SetErrorState(const std::string
& reason
) {
577 DCHECK(CalledOnValidThread());
579 client_
->OnError(reason
);