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 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 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 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 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 158 void VideoCaptureDeviceWin::ScopedMediaType::Free() { 159 if (!media_type_) 160 return; 161 162 DeleteMediaType(media_type_); 163 media_type_= NULL; 164 } 165 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 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 189 void VideoCaptureDeviceWin::ScopedMediaType::DeleteMediaType( 190 AM_MEDIA_TYPE* mt) { 191 if (mt != NULL) { 192 FreeMediaType(mt); 193 CoTaskMemFree(mt); 194 } 195 } 196 197 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name) 198 : device_name_(device_name), 199 state_(kIdle) { 200 DetachFromThread(); 201 } 202 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 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 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 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. 418 void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer, 419 int length) { 420 client_->OnIncomingCapturedData( 421 buffer, length, capture_format_, 0, base::TimeTicks::Now()); 422 } 423 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. 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 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