• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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