| // 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 |