858 lines
28 KiB
C++
858 lines
28 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2015 The Qt Company Ltd and/or its subsidiary(-ies).
|
|
** Contact: http://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL3$
|
|
** 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 http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.GPL included in
|
|
** the packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 2.0 requirements will be
|
|
** met: http://www.gnu.org/licenses/gpl-2.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qwinrtcameracontrol.h"
|
|
#include "qwinrtcameravideorenderercontrol.h"
|
|
#include "qwinrtvideodeviceselectorcontrol.h"
|
|
#include "qwinrtcameraimagecapturecontrol.h"
|
|
#include "qwinrtimageencodercontrol.h"
|
|
|
|
#include <QtCore/qfunctions_winrt.h>
|
|
#include <QtCore/QCoreApplication>
|
|
#include <QtCore/QPointer>
|
|
|
|
#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>
|
|
|
|
#ifdef Q_OS_WINPHONE
|
|
#include <Windows.Security.ExchangeActiveSyncProvisioning.h>
|
|
using namespace ABI::Windows::Security::ExchangeActiveSyncProvisioning;
|
|
#endif
|
|
|
|
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); \
|
|
}
|
|
|
|
inline uint qHash (const QSize &key) {
|
|
return key.width() * key.height();
|
|
}
|
|
|
|
class CriticalSectionLocker
|
|
{
|
|
public:
|
|
CriticalSectionLocker(CRITICAL_SECTION *section)
|
|
: m_section(section)
|
|
{
|
|
EnterCriticalSection(m_section);
|
|
}
|
|
~CriticalSectionLocker()
|
|
{
|
|
LeaveCriticalSection(m_section);
|
|
}
|
|
private:
|
|
CRITICAL_SECTION *m_section;
|
|
};
|
|
|
|
class MediaStream : public RuntimeClass<RuntimeClassFlags<WinRtClassicComMix>, IMFStreamSink, IMFMediaEventGenerator, IMFMediaTypeHandler>
|
|
{
|
|
enum Flags { NoFlag = 0, BufferLockRequired = 1 };
|
|
|
|
template <int n>
|
|
static Flags bufferLockRequired(const wchar_t (&blackListName)[n], const HString &deviceModel)
|
|
{
|
|
quint32 deviceNameLength;
|
|
const wchar_t *deviceName = deviceModel.GetRawBuffer(&deviceNameLength);
|
|
if (n - 1 <= deviceNameLength && !wmemcmp(blackListName, deviceName, n - 1))
|
|
return BufferLockRequired;
|
|
return NoFlag;
|
|
}
|
|
|
|
public:
|
|
MediaStream(IMFMediaType *type, IMFMediaSink *mediaSink, QWinRTCameraVideoRendererControl *videoRenderer)
|
|
: m_type(type), m_sink(mediaSink), m_videoRenderer(videoRenderer), m_flags(NoFlag)
|
|
{
|
|
Q_ASSERT(m_videoRenderer);
|
|
|
|
InitializeCriticalSectionEx(&m_mutex, 0, 0);
|
|
|
|
HRESULT hr;
|
|
hr = MFCreateEventQueue(&m_eventQueue);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
hr = MFAllocateSerialWorkQueue(MFASYNC_CALLBACK_QUEUE_STANDARD, &m_workQueueId);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
|
|
#ifdef Q_OS_WINPHONE
|
|
// Workaround for certain devices which fail to blit software buffers without first mapping them
|
|
ComPtr<IEasClientDeviceInformation> deviceInfo;
|
|
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Security_ExchangeActiveSyncProvisioning_EasClientDeviceInformation).Get(),
|
|
&deviceInfo);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
HString deviceModel;
|
|
hr = deviceInfo->get_SystemSku(deviceModel.GetAddressOf());
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
m_flags |= bufferLockRequired(L"NOKIA RM-976", deviceModel);
|
|
m_flags |= bufferLockRequired(L"NOKIA RM-1019", deviceModel);
|
|
#endif
|
|
}
|
|
|
|
~MediaStream()
|
|
{
|
|
CriticalSectionLocker locker(&m_mutex);
|
|
m_eventQueue->Shutdown();
|
|
DeleteCriticalSection(&m_mutex);
|
|
}
|
|
|
|
HRESULT RequestSample()
|
|
{
|
|
if (m_pendingSamples.load() < 3) {
|
|
m_pendingSamples.ref();
|
|
return QueueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, Q_NULLPTR);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetEvent(DWORD flags, IMFMediaEvent **event) Q_DECL_OVERRIDE
|
|
{
|
|
EnterCriticalSection(&m_mutex);
|
|
// Create an extra reference to avoid deadlock
|
|
ComPtr<IMFMediaEventQueue> eventQueue = m_eventQueue;
|
|
LeaveCriticalSection(&m_mutex);
|
|
|
|
return eventQueue->GetEvent(flags, event);
|
|
}
|
|
|
|
HRESULT __stdcall BeginGetEvent(IMFAsyncCallback *callback, IUnknown *state) Q_DECL_OVERRIDE
|
|
{
|
|
CriticalSectionLocker locker(&m_mutex);
|
|
HRESULT hr = m_eventQueue->BeginGetEvent(callback, state);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT __stdcall EndGetEvent(IMFAsyncResult *result, IMFMediaEvent **event) Q_DECL_OVERRIDE
|
|
{
|
|
CriticalSectionLocker locker(&m_mutex);
|
|
return m_eventQueue->EndGetEvent(result, event);
|
|
}
|
|
|
|
HRESULT __stdcall QueueEvent(MediaEventType eventType, const GUID &extendedType, HRESULT status, const PROPVARIANT *value) Q_DECL_OVERRIDE
|
|
{
|
|
CriticalSectionLocker locker(&m_mutex);
|
|
return m_eventQueue->QueueEventParamVar(eventType, extendedType, status, value);
|
|
}
|
|
|
|
HRESULT __stdcall GetMediaSink(IMFMediaSink **mediaSink) Q_DECL_OVERRIDE
|
|
{
|
|
*mediaSink = m_sink;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetIdentifier(DWORD *identifier) Q_DECL_OVERRIDE
|
|
{
|
|
*identifier = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetMediaTypeHandler(IMFMediaTypeHandler **handler) Q_DECL_OVERRIDE
|
|
{
|
|
return QueryInterface(IID_PPV_ARGS(handler));
|
|
}
|
|
|
|
HRESULT __stdcall ProcessSample(IMFSample *sample) Q_DECL_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");
|
|
|
|
#ifdef Q_OS_WINPHONE
|
|
if (m_flags & BufferLockRequired) {
|
|
BYTE *bytes;
|
|
LONG stride;
|
|
hr = buffer2d->Lock2D(&bytes, &stride);
|
|
RETURN_HR_IF_FAILED("Failed to lock camera frame buffer");
|
|
hr = buffer2d->Unlock2D();
|
|
RETURN_HR_IF_FAILED("Failed to unlock camera frame buffer");
|
|
}
|
|
#endif
|
|
m_pendingSamples.deref();
|
|
m_videoRenderer->queueBuffer(buffer2d.Get());
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT __stdcall PlaceMarker(MFSTREAMSINK_MARKER_TYPE type, const PROPVARIANT *value, const PROPVARIANT *context) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(type);
|
|
Q_UNUSED(value);
|
|
QueueEvent(MEStreamSinkMarker, GUID_NULL, S_OK, context);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall Flush() Q_DECL_OVERRIDE
|
|
{
|
|
m_videoRenderer->discardBuffers();
|
|
m_pendingSamples.store(0);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall IsMediaTypeSupported(IMFMediaType *type, IMFMediaType **) Q_DECL_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) Q_DECL_OVERRIDE
|
|
{
|
|
*typeCount = 1;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetMediaTypeByIndex(DWORD index, IMFMediaType **type) Q_DECL_OVERRIDE
|
|
{
|
|
if (index == 0)
|
|
return m_type.CopyTo(type);
|
|
return E_BOUNDS;
|
|
}
|
|
|
|
HRESULT __stdcall SetCurrentMediaType(IMFMediaType *type) Q_DECL_OVERRIDE
|
|
{
|
|
if (FAILED(IsMediaTypeSupported(type, Q_NULLPTR)))
|
|
return MF_E_INVALIDREQUEST;
|
|
|
|
m_type = type;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetCurrentMediaType(IMFMediaType **type) Q_DECL_OVERRIDE
|
|
{
|
|
return m_type.CopyTo(type);
|
|
}
|
|
|
|
HRESULT __stdcall GetMajorType(GUID *majorType) Q_DECL_OVERRIDE
|
|
{
|
|
return m_type->GetMajorType(majorType);
|
|
}
|
|
|
|
private:
|
|
CRITICAL_SECTION m_mutex;
|
|
ComPtr<IMFMediaType> m_type;
|
|
IMFMediaSink *m_sink;
|
|
ComPtr<IMFMediaEventQueue> m_eventQueue;
|
|
DWORD m_workQueueId;
|
|
|
|
QWinRTCameraVideoRendererControl *m_videoRenderer;
|
|
QAtomicInt m_pendingSamples;
|
|
quint32 m_flags;
|
|
};
|
|
|
|
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()
|
|
{
|
|
}
|
|
|
|
HRESULT RequestSample()
|
|
{
|
|
return m_stream->RequestSample();
|
|
}
|
|
|
|
HRESULT __stdcall SetProperties(Collections::IPropertySet *configuration) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(configuration);
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT __stdcall GetCharacteristics(DWORD *characteristics) Q_DECL_OVERRIDE
|
|
{
|
|
*characteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_RATELESS;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall AddStreamSink(DWORD streamSinkIdentifier, IMFMediaType *mediaType, IMFStreamSink **streamSink) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(streamSinkIdentifier);
|
|
Q_UNUSED(mediaType);
|
|
Q_UNUSED(streamSink);
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT __stdcall RemoveStreamSink(DWORD streamSinkIdentifier) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(streamSinkIdentifier);
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
HRESULT __stdcall GetStreamSinkCount(DWORD *streamSinkCount) Q_DECL_OVERRIDE
|
|
{
|
|
*streamSinkCount = 1;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall GetStreamSinkByIndex(DWORD index, IMFStreamSink **streamSink) Q_DECL_OVERRIDE
|
|
{
|
|
if (index == 0)
|
|
return m_stream.CopyTo(streamSink);
|
|
return MF_E_INVALIDINDEX;
|
|
}
|
|
|
|
HRESULT __stdcall GetStreamSinkById(DWORD streamSinkIdentifier, IMFStreamSink **streamSink) Q_DECL_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) Q_DECL_OVERRIDE
|
|
{
|
|
HRESULT hr = S_OK;
|
|
m_presentationClock = presentationClock;
|
|
if (m_presentationClock)
|
|
hr = m_presentationClock->AddClockStateSink(this);
|
|
return hr;
|
|
}
|
|
|
|
HRESULT __stdcall GetPresentationClock(IMFPresentationClock **presentationClock) Q_DECL_OVERRIDE
|
|
{
|
|
return m_presentationClock.CopyTo(presentationClock);
|
|
}
|
|
|
|
HRESULT __stdcall Shutdown() Q_DECL_OVERRIDE
|
|
{
|
|
m_stream->Flush();
|
|
m_videoRenderer->setActive(false);
|
|
return m_presentationClock->Stop();
|
|
}
|
|
|
|
HRESULT __stdcall OnClockStart(MFTIME systemTime, LONGLONG clockStartOffset) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(systemTime);
|
|
Q_UNUSED(clockStartOffset);
|
|
|
|
m_videoRenderer->setActive(true);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT __stdcall OnClockStop(MFTIME systemTime) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(systemTime);
|
|
|
|
m_videoRenderer->setActive(false);
|
|
|
|
return m_stream->QueueEvent(MEStreamSinkStopped, GUID_NULL, S_OK, Q_NULLPTR);
|
|
}
|
|
|
|
HRESULT __stdcall OnClockPause(MFTIME systemTime) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(systemTime);
|
|
|
|
m_videoRenderer->setActive(false);
|
|
|
|
return m_stream->QueueEvent(MEStreamSinkPaused, GUID_NULL, S_OK, Q_NULLPTR);
|
|
}
|
|
|
|
HRESULT __stdcall OnClockRestart(MFTIME systemTime) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(systemTime);
|
|
|
|
m_videoRenderer->setActive(true);
|
|
|
|
return m_stream->QueueEvent(MEStreamSinkStarted, GUID_NULL, S_OK, Q_NULLPTR);
|
|
}
|
|
|
|
HRESULT __stdcall OnClockSetRate(MFTIME systemTime, float rate) Q_DECL_OVERRIDE
|
|
{
|
|
Q_UNUSED(systemTime);
|
|
Q_UNUSED(rate);
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
private:
|
|
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;
|
|
|
|
QSize size;
|
|
QPointer<QWinRTCameraVideoRendererControl> videoRenderer;
|
|
QPointer<QWinRTVideoDeviceSelectorControl> videoDeviceSelector;
|
|
QPointer<QWinRTCameraImageCaptureControl> imageCaptureControl;
|
|
QPointer<QWinRTImageEncoderControl> imageEncoderControl;
|
|
};
|
|
|
|
QWinRTCameraControl::QWinRTCameraControl(QObject *parent)
|
|
: QCameraControl(parent), d_ptr(new QWinRTCameraControlPrivate)
|
|
{
|
|
Q_D(QWinRTCameraControl);
|
|
|
|
d->state = QCamera::UnloadedState;
|
|
d->status = QCamera::UnloadedStatus;
|
|
d->captureMode = QCamera::CaptureStillImage;
|
|
d->captureFailedCookie.value = 0;
|
|
d->recordLimitationCookie.value = 0;
|
|
d->videoRenderer = new QWinRTCameraVideoRendererControl(d->size, this);
|
|
connect(d->videoRenderer, &QWinRTCameraVideoRendererControl::bufferRequested,
|
|
this, &QWinRTCameraControl::onBufferRequested);
|
|
d->videoDeviceSelector = new QWinRTVideoDeviceSelectorControl(this);
|
|
d->imageCaptureControl = new QWinRTCameraImageCaptureControl(this);
|
|
d->imageEncoderControl = new QWinRTImageEncoderControl(this);
|
|
}
|
|
|
|
QWinRTCameraControl::~QWinRTCameraControl()
|
|
{
|
|
setState(QCamera::UnloadedState);
|
|
}
|
|
|
|
QCamera::State QWinRTCameraControl::state() const
|
|
{
|
|
Q_D(const QWinRTCameraControl);
|
|
return d->state;
|
|
}
|
|
|
|
void QWinRTCameraControl::setState(QCamera::State 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) {
|
|
hr = initialize();
|
|
RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture");
|
|
}
|
|
Q_ASSERT(d->state == QCamera::LoadedState);
|
|
|
|
d->mediaSink = Make<MediaSink>(d->encodingProfile.Get(), d->videoRenderer);
|
|
ComPtr<IAsyncAction> op;
|
|
hr = d->capturePreview->StartPreviewToCustomSinkAsync(d->encodingProfile.Get(), d->mediaSink.Get(), &op);
|
|
RETURN_VOID_AND_EMIT_ERROR("Failed to initiate capture");
|
|
if (d->status != QCamera::StartingStatus) {
|
|
d->status = QCamera::StartingStatus;
|
|
emit statusChanged(d->status);
|
|
}
|
|
|
|
hr = 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;
|
|
}
|
|
|
|
d->state = QCamera::ActiveState;
|
|
emit stateChanged(d->state);
|
|
d->status = QCamera::ActiveStatus;
|
|
emit statusChanged(d->status);
|
|
break;
|
|
}
|
|
case QCamera::LoadedState: {
|
|
// If moving from unloaded, initialize the camera
|
|
if (d->state == QCamera::UnloadedState) {
|
|
hr = initialize();
|
|
RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture");
|
|
}
|
|
// fall through
|
|
}
|
|
case QCamera::UnloadedState: {
|
|
// Stop the camera if it is running (transition to LoadedState)
|
|
if (d->status == QCamera::ActiveStatus) {
|
|
ComPtr<IAsyncAction> op;
|
|
hr = d->capturePreview->StopPreviewAsync(&op);
|
|
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 = QWinRTFunctions::await(op); // Synchronize unloading
|
|
if (FAILED(hr))
|
|
emit error(QCamera::InvalidRequestError, qt_error_string(hr));
|
|
|
|
d->mediaSink->Shutdown();
|
|
d->mediaSink.Reset();
|
|
|
|
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);
|
|
}
|
|
|
|
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_VOID_AND_EMIT_ERROR("Failed to close the capture manger");
|
|
d->capture.Reset();
|
|
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);
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
IMediaCapture *QWinRTCameraControl::handle() const
|
|
{
|
|
Q_D(const QWinRTCameraControl);
|
|
return d->capture.Get();
|
|
}
|
|
|
|
void QWinRTCameraControl::onBufferRequested()
|
|
{
|
|
Q_D(QWinRTCameraControl);
|
|
|
|
if (d->mediaSink)
|
|
d->mediaSink->RequestSample();
|
|
}
|
|
|
|
HRESULT QWinRTCameraControl::initialize()
|
|
{
|
|
Q_D(QWinRTCameraControl);
|
|
|
|
if (d->status != QCamera::LoadingStatus) {
|
|
d->status = QCamera::LoadingStatus;
|
|
emit statusChanged(d->status);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (d->videoDeviceSelector->cameraPosition(deviceName) == QCamera::FrontFace)
|
|
d->videoRenderer->setScanLineDirection(QVideoSurfaceFormat::BottomToTop);
|
|
|
|
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()), 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 = QWinRTFunctions::await(op, QWinRTFunctions::ProcessThreadEvents);
|
|
if (hr == E_ACCESSDENIED) {
|
|
qWarning("Access denied when initializing the media capture manager. "
|
|
"Check your manifest settings for microphone and webcam access.");
|
|
}
|
|
RETURN_HR_IF_FAILED("Failed to initialize media capture manager");
|
|
|
|
ComPtr<IVideoDeviceController> videoDeviceController;
|
|
hr = d->capture->get_VideoDeviceController(&videoDeviceController);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
ComPtr<IMediaDeviceController> deviceController;
|
|
hr = videoDeviceController.As(&deviceController);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
ComPtr<IVectorView<IMediaEncodingProperties *>> encodingPropertiesList;
|
|
MediaStreamType mediaStreamType;
|
|
switch (d->captureMode) {
|
|
default:
|
|
case QCamera::CaptureViewfinder:
|
|
mediaStreamType = MediaStreamType_VideoPreview;
|
|
break;
|
|
case QCamera::CaptureStillImage:
|
|
mediaStreamType = MediaStreamType_Photo;
|
|
break;
|
|
case QCamera::CaptureVideo:
|
|
mediaStreamType = MediaStreamType_VideoRecord;
|
|
break;
|
|
}
|
|
hr = deviceController->GetAvailableMediaStreamProperties(mediaStreamType, &encodingPropertiesList);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
|
|
d->size = QSize();
|
|
quint32 encodingPropertiesListSize;
|
|
hr = encodingPropertiesList->get_Size(&encodingPropertiesListSize);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
QHash<QSize, ComPtr<IVideoEncodingProperties>> videoEncodingPropertiesList;
|
|
int pixelCount = 0;
|
|
for (quint32 i = 0; i < encodingPropertiesListSize; ++i) {
|
|
ComPtr<IMediaEncodingProperties> properties;
|
|
hr = encodingPropertiesList->GetAt(i, &properties);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
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);
|
|
if (d->captureMode != QCamera::CaptureStillImage && int(width * height) > pixelCount) {
|
|
d->size = QSize(width, height);// Choose the Highest-quality format
|
|
pixelCount = d->size.width() * d->size.height();
|
|
}
|
|
videoEncodingPropertiesList.insert(QSize(width, height), videoProperties);
|
|
}
|
|
|
|
if (videoEncodingPropertiesList.isEmpty()) {
|
|
hr = MF_E_INVALID_FORMAT;
|
|
RETURN_HR_IF_FAILED("Failed to find a suitable video format");
|
|
}
|
|
|
|
if (d->captureMode == QCamera::CaptureStillImage) {
|
|
d->imageEncoderControl->setSupportedResolutionsList(videoEncodingPropertiesList.keys());
|
|
d->size = d->imageEncoderControl->imageSettings().resolution();
|
|
}
|
|
|
|
hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_MediaEncodingProfile).Get(),
|
|
&d->encodingProfile);
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
|
|
const ComPtr<IVideoEncodingProperties> videoEncodingProperties = videoEncodingPropertiesList[d->size];
|
|
if (!videoEncodingProperties) {
|
|
hr = MF_E_INVALID_FORMAT;
|
|
RETURN_HR_IF_FAILED("Failed to find a suitable video format properties");
|
|
}
|
|
|
|
hr = d->encodingProfile->put_Video(videoEncodingProperties.Get());
|
|
Q_ASSERT_SUCCEEDED(hr);
|
|
if (d->videoRenderer)
|
|
d->videoRenderer->setSize(d->size);
|
|
|
|
if (SUCCEEDED(hr) && d->state != QCamera::LoadedState) {
|
|
d->state = QCamera::LoadedState;
|
|
emit stateChanged(d->state);
|
|
}
|
|
if (SUCCEEDED(hr) && d->status != QCamera::LoadedStatus) {
|
|
d->status = QCamera::LoadedStatus;
|
|
emit statusChanged(d->status);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT QWinRTCameraControl::onCaptureFailed(IMediaCapture *, IMediaCaptureFailedEventArgs *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, messageLength));
|
|
setState(QCamera::LoadedState);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT QWinRTCameraControl::onRecordLimitationExceeded(IMediaCapture *)
|
|
{
|
|
emit error(QCamera::CameraError, QStringLiteral("Recording limit exceeded."));
|
|
setState(QCamera::LoadedState);
|
|
return S_OK;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|