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.
4
5 #include "media/video/capture/win/video_capture_device_win.h"
6
7 #include <ks.h>
8 #include <ksmedia.h>
9
10 #include <algorithm>
11 #include <list>
12
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/video/capture/win/video_capture_device_mf_win.h"
17
18 using base::win::ScopedCoMem;
19 using base::win::ScopedComPtr;
20 using base::win::ScopedVariant;
21
22 namespace media {
23
24 // Finds and creates a DirectShow Video Capture filter matching the device_name.
25 // static
GetDeviceFilter(const VideoCaptureDevice::Name & device_name,IBaseFilter ** filter)26 HRESULT VideoCaptureDeviceWin::GetDeviceFilter(
27 const VideoCaptureDevice::Name& device_name,
28 IBaseFilter** filter) {
29 DCHECK(filter);
30
31 ScopedComPtr<ICreateDevEnum> dev_enum;
32 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL,
33 CLSCTX_INPROC);
34 if (FAILED(hr))
35 return hr;
36
37 ScopedComPtr<IEnumMoniker> enum_moniker;
38 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
39 enum_moniker.Receive(), 0);
40 // CreateClassEnumerator returns S_FALSE on some Windows OS
41 // when no camera exist. Therefore the FAILED macro can't be used.
42 if (hr != S_OK)
43 return NULL;
44
45 ScopedComPtr<IMoniker> moniker;
46 ScopedComPtr<IBaseFilter> capture_filter;
47 DWORD fetched = 0;
48 while (enum_moniker->Next(1, moniker.Receive(), &fetched) == S_OK) {
49 ScopedComPtr<IPropertyBag> prop_bag;
50 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid());
51 if (FAILED(hr)) {
52 moniker.Release();
53 continue;
54 }
55
56 // Find the description or friendly name.
57 static const wchar_t* kPropertyNames[] = {
58 L"DevicePath", L"Description", L"FriendlyName"
59 };
60 ScopedVariant name;
61 for (size_t i = 0;
62 i < arraysize(kPropertyNames) && name.type() != VT_BSTR; ++i) {
63 prop_bag->Read(kPropertyNames[i], name.Receive(), 0);
64 }
65 if (name.type() == VT_BSTR) {
66 std::string device_path(base::SysWideToUTF8(V_BSTR(&name)));
67 if (device_path.compare(device_name.id()) == 0) {
68 // We have found the requested device
69 hr = moniker->BindToObject(0, 0, IID_IBaseFilter,
70 capture_filter.ReceiveVoid());
71 DVPLOG_IF(2, FAILED(hr)) << "Failed to bind camera filter.";
72 break;
73 }
74 }
75 moniker.Release();
76 }
77
78 *filter = capture_filter.Detach();
79 if (!*filter && SUCCEEDED(hr))
80 hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
81
82 return hr;
83 }
84
85 // Check if a Pin matches a category.
86 // static
PinMatchesCategory(IPin * pin,REFGUID category)87 bool VideoCaptureDeviceWin::PinMatchesCategory(IPin* pin, REFGUID category) {
88 DCHECK(pin);
89 bool found = false;
90 ScopedComPtr<IKsPropertySet> ks_property;
91 HRESULT hr = ks_property.QueryFrom(pin);
92 if (SUCCEEDED(hr)) {
93 GUID pin_category;
94 DWORD return_value;
95 hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0,
96 &pin_category, sizeof(pin_category), &return_value);
97 if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) {
98 found = (pin_category == category);
99 }
100 }
101 return found;
102 }
103
104 // Finds a IPin on a IBaseFilter given the direction an category.
105 // static
GetPin(IBaseFilter * filter,PIN_DIRECTION pin_dir,REFGUID category)106 ScopedComPtr<IPin> VideoCaptureDeviceWin::GetPin(IBaseFilter* filter,
107 PIN_DIRECTION pin_dir,
108 REFGUID category) {
109 ScopedComPtr<IPin> pin;
110 ScopedComPtr<IEnumPins> pin_emum;
111 HRESULT hr = filter->EnumPins(pin_emum.Receive());
112 if (pin_emum == NULL)
113 return pin;
114
115 // Get first unconnected pin.
116 hr = pin_emum->Reset(); // set to first pin
117 while ((hr = pin_emum->Next(1, pin.Receive(), NULL)) == S_OK) {
118 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1);
119 hr = pin->QueryDirection(&this_pin_dir);
120 if (pin_dir == this_pin_dir) {
121 if (category == GUID_NULL || PinMatchesCategory(pin, category))
122 return pin;
123 }
124 pin.Release();
125 }
126
127 DCHECK(!pin);
128 return pin;
129 }
130
131 // static
TranslateMediaSubtypeToPixelFormat(const GUID & sub_type)132 VideoPixelFormat VideoCaptureDeviceWin::TranslateMediaSubtypeToPixelFormat(
133 const GUID& sub_type) {
134 static struct {
135 const GUID& sub_type;
136 VideoPixelFormat format;
137 } pixel_formats[] = {
138 { kMediaSubTypeI420, PIXEL_FORMAT_I420 },
139 { MEDIASUBTYPE_IYUV, PIXEL_FORMAT_I420 },
140 { MEDIASUBTYPE_RGB24, PIXEL_FORMAT_RGB24 },
141 { MEDIASUBTYPE_YUY2, PIXEL_FORMAT_YUY2 },
142 { MEDIASUBTYPE_MJPG, PIXEL_FORMAT_MJPEG },
143 { MEDIASUBTYPE_UYVY, PIXEL_FORMAT_UYVY },
144 { MEDIASUBTYPE_ARGB32, PIXEL_FORMAT_ARGB },
145 };
146 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(pixel_formats); ++i) {
147 if (sub_type == pixel_formats[i].sub_type)
148 return pixel_formats[i].format;
149 }
150 #ifndef NDEBUG
151 WCHAR guid_str[128];
152 StringFromGUID2(sub_type, guid_str, arraysize(guid_str));
153 DVLOG(2) << "Device (also) supports an unknown media type " << guid_str;
154 #endif
155 return PIXEL_FORMAT_UNKNOWN;
156 }
157
Free()158 void VideoCaptureDeviceWin::ScopedMediaType::Free() {
159 if (!media_type_)
160 return;
161
162 DeleteMediaType(media_type_);
163 media_type_= NULL;
164 }
165
Receive()166 AM_MEDIA_TYPE** VideoCaptureDeviceWin::ScopedMediaType::Receive() {
167 DCHECK(!media_type_);
168 return &media_type_;
169 }
170
171 // Release the format block for a media type.
172 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
FreeMediaType(AM_MEDIA_TYPE * mt)173 void VideoCaptureDeviceWin::ScopedMediaType::FreeMediaType(AM_MEDIA_TYPE* mt) {
174 if (mt->cbFormat != 0) {
175 CoTaskMemFree(mt->pbFormat);
176 mt->cbFormat = 0;
177 mt->pbFormat = NULL;
178 }
179 if (mt->pUnk != NULL) {
180 NOTREACHED();
181 // pUnk should not be used.
182 mt->pUnk->Release();
183 mt->pUnk = NULL;
184 }
185 }
186
187 // Delete a media type structure that was allocated on the heap.
188 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx
DeleteMediaType(AM_MEDIA_TYPE * mt)189 void VideoCaptureDeviceWin::ScopedMediaType::DeleteMediaType(
190 AM_MEDIA_TYPE* mt) {
191 if (mt != NULL) {
192 FreeMediaType(mt);
193 CoTaskMemFree(mt);
194 }
195 }
196
VideoCaptureDeviceWin(const Name & device_name)197 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name)
198 : device_name_(device_name),
199 state_(kIdle) {
200 DetachFromThread();
201 }
202
~VideoCaptureDeviceWin()203 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() {
204 DCHECK(CalledOnValidThread());
205 if (media_control_)
206 media_control_->Stop();
207
208 if (graph_builder_) {
209 if (sink_filter_) {
210 graph_builder_->RemoveFilter(sink_filter_);
211 sink_filter_ = NULL;
212 }
213
214 if (capture_filter_)
215 graph_builder_->RemoveFilter(capture_filter_);
216
217 if (mjpg_filter_)
218 graph_builder_->RemoveFilter(mjpg_filter_);
219 }
220 }
221
Init()222 bool VideoCaptureDeviceWin::Init() {
223 DCHECK(CalledOnValidThread());
224 HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive());
225 if (!capture_filter_) {
226 DVLOG(2) << "Failed to create capture filter.";
227 return false;
228 }
229
230 output_capture_pin_ =
231 GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE);
232 if (!output_capture_pin_) {
233 DVLOG(2) << "Failed to get capture output pin";
234 return false;
235 }
236
237 // Create the sink filter used for receiving Captured frames.
238 sink_filter_ = new SinkFilter(this);
239 if (sink_filter_ == NULL) {
240 DVLOG(2) << "Failed to create send filter";
241 return false;
242 }
243
244 input_sink_pin_ = sink_filter_->GetPin(0);
245
246 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL,
247 CLSCTX_INPROC_SERVER);
248 if (FAILED(hr)) {
249 DVLOG(2) << "Failed to create graph builder.";
250 return false;
251 }
252
253 hr = graph_builder_.QueryInterface(media_control_.Receive());
254 if (FAILED(hr)) {
255 DVLOG(2) << "Failed to create media control builder.";
256 return false;
257 }
258
259 hr = graph_builder_->AddFilter(capture_filter_, NULL);
260 if (FAILED(hr)) {
261 DVLOG(2) << "Failed to add the capture device to the graph.";
262 return false;
263 }
264
265 hr = graph_builder_->AddFilter(sink_filter_, NULL);
266 if (FAILED(hr)) {
267 DVLOG(2)<< "Failed to add the send filter to the graph.";
268 return false;
269 }
270
271 return CreateCapabilityMap();
272 }
273
AllocateAndStart(const VideoCaptureParams & params,scoped_ptr<VideoCaptureDevice::Client> client)274 void VideoCaptureDeviceWin::AllocateAndStart(
275 const VideoCaptureParams& params,
276 scoped_ptr<VideoCaptureDevice::Client> client) {
277 DCHECK(CalledOnValidThread());
278 if (state_ != kIdle)
279 return;
280
281 client_ = client.Pass();
282
283 // Get the camera capability that best match the requested resolution.
284 const VideoCaptureCapabilityWin& found_capability =
285 capabilities_.GetBestMatchedFormat(
286 params.requested_format.frame_size.width(),
287 params.requested_format.frame_size.height(),
288 params.requested_format.frame_rate);
289 VideoCaptureFormat format = found_capability.supported_format;
290
291 // Reduce the frame rate if the requested frame rate is lower
292 // than the capability.
293 if (format.frame_rate > params.requested_format.frame_rate)
294 format.frame_rate = params.requested_format.frame_rate;
295
296 ScopedComPtr<IAMStreamConfig> stream_config;
297 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
298 if (FAILED(hr)) {
299 SetErrorState("Can't get the Capture format settings");
300 return;
301 }
302
303 int count = 0, size = 0;
304 hr = stream_config->GetNumberOfCapabilities(&count, &size);
305 if (FAILED(hr)) {
306 DVLOG(2) << "Failed to GetNumberOfCapabilities";
307 return;
308 }
309
310 scoped_ptr<BYTE[]> caps(new BYTE[size]);
311 ScopedMediaType media_type;
312
313 // Get the windows capability from the capture device.
314 hr = stream_config->GetStreamCaps(
315 found_capability.stream_index, media_type.Receive(), caps.get());
316 if (SUCCEEDED(hr)) {
317 if (media_type->formattype == FORMAT_VideoInfo) {
318 VIDEOINFOHEADER* h =
319 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
320 if (format.frame_rate > 0)
321 h->AvgTimePerFrame = kSecondsToReferenceTime / format.frame_rate;
322 }
323 // Set the sink filter to request this format.
324 sink_filter_->SetRequestedMediaFormat(format);
325 // Order the capture device to use this format.
326 hr = stream_config->SetFormat(media_type.get());
327 }
328
329 if (FAILED(hr))
330 SetErrorState("Failed to set capture device output format");
331
332 if (format.pixel_format == PIXEL_FORMAT_MJPEG && !mjpg_filter_.get()) {
333 // Create MJPG filter if we need it.
334 hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC);
335
336 if (SUCCEEDED(hr)) {
337 input_mjpg_pin_ = GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL);
338 output_mjpg_pin_ = GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL);
339 hr = graph_builder_->AddFilter(mjpg_filter_, NULL);
340 }
341
342 if (FAILED(hr)) {
343 mjpg_filter_.Release();
344 input_mjpg_pin_.Release();
345 output_mjpg_pin_.Release();
346 }
347 }
348
349 SetAntiFlickerInCaptureFilter();
350
351 if (format.pixel_format == PIXEL_FORMAT_MJPEG && mjpg_filter_.get()) {
352 // Connect the camera to the MJPEG decoder.
353 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_,
354 NULL);
355 // Connect the MJPEG filter to the Capture filter.
356 hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_,
357 NULL);
358 } else {
359 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_,
360 NULL);
361 }
362
363 if (FAILED(hr)) {
364 SetErrorState("Failed to connect the Capture graph.");
365 return;
366 }
367
368 hr = media_control_->Pause();
369 if (FAILED(hr)) {
370 SetErrorState("Failed to Pause the Capture device. "
371 "Is it already occupied?");
372 return;
373 }
374
375 // Get the format back from the sink filter after the filter have been
376 // connected.
377 capture_format_ = sink_filter_->ResultingFormat();
378
379 // Start capturing.
380 hr = media_control_->Run();
381 if (FAILED(hr)) {
382 SetErrorState("Failed to start the Capture device.");
383 return;
384 }
385
386 state_ = kCapturing;
387 }
388
StopAndDeAllocate()389 void VideoCaptureDeviceWin::StopAndDeAllocate() {
390 DCHECK(CalledOnValidThread());
391 if (state_ != kCapturing)
392 return;
393
394 HRESULT hr = media_control_->Stop();
395 if (FAILED(hr)) {
396 SetErrorState("Failed to stop the capture graph.");
397 return;
398 }
399
400 graph_builder_->Disconnect(output_capture_pin_);
401 graph_builder_->Disconnect(input_sink_pin_);
402
403 // If the _mjpg filter exist disconnect it even if it has not been used.
404 if (mjpg_filter_) {
405 graph_builder_->Disconnect(input_mjpg_pin_);
406 graph_builder_->Disconnect(output_mjpg_pin_);
407 }
408
409 if (FAILED(hr)) {
410 SetErrorState("Failed to Stop the Capture device");
411 return;
412 }
413 client_.reset();
414 state_ = kIdle;
415 }
416
417 // Implements SinkFilterObserver::SinkFilterObserver.
FrameReceived(const uint8 * buffer,int length)418 void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer,
419 int length) {
420 client_->OnIncomingCapturedData(
421 buffer, length, capture_format_, 0, base::TimeTicks::Now());
422 }
423
CreateCapabilityMap()424 bool VideoCaptureDeviceWin::CreateCapabilityMap() {
425 DCHECK(CalledOnValidThread());
426 ScopedComPtr<IAMStreamConfig> stream_config;
427 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive());
428 if (FAILED(hr)) {
429 DVLOG(2) << "Failed to get IAMStreamConfig interface from "
430 "capture device";
431 return false;
432 }
433
434 // Get interface used for getting the frame rate.
435 ScopedComPtr<IAMVideoControl> video_control;
436 hr = capture_filter_.QueryInterface(video_control.Receive());
437 DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED";
438
439 int count = 0, size = 0;
440 hr = stream_config->GetNumberOfCapabilities(&count, &size);
441 if (FAILED(hr)) {
442 DVLOG(2) << "Failed to GetNumberOfCapabilities";
443 return false;
444 }
445
446 scoped_ptr<BYTE[]> caps(new BYTE[size]);
447 for (int i = 0; i < count; ++i) {
448 ScopedMediaType media_type;
449 hr = stream_config->GetStreamCaps(i, media_type.Receive(), caps.get());
450 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED()
451 // macros here since they'll trigger incorrectly.
452 if (hr != S_OK) {
453 DVLOG(2) << "Failed to GetStreamCaps";
454 return false;
455 }
456
457 if (media_type->majortype == MEDIATYPE_Video &&
458 media_type->formattype == FORMAT_VideoInfo) {
459 VideoCaptureCapabilityWin capability(i);
460 capability.supported_format.pixel_format =
461 TranslateMediaSubtypeToPixelFormat(media_type->subtype);
462 if (capability.supported_format.pixel_format == PIXEL_FORMAT_UNKNOWN)
463 continue;
464
465 VIDEOINFOHEADER* h =
466 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat);
467 capability.supported_format.frame_size.SetSize(h->bmiHeader.biWidth,
468 h->bmiHeader.biHeight);
469
470 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use
471 // the value from VIDEOINFOHEADER.
472 REFERENCE_TIME time_per_frame = h->AvgTimePerFrame;
473 if (video_control) {
474 ScopedCoMem<LONGLONG> max_fps;
475 LONG list_size = 0;
476 SIZE size = {capability.supported_format.frame_size.width(),
477 capability.supported_format.frame_size.height()};
478
479 // GetFrameRateList doesn't return max frame rate always
480 // eg: Logitech Notebook. This may be due to a bug in that API
481 // because GetFrameRateList array is reversed in the above camera. So
482 // a util method written. Can't assume the first value will return
483 // the max fps.
484 hr = video_control->GetFrameRateList(output_capture_pin_, i, size,
485 &list_size, &max_fps);
486 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some
487 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates
488 // into success, so explicitly check S_OK. See http://crbug.com/306237.
489 if (hr == S_OK && list_size > 0 && max_fps) {
490 time_per_frame = *std::min_element(max_fps.get(),
491 max_fps.get() + list_size);
492 }
493 }
494
495 capability.supported_format.frame_rate =
496 (time_per_frame > 0)
497 ? (kSecondsToReferenceTime / static_cast<float>(time_per_frame))
498 : 0.0;
499
500 // DirectShow works at the moment only on integer frame_rate but the
501 // best capability matching class works on rational frame rates.
502 capability.frame_rate_numerator = capability.supported_format.frame_rate;
503 capability.frame_rate_denominator = 1;
504
505 capabilities_.Add(capability);
506 }
507 }
508
509 return !capabilities_.empty();
510 }
511
512 // Set the power line frequency removal in |capture_filter_| if available.
SetAntiFlickerInCaptureFilter()513 void VideoCaptureDeviceWin::SetAntiFlickerInCaptureFilter() {
514 const int power_line_frequency = GetPowerLineFrequencyForLocation();
515 if (power_line_frequency != kPowerLine50Hz &&
516 power_line_frequency != kPowerLine60Hz) {
517 return;
518 }
519 ScopedComPtr<IKsPropertySet> ks_propset;
520 DWORD type_support = 0;
521 HRESULT hr;
522 if (SUCCEEDED(hr = ks_propset.QueryFrom(capture_filter_)) &&
523 SUCCEEDED(hr = ks_propset->QuerySupported(PROPSETID_VIDCAP_VIDEOPROCAMP,
524 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY, &type_support)) &&
525 (type_support & KSPROPERTY_SUPPORT_SET)) {
526 KSPROPERTY_VIDEOPROCAMP_S data = {};
527 data.Property.Set = PROPSETID_VIDCAP_VIDEOPROCAMP;
528 data.Property.Id = KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY;
529 data.Property.Flags = KSPROPERTY_TYPE_SET;
530 data.Value = (power_line_frequency == kPowerLine50Hz) ? 1 : 2;
531 data.Flags = KSPROPERTY_VIDEOPROCAMP_FLAGS_MANUAL;
532 hr = ks_propset->Set(PROPSETID_VIDCAP_VIDEOPROCAMP,
533 KSPROPERTY_VIDEOPROCAMP_POWERLINE_FREQUENCY,
534 &data, sizeof(data), &data, sizeof(data));
535 DVLOG_IF(ERROR, FAILED(hr)) << "Anti-flicker setting failed.";
536 DVLOG_IF(2, SUCCEEDED(hr)) << "Anti-flicker set correctly.";
537 } else {
538 DVLOG(2) << "Anti-flicker setting not supported.";
539 }
540 }
541
SetErrorState(const std::string & reason)542 void VideoCaptureDeviceWin::SetErrorState(const std::string& reason) {
543 DCHECK(CalledOnValidThread());
544 DVLOG(1) << reason;
545 state_ = kError;
546 client_->OnError(reason);
547 }
548 } // namespace media
549