Updating XTBs based on .GRDs from branch master
[chromium-blink-merge.git] / media / capture / video / win / video_capture_device_win.cc
blob0ca81f19a8dfcaaff58c69c3a2616cef48263dbe
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"
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;
22 namespace media {
24 // Check if a Pin matches a category.
25 bool PinMatchesCategory(IPin* pin, REFGUID category) {
26 DCHECK(pin);
27 bool found = false;
28 ScopedComPtr<IKsPropertySet> ks_property;
29 HRESULT hr = ks_property.QueryFrom(pin);
30 if (SUCCEEDED(hr)) {
31 GUID pin_category;
32 DWORD return_value;
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);
39 return found;
42 // Check if a Pin's MediaType matches a given |major_type|.
43 bool PinMatchesMajorType(IPin* pin, REFGUID major_type) {
44 DCHECK(pin);
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.
54 // static
55 HRESULT VideoCaptureDeviceWin::GetDeviceFilter(const std::string& device_id,
56 const CLSID device_class_id,
57 IBaseFilter** filter) {
58 DCHECK(filter);
60 ScopedComPtr<ICreateDevEnum> dev_enum;
61 HRESULT hr =
62 dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
63 if (FAILED(hr))
64 return hr;
66 ScopedComPtr<IEnumMoniker> enum_moniker;
67 hr = dev_enum->CreateClassEnumerator(device_class_id, enum_moniker.Receive(),
68 0);
69 // CreateClassEnumerator returns S_FALSE on some Windows OS
70 // when no camera exist. Therefore the FAILED macro can't be used.
71 if (hr != S_OK)
72 return NULL;
74 ScopedComPtr<IMoniker> moniker;
75 ScopedComPtr<IBaseFilter> capture_filter;
76 DWORD fetched = 0;
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());
80 if (FAILED(hr)) {
81 moniker.Release();
82 continue;
85 // Find the device via DevicePath, Description or FriendlyName, whichever is
86 // available first.
87 static const wchar_t* kPropertyNames[] = {
88 L"DevicePath", L"Description", L"FriendlyName"};
90 ScopedVariant name;
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);
104 break;
107 moniker.Release();
110 *filter = capture_filter.Detach();
111 if (!*filter && SUCCEEDED(hr))
112 hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
114 return hr;
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.
119 // static
120 ScopedComPtr<IPin> VideoCaptureDeviceWin::GetPin(IBaseFilter* filter,
121 PIN_DIRECTION pin_dir,
122 REFGUID category,
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)
128 return pin;
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))) {
139 return pin;
142 pin.Release();
145 DCHECK(!pin.get());
146 return pin;
149 // static
150 VideoCapturePixelFormat
151 VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
152 const GUID& sub_type) {
153 static struct {
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;
170 #ifndef NDEBUG
171 WCHAR guid_str[128];
172 StringFromGUID2(sub_type, guid_str, arraysize(guid_str));
173 DVLOG(2) << "Device (also) supports an unknown media type " << guid_str;
174 #endif
175 return VIDEO_CAPTURE_PIXEL_FORMAT_UNKNOWN;
178 void VideoCaptureDeviceWin::ScopedMediaType::Free() {
179 if (!media_type_)
180 return;
182 DeleteMediaType(media_type_);
183 media_type_ = NULL;
186 AM_MEDIA_TYPE** VideoCaptureDeviceWin::ScopedMediaType::Receive() {
187 DCHECK(!media_type_);
188 return &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);
196 mt->cbFormat = 0;
197 mt->pbFormat = NULL;
199 if (mt->pUnk != NULL) {
200 NOTREACHED();
201 // pUnk should not be used.
202 mt->pUnk->Release();
203 mt->pUnk = NULL;
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(
210 AM_MEDIA_TYPE* mt) {
211 if (mt != NULL) {
212 FreeMediaType(mt);
213 CoTaskMemFree(mt);
217 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name)
218 : device_name_(device_name), state_(kIdle) {
219 DetachFromThread();
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());
230 sink_filter_ = NULL;
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());
243 HRESULT hr;
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);
251 return false;
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";
258 return false;
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";
265 return false;
268 input_sink_pin_ = sink_filter_->GetPin(0);
270 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
271 CLSCTX_INPROC_SERVER);
272 if (FAILED(hr)) {
273 DLOG(ERROR) << "Failed to create graph builder: "
274 << logging::SystemErrorCodeToString(hr);
275 return false;
278 hr = capture_graph_builder_.CreateInstance(CLSID_CaptureGraphBuilder2, NULL,
279 CLSCTX_INPROC);
280 if (FAILED(hr)) {
281 DLOG(ERROR) << "Failed to create the Capture Graph Builder: "
282 << logging::SystemErrorCodeToString(hr);
283 return false;
286 hr = capture_graph_builder_->SetFiltergraph(graph_builder_.get());
287 if (FAILED(hr)) {
288 DLOG(ERROR) << "Failed to give graph to capture graph builder: "
289 << logging::SystemErrorCodeToString(hr);
290 return false;
293 hr = graph_builder_.QueryInterface(media_control_.Receive());
294 if (FAILED(hr)) {
295 DLOG(ERROR) << "Failed to create media control builder: "
296 << logging::SystemErrorCodeToString(hr);
297 return false;
300 hr = graph_builder_->AddFilter(capture_filter_.get(), NULL);
301 if (FAILED(hr)) {
302 DLOG(ERROR) << "Failed to add the capture device to the graph: "
303 << logging::SystemErrorCodeToString(hr);
304 return false;
307 hr = graph_builder_->AddFilter(sink_filter_.get(), NULL);
308 if (FAILED(hr)) {
309 DLOG(ERROR) << "Failed to add the send filter to the graph: "
310 << logging::SystemErrorCodeToString(hr);
311 return false;
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());
325 if (FAILED(hr)) {
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());
340 if (state_ != kIdle)
341 return;
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());
356 if (FAILED(hr)) {
357 SetErrorState("Can't get the Capture format settings");
358 return;
361 int count = 0, size = 0;
362 hr = stream_config->GetNumberOfCapabilities(&count, &size);
363 if (FAILED(hr)) {
364 SetErrorState("Failed to GetNumberOfCapabilities");
365 return;
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());
376 if (hr != S_OK) {
377 SetErrorState("Failed to get capture device capabilities");
378 return;
380 if (media_type->formattype == FORMAT_VideoInfo) {
381 VIDEOINFOHEADER* h =
382 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
383 if (frame_rate > 0)
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());
392 if (FAILED(hr)) {
393 // TODO(grunell): Log the error. http://crbug.com/405016.
394 SetErrorState("Failed to set capture device output format");
395 return;
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());
405 } else {
406 hr = graph_builder_->ConnectDirect(output_capture_pin_.get(),
407 input_sink_pin_.get(), NULL);
410 if (FAILED(hr)) {
411 SetErrorState("Failed to connect the Capture graph.");
412 return;
415 hr = media_control_->Pause();
416 if (FAILED(hr)) {
417 SetErrorState(
418 "Failed to Pause the Capture device. "
419 "Is it already occupied?");
420 return;
423 // Get the format back from the sink filter after the filter have been
424 // connected.
425 capture_format_ = sink_filter_->ResultingFormat();
427 // Start capturing.
428 hr = media_control_->Run();
429 if (FAILED(hr)) {
430 SetErrorState("Failed to start the Capture device.");
431 return;
434 state_ = kCapturing;
437 void VideoCaptureDeviceWin::StopAndDeAllocate() {
438 DCHECK(CalledOnValidThread());
439 if (state_ != kCapturing)
440 return;
442 HRESULT hr = media_control_->Stop();
443 if (FAILED(hr)) {
444 SetErrorState("Failed to stop the capture graph.");
445 return;
448 graph_builder_->Disconnect(output_capture_pin_.get());
449 graph_builder_->Disconnect(input_sink_pin_.get());
451 client_.reset();
452 state_ = kIdle;
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());
465 if (FAILED(hr)) {
466 DPLOG(ERROR) << "Failed to get IAMStreamConfig interface from "
467 "capture device: " << logging::SystemErrorCodeToString(hr);
468 return false;
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);
479 if (FAILED(hr)) {
480 DLOG(ERROR) << "Failed to GetNumberOfCapabilities: "
481 << logging::SystemErrorCodeToString(hr);
482 return false;
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(),
489 caps.get());
490 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
491 // macros here since they'll trigger incorrectly.
492 if (hr != S_OK) {
493 DLOG(ERROR) << "Failed to GetStreamCaps: "
494 << logging::SystemErrorCodeToString(hr);
495 return false;
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)
504 continue;
506 VIDEOINFOHEADER* h =
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;
515 LONG list_size = 0;
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,
520 &max_fps);
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) {
526 time_per_frame =
527 *std::min_element(max_fps.get(), max_fps.get() + list_size);
531 format.frame_rate =
532 (time_per_frame > 0)
533 ? (kSecondsToReferenceTime / static_cast<float>(time_per_frame))
534 : 0.0;
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) {
548 return;
550 ScopedComPtr<IKsPropertySet> ks_propset;
551 DWORD type_support = 0;
552 HRESULT hr;
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,
557 &type_support)) &&
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.";
571 } else {
572 DVLOG(2) << "Anti-flicker setting not supported.";
576 void VideoCaptureDeviceWin::SetErrorState(const std::string& reason) {
577 DCHECK(CalledOnValidThread());
578 state_ = kError;
579 client_->OnError(reason);
581 } // namespace media