Home | History | Annotate | Download | only in win
      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