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 <algorithm> 8 #include <list> 9 10 #include "base/command_line.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/sys_string_conversions.h" 13 #include "base/win/metro.h" 14 #include "base/win/scoped_co_mem.h" 15 #include "base/win/scoped_variant.h" 16 #include "media/base/media_switches.h" 17 #include "media/video/capture/win/video_capture_device_mf_win.h" 18 19 using base::win::ScopedCoMem; 20 using base::win::ScopedComPtr; 21 using base::win::ScopedVariant; 22 23 namespace media { 24 namespace { 25 26 // Finds and creates a DirectShow Video Capture filter matching the device_name. 27 HRESULT GetDeviceFilter(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 bool PinMatchesCategory(IPin* pin, REFGUID category) { 87 DCHECK(pin); 88 bool found = false; 89 ScopedComPtr<IKsPropertySet> ks_property; 90 HRESULT hr = ks_property.QueryFrom(pin); 91 if (SUCCEEDED(hr)) { 92 GUID pin_category; 93 DWORD return_value; 94 hr = ks_property->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0, 95 &pin_category, sizeof(pin_category), &return_value); 96 if (SUCCEEDED(hr) && (return_value == sizeof(pin_category))) { 97 found = (pin_category == category); 98 } 99 } 100 return found; 101 } 102 103 // Finds a IPin on a IBaseFilter given the direction an category. 104 HRESULT GetPin(IBaseFilter* filter, PIN_DIRECTION pin_dir, REFGUID category, 105 IPin** pin) { 106 DCHECK(pin); 107 ScopedComPtr<IEnumPins> pin_emum; 108 HRESULT hr = filter->EnumPins(pin_emum.Receive()); 109 if (pin_emum == NULL) 110 return hr; 111 112 // Get first unconnected pin. 113 hr = pin_emum->Reset(); // set to first pin 114 while ((hr = pin_emum->Next(1, pin, NULL)) == S_OK) { 115 PIN_DIRECTION this_pin_dir = static_cast<PIN_DIRECTION>(-1); 116 hr = (*pin)->QueryDirection(&this_pin_dir); 117 if (pin_dir == this_pin_dir) { 118 if (category == GUID_NULL || PinMatchesCategory(*pin, category)) 119 return S_OK; 120 } 121 (*pin)->Release(); 122 } 123 124 return E_FAIL; 125 } 126 127 // Release the format block for a media type. 128 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx 129 void FreeMediaType(AM_MEDIA_TYPE* mt) { 130 if (mt->cbFormat != 0) { 131 CoTaskMemFree(mt->pbFormat); 132 mt->cbFormat = 0; 133 mt->pbFormat = NULL; 134 } 135 if (mt->pUnk != NULL) { 136 NOTREACHED(); 137 // pUnk should not be used. 138 mt->pUnk->Release(); 139 mt->pUnk = NULL; 140 } 141 } 142 143 // Delete a media type structure that was allocated on the heap. 144 // http://msdn.microsoft.com/en-us/library/dd375432(VS.85).aspx 145 void DeleteMediaType(AM_MEDIA_TYPE* mt) { 146 if (mt != NULL) { 147 FreeMediaType(mt); 148 CoTaskMemFree(mt); 149 } 150 } 151 152 } // namespace 153 154 // static 155 void VideoCaptureDevice::GetDeviceNames(Names* device_names) { 156 const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); 157 // Use Media Foundation for Metro processes (after and including Win8) 158 // and DirectShow for any other platforms. 159 if (base::win::IsMetroProcess() && 160 !cmd_line->HasSwitch(switches::kForceDirectShowVideoCapture)) { 161 VideoCaptureDeviceMFWin::GetDeviceNames(device_names); 162 } else { 163 VideoCaptureDeviceWin::GetDeviceNames(device_names); 164 } 165 } 166 167 // static 168 void VideoCaptureDevice::GetDeviceSupportedFormats(const Name& device, 169 VideoCaptureFormats* formats) { 170 NOTIMPLEMENTED(); 171 } 172 173 // static 174 VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) { 175 VideoCaptureDevice* ret = NULL; 176 if (device_name.capture_api_type() == Name::MEDIA_FOUNDATION) { 177 DCHECK(VideoCaptureDeviceMFWin::PlatformSupported()); 178 scoped_ptr<VideoCaptureDeviceMFWin> device( 179 new VideoCaptureDeviceMFWin(device_name)); 180 DVLOG(1) << " MediaFoundation Device: " << device_name.name(); 181 if (device->Init()) 182 ret = device.release(); 183 } else if (device_name.capture_api_type() == Name::DIRECT_SHOW) { 184 scoped_ptr<VideoCaptureDeviceWin> device( 185 new VideoCaptureDeviceWin(device_name)); 186 DVLOG(1) << " DirectShow Device: " << device_name.name(); 187 if (device->Init()) 188 ret = device.release(); 189 } else{ 190 NOTREACHED() << " Couldn't recognize VideoCaptureDevice type"; 191 } 192 193 return ret; 194 } 195 196 // static 197 void VideoCaptureDeviceWin::GetDeviceNames(Names* device_names) { 198 DCHECK(device_names); 199 200 ScopedComPtr<ICreateDevEnum> dev_enum; 201 HRESULT hr = dev_enum.CreateInstance(CLSID_SystemDeviceEnum, NULL, 202 CLSCTX_INPROC); 203 if (FAILED(hr)) 204 return; 205 206 ScopedComPtr<IEnumMoniker> enum_moniker; 207 hr = dev_enum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, 208 enum_moniker.Receive(), 0); 209 // CreateClassEnumerator returns S_FALSE on some Windows OS 210 // when no camera exist. Therefore the FAILED macro can't be used. 211 if (hr != S_OK) 212 return; 213 214 device_names->clear(); 215 216 // Name of a fake DirectShow filter that exist on computers with 217 // GTalk installed. 218 static const char kGoogleCameraAdapter[] = "google camera adapter"; 219 220 // Enumerate all video capture devices. 221 ScopedComPtr<IMoniker> moniker; 222 int index = 0; 223 while (enum_moniker->Next(1, moniker.Receive(), NULL) == S_OK) { 224 ScopedComPtr<IPropertyBag> prop_bag; 225 hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, prop_bag.ReceiveVoid()); 226 if (FAILED(hr)) { 227 moniker.Release(); 228 continue; 229 } 230 231 // Find the description or friendly name. 232 ScopedVariant name; 233 hr = prop_bag->Read(L"Description", name.Receive(), 0); 234 if (FAILED(hr)) 235 hr = prop_bag->Read(L"FriendlyName", name.Receive(), 0); 236 237 if (SUCCEEDED(hr) && name.type() == VT_BSTR) { 238 // Ignore all VFW drivers and the special Google Camera Adapter. 239 // Google Camera Adapter is not a real DirectShow camera device. 240 // VFW is very old Video for Windows drivers that can not be used. 241 const wchar_t* str_ptr = V_BSTR(&name); 242 const int name_length = arraysize(kGoogleCameraAdapter) - 1; 243 244 if ((wcsstr(str_ptr, L"(VFW)") == NULL) && 245 lstrlenW(str_ptr) < name_length || 246 (!(LowerCaseEqualsASCII(str_ptr, str_ptr + name_length, 247 kGoogleCameraAdapter)))) { 248 std::string id; 249 std::string device_name(base::SysWideToUTF8(str_ptr)); 250 name.Reset(); 251 hr = prop_bag->Read(L"DevicePath", name.Receive(), 0); 252 if (FAILED(hr) || name.type() != VT_BSTR) { 253 id = device_name; 254 } else { 255 DCHECK_EQ(name.type(), VT_BSTR); 256 id = base::SysWideToUTF8(V_BSTR(&name)); 257 } 258 259 device_names->push_back(Name(device_name, id, Name::DIRECT_SHOW)); 260 } 261 } 262 moniker.Release(); 263 } 264 } 265 266 VideoCaptureDeviceWin::VideoCaptureDeviceWin(const Name& device_name) 267 : device_name_(device_name), 268 state_(kIdle) { 269 DetachFromThread(); 270 } 271 272 VideoCaptureDeviceWin::~VideoCaptureDeviceWin() { 273 DCHECK(CalledOnValidThread()); 274 if (media_control_) 275 media_control_->Stop(); 276 277 if (graph_builder_) { 278 if (sink_filter_) { 279 graph_builder_->RemoveFilter(sink_filter_); 280 sink_filter_ = NULL; 281 } 282 283 if (capture_filter_) 284 graph_builder_->RemoveFilter(capture_filter_); 285 286 if (mjpg_filter_) 287 graph_builder_->RemoveFilter(mjpg_filter_); 288 } 289 } 290 291 bool VideoCaptureDeviceWin::Init() { 292 DCHECK(CalledOnValidThread()); 293 HRESULT hr = GetDeviceFilter(device_name_, capture_filter_.Receive()); 294 if (!capture_filter_) { 295 DVLOG(2) << "Failed to create capture filter."; 296 return false; 297 } 298 299 hr = GetPin(capture_filter_, PINDIR_OUTPUT, PIN_CATEGORY_CAPTURE, 300 output_capture_pin_.Receive()); 301 if (!output_capture_pin_) { 302 DVLOG(2) << "Failed to get capture output pin"; 303 return false; 304 } 305 306 // Create the sink filter used for receiving Captured frames. 307 sink_filter_ = new SinkFilter(this); 308 if (sink_filter_ == NULL) { 309 DVLOG(2) << "Failed to create send filter"; 310 return false; 311 } 312 313 input_sink_pin_ = sink_filter_->GetPin(0); 314 315 hr = graph_builder_.CreateInstance(CLSID_FilterGraph, NULL, 316 CLSCTX_INPROC_SERVER); 317 if (FAILED(hr)) { 318 DVLOG(2) << "Failed to create graph builder."; 319 return false; 320 } 321 322 hr = graph_builder_.QueryInterface(media_control_.Receive()); 323 if (FAILED(hr)) { 324 DVLOG(2) << "Failed to create media control builder."; 325 return false; 326 } 327 328 hr = graph_builder_->AddFilter(capture_filter_, NULL); 329 if (FAILED(hr)) { 330 DVLOG(2) << "Failed to add the capture device to the graph."; 331 return false; 332 } 333 334 hr = graph_builder_->AddFilter(sink_filter_, NULL); 335 if (FAILED(hr)) { 336 DVLOG(2)<< "Failed to add the send filter to the graph."; 337 return false; 338 } 339 340 return CreateCapabilityMap(); 341 } 342 343 void VideoCaptureDeviceWin::AllocateAndStart( 344 const VideoCaptureParams& params, 345 scoped_ptr<VideoCaptureDevice::Client> client) { 346 DCHECK(CalledOnValidThread()); 347 if (state_ != kIdle) 348 return; 349 350 client_ = client.Pass(); 351 352 // Get the camera capability that best match the requested resolution. 353 const VideoCaptureCapabilityWin& found_capability = 354 capabilities_.GetBestMatchedFormat( 355 params.requested_format.frame_size.width(), 356 params.requested_format.frame_size.height(), 357 params.requested_format.frame_rate); 358 VideoCaptureFormat format = found_capability.supported_format; 359 360 // Reduce the frame rate if the requested frame rate is lower 361 // than the capability. 362 if (format.frame_rate > params.requested_format.frame_rate) 363 format.frame_rate = params.requested_format.frame_rate; 364 365 AM_MEDIA_TYPE* pmt = NULL; 366 VIDEO_STREAM_CONFIG_CAPS caps; 367 368 ScopedComPtr<IAMStreamConfig> stream_config; 369 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); 370 if (FAILED(hr)) { 371 SetErrorState("Can't get the Capture format settings"); 372 return; 373 } 374 375 // Get the windows capability from the capture device. 376 hr = stream_config->GetStreamCaps(found_capability.stream_index, &pmt, 377 reinterpret_cast<BYTE*>(&caps)); 378 if (SUCCEEDED(hr)) { 379 if (pmt->formattype == FORMAT_VideoInfo) { 380 VIDEOINFOHEADER* h = reinterpret_cast<VIDEOINFOHEADER*>(pmt->pbFormat); 381 if (format.frame_rate > 0) 382 h->AvgTimePerFrame = kSecondsToReferenceTime / format.frame_rate; 383 } 384 // Set the sink filter to request this format. 385 sink_filter_->SetRequestedMediaFormat(format); 386 // Order the capture device to use this format. 387 hr = stream_config->SetFormat(pmt); 388 } 389 390 if (FAILED(hr)) 391 SetErrorState("Failed to set capture device output format"); 392 393 if (format.pixel_format == PIXEL_FORMAT_MJPEG && !mjpg_filter_.get()) { 394 // Create MJPG filter if we need it. 395 hr = mjpg_filter_.CreateInstance(CLSID_MjpegDec, NULL, CLSCTX_INPROC); 396 397 if (SUCCEEDED(hr)) { 398 GetPin(mjpg_filter_, PINDIR_INPUT, GUID_NULL, input_mjpg_pin_.Receive()); 399 GetPin(mjpg_filter_, PINDIR_OUTPUT, GUID_NULL, 400 output_mjpg_pin_.Receive()); 401 hr = graph_builder_->AddFilter(mjpg_filter_, NULL); 402 } 403 404 if (FAILED(hr)) { 405 mjpg_filter_.Release(); 406 input_mjpg_pin_.Release(); 407 output_mjpg_pin_.Release(); 408 } 409 } 410 411 if (format.pixel_format == PIXEL_FORMAT_MJPEG && mjpg_filter_.get()) { 412 // Connect the camera to the MJPEG decoder. 413 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_mjpg_pin_, 414 NULL); 415 // Connect the MJPEG filter to the Capture filter. 416 hr += graph_builder_->ConnectDirect(output_mjpg_pin_, input_sink_pin_, 417 NULL); 418 } else { 419 hr = graph_builder_->ConnectDirect(output_capture_pin_, input_sink_pin_, 420 NULL); 421 } 422 423 if (FAILED(hr)) { 424 SetErrorState("Failed to connect the Capture graph."); 425 return; 426 } 427 428 hr = media_control_->Pause(); 429 if (FAILED(hr)) { 430 SetErrorState("Failed to Pause the Capture device. " 431 "Is it already occupied?"); 432 return; 433 } 434 435 // Get the format back from the sink filter after the filter have been 436 // connected. 437 capture_format_ = sink_filter_->ResultingFormat(); 438 439 // Start capturing. 440 hr = media_control_->Run(); 441 if (FAILED(hr)) { 442 SetErrorState("Failed to start the Capture device."); 443 return; 444 } 445 446 state_ = kCapturing; 447 } 448 449 void VideoCaptureDeviceWin::StopAndDeAllocate() { 450 DCHECK(CalledOnValidThread()); 451 if (state_ != kCapturing) 452 return; 453 454 HRESULT hr = media_control_->Stop(); 455 if (FAILED(hr)) { 456 SetErrorState("Failed to stop the capture graph."); 457 return; 458 } 459 460 graph_builder_->Disconnect(output_capture_pin_); 461 graph_builder_->Disconnect(input_sink_pin_); 462 463 // If the _mjpg filter exist disconnect it even if it has not been used. 464 if (mjpg_filter_) { 465 graph_builder_->Disconnect(input_mjpg_pin_); 466 graph_builder_->Disconnect(output_mjpg_pin_); 467 } 468 469 if (FAILED(hr)) { 470 SetErrorState("Failed to Stop the Capture device"); 471 return; 472 } 473 client_.reset(); 474 state_ = kIdle; 475 } 476 477 // Implements SinkFilterObserver::SinkFilterObserver. 478 void VideoCaptureDeviceWin::FrameReceived(const uint8* buffer, 479 int length) { 480 client_->OnIncomingCapturedFrame( 481 buffer, length, base::Time::Now(), 0, capture_format_); 482 } 483 484 bool VideoCaptureDeviceWin::CreateCapabilityMap() { 485 DCHECK(CalledOnValidThread()); 486 ScopedComPtr<IAMStreamConfig> stream_config; 487 HRESULT hr = output_capture_pin_.QueryInterface(stream_config.Receive()); 488 if (FAILED(hr)) { 489 DVLOG(2) << "Failed to get IAMStreamConfig interface from " 490 "capture device"; 491 return false; 492 } 493 494 // Get interface used for getting the frame rate. 495 ScopedComPtr<IAMVideoControl> video_control; 496 hr = capture_filter_.QueryInterface(video_control.Receive()); 497 DVLOG_IF(2, FAILED(hr)) << "IAMVideoControl Interface NOT SUPPORTED"; 498 499 AM_MEDIA_TYPE* media_type = NULL; 500 VIDEO_STREAM_CONFIG_CAPS caps; 501 int count, size; 502 503 hr = stream_config->GetNumberOfCapabilities(&count, &size); 504 if (FAILED(hr)) { 505 DVLOG(2) << "Failed to GetNumberOfCapabilities"; 506 return false; 507 } 508 509 for (int i = 0; i < count; ++i) { 510 hr = stream_config->GetStreamCaps(i, &media_type, 511 reinterpret_cast<BYTE*>(&caps)); 512 // GetStreamCaps() may return S_FALSE, so don't use FAILED() or SUCCEED() 513 // macros here since they'll trigger incorrectly. 514 if (hr != S_OK) { 515 DVLOG(2) << "Failed to GetStreamCaps"; 516 return false; 517 } 518 519 if (media_type->majortype == MEDIATYPE_Video && 520 media_type->formattype == FORMAT_VideoInfo) { 521 VideoCaptureCapabilityWin capability(i); 522 VIDEOINFOHEADER* h = 523 reinterpret_cast<VIDEOINFOHEADER*>(media_type->pbFormat); 524 capability.supported_format.frame_size.SetSize(h->bmiHeader.biWidth, 525 h->bmiHeader.biHeight); 526 527 // Try to get a better |time_per_frame| from IAMVideoControl. If not, use 528 // the value from VIDEOINFOHEADER. 529 REFERENCE_TIME time_per_frame = h->AvgTimePerFrame; 530 if (video_control) { 531 ScopedCoMem<LONGLONG> max_fps; 532 LONG list_size = 0; 533 SIZE size = {capability.supported_format.frame_size.width(), 534 capability.supported_format.frame_size.height()}; 535 536 // GetFrameRateList doesn't return max frame rate always 537 // eg: Logitech Notebook. This may be due to a bug in that API 538 // because GetFrameRateList array is reversed in the above camera. So 539 // a util method written. Can't assume the first value will return 540 // the max fps. 541 hr = video_control->GetFrameRateList(output_capture_pin_, i, size, 542 &list_size, &max_fps); 543 // Sometimes |list_size| will be > 0, but max_fps will be NULL. Some 544 // drivers may return an HRESULT of S_FALSE which SUCCEEDED() translates 545 // into success, so explicitly check S_OK. See http://crbug.com/306237. 546 if (hr == S_OK && list_size > 0 && max_fps) { 547 time_per_frame = *std::min_element(max_fps.get(), 548 max_fps.get() + list_size); 549 } 550 } 551 552 capability.supported_format.frame_rate = 553 (time_per_frame > 0) 554 ? static_cast<int>(kSecondsToReferenceTime / time_per_frame) 555 : 0; 556 557 // DirectShow works at the moment only on integer frame_rate but the 558 // best capability matching class works on rational frame rates. 559 capability.frame_rate_numerator = capability.supported_format.frame_rate; 560 capability.frame_rate_denominator = 1; 561 562 // We can't switch MEDIATYPE :~(. 563 if (media_type->subtype == kMediaSubTypeI420) { 564 capability.supported_format.pixel_format = PIXEL_FORMAT_I420; 565 } else if (media_type->subtype == MEDIASUBTYPE_IYUV) { 566 // This is identical to PIXEL_FORMAT_I420. 567 capability.supported_format.pixel_format = PIXEL_FORMAT_I420; 568 } else if (media_type->subtype == MEDIASUBTYPE_RGB24) { 569 capability.supported_format.pixel_format = PIXEL_FORMAT_RGB24; 570 } else if (media_type->subtype == MEDIASUBTYPE_YUY2) { 571 capability.supported_format.pixel_format = PIXEL_FORMAT_YUY2; 572 } else if (media_type->subtype == MEDIASUBTYPE_MJPG) { 573 capability.supported_format.pixel_format = PIXEL_FORMAT_MJPEG; 574 } else if (media_type->subtype == MEDIASUBTYPE_UYVY) { 575 capability.supported_format.pixel_format = PIXEL_FORMAT_UYVY; 576 } else if (media_type->subtype == MEDIASUBTYPE_ARGB32) { 577 capability.supported_format.pixel_format = PIXEL_FORMAT_ARGB; 578 } else { 579 WCHAR guid_str[128]; 580 StringFromGUID2(media_type->subtype, guid_str, arraysize(guid_str)); 581 DVLOG(2) << "Device supports (also) an unknown media type " << guid_str; 582 continue; 583 } 584 capabilities_.Add(capability); 585 } 586 DeleteMediaType(media_type); 587 media_type = NULL; 588 } 589 590 return !capabilities_.empty(); 591 } 592 593 void VideoCaptureDeviceWin::SetErrorState(const char* reason) { 594 DCHECK(CalledOnValidThread()); 595 DVLOG(1) << reason; 596 state_ = kError; 597 client_->OnError(); 598 } 599 } // namespace media 600