| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwinrtcameracontrol.h" |
| #include "qwinrtcameravideorenderercontrol.h" |
| #include "qwinrtvideodeviceselectorcontrol.h" |
| #include "qwinrtcameraimagecapturecontrol.h" |
| #include "qwinrtimageencodercontrol.h" |
| #include "qwinrtcameraflashcontrol.h" |
| #include "qwinrtcamerafocuscontrol.h" |
| #include "qwinrtcameralockscontrol.h" |
| |
| #include <QtCore/qfunctions_winrt.h> |
| #include <QtCore/QMutex> |
| #include <QtCore/QPointer> |
| #include <QtGui/QGuiApplication> |
| #include <private/qeventdispatcher_winrt_p.h> |
| |
| #include <functional> |
| #include <mfapi.h> |
| #include <mferror.h> |
| #include <mfidl.h> |
| #include <wrl.h> |
| #include <windows.devices.enumeration.h> |
| #include <windows.media.capture.h> |
| #include <windows.storage.streams.h> |
| #include <windows.media.devices.h> |
| |
| #include <algorithm> |
| |
| using namespace Microsoft::WRL; |
| using namespace Microsoft::WRL::Wrappers; |
| using namespace ABI::Windows::Devices::Enumeration; |
| using namespace ABI::Windows::Foundation; |
| using namespace ABI::Windows::Foundation::Collections; |
| using namespace ABI::Windows::Media; |
| using namespace ABI::Windows::Media::Capture; |
| using namespace ABI::Windows::Media::Devices; |
| using namespace ABI::Windows::Media::MediaProperties; |
| using namespace ABI::Windows::Storage::Streams; |
| |
| QT_BEGIN_NAMESPACE |
| |
| #define RETURN_VOID_AND_EMIT_ERROR(msg) \ |
| if (FAILED(hr)) { \ |
| emit error(QCamera::CameraError, qt_error_string(hr)); \ |
| RETURN_VOID_IF_FAILED(msg); \ |
| } |
| |
| #define FOCUS_RECT_SIZE 0.01f |
| #define FOCUS_RECT_HALF_SIZE 0.005f // FOCUS_RECT_SIZE / 2 |
| #define FOCUS_RECT_BOUNDARY 1.0f |
| #define FOCUS_RECT_POSITION_MIN 0.0f |
| #define FOCUS_RECT_POSITION_MAX 0.995f // FOCUS_RECT_BOUNDARY - FOCUS_RECT_HALF_SIZE |
| #define ASPECTRATIO_EPSILON 0.01f |
| |
| Q_LOGGING_CATEGORY(lcMMCamera, "qt.mm.camera") |
| |
| HRESULT getMediaStreamResolutions(IMediaDeviceController *device, |
| MediaStreamType type, |
| IVectorView<IMediaEncodingProperties *> **propertiesList, |
| QVector<QSize> *resolutions) |
| { |
| HRESULT hr; |
| hr = device->GetAvailableMediaStreamProperties(type, propertiesList); |
| Q_ASSERT_SUCCEEDED(hr); |
| quint32 listSize; |
| hr = (*propertiesList)->get_Size(&listSize); |
| Q_ASSERT_SUCCEEDED(hr); |
| resolutions->reserve(int(listSize)); |
| for (quint32 index = 0; index < listSize; ++index) { |
| ComPtr<IMediaEncodingProperties> properties; |
| hr = (*propertiesList)->GetAt(index, &properties); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString propertyType; |
| hr = properties->get_Type(propertyType.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| const HStringReference videoRef = HString::MakeReference(L"Video"); |
| const HStringReference imageRef = HString::MakeReference(L"Image"); |
| if (propertyType == videoRef) { |
| ComPtr<IVideoEncodingProperties> videoProperties; |
| hr = properties.As(&videoProperties); |
| Q_ASSERT_SUCCEEDED(hr); |
| UINT32 width, height; |
| hr = videoProperties->get_Width(&width); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = videoProperties->get_Height(&height); |
| Q_ASSERT_SUCCEEDED(hr); |
| resolutions->append(QSize(int(width), int(height))); |
| } else if (propertyType == imageRef) { |
| ComPtr<IImageEncodingProperties> imageProperties; |
| hr = properties.As(&imageProperties); |
| Q_ASSERT_SUCCEEDED(hr); |
| UINT32 width, height; |
| hr = imageProperties->get_Width(&width); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = imageProperties->get_Height(&height); |
| Q_ASSERT_SUCCEEDED(hr); |
| resolutions->append(QSize(int(width), int(height))); |
| } |
| } |
| return resolutions->isEmpty() ? MF_E_INVALID_FORMAT : hr; |
| } |
| |
| template<typename T, size_t typeSize> struct CustomPropertyValue; |
| |
| inline static ComPtr<IPropertyValueStatics> propertyValueStatics() |
| { |
| ComPtr<IPropertyValueStatics> valueStatics; |
| GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Foundation_PropertyValue).Get(), &valueStatics); |
| return valueStatics; |
| } |
| |
| template <typename T> |
| struct CustomPropertyValue<T, 4> |
| { |
| static ComPtr<IReference<T>> create(T value) |
| { |
| ComPtr<IInspectable> propertyValueObject; |
| HRESULT hr = propertyValueStatics()->CreateUInt32(value, &propertyValueObject); |
| ComPtr<IReference<UINT32>> uint32Object; |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = propertyValueObject.As(&uint32Object); |
| Q_ASSERT_SUCCEEDED(hr); |
| return reinterpret_cast<IReference<T> *>(uint32Object.Get()); |
| } |
| }; |
| |
| template <typename T> |
| struct CustomPropertyValue<T, 8> |
| { |
| static ComPtr<IReference<T>> create(T value) |
| { |
| ComPtr<IInspectable> propertyValueObject; |
| HRESULT hr = propertyValueStatics()->CreateUInt64(value, &propertyValueObject); |
| ComPtr<IReference<UINT64>> uint64Object; |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = propertyValueObject.As(&uint64Object); |
| Q_ASSERT_SUCCEEDED(hr); |
| return reinterpret_cast<IReference<T> *>(uint64Object.Get()); |
| } |
| }; |
| |
| // Required camera point focus |
| class WindowsRegionOfInterestIterableIterator : public RuntimeClass<IIterator<RegionOfInterest *>> |
| { |
| public: |
| explicit WindowsRegionOfInterestIterableIterator(const ComPtr<IRegionOfInterest> &item) |
| { |
| regionOfInterest = item; |
| } |
| HRESULT __stdcall get_Current(IRegionOfInterest **current) |
| { |
| *current = regionOfInterest.Detach(); |
| return S_OK; |
| } |
| HRESULT __stdcall get_HasCurrent(boolean *hasCurrent) |
| { |
| *hasCurrent = true; |
| return S_OK; |
| } |
| HRESULT __stdcall MoveNext(boolean *hasCurrent) |
| { |
| *hasCurrent = false; |
| return S_OK; |
| } |
| private: |
| ComPtr<IRegionOfInterest> regionOfInterest; |
| }; |
| |
| class WindowsRegionOfInterestIterable : public RuntimeClass<IIterable<RegionOfInterest *>> |
| { |
| public: |
| explicit WindowsRegionOfInterestIterable(const ComPtr<IRegionOfInterest> &item) |
| { |
| regionOfInterest = item; |
| } |
| HRESULT __stdcall First(IIterator<RegionOfInterest *> **first) |
| { |
| ComPtr<WindowsRegionOfInterestIterableIterator> iterator = Make<WindowsRegionOfInterestIterableIterator>(regionOfInterest); |
| *first = iterator.Detach(); |
| return S_OK; |
| } |
| private: |
| ComPtr<IRegionOfInterest> regionOfInterest; |
| }; |
| |
| class MediaStream : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IMFStreamSink, IMFMediaEventGenerator, IMFMediaTypeHandler> |
| { |
| public: |
| MediaStream(IMFMediaType *type, IMFMediaSink *mediaSink, QWinRTCameraVideoRendererControl *videoRenderer) |
| : m_type(type), m_sink(mediaSink), m_videoRenderer(videoRenderer) |
| { |
| Q_ASSERT(m_videoRenderer); |
| |
| HRESULT hr; |
| hr = MFCreateEventQueue(&m_eventQueue); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = MFAllocateSerialWorkQueue(MFASYNC_CALLBACK_QUEUE_STANDARD, &m_workQueueId); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| ~MediaStream() override |
| { |
| QMutexLocker locker(&m_mutex); |
| m_eventQueue->Shutdown(); |
| } |
| |
| HRESULT RequestSample() |
| { |
| if (m_pendingSamples.load() < 3) { |
| m_pendingSamples.ref(); |
| return QueueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, nullptr); |
| } |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetEvent(DWORD flags, IMFMediaEvent **event) override |
| { |
| QMutexLocker locker(&m_mutex); |
| // Create an extra reference to avoid deadlock |
| ComPtr<IMFMediaEventQueue> eventQueue = m_eventQueue; |
| locker.unlock(); |
| |
| return eventQueue->GetEvent(flags, event); |
| } |
| |
| HRESULT __stdcall BeginGetEvent(IMFAsyncCallback *callback, IUnknown *state) override |
| { |
| QMutexLocker locker(&m_mutex); |
| HRESULT hr = m_eventQueue->BeginGetEvent(callback, state); |
| return hr; |
| } |
| |
| HRESULT __stdcall EndGetEvent(IMFAsyncResult *result, IMFMediaEvent **event) override |
| { |
| QMutexLocker locker(&m_mutex); |
| return m_eventQueue->EndGetEvent(result, event); |
| } |
| |
| HRESULT __stdcall QueueEvent(MediaEventType eventType, const GUID &extendedType, HRESULT status, const PROPVARIANT *value) override |
| { |
| QMutexLocker locker(&m_mutex); |
| return m_eventQueue->QueueEventParamVar(eventType, extendedType, status, value); |
| } |
| |
| HRESULT __stdcall GetMediaSink(IMFMediaSink **mediaSink) override |
| { |
| m_sink->AddRef(); |
| *mediaSink = m_sink; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetIdentifier(DWORD *identifier) override |
| { |
| *identifier = 0; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetMediaTypeHandler(IMFMediaTypeHandler **handler) override |
| { |
| return QueryInterface(IID_PPV_ARGS(handler)); |
| } |
| |
| HRESULT __stdcall ProcessSample(IMFSample *sample) override |
| { |
| ComPtr<IMFMediaBuffer> buffer; |
| HRESULT hr = sample->GetBufferByIndex(0, &buffer); |
| RETURN_HR_IF_FAILED("Failed to get buffer from camera sample"); |
| ComPtr<IMF2DBuffer> buffer2d; |
| hr = buffer.As(&buffer2d); |
| RETURN_HR_IF_FAILED("Failed to cast camera sample buffer to 2D buffer"); |
| |
| m_pendingSamples.deref(); |
| m_videoRenderer->queueBuffer(buffer2d.Get()); |
| |
| return hr; |
| } |
| |
| HRESULT __stdcall PlaceMarker(MFSTREAMSINK_MARKER_TYPE type, const PROPVARIANT *value, const PROPVARIANT *context) override |
| { |
| Q_UNUSED(type); |
| Q_UNUSED(value); |
| QueueEvent(MEStreamSinkMarker, GUID_NULL, S_OK, context); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall Flush() override |
| { |
| m_videoRenderer->discardBuffers(); |
| m_pendingSamples.store(0); |
| return S_OK; |
| } |
| |
| HRESULT __stdcall IsMediaTypeSupported(IMFMediaType *type, IMFMediaType **) override |
| { |
| HRESULT hr; |
| GUID majorType; |
| hr = type->GetMajorType(&majorType); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (!IsEqualGUID(majorType, MFMediaType_Video)) |
| return MF_E_INVALIDMEDIATYPE; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetMediaTypeCount(DWORD *typeCount) override |
| { |
| *typeCount = 1; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetMediaTypeByIndex(DWORD index, IMFMediaType **type) override |
| { |
| if (index == 0) |
| return m_type.CopyTo(type); |
| return E_BOUNDS; |
| } |
| |
| HRESULT __stdcall SetCurrentMediaType(IMFMediaType *type) override |
| { |
| if (FAILED(IsMediaTypeSupported(type, nullptr))) |
| return MF_E_INVALIDREQUEST; |
| |
| m_type = type; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetCurrentMediaType(IMFMediaType **type) override |
| { |
| return m_type.CopyTo(type); |
| } |
| |
| HRESULT __stdcall GetMajorType(GUID *majorType) override |
| { |
| return m_type->GetMajorType(majorType); |
| } |
| |
| private: |
| QMutex m_mutex; |
| ComPtr<IMFMediaType> m_type; |
| IMFMediaSink *m_sink; |
| ComPtr<IMFMediaEventQueue> m_eventQueue; |
| DWORD m_workQueueId; |
| |
| QWinRTCameraVideoRendererControl *m_videoRenderer; |
| QAtomicInt m_pendingSamples; |
| }; |
| |
| class MediaSink : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IMediaExtension, IMFMediaSink, IMFClockStateSink> |
| { |
| public: |
| MediaSink(IMediaEncodingProfile *encodingProfile, QWinRTCameraVideoRendererControl *videoRenderer) |
| : m_videoRenderer(videoRenderer) |
| { |
| HRESULT hr; |
| ComPtr<IVideoEncodingProperties> videoProperties; |
| hr = encodingProfile->get_Video(&videoProperties); |
| RETURN_VOID_IF_FAILED("Failed to get video properties"); |
| ComPtr<IMFMediaType> videoType; |
| hr = MFCreateMediaTypeFromProperties(videoProperties.Get(), &videoType); |
| RETURN_VOID_IF_FAILED("Failed to create video type"); |
| m_stream = Make<MediaStream>(videoType.Get(), this, videoRenderer); |
| } |
| |
| ~MediaSink() override = default; |
| |
| HRESULT RequestSample() |
| { |
| return m_stream->RequestSample(); |
| } |
| |
| HRESULT __stdcall SetProperties(Collections::IPropertySet *configuration) override |
| { |
| Q_UNUSED(configuration); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall GetCharacteristics(DWORD *characteristics) override |
| { |
| *characteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_RATELESS; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall AddStreamSink(DWORD streamSinkIdentifier, IMFMediaType *mediaType, IMFStreamSink **streamSink) override |
| { |
| Q_UNUSED(streamSinkIdentifier); |
| Q_UNUSED(mediaType); |
| Q_UNUSED(streamSink); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall RemoveStreamSink(DWORD streamSinkIdentifier) override |
| { |
| Q_UNUSED(streamSinkIdentifier); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT __stdcall GetStreamSinkCount(DWORD *streamSinkCount) override |
| { |
| *streamSinkCount = 1; |
| return S_OK; |
| } |
| |
| HRESULT __stdcall GetStreamSinkByIndex(DWORD index, IMFStreamSink **streamSink) override |
| { |
| if (index == 0) |
| return m_stream.CopyTo(streamSink); |
| return MF_E_INVALIDINDEX; |
| } |
| |
| HRESULT __stdcall GetStreamSinkById(DWORD streamSinkIdentifier, IMFStreamSink **streamSink) override |
| { |
| // ID and index are always 0 |
| HRESULT hr = GetStreamSinkByIndex(streamSinkIdentifier, streamSink); |
| return hr == MF_E_INVALIDINDEX ? MF_E_INVALIDSTREAMNUMBER : hr; |
| } |
| |
| HRESULT __stdcall SetPresentationClock(IMFPresentationClock *presentationClock) override |
| { |
| HRESULT hr = S_OK; |
| m_presentationClock = presentationClock; |
| if (m_presentationClock) |
| hr = m_presentationClock->AddClockStateSink(this); |
| return hr; |
| } |
| |
| HRESULT __stdcall GetPresentationClock(IMFPresentationClock **presentationClock) override |
| { |
| return m_presentationClock.CopyTo(presentationClock); |
| } |
| |
| HRESULT __stdcall Shutdown() override |
| { |
| m_stream->Flush(); |
| scheduleSetActive(false); |
| return m_presentationClock ? m_presentationClock->Stop() : S_OK; |
| } |
| |
| HRESULT __stdcall OnClockStart(MFTIME systemTime, LONGLONG clockStartOffset) override |
| { |
| Q_UNUSED(systemTime); |
| Q_UNUSED(clockStartOffset); |
| |
| scheduleSetActive(true); |
| |
| return S_OK; |
| } |
| |
| HRESULT __stdcall OnClockStop(MFTIME systemTime) override |
| { |
| Q_UNUSED(systemTime); |
| |
| scheduleSetActive(false); |
| |
| return m_stream->QueueEvent(MEStreamSinkStopped, GUID_NULL, S_OK, nullptr); |
| } |
| |
| HRESULT __stdcall OnClockPause(MFTIME systemTime) override |
| { |
| Q_UNUSED(systemTime); |
| |
| scheduleSetActive(false); |
| |
| return m_stream->QueueEvent(MEStreamSinkPaused, GUID_NULL, S_OK, nullptr); |
| } |
| |
| HRESULT __stdcall OnClockRestart(MFTIME systemTime) override |
| { |
| Q_UNUSED(systemTime); |
| |
| scheduleSetActive(true); |
| |
| return m_stream->QueueEvent(MEStreamSinkStarted, GUID_NULL, S_OK, nullptr); |
| } |
| |
| HRESULT __stdcall OnClockSetRate(MFTIME systemTime, float rate) override |
| { |
| Q_UNUSED(systemTime); |
| Q_UNUSED(rate); |
| return E_NOTIMPL; |
| } |
| |
| private: |
| |
| inline void scheduleSetActive(bool active) |
| { |
| QMetaObject::invokeMethod(m_videoRenderer, "setActive", Qt::QueuedConnection, Q_ARG(bool, active)); |
| } |
| |
| ComPtr<MediaStream> m_stream; |
| ComPtr<IMFPresentationClock> m_presentationClock; |
| |
| QWinRTCameraVideoRendererControl *m_videoRenderer; |
| }; |
| |
| class QWinRTCameraControlPrivate |
| { |
| public: |
| QCamera::State state; |
| QCamera::Status status; |
| QCamera::CaptureModes captureMode; |
| |
| ComPtr<IMediaCapture> capture; |
| ComPtr<IMediaCaptureVideoPreview> capturePreview; |
| EventRegistrationToken captureFailedCookie; |
| EventRegistrationToken recordLimitationCookie; |
| |
| ComPtr<IMediaEncodingProfileStatics> encodingProfileFactory; |
| |
| ComPtr<IMediaEncodingProfile> encodingProfile; |
| ComPtr<MediaSink> mediaSink; |
| ComPtr<IFocusControl> focusControl; |
| ComPtr<IRegionsOfInterestControl> regionsOfInterestControl; |
| ComPtr<IAsyncAction> focusOperation; |
| |
| QPointer<QWinRTCameraVideoRendererControl> videoRenderer; |
| QPointer<QWinRTVideoDeviceSelectorControl> videoDeviceSelector; |
| QPointer<QWinRTCameraImageCaptureControl> imageCaptureControl; |
| QPointer<QWinRTImageEncoderControl> imageEncoderControl; |
| QPointer<QWinRTCameraFlashControl> cameraFlashControl; |
| QPointer<QWinRTCameraFocusControl> cameraFocusControl; |
| QPointer<QWinRTCameraLocksControl> cameraLocksControl; |
| QAtomicInt framesMapped; |
| QEventLoop *delayClose; |
| |
| bool initializing = false; |
| bool initialized = false; |
| HANDLE initializationCompleteEvent; |
| }; |
| |
| QWinRTCameraControl::QWinRTCameraControl(QObject *parent) |
| : QCameraControl(parent), d_ptr(new QWinRTCameraControlPrivate) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << parent; |
| Q_D(QWinRTCameraControl); |
| |
| d->delayClose = nullptr; |
| d->state = QCamera::UnloadedState; |
| d->status = QCamera::UnloadedStatus; |
| d->captureMode = QCamera::CaptureStillImage; |
| d->captureFailedCookie.value = 0; |
| d->recordLimitationCookie.value = 0; |
| d->videoRenderer = new QWinRTCameraVideoRendererControl(QSize(), this); |
| connect(d->videoRenderer, &QWinRTCameraVideoRendererControl::bufferRequested, |
| this, &QWinRTCameraControl::onBufferRequested); |
| d->videoDeviceSelector = new QWinRTVideoDeviceSelectorControl(this); |
| connect(d->videoDeviceSelector, QOverload<int>::of(&QWinRTVideoDeviceSelectorControl::selectedDeviceChanged), |
| d->videoRenderer, &QWinRTCameraVideoRendererControl::resetSampleFormat); |
| d->imageCaptureControl = new QWinRTCameraImageCaptureControl(this); |
| d->imageEncoderControl = new QWinRTImageEncoderControl(this); |
| d->cameraFlashControl = new QWinRTCameraFlashControl(this); |
| d->cameraFocusControl = new QWinRTCameraFocusControl(this); |
| d->cameraLocksControl = new QWinRTCameraLocksControl(this); |
| |
| d->initializationCompleteEvent = CreateEvent(nullptr, false, false, nullptr); |
| |
| if (qGuiApp) { |
| connect(qGuiApp, &QGuiApplication::applicationStateChanged, |
| this, &QWinRTCameraControl::onApplicationStateChanged); |
| } |
| } |
| |
| QWinRTCameraControl::~QWinRTCameraControl() |
| { |
| setState(QCamera::UnloadedState); |
| } |
| |
| QCamera::State QWinRTCameraControl::state() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->state; |
| } |
| |
| void QWinRTCameraControl::setState(QCamera::State state) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << state; |
| Q_D(QWinRTCameraControl); |
| |
| if (d->state == state) |
| return; |
| |
| HRESULT hr; |
| switch (state) { |
| case QCamera::ActiveState: { |
| // Capture has not been created or initialized |
| if (d->state == QCamera::UnloadedState) { |
| if (!d->initialized) { |
| if (!d->initializing) { |
| hr = initialize(); |
| RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture"); |
| } |
| DWORD waitResult = WaitForSingleObjectEx(d->initializationCompleteEvent, 30000, FALSE); |
| if (waitResult != WAIT_OBJECT_0) { |
| RETURN_VOID_AND_EMIT_ERROR("Failed to initialize camera control."); |
| return; |
| } |
| } |
| |
| d->state = QCamera::LoadedState; |
| emit stateChanged(d->state); |
| |
| d->status = QCamera::LoadedStatus; |
| emit statusChanged(d->status); |
| } |
| Q_ASSERT(d->state == QCamera::LoadedState); |
| |
| ComPtr<IAsyncAction> op; |
| hr = QEventDispatcherWinRT::runOnXamlThread([d, &op]() { |
| d->mediaSink = Make<MediaSink>(d->encodingProfile.Get(), d->videoRenderer); |
| HRESULT hr = d->capturePreview->StartPreviewToCustomSinkAsync(d->encodingProfile.Get(), d->mediaSink.Get(), &op); |
| return hr; |
| }); |
| RETURN_VOID_AND_EMIT_ERROR("Failed to initiate capture."); |
| if (d->status != QCamera::StartingStatus) { |
| d->status = QCamera::StartingStatus; |
| emit statusChanged(d->status); |
| } |
| |
| hr = QEventDispatcherWinRT::runOnXamlThread([&op]() { |
| return QWinRTFunctions::await(op); |
| }); |
| if (FAILED(hr)) { |
| emit error(QCamera::CameraError, qt_error_string(hr)); |
| setState(QCamera::UnloadedState); // Unload everything, as initialize() will need be called again |
| return; |
| } |
| |
| QCameraFocus::FocusModes focusMode = d->cameraFocusControl->focusMode(); |
| if (focusMode != 0 && setFocus(focusMode) && focusMode == QCameraFocus::ContinuousFocus) |
| focus(); |
| |
| d->state = QCamera::ActiveState; |
| emit stateChanged(d->state); |
| d->status = QCamera::ActiveStatus; |
| emit statusChanged(d->status); |
| QEventDispatcherWinRT::runOnXamlThread([d]() { d->mediaSink->RequestSample(); return S_OK;}); |
| break; |
| } |
| case QCamera::LoadedState: { |
| // If moving from unloaded, initialize the camera |
| if (d->state == QCamera::UnloadedState) { |
| if (!d->initialized) { |
| if (!d->initializing) { |
| hr = initialize(); |
| RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture"); |
| } |
| DWORD waitResult = WaitForSingleObjectEx(d->initializationCompleteEvent, 30000, FALSE); |
| if (waitResult != WAIT_OBJECT_0) { |
| RETURN_VOID_AND_EMIT_ERROR("Failed to initialize camera control."); |
| return; |
| } |
| } |
| |
| d->state = QCamera::LoadedState; |
| emit stateChanged(d->state); |
| |
| d->status = QCamera::LoadedStatus; |
| emit statusChanged(d->status); |
| } |
| // fall through |
| } |
| case QCamera::UnloadedState: { |
| // Stop the camera if it is running (transition to LoadedState) |
| if (d->status == QCamera::ActiveStatus) { |
| HRESULT hr; |
| if (d->focusOperation) { |
| hr = QWinRTFunctions::await(d->focusOperation); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| if (d->framesMapped > 0) { |
| qWarning("%d QVideoFrame(s) mapped when closing down camera. Camera will wait for unmap before closing down.", |
| d->framesMapped); |
| if (!d->delayClose) |
| d->delayClose = new QEventLoop(this); |
| d->delayClose->exec(); |
| } |
| |
| ComPtr<IAsyncAction> op; |
| hr = QEventDispatcherWinRT::runOnXamlThread([d, &op]() { |
| HRESULT hr = d->capturePreview->StopPreviewAsync(&op); |
| return hr; |
| }); |
| RETURN_VOID_AND_EMIT_ERROR("Failed to stop camera preview"); |
| if (d->status != QCamera::StoppingStatus) { |
| d->status = QCamera::StoppingStatus; |
| emit statusChanged(d->status); |
| } |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = QEventDispatcherWinRT::runOnXamlThread([&op]() { |
| return QWinRTFunctions::await(op); // Synchronize unloading |
| }); |
| if (FAILED(hr)) |
| emit error(QCamera::InvalidRequestError, qt_error_string(hr)); |
| |
| if (d->mediaSink) { |
| hr = QEventDispatcherWinRT::runOnXamlThread([d]() { |
| d->mediaSink->Shutdown(); |
| d->mediaSink.Reset(); |
| return S_OK; |
| }); |
| } |
| |
| d->state = QCamera::LoadedState; |
| emit stateChanged(d->state); |
| |
| d->status = QCamera::LoadedStatus; |
| emit statusChanged(d->status); |
| } |
| // Completely unload if needed |
| if (state == QCamera::UnloadedState) { |
| if (!d->capture) // Already unloaded |
| break; |
| |
| if (d->status != QCamera::UnloadingStatus) { |
| d->status = QCamera::UnloadingStatus; |
| emit statusChanged(d->status); |
| } |
| |
| hr = QEventDispatcherWinRT::runOnXamlThread([d]() { |
| HRESULT hr; |
| if (d->capture && d->captureFailedCookie.value) { |
| hr = d->capture->remove_Failed(d->captureFailedCookie); |
| Q_ASSERT_SUCCEEDED(hr); |
| d->captureFailedCookie.value = 0; |
| } |
| if (d->capture && d->recordLimitationCookie.value) { |
| d->capture->remove_RecordLimitationExceeded(d->recordLimitationCookie); |
| Q_ASSERT_SUCCEEDED(hr); |
| d->recordLimitationCookie.value = 0; |
| } |
| ComPtr<IClosable> capture; |
| hr = d->capture.As(&capture); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = capture->Close(); |
| RETURN_HR_IF_FAILED(""); |
| d->capture.Reset(); |
| return hr; |
| }); |
| RETURN_VOID_AND_EMIT_ERROR("Failed to close the capture manger"); |
| if (d->state != QCamera::UnloadedState) { |
| d->state = QCamera::UnloadedState; |
| emit stateChanged(d->state); |
| } |
| if (d->status != QCamera::UnloadedStatus) { |
| d->status = QCamera::UnloadedStatus; |
| emit statusChanged(d->status); |
| } |
| d->initialized = false; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| QCamera::Status QWinRTCameraControl::status() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->status; |
| } |
| |
| QCamera::CaptureModes QWinRTCameraControl::captureMode() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->captureMode; |
| } |
| |
| void QWinRTCameraControl::setCaptureMode(QCamera::CaptureModes mode) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << mode; |
| Q_D(QWinRTCameraControl); |
| |
| if (d->captureMode == mode) |
| return; |
| |
| if (!isCaptureModeSupported(mode)) { |
| qWarning("Unsupported capture mode: %d", mode); |
| return; |
| } |
| |
| d->captureMode = mode; |
| emit captureModeChanged(d->captureMode); |
| } |
| |
| bool QWinRTCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const |
| { |
| return mode >= QCamera::CaptureViewfinder && mode <= QCamera::CaptureStillImage; |
| } |
| |
| bool QWinRTCameraControl::canChangeProperty(QCameraControl::PropertyChangeType changeType, QCamera::Status status) const |
| { |
| Q_UNUSED(changeType); |
| |
| return status == QCamera::UnloadedStatus; // For now, assume shutdown is required for all property changes |
| } |
| |
| QVideoRendererControl *QWinRTCameraControl::videoRenderer() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->videoRenderer; |
| } |
| |
| QVideoDeviceSelectorControl *QWinRTCameraControl::videoDeviceSelector() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->videoDeviceSelector; |
| } |
| |
| QCameraImageCaptureControl *QWinRTCameraControl::imageCaptureControl() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->imageCaptureControl; |
| } |
| |
| QImageEncoderControl *QWinRTCameraControl::imageEncoderControl() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->imageEncoderControl; |
| } |
| |
| QCameraFlashControl *QWinRTCameraControl::cameraFlashControl() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->cameraFlashControl; |
| } |
| |
| QCameraFocusControl *QWinRTCameraControl::cameraFocusControl() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->cameraFocusControl; |
| } |
| |
| QCameraLocksControl *QWinRTCameraControl::cameraLocksControl() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->cameraLocksControl; |
| } |
| |
| Microsoft::WRL::ComPtr<ABI::Windows::Media::Capture::IMediaCapture> QWinRTCameraControl::handle() const |
| { |
| Q_D(const QWinRTCameraControl); |
| return d->capture; |
| } |
| |
| void QWinRTCameraControl::onBufferRequested() |
| { |
| Q_D(QWinRTCameraControl); |
| |
| if (d->mediaSink) |
| d->mediaSink->RequestSample(); |
| } |
| |
| void QWinRTCameraControl::onApplicationStateChanged(Qt::ApplicationState state) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << state; |
| #ifdef _DEBUG |
| return; |
| #else // !_DEBUG |
| Q_D(QWinRTCameraControl); |
| static QCamera::State savedState = d->state; |
| switch (state) { |
| case Qt::ApplicationInactive: |
| if (d->state != QCamera::UnloadedState) { |
| savedState = d->state; |
| setState(QCamera::UnloadedState); |
| } |
| break; |
| case Qt::ApplicationActive: |
| setState(QCamera::State(savedState)); |
| break; |
| default: |
| break; |
| } |
| #endif // _DEBUG |
| } |
| |
| HRESULT QWinRTCameraControl::initialize() |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__; |
| Q_D(QWinRTCameraControl); |
| |
| if (d->status != QCamera::LoadingStatus) { |
| d->status = QCamera::LoadingStatus; |
| emit statusChanged(d->status); |
| } |
| |
| HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { |
| HRESULT hr; |
| ComPtr<IInspectable> capture; |
| hr = RoActivateInstance(Wrappers::HString::MakeReference(RuntimeClass_Windows_Media_Capture_MediaCapture).Get(), |
| &capture); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = capture.As(&d->capture); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->capture.As(&d->capturePreview); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->capture->add_Failed(Callback<IMediaCaptureFailedEventHandler>(this, &QWinRTCameraControl::onCaptureFailed).Get(), |
| &d->captureFailedCookie); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->capture->add_RecordLimitationExceeded(Callback<IRecordLimitationExceededEventHandler>(this, &QWinRTCameraControl::onRecordLimitationExceeded).Get(), |
| &d->recordLimitationCookie); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_MediaEncodingProfile).Get(), |
| IID_PPV_ARGS(&d->encodingProfileFactory)); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| int deviceIndex = d->videoDeviceSelector->selectedDevice(); |
| if (deviceIndex < 0) |
| deviceIndex = d->videoDeviceSelector->defaultDevice(); |
| |
| const QString deviceName = d->videoDeviceSelector->deviceName(deviceIndex); |
| if (deviceName.isEmpty()) { |
| qWarning("No video device available or selected."); |
| return E_FAIL; |
| } |
| |
| d->videoRenderer->setScanLineDirection(QVideoSurfaceFormat::TopToBottom); |
| ComPtr<IMediaCaptureInitializationSettings> settings; |
| hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Capture_MediaCaptureInitializationSettings).Get(), |
| &settings); |
| Q_ASSERT_SUCCEEDED(hr); |
| HStringReference deviceId(reinterpret_cast<LPCWSTR>(deviceName.utf16()), uint(deviceName.length())); |
| hr = settings->put_VideoDeviceId(deviceId.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| hr = settings->put_StreamingCaptureMode(StreamingCaptureMode_Video); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| hr = settings->put_PhotoCaptureSource(PhotoCaptureSource_Auto); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<IAsyncAction> op; |
| hr = d->capture->InitializeWithSettingsAsync(settings.Get(), &op); |
| RETURN_HR_IF_FAILED("Failed to begin initialization of media capture manager"); |
| hr = op.Get()->put_Completed(Callback<IAsyncActionCompletedHandler>( |
| this, &QWinRTCameraControl::onInitializationCompleted).Get()); |
| RETURN_HR_IF_FAILED("Failed to register initialization callback"); |
| return S_OK; |
| }); |
| return hr; |
| } |
| |
| #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) |
| |
| HRESULT QWinRTCameraControl::initializeFocus() |
| { |
| Q_D(QWinRTCameraControl); |
| ComPtr<IFocusControl2> focusControl2; |
| HRESULT hr = d->focusControl.As(&focusControl2); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IVectorView<enum FocusMode>> focusModes; |
| hr = focusControl2->get_SupportedFocusModes(&focusModes); |
| if (FAILED(hr)) { |
| d->cameraFocusControl->setSupportedFocusMode(nullptr); |
| d->cameraFocusControl->setSupportedFocusPointMode(QSet<QCameraFocus::FocusPointMode>()); |
| qErrnoWarning(hr, "Failed to get camera supported focus mode list"); |
| return hr; |
| } |
| quint32 size; |
| hr = focusModes->get_Size(&size); |
| Q_ASSERT_SUCCEEDED(hr); |
| QCameraFocus::FocusModes supportedModeFlag = nullptr; |
| for (quint32 i = 0; i < size; ++i) { |
| FocusMode mode; |
| hr = focusModes->GetAt(i, &mode); |
| Q_ASSERT_SUCCEEDED(hr); |
| switch (mode) { |
| case FocusMode_Continuous: |
| supportedModeFlag |= QCameraFocus::ContinuousFocus; |
| break; |
| case FocusMode_Single: |
| supportedModeFlag |= QCameraFocus::AutoFocus; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| ComPtr<IVectorView<enum AutoFocusRange>> focusRange; |
| hr = focusControl2->get_SupportedFocusRanges(&focusRange); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to get camera supported focus range list"); |
| } else { |
| hr = focusRange->get_Size(&size); |
| Q_ASSERT_SUCCEEDED(hr); |
| for (quint32 i = 0; i < size; ++i) { |
| AutoFocusRange range; |
| hr = focusRange->GetAt(i, &range); |
| Q_ASSERT_SUCCEEDED(hr); |
| switch (range) { |
| case AutoFocusRange_Macro: |
| supportedModeFlag |= QCameraFocus::MacroFocus; |
| break; |
| case AutoFocusRange_FullRange: |
| supportedModeFlag |= QCameraFocus::InfinityFocus; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| d->cameraFocusControl->setSupportedFocusMode(supportedModeFlag); |
| if (!d->regionsOfInterestControl) { |
| d->cameraFocusControl->setSupportedFocusPointMode(QSet<QCameraFocus::FocusPointMode>()); |
| return S_OK; |
| } |
| boolean isRegionsfocusSupported = false; |
| hr = d->regionsOfInterestControl->get_AutoFocusSupported(&isRegionsfocusSupported); |
| Q_ASSERT_SUCCEEDED(hr); |
| UINT32 maxRegions; |
| hr = d->regionsOfInterestControl->get_MaxRegions(&maxRegions); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (!isRegionsfocusSupported || maxRegions == 0) { |
| d->cameraFocusControl->setSupportedFocusPointMode(QSet<QCameraFocus::FocusPointMode>()); |
| return S_OK; |
| } |
| QSet<QCameraFocus::FocusPointMode> supportedFocusPointModes; |
| supportedFocusPointModes << QCameraFocus::FocusPointCustom |
| << QCameraFocus::FocusPointCenter |
| << QCameraFocus::FocusPointAuto; |
| d->cameraFocusControl->setSupportedFocusPointMode(supportedFocusPointModes); |
| return S_OK; |
| } |
| |
| bool QWinRTCameraControl::setFocus(QCameraFocus::FocusModes modes) |
| { |
| Q_D(QWinRTCameraControl); |
| if (d->status == QCamera::UnloadedStatus) |
| return false; |
| |
| bool result = false; |
| HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([modes, &result, d, this]() { |
| ComPtr<IFocusSettings> focusSettings; |
| ComPtr<IInspectable> focusSettingsObject; |
| HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Devices_FocusSettings).Get(), &focusSettingsObject); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusSettingsObject.As(&focusSettings); |
| Q_ASSERT_SUCCEEDED(hr); |
| FocusMode mode; |
| if (modes.testFlag(QCameraFocus::ContinuousFocus)) { |
| mode = FocusMode_Continuous; |
| } else if (modes.testFlag(QCameraFocus::AutoFocus) |
| || modes.testFlag(QCameraFocus::MacroFocus) |
| || modes.testFlag(QCameraFocus::InfinityFocus)) { |
| // The Macro and infinity focus modes are only supported in auto focus mode on WinRT. |
| // QML camera focus doesn't support combined focus flags settings. In the case of macro |
| // and infinity Focus modes, the auto focus setting is applied. |
| mode = FocusMode_Single; |
| } else { |
| emit error(QCamera::NotSupportedFeatureError, QStringLiteral("Unsupported camera focus modes.")); |
| result = false; |
| return S_OK; |
| } |
| hr = focusSettings->put_Mode(mode); |
| Q_ASSERT_SUCCEEDED(hr); |
| AutoFocusRange range = AutoFocusRange_Normal; |
| if (modes.testFlag(QCameraFocus::MacroFocus)) |
| range = AutoFocusRange_Macro; |
| else if (modes.testFlag(QCameraFocus::InfinityFocus)) |
| range = AutoFocusRange_FullRange; |
| hr = focusSettings->put_AutoFocusRange(range); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusSettings->put_WaitForFocus(true); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusSettings->put_DisableDriverFallback(false); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<IFocusControl2> focusControl2; |
| hr = d->focusControl.As(&focusControl2); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusControl2->Configure(focusSettings.Get()); |
| result = SUCCEEDED(hr); |
| RETURN_OK_IF_FAILED("Failed to configure camera focus control"); |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| Q_UNUSED(hr); // Silence release build |
| return result; |
| } |
| |
| bool QWinRTCameraControl::setFocusPoint(const QPointF &focusPoint) |
| { |
| Q_D(QWinRTCameraControl); |
| if (focusPoint.x() < double(FOCUS_RECT_POSITION_MIN) |
| || focusPoint.x() > double(FOCUS_RECT_BOUNDARY)) { |
| emit error(QCamera::CameraError, QStringLiteral("Focus horizontal location should be between 0.0 and 1.0.")); |
| return false; |
| } |
| |
| if (focusPoint.y() < double(FOCUS_RECT_POSITION_MIN) |
| || focusPoint.y() > double(FOCUS_RECT_BOUNDARY)) { |
| emit error(QCamera::CameraError, QStringLiteral("Focus vertical location should be between 0.0 and 1.0.")); |
| return false; |
| } |
| |
| ABI::Windows::Foundation::Rect rect; |
| rect.X = qBound<float>(FOCUS_RECT_POSITION_MIN, float(focusPoint.x() - double(FOCUS_RECT_HALF_SIZE)), FOCUS_RECT_POSITION_MAX); |
| rect.Y = qBound<float>(FOCUS_RECT_POSITION_MIN, float(focusPoint.y() - double(FOCUS_RECT_HALF_SIZE)), FOCUS_RECT_POSITION_MAX); |
| rect.Width = (rect.X + FOCUS_RECT_SIZE) < FOCUS_RECT_BOUNDARY ? FOCUS_RECT_SIZE : FOCUS_RECT_BOUNDARY - rect.X; |
| rect.Height = (rect.Y + FOCUS_RECT_SIZE) < FOCUS_RECT_BOUNDARY ? FOCUS_RECT_SIZE : FOCUS_RECT_BOUNDARY - rect.Y; |
| |
| ComPtr<IRegionOfInterest> regionOfInterest; |
| ComPtr<IInspectable> regionOfInterestObject; |
| HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Devices_RegionOfInterest).Get(), ®ionOfInterestObject); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterestObject.As(®ionOfInterest); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IRegionOfInterest2> regionOfInterest2; |
| hr = regionOfInterestObject.As(®ionOfInterest2); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterest2->put_BoundsNormalized(true); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterest2->put_Weight(1); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterest2->put_Type(RegionOfInterestType_Unknown); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterest->put_AutoFocusEnabled(true); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = regionOfInterest->put_Bounds(rect); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<WindowsRegionOfInterestIterable> regionOfInterestIterable = Make<WindowsRegionOfInterestIterable>(regionOfInterest); |
| ComPtr<IAsyncAction> op; |
| hr = d->regionsOfInterestControl->SetRegionsAsync(regionOfInterestIterable.Get(), &op); |
| Q_ASSERT_SUCCEEDED(hr); |
| return QWinRTFunctions::await(op) == S_OK; |
| } |
| |
| bool QWinRTCameraControl::focus() |
| { |
| Q_D(QWinRTCameraControl); |
| HRESULT hr; |
| if (!d->focusControl) |
| return false; |
| |
| QEventDispatcherWinRT::runOnXamlThread([&d, &hr]() { |
| if (d->focusOperation) { |
| ComPtr<IAsyncInfo> info; |
| hr = d->focusOperation.As(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| AsyncStatus status = AsyncStatus::Completed; |
| hr = info->get_Status(&status); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (status == AsyncStatus::Started) |
| return E_ASYNC_OPERATION_NOT_STARTED; |
| } |
| |
| hr = d->focusControl->FocusAsync(&d->focusOperation); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| const long errorCode = HRESULT_CODE(hr); |
| if (errorCode == ERROR_OPERATION_IN_PROGRESS |
| || errorCode == ERROR_WRITE_PROTECT) { |
| return E_ASYNC_OPERATION_NOT_STARTED; |
| } |
| Q_ASSERT_SUCCEEDED(hr); |
| return S_OK; |
| }); |
| |
| return hr == S_OK; |
| } |
| |
| void QWinRTCameraControl::clearFocusPoint() |
| { |
| Q_D(QWinRTCameraControl); |
| if (!d->focusControl) |
| return; |
| ComPtr<IAsyncAction> op; |
| HRESULT hr = d->regionsOfInterestControl->ClearRegionsAsync(&op); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = QWinRTFunctions::await(op); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| bool QWinRTCameraControl::lockFocus() |
| { |
| Q_D(QWinRTCameraControl); |
| if (!d->focusControl) |
| return false; |
| |
| bool result = false; |
| ComPtr<IAsyncAction> op; |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([d, &result, &op]() { |
| ComPtr<IFocusControl2> focusControl2; |
| HRESULT hr = d->focusControl.As(&focusControl2); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusControl2->LockAsync(&op); |
| if (HRESULT_CODE(hr) == ERROR_WRITE_PROTECT) |
| return S_OK; |
| Q_ASSERT_SUCCEEDED(hr); |
| result = true; |
| return hr; |
| }); |
| return result ? (QWinRTFunctions::await(op) == S_OK) : false; |
| } |
| |
| bool QWinRTCameraControl::unlockFocus() |
| { |
| Q_D(QWinRTCameraControl); |
| if (!d->focusControl) |
| return false; |
| |
| bool result = false; |
| ComPtr<IAsyncAction> op; |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([d, &result, &op]() { |
| ComPtr<IFocusControl2> focusControl2; |
| HRESULT hr = d->focusControl.As(&focusControl2); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = focusControl2->UnlockAsync(&op); |
| if (HRESULT_CODE(hr) == ERROR_WRITE_PROTECT) |
| return S_OK; |
| Q_ASSERT_SUCCEEDED(hr); |
| result = true; |
| return hr; |
| }); |
| return result ? (QWinRTFunctions::await(op) == S_OK) : false; |
| } |
| |
| #else // !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) |
| |
| HRESULT QWinRTCameraControl::initializeFocus() |
| { |
| Q_D(QWinRTCameraControl); |
| d->cameraFocusControl->setSupportedFocusMode(0); |
| d->cameraFocusControl->setSupportedFocusPointMode(QSet<QCameraFocus::FocusPointMode>()); |
| return S_OK; |
| } |
| |
| bool QWinRTCameraControl::setFocus(QCameraFocus::FocusModes modes) |
| { |
| Q_UNUSED(modes) |
| return false; |
| } |
| |
| bool QWinRTCameraControl::setFocusPoint(const QPointF &focusPoint) |
| { |
| Q_UNUSED(focusPoint) |
| return false; |
| } |
| |
| bool QWinRTCameraControl::focus() |
| { |
| return false; |
| } |
| |
| void QWinRTCameraControl::clearFocusPoint() |
| { |
| } |
| |
| bool QWinRTCameraControl::lockFocus() |
| { |
| return false; |
| } |
| |
| bool QWinRTCameraControl::unlockFocus() |
| { |
| return false; |
| } |
| |
| #endif // !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) |
| |
| void QWinRTCameraControl::frameMapped() |
| { |
| Q_D(QWinRTCameraControl); |
| ++d->framesMapped; |
| } |
| |
| void QWinRTCameraControl::frameUnmapped() |
| { |
| Q_D(QWinRTCameraControl); |
| --d->framesMapped; |
| Q_ASSERT(d->framesMapped >= 0); |
| if (!d->framesMapped && d->delayClose && d->delayClose->isRunning()) |
| d->delayClose->exit(); |
| } |
| |
| HRESULT QWinRTCameraControl::onCaptureFailed(IMediaCapture *, IMediaCaptureFailedEventArgs *args) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << args; |
| HRESULT hr; |
| UINT32 code; |
| hr = args->get_Code(&code); |
| RETURN_HR_IF_FAILED("Failed to get error code"); |
| HString message; |
| args->get_Message(message.GetAddressOf()); |
| RETURN_HR_IF_FAILED("Failed to get error message"); |
| quint32 messageLength; |
| const wchar_t *messageBuffer = message.GetRawBuffer(&messageLength); |
| emit error(QCamera::CameraError, QString::fromWCharArray(messageBuffer, int(messageLength))); |
| setState(QCamera::LoadedState); |
| return S_OK; |
| } |
| |
| HRESULT QWinRTCameraControl::onRecordLimitationExceeded(IMediaCapture *) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__; |
| emit error(QCamera::CameraError, QStringLiteral("Recording limit exceeded.")); |
| setState(QCamera::LoadedState); |
| return S_OK; |
| } |
| |
| HRESULT QWinRTCameraControl::onInitializationCompleted(IAsyncAction *, AsyncStatus status) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__; |
| Q_D(QWinRTCameraControl); |
| |
| if (status != Completed) { |
| d->initializing = false; |
| d->initialized = false; |
| return S_OK; |
| } |
| |
| ComPtr<IVideoDeviceController> videoDeviceController; |
| HRESULT hr = d->capture->get_VideoDeviceController(&videoDeviceController); |
| ComPtr<IAdvancedVideoCaptureDeviceController2> advancedVideoDeviceController; |
| hr = videoDeviceController.As(&advancedVideoDeviceController); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = advancedVideoDeviceController->get_FocusControl(&d->focusControl); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| d->cameraFlashControl->initialize(advancedVideoDeviceController); |
| |
| boolean isFocusSupported; |
| hr = d->focusControl->get_Supported(&isFocusSupported); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (isFocusSupported) { |
| hr = advancedVideoDeviceController->get_RegionsOfInterestControl(&d->regionsOfInterestControl); |
| if (FAILED(hr)) |
| qCDebug(lcMMCamera) << "Focus supported, but no control for regions of interest available"; |
| hr = initializeFocus(); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IMediaDeviceController> deviceController; |
| hr = videoDeviceController.As(&deviceController); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| // Get preview stream properties. |
| ComPtr<IVectorView<IMediaEncodingProperties *>> previewPropertiesList; |
| QVector<QSize> previewResolutions; |
| hr = getMediaStreamResolutions(deviceController.Get(), |
| MediaStreamType_VideoPreview, |
| &previewPropertiesList, |
| &previewResolutions); |
| RETURN_HR_IF_FAILED("Failed to find a suitable video format"); |
| |
| MediaStreamType mediaStreamType = |
| d->captureMode == QCamera::CaptureVideo ? MediaStreamType_VideoRecord : MediaStreamType_Photo; |
| |
| // Get capture stream properties. |
| ComPtr<IVectorView<IMediaEncodingProperties *>> capturePropertiesList; |
| QVector<QSize> captureResolutions; |
| hr = getMediaStreamResolutions(deviceController.Get(), |
| mediaStreamType, |
| &capturePropertiesList, |
| &captureResolutions); |
| RETURN_HR_IF_FAILED("Failed to find a suitable video format"); |
| |
| std::sort(captureResolutions.begin(), captureResolutions.end(), [](QSize size1, QSize size2) { |
| return size1.width() * size1.height() < size2.width() * size2.height(); |
| }); |
| |
| // Set capture resolutions. |
| d->imageEncoderControl->setSupportedResolutionsList(captureResolutions.toList()); |
| const QSize captureResolution = d->imageEncoderControl->imageSettings().resolution(); |
| const quint32 captureResolutionIndex = quint32(captureResolutions.indexOf(captureResolution)); |
| ComPtr<IMediaEncodingProperties> captureProperties; |
| hr = capturePropertiesList->GetAt(captureResolutionIndex, &captureProperties); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IAsyncAction> op; |
| hr = deviceController->SetMediaStreamPropertiesAsync(mediaStreamType, captureProperties.Get(), &op); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = QWinRTFunctions::await(op); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| // Set preview resolution. |
| QSize maxSize; |
| const float captureAspectRatio = float(captureResolution.width()) / captureResolution.height(); |
| for (const QSize &resolution : qAsConst(previewResolutions)) { |
| const float aspectRatio = float(resolution.width()) / resolution.height(); |
| if ((qAbs(aspectRatio - captureAspectRatio) <= ASPECTRATIO_EPSILON) |
| && (maxSize.width() * maxSize.height() < resolution.width() * resolution.height())) { |
| maxSize = resolution; |
| } |
| } |
| |
| const QSize &viewfinderResolution = maxSize; |
| const quint32 viewfinderResolutionIndex = quint32(previewResolutions.indexOf(viewfinderResolution)); |
| hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_MediaEncodingProfile).Get(), |
| &d->encodingProfile); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IMediaEncodingProperties> previewProperties; |
| hr = previewPropertiesList->GetAt(viewfinderResolutionIndex, &previewProperties); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = deviceController->SetMediaStreamPropertiesAsync(MediaStreamType_VideoPreview, previewProperties.Get(), &op); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = QWinRTFunctions::await(op); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IVideoEncodingProperties> videoPreviewProperties; |
| hr = previewProperties.As(&videoPreviewProperties); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->encodingProfile->put_Video(videoPreviewProperties.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| if (d->videoRenderer) |
| d->videoRenderer->setSize(viewfinderResolution); |
| |
| if (!isFocusSupported) { |
| d->cameraFocusControl->setSupportedFocusMode(0); |
| d->cameraFocusControl->setSupportedFocusPointMode(QSet<QCameraFocus::FocusPointMode>()); |
| } |
| d->cameraLocksControl->initialize(); |
| |
| d->initializing = false; |
| d->initialized = true; |
| SetEvent(d->initializationCompleteEvent); |
| return S_OK; |
| } |
| |
| void QWinRTCameraControl::emitError(int errorCode, const QString &errorString) |
| { |
| qCDebug(lcMMCamera) << __FUNCTION__ << errorString << errorCode; |
| emit error(errorCode, errorString); |
| } |
| |
| QT_END_NAMESPACE |