blob: 1c0eefb7e41ac240e68163724eda870c009adc4f [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 <mfapi.h>
#include <mferror.h>
#include <stddef.h>
#include <wincodec.h>
#include <algorithm>
#include <cmath>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/task/thread_pool.h"
#include "base/test/task_environment.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "media/base/win/mf_helpers.h"
#include "media/capture/video/win/d3d_capture_test_utils.h"
#include "media/capture/video/win/sink_filter_win.h"
#include "media/capture/video/win/video_capture_device_factory_win.h"
#include "media/capture/video/win/video_capture_device_mf_win.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::AtLeast;
using ::testing::Mock;
using Microsoft::WRL::ComPtr;
namespace media {
namespace {
constexpr int kArcSecondsInDegree = 3600;
constexpr int kHundredMicrosecondsInSecond = 10000;
constexpr long kCameraControlCurrentBase = 10;
constexpr long kCameraControlMinBase = -20;
constexpr long kCameraControlMaxBase = 20;
constexpr long kCameraControlStep = 2;
constexpr long kVideoProcAmpCurrentBase = 25;
constexpr long kVideoProcAmpMinBase = -50;
constexpr long kVideoProcAmpMaxBase = 50;
constexpr long kVideoProcAmpStep = 1;
constexpr uint32_t kMFSampleBufferLength = 1;
class MockClient : public VideoCaptureDevice::Client {
public:
void OnIncomingCapturedData(const uint8_t* data,
int length,
const VideoCaptureFormat& frame_format,
const gfx::ColorSpace& color_space,
int clockwise_rotation,
bool flip_y,
base::TimeTicks reference_time,
base::TimeDelta timestamp,
int frame_feedback_id = 0) override {}
void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
const VideoCaptureFormat& frame_format,
int clockwise_rotation,
base::TimeTicks reference_time,
base::TimeDelta timestamp,
int frame_feedback_id = 0) override {}
void OnIncomingCapturedExternalBuffer(
CapturedExternalVideoBuffer buffer,
std::vector<CapturedExternalVideoBuffer> scaled_buffers,
base::TimeTicks reference_time,
base::TimeDelta timestamp) override {}
MOCK_METHOD4(ReserveOutputBuffer,
ReserveResult(const gfx::Size&, VideoPixelFormat, int, Buffer*));
void OnIncomingCapturedBuffer(Buffer buffer,
const VideoCaptureFormat& format,
base::TimeTicks reference_,
base::TimeDelta timestamp) override {}
MOCK_METHOD7(OnIncomingCapturedBufferExt,
void(Buffer,
const VideoCaptureFormat&,
const gfx::ColorSpace&,
base::TimeTicks,
base::TimeDelta,
gfx::Rect,
const VideoFrameMetadata&));
MOCK_METHOD3(OnError,
void(VideoCaptureError,
const base::Location&,
const std::string&));
MOCK_METHOD1(OnFrameDropped, void(VideoCaptureFrameDropReason));
double GetBufferPoolUtilization() const override { return 0.0; }
MOCK_METHOD0(OnStarted, void());
};
class MockImageCaptureClient
: public base::RefCountedThreadSafe<MockImageCaptureClient> {
public:
// GMock doesn't support move-only arguments, so we use this forward method.
void DoOnGetPhotoState(mojom::PhotoStatePtr received_state) {
state = std::move(received_state);
}
MOCK_METHOD1(OnCorrectSetPhotoOptions, void(bool));
// GMock doesn't support move-only arguments, so we use this forward method.
void DoOnPhotoTaken(mojom::BlobPtr blob) {
EXPECT_TRUE(blob);
OnCorrectPhotoTaken();
}
MOCK_METHOD0(OnCorrectPhotoTaken, void(void));
mojom::PhotoStatePtr state;
private:
friend class base::RefCountedThreadSafe<MockImageCaptureClient>;
virtual ~MockImageCaptureClient() = default;
};
template <class Interface>
Interface* AddReference(Interface* object) {
DCHECK(object);
object->AddRef();
return object;
}
template <class Interface>
class MockInterface
: public base::RefCountedThreadSafe<MockInterface<Interface>>,
public Interface {
public:
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** object) override {
if (riid == __uuidof(this) || riid == __uuidof(IUnknown)) {
*object = AddReference(this);
return S_OK;
}
return E_NOINTERFACE;
}
IFACEMETHODIMP_(ULONG) AddRef() override {
base::RefCountedThreadSafe<MockInterface>::AddRef();
return 1U;
}
IFACEMETHODIMP_(ULONG) Release() override {
base::RefCountedThreadSafe<MockInterface>::Release();
return 1U;
}
protected:
friend class base::RefCountedThreadSafe<MockInterface<Interface>>;
virtual ~MockInterface() = default;
};
class MockAMCameraControl final : public MockInterface<IAMCameraControl> {
public:
IFACEMETHODIMP Get(long property, long* value, long* flags) override {
switch (property) {
case CameraControl_Pan:
case CameraControl_Tilt:
case CameraControl_Roll:
case CameraControl_Zoom:
case CameraControl_Exposure:
case CameraControl_Iris:
case CameraControl_Focus:
*value = kCameraControlCurrentBase + property;
*flags = CameraControl_Flags_Auto;
return S_OK;
default:
NOTREACHED();
return E_NOTIMPL;
}
}
IFACEMETHODIMP GetRange(long property,
long* min,
long* max,
long* step,
long* default_value,
long* caps_flags) override {
switch (property) {
case CameraControl_Pan:
case CameraControl_Tilt:
case CameraControl_Roll:
case CameraControl_Zoom:
case CameraControl_Exposure:
case CameraControl_Iris:
case CameraControl_Focus:
*min = kCameraControlMinBase + property;
*max = kCameraControlMaxBase + property;
*step = kCameraControlStep;
*default_value = (*min + *max) / 2;
*caps_flags = CameraControl_Flags_Auto | CameraControl_Flags_Manual;
return S_OK;
default:
NOTREACHED();
return E_NOTIMPL;
}
}
IFACEMETHODIMP Set(long property, long value, long flags) override {
return E_NOTIMPL;
}
protected:
~MockAMCameraControl() override = default;
};
class MockAMVideoProcAmp final : public MockInterface<IAMVideoProcAmp> {
public:
IFACEMETHODIMP Get(long property, long* value, long* flags) override {
switch (property) {
case VideoProcAmp_Brightness:
case VideoProcAmp_Contrast:
case VideoProcAmp_Hue:
case VideoProcAmp_Saturation:
case VideoProcAmp_Sharpness:
case VideoProcAmp_Gamma:
case VideoProcAmp_ColorEnable:
case VideoProcAmp_WhiteBalance:
case VideoProcAmp_BacklightCompensation:
case VideoProcAmp_Gain:
*value = kVideoProcAmpCurrentBase + property;
*flags = VideoProcAmp_Flags_Auto;
return S_OK;
default:
NOTREACHED();
return E_NOTIMPL;
}
}
IFACEMETHODIMP GetRange(long property,
long* min,
long* max,
long* step,
long* default_value,
long* caps_flags) override {
switch (property) {
case VideoProcAmp_Brightness:
case VideoProcAmp_Contrast:
case VideoProcAmp_Hue:
case VideoProcAmp_Saturation:
case VideoProcAmp_Sharpness:
case VideoProcAmp_Gamma:
case VideoProcAmp_ColorEnable:
case VideoProcAmp_WhiteBalance:
case VideoProcAmp_BacklightCompensation:
case VideoProcAmp_Gain:
*min = kVideoProcAmpMinBase + property;
*max = kVideoProcAmpMaxBase + property;
*step = kVideoProcAmpStep;
*default_value = (*min + *max) / 2;
*caps_flags = VideoProcAmp_Flags_Auto | VideoProcAmp_Flags_Manual;
return S_OK;
default:
NOTREACHED();
return E_NOTIMPL;
}
}
IFACEMETHODIMP Set(long property, long value, long flags) override {
return E_NOTIMPL;
}
protected:
~MockAMVideoProcAmp() override = default;
};
class MockMFMediaSource : public MockInterface<IMFMediaSourceEx> {
public:
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** object) override {
if (riid == __uuidof(IAMCameraControl)) {
*object = AddReference(new MockAMCameraControl);
return S_OK;
}
if (riid == __uuidof(IAMVideoProcAmp)) {
*object = AddReference(new MockAMVideoProcAmp);
return S_OK;
}
if (riid == __uuidof(IMFMediaSource)) {
*object = AddReference(static_cast<IMFMediaSource*>(this));
return S_OK;
}
return MockInterface::QueryInterface(riid, object);
}
// IMFMediaEventGenerator
IFACEMETHODIMP GetEvent(DWORD dwFlags, IMFMediaEvent** ppEvent) override {
return E_NOTIMPL;
}
IFACEMETHODIMP BeginGetEvent(IMFAsyncCallback* pCallback,
IUnknown* punkState) override {
return E_NOTIMPL;
}
IFACEMETHODIMP EndGetEvent(IMFAsyncResult* pResult,
IMFMediaEvent** ppEvent) override {
return E_NOTIMPL;
}
IFACEMETHODIMP QueueEvent(MediaEventType met,
REFGUID guidExtendedType,
HRESULT hrStatus,
const PROPVARIANT* pvValue) override {
return E_NOTIMPL;
}
// IMFMediaSource
IFACEMETHODIMP GetCharacteristics(DWORD* pdwCharacteristics) override {
return E_NOTIMPL;
}
IFACEMETHODIMP CreatePresentationDescriptor(
IMFPresentationDescriptor** ppPresentationDescriptor) override {
return E_NOTIMPL;
}
IFACEMETHODIMP Start(IMFPresentationDescriptor* pPresentationDescriptor,
const GUID* pguidTimeFormat,
const PROPVARIANT* pvarStartPosition) override {
return E_NOTIMPL;
}
IFACEMETHODIMP Stop(void) override { return E_NOTIMPL; }
IFACEMETHODIMP Pause(void) override { return E_NOTIMPL; }
IFACEMETHODIMP Shutdown(void) override { return E_NOTIMPL; }
// IMFMediaSourceEx
IFACEMETHODIMP GetSourceAttributes(IMFAttributes** attributes) {
return E_NOTIMPL;
}
IFACEMETHODIMP GetStreamAttributes(DWORD stream_id,
IMFAttributes** attributes) {
return E_NOTIMPL;
}
IFACEMETHODIMP SetD3DManager(IUnknown* manager) { return S_OK; }
private:
~MockMFMediaSource() override = default;
};
class MockMFCaptureSource : public MockInterface<IMFCaptureSource> {
public:
IFACEMETHODIMP GetCaptureDeviceSource(
MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType,
IMFMediaSource** ppMediaSource) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetCaptureDeviceActivate(
MF_CAPTURE_ENGINE_DEVICE_TYPE mfCaptureEngineDeviceType,
IMFActivate** ppActivate) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetService(REFIID rguidService,
REFIID riid,
IUnknown** ppUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP AddEffect(DWORD dwSourceStreamIndex,
IUnknown* pUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP RemoveEffect(DWORD dwSourceStreamIndex,
IUnknown* pUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP RemoveAllEffects(DWORD dwSourceStreamIndex) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetAvailableDeviceMediaType(
DWORD stream_index,
DWORD media_type_index,
IMFMediaType** media_type) override {
return DoGetAvailableDeviceMediaType(stream_index, media_type_index,
media_type);
}
MOCK_METHOD3(DoGetAvailableDeviceMediaType,
HRESULT(DWORD, DWORD, IMFMediaType**));
IFACEMETHODIMP SetCurrentDeviceMediaType(DWORD dwSourceStreamIndex,
IMFMediaType* pMediaType) override {
return DoSetCurrentDeviceMediaType(dwSourceStreamIndex, pMediaType);
}
MOCK_METHOD2(DoSetCurrentDeviceMediaType, HRESULT(DWORD, IMFMediaType*));
IFACEMETHODIMP GetCurrentDeviceMediaType(DWORD stream_index,
IMFMediaType** media_type) {
return DoGetCurrentDeviceMediaType(stream_index, media_type);
}
MOCK_METHOD2(DoGetCurrentDeviceMediaType, HRESULT(DWORD, IMFMediaType**));
IFACEMETHODIMP GetDeviceStreamCount(DWORD* count) {
return DoGetDeviceStreamCount(count);
}
MOCK_METHOD1(DoGetDeviceStreamCount, HRESULT(DWORD*));
IFACEMETHODIMP GetDeviceStreamCategory(
DWORD stream_index,
MF_CAPTURE_ENGINE_STREAM_CATEGORY* category) {
return DoGetDeviceStreamCategory(stream_index, category);
}
MOCK_METHOD2(DoGetDeviceStreamCategory,
HRESULT(DWORD, MF_CAPTURE_ENGINE_STREAM_CATEGORY*));
IFACEMETHODIMP GetMirrorState(DWORD dwStreamIndex,
BOOL* pfMirrorState) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetMirrorState(DWORD dwStreamIndex,
BOOL fMirrorState) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetStreamIndexFromFriendlyName(
UINT32 uifriendlyName,
DWORD* pdwActualStreamIndex) override {
return E_NOTIMPL;
}
private:
~MockMFCaptureSource() override = default;
};
class MockCapturePreviewSink : public MockInterface<IMFCapturePreviewSink> {
public:
IFACEMETHODIMP GetOutputMediaType(DWORD dwSinkStreamIndex,
IMFMediaType** ppMediaType) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetService(DWORD dwSinkStreamIndex,
REFGUID rguidService,
REFIID riid,
IUnknown** ppUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP AddStream(DWORD stream_index,
IMFMediaType* media_type,
IMFAttributes* attributes,
DWORD* sink_stream_index) override {
return DoAddStream(stream_index, media_type, attributes, sink_stream_index);
}
MOCK_METHOD4(DoAddStream,
HRESULT(DWORD, IMFMediaType*, IMFAttributes*, DWORD*));
IFACEMETHODIMP Prepare(void) override { return E_NOTIMPL; }
IFACEMETHODIMP RemoveAllStreams(void) override { return S_OK; }
IFACEMETHODIMP SetRenderHandle(HANDLE handle) override { return E_NOTIMPL; }
IFACEMETHODIMP SetRenderSurface(IUnknown* pSurface) override {
return E_NOTIMPL;
}
IFACEMETHODIMP UpdateVideo(const MFVideoNormalizedRect* pSrc,
const RECT* pDst,
const COLORREF* pBorderClr) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetSampleCallback(
DWORD dwStreamSinkIndex,
IMFCaptureEngineOnSampleCallback* pCallback) override {
sample_callback = pCallback;
return S_OK;
}
IFACEMETHODIMP GetMirrorState(BOOL* pfMirrorState) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetMirrorState(BOOL fMirrorState) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetRotation(DWORD dwStreamIndex,
DWORD* pdwRotationValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetRotation(DWORD dwStreamIndex,
DWORD dwRotationValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetCustomSink(IMFMediaSink* pMediaSink) override {
return E_NOTIMPL;
}
scoped_refptr<IMFCaptureEngineOnSampleCallback> sample_callback;
private:
~MockCapturePreviewSink() override = default;
};
class MockCapturePhotoSink : public MockInterface<IMFCapturePhotoSink> {
public:
IFACEMETHODIMP GetOutputMediaType(DWORD dwSinkStreamIndex,
IMFMediaType** ppMediaType) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetService(DWORD dwSinkStreamIndex,
REFGUID rguidService,
REFIID riid,
IUnknown** ppUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP AddStream(DWORD dwSourceStreamIndex,
IMFMediaType* pMediaType,
IMFAttributes* pAttributes,
DWORD* pdwSinkStreamIndex) override {
return S_OK;
}
IFACEMETHODIMP Prepare(void) override { return E_NOTIMPL; }
IFACEMETHODIMP RemoveAllStreams(void) override { return S_OK; }
IFACEMETHODIMP SetOutputFileName(LPCWSTR fileName) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetSampleCallback(
IMFCaptureEngineOnSampleCallback* pCallback) override {
sample_callback = pCallback;
return S_OK;
}
IFACEMETHODIMP SetOutputByteStream(IMFByteStream* pByteStream) override {
return E_NOTIMPL;
}
scoped_refptr<IMFCaptureEngineOnSampleCallback> sample_callback;
private:
~MockCapturePhotoSink() override = default;
};
class MockMFCaptureEngine : public MockInterface<IMFCaptureEngine> {
public:
IFACEMETHODIMP Initialize(IMFCaptureEngineOnEventCallback* pEventCallback,
IMFAttributes* pAttributes,
IUnknown* pAudioSource,
IUnknown* pVideoSource) override {
EXPECT_TRUE(pEventCallback);
EXPECT_TRUE(pAttributes);
EXPECT_TRUE(pVideoSource);
Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> device_manager;
EXPECT_EQ(SUCCEEDED(pAttributes->GetUnknown(MF_CAPTURE_ENGINE_D3D_MANAGER,
IID_PPV_ARGS(&device_manager))),
expect_mf_dxgi_device_manager_attribute_);
event_callback = pEventCallback;
OnCorrectInitializeQueued();
ON_CALL(*this, OnInitStatus).WillByDefault(Return(S_OK));
ON_CALL(*this, OnInitEventGuid)
.WillByDefault(Return(MF_CAPTURE_ENGINE_INITIALIZED));
// HW Cameras usually add about 500ms latency on init
ON_CALL(*this, InitEventDelay)
.WillByDefault(Return(base::Milliseconds(500)));
base::TimeDelta event_delay = InitEventDelay();
base::ThreadPool::PostDelayedTask(
FROM_HERE,
base::BindOnce(&MockMFCaptureEngine::FireCaptureEvent, this,
OnInitEventGuid(), OnInitStatus()),
event_delay);
// if zero is passed ensure event fires before wait starts
if (event_delay == base::Milliseconds(0)) {
base::PlatformThread::Sleep(base::Milliseconds(200));
}
return S_OK;
}
MOCK_METHOD0(OnCorrectInitializeQueued, void(void));
MOCK_METHOD0(OnInitEventGuid, GUID(void));
MOCK_METHOD0(OnInitStatus, HRESULT(void));
MOCK_METHOD0(InitEventDelay, base::TimeDelta(void));
IFACEMETHODIMP StartPreview(void) override {
OnStartPreview();
return S_OK;
}
MOCK_METHOD0(OnStartPreview, void(void));
IFACEMETHODIMP StopPreview(void) override {
OnStopPreview();
return S_OK;
}
MOCK_METHOD0(OnStopPreview, void(void));
IFACEMETHODIMP StartRecord(void) override { return E_NOTIMPL; }
IFACEMETHODIMP StopRecord(BOOL bFinalize, BOOL bFlushUnprocessedSamples) {
return E_NOTIMPL;
}
IFACEMETHODIMP TakePhoto(void) override {
OnTakePhoto();
return S_OK;
}
MOCK_METHOD0(OnTakePhoto, void(void));
IFACEMETHODIMP GetSink(MF_CAPTURE_ENGINE_SINK_TYPE type,
IMFCaptureSink** sink) {
return DoGetSink(type, sink);
}
MOCK_METHOD2(DoGetSink,
HRESULT(MF_CAPTURE_ENGINE_SINK_TYPE, IMFCaptureSink**));
IFACEMETHODIMP GetSource(IMFCaptureSource** source) {
*source = DoGetSource();
return source ? S_OK : E_FAIL;
}
MOCK_METHOD0(DoGetSource, IMFCaptureSource*());
void FireCaptureEvent(GUID event, HRESULT hrStatus) {
ComPtr<IMFMediaEvent> captureEvent;
MFCreateMediaEvent(MEExtendedType, event, hrStatus, nullptr, &captureEvent);
if (event_callback) {
event_callback->OnEvent(captureEvent.Get());
}
}
scoped_refptr<IMFCaptureEngineOnEventCallback> event_callback;
void set_expect_mf_dxgi_device_manager_attribute(bool expect) {
expect_mf_dxgi_device_manager_attribute_ = expect;
}
private:
~MockMFCaptureEngine() override = default;
bool expect_mf_dxgi_device_manager_attribute_ = false;
};
class StubMFMediaType : public MockInterface<IMFMediaType> {
public:
StubMFMediaType(GUID major_type,
GUID sub_type,
int frame_width,
int frame_height,
int frame_rate)
: major_type_(major_type),
sub_type_(sub_type),
frame_width_(frame_width),
frame_height_(frame_height),
frame_rate_(frame_rate) {}
IFACEMETHODIMP GetItem(REFGUID key, PROPVARIANT* value) override {
if (key == MF_MT_FRAME_SIZE) {
value->vt = VT_UI8;
value->uhVal.QuadPart = Pack2UINT32AsUINT64(frame_width_, frame_height_);
return S_OK;
}
if (key == MF_MT_FRAME_RATE) {
value->vt = VT_UI8;
value->uhVal.QuadPart = Pack2UINT32AsUINT64(frame_rate_, 1);
return S_OK;
}
if (key == MF_MT_PIXEL_ASPECT_RATIO) {
value->vt = VT_UI8;
value->uhVal.QuadPart = Pack2UINT32AsUINT64(1, 1);
return S_OK;
}
if (key == MF_MT_INTERLACE_MODE) {
value->vt = VT_UI4;
value->uintVal = MFVideoInterlace_Progressive;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP GetItemType(REFGUID guidKey,
MF_ATTRIBUTE_TYPE* pType) override {
return E_NOTIMPL;
}
IFACEMETHODIMP CompareItem(REFGUID guidKey,
REFPROPVARIANT Value,
BOOL* pbResult) override {
return E_NOTIMPL;
}
IFACEMETHODIMP Compare(IMFAttributes* pTheirs,
MF_ATTRIBUTES_MATCH_TYPE MatchType,
BOOL* pbResult) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetUINT32(REFGUID key, UINT32* value) override {
if (key == MF_MT_INTERLACE_MODE) {
*value = MFVideoInterlace_Progressive;
return S_OK;
}
return E_NOTIMPL;
}
IFACEMETHODIMP GetUINT64(REFGUID key, UINT64* value) override {
if (key == MF_MT_FRAME_SIZE) {
*value = (long long)frame_width_ << 32 | frame_height_;
return S_OK;
}
if (key == MF_MT_FRAME_RATE) {
*value = (long long)frame_rate_ << 32 | 1;
return S_OK;
}
if (key == MF_MT_PIXEL_ASPECT_RATIO) {
*value = (long long)1 << 32 | 1;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP GetDouble(REFGUID guidKey, double* pfValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetGUID(REFGUID key, GUID* value) override {
if (key == MF_MT_MAJOR_TYPE) {
*value = major_type_;
return S_OK;
}
if (key == MF_MT_SUBTYPE) {
*value = sub_type_;
return S_OK;
}
return E_FAIL;
}
IFACEMETHODIMP GetStringLength(REFGUID guidKey, UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetString(REFGUID guidKey,
LPWSTR pwszValue,
UINT32 cchBufSize,
UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetAllocatedString(REFGUID guidKey,
LPWSTR* ppwszValue,
UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetBlobSize(REFGUID guidKey, UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetBlob(REFGUID guidKey,
UINT8* pBuf,
UINT32 cbBufSize,
UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetAllocatedBlob(REFGUID guidKey,
UINT8** ppBuf,
UINT32* pcbSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetUnknown(REFGUID guidKey,
REFIID riid,
LPVOID* ppv) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT Value) override {
return E_NOTIMPL;
}
IFACEMETHODIMP DeleteItem(REFGUID guidKey) override { return E_NOTIMPL; }
IFACEMETHODIMP DeleteAllItems(void) override { return E_NOTIMPL; }
IFACEMETHODIMP SetUINT32(REFGUID guidKey, UINT32 unValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetUINT64(REFGUID guidKey, UINT64 unValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetDouble(REFGUID guidKey, double fValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetGUID(REFGUID guidKey, REFGUID guidValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetString(REFGUID guidKey, LPCWSTR wszValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetBlob(REFGUID guidKey,
const UINT8* pBuf,
UINT32 cbBufSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetUnknown(REFGUID guidKey, IUnknown* pUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP LockStore(void) override { return E_NOTIMPL; }
IFACEMETHODIMP UnlockStore(void) override { return E_NOTIMPL; }
IFACEMETHODIMP GetCount(UINT32* pcItems) override { return E_NOTIMPL; }
IFACEMETHODIMP GetItemByIndex(UINT32 unIndex,
GUID* pguidKey,
PROPVARIANT* pValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP CopyAllItems(IMFAttributes* pDest) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetMajorType(GUID* pguidMajorType) override {
return E_NOTIMPL;
}
IFACEMETHODIMP IsCompressedFormat(BOOL* pfCompressed) override {
return E_NOTIMPL;
}
IFACEMETHODIMP IsEqual(IMFMediaType* pIMediaType, DWORD* pdwFlags) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetRepresentation(GUID guidRepresentation,
LPVOID* ppvRepresentation) override {
return E_NOTIMPL;
}
IFACEMETHODIMP FreeRepresentation(GUID guidRepresentation,
LPVOID pvRepresentation) override {
return E_NOTIMPL;
}
private:
~StubMFMediaType() override = default;
const GUID major_type_;
const GUID sub_type_;
const int frame_width_;
const int frame_height_;
const int frame_rate_;
};
class MockMFMediaEvent : public MockInterface<IMFMediaEvent> {
public:
IFACEMETHODIMP GetItem(REFGUID guidKey, PROPVARIANT* pValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetItemType(REFGUID guidKey,
MF_ATTRIBUTE_TYPE* pType) override {
return E_NOTIMPL;
}
IFACEMETHODIMP CompareItem(REFGUID guidKey,
REFPROPVARIANT Value,
BOOL* pbResult) override {
return E_NOTIMPL;
}
IFACEMETHODIMP Compare(IMFAttributes* pTheirs,
MF_ATTRIBUTES_MATCH_TYPE MatchType,
BOOL* pbResult) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetUINT32(REFGUID guidKey, UINT32* punValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetUINT64(REFGUID guidKey, UINT64* punValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetDouble(REFGUID guidKey, double* pfValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetGUID(REFGUID guidKey, GUID* pguidValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetStringLength(REFGUID guidKey, UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetString(REFGUID guidKey,
LPWSTR pwszValue,
UINT32 cchBufSize,
UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetAllocatedString(REFGUID guidKey,
LPWSTR* ppwszValue,
UINT32* pcchLength) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetBlobSize(REFGUID guidKey, UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetBlob(REFGUID guidKey,
UINT8* pBuf,
UINT32 cbBufSize,
UINT32* pcbBlobSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetAllocatedBlob(REFGUID guidKey,
UINT8** ppBuf,
UINT32* pcbSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetUnknown(REFGUID guidKey,
REFIID riid,
LPVOID* ppv) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT Value) override {
return E_NOTIMPL;
}
IFACEMETHODIMP DeleteItem(REFGUID guidKey) override { return E_NOTIMPL; }
IFACEMETHODIMP DeleteAllItems(void) override { return E_NOTIMPL; }
IFACEMETHODIMP SetUINT32(REFGUID guidKey, UINT32 unValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetUINT64(REFGUID guidKey, UINT64 unValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetDouble(REFGUID guidKey, double fValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetGUID(REFGUID guidKey, REFGUID guidValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetString(REFGUID guidKey, LPCWSTR wszValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetBlob(REFGUID guidKey,
const UINT8* pBuf,
UINT32 cbBufSize) override {
return E_NOTIMPL;
}
IFACEMETHODIMP SetUnknown(REFGUID guidKey, IUnknown* pUnknown) override {
return E_NOTIMPL;
}
IFACEMETHODIMP LockStore(void) override { return E_NOTIMPL; }
IFACEMETHODIMP UnlockStore(void) override { return E_NOTIMPL; }
IFACEMETHODIMP GetCount(UINT32* pcItems) override { return E_NOTIMPL; }
IFACEMETHODIMP GetItemByIndex(UINT32 unIndex,
GUID* pguidKey,
PROPVARIANT* pValue) override {
return E_NOTIMPL;
}
IFACEMETHODIMP CopyAllItems(IMFAttributes* pDest) override {
return E_NOTIMPL;
}
IFACEMETHODIMP GetType(MediaEventType* pmet) override {
*pmet = DoGetType();
return S_OK;
}
MOCK_METHOD0(DoGetType, MediaEventType());
IFACEMETHODIMP GetExtendedType(GUID* pguidExtendedType) override {
*pguidExtendedType = DoGetExtendedType();
return S_OK;
}
MOCK_METHOD0(DoGetExtendedType, GUID());
IFACEMETHODIMP GetStatus(HRESULT* status) override {
*status = DoGetStatus();
return S_OK;
}
MOCK_METHOD0(DoGetStatus, HRESULT());
IFACEMETHODIMP GetValue(PROPVARIANT* pvValue) override { return E_NOTIMPL; }
private:
~MockMFMediaEvent() override = default;
};
struct DepthDeviceParams {
GUID depth_video_stream_subtype;
// Depth stream could offer other (e.g. I420) formats, in addition to 16-bit.
bool additional_i420_formats_in_depth_stream;
// Depth device sometimes provides multiple video streams.
bool additional_i420_video_stream;
};
class MockCaptureHandleProvider
: public VideoCaptureDevice::Client::Buffer::HandleProvider {
public:
// Duplicate as an writable (unsafe) shared memory region.
base::UnsafeSharedMemoryRegion DuplicateAsUnsafeRegion() override {
return base::UnsafeSharedMemoryRegion();
}
// Duplicate as a writable (unsafe) mojo buffer.
mojo::ScopedSharedBufferHandle DuplicateAsMojoBuffer() override {
return mojo::ScopedSharedBufferHandle();
}
// Access a |VideoCaptureBufferHandle| for local, writable memory.
std::unique_ptr<VideoCaptureBufferHandle> GetHandleForInProcessAccess()
override {
return nullptr;
}
// Clone a |GpuMemoryBufferHandle| for IPC.
gfx::GpuMemoryBufferHandle GetGpuMemoryBufferHandle() override {
// Create a fake DXGI buffer handle
// (ensure that the fake is still a valid NT handle by using an event
// handle)
base::win::ScopedHandle fake_dxgi_handle(
CreateEvent(nullptr, FALSE, FALSE, nullptr));
gfx::GpuMemoryBufferHandle handle;
handle.type = gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE;
handle.dxgi_handle = std::move(fake_dxgi_handle);
return handle;
}
};
} // namespace
const int kArbitraryValidVideoWidth = 1920;
const int kArbitraryValidVideoHeight = 1080;
const int kArbitraryValidPhotoWidth = 3264;
const int kArbitraryValidPhotoHeight = 2448;
class VideoCaptureDeviceMFWinTest : public ::testing::Test {
protected:
VideoCaptureDeviceMFWinTest()
: descriptor_(VideoCaptureDeviceDescriptor()),
media_source_(new MockMFMediaSource()),
engine_(new MockMFCaptureEngine()),
client_(new MockClient()),
image_capture_client_(new MockImageCaptureClient()),
device_(new VideoCaptureDeviceMFWin(descriptor_,
media_source_,
nullptr,
engine_)),
capture_source_(new MockMFCaptureSource()),
capture_preview_sink_(new MockCapturePreviewSink()),
media_foundation_supported_(
VideoCaptureDeviceFactoryWin::PlatformSupportsMediaFoundation()) {}
void SetUp() override {
if (!media_foundation_supported_)
return;
device_->set_max_retry_count_for_testing(3);
device_->set_retry_delay_in_ms_for_testing(1);
device_->set_dxgi_device_manager_for_testing(dxgi_device_manager_);
engine_->set_expect_mf_dxgi_device_manager_attribute(dxgi_device_manager_ !=
nullptr);
EXPECT_CALL(*(engine_.Get()), OnCorrectInitializeQueued());
EXPECT_TRUE(device_->Init());
EXPECT_CALL(*(engine_.Get()), DoGetSource())
.WillRepeatedly(Invoke([this]() {
this->capture_source_->AddRef();
return this->capture_source_.get();
}));
}
bool ShouldSkipTest() {
if (media_foundation_supported_)
return false;
DVLOG(1) << "Media foundation is not supported by the current platform. "
"Skipping test.";
return true;
}
bool ShouldSkipD3D11Test() {
// D3D11 is only supported with Media Foundation on Windows 8 or later
if (base::win::GetVersion() >= base::win::Version::WIN8)
return false;
DVLOG(1) << "D3D11 with Media foundation is not supported by the current "
"platform. "
"Skipping test.";
return true;
}
void PrepareMFDeviceWithOneVideoStream(GUID mf_video_subtype) {
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCount(_))
.WillRepeatedly(Invoke([](DWORD* stream_count) {
*stream_count = 1;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCategory(0, _))
.WillRepeatedly(Invoke([](DWORD stream_index,
MF_CAPTURE_ENGINE_STREAM_CATEGORY* category) {
*category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetAvailableDeviceMediaType(0, _, _))
.WillRepeatedly(Invoke([mf_video_subtype](DWORD stream_index,
DWORD media_type_index,
IMFMediaType** media_type) {
if (media_type_index != 0)
return MF_E_NO_MORE_TYPES;
*media_type = new StubMFMediaType(MFMediaType_Video, mf_video_subtype,
kArbitraryValidVideoWidth,
kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
}));
EXPECT_CALL(*(engine_.Get()),
DoGetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _))
.WillRepeatedly(Invoke([this](MF_CAPTURE_ENGINE_SINK_TYPE sink_type,
IMFCaptureSink** sink) {
*sink = this->capture_preview_sink_.get();
this->capture_preview_sink_->AddRef();
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetCurrentDeviceMediaType(_, _))
.WillRepeatedly(Invoke([mf_video_subtype](DWORD stream_index,
IMFMediaType** media_type) {
*media_type = new StubMFMediaType(MFMediaType_Video, mf_video_subtype,
kArbitraryValidVideoWidth,
kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
}));
}
void PrepareMFDeviceWithOneVideoStreamAndOnePhotoStream(
GUID mf_video_subtype) {
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCount(_))
.WillRepeatedly(Invoke([](DWORD* stream_count) {
*stream_count = 2;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCategory(_, _))
.WillRepeatedly(Invoke([](DWORD stream_index,
MF_CAPTURE_ENGINE_STREAM_CATEGORY* category) {
if (stream_index == 0) {
*category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW;
return S_OK;
} else if (stream_index == 1) {
*category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_PHOTO_INDEPENDENT;
return S_OK;
}
return E_FAIL;
}));
auto get_device_media_type = [mf_video_subtype](DWORD stream_index,
IMFMediaType** media_type) {
if (stream_index == 0) {
*media_type = new StubMFMediaType(MFMediaType_Video, mf_video_subtype,
kArbitraryValidVideoWidth,
kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
} else if (stream_index == 1) {
*media_type = new StubMFMediaType(
MFMediaType_Image, GUID_ContainerFormatJpeg,
kArbitraryValidPhotoWidth, kArbitraryValidPhotoHeight, 0);
(*media_type)->AddRef();
return S_OK;
}
return E_FAIL;
};
EXPECT_CALL(*capture_source_, DoGetAvailableDeviceMediaType(_, _, _))
.WillRepeatedly(Invoke(
[get_device_media_type](DWORD stream_index, DWORD media_type_index,
IMFMediaType** media_type) {
if (media_type_index != 0)
return MF_E_NO_MORE_TYPES;
return get_device_media_type(stream_index, media_type);
}));
EXPECT_CALL(*(engine_.Get()), DoGetSink(_, _))
.WillRepeatedly(Invoke([this](MF_CAPTURE_ENGINE_SINK_TYPE sink_type,
IMFCaptureSink** sink) {
if (sink_type == MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW) {
*sink = this->capture_preview_sink_.get();
this->capture_preview_sink_->AddRef();
return S_OK;
} else if (sink_type == MF_CAPTURE_ENGINE_SINK_TYPE_PHOTO) {
*sink = new MockCapturePhotoSink();
(*sink)->AddRef();
return S_OK;
}
return E_FAIL;
}));
EXPECT_CALL(*capture_source_, DoGetCurrentDeviceMediaType(_, _))
.WillRepeatedly(Invoke(get_device_media_type));
}
void PrepareMFDepthDeviceWithCombinedFormatsAndStreams(
DepthDeviceParams params) {
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCount(_))
.WillRepeatedly(Invoke([params](DWORD* stream_count) {
*stream_count = params.additional_i420_video_stream ? 2 : 1;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCategory(_, _))
.WillRepeatedly(Invoke([](DWORD stream_index,
MF_CAPTURE_ENGINE_STREAM_CATEGORY* category) {
if (stream_index <= 1) {
*category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW;
return S_OK;
}
return E_FAIL;
}));
auto get_device_media_type = [params](DWORD stream_index,
IMFMediaType** media_type) {
if (stream_index == 0) {
*media_type = new StubMFMediaType(
MFMediaType_Video, params.depth_video_stream_subtype,
kArbitraryValidVideoWidth, kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
} else if (stream_index == 1 && params.additional_i420_video_stream) {
*media_type = new StubMFMediaType(MFMediaType_Video, MFVideoFormat_I420,
kArbitraryValidVideoWidth,
kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
}
return E_FAIL;
};
EXPECT_CALL(*capture_source_, DoGetAvailableDeviceMediaType(_, _, _))
.WillRepeatedly(Invoke([params, get_device_media_type](
DWORD stream_index, DWORD media_type_index,
IMFMediaType** media_type) {
if (stream_index == 0 &&
params.additional_i420_formats_in_depth_stream &&
media_type_index == 1) {
*media_type = new StubMFMediaType(
MFMediaType_Video, MFVideoFormat_I420,
kArbitraryValidVideoWidth, kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
}
if (media_type_index != 0)
return MF_E_NO_MORE_TYPES;
return get_device_media_type(stream_index, media_type);
}));
EXPECT_CALL(*(engine_.Get()),
DoGetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _))
.WillRepeatedly(Invoke([this](MF_CAPTURE_ENGINE_SINK_TYPE sink_type,
IMFCaptureSink** sink) {
*sink = this->capture_preview_sink_.get();
this->capture_preview_sink_->AddRef();
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetCurrentDeviceMediaType(_, _))
.WillRepeatedly(Invoke(get_device_media_type));
}
VideoCaptureDeviceDescriptor descriptor_;
Microsoft::WRL::ComPtr<MockMFMediaSource> media_source_;
Microsoft::WRL::ComPtr<MockMFCaptureEngine> engine_;
std::unique_ptr<MockClient> client_;
scoped_refptr<MockImageCaptureClient> image_capture_client_;
std::unique_ptr<VideoCaptureDeviceMFWin> device_;
VideoCaptureFormat last_format_;
scoped_refptr<MockMFCaptureSource> capture_source_;
scoped_refptr<MockCapturePreviewSink> capture_preview_sink_;
base::test::TaskEnvironment task_environment_;
scoped_refptr<DXGIDeviceManager> dxgi_device_manager_;
private:
const bool media_foundation_supported_;
};
// Expects StartPreview() to be called on AllocateAndStart()
TEST_F(VideoCaptureDeviceMFWinTest, StartPreviewOnAllocateAndStart) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
EXPECT_CALL(*(engine_.Get()), OnStopPreview());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
device_->StopAndDeAllocate();
}
// Expects device's |camera_rotation_| to be populated after first OnSample().
TEST_F(VideoCaptureDeviceMFWinTest, PopulateCameraRotationOnSample) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
// Create a valid IMFSample to use with the callback.
Microsoft::WRL::ComPtr<IMFSample> test_sample =
CreateEmptySampleWithBuffer(kMFSampleBufferLength, 0);
capture_preview_sink_->sample_callback->OnSample(test_sample.Get());
EXPECT_TRUE(device_->camera_rotation().has_value());
}
// Expects OnError() to be called on an errored IMFMediaEvent
TEST_F(VideoCaptureDeviceMFWinTest, CallClientOnErrorMediaEvent) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
EXPECT_CALL(*client_, OnError(_, _, _));
scoped_refptr<MockMFMediaEvent> media_event_error = new MockMFMediaEvent();
EXPECT_CALL(*media_event_error, DoGetStatus()).WillRepeatedly(Return(E_FAIL));
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
engine_->event_callback->OnEvent(media_event_error.get());
}
// Expects Init to fail due to OnError() event
TEST_F(VideoCaptureDeviceMFWinTest, CallClientOnErrorDurringInit) {
if (ShouldSkipTest())
return;
VideoCaptureDeviceDescriptor descriptor = VideoCaptureDeviceDescriptor();
Microsoft::WRL::ComPtr<MockMFMediaSource> media_source =
new MockMFMediaSource();
Microsoft::WRL::ComPtr<MockMFCaptureEngine> engine =
new MockMFCaptureEngine();
std::unique_ptr<VideoCaptureDeviceMFWin> device =
std::make_unique<VideoCaptureDeviceMFWin>(
descriptor, media_source,
/*mf_dxgi_device_manager=*/nullptr, engine);
EXPECT_CALL(*(engine.Get()), OnInitEventGuid).WillOnce([]() {
return MF_CAPTURE_ENGINE_INITIALIZED;
});
// E_ACCESSDENIED is thrown if application is denied access in settings UI
EXPECT_CALL(*(engine.Get()), OnInitStatus).WillOnce([]() {
return E_ACCESSDENIED;
});
EXPECT_CALL(*(engine.Get()), OnCorrectInitializeQueued());
EXPECT_FALSE(device->Init());
}
// Expects Init to succeed but MF_CAPTURE_ENGINE_INITIALIZED fired before
// WaitOnCaptureEvent is called.
TEST_F(VideoCaptureDeviceMFWinTest, CallClientOnFireCaptureEngineInitEarly) {
if (ShouldSkipTest())
return;
VideoCaptureDeviceDescriptor descriptor = VideoCaptureDeviceDescriptor();
Microsoft::WRL::ComPtr<MockMFMediaSource> media_source =
new MockMFMediaSource();
Microsoft::WRL::ComPtr<MockMFCaptureEngine> engine =
new MockMFCaptureEngine();
std::unique_ptr<VideoCaptureDeviceMFWin> device =
std::make_unique<VideoCaptureDeviceMFWin>(
descriptor, media_source,
/*mf_dxgi_device_manager=*/nullptr, engine);
EXPECT_CALL(*(engine.Get()), OnInitEventGuid).WillOnce([]() {
return MF_CAPTURE_ENGINE_INITIALIZED;
});
EXPECT_CALL(*(engine.Get()), InitEventDelay).WillOnce([]() {
return base::Milliseconds(0);
});
EXPECT_CALL(*(engine.Get()), OnCorrectInitializeQueued());
EXPECT_TRUE(device->Init());
}
// Send MFVideoCallback::OnEvent when VideoCaptureDeviceMFWin has been destroyed
TEST_F(VideoCaptureDeviceMFWinTest,
SendMFVideoCallbackAfterVideoCaptureDeviceMFWinDestructor) {
if (ShouldSkipTest())
return;
VideoCaptureDeviceDescriptor descriptor = VideoCaptureDeviceDescriptor();
Microsoft::WRL::ComPtr<MockMFMediaSource> media_source =
new MockMFMediaSource();
Microsoft::WRL::ComPtr<MockMFCaptureEngine> engine =
new MockMFCaptureEngine();
std::unique_ptr<VideoCaptureDeviceMFWin> device =
std::make_unique<VideoCaptureDeviceMFWin>(
descriptor, media_source,
/*mf_dxgi_device_manager=*/nullptr, engine);
EXPECT_CALL(*(engine.Get()), OnInitEventGuid).WillOnce([]() {
return MF_CAPTURE_ENGINE_INITIALIZED;
});
EXPECT_CALL(*(engine.Get()), OnCorrectInitializeQueued());
EXPECT_TRUE(device->Init());
// Force ~VideoCaptureDeviceMFWin() which will invalidate
// MFVideoCallback::observer_
device.reset();
// Send event to MFVideoCallback::OnEvent
engine->FireCaptureEvent(MF_CAPTURE_ENGINE_ERROR,
MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED);
}
// Allocates device with flaky methods failing with MF_E_INVALIDREQUEST and
// expects the device to retry and start correctly
TEST_F(VideoCaptureDeviceMFWinTest, AllocateAndStartWithFlakyInvalidRequest) {
if (ShouldSkipTest())
return;
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCount(_))
.Times(AtLeast(2))
.WillOnce(Return(MF_E_INVALIDREQUEST))
.WillRepeatedly(Invoke([](DWORD* stream_count) {
*stream_count = 1;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCategory(0, _))
.Times(AtLeast(2))
.WillOnce(Return(MF_E_INVALIDREQUEST))
.WillRepeatedly(Invoke(
[](DWORD stream_index, MF_CAPTURE_ENGINE_STREAM_CATEGORY* category) {
*category = MF_CAPTURE_ENGINE_STREAM_CATEGORY_VIDEO_PREVIEW;
return S_OK;
}));
EXPECT_CALL(*capture_source_, DoGetAvailableDeviceMediaType(0, _, _))
.Times(AtLeast(2))
.WillOnce(Return(MF_E_INVALIDREQUEST))
.WillRepeatedly(Invoke([](DWORD stream_index, DWORD media_type_index,
IMFMediaType** media_type) {
if (media_type_index != 0)
return MF_E_NO_MORE_TYPES;
*media_type = new StubMFMediaType(MFMediaType_Video, MFVideoFormat_MJPG,
kArbitraryValidVideoWidth,
kArbitraryValidVideoHeight, 30);
(*media_type)->AddRef();
return S_OK;
}));
auto mock_sink = base::MakeRefCounted<MockCapturePreviewSink>();
EXPECT_CALL(*(engine_.Get()),
DoGetSink(MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, _))
.WillRepeatedly(Invoke([&mock_sink](MF_CAPTURE_ENGINE_SINK_TYPE sink_type,
IMFCaptureSink** sink) {
*sink = mock_sink.get();
(*sink)->AddRef();
return S_OK;
}));
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
mock_sink->sample_callback->OnSample(nullptr);
}
// Allocates device with methods always failing with MF_E_INVALIDREQUEST and
// expects the device to give up and call OnError()
TEST_F(VideoCaptureDeviceMFWinTest, AllocateAndStartWithFailingInvalidRequest) {
if (ShouldSkipTest())
return;
EXPECT_CALL(*capture_source_, DoGetDeviceStreamCount(_))
.WillRepeatedly(Return(MF_E_INVALIDREQUEST));
EXPECT_CALL(*client_, OnError(_, _, _));
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
}
TEST_F(VideoCaptureDeviceMFWinTest,
SendsOnErrorWithoutOnStartedIfDeviceIsBusy) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted()).Times(0);
EXPECT_CALL(*client_, OnError(_, _, _));
scoped_refptr<MockMFMediaEvent> media_event_preview_started =
new MockMFMediaEvent();
ON_CALL(*media_event_preview_started, DoGetStatus())
.WillByDefault(Return(S_OK));
ON_CALL(*media_event_preview_started, DoGetType())
.WillByDefault(Return(MEExtendedType));
ON_CALL(*media_event_preview_started, DoGetExtendedType())
.WillByDefault(Return(MF_CAPTURE_ENGINE_PREVIEW_STARTED));
scoped_refptr<MockMFMediaEvent> media_event_error = new MockMFMediaEvent();
EXPECT_CALL(*media_event_error, DoGetStatus()).WillRepeatedly(Return(E_FAIL));
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
// Even if the device is busy, MediaFoundation sends
// MF_CAPTURE_ENGINE_PREVIEW_STARTED before sending an error event.
engine_->event_callback->OnEvent(media_event_preview_started.get());
engine_->event_callback->OnEvent(media_event_error.get());
}
// Given an |IMFCaptureSource| offering a video stream without photo stream to
// |VideoCaptureDevice|, when asking the photo state from |VideoCaptureDevice|
// then expect the returned state to match the video resolution
TEST_F(VideoCaptureDeviceMFWinTest, GetPhotoStateViaVideoStream) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
VideoCaptureDevice::GetPhotoStateCallback get_photo_state_callback =
base::BindOnce(&MockImageCaptureClient::DoOnGetPhotoState,
image_capture_client_);
device_->GetPhotoState(std::move(get_photo_state_callback));
mojom::PhotoState* state = image_capture_client_->state.get();
EXPECT_EQ(state->width->min, kArbitraryValidVideoWidth);
EXPECT_EQ(state->width->current, kArbitraryValidVideoWidth);
EXPECT_EQ(state->width->max, kArbitraryValidVideoWidth);
EXPECT_EQ(state->height->min, kArbitraryValidVideoHeight);
EXPECT_EQ(state->height->current, kArbitraryValidVideoHeight);
EXPECT_EQ(state->height->max, kArbitraryValidVideoHeight);
}
// Given an |IMFCaptureSource| offering a video stream and a photo stream to
// |VideoCaptureDevice|, when asking the photo state from |VideoCaptureDevice|
// then expect the returned state to match the available photo resolution
TEST_F(VideoCaptureDeviceMFWinTest, GetPhotoStateViaPhotoStream) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStreamAndOnePhotoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
VideoCaptureDevice::GetPhotoStateCallback get_photo_state_callback =
base::BindOnce(&MockImageCaptureClient::DoOnGetPhotoState,
image_capture_client_);
device_->GetPhotoState(std::move(get_photo_state_callback));
mojom::PhotoState* state = image_capture_client_->state.get();
EXPECT_EQ(state->width->min, kArbitraryValidPhotoWidth);
EXPECT_EQ(state->width->current, kArbitraryValidPhotoWidth);
EXPECT_EQ(state->width->max, kArbitraryValidPhotoWidth);
EXPECT_EQ(state->height->min, kArbitraryValidPhotoHeight);
EXPECT_EQ(state->height->current, kArbitraryValidPhotoHeight);
EXPECT_EQ(state->height->max, kArbitraryValidPhotoHeight);
EXPECT_EQ(state->supported_white_balance_modes.size(), 2u);
EXPECT_EQ(std::count(state->supported_white_balance_modes.begin(),
state->supported_white_balance_modes.end(),
mojom::MeteringMode::CONTINUOUS),
1);
EXPECT_EQ(std::count(state->supported_white_balance_modes.begin(),
state->supported_white_balance_modes.end(),
mojom::MeteringMode::MANUAL),
1);
EXPECT_EQ(state->current_white_balance_mode, mojom::MeteringMode::CONTINUOUS);
EXPECT_EQ(state->supported_exposure_modes.size(), 2u);
EXPECT_EQ(std::count(state->supported_exposure_modes.begin(),
state->supported_exposure_modes.end(),
mojom::MeteringMode::CONTINUOUS),
1);
EXPECT_EQ(std::count(state->supported_exposure_modes.begin(),
state->supported_exposure_modes.end(),
mojom::MeteringMode::MANUAL),
1);
EXPECT_EQ(state->current_exposure_mode, mojom::MeteringMode::CONTINUOUS);
EXPECT_EQ(state->supported_focus_modes.size(), 2u);
EXPECT_EQ(std::count(state->supported_focus_modes.begin(),
state->supported_focus_modes.end(),
mojom::MeteringMode::CONTINUOUS),
1);
EXPECT_EQ(std::count(state->supported_focus_modes.begin(),
state->supported_focus_modes.end(),
mojom::MeteringMode::MANUAL),
1);
EXPECT_EQ(state->current_focus_mode, mojom::MeteringMode::CONTINUOUS);
EXPECT_EQ(state->points_of_interest.size(), 0u);
EXPECT_EQ(state->exposure_compensation->current,
kVideoProcAmpCurrentBase + VideoProcAmp_Gain);
EXPECT_EQ(state->exposure_compensation->min,
kVideoProcAmpMinBase + VideoProcAmp_Gain);
EXPECT_EQ(state->exposure_compensation->max,
kVideoProcAmpMaxBase + VideoProcAmp_Gain);
EXPECT_EQ(state->exposure_compensation->step, kVideoProcAmpStep);
EXPECT_DOUBLE_EQ(
std::log2(state->exposure_time->current / kHundredMicrosecondsInSecond),
kCameraControlCurrentBase + CameraControl_Exposure);
EXPECT_DOUBLE_EQ(
std::log2(state->exposure_time->min / kHundredMicrosecondsInSecond),
kCameraControlMinBase + CameraControl_Exposure);
EXPECT_DOUBLE_EQ(
std::log2(state->exposure_time->max / kHundredMicrosecondsInSecond),
kCameraControlMaxBase + CameraControl_Exposure);
EXPECT_DOUBLE_EQ(state->exposure_time->step,
std::exp2(kCameraControlStep) * state->exposure_time->min -
state->exposure_time->min);
EXPECT_EQ(state->color_temperature->current,
kVideoProcAmpCurrentBase + VideoProcAmp_WhiteBalance);
EXPECT_EQ(state->color_temperature->min,
kVideoProcAmpMinBase + VideoProcAmp_WhiteBalance);
EXPECT_EQ(state->color_temperature->max,
kVideoProcAmpMaxBase + VideoProcAmp_WhiteBalance);
EXPECT_EQ(state->color_temperature->step, kVideoProcAmpStep);
EXPECT_EQ(state->iso->min, state->iso->max);
EXPECT_EQ(state->brightness->current,
kVideoProcAmpCurrentBase + VideoProcAmp_Brightness);
EXPECT_EQ(state->brightness->min,
kVideoProcAmpMinBase + VideoProcAmp_Brightness);
EXPECT_EQ(state->brightness->max,
kVideoProcAmpMaxBase + VideoProcAmp_Brightness);
EXPECT_EQ(state->brightness->step, kVideoProcAmpStep);
EXPECT_EQ(state->contrast->current,
kVideoProcAmpCurrentBase + VideoProcAmp_Contrast);
EXPECT_EQ(state->contrast->min, kVideoProcAmpMinBase + VideoProcAmp_Contrast);
EXPECT_EQ(state->contrast->max, kVideoProcAmpMaxBase + VideoProcAmp_Contrast);
EXPECT_EQ(state->contrast->step, kVideoProcAmpStep);
EXPECT_EQ(state->saturation->current,
kVideoProcAmpCurrentBase + VideoProcAmp_Saturation);
EXPECT_EQ(state->saturation->min,
kVideoProcAmpMinBase + VideoProcAmp_Saturation);
EXPECT_EQ(state->saturation->max,
kVideoProcAmpMaxBase + VideoProcAmp_Saturation);
EXPECT_EQ(state->saturation->step, kVideoProcAmpStep);
EXPECT_EQ(state->sharpness->current,
kVideoProcAmpCurrentBase + VideoProcAmp_Sharpness);
EXPECT_EQ(state->sharpness->min,
kVideoProcAmpMinBase + VideoProcAmp_Sharpness);
EXPECT_EQ(state->sharpness->max,
kVideoProcAmpMaxBase + VideoProcAmp_Sharpness);
EXPECT_EQ(state->sharpness->step, kVideoProcAmpStep);
EXPECT_EQ(state->focus_distance->current,
kCameraControlCurrentBase + CameraControl_Focus);
EXPECT_EQ(state->focus_distance->min,
kCameraControlMinBase + CameraControl_Focus);
EXPECT_EQ(state->focus_distance->max,
kCameraControlMaxBase + CameraControl_Focus);
EXPECT_EQ(state->focus_distance->step, kCameraControlStep);
EXPECT_DOUBLE_EQ(state->pan->current / kArcSecondsInDegree,
kCameraControlCurrentBase + CameraControl_Pan);
EXPECT_DOUBLE_EQ(state->pan->min / kArcSecondsInDegree,
kCameraControlMinBase + CameraControl_Pan);
EXPECT_DOUBLE_EQ(state->pan->max / kArcSecondsInDegree,
kCameraControlMaxBase + CameraControl_Pan);
EXPECT_DOUBLE_EQ(state->pan->step / kArcSecondsInDegree, kCameraControlStep);
EXPECT_DOUBLE_EQ(state->tilt->current / kArcSecondsInDegree,
kCameraControlCurrentBase + CameraControl_Tilt);
EXPECT_DOUBLE_EQ(state->tilt->min / kArcSecondsInDegree,
kCameraControlMinBase + CameraControl_Tilt);
EXPECT_DOUBLE_EQ(state->tilt->max / kArcSecondsInDegree,
kCameraControlMaxBase + CameraControl_Tilt);
EXPECT_DOUBLE_EQ(state->tilt->step / kArcSecondsInDegree, kCameraControlStep);
EXPECT_EQ(state->zoom->current,
kCameraControlCurrentBase + CameraControl_Zoom);
EXPECT_EQ(state->zoom->min, kCameraControlMinBase + CameraControl_Zoom);
EXPECT_EQ(state->zoom->max, kCameraControlMaxBase + CameraControl_Zoom);
EXPECT_EQ(state->zoom->step, kCameraControlStep);
EXPECT_FALSE(state->supports_torch);
EXPECT_FALSE(state->torch);
EXPECT_EQ(state->red_eye_reduction, mojom::RedEyeReduction::NEVER);
EXPECT_EQ(state->fill_light_mode.size(), 0u);
}
// Given an |IMFCaptureSource| offering a video stream and a photo stream to
// |VideoCaptureDevice|, when taking photo from |VideoCaptureDevice| then
// expect IMFCaptureEngine::TakePhoto() to be called
TEST_F(VideoCaptureDeviceMFWinTest, TakePhotoViaPhotoStream) {
if (ShouldSkipTest())
return;
PrepareMFDeviceWithOneVideoStreamAndOnePhotoStream(MFVideoFormat_MJPG);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
EXPECT_CALL(*(engine_.Get()), OnTakePhoto());
device_->AllocateAndStart(VideoCaptureParams(), std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
VideoCaptureDevice::TakePhotoCallback take_photo_callback = base::BindOnce(
&MockImageCaptureClient::DoOnPhotoTaken, image_capture_client_);
device_->TakePhoto(std::move(take_photo_callback));
}
class DepthCameraDeviceMFWinTest
: public VideoCaptureDeviceMFWinTest,
public testing::WithParamInterface<DepthDeviceParams> {};
const DepthDeviceParams kDepthCamerasParams[] = {
{kMediaSubTypeY16, false, false},
{kMediaSubTypeZ16, false, true},
{kMediaSubTypeINVZ, true, false},
{MFVideoFormat_D16, true, true}};
INSTANTIATE_TEST_SUITE_P(DepthCameraDeviceMFWinTests,
DepthCameraDeviceMFWinTest,
testing::ValuesIn(kDepthCamerasParams));
// Given an |IMFCaptureSource| offering a video stream with subtype Y16, Z16,
// INVZ or D16, when allocating and starting |VideoCaptureDevice| expect the MF
// source and the MF sink to be set to the same media subtype.
TEST_P(DepthCameraDeviceMFWinTest, AllocateAndStartDepthCamera) {
if (ShouldSkipTest())
return;
DepthDeviceParams params = GetParam();
if (!params.additional_i420_video_stream &&
!params.additional_i420_formats_in_depth_stream) {
PrepareMFDeviceWithOneVideoStream(params.depth_video_stream_subtype);
} else {
PrepareMFDepthDeviceWithCombinedFormatsAndStreams(params);
}
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
EXPECT_CALL(*(capture_source_.get()), DoSetCurrentDeviceMediaType(0, _))
.WillOnce(Invoke([params](DWORD stream_index, IMFMediaType* media_type) {
GUID source_video_media_subtype;
media_type->GetGUID(MF_MT_SUBTYPE, &source_video_media_subtype);
EXPECT_EQ(source_video_media_subtype,
params.depth_video_stream_subtype);
return S_OK;
}));
EXPECT_CALL(*(capture_preview_sink_.get()), DoAddStream(0, _, _, _))
.WillOnce(Invoke([params](DWORD stream_index, IMFMediaType* media_type,
IMFAttributes* attributes,
DWORD* sink_stream_index) {
GUID sink_video_media_subtype;
media_type->GetGUID(MF_MT_SUBTYPE, &sink_video_media_subtype);
EXPECT_EQ(sink_video_media_subtype, params.depth_video_stream_subtype);
return S_OK;
}));
VideoCaptureFormat format(gfx::Size(640, 480), 30, media::PIXEL_FORMAT_Y16);
VideoCaptureParams video_capture_params;
video_capture_params.requested_format = format;
device_->AllocateAndStart(video_capture_params, std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
}
class VideoCaptureDeviceMFWinTestWithDXGI : public VideoCaptureDeviceMFWinTest {
protected:
void SetUp() override {
if (ShouldSkipD3D11Test())
GTEST_SKIP();
dxgi_device_manager_ = DXGIDeviceManager::Create();
VideoCaptureDeviceMFWinTest::SetUp();
}
};
TEST_F(VideoCaptureDeviceMFWinTestWithDXGI, SimpleInit) {
if (ShouldSkipTest())
return;
// The purpose of this test is to ensure that the capture engine is correctly
// initialized with a MF DXGI device manager.
// All required logic for this test is in SetUp().
}
TEST_F(VideoCaptureDeviceMFWinTestWithDXGI, EnsureNV12SinkSubtype) {
if (ShouldSkipTest())
return;
// Ensures that the stream which is added to the preview sink has a media type
// with a subtype of NV12
const GUID expected_subtype = MFVideoFormat_NV12;
PrepareMFDeviceWithOneVideoStream(expected_subtype);
EXPECT_CALL(*(engine_.Get()), OnStartPreview());
EXPECT_CALL(*client_, OnStarted());
EXPECT_CALL(*(capture_source_.get()), DoSetCurrentDeviceMediaType(0, _))
.WillOnce(Invoke(
[expected_subtype](DWORD stream_index, IMFMediaType* media_type) {
GUID source_video_media_subtype;
media_type->GetGUID(MF_MT_SUBTYPE, &source_video_media_subtype);
EXPECT_EQ(source_video_media_subtype, expected_subtype);
return S_OK;
}));
EXPECT_CALL(*(capture_preview_sink_.get()), DoAddStream(0, _, _, _))
.WillOnce(Invoke([expected_subtype](DWORD stream_index,
IMFMediaType* media_type,
IMFAttributes* attributes,
DWORD* sink_stream_index) {
GUID sink_video_media_subtype;
media_type->GetGUID(MF_MT_SUBTYPE, &sink_video_media_subtype);
EXPECT_EQ(sink_video_media_subtype, expected_subtype);
return S_OK;
}));
VideoCaptureFormat format(gfx::Size(640, 480), 30, media::PIXEL_FORMAT_NV12);
VideoCaptureParams video_capture_params;
video_capture_params.requested_format = format;
device_->AllocateAndStart(video_capture_params, std::move(client_));
capture_preview_sink_->sample_callback->OnSample(nullptr);
}
TEST_F(VideoCaptureDeviceMFWinTestWithDXGI, DeliverGMBCaptureBuffers) {
if (ShouldSkipTest())
return;
const GUID expected_subtype = MFVideoFormat_NV12;
PrepareMFDeviceWithOneVideoStream(expected_subtype);
const gfx::Size expected_size(640, 480);
// Verify that an output capture buffer is reserved from the client
EXPECT_CALL(*client_, ReserveOutputBuffer)
.WillOnce(Invoke(
[expected_size](const gfx::Size& size, VideoPixelFormat format,
int feedback_id,
VideoCaptureDevice::Client::Buffer* capture_buffer) {
EXPECT_EQ(size.width(), expected_size.width());
EXPECT_EQ(size.height(), expected_size.height());
EXPECT_EQ(format, PIXEL_FORMAT_NV12);
capture_buffer->handle_provider =
std::make_unique<MockCaptureHandleProvider>();
return VideoCaptureDevice::Client::ReserveResult::kSucceeded;
}));
Microsoft::WRL::ComPtr<MockD3D11Device> mock_device(new MockD3D11Device());
// Create mock source texture (to be provided to capture device from MF
// capture API)
D3D11_TEXTURE2D_DESC mock_desc = {};
mock_desc.Format = DXGI_FORMAT_NV12;
mock_desc.Width = expected_size.width();
mock_desc.Height = expected_size.height();
Microsoft::WRL::ComPtr<ID3D11Texture2D> mock_source_texture_2d;
Microsoft::WRL::ComPtr<MockD3D11Texture2D> mock_source_texture(
new MockD3D11Texture2D(mock_desc, mock_device.Get()));
EXPECT_TRUE(SUCCEEDED(
mock_source_texture.CopyTo(IID_PPV_ARGS(&mock_source_texture_2d))));
// Create mock target texture with matching dimensions/format
// (to be provided from the capture device to the capture client)
Microsoft::WRL::ComPtr<ID3D11Texture2D> mock_target_texture_2d;
Microsoft::WRL::ComPtr<MockD3D11Texture2D> mock_target_texture(
new MockD3D11Texture2D(mock_desc, mock_device.Get()));
EXPECT_TRUE(SUCCEEDED(
mock_target_texture.CopyTo(IID_PPV_ARGS(&mock_target_texture_2d))));
// Mock OpenSharedResource call on mock D3D device to return target texture
EXPECT_CALL(*mock_device.Get(), DoOpenSharedResource1)
.WillOnce(Invoke([&mock_target_texture_2d](HANDLE resource,
REFIID returned_interface,
void** resource_out) {
return mock_target_texture_2d.CopyTo(returned_interface, resource_out);
}));
// Expect call to copy source texture to target on immediate context
ID3D11Resource* expected_source =
static_cast<ID3D11Resource*>(mock_source_texture_2d.Get());
ID3D11Resource* expected_target =
static_cast<ID3D11Resource*>(mock_target_texture_2d.Get());
EXPECT_CALL(*mock_device->mock_immediate_context_.Get(),
OnCopySubresourceRegion(expected_target, _, _, _, _,
expected_source, _, _))
.Times(1);
// Expect the client to receive a buffer containing a GMB containing the
// expected fake DXGI handle
EXPECT_CALL(*client_, OnIncomingCapturedBufferExt)
.WillOnce(Invoke([](VideoCaptureDevice::Client::Buffer buffer,
const VideoCaptureFormat&, const gfx::ColorSpace&,
base::TimeTicks, base::TimeDelta, gfx::Rect,
const VideoFrameMetadata&) {
gfx::GpuMemoryBufferHandle gmb_handle =
buffer.handle_provider->GetGpuMemoryBufferHandle();
EXPECT_EQ(gmb_handle.type,
gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE);
}));
// Init capture
VideoCaptureFormat format(expected_size, 30, media::PIXEL_FORMAT_NV12);
VideoCaptureParams video_capture_params;
video_capture_params.requested_format = format;
device_->AllocateAndStart(video_capture_params, std::move(client_));
// Create MF sample and provide to sample callback on capture device
Microsoft::WRL::ComPtr<IMFSample> sample;
EXPECT_TRUE(SUCCEEDED(MFCreateSample(&sample)));
Microsoft::WRL::ComPtr<IMFMediaBuffer> dxgi_buffer;
EXPECT_TRUE(SUCCEEDED(MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D),
mock_source_texture_2d.Get(),
0, FALSE, &dxgi_buffer)));
EXPECT_TRUE(SUCCEEDED(sample->AddBuffer(dxgi_buffer.Get())));
capture_preview_sink_->sample_callback->OnSample(sample.Get());
}
} // namespace media