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_mf_win.h" 6 7 #include <mfapi.h> 8 #include <mferror.h> 9 10 #include "base/lazy_instance.h" 11 #include "base/memory/ref_counted.h" 12 #include "base/strings/sys_string_conversions.h" 13 #include "base/synchronization/waitable_event.h" 14 #include "base/win/scoped_co_mem.h" 15 #include "base/win/windows_version.h" 16 #include "media/video/capture/win/capability_list_win.h" 17 18 using base::win::ScopedCoMem; 19 using base::win::ScopedComPtr; 20 21 namespace media { 22 namespace { 23 24 // In Windows device identifiers, the USB VID and PID are preceded by the string 25 // "vid_" or "pid_". The identifiers are each 4 bytes long. 26 const char kVidPrefix[] = "vid_"; // Also contains '\0'. 27 const char kPidPrefix[] = "pid_"; // Also contains '\0'. 28 const size_t kVidPidSize = 4; 29 30 class MFInitializerSingleton { 31 public: 32 MFInitializerSingleton() { MFStartup(MF_VERSION, MFSTARTUP_LITE); } 33 ~MFInitializerSingleton() { MFShutdown(); } 34 }; 35 36 static base::LazyInstance<MFInitializerSingleton> g_mf_initialize = 37 LAZY_INSTANCE_INITIALIZER; 38 39 void EnsureMFInit() { 40 g_mf_initialize.Get(); 41 } 42 43 bool PrepareVideoCaptureAttributes(IMFAttributes** attributes, int count) { 44 EnsureMFInit(); 45 46 if (FAILED(MFCreateAttributes(attributes, count))) 47 return false; 48 49 return SUCCEEDED((*attributes)->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, 50 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)); 51 } 52 53 bool EnumerateVideoDevices(IMFActivate*** devices, 54 UINT32* count) { 55 ScopedComPtr<IMFAttributes> attributes; 56 if (!PrepareVideoCaptureAttributes(attributes.Receive(), 1)) 57 return false; 58 59 return SUCCEEDED(MFEnumDeviceSources(attributes, devices, count)); 60 } 61 62 bool CreateVideoCaptureDevice(const char* sym_link, IMFMediaSource** source) { 63 ScopedComPtr<IMFAttributes> attributes; 64 if (!PrepareVideoCaptureAttributes(attributes.Receive(), 2)) 65 return false; 66 67 attributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, 68 base::SysUTF8ToWide(sym_link).c_str()); 69 70 return SUCCEEDED(MFCreateDeviceSource(attributes, source)); 71 } 72 73 bool FormatFromGuid(const GUID& guid, VideoPixelFormat* format) { 74 struct { 75 const GUID& guid; 76 const VideoPixelFormat format; 77 } static const kFormatMap[] = { 78 { MFVideoFormat_I420, PIXEL_FORMAT_I420 }, 79 { MFVideoFormat_YUY2, PIXEL_FORMAT_YUY2 }, 80 { MFVideoFormat_UYVY, PIXEL_FORMAT_UYVY }, 81 { MFVideoFormat_RGB24, PIXEL_FORMAT_RGB24 }, 82 { MFVideoFormat_ARGB32, PIXEL_FORMAT_ARGB }, 83 { MFVideoFormat_MJPG, PIXEL_FORMAT_MJPEG }, 84 { MFVideoFormat_YV12, PIXEL_FORMAT_YV12 }, 85 }; 86 87 for (int i = 0; i < arraysize(kFormatMap); ++i) { 88 if (kFormatMap[i].guid == guid) { 89 *format = kFormatMap[i].format; 90 return true; 91 } 92 } 93 94 return false; 95 } 96 97 bool GetFrameSize(IMFMediaType* type, gfx::Size* frame_size) { 98 UINT32 width32, height32; 99 if (FAILED(MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width32, &height32))) 100 return false; 101 frame_size->SetSize(width32, height32); 102 return true; 103 } 104 105 bool GetFrameRate(IMFMediaType* type, 106 int* frame_rate_numerator, 107 int* frame_rate_denominator) { 108 UINT32 numerator, denominator; 109 if (FAILED(MFGetAttributeRatio(type, MF_MT_FRAME_RATE, &numerator, 110 &denominator))|| 111 !denominator) { 112 return false; 113 } 114 *frame_rate_numerator = numerator; 115 *frame_rate_denominator = denominator; 116 return true; 117 } 118 119 bool FillCapabilitiesFromType(IMFMediaType* type, 120 VideoCaptureCapabilityWin* capability) { 121 GUID type_guid; 122 if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &type_guid)) || 123 !GetFrameSize(type, &capability->supported_format.frame_size) || 124 !GetFrameRate(type, 125 &capability->frame_rate_numerator, 126 &capability->frame_rate_denominator) || 127 !FormatFromGuid(type_guid, &capability->supported_format.pixel_format)) { 128 return false; 129 } 130 // Keep the integer version of the frame_rate for (potential) returns. 131 capability->supported_format.frame_rate = 132 capability->frame_rate_numerator / capability->frame_rate_denominator; 133 134 return true; 135 } 136 137 HRESULT FillCapabilities(IMFSourceReader* source, 138 CapabilityList* capabilities) { 139 DWORD stream_index = 0; 140 ScopedComPtr<IMFMediaType> type; 141 HRESULT hr; 142 while (SUCCEEDED(hr = source->GetNativeMediaType( 143 MF_SOURCE_READER_FIRST_VIDEO_STREAM, stream_index, type.Receive()))) { 144 VideoCaptureCapabilityWin capability(stream_index++); 145 if (FillCapabilitiesFromType(type, &capability)) 146 capabilities->Add(capability); 147 type.Release(); 148 } 149 150 if (capabilities->empty() && (SUCCEEDED(hr) || hr == MF_E_NO_MORE_TYPES)) 151 hr = HRESULT_FROM_WIN32(ERROR_EMPTY); 152 153 return (hr == MF_E_NO_MORE_TYPES) ? S_OK : hr; 154 } 155 156 bool LoadMediaFoundationDlls() { 157 static const wchar_t* const kMfDLLs[] = { 158 L"%WINDIR%\\system32\\mf.dll", 159 L"%WINDIR%\\system32\\mfplat.dll", 160 L"%WINDIR%\\system32\\mfreadwrite.dll", 161 }; 162 163 for (int i = 0; i < arraysize(kMfDLLs); ++i) { 164 wchar_t path[MAX_PATH] = {0}; 165 ExpandEnvironmentStringsW(kMfDLLs[i], path, arraysize(path)); 166 if (!LoadLibraryExW(path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH)) 167 return false; 168 } 169 170 return true; 171 } 172 173 } // namespace 174 175 class MFReaderCallback 176 : public base::RefCountedThreadSafe<MFReaderCallback>, 177 public IMFSourceReaderCallback { 178 public: 179 MFReaderCallback(VideoCaptureDeviceMFWin* observer) 180 : observer_(observer), wait_event_(NULL) { 181 } 182 183 void SetSignalOnFlush(base::WaitableEvent* event) { 184 wait_event_ = event; 185 } 186 187 STDMETHOD(QueryInterface)(REFIID riid, void** object) { 188 if (riid != IID_IUnknown && riid != IID_IMFSourceReaderCallback) 189 return E_NOINTERFACE; 190 *object = static_cast<IMFSourceReaderCallback*>(this); 191 AddRef(); 192 return S_OK; 193 } 194 195 STDMETHOD_(ULONG, AddRef)() { 196 base::RefCountedThreadSafe<MFReaderCallback>::AddRef(); 197 return 1U; 198 } 199 200 STDMETHOD_(ULONG, Release)() { 201 base::RefCountedThreadSafe<MFReaderCallback>::Release(); 202 return 1U; 203 } 204 205 STDMETHOD(OnReadSample)(HRESULT status, DWORD stream_index, 206 DWORD stream_flags, LONGLONG time_stamp, IMFSample* sample) { 207 base::Time stamp(base::Time::Now()); 208 if (!sample) { 209 observer_->OnIncomingCapturedFrame(NULL, 0, stamp, 0); 210 return S_OK; 211 } 212 213 DWORD count = 0; 214 sample->GetBufferCount(&count); 215 216 for (DWORD i = 0; i < count; ++i) { 217 ScopedComPtr<IMFMediaBuffer> buffer; 218 sample->GetBufferByIndex(i, buffer.Receive()); 219 if (buffer) { 220 DWORD length = 0, max_length = 0; 221 BYTE* data = NULL; 222 buffer->Lock(&data, &max_length, &length); 223 observer_->OnIncomingCapturedFrame(data, length, stamp, 0); 224 buffer->Unlock(); 225 } 226 } 227 return S_OK; 228 } 229 230 STDMETHOD(OnFlush)(DWORD stream_index) { 231 if (wait_event_) { 232 wait_event_->Signal(); 233 wait_event_ = NULL; 234 } 235 return S_OK; 236 } 237 238 STDMETHOD(OnEvent)(DWORD stream_index, IMFMediaEvent* event) { 239 NOTIMPLEMENTED(); 240 return S_OK; 241 } 242 243 private: 244 friend class base::RefCountedThreadSafe<MFReaderCallback>; 245 ~MFReaderCallback() {} 246 247 VideoCaptureDeviceMFWin* observer_; 248 base::WaitableEvent* wait_event_; 249 }; 250 251 // static 252 bool VideoCaptureDeviceMFWin::PlatformSupported() { 253 // Even though the DLLs might be available on Vista, we get crashes 254 // when running our tests on the build bots. 255 if (base::win::GetVersion() < base::win::VERSION_WIN7) 256 return false; 257 258 static bool g_dlls_available = LoadMediaFoundationDlls(); 259 return g_dlls_available; 260 } 261 262 // static 263 void VideoCaptureDeviceMFWin::GetDeviceNames(Names* device_names) { 264 ScopedCoMem<IMFActivate*> devices; 265 UINT32 count; 266 if (!EnumerateVideoDevices(&devices, &count)) 267 return; 268 269 HRESULT hr; 270 for (UINT32 i = 0; i < count; ++i) { 271 UINT32 name_size, id_size; 272 ScopedCoMem<wchar_t> name, id; 273 if (SUCCEEDED(hr = devices[i]->GetAllocatedString( 274 MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &name, &name_size)) && 275 SUCCEEDED(hr = devices[i]->GetAllocatedString( 276 MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &id, 277 &id_size))) { 278 std::wstring name_w(name, name_size), id_w(id, id_size); 279 Name device(base::SysWideToUTF8(name_w), base::SysWideToUTF8(id_w), 280 Name::MEDIA_FOUNDATION); 281 device_names->push_back(device); 282 } else { 283 DLOG(WARNING) << "GetAllocatedString failed: " << std::hex << hr; 284 } 285 devices[i]->Release(); 286 } 287 } 288 289 const std::string VideoCaptureDevice::Name::GetModel() const { 290 const size_t vid_prefix_size = sizeof(kVidPrefix) - 1; 291 const size_t pid_prefix_size = sizeof(kPidPrefix) - 1; 292 const size_t vid_location = unique_id_.find(kVidPrefix); 293 if (vid_location == std::string::npos || 294 vid_location + vid_prefix_size + kVidPidSize > unique_id_.size()) { 295 return ""; 296 } 297 const size_t pid_location = unique_id_.find(kPidPrefix); 298 if (pid_location == std::string::npos || 299 pid_location + pid_prefix_size + kVidPidSize > unique_id_.size()) { 300 return ""; 301 } 302 std::string id_vendor = 303 unique_id_.substr(vid_location + vid_prefix_size, kVidPidSize); 304 std::string id_product = 305 unique_id_.substr(pid_location + pid_prefix_size, kVidPidSize); 306 return id_vendor + ":" + id_product; 307 } 308 309 VideoCaptureDeviceMFWin::VideoCaptureDeviceMFWin(const Name& device_name) 310 : name_(device_name), capture_(0) { 311 DetachFromThread(); 312 } 313 314 VideoCaptureDeviceMFWin::~VideoCaptureDeviceMFWin() { 315 DCHECK(CalledOnValidThread()); 316 } 317 318 bool VideoCaptureDeviceMFWin::Init() { 319 DCHECK(CalledOnValidThread()); 320 DCHECK(!reader_); 321 322 ScopedComPtr<IMFMediaSource> source; 323 if (!CreateVideoCaptureDevice(name_.id().c_str(), source.Receive())) 324 return false; 325 326 ScopedComPtr<IMFAttributes> attributes; 327 MFCreateAttributes(attributes.Receive(), 1); 328 DCHECK(attributes); 329 330 callback_ = new MFReaderCallback(this); 331 attributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback_.get()); 332 333 return SUCCEEDED(MFCreateSourceReaderFromMediaSource(source, attributes, 334 reader_.Receive())); 335 } 336 337 void VideoCaptureDeviceMFWin::AllocateAndStart( 338 const VideoCaptureParams& params, 339 scoped_ptr<VideoCaptureDevice::Client> client) { 340 DCHECK(CalledOnValidThread()); 341 342 base::AutoLock lock(lock_); 343 344 client_ = client.Pass(); 345 DCHECK_EQ(capture_, false); 346 347 CapabilityList capabilities; 348 HRESULT hr = S_OK; 349 if (!reader_ || FAILED(hr = FillCapabilities(reader_, &capabilities))) { 350 OnError(hr); 351 return; 352 } 353 354 VideoCaptureCapabilityWin found_capability = 355 capabilities.GetBestMatchedFormat( 356 params.requested_format.frame_size.width(), 357 params.requested_format.frame_size.height(), 358 params.requested_format.frame_rate); 359 360 ScopedComPtr<IMFMediaType> type; 361 if (FAILED(hr = reader_->GetNativeMediaType( 362 MF_SOURCE_READER_FIRST_VIDEO_STREAM, found_capability.stream_index, 363 type.Receive())) || 364 FAILED(hr = reader_->SetCurrentMediaType( 365 MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, type))) { 366 OnError(hr); 367 return; 368 } 369 370 if (FAILED(hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, 371 NULL, NULL, NULL, NULL))) { 372 OnError(hr); 373 return; 374 } 375 capture_format_ = found_capability.supported_format; 376 capture_ = true; 377 } 378 379 void VideoCaptureDeviceMFWin::StopAndDeAllocate() { 380 DCHECK(CalledOnValidThread()); 381 base::WaitableEvent flushed(false, false); 382 const int kFlushTimeOutInMs = 1000; 383 bool wait = false; 384 { 385 base::AutoLock lock(lock_); 386 if (capture_) { 387 capture_ = false; 388 callback_->SetSignalOnFlush(&flushed); 389 HRESULT hr = reader_->Flush(MF_SOURCE_READER_ALL_STREAMS); 390 wait = SUCCEEDED(hr); 391 if (!wait) { 392 callback_->SetSignalOnFlush(NULL); 393 } 394 } 395 client_.reset(); 396 } 397 398 // If the device has been unplugged, the Flush() won't trigger the event 399 // and a timeout will happen. 400 // TODO(tommi): Hook up the IMFMediaEventGenerator notifications API and 401 // do not wait at all after getting MEVideoCaptureDeviceRemoved event. 402 // See issue/226396. 403 if (wait) 404 flushed.TimedWait(base::TimeDelta::FromMilliseconds(kFlushTimeOutInMs)); 405 } 406 407 void VideoCaptureDeviceMFWin::OnIncomingCapturedFrame( 408 const uint8* data, 409 int length, 410 const base::Time& time_stamp, 411 int rotation) { 412 base::AutoLock lock(lock_); 413 if (data && client_.get()) 414 client_->OnIncomingCapturedFrame(data, 415 length, 416 time_stamp, 417 rotation, 418 capture_format_); 419 420 if (capture_) { 421 HRESULT hr = reader_->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, 422 NULL, NULL, NULL, NULL); 423 if (FAILED(hr)) { 424 // If running the *VideoCap* unit tests on repeat, this can sometimes 425 // fail with HRESULT_FROM_WINHRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION). 426 // It's not clear to me why this is, but it is possible that it has 427 // something to do with this bug: 428 // http://support.microsoft.com/kb/979567 429 OnError(hr); 430 } 431 } 432 } 433 434 void VideoCaptureDeviceMFWin::OnError(HRESULT hr) { 435 DLOG(ERROR) << "VideoCaptureDeviceMFWin: " << std::hex << hr; 436 if (client_.get()) 437 client_->OnError(); 438 } 439 440 } // namespace media 441