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"
17 using base::win::ScopedCoMem
;
18 using base::win::ScopedComPtr
;
19 using base::win::ScopedVariant
;
23 // Check if a Pin matches a category.
24 bool PinMatchesCategory(IPin
* pin
, REFGUID category
) {
27 ScopedComPtr
<IKsPropertySet
> ks_property
;
28 HRESULT hr
= ks_property
.QueryFrom(pin
);
32 hr
= ks_property
->Get(AMPROPSETID_Pin
, AMPROPERTY_PIN_CATEGORY
, NULL
, 0,
33 &pin_category
, sizeof(pin_category
), &return_value
);
34 if (SUCCEEDED(hr
) && (return_value
== sizeof(pin_category
))) {
35 found
= (pin_category
== category
);
41 // Check if a Pin's MediaType matches a given |major_type|.
42 bool PinMatchesMajorType(IPin
* pin
, REFGUID major_type
) {
44 AM_MEDIA_TYPE connection_media_type
;
45 const HRESULT hr
= pin
->ConnectionMediaType(&connection_media_type
);
46 return SUCCEEDED(hr
) && connection_media_type
.majortype
== major_type
;
49 // Finds and creates a DirectShow Video Capture filter matching the |device_id|.
51 HRESULT
VideoCaptureDeviceWin::GetDeviceFilter(const std::string
& device_id
,
52 IBaseFilter
** filter
) {
55 ScopedComPtr
<ICreateDevEnum
> dev_enum
;
57 dev_enum
.CreateInstance(CLSID_SystemDeviceEnum
, NULL
, CLSCTX_INPROC
);
61 ScopedComPtr
<IEnumMoniker
> enum_moniker
;
62 hr
= dev_enum
->CreateClassEnumerator(CLSID_VideoInputDeviceCategory
,
63 enum_moniker
.Receive(), 0);
64 // CreateClassEnumerator returns S_FALSE on some Windows OS
65 // when no camera exist. Therefore the FAILED macro can't be used.
69 ScopedComPtr
<IBaseFilter
> capture_filter
;
70 for (ScopedComPtr
<IMoniker
> moniker
;
71 enum_moniker
->Next(1, moniker
.Receive(), NULL
) == S_OK
;
73 ScopedComPtr
<IPropertyBag
> prop_bag
;
74 hr
= moniker
->BindToStorage(0, 0, IID_IPropertyBag
, prop_bag
.ReceiveVoid());
78 // Find |device_id| via DevicePath, Description or FriendlyName, whichever
79 // is available first and is a VT_BSTR (i.e. String) type.
80 static const wchar_t* kPropertyNames
[] = {
81 L
"DevicePath", L
"Description", L
"FriendlyName"};
84 for (const auto* property_name
: kPropertyNames
) {
85 prop_bag
->Read(property_name
, name
.Receive(), 0);
86 if (name
.type() == VT_BSTR
)
90 if (name
.type() == VT_BSTR
) {
91 const std::string
device_path(base::SysWideToUTF8(V_BSTR(name
.ptr())));
92 if (device_path
.compare(device_id
) == 0) {
93 // We have found the requested device
94 hr
= moniker
->BindToObject(0, 0, IID_IBaseFilter
,
95 capture_filter
.ReceiveVoid());
96 DLOG_IF(ERROR
, FAILED(hr
)) << "Failed to bind camera filter: "
97 << logging::SystemErrorCodeToString(hr
);
103 *filter
= capture_filter
.Detach();
104 if (!*filter
&& SUCCEEDED(hr
))
105 hr
= HRESULT_FROM_WIN32(ERROR_NOT_FOUND
);
110 // Finds an IPin on an IBaseFilter given the direction, Category and/or Major
111 // Type. If either |category| or |major_type| are GUID_NULL, they are ignored.
113 ScopedComPtr
<IPin
> VideoCaptureDeviceWin::GetPin(IBaseFilter
* filter
,
114 PIN_DIRECTION pin_dir
,
116 REFGUID major_type
) {
117 ScopedComPtr
<IPin
> pin
;
118 ScopedComPtr
<IEnumPins
> pin_enum
;
119 HRESULT hr
= filter
->EnumPins(pin_enum
.Receive());
120 if (pin_enum
.get() == NULL
)
123 // Get first unconnected pin.
124 hr
= pin_enum
->Reset(); // set to first pin
125 while ((hr
= pin_enum
->Next(1, pin
.Receive(), NULL
)) == S_OK
) {
126 PIN_DIRECTION this_pin_dir
= static_cast<PIN_DIRECTION
>(-1);
127 hr
= pin
->QueryDirection(&this_pin_dir
);
128 if (pin_dir
== this_pin_dir
) {
129 if ((category
== GUID_NULL
|| PinMatchesCategory(pin
.get(), category
)) &&
130 (major_type
== GUID_NULL
||
131 PinMatchesMajorType(pin
.get(), major_type
))) {
144 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
145 const GUID
& sub_type
) {
147 const GUID
& sub_type
;
148 VideoPixelFormat format
;
149 } const kMediaSubtypeToPixelFormatCorrespondence
[] = {
150 {kMediaSubTypeI420
, PIXEL_FORMAT_I420
},
151 {MEDIASUBTYPE_IYUV
, PIXEL_FORMAT_I420
},
152 {MEDIASUBTYPE_RGB24
, PIXEL_FORMAT_RGB24
},
153 {MEDIASUBTYPE_YUY2
, PIXEL_FORMAT_YUY2
},
154 {MEDIASUBTYPE_MJPG
, PIXEL_FORMAT_MJPEG
},
155 {MEDIASUBTYPE_UYVY
, PIXEL_FORMAT_UYVY
},
156 {MEDIASUBTYPE_ARGB32
, PIXEL_FORMAT_ARGB
},
157 {kMediaSubTypeHDYC
, PIXEL_FORMAT_UYVY
},
159 for (const auto& pixel_format
: kMediaSubtypeToPixelFormatCorrespondence
) {
160 if (sub_type
== pixel_format
.sub_type
)
161 return pixel_format
.format
;
165 StringFromGUID2(sub_type
, guid_str
, arraysize(guid_str
));
166 DVLOG(2) << "Device (also) supports an unknown media type " << guid_str
;
168 return PIXEL_FORMAT_UNKNOWN
;
171 void VideoCaptureDeviceWin::ScopedMediaType::Free() {
175 DeleteMediaType(media_type_
);
179 AM_MEDIA_TYPE
** VideoCaptureDeviceWin::ScopedMediaType::Receive() {
180 DCHECK(!media_type_
);
184 // Release the format block for a media type.
185 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
186 void VideoCaptureDeviceWin::ScopedMediaType::FreeMediaType(AM_MEDIA_TYPE
* mt
) {
187 if (mt
->cbFormat
!= 0) {
188 CoTaskMemFree(mt
->pbFormat
);
192 if (mt
->pUnk
!= NULL
) {
194 // pUnk should not be used.
200 // Delete a media type structure that was allocated on the heap.
201 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
202 void VideoCaptureDeviceWin::ScopedMediaType::DeleteMediaType(
210 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name
& device_name
)
211 : device_name_(device_name
), state_(kIdle
) {
212 // TODO(mcasas): Check that CoInitializeEx() has been called with the
213 // appropriate Apartment model, i.e., Single Threaded.
216 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
217 DCHECK(thread_checker_
.CalledOnValidThread());
218 if (media_control_
.get())
219 media_control_
->Stop();
221 if (graph_builder_
.get()) {
222 if (sink_filter_
.get()) {
223 graph_builder_
->RemoveFilter(sink_filter_
.get());
227 if (capture_filter_
.get())
228 graph_builder_
->RemoveFilter(capture_filter_
.get());
231 if (capture_graph_builder_
.get())
232 capture_graph_builder_
.Release();
235 bool VideoCaptureDeviceWin::Init() {
236 DCHECK(thread_checker_
.CalledOnValidThread());
239 hr
= GetDeviceFilter(device_name_
.id(), capture_filter_
.Receive());
241 if (!capture_filter_
.get()) {
242 DLOG(ERROR
) << "Failed to create capture filter: "
243 << logging::SystemErrorCodeToString(hr
);
247 output_capture_pin_
= GetPin(capture_filter_
.get(), PINDIR_OUTPUT
,
248 PIN_CATEGORY_CAPTURE
, GUID_NULL
);
249 if (!output_capture_pin_
.get()) {
250 DLOG(ERROR
) << "Failed to get capture output pin";
254 // Create the sink filter used for receiving Captured frames.
255 sink_filter_
= new SinkFilter(this);
256 if (sink_filter_
.get() == NULL
) {
257 DLOG(ERROR
) << "Failed to create sink filter";
261 input_sink_pin_
= sink_filter_
->GetPin(0);
263 hr
= graph_builder_
.CreateInstance(CLSID_FilterGraph
, NULL
,
264 CLSCTX_INPROC_SERVER
);
266 DLOG(ERROR
) << "Failed to create graph builder: "
267 << logging::SystemErrorCodeToString(hr
);
271 hr
= capture_graph_builder_
.CreateInstance(CLSID_CaptureGraphBuilder2
, NULL
,
274 DLOG(ERROR
) << "Failed to create the Capture Graph Builder: "
275 << logging::SystemErrorCodeToString(hr
);
279 hr
= capture_graph_builder_
->SetFiltergraph(graph_builder_
.get());
281 DLOG(ERROR
) << "Failed to give graph to capture graph builder: "
282 << logging::SystemErrorCodeToString(hr
);
286 hr
= graph_builder_
.QueryInterface(media_control_
.Receive());
288 DLOG(ERROR
) << "Failed to create media control builder: "
289 << logging::SystemErrorCodeToString(hr
);
293 hr
= graph_builder_
->AddFilter(capture_filter_
.get(), NULL
);
295 DLOG(ERROR
) << "Failed to add the capture device to the graph: "
296 << logging::SystemErrorCodeToString(hr
);
300 hr
= graph_builder_
->AddFilter(sink_filter_
.get(), NULL
);
302 DLOG(ERROR
) << "Failed to add the sink filter to the graph: "
303 << logging::SystemErrorCodeToString(hr
);
307 // The following code builds the upstream portions of the graph,
308 // for example if a capture device uses a Windows Driver Model (WDM)
309 // driver, the graph may require certain filters upstream from the
310 // WDM Video Capture filter, such as a TV Tuner filter or an Analog
311 // Video Crossbar filter. We try using the more prevalent
312 // MEDIATYPE_Interleaved first.
313 base::win::ScopedComPtr
<IAMStreamConfig
> stream_config
;
315 hr
= capture_graph_builder_
->FindInterface(
316 &PIN_CATEGORY_CAPTURE
, &MEDIATYPE_Interleaved
, capture_filter_
.get(),
317 IID_IAMStreamConfig
, (void**)stream_config
.Receive());
319 hr
= capture_graph_builder_
->FindInterface(
320 &PIN_CATEGORY_CAPTURE
, &MEDIATYPE_Video
, capture_filter_
.get(),
321 IID_IAMStreamConfig
, (void**)stream_config
.Receive());
322 DLOG_IF(ERROR
, FAILED(hr
)) << "Failed to find CapFilter:IAMStreamConfig: "
323 << logging::SystemErrorCodeToString(hr
);
326 return CreateCapabilityMap();
329 void VideoCaptureDeviceWin::AllocateAndStart(
330 const VideoCaptureParams
& params
,
331 scoped_ptr
<VideoCaptureDevice::Client
> client
) {
332 DCHECK(thread_checker_
.CalledOnValidThread());
336 client_
= client
.Pass();
338 // Get the camera capability that best match the requested format.
339 const CapabilityWin found_capability
=
340 GetBestMatchedCapability(params
.requested_format
, capabilities_
);
342 // Reduce the frame rate if the requested frame rate is lower
343 // than the capability.
344 const float frame_rate
=
345 std::min(params
.requested_format
.frame_rate
,
346 found_capability
.supported_format
.frame_rate
);
348 ScopedComPtr
<IAMStreamConfig
> stream_config
;
349 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
351 SetErrorState("Can't get the Capture format settings");
355 int count
= 0, size
= 0;
356 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
358 SetErrorState("Failed to GetNumberOfCapabilities");
362 scoped_ptr
<BYTE
[]> caps(new BYTE
[size
]);
363 ScopedMediaType media_type
;
365 // Get the windows capability from the capture device.
366 // GetStreamCaps can return S_FALSE which we consider an error. Therefore the
367 // FAILED macro can't be used.
368 hr
= stream_config
->GetStreamCaps(found_capability
.stream_index
,
369 media_type
.Receive(), caps
.get());
371 SetErrorState("Failed to get capture device capabilities");
374 if (media_type
->formattype
== FORMAT_VideoInfo
) {
376 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
378 h
->AvgTimePerFrame
= kSecondsToReferenceTime
/ frame_rate
;
380 // Set the sink filter to request this format.
381 sink_filter_
->SetRequestedMediaFormat(
382 found_capability
.supported_format
.pixel_format
, frame_rate
,
383 found_capability
.info_header
);
384 // Order the capture device to use this format.
385 hr
= stream_config
->SetFormat(media_type
.get());
387 // TODO(grunell): Log the error. http://crbug.com/405016.
388 SetErrorState("Failed to set capture device output format");
392 SetAntiFlickerInCaptureFilter(params
);
394 if (media_type
->subtype
== kMediaSubTypeHDYC
) {
395 // HDYC pixel format, used by the DeckLink capture card, needs an AVI
396 // decompressor filter after source, let |graph_builder_| add it.
397 hr
= graph_builder_
->Connect(output_capture_pin_
.get(),
398 input_sink_pin_
.get());
400 hr
= graph_builder_
->ConnectDirect(output_capture_pin_
.get(),
401 input_sink_pin_
.get(), NULL
);
405 SetErrorState("Failed to connect the Capture graph.");
409 hr
= media_control_
->Pause();
412 "Failed to pause the Capture device, is it already occupied?");
416 // Get the format back from the sink filter after the filter have been
418 capture_format_
= sink_filter_
->ResultingFormat();
421 hr
= media_control_
->Run();
423 SetErrorState("Failed to start the Capture device.");
430 void VideoCaptureDeviceWin::StopAndDeAllocate() {
431 DCHECK(thread_checker_
.CalledOnValidThread());
432 if (state_
!= kCapturing
)
435 HRESULT hr
= media_control_
->Stop();
437 SetErrorState("Failed to stop the capture graph.");
441 graph_builder_
->Disconnect(output_capture_pin_
.get());
442 graph_builder_
->Disconnect(input_sink_pin_
.get());
448 // Implements SinkFilterObserver::SinkFilterObserver.
449 void VideoCaptureDeviceWin::FrameReceived(
452 base::TimeTicks timestamp
) {
453 client_
->OnIncomingCapturedData(buffer
, length
, capture_format_
, 0,
457 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
458 DCHECK(thread_checker_
.CalledOnValidThread());
459 ScopedComPtr
<IAMStreamConfig
> stream_config
;
460 HRESULT hr
= output_capture_pin_
.QueryInterface(stream_config
.Receive());
462 DPLOG(ERROR
) << "Failed to get IAMStreamConfig interface from "
463 "capture device: " << logging::SystemErrorCodeToString(hr
);
467 // Get interface used for getting the frame rate.
468 ScopedComPtr
<IAMVideoControl
> video_control
;
469 hr
= capture_filter_
.QueryInterface(video_control
.Receive());
470 DLOG_IF(WARNING
, FAILED(hr
)) << "IAMVideoControl Interface NOT SUPPORTED: "
471 << logging::SystemErrorCodeToString(hr
);
473 int count
= 0, size
= 0;
474 hr
= stream_config
->GetNumberOfCapabilities(&count
, &size
);
476 DLOG(ERROR
) << "Failed to GetNumberOfCapabilities: "
477 << logging::SystemErrorCodeToString(hr
);
481 scoped_ptr
<BYTE
[]> caps(new BYTE
[size
]);
482 for (int stream_index
= 0; stream_index
< count
; ++stream_index
) {
483 ScopedMediaType media_type
;
484 hr
= stream_config
->GetStreamCaps(stream_index
, media_type
.Receive(),
486 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
487 // macros here since they'll trigger incorrectly.
489 DLOG(ERROR
) << "Failed to GetStreamCaps: "
490 << logging::SystemErrorCodeToString(hr
);
494 if (media_type
->majortype
== MEDIATYPE_Video
&&
495 media_type
->formattype
== FORMAT_VideoInfo
) {
496 VideoCaptureFormat format
;
497 format
.pixel_format
=
498 TranslateMediaSubtypeToPixelFormat(media_type
->subtype
);
499 if (format
.pixel_format
== PIXEL_FORMAT_UNKNOWN
)
503 reinterpret_cast<VIDEOINFOHEADER
*>(media_type
->pbFormat
);
504 format
.frame_size
.SetSize(h
->bmiHeader
.biWidth
, h
->bmiHeader
.biHeight
);
506 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use
507 // the value from VIDEOINFOHEADER.
508 REFERENCE_TIME time_per_frame
= h
->AvgTimePerFrame
;
509 if (video_control
.get()) {
510 ScopedCoMem
<LONGLONG
> max_fps
;
512 const SIZE size
= {format
.frame_size
.width(),
513 format
.frame_size
.height()};
514 hr
= video_control
->GetFrameRateList(output_capture_pin_
.get(),
515 stream_index
, size
, &list_size
,
517 // Can't assume the first value will return the max fps.
518 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some
519 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates
520 // into success, so explicitly check S_OK. See http://crbug.com/306237.
521 if (hr
== S_OK
&& list_size
> 0 && max_fps
) {
523 *std::min_element(max_fps
.get(), max_fps
.get() + list_size
);
529 ? (kSecondsToReferenceTime
/ static_cast<float>(time_per_frame
))
532 capabilities_
.emplace_back(stream_index
, format
, h
->bmiHeader
);
536 return !capabilities_
.empty();
539 // Set the power line frequency removal in |capture_filter_| if available.
540 void VideoCaptureDeviceWin::SetAntiFlickerInCaptureFilter(
541 const VideoCaptureParams
& params
) {
542 const int power_line_frequency
= GetPowerLineFrequency(params
);
543 if (power_line_frequency
!=
544 static_cast<int>(media::PowerLineFrequency::FREQUENCY_50HZ
) &&
545 power_line_frequency
!=
546 static_cast<int>(media::PowerLineFrequency::FREQUENCY_60HZ
)) {
549 ScopedComPtr
<IKsPropertySet
> ks_propset
;
550 DWORD type_support
= 0;
552 if (SUCCEEDED(hr
= ks_propset
.QueryFrom(capture_filter_
.get())) &&
553 SUCCEEDED(hr
= ks_propset
->QuerySupported(
554 PROPSETID_VIDCAP_VIDEOPROCAMP
,
555 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
,
557 (type_support
& KSPROPERTY_SUPPORT_SET
)) {
558 KSPROPERTY_VIDEOPROCAMP_S data
= {};
559 data
.Property
.Set
= PROPSETID_VIDCAP_VIDEOPROCAMP
;
560 data
.Property
.Id
= KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
;
561 data
.Property
.Flags
= KSPROPERTY_TYPE_SET
;
562 data
.Value
= (power_line_frequency
==
563 static_cast<int>(media::PowerLineFrequency::FREQUENCY_50HZ
))
566 data
.Flags
= KSPROPERTY_VIDEOPROCAMP_FLAGS_MANUAL
;
567 hr
= ks_propset
->Set(PROPSETID_VIDCAP_VIDEOPROCAMP
,
568 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY
, &data
,
569 sizeof(data
), &data
, sizeof(data
));
570 DLOG_IF(ERROR
, FAILED(hr
)) << "Anti-flicker setting failed: "
571 << logging::SystemErrorCodeToString(hr
);
572 DVLOG_IF(2, SUCCEEDED(hr
)) << "Anti-flicker set correctly.";
574 DVLOG(2) << "Anti-flicker setting not supported.";
578 void VideoCaptureDeviceWin::SetErrorState(const std::string
& reason
) {
579 DCHECK(thread_checker_
.CalledOnValidThread());
581 client_
->OnError(reason
);