diff options
Diffstat (limited to 'src/plugins/multimedia/ffmpeg/qwindowscamera.cpp')
-rw-r--r-- | src/plugins/multimedia/ffmpeg/qwindowscamera.cpp | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp b/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp new file mode 100644 index 000000000..d298e2c99 --- /dev/null +++ b/src/plugins/multimedia/ffmpeg/qwindowscamera.cpp @@ -0,0 +1,326 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qwindowscamera_p.h" +#include "qsemaphore.h" +#include "qmutex.h" + +#include <private/qmemoryvideobuffer_p.h> +#include <private/qwindowsmfdefs_p.h> +#include <private/qwindowsmultimediautils_p.h> +#include <private/qcomobject_p.h> + +#include <mfapi.h> +#include <mfidl.h> +#include <mferror.h> +#include <mfreadwrite.h> + +#include <system_error> + +QT_BEGIN_NAMESPACE + +using namespace QWindowsMultimediaUtils; + +class CameraReaderCallback : public QComObject<IMFSourceReaderCallback> +{ +public: + //from IMFSourceReaderCallback + STDMETHODIMP OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) override; + STDMETHODIMP OnFlush(DWORD) override; + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) override { return S_OK; } + + void setActiveCamera(ActiveCamera *activeCamera) + { + QMutexLocker locker(&m_mutex); + m_activeCamera = activeCamera; + } +private: + // Destructor is not public. Caller should call Release. + ~CameraReaderCallback() override = default; + + ActiveCamera *m_activeCamera = nullptr; + QMutex m_mutex; +}; + +static ComPtr<IMFSourceReader> createCameraReader(IMFMediaSource *mediaSource, + const ComPtr<CameraReaderCallback> &callback) +{ + ComPtr<IMFSourceReader> sourceReader; + ComPtr<IMFAttributes> readerAttributes; + + HRESULT hr = MFCreateAttributes(readerAttributes.GetAddressOf(), 1); + if (SUCCEEDED(hr)) { + hr = readerAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, callback.Get()); + if (SUCCEEDED(hr)) { + hr = MFCreateSourceReaderFromMediaSource(mediaSource, readerAttributes.Get(), sourceReader.GetAddressOf()); + if (SUCCEEDED(hr)) + return sourceReader; + } + } + + qWarning() << "Failed to create camera IMFSourceReader" << hr; + return sourceReader; +} + +static ComPtr<IMFMediaSource> createCameraSource(const QString &deviceId) +{ + ComPtr<IMFMediaSource> mediaSource; + ComPtr<IMFAttributes> sourceAttributes; + HRESULT hr = MFCreateAttributes(sourceAttributes.GetAddressOf(), 2); + if (SUCCEEDED(hr)) { + hr = sourceAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, QMM_MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (SUCCEEDED(hr)) { + hr = sourceAttributes->SetString(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, + reinterpret_cast<LPCWSTR>(deviceId.utf16())); + if (SUCCEEDED(hr)) { + hr = MFCreateDeviceSource(sourceAttributes.Get(), mediaSource.GetAddressOf()); + if (SUCCEEDED(hr)) + return mediaSource; + } + } + } + qWarning() << "Failed to create camera IMFMediaSource" << hr; + return mediaSource; +} + +static int calculateVideoFrameStride(IMFMediaType *videoType, int width) +{ + Q_ASSERT(videoType); + + GUID subtype = GUID_NULL; + HRESULT hr = videoType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (SUCCEEDED(hr)) { + LONG stride = 0; + hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &stride); + if (SUCCEEDED(hr)) + return int(qAbs(stride)); + } + + qWarning() << "Failed to calculate video stride" << errorString(hr); + return 0; +} + +static bool setCameraReaderFormat(IMFSourceReader *sourceReader, IMFMediaType *videoType) +{ + Q_ASSERT(sourceReader); + Q_ASSERT(videoType); + + HRESULT hr = sourceReader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, + videoType); + if (FAILED(hr)) + qWarning() << "Failed to set video format" << errorString(hr); + + return SUCCEEDED(hr); +} + +static ComPtr<IMFMediaType> findVideoType(IMFSourceReader *reader, + const QCameraFormat &format) +{ + for (DWORD i = 0;; ++i) { + ComPtr<IMFMediaType> candidate; + HRESULT hr = reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, i, + candidate.GetAddressOf()); + if (FAILED(hr)) + break; + + GUID subtype = GUID_NULL; + if (FAILED(candidate->GetGUID(MF_MT_SUBTYPE, &subtype))) + continue; + + if (format.pixelFormat() != pixelFormatFromMediaSubtype(subtype)) + continue; + + UINT32 width = 0u; + UINT32 height = 0u; + if (FAILED(MFGetAttributeSize(candidate.Get(), MF_MT_FRAME_SIZE, &width, &height))) + continue; + + if (format.resolution() != QSize{ int(width), int(height) }) + continue; + + return candidate; + } + return {}; +} + +class ActiveCamera { +public: + static std::unique_ptr<ActiveCamera> create(QWindowsCamera &wc, const QCameraDevice &device, const QCameraFormat &format) + { + auto ac = std::unique_ptr<ActiveCamera>(new ActiveCamera(wc)); + ac->m_source = createCameraSource(device.id()); + if (!ac->m_source) + return {}; + + ac->m_readerCallback = makeComObject<CameraReaderCallback>(); + ac->m_readerCallback->setActiveCamera(ac.get()); + ac->m_reader = createCameraReader(ac->m_source.Get(), ac->m_readerCallback); + if (!ac->m_reader) + return {}; + + if (!ac->setFormat(format)) + return {}; + + return ac; + } + + bool setFormat(const QCameraFormat &format) + { + flush(); + + auto videoType = findVideoType(m_reader.Get(), format); + if (videoType) { + if (setCameraReaderFormat(m_reader.Get(), videoType.Get())) { + m_frameFormat = { format.resolution(), format.pixelFormat() }; + m_videoFrameStride = + calculateVideoFrameStride(videoType.Get(), format.resolution().width()); + } + } + + m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr, nullptr, nullptr, + nullptr); + return true; + } + + void onReadSample(HRESULT status, LONGLONG timestamp, IMFSample *sample) + { + if (FAILED(status)) { + const std::string msg{ std::system_category().message(status) }; + m_windowsCamera.updateError(QCamera::CameraError, QString::fromStdString(msg)); + return; + } + + if (sample) { + ComPtr<IMFMediaBuffer> mediaBuffer; + if (SUCCEEDED(sample->ConvertToContiguousBuffer(mediaBuffer.GetAddressOf()))) { + + DWORD bufLen = 0; + BYTE *buffer = nullptr; + if (SUCCEEDED(mediaBuffer->Lock(&buffer, nullptr, &bufLen))) { + QByteArray bytes(reinterpret_cast<char*>(buffer), qsizetype(bufLen)); + QVideoFrame frame(new QMemoryVideoBuffer(bytes, m_videoFrameStride), m_frameFormat); + + // WMF uses 100-nanosecond units, Qt uses microseconds + frame.setStartTime(timestamp / 10); + + LONGLONG duration = -1; + if (SUCCEEDED(sample->GetSampleDuration(&duration))) + frame.setEndTime((timestamp + duration) / 10); + + emit m_windowsCamera.newVideoFrame(frame); + mediaBuffer->Unlock(); + } + } + } + + m_reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, nullptr, + nullptr, nullptr, nullptr); + } + + void onFlush() + { + m_flushWait.release(); + } + + ~ActiveCamera() + { + flush(); + m_readerCallback->setActiveCamera(nullptr); + } + +private: + explicit ActiveCamera(QWindowsCamera &wc) : m_windowsCamera(wc), m_flushWait(0) {}; + + void flush() + { + if (SUCCEEDED(m_reader->Flush(MF_SOURCE_READER_FIRST_VIDEO_STREAM))) { + m_flushWait.acquire(); + } + } + + QWindowsCamera &m_windowsCamera; + + QSemaphore m_flushWait; + + ComPtr<IMFMediaSource> m_source; + ComPtr<IMFSourceReader> m_reader; + ComPtr<CameraReaderCallback> m_readerCallback; + + QVideoFrameFormat m_frameFormat; + int m_videoFrameStride = 0; +}; + +STDMETHODIMP CameraReaderCallback::OnReadSample(HRESULT status, DWORD, DWORD, LONGLONG timestamp, IMFSample *sample) +{ + QMutexLocker locker(&m_mutex); + if (m_activeCamera) + m_activeCamera->onReadSample(status, timestamp, sample); + + return status; +} + +STDMETHODIMP CameraReaderCallback::OnFlush(DWORD) +{ + QMutexLocker locker(&m_mutex); + if (m_activeCamera) + m_activeCamera->onFlush(); + return S_OK; +} + +QWindowsCamera::QWindowsCamera(QCamera *camera) + : QPlatformCamera(camera) +{ + m_cameraDevice = camera ? camera->cameraDevice() : QCameraDevice{}; +} + +QWindowsCamera::~QWindowsCamera() +{ + QWindowsCamera::setActive(false); +} + +void QWindowsCamera::setActive(bool active) +{ + if (bool(m_active) == active) + return; + + if (active) { + if (m_cameraDevice.isNull()) + return; + + if (m_cameraFormat.isNull()) + m_cameraFormat = findBestCameraFormat(m_cameraDevice); + + m_active = ActiveCamera::create(*this, m_cameraDevice, m_cameraFormat); + if (m_active) + activeChanged(true); + + } else { + m_active.reset(); + emit activeChanged(false); + } +} + +void QWindowsCamera::setCamera(const QCameraDevice &camera) +{ + bool active = bool(m_active); + if (active) + setActive(false); + m_cameraDevice = camera; + m_cameraFormat = {}; + if (active) + setActive(true); +} + +bool QWindowsCamera::setCameraFormat(const QCameraFormat &format) +{ + if (format.isNull()) + return false; + + bool ok = m_active ? m_active->setFormat(format) : true; + if (ok) + m_cameraFormat = format; + + return ok; +} + +QT_END_NAMESPACE |