blob: f2a560a2ddc729d02b53978197657192411f1b2d [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <mfidl.h>
#include <ks.h>
#include <mfapi.h>
#include <mferror.h>
#include <stddef.h>
#include "base/bind.h"
#include "base/strings/sys_string_conversions.h"
#include "media/capture/video/win/video_capture_device_factory_win.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::Mock;
namespace media {
namespace {
// MediaFoundation devices
const wchar_t* kMFDeviceId0 = L"\\\\?\\usb#vid_0000&pid_0000&mi_00";
const wchar_t* kMFDeviceName0 = L"Device 0";
const wchar_t* kMFDeviceId1 = L"\\\\?\\usb#vid_0001&pid_0001&mi_00";
const wchar_t* kMFDeviceName1 = L"Device 1";
const wchar_t* kMFDeviceId2 = L"\\\\?\\usb#vid_0002&pid_0002&mi_00";
const wchar_t* kMFDeviceName2 = L"Device 2";
const wchar_t* kMFDeviceId5 = L"\\\\?\\usb#vid_0005&pid_0005&mi_00";
const wchar_t* kMFDeviceName5 = L"Dazzle";
const wchar_t* kMFDeviceId6 = L"\\\\?\\usb#vid_eb1a&pid_2860&mi_00";
const wchar_t* kMFDeviceName6 = L"Empia Device";
void GetMFSupportedFormats(const VideoCaptureDeviceDescriptor& device,
VideoCaptureFormats* formats) {
if (device.device_id == base::SysWideToUTF8(kMFDeviceId6)) {
VideoCaptureFormat arbitrary_format;
formats->emplace_back(arbitrary_format);
}
}
// DirectShow devices
const wchar_t* kDirectShowDeviceId0 = L"\\\\?\\usb#vid_0000&pid_0000&mi_00";
const wchar_t* kDirectShowDeviceName0 = L"Device 0";
const wchar_t* kDirectShowDeviceId1 = L"\\\\?\\usb#vid_0001&pid_0001&mi_00#1";
const wchar_t* kDirectShowDeviceName1 = L"Device 1";
const wchar_t* kDirectShowDeviceId3 = L"Virtual Camera 3";
const wchar_t* kDirectShowDeviceName3 = L"Virtual Camera";
const wchar_t* kDirectShowDeviceId4 = L"Virtual Camera 4";
const wchar_t* kDirectShowDeviceName4 = L"Virtual Camera";
const wchar_t* kDirectShowDeviceId5 = L"\\\\?\\usb#vid_0005&pid_0005&mi_00#5";
const wchar_t* kDirectShowDeviceName5 = L"Dazzle";
const wchar_t* kDirectShowDeviceId6 = L"\\\\?\\usb#vid_eb1a&pid_2860&mi_00";
const wchar_t* kDirectShowDeviceName6 = L"Empia Device";
void GetDirectShowSupportedFormats(const VideoCaptureDeviceDescriptor& device,
VideoCaptureFormats* formats) {
if (device.device_id == base::SysWideToUTF8(kDirectShowDeviceId5)) {
VideoCaptureFormat arbitrary_format;
formats->emplace_back(arbitrary_format);
}
}
using iterator = VideoCaptureDeviceDescriptors::const_iterator;
iterator FindDescriptorInRange(iterator begin,
iterator end,
const std::string& device_id) {
return std::find_if(
begin, end, [device_id](const VideoCaptureDeviceDescriptor& descriptor) {
return device_id == descriptor.device_id;
});
}
class MockMFActivate : public base::RefCountedThreadSafe<MockMFActivate>,
public IMFActivate {
public:
MockMFActivate(const std::wstring& symbolic_link,
const std::wstring& name,
bool kscategory_video_camera,
bool kscategory_sensor_camera)
: symbolic_link_(symbolic_link),
name_(name),
kscategory_video_camera_(kscategory_video_camera),
kscategory_sensor_camera_(kscategory_sensor_camera) {}
bool MatchesQuery(IMFAttributes* query, HRESULT* status) {
UINT32 count;
*status = query->GetCount(&count);
if (FAILED(*status))
return false;
GUID value;
*status = query->GetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &value);
if (FAILED(*status))
return false;
if (value != MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)
return false;
*status = query->GetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY,
&value);
if (SUCCEEDED(*status)) {
if ((value == KSCATEGORY_SENSOR_CAMERA && kscategory_sensor_camera_) ||
(value == KSCATEGORY_VIDEO_CAMERA && kscategory_video_camera_))
return true;
} else if (*status == MF_E_ATTRIBUTENOTFOUND) {
// When no category attribute is specified, it should behave the same as
// if KSCATEGORY_VIDEO_CAMERA is specified.
*status = S_OK;
if (kscategory_video_camera_)
return true;
}
return false;
}
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override {
return E_NOTIMPL;
}
STDMETHOD_(ULONG, AddRef)() override {
base::RefCountedThreadSafe<MockMFActivate>::AddRef();
return 1U;
}
STDMETHOD_(ULONG, Release)() override {
base::RefCountedThreadSafe<MockMFActivate>::Release();
return 1U;
}
STDMETHOD(GetItem)(REFGUID key, PROPVARIANT* value) override {
return E_FAIL;
}
STDMETHOD(GetItemType)(REFGUID guidKey, MF_ATTRIBUTE_TYPE* pType) override {
return E_NOTIMPL;
}
STDMETHOD(CompareItem)
(REFGUID guidKey, REFPROPVARIANT Value, BOOL* pbResult) override {
return E_NOTIMPL;
}
STDMETHOD(Compare)
(IMFAttributes* pTheirs,
MF_ATTRIBUTES_MATCH_TYPE MatchType,
BOOL* pbResult) override {
return E_NOTIMPL;
}
STDMETHOD(GetUINT32)(REFGUID key, UINT32* value) override {
if (key == MF_MT_INTERLACE_MODE) {
*value = MFVideoInterlace_Progressive;
return S_OK;
}
return E_NOTIMPL;
}
STDMETHOD(GetUINT64)(REFGUID key, UINT64* value) override { return E_FAIL; }
STDMETHOD(GetDouble)(REFGUID guidKey, double* pfValue) override {
return E_NOTIMPL;
}
STDMETHOD(GetGUID)(REFGUID key, GUID* value) override { return E_FAIL; }
STDMETHOD(GetStringLength)(REFGUID guidKey, UINT32* pcchLength) override {
return E_NOTIMPL;
}
STDMETHOD(GetString)
(REFGUID guidKey,
LPWSTR pwszValue,
UINT32 cchBufSize,
UINT32* pcchLength) override {
return E_NOTIMPL;
}
STDMETHOD(GetAllocatedString)
(REFGUID guidKey, LPWSTR* ppwszValue, UINT32* pcchLength) override {
std::wstring value;
if (guidKey == MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK) {
value = symbolic_link_;
} else if (guidKey == MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME) {
value = name_;
} else {
return E_NOTIMPL;
}
*ppwszValue = static_cast<wchar_t*>(
CoTaskMemAlloc((value.size() + 1) * sizeof(wchar_t)));
wcscpy(*ppwszValue, value.c_str());
*pcchLength = value.length();
return S_OK;
}
STDMETHOD(GetBlobSize)(REFGUID guidKey, UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
STDMETHOD(GetBlob)
(REFGUID guidKey,
UINT8* pBuf,
UINT32 cbBufSize,
UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
STDMETHOD(GetAllocatedBlob)
(REFGUID guidKey, UINT8** ppBuf, UINT32* pcbSize) override {
return E_NOTIMPL;
}
STDMETHOD(GetUnknown)(REFGUID guidKey, REFIID riid, LPVOID* ppv) override {
return E_NOTIMPL;
}
STDMETHOD(SetItem)(REFGUID guidKey, REFPROPVARIANT Value) override {
return E_NOTIMPL;
}
STDMETHOD(DeleteItem)(REFGUID guidKey) override { return E_NOTIMPL; }
STDMETHOD(DeleteAllItems)(void) override { return E_NOTIMPL; }
STDMETHOD(SetUINT32)(REFGUID guidKey, UINT32 unValue) override {
return E_NOTIMPL;
}
STDMETHOD(SetUINT64)(REFGUID guidKey, UINT64 unValue) override {
return E_NOTIMPL;
}
STDMETHOD(SetDouble)(REFGUID guidKey, double fValue) override {
return E_NOTIMPL;
}
STDMETHOD(SetGUID)(REFGUID guidKey, REFGUID guidValue) override {
return E_NOTIMPL;
}
STDMETHOD(SetString)(REFGUID guidKey, LPCWSTR wszValue) override {
return E_NOTIMPL;
}
STDMETHOD(SetBlob)
(REFGUID guidKey, const UINT8* pBuf, UINT32 cbBufSize) override {
return E_NOTIMPL;
}
STDMETHOD(SetUnknown)(REFGUID guidKey, IUnknown* pUnknown) override {
return E_NOTIMPL;
}
STDMETHOD(LockStore)(void) override { return E_NOTIMPL; }
STDMETHOD(UnlockStore)(void) override { return E_NOTIMPL; }
STDMETHOD(GetCount)(UINT32* pcItems) override { return E_NOTIMPL; }
STDMETHOD(GetItemByIndex)
(UINT32 unIndex, GUID* pguidKey, PROPVARIANT* pValue) override {
return E_NOTIMPL;
}
STDMETHOD(CopyAllItems)(IMFAttributes* pDest) override { return E_NOTIMPL; }
STDMETHOD(ActivateObject)(REFIID riid, void** ppv) override {
return E_NOTIMPL;
}
STDMETHOD(DetachObject)(void) override { return E_NOTIMPL; }
STDMETHOD(ShutdownObject)(void) override { return E_NOTIMPL; }
private:
friend class base::RefCountedThreadSafe<MockMFActivate>;
virtual ~MockMFActivate() = default;
const std::wstring symbolic_link_;
const std::wstring name_;
const bool kscategory_video_camera_;
const bool kscategory_sensor_camera_;
};
class StubPropertyBag : public base::RefCountedThreadSafe<StubPropertyBag>,
public IPropertyBag {
public:
StubPropertyBag(const wchar_t* device_path, const wchar_t* description)
: device_path_(device_path), description_(description) {}
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override {
return E_NOTIMPL;
}
STDMETHOD_(ULONG, AddRef)(void) override {
base::RefCountedThreadSafe<StubPropertyBag>::AddRef();
return 1U;
}
STDMETHOD_(ULONG, Release)(void) override {
base::RefCountedThreadSafe<StubPropertyBag>::Release();
return 1U;
}
STDMETHOD(Read)
(LPCOLESTR pszPropName, VARIANT* pVar, IErrorLog* pErrorLog) override {
if (pszPropName == std::wstring(L"Description")) {
pVar->vt = VT_BSTR;
pVar->bstrVal = SysAllocString(description_);
return S_OK;
}
if (pszPropName == std::wstring(L"DevicePath")) {
pVar->vt = VT_BSTR;
pVar->bstrVal = SysAllocString(device_path_);
return S_OK;
}
return E_NOTIMPL;
}
STDMETHOD(Write)(LPCOLESTR pszPropName, VARIANT* pVar) override {
return E_NOTIMPL;
}
private:
friend class base::RefCountedThreadSafe<StubPropertyBag>;
virtual ~StubPropertyBag() = default;
const wchar_t* device_path_;
const wchar_t* description_;
};
class StubMoniker : public base::RefCountedThreadSafe<StubMoniker>,
public IMoniker {
public:
StubMoniker(const wchar_t* device_path, const wchar_t* description)
: device_path_(device_path), description_(description) {}
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override {
return E_NOTIMPL;
}
STDMETHOD_(ULONG, AddRef)(void) override {
base::RefCountedThreadSafe<StubMoniker>::AddRef();
return 1U;
}
STDMETHOD_(ULONG, Release)(void) override {
base::RefCountedThreadSafe<StubMoniker>::Release();
return 1U;
}
STDMETHOD(GetClassID)(CLSID* pClassID) override { return E_NOTIMPL; }
STDMETHOD(IsDirty)(void) override { return E_NOTIMPL; }
STDMETHOD(Load)(IStream* pStm) override { return E_NOTIMPL; }
STDMETHOD(Save)(IStream* pStm, BOOL fClearDirty) override {
return E_NOTIMPL;
}
STDMETHOD(GetSizeMax)(ULARGE_INTEGER* pcbSize) override { return E_NOTIMPL; }
STDMETHOD(BindToObject)
(IBindCtx* pbc,
IMoniker* pmkToLeft,
REFIID riidResult,
void** ppvResult) override {
return E_NOTIMPL;
}
STDMETHOD(BindToStorage)
(IBindCtx* pbc, IMoniker* pmkToLeft, REFIID riid, void** ppvObj) override {
StubPropertyBag* propertyBag =
new StubPropertyBag(device_path_, description_);
propertyBag->AddRef();
*ppvObj = propertyBag;
return S_OK;
}
STDMETHOD(Reduce)
(IBindCtx* pbc,
DWORD dwReduceHowFar,
IMoniker** ppmkToLeft,
IMoniker** ppmkReduced) override {
return E_NOTIMPL;
}
STDMETHOD(ComposeWith)
(IMoniker* pmkRight,
BOOL fOnlyIfNotGeneric,
IMoniker** ppmkComposite) override {
return E_NOTIMPL;
}
STDMETHOD(Enum)(BOOL fForward, IEnumMoniker** ppenumMoniker) override {
return E_NOTIMPL;
}
STDMETHOD(IsEqual)(IMoniker* pmkOtherMoniker) override { return E_NOTIMPL; }
STDMETHOD(Hash)(DWORD* pdwHash) override { return E_NOTIMPL; }
STDMETHOD(IsRunning)
(IBindCtx* pbc, IMoniker* pmkToLeft, IMoniker* pmkNewlyRunning) override {
return E_NOTIMPL;
}
STDMETHOD(GetTimeOfLastChange)
(IBindCtx* pbc, IMoniker* pmkToLeft, FILETIME* pFileTime) override {
return E_NOTIMPL;
}
STDMETHOD(Inverse)(IMoniker** ppmk) override { return E_NOTIMPL; }
STDMETHOD(CommonPrefixWith)
(IMoniker* pmkOther, IMoniker** ppmkPrefix) override { return E_NOTIMPL; }
STDMETHOD(RelativePathTo)
(IMoniker* pmkOther, IMoniker** ppmkRelPath) override { return E_NOTIMPL; }
STDMETHOD(GetDisplayName)
(IBindCtx* pbc, IMoniker* pmkToLeft, LPOLESTR* ppszDisplayName) override {
return E_NOTIMPL;
}
STDMETHOD(ParseDisplayName)
(IBindCtx* pbc,
IMoniker* pmkToLeft,
LPOLESTR pszDisplayName,
ULONG* pchEaten,
IMoniker** ppmkOut) override {
return E_NOTIMPL;
}
STDMETHOD(IsSystemMoniker)(DWORD* pdwMksys) override { return E_NOTIMPL; }
private:
friend class base::RefCountedThreadSafe<StubMoniker>;
virtual ~StubMoniker() = default;
const wchar_t* device_path_;
const wchar_t* description_;
};
class StubEnumMoniker : public base::RefCountedThreadSafe<StubEnumMoniker>,
public IEnumMoniker {
public:
void AddMoniker(StubMoniker* moniker) { monikers_.push_back(moniker); }
STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override {
return E_NOTIMPL;
}
STDMETHOD_(ULONG, AddRef)() override {
base::RefCountedThreadSafe<StubEnumMoniker>::AddRef();
return 1U;
}
STDMETHOD_(ULONG, Release)() override {
base::RefCountedThreadSafe<StubEnumMoniker>::Release();
return 1U;
}
STDMETHOD(Next)
(ULONG celt, IMoniker** rgelt, ULONG* pceltFetched) override {
cursor_position_ = cursor_position_ + celt;
if (cursor_position_ >= monikers_.size())
return E_FAIL;
IMoniker* moniker = monikers_.at(cursor_position_);
*rgelt = moniker;
moniker->AddRef();
return S_OK;
}
STDMETHOD(Skip)(ULONG celt) override { return E_NOTIMPL; }
STDMETHOD(Reset)(void) override {
cursor_position_ = unsigned(-1);
return S_OK;
}
STDMETHOD(Clone)(IEnumMoniker** ppenum) override { return E_NOTIMPL; }
private:
friend class base::RefCountedThreadSafe<StubEnumMoniker>;
virtual ~StubEnumMoniker() = default;
std::vector<IMoniker*> monikers_;
ULONG cursor_position_ = unsigned(-1);
};
HRESULT __stdcall MockMFEnumDeviceSources(IMFAttributes* attributes,
IMFActivate*** devices,
UINT32* count) {
MockMFActivate* mock_devices[] = {
new MockMFActivate(kMFDeviceId0, kMFDeviceName0, true, false),
new MockMFActivate(kMFDeviceId1, kMFDeviceName1, true, true),
new MockMFActivate(kMFDeviceId2, kMFDeviceName2, false, true),
new MockMFActivate(kMFDeviceId5, kMFDeviceName5, true, false),
new MockMFActivate(kMFDeviceId6, kMFDeviceName6, true, false)};
// Iterate once to get the match count and check for errors.
*count = 0U;
HRESULT hr;
for (MockMFActivate* device : mock_devices) {
if (device->MatchesQuery(attributes, &hr))
(*count)++;
if (FAILED(hr))
return hr;
}
// Second iteration packs the returned devices and increments their
// reference count.
*devices = static_cast<IMFActivate**>(
CoTaskMemAlloc(sizeof(IMFActivate*) * (*count)));
int offset = 0;
for (MockMFActivate* device : mock_devices) {
if (!device->MatchesQuery(attributes, &hr))
continue;
*(*devices + offset++) = device;
device->AddRef();
}
return S_OK;
}
HRESULT EnumerateStubDirectShowDevices(IEnumMoniker** enum_moniker) {
StubMoniker* monikers[] = {
new StubMoniker(kDirectShowDeviceId0, kDirectShowDeviceName0),
new StubMoniker(kDirectShowDeviceId1, kDirectShowDeviceName1),
new StubMoniker(kDirectShowDeviceId3, kDirectShowDeviceName3),
new StubMoniker(kDirectShowDeviceId4, kDirectShowDeviceName4),
new StubMoniker(kDirectShowDeviceId5, kDirectShowDeviceName5),
new StubMoniker(kDirectShowDeviceId6, kDirectShowDeviceName6)};
StubEnumMoniker* stub_enum_moniker = new StubEnumMoniker();
for (StubMoniker* moniker : monikers)
stub_enum_moniker->AddMoniker(moniker);
stub_enum_moniker->AddRef();
*enum_moniker = stub_enum_moniker;
return S_OK;
}
} // namespace
class VideoCaptureDeviceFactoryWinTest : public ::testing::Test {
protected:
VideoCaptureDeviceFactoryWinTest()
: media_foundation_supported_(
VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation()) {}
void SetUp() override {
factory_.set_mf_get_supported_formats_func_for_testing(
base::BindRepeating(&GetMFSupportedFormats));
factory_.set_direct_show_get_supported_formats_func_for_testing(
base::BindRepeating(&GetDirectShowSupportedFormats));
}
bool ShouldSkipMFTest() {
if (media_foundation_supported_)
return false;
DVLOG(1) << "Media foundation is not supported by the current platform. "
"Skipping test.";
return true;
}
VideoCaptureDeviceFactoryWin factory_;
const bool media_foundation_supported_;
};
class VideoCaptureDeviceFactoryMFWinTest
: public VideoCaptureDeviceFactoryWinTest {
void SetUp() override {
VideoCaptureDeviceFactoryWinTest::SetUp();
factory_.set_use_media_foundation_for_testing(true);
}
};
TEST_F(VideoCaptureDeviceFactoryMFWinTest, GetDeviceDescriptors) {
if (ShouldSkipMFTest())
return;
factory_.set_mf_enum_device_sources_func_for_testing(
&MockMFEnumDeviceSources);
factory_.set_direct_show_enum_devices_func_for_testing(
base::BindRepeating(&EnumerateStubDirectShowDevices));
VideoCaptureDeviceDescriptors descriptors;
factory_.GetDeviceDescriptors(&descriptors);
EXPECT_EQ(descriptors.size(), 7U);
for (auto it = descriptors.begin(); it != descriptors.end(); it++) {
// Verify that there are no duplicates.
EXPECT_EQ(FindDescriptorInRange(descriptors.begin(), it, it->device_id),
it);
}
iterator it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kMFDeviceId0));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_MEDIA_FOUNDATION);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kMFDeviceName0));
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kMFDeviceId1));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_MEDIA_FOUNDATION);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kMFDeviceName1));
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kMFDeviceId2));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_MEDIA_FOUNDATION_SENSOR);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kMFDeviceName2));
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kDirectShowDeviceId3));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_DIRECT_SHOW);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kDirectShowDeviceName3));
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kDirectShowDeviceId4));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_DIRECT_SHOW);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kDirectShowDeviceName4));
// Devices that are listed in MediaFoundation but only report supported
// formats in DirectShow are expected to get enumerated with
// VideoCaptureApi::WIN_DIRECT_SHOW
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kDirectShowDeviceId5));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_DIRECT_SHOW);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kDirectShowDeviceName5));
// Devices that are listed in both MediaFoundation and DirectShow but are
// blacklisted for use with MediaFoundation are expected to get enumerated
// with VideoCaptureApi::WIN_DIRECT_SHOW.
it = FindDescriptorInRange(descriptors.begin(), descriptors.end(),
base::SysWideToUTF8(kDirectShowDeviceId6));
EXPECT_NE(it, descriptors.end());
EXPECT_EQ(it->capture_api, VideoCaptureApi::WIN_DIRECT_SHOW);
EXPECT_EQ(it->display_name(), base::SysWideToUTF8(kDirectShowDeviceName6));
}
} // namespace media