Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / media / capture / video / win / video_capture_device_win.cc
blob055fc2105f1e51c7af9e847da6da6f97bfc060ca
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"
7 #include <ks.h>
8 #include <ksmedia.h>
10 #include <algorithm>
11 #include <list>
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;
21 namespace media {
23 // Check if a Pin matches a category.
24 bool PinMatchesCategory(IPin* pin, REFGUID category) {
25 DCHECK(pin);
26 bool found = false;
27 ScopedComPtr<IKsPropertySet> ks_property;
28 HRESULT hr = ks_property.QueryFrom(pin);
29 if (SUCCEEDED(hr)) {
30 GUID pin_category;
31 DWORD return_value;
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);
38 return found;
41 // Check if a Pin's MediaType matches a given |major_type|.
42 bool PinMatchesMajorType(IPin* pin, REFGUID major_type) {
43 DCHECK(pin);
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|.
50 // static
51 HRESULT VideoCaptureDeviceWin::GetDeviceFilter(const std::string& device_id,
52 IBaseFilter** filter) {
53 DCHECK(filter);
55 ScopedComPtr<ICreateDevEnum> dev_enum;
56 HRESULT hr =
57 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
58 if (FAILED(hr))
59 return hr;
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.
66 if (hr != S_OK)
67 return hr;
69 ScopedComPtr<IBaseFilter> capture_filter;
70 for (ScopedComPtr<IMoniker> moniker;
71 enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK;
72 moniker.Release()) {
73 ScopedComPtr<IPropertyBag> prop_bag;
74 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
75 if (FAILED(hr))
76 continue;
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"};
83 ScopedVariant name;
84 for (const auto* property_name : kPropertyNames) {
85 prop_bag->Read(property_name, name.Receive(), 0);
86 if (name.type() == VT_BSTR)
87 break;
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);
98 break;
103 *filter = capture_filter.Detach();
104 if (!*filter && SUCCEEDED(hr))
105 hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
107 return hr;
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.
112 // static
113 ScopedComPtr<IPin> VideoCaptureDeviceWin::GetPin(IBaseFilter* filter,
114 PIN_DIRECTION pin_dir,
115 REFGUID category,
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)
121 return pin;
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))) {
132 return pin;
135 pin.Release();
138 DCHECK(!pin.get());
139 return pin;
142 // static
143 VideoPixelFormat
144 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
145 const GUID& sub_type) {
146 static struct {
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;
163 #ifndef NDEBUG
164 WCHAR guid_str[128];
165 StringFromGUID2(sub_type, guid_str, arraysize(guid_str));
166 DVLOG(2) << "Device (also) supports an unknown media type " << guid_str;
167 #endif
168 return PIXEL_FORMAT_UNKNOWN;
171 void VideoCaptureDeviceWin::ScopedMediaType::Free() {
172 if (!media_type_)
173 return;
175 DeleteMediaType(media_type_);
176 media_type_ = NULL;
179 AM_MEDIA_TYPE** VideoCaptureDeviceWin::ScopedMediaType::Receive() {
180 DCHECK(!media_type_);
181 return &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);
189 mt->cbFormat = 0;
190 mt->pbFormat = NULL;
192 if (mt->pUnk != NULL) {
193 NOTREACHED();
194 // pUnk should not be used.
195 mt->pUnk->Release();
196 mt->pUnk = NULL;
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(
203 AM_MEDIA_TYPE* mt) {
204 if (mt != NULL) {
205 FreeMediaType(mt);
206 CoTaskMemFree(mt);
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());
224 sink_filter_ = NULL;
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());
237 HRESULT hr;
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);
244 return false;
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";
251 return false;
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";
258 return false;
261 input_sink_pin_ = sink_filter_->GetPin(0);
263 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
264 CLSCTX_INPROC_SERVER);
265 if (FAILED(hr)) {
266 DLOG(ERROR) << "Failed to create graph builder: "
267 << logging::SystemErrorCodeToString(hr);
268 return false;
271 hr = capture_graph_builder_.CreateInstance(CLSID_CaptureGraphBuilder2, NULL,
272 CLSCTX_INPROC);
273 if (FAILED(hr)) {
274 DLOG(ERROR) << "Failed to create the Capture Graph Builder: "
275 << logging::SystemErrorCodeToString(hr);
276 return false;
279 hr = capture_graph_builder_->SetFiltergraph(graph_builder_.get());
280 if (FAILED(hr)) {
281 DLOG(ERROR) << "Failed to give graph to capture graph builder: "
282 << logging::SystemErrorCodeToString(hr);
283 return false;
286 hr = graph_builder_.QueryInterface(media_control_.Receive());
287 if (FAILED(hr)) {
288 DLOG(ERROR) << "Failed to create media control builder: "
289 << logging::SystemErrorCodeToString(hr);
290 return false;
293 hr = graph_builder_->AddFilter(capture_filter_.get(), NULL);
294 if (FAILED(hr)) {
295 DLOG(ERROR) << "Failed to add the capture device to the graph: "
296 << logging::SystemErrorCodeToString(hr);
297 return false;
300 hr = graph_builder_->AddFilter(sink_filter_.get(), NULL);
301 if (FAILED(hr)) {
302 DLOG(ERROR) << "Failed to add the sink filter to the graph: "
303 << logging::SystemErrorCodeToString(hr);
304 return false;
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());
318 if (FAILED(hr)) {
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());
333 if (state_ != kIdle)
334 return;
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());
350 if (FAILED(hr)) {
351 SetErrorState("Can't get the Capture format settings");
352 return;
355 int count = 0, size = 0;
356 hr = stream_config->GetNumberOfCapabilities(&count, &size);
357 if (FAILED(hr)) {
358 SetErrorState("Failed to GetNumberOfCapabilities");
359 return;
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());
370 if (hr != S_OK) {
371 SetErrorState("Failed to get capture device capabilities");
372 return;
374 if (media_type->formattype == FORMAT_VideoInfo) {
375 VIDEOINFOHEADER* h =
376 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
377 if (frame_rate > 0)
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());
386 if (FAILED(hr)) {
387 // TODO(grunell): Log the error. http://crbug.com/405016.
388 SetErrorState("Failed to set capture device output format");
389 return;
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());
399 } else {
400 hr = graph_builder_->ConnectDirect(output_capture_pin_.get(),
401 input_sink_pin_.get(), NULL);
404 if (FAILED(hr)) {
405 SetErrorState("Failed to connect the Capture graph.");
406 return;
409 hr = media_control_->Pause();
410 if (FAILED(hr)) {
411 SetErrorState(
412 "Failed to pause the Capture device, is it already occupied?");
413 return;
416 // Get the format back from the sink filter after the filter have been
417 // connected.
418 capture_format_ = sink_filter_->ResultingFormat();
420 // Start capturing.
421 hr = media_control_->Run();
422 if (FAILED(hr)) {
423 SetErrorState("Failed to start the Capture device.");
424 return;
427 state_ = kCapturing;
430 void VideoCaptureDeviceWin::StopAndDeAllocate() {
431 DCHECK(thread_checker_.CalledOnValidThread());
432 if (state_ != kCapturing)
433 return;
435 HRESULT hr = media_control_->Stop();
436 if (FAILED(hr)) {
437 SetErrorState("Failed to stop the capture graph.");
438 return;
441 graph_builder_->Disconnect(output_capture_pin_.get());
442 graph_builder_->Disconnect(input_sink_pin_.get());
444 client_.reset();
445 state_ = kIdle;
448 // Implements SinkFilterObserver::SinkFilterObserver.
449 void VideoCaptureDeviceWin::FrameReceived(
450 const uint8* buffer,
451 int length,
452 base::TimeTicks timestamp) {
453 client_->OnIncomingCapturedData(buffer, length, capture_format_, 0,
454 timestamp);
457 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
458 DCHECK(thread_checker_.CalledOnValidThread());
459 ScopedComPtr<IAMStreamConfig> stream_config;
460 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
461 if (FAILED(hr)) {
462 DPLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
463 "capture device: " << logging::SystemErrorCodeToString(hr);
464 return false;
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);
475 if (FAILED(hr)) {
476 DLOG(ERROR) << "Failed to GetNumberOfCapabilities: "
477 << logging::SystemErrorCodeToString(hr);
478 return false;
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(),
485 caps.get());
486 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
487 // macros here since they'll trigger incorrectly.
488 if (hr != S_OK) {
489 DLOG(ERROR) << "Failed to GetStreamCaps: "
490 << logging::SystemErrorCodeToString(hr);
491 return false;
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)
500 continue;
502 VIDEOINFOHEADER* h =
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;
511 LONG list_size = 0;
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,
516 &max_fps);
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) {
522 time_per_frame =
523 *std::min_element(max_fps.get(), max_fps.get() + list_size);
527 format.frame_rate =
528 (time_per_frame > 0)
529 ? (kSecondsToReferenceTime / static_cast<float>(time_per_frame))
530 : 0.0;
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)) {
547 return;
549 ScopedComPtr<IKsPropertySet> ks_propset;
550 DWORD type_support = 0;
551 HRESULT hr;
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,
556 &type_support)) &&
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))
565 : 2;
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.";
573 } else {
574 DVLOG(2) << "Anti-flicker setting not supported.";
578 void VideoCaptureDeviceWin::SetErrorState(const std::string& reason) {
579 DCHECK(thread_checker_.CalledOnValidThread());
580 state_ = kError;
581 client_->OnError(reason);
583 } // namespace media