summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/qnx
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/qnx')
-rw-r--r--src/plugins/multimedia/qnx/CMakeLists.txt39
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxcamera.cpp820
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxcamera_p.h201
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer.cpp299
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer_p.h60
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxcamerahandle_p.h102
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnximagecapture.cpp257
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnximagecapture_p.h63
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxplatformcamera.cpp426
-rw-r--r--src/plugins/multimedia/qnx/camera/qqnxplatformcamera_p.h113
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp284
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxaudiorecorder_p.h103
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxmediacapturesession.cpp121
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxmediacapturesession_p.h67
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxmediarecorder.cpp115
-rw-r--r--src/plugins/multimedia/qnx/capture/qqnxmediarecorder_p.h51
-rw-r--r--src/plugins/multimedia/qnx/common/mmrenderertypes.h95
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxaudioinput.cpp25
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxaudioinput_p.h33
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxaudiooutput.cpp52
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxaudiooutput_p.h39
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxmediaeventthread.cpp98
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxmediaeventthread_p.h55
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxwindowgrabber.cpp435
-rw-r--r--src/plugins/multimedia/qnx/common/qqnxwindowgrabber_p.h114
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata.cpp262
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata_p.h74
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer.cpp887
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer_p.h167
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil.cpp126
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil_p.h32
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink.cpp26
-rw-r--r--src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink_p.h41
-rw-r--r--src/plugins/multimedia/qnx/qnx.json3
-rw-r--r--src/plugins/multimedia/qnx/qqnxformatinfo.cpp36
-rw-r--r--src/plugins/multimedia/qnx/qqnxformatinfo_p.h33
-rw-r--r--src/plugins/multimedia/qnx/qqnxmediaintegration.cpp79
-rw-r--r--src/plugins/multimedia/qnx/qqnxmediaintegration_p.h50
-rw-r--r--src/plugins/multimedia/qnx/qqnxvideodevices.cpp111
-rw-r--r--src/plugins/multimedia/qnx/qqnxvideodevices_p.h32
40 files changed, 6026 insertions, 0 deletions
diff --git a/src/plugins/multimedia/qnx/CMakeLists.txt b/src/plugins/multimedia/qnx/CMakeLists.txt
new file mode 100644
index 000000000..e1ac0ffa3
--- /dev/null
+++ b/src/plugins/multimedia/qnx/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+
+qt_internal_add_plugin(QQnxMediaPlugin
+ OUTPUT_NAME qnxmediaplugin
+ PLUGIN_TYPE multimedia
+ SOURCES
+ camera/qqnxcamera.cpp camera/qqnxcamera_p.h
+ camera/qqnxplatformcamera.cpp camera/qqnxplatformcamera_p.h
+ camera/qqnxcameraframebuffer.cpp camera/qqnxcameraframebuffer_p.h
+ camera/qqnximagecapture.cpp camera/qqnximagecapture_p.h
+ common/qqnxaudioinput.cpp common/qqnxaudioinput_p.h
+ common/qqnxaudiooutput.cpp common/qqnxaudiooutput_p.h
+ common/qqnxmediaeventthread.cpp common/qqnxmediaeventthread_p.h
+ common/qqnxwindowgrabber.cpp common/qqnxwindowgrabber_p.h
+ capture/qqnxaudiorecorder.cpp capture/qqnxaudiorecorder_p.h
+ capture/qqnxmediacapturesession.cpp capture/qqnxmediacapturesession_p.h
+ capture/qqnxmediarecorder.cpp capture/qqnxmediarecorder_p.h
+ mediaplayer/qqnxmediaplayer.cpp mediaplayer/qqnxmediaplayer_p.h
+ mediaplayer/qqnxmediametadata.cpp mediaplayer/qqnxmediametadata_p.h
+ mediaplayer/qqnxvideosink.cpp mediaplayer/qqnxvideosink_p.h
+ mediaplayer/qqnxmediautil.cpp mediaplayer/qqnxmediautil_p.h
+ qqnxformatinfo.cpp qqnxformatinfo_p.h
+ qqnxmediaintegration.cpp qqnxmediaintegration_p.h
+ qqnxvideodevices.cpp qqnxvideodevices_p.h
+ INCLUDE_DIRECTORIES
+ audio
+ camera
+ capture
+ common
+ mediaplayer
+ LIBRARIES
+ Qt::MultimediaPrivate
+ Qt::CorePrivate
+ MMRenderer::MMRenderer
+ strm
+ camapi
+)
diff --git a/src/plugins/multimedia/qnx/camera/qqnxcamera.cpp b/src/plugins/multimedia/qnx/camera/qqnxcamera.cpp
new file mode 100644
index 000000000..6976221bd
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxcamera.cpp
@@ -0,0 +1,820 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnxcamera_p.h"
+#include "qqnxcameraframebuffer_p.h"
+#include "qqnxmediacapturesession_p.h"
+#include "qqnxvideosink_p.h"
+
+#include <qcameradevice.h>
+#include <qmediadevices.h>
+
+#include <private/qmediastoragelocation_p.h>
+
+QDebug &operator<<(QDebug &d, const QQnxCamera::VideoFormat &f)
+{
+ d << "VideoFormat - width=" << f.width
+ << "height=" << f.height
+ << "rotation=" << f.rotation
+ << "frameRate=" << f.frameRate
+ << "frameType=" << f.frameType;
+
+ return d;
+}
+
+static QString statusToString(camera_devstatus_t status)
+{
+ switch (status) {
+ case CAMERA_STATUS_DISCONNECTED:
+ return QStringLiteral("No user is connected to the camera");
+ case CAMERA_STATUS_POWERDOWN:
+ return QStringLiteral("Power down");
+ case CAMERA_STATUS_VIDEOVF:
+ return QStringLiteral("The video viewfinder has started");
+ case CAMERA_STATUS_CAPTURE_ABORTED:
+ return QStringLiteral("The capture of a still image failed and was aborted");
+ case CAMERA_STATUS_FILESIZE_WARNING:
+ return QStringLiteral("Time-remaining threshold has been exceeded");
+ case CAMERA_STATUS_FOCUS_CHANGE:
+ return QStringLiteral("The focus has changed on the camera");
+ case CAMERA_STATUS_RESOURCENOTAVAIL:
+ return QStringLiteral("The camera is about to free resources");
+ case CAMERA_STATUS_VIEWFINDER_ERROR:
+ return QStringLiteral(" An unexpected error was encountered while the "
+ "viewfinder was active");
+ case CAMERA_STATUS_MM_ERROR:
+ return QStringLiteral("The recording has stopped due to a memory error or multimedia "
+ "framework error");
+ case CAMERA_STATUS_FILESIZE_ERROR:
+ return QStringLiteral("A file has exceeded the maximum size.");
+ case CAMERA_STATUS_NOSPACE_ERROR:
+ return QStringLiteral("Not enough disk space");
+ case CAMERA_STATUS_BUFFER_UNDERFLOW:
+ return QStringLiteral("The viewfinder is out of buffers");
+ default:
+ break;
+ }
+
+ return {};
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxCamera::QQnxCamera(camera_unit_t unit, QObject *parent)
+ : QObject(parent)
+ , m_cameraUnit(unit)
+{
+ if (!m_handle.open(m_cameraUnit, CAMERA_MODE_RW))
+ qWarning("QQnxCamera: Failed to open camera (0x%x)", m_handle.lastError());
+
+ if (camera_set_vf_mode(m_handle.get(), CAMERA_VFMODE_VIDEO) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to configure viewfinder mode");
+ return;
+ }
+
+ if (camera_set_vf_property(m_handle.get(), CAMERA_IMGPROP_CREATEWINDOW, 0,
+ CAMERA_IMGPROP_RENDERTOWINDOW, 0) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to set camera properties");
+ return;
+ }
+
+ updateZoomLimits();
+ updateSupportedWhiteBalanceValues();
+
+ m_valid = true;
+}
+
+QQnxCamera::~QQnxCamera()
+{
+ stop();
+}
+
+camera_unit_t QQnxCamera::unit() const
+{
+ return m_cameraUnit;
+}
+
+QString QQnxCamera::name() const
+{
+ char name[CAMERA_LOCATION_NAMELEN];
+
+ if (camera_get_location_property(m_cameraUnit,
+ CAMERA_LOCATION_NAME, &name, CAMERA_LOCATION_END) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to obtain camera name");
+ return {};
+ }
+
+ return QString::fromUtf8(name);
+}
+
+bool QQnxCamera::isValid() const
+{
+ return m_valid;
+}
+
+bool QQnxCamera::isActive() const
+{
+ return m_handle.isOpen() && m_viewfinderActive;
+}
+
+void QQnxCamera::start()
+{
+ if (isActive())
+ return;
+
+ if (camera_start_viewfinder(m_handle.get(), viewfinderCallback,
+ statusCallback, this) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to start viewfinder");
+ return;
+ }
+
+ m_viewfinderActive = true;
+}
+
+void QQnxCamera::stop()
+{
+ if (!isActive())
+ return;
+
+ if (m_recordingVideo)
+ stopVideoRecording();
+
+ if (camera_stop_viewfinder(m_handle.get()) != CAMERA_EOK)
+ qWarning("QQnxCamera: Failed to stop camera");
+
+ m_viewfinderActive = false;
+}
+
+bool QQnxCamera::setCameraFormat(uint32_t width, uint32_t height, double frameRate)
+{
+ if (!m_handle.isOpen())
+ return false;
+
+ const camera_error_t error = camera_set_vf_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, width,
+ CAMERA_IMGPROP_HEIGHT, height,
+ CAMERA_IMGPROP_FRAMERATE, frameRate);
+
+ if (error != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to set camera format");
+ return false;
+ }
+
+ return true;
+}
+
+bool QQnxCamera::isFocusModeSupported(camera_focusmode_t mode) const
+{
+ return supportedFocusModes().contains(mode);
+}
+
+bool QQnxCamera::setFocusMode(camera_focusmode_t mode)
+{
+ if (!isActive())
+ return false;
+
+ const camera_error_t result = camera_set_focus_mode(m_handle.get(), mode);
+
+ if (result != CAMERA_EOK) {
+ qWarning("QQnxCamera: Unable to set focus mode (0x%x)", result);
+ return false;
+ }
+
+ focusModeChanged(mode);
+
+ return true;
+}
+
+camera_focusmode_t QQnxCamera::focusMode() const
+{
+ if (!isActive())
+ return CAMERA_FOCUSMODE_OFF;
+
+ camera_focusmode_t mode;
+
+ const camera_error_t result = camera_get_focus_mode(m_handle.get(), &mode);
+
+ if (result != CAMERA_EOK) {
+ qWarning("QQnxCamera: Unable to set focus mode (0x%x)", result);
+ return CAMERA_FOCUSMODE_OFF;
+ }
+
+ return mode;
+}
+
+QQnxCamera::VideoFormat QQnxCamera::vfFormat() const
+{
+ VideoFormat f = {};
+
+ if (camera_get_vf_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, &f.width,
+ CAMERA_IMGPROP_HEIGHT, &f.height,
+ CAMERA_IMGPROP_ROTATION, &f.rotation,
+ CAMERA_IMGPROP_FRAMERATE, &f.frameRate,
+ CAMERA_IMGPROP_FORMAT, &f.frameType) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to query video finder frameType");
+ }
+
+ return f;
+}
+
+void QQnxCamera::setVfFormat(const VideoFormat &f)
+{
+ const bool active = isActive();
+
+ if (active)
+ stop();
+
+ if (camera_set_vf_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, f.width,
+ CAMERA_IMGPROP_HEIGHT, f.height,
+ CAMERA_IMGPROP_ROTATION, f.rotation,
+ CAMERA_IMGPROP_FRAMERATE, f.frameRate,
+ CAMERA_IMGPROP_FORMAT, f.frameType) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to set video finder frameType");
+ }
+
+ if (active)
+ start();
+}
+
+QQnxCamera::VideoFormat QQnxCamera::recordingFormat() const
+{
+ VideoFormat f = {};
+
+ if (camera_get_video_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, &f.width,
+ CAMERA_IMGPROP_HEIGHT, &f.height,
+ CAMERA_IMGPROP_ROTATION, &f.rotation,
+ CAMERA_IMGPROP_FRAMERATE, &f.frameRate,
+ CAMERA_IMGPROP_FORMAT, &f.frameType) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to query recording frameType");
+ }
+
+ return f;
+}
+
+void QQnxCamera::setRecordingFormat(const VideoFormat &f)
+{
+ if (camera_set_video_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, f.width,
+ CAMERA_IMGPROP_HEIGHT, f.height,
+ CAMERA_IMGPROP_ROTATION, f.rotation,
+ CAMERA_IMGPROP_FRAMERATE, f.frameRate,
+ CAMERA_IMGPROP_FORMAT, f.frameType) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to set recording frameType");
+ }
+}
+
+void QQnxCamera::setCustomFocusPoint(const QPointF &point)
+{
+ const QSize vfSize = viewFinderSize();
+
+ if (vfSize.isEmpty())
+ return;
+
+ const auto toUint32 = [](double value) {
+ return static_cast<uint32_t>(std::max(0.0, value));
+ };
+
+ // define a 40x40 pixel focus region around the custom focus point
+ constexpr int pixelSize = 40;
+
+ const auto left = toUint32(point.x() * vfSize.width() - pixelSize / 2);
+ const auto top = toUint32(point.y() * vfSize.height() - pixelSize / 2);
+
+ camera_region_t focusRegion {
+ .left = left,
+ .top = top,
+ .width = pixelSize,
+ .height = pixelSize,
+ .extra = 0
+ };
+
+ if (camera_set_focus_regions(m_handle.get(), 1, &focusRegion) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Unable to set focus region");
+ return;
+ }
+
+ if (setFocusMode(focusMode()))
+ customFocusPointChanged(point);
+}
+
+void QQnxCamera::setManualFocusStep(int step)
+{
+ if (!isActive()) {
+ qWarning("QQnxCamera: Failed to set focus distance - view finder not active");
+ return;
+ }
+
+ if (!isFocusModeSupported(CAMERA_FOCUSMODE_MANUAL)) {
+ qWarning("QQnxCamera: Failed to set focus distance - manual focus mode not supported");
+ return;
+ }
+
+ if (camera_set_manual_focus_step(m_handle.get(), step) != CAMERA_EOK)
+ qWarning("QQnxCamera: Failed to set focus distance");
+}
+
+int QQnxCamera::manualFocusStep() const
+{
+ return focusStep().step;
+}
+
+int QQnxCamera::maxFocusStep() const
+{
+ return focusStep().maxStep;
+}
+
+QQnxCamera::FocusStep QQnxCamera::focusStep() const
+{
+ constexpr FocusStep invalidStep { -1, -1 };
+
+ if (!isActive()) {
+ qWarning("QQnxCamera: Failed to query max focus distance - view finder not active");
+ return invalidStep;
+ }
+
+ if (!isFocusModeSupported(CAMERA_FOCUSMODE_MANUAL)) {
+ qWarning("QQnxCamera: Failed to query max focus distance - "
+ "manual focus mode not supported");
+ return invalidStep;
+ }
+
+ FocusStep focusStep;
+
+ if (camera_get_manual_focus_step(m_handle.get(),
+ &focusStep.maxStep, &focusStep.step) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Unable to query camera focus step");
+ return invalidStep;
+ }
+
+ return focusStep;
+}
+
+
+QSize QQnxCamera::viewFinderSize() const
+{
+ // get the size of the viewfinder
+ int width = 0;
+ int height = 0;
+
+ if (camera_get_vf_property(m_handle.get(),
+ CAMERA_IMGPROP_WIDTH, width,
+ CAMERA_IMGPROP_HEIGHT, height) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to query view finder size");
+ return {};
+ }
+
+ return { width, height };
+}
+
+uint32_t QQnxCamera::minimumZoomLevel() const
+{
+ return m_minZoom;
+}
+
+uint32_t QQnxCamera::maximumZoomLevel() const
+{
+ return m_maxZoom;
+}
+
+bool QQnxCamera::isSmoothZoom() const
+{
+ return m_smoothZoom;
+}
+
+double QQnxCamera::zoomRatio(uint32_t zoomLevel) const
+{
+ double ratio;
+
+ if (camera_get_zoom_ratio_from_zoom_level(m_handle.get(), zoomLevel, &ratio) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to query zoom ratio from zoom level");
+ return 0.0;
+ }
+
+ return ratio;
+}
+
+bool QQnxCamera::setZoomFactor(uint32_t factor)
+{
+ if (camera_set_vf_property(m_handle.get(), CAMERA_IMGPROP_ZOOMFACTOR, factor) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to set zoom factor");
+ return false;
+ }
+
+ return true;
+}
+
+void QQnxCamera::setEvOffset(float ev)
+{
+ if (!isActive())
+ return;
+
+ if (camera_set_ev_offset(m_handle.get(), ev) != CAMERA_EOK)
+ qWarning("QQnxCamera: Failed to set up exposure compensation");
+}
+
+uint32_t QQnxCamera::manualIsoSensitivity() const
+{
+ if (!isActive())
+ return 0;
+
+ uint32_t isoValue;
+
+ if (camera_get_manual_iso(m_handle.get(), &isoValue) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to query ISO value");
+ return 0;
+ }
+
+ return isoValue;
+}
+
+void QQnxCamera::setManualIsoSensitivity(uint32_t value)
+{
+ if (!isActive())
+ return;
+
+ if (camera_set_manual_iso(m_handle.get(), value) != CAMERA_EOK)
+ qWarning("QQnxCamera: Failed to set ISO value");
+}
+
+void QQnxCamera::setManualExposureTime(double seconds)
+{
+ if (!isActive())
+ return;
+
+ if (camera_set_manual_shutter_speed(m_handle.get(), seconds) != CAMERA_EOK)
+ qWarning("QQnxCamera: Failed to set exposure time");
+}
+
+double QQnxCamera::manualExposureTime() const
+{
+ if (!isActive())
+ return 0.0;
+
+ double shutterSpeed;
+
+ if (camera_get_manual_shutter_speed(m_handle.get(), &shutterSpeed) != CAMERA_EOK) {
+ qWarning("QQnxCamera: Failed to get exposure time");
+ return 0.0;
+ }
+
+ return shutterSpeed;
+}
+
+bool QQnxCamera::hasFeature(camera_feature_t feature) const
+{
+ return camera_has_feature(m_handle.get(), feature);
+}
+
+void QQnxCamera::setWhiteBalanceMode(camera_whitebalancemode_t mode)
+{
+ if (!isActive())
+ return;
+
+ if (camera_set_whitebalance_mode(m_handle.get(), mode) != CAMERA_EOK)
+ qWarning("QQnxCamera: failed to set whitebalance mode");
+}
+
+camera_whitebalancemode_t QQnxCamera::whiteBalanceMode() const
+{
+ if (!isActive())
+ return CAMERA_WHITEBALANCEMODE_OFF;
+
+ camera_whitebalancemode_t mode;
+
+ if (camera_get_whitebalance_mode(m_handle.get(), &mode) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to get white balance mode");
+ return CAMERA_WHITEBALANCEMODE_OFF;
+ }
+
+ return mode;
+}
+
+void QQnxCamera::setManualWhiteBalance(uint32_t value)
+{
+ if (!isActive())
+ return;
+
+ if (camera_set_manual_white_balance(m_handle.get(), value) != CAMERA_EOK)
+ qWarning("QQnxCamera: failed to set manual white balance");
+}
+
+uint32_t QQnxCamera::manualWhiteBalance() const
+{
+ if (!isActive())
+ return 0;
+
+ uint32_t value;
+
+ if (camera_get_manual_white_balance(m_handle.get(), &value) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to get manual white balance");
+ return 0;
+ }
+
+ return value;
+}
+
+bool QQnxCamera::startVideoRecording(const QString &filename)
+{
+ // when preview is video, we must ensure that the recording properties
+ // match the view finder properties
+ if (hasFeature(CAMERA_FEATURE_PREVIEWISVIDEO)) {
+ VideoFormat newFormat = vfFormat();
+
+ const QList<camera_frametype_t> recordingTypes = supportedRecordingFrameTypes();
+
+ // find a suitable matching frame type in case the current view finder
+ // frametype is not supported
+ if (newFormat.frameType != recordingFormat().frameType
+ && !recordingTypes.contains(newFormat.frameType)) {
+
+ bool found = false;
+
+ for (const camera_frametype_t type : supportedVfFrameTypes()) {
+ if (recordingTypes.contains(type)) {
+ newFormat.frameType = type;
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ m_originalVfFormat = vfFormat();
+
+ // reconfigure and restart the view finder
+ setVfFormat(newFormat);
+ } else {
+ qWarning("QQnxCamera: failed to find suitable frame type for recording - aborting");
+ return false;
+ }
+ }
+
+ setRecordingFormat(newFormat);
+ }
+
+ if (camera_start_video(m_handle.get(), qPrintable(filename),
+ nullptr, nullptr, nullptr) == CAMERA_EOK) {
+ m_recordingVideo = true;
+ } else {
+ qWarning("QQnxCamera: failed to start video encoding");
+ }
+
+ return m_recordingVideo;
+}
+
+void QQnxCamera::stopVideoRecording()
+{
+ m_recordingVideo = false;
+
+ if (camera_stop_video(m_handle.get()) != CAMERA_EOK)
+ qWarning("QQnxCamera: error when stopping video recording");
+
+ // restore original vf format
+ if (m_originalVfFormat) {
+ setVfFormat(*m_originalVfFormat);
+ m_originalVfFormat.reset();
+ }
+}
+
+bool QQnxCamera::isVideoEncodingSupported() const
+{
+ if (!isActive())
+ return false;
+
+ return camera_has_feature(m_handle.get(), CAMERA_FEATURE_VIDEO);
+}
+
+camera_handle_t QQnxCamera::handle() const
+{
+ return m_handle.get();
+}
+
+void QQnxCamera::updateZoomLimits()
+{
+ bool smooth;
+
+ if (camera_get_zoom_limits(m_handle.get(), &m_minZoom, &m_maxZoom, &smooth) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to update zoom limits - using default values");
+ m_minZoom = m_maxZoom = 0;
+ }
+}
+
+void QQnxCamera::updateSupportedWhiteBalanceValues()
+{
+ uint32_t numSupported = 0;
+
+ const camera_error_t result = camera_get_supported_manual_white_balance_values(
+ m_handle.get(), 0, &numSupported, nullptr, &m_continuousWhiteBalanceValues);
+
+ if (result != CAMERA_EOK) {
+ if (result == CAMERA_EOPNOTSUPP)
+ qWarning("QQnxCamera: white balance not supported");
+ else
+ qWarning("QQnxCamera: unable to query manual white balance value count");
+
+ m_supportedWhiteBalanceValues.clear();
+
+ return;
+ }
+
+ m_supportedWhiteBalanceValues.resize(numSupported);
+
+ if (camera_get_supported_manual_white_balance_values(m_handle.get(),
+ m_supportedWhiteBalanceValues.size(),
+ &numSupported,
+ m_supportedWhiteBalanceValues.data(),
+ &m_continuousWhiteBalanceValues) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to query manual white balance values");
+
+ m_supportedWhiteBalanceValues.clear();
+ }
+}
+
+QList<camera_vfmode_t> QQnxCamera::supportedVfModes() const
+{
+ return queryValues(camera_get_supported_vf_modes);
+}
+
+QList<camera_res_t> QQnxCamera::supportedVfResolutions() const
+{
+ return queryValues(camera_get_supported_vf_resolutions);
+}
+
+QList<camera_frametype_t> QQnxCamera::supportedVfFrameTypes() const
+{
+ return queryValues(camera_get_supported_vf_frame_types);
+}
+
+QList<camera_focusmode_t> QQnxCamera::supportedFocusModes() const
+{
+ return queryValues(camera_get_focus_modes);
+}
+
+QList<double> QQnxCamera::specifiedVfFrameRates(camera_frametype_t frameType,
+ camera_res_t resolution) const
+{
+ uint32_t numSupported = 0;
+
+ if (camera_get_specified_vf_framerates(m_handle.get(), frameType, resolution,
+ 0, &numSupported, nullptr, nullptr) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to query specified framerates count");
+ return {};
+ }
+
+ QList<double> values(numSupported);
+
+ if (camera_get_specified_vf_framerates(m_handle.get(), frameType, resolution,
+ values.size(), &numSupported, values.data(), nullptr) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to query specified framerates values");
+ return {};
+ }
+
+ return values;
+}
+
+QList<camera_frametype_t> QQnxCamera::supportedRecordingFrameTypes() const
+{
+ return queryValues(camera_get_video_frame_types);
+}
+
+QList<uint32_t> QQnxCamera::supportedWhiteBalanceValues() const
+{
+ return m_supportedWhiteBalanceValues;
+}
+
+bool QQnxCamera::hasContinuousWhiteBalanceValues() const
+{
+ return m_continuousWhiteBalanceValues;
+}
+
+QList<camera_unit_t> QQnxCamera::supportedUnits()
+{
+ unsigned int numSupported = 0;
+
+ if (camera_get_supported_cameras(0, &numSupported, nullptr) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to query supported camera unit count");
+ return {};
+ }
+
+ QList<camera_unit_t> cameraUnits(numSupported);
+
+ if (camera_get_supported_cameras(cameraUnits.size(), &numSupported,
+ cameraUnits.data()) != CAMERA_EOK) {
+ qWarning("QQnxCamera: failed to enumerate supported camera units");
+ return {};
+ }
+
+ return cameraUnits;
+}
+
+template <typename T, typename U>
+QList<T> QQnxCamera::queryValues(QueryFuncPtr<T,U> func) const
+{
+ static_assert(std::is_integral_v<U>, "Parameter U must be of integral type");
+
+ U numSupported = 0;
+
+ if (func(m_handle.get(), 0, &numSupported, nullptr) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to query camera value count");
+ return {};
+ }
+
+ QList<T> values(numSupported);
+
+ if (func(m_handle.get(), values.size(), &numSupported, values.data()) != CAMERA_EOK) {
+ qWarning("QQnxCamera: unable to query camera values");
+ return {};
+ }
+
+ return values;
+}
+
+void QQnxCamera::handleVfBuffer(camera_buffer_t *buffer)
+{
+ // process the frame on this thread before locking the mutex
+ auto frame = std::make_unique<QQnxCameraFrameBuffer>(buffer);
+
+ // skip a frame if mutex is busy
+ if (m_currentFrameMutex.tryLock()) {
+ m_currentFrame = std::move(frame);
+ m_currentFrameMutex.unlock();
+
+ Q_EMIT frameAvailable();
+ }
+}
+
+void QQnxCamera::handleVfStatus(camera_devstatus_t status, uint16_t extraData)
+{
+ QMetaObject::invokeMethod(this, "handleStatusChange", Qt::QueuedConnection,
+ Q_ARG(camera_devstatus_t, status),
+ Q_ARG(uint16_t, extraData));
+}
+
+void QQnxCamera::handleStatusChange(camera_devstatus_t status, uint16_t extraData)
+{
+ Q_UNUSED(extraData);
+
+ switch (status) {
+ case CAMERA_STATUS_BUFFER_UNDERFLOW:
+ case CAMERA_STATUS_CAPTURECOMPLETE:
+ case CAMERA_STATUS_CAPTURE_ABORTED:
+ case CAMERA_STATUS_CONNECTED:
+ case CAMERA_STATUS_DISCONNECTED:
+ case CAMERA_STATUS_FILESIZE_ERROR:
+ case CAMERA_STATUS_FILESIZE_LIMIT_WARNING:
+ case CAMERA_STATUS_FILESIZE_WARNING:
+ case CAMERA_STATUS_FLASH_LEVEL_CHANGE:
+ case CAMERA_STATUS_FOCUS_CHANGE:
+ case CAMERA_STATUS_FRAME_DROPPED:
+ case CAMERA_STATUS_LOWLIGHT:
+ case CAMERA_STATUS_MM_ERROR:
+ case CAMERA_STATUS_NOSPACE_ERROR:
+ case CAMERA_STATUS_PHOTOVF:
+ case CAMERA_STATUS_POWERDOWN:
+ case CAMERA_STATUS_POWERUP:
+ case CAMERA_STATUS_RESOURCENOTAVAIL:
+ case CAMERA_STATUS_UNKNOWN:
+ case CAMERA_STATUS_VIDEOLIGHT_CHANGE:
+ case CAMERA_STATUS_VIDEOLIGHT_LEVEL_CHANGE:
+ case CAMERA_STATUS_VIDEOVF:
+ case CAMERA_STATUS_VIDEO_PAUSE:
+ case CAMERA_STATUS_VIDEO_RESUME:
+ case CAMERA_STATUS_VIEWFINDER_ACTIVE:
+ case CAMERA_STATUS_VIEWFINDER_ERROR:
+ case CAMERA_STATUS_VIEWFINDER_FREEZE:
+ case CAMERA_STATUS_VIEWFINDER_SUSPEND:
+ case CAMERA_STATUS_VIEWFINDER_UNFREEZE:
+ case CAMERA_STATUS_VIEWFINDER_UNSUSPEND:
+ qDebug() << "QQnxCamera:" << ::statusToString(status);
+ break;
+ }
+}
+
+std::unique_ptr<QQnxCameraFrameBuffer> QQnxCamera::takeCurrentFrame()
+{
+ QMutexLocker l(&m_currentFrameMutex);
+
+ return std::move(m_currentFrame);
+}
+
+void QQnxCamera::viewfinderCallback(camera_handle_t handle, camera_buffer_t *buffer, void *arg)
+{
+ Q_UNUSED(handle);
+
+ auto *camera = static_cast<QQnxCamera*>(arg);
+ camera->handleVfBuffer(buffer);
+}
+
+void QQnxCamera::statusCallback(camera_handle_t handle, camera_devstatus_t status,
+ uint16_t extraData, void *arg)
+{
+ Q_UNUSED(handle);
+
+ auto *camera = static_cast<QQnxCamera*>(arg);
+ camera->handleVfStatus(status, extraData);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxcamera_p.cpp"
diff --git a/src/plugins/multimedia/qnx/camera/qqnxcamera_p.h b/src/plugins/multimedia/qnx/camera/qqnxcamera_p.h
new file mode 100644
index 000000000..a4ddbfed6
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxcamera_p.h
@@ -0,0 +1,201 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQNXCAMERA_H
+#define QQNXCAMERA_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqnxcamerahandle_p.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qurl.h>
+
+#include <camera/camera_api.h>
+#include <camera/camera_3a.h>
+
+#include <memory>
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxCameraFrameBuffer;
+class QQnxMediaCaptureSession;
+class QQnxVideoSink;
+
+class QQnxCamera : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QQnxCamera(camera_unit_t unit, QObject *parent = nullptr);
+ ~QQnxCamera();
+
+ camera_unit_t unit() const;
+
+ QString name() const;
+
+ bool isValid() const;
+
+ bool isActive() const;
+ void start();
+ void stop();
+
+ bool startVideoRecording(const QString &filename);
+ void stopVideoRecording();
+
+ bool setCameraFormat(uint32_t width, uint32_t height, double frameRate);
+
+ bool isFocusModeSupported(camera_focusmode_t mode) const;
+ bool setFocusMode(camera_focusmode_t mode);
+ camera_focusmode_t focusMode() const;
+
+ void setCustomFocusPoint(const QPointF &point);
+
+ void setManualFocusStep(int step);
+ int manualFocusStep() const;
+ int maxFocusStep() const;
+
+ QSize viewFinderSize() const;
+
+ uint32_t minimumZoomLevel() const;
+ uint32_t maximumZoomLevel() const;
+ bool isSmoothZoom() const;
+ double zoomRatio(uint32_t zoomLevel) const;
+ bool setZoomFactor(uint32_t factor);
+
+ void setEvOffset(float ev);
+
+ uint32_t manualIsoSensitivity() const;
+ void setManualIsoSensitivity(uint32_t value);
+ void setManualExposureTime(double seconds);
+ double manualExposureTime() const;
+
+ void setWhiteBalanceMode(camera_whitebalancemode_t mode);
+ camera_whitebalancemode_t whiteBalanceMode() const;
+
+ void setManualWhiteBalance(uint32_t value);
+ uint32_t manualWhiteBalance() const;
+
+ bool hasFeature(camera_feature_t feature) const;
+
+ camera_handle_t handle() const;
+
+ QList<camera_vfmode_t> supportedVfModes() const;
+ QList<camera_res_t> supportedVfResolutions() const;
+ QList<camera_frametype_t> supportedVfFrameTypes() const;
+ QList<camera_focusmode_t> supportedFocusModes() const;
+ QList<double> specifiedVfFrameRates(camera_frametype_t frameType,
+ camera_res_t resolution) const;
+
+ QList<camera_frametype_t> supportedRecordingFrameTypes() const;
+
+ QList<uint32_t> supportedWhiteBalanceValues() const;
+
+ bool hasContinuousWhiteBalanceValues() const;
+
+ static QList<camera_unit_t> supportedUnits();
+
+ std::unique_ptr<QQnxCameraFrameBuffer> takeCurrentFrame();
+
+Q_SIGNALS:
+ void focusModeChanged(camera_focusmode_t mode);
+ void customFocusPointChanged(const QPointF &point);
+ void minimumZoomFactorChanged(double factor);
+
+ double maximumZoomFactorChanged(double factor);
+
+ void frameAvailable();
+
+private:
+ struct FocusStep
+ {
+ int step; // current step
+ int maxStep; // max supported step
+ };
+
+ FocusStep focusStep() const;
+
+ struct VideoFormat
+ {
+ uint32_t width;
+ uint32_t height;
+ uint32_t rotation;
+ double frameRate;
+ camera_frametype_t frameType;
+ };
+
+ friend QDebug &operator<<(QDebug&, const VideoFormat&);
+
+ VideoFormat vfFormat() const;
+ void setVfFormat(const VideoFormat &format);
+
+ VideoFormat recordingFormat() const;
+ void setRecordingFormat(const VideoFormat &format);
+
+ void updateZoomLimits();
+ void updateSupportedWhiteBalanceValues();
+ void setColorTemperatureInternal(unsigned temp);
+
+ bool isVideoEncodingSupported() const;
+
+ void handleVfBuffer(camera_buffer_t *buffer);
+
+ // viewfinder callback
+ void handleVfStatus(camera_devstatus_t status, uint16_t extraData);
+
+ // our handler running on main thread
+ Q_INVOKABLE void handleStatusChange(camera_devstatus_t status, uint16_t extraData);
+
+ template <typename T, typename U>
+ using QueryFuncPtr = camera_error_t (*)(camera_handle_t, U, U *, T *);
+
+ template <typename T, typename U>
+ QList<T> queryValues(QueryFuncPtr<T, U> func) const;
+
+ static void viewfinderCallback(camera_handle_t handle,
+ camera_buffer_t *buffer, void *arg);
+
+ static void statusCallback(camera_handle_t handle, camera_devstatus_t status,
+ uint16_t extraData, void *arg);
+
+ QQnxMediaCaptureSession *m_session = nullptr;
+
+ camera_unit_t m_cameraUnit = CAMERA_UNIT_NONE;
+
+ QQnxCameraHandle m_handle;
+
+ uint32_t m_minZoom = 0;
+ uint32_t m_maxZoom = 0;
+
+ QMutex m_currentFrameMutex;
+
+ QList<uint32_t> m_supportedWhiteBalanceValues;
+
+ std::unique_ptr<QQnxCameraFrameBuffer> m_currentFrame;
+
+ std::optional<VideoFormat> m_originalVfFormat;
+
+ bool m_viewfinderActive = false;
+ bool m_recordingVideo = false;
+ bool m_valid = false;
+ bool m_smoothZoom = false;
+ bool m_continuousWhiteBalanceValues = false;
+};
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(camera_devstatus_t)
+Q_DECLARE_METATYPE(uint16_t)
+
+#endif
diff --git a/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer.cpp b/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer.cpp
new file mode 100644
index 000000000..6595c5d42
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer.cpp
@@ -0,0 +1,299 @@
+// Copyright (C) 2022 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqnxcameraframebuffer_p.h"
+
+#include <limits>
+
+template <typename T>
+static constexpr int toInt(T value)
+{
+ if constexpr (sizeof(T) >= sizeof(int)) {
+ if (std::is_signed_v<T>) {
+ return static_cast<int>(std::clamp<T>(value,
+ std::numeric_limits<int>::min(), std::numeric_limits<int>::max()));
+ } else {
+ return static_cast<int>(std::min<T>(value, std::numeric_limits<int>::max()));
+ }
+ } else {
+ return static_cast<int>(value);
+ }
+}
+
+template <typename T>
+static constexpr QSize frameSize(const T &frame)
+{
+ return { toInt(frame.width), toInt(frame.height) };
+}
+
+static constexpr QVideoFrameFormat::PixelFormat frameTypeToPixelFormat(camera_frametype_t type)
+{
+ switch (type) {
+ case CAMERA_FRAMETYPE_NV12:
+ return QVideoFrameFormat::Format_NV12;
+ case CAMERA_FRAMETYPE_RGB8888:
+ return QVideoFrameFormat::Format_ARGB8888;
+ case CAMERA_FRAMETYPE_GRAY8:
+ return QVideoFrameFormat::Format_Y8;
+ case CAMERA_FRAMETYPE_CBYCRY:
+ return QVideoFrameFormat::Format_UYVY;
+ case CAMERA_FRAMETYPE_YCBCR420P:
+ return QVideoFrameFormat::Format_YUV420P;
+ case CAMERA_FRAMETYPE_YCBYCR:
+ return QVideoFrameFormat::Format_YUYV;
+ default:
+ break;
+ }
+
+ return QVideoFrameFormat::Format_Invalid;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_nv12_t &frame)
+{
+ return frame.uv_offset + frame.uv_stride * frame.height / 2;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_rgb8888_t &frame)
+{
+ return frame.stride * frame.height;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_gray8_t &frame)
+{
+ return frame.stride * frame.height;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_cbycry_t &frame)
+{
+ return frame.bufsize;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_ycbcr420p_t &frame)
+{
+ return frame.cr_offset + frame.cr_stride * frame.height / 2;
+}
+
+static constexpr size_t bufferDataSize(const camera_frame_ycbycr_t &frame)
+{
+ return frame.stride * frame.height;
+}
+
+static constexpr size_t bufferDataSize(const camera_buffer_t *buffer)
+{
+ switch (buffer->frametype) {
+ case CAMERA_FRAMETYPE_NV12:
+ return bufferDataSize(buffer->framedesc.nv12);
+ case CAMERA_FRAMETYPE_RGB8888:
+ return bufferDataSize(buffer->framedesc.rgb8888);
+ case CAMERA_FRAMETYPE_GRAY8:
+ return bufferDataSize(buffer->framedesc.gray8);
+ case CAMERA_FRAMETYPE_CBYCRY:
+ return bufferDataSize(buffer->framedesc.cbycry);
+ case CAMERA_FRAMETYPE_YCBCR420P:
+ return bufferDataSize(buffer->framedesc.ycbcr420p);
+ case CAMERA_FRAMETYPE_YCBYCR:
+ return bufferDataSize(buffer->framedesc.ycbycr);
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_nv12_t &frame,
+ unsigned char *baseAddress)
+{
+
+ return {
+ .planeCount = 2,
+ .bytesPerLine = {
+ toInt(frame.stride),
+ toInt(frame.uv_stride)
+ },
+ .data = {
+ baseAddress,
+ baseAddress + frame.uv_offset
+ },
+ .dataSize = {
+ toInt(frame.stride * frame.height),
+ toInt(frame.uv_stride * frame.height / 2)
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_rgb8888_t &frame,
+ unsigned char *baseAddress)
+{
+ return {
+ .planeCount = 1,
+ .bytesPerLine = {
+ toInt(frame.stride)
+ },
+ .data = {
+ baseAddress
+ },
+ .dataSize = {
+ toInt(frame.stride * frame.height),
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_gray8_t &frame,
+ unsigned char *baseAddress)
+{
+ return {
+ .planeCount = 1,
+ .bytesPerLine = {
+ toInt(frame.stride)
+ },
+ .data = {
+ baseAddress
+ },
+ .dataSize = {
+ toInt(frame.stride * frame.height)
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_cbycry_t &frame,
+ unsigned char *baseAddress)
+{
+ return {
+ .planeCount = 1,
+ .bytesPerLine = {
+ toInt(frame.stride)
+ },
+ .data = {
+ baseAddress
+ },
+ .dataSize = {
+ toInt(frame.bufsize),
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_ycbcr420p_t &frame,
+ unsigned char *baseAddress)
+{
+ return {
+ .planeCount = 3,
+ .bytesPerLine = {
+ toInt(frame.y_stride),
+ frame.cb_stride,
+ frame.cr_stride,
+ },
+ .data = {
+ baseAddress,
+ baseAddress + frame.cb_offset,
+ baseAddress + frame.cr_offset,
+ },
+ .dataSize = {
+ toInt(frame.y_stride * frame.height),
+ toInt(frame.cb_stride * frame.height / 2),
+ toInt(frame.cr_stride * frame.height / 2)
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_frame_ycbycr_t &frame,
+ unsigned char *baseAddress)
+{
+ return {
+ .planeCount = 1,
+ .bytesPerLine = {
+ toInt(frame.stride)
+ },
+ .data = {
+ baseAddress
+ },
+ .dataSize = {
+ toInt(frame.stride * frame.height)
+ }
+ };
+}
+
+static QAbstractVideoBuffer::MapData mapData(const camera_buffer_t *buffer,
+ unsigned char *baseAddress)
+{
+ switch (buffer->frametype) {
+ case CAMERA_FRAMETYPE_NV12:
+ return mapData(buffer->framedesc.nv12, baseAddress);
+ case CAMERA_FRAMETYPE_RGB8888:
+ return mapData(buffer->framedesc.rgb8888, baseAddress);
+ case CAMERA_FRAMETYPE_GRAY8:
+ return mapData(buffer->framedesc.gray8, baseAddress);
+ case CAMERA_FRAMETYPE_CBYCRY:
+ return mapData(buffer->framedesc.cbycry, baseAddress);
+ case CAMERA_FRAMETYPE_YCBCR420P:
+ return mapData(buffer->framedesc.ycbcr420p, baseAddress);
+ case CAMERA_FRAMETYPE_YCBYCR:
+ return mapData(buffer->framedesc.ycbycr, baseAddress);
+ default:
+ break;
+ }
+
+ return {};
+}
+
+static constexpr QSize frameSize(const camera_buffer_t *buffer)
+{
+ switch (buffer->frametype) {
+ case CAMERA_FRAMETYPE_NV12:
+ return frameSize(buffer->framedesc.nv12);
+ case CAMERA_FRAMETYPE_RGB8888:
+ return frameSize(buffer->framedesc.rgb8888);
+ case CAMERA_FRAMETYPE_GRAY8:
+ return frameSize(buffer->framedesc.gray8);
+ case CAMERA_FRAMETYPE_CBYCRY:
+ return frameSize(buffer->framedesc.cbycry);
+ case CAMERA_FRAMETYPE_YCBCR420P:
+ return frameSize(buffer->framedesc.ycbcr420p);
+ case CAMERA_FRAMETYPE_YCBYCR:
+ return frameSize(buffer->framedesc.ycbycr);
+ default:
+ break;
+ }
+
+ return {};
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxCameraFrameBuffer::QQnxCameraFrameBuffer(const camera_buffer_t *buffer, QRhi *rhi)
+ : QHwVideoBuffer(rhi ? QVideoFrame::RhiTextureHandle : QVideoFrame::NoHandle, rhi),
+ m_rhi(rhi),
+ m_pixelFormat(::frameTypeToPixelFormat(buffer->frametype)),
+ m_dataSize(::bufferDataSize(buffer))
+{
+ if (m_dataSize <= 0)
+ return;
+
+ m_data = std::make_unique<unsigned char[]>(m_dataSize);
+
+ memcpy(m_data.get(), buffer->framebuf, m_dataSize);
+
+ m_mapData = ::mapData(buffer, m_data.get());
+
+ m_frameSize = ::frameSize(buffer);
+}
+
+QAbstractVideoBuffer::MapData QQnxCameraFrameBuffer::map(QtVideo::MapMode)
+{
+ return m_mapData;
+}
+
+void QQnxCameraFrameBuffer::unmap()
+{
+}
+
+QVideoFrameFormat::PixelFormat QQnxCameraFrameBuffer::pixelFormat() const
+{
+ return m_pixelFormat;
+}
+
+QSize QQnxCameraFrameBuffer::size() const
+{
+ return m_frameSize;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer_p.h b/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer_p.h
new file mode 100644
index 000000000..20f724552
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxcameraframebuffer_p.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2022 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQNXCAMERAFRAMEBUFFER_H
+#define QQNXCAMERAFRAMEBUFFER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qhwvideobuffer_p.h>
+
+#include <QtCore/qsize.h>
+
+#include <camera/camera_api.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+class QRhi;
+
+class QQnxCameraFrameBuffer : public QHwVideoBuffer
+{
+public:
+ explicit QQnxCameraFrameBuffer(const camera_buffer_t *buffer, QRhi *rhi = nullptr);
+
+ QQnxCameraFrameBuffer(const QQnxCameraFrameBuffer&) = delete;
+ QQnxCameraFrameBuffer& operator=(const QQnxCameraFrameBuffer&) = delete;
+
+ MapData map(QtVideo::MapMode mode) override;
+ void unmap() override;
+
+ QVideoFrameFormat::PixelFormat pixelFormat() const;
+
+ QSize size() const;
+
+private:
+ QRhi *m_rhi;
+
+ QVideoFrameFormat::PixelFormat m_pixelFormat;
+
+ std::unique_ptr<unsigned char[]> m_data;
+
+ size_t m_dataSize;
+
+ MapData m_mapData;
+
+ QSize m_frameSize;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/camera/qqnxcamerahandle_p.h b/src/plugins/multimedia/qnx/camera/qqnxcamerahandle_p.h
new file mode 100644
index 000000000..3d7863dc2
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxcamerahandle_p.h
@@ -0,0 +1,102 @@
+// 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
+#ifndef QQNXCAMERAHANDLE_P_H
+#define QQNXCAMERAHANDLE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <camera/camera_api.h>
+
+#include <utility>
+
+class QQnxCameraHandle
+{
+public:
+ QQnxCameraHandle() = default;
+
+ explicit QQnxCameraHandle(camera_handle_t h)
+ : m_handle (h) {}
+
+ explicit QQnxCameraHandle(QQnxCameraHandle &&other)
+ : m_handle(other.m_handle)
+ , m_lastError(other.m_lastError)
+ {
+ other = QQnxCameraHandle();
+ }
+
+ QQnxCameraHandle(const QQnxCameraHandle&) = delete;
+
+ QQnxCameraHandle& operator=(QQnxCameraHandle&& other)
+ {
+ m_handle = other.m_handle;
+ m_lastError = other.m_lastError;
+
+ other = QQnxCameraHandle();
+
+ return *this;
+ }
+
+ ~QQnxCameraHandle()
+ {
+ close();
+ }
+
+ bool open(camera_unit_t unit, uint32_t mode)
+ {
+ if (isOpen()) {
+ m_lastError = CAMERA_EALREADY;
+ return false;
+ }
+
+ return cacheError(camera_open, unit, mode, &m_handle);
+ }
+
+ bool close()
+ {
+ if (!isOpen())
+ return true;
+
+ const bool success = cacheError(camera_close, m_handle);
+ m_handle = CAMERA_HANDLE_INVALID;
+
+ return success;
+ }
+
+ camera_handle_t get() const
+ {
+ return m_handle;
+ }
+
+ bool isOpen() const
+ {
+ return m_handle != CAMERA_HANDLE_INVALID;
+ }
+
+ camera_error_t lastError() const
+ {
+ return m_lastError;
+ }
+
+private:
+ template <typename Func, typename ...Args>
+ bool cacheError(Func f, Args &&...args)
+ {
+ m_lastError = f(std::forward<Args>(args)...);
+
+ return m_lastError == CAMERA_EOK;
+ }
+
+ camera_handle_t m_handle = CAMERA_HANDLE_INVALID;
+ camera_error_t m_lastError = CAMERA_EOK;
+};
+
+#endif
diff --git a/src/plugins/multimedia/qnx/camera/qqnximagecapture.cpp b/src/plugins/multimedia/qnx/camera/qqnximagecapture.cpp
new file mode 100644
index 000000000..3983dddbb
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnximagecapture.cpp
@@ -0,0 +1,257 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnximagecapture_p.h"
+
+#include "qqnxplatformcamera_p.h"
+#include "qqnxmediacapturesession_p.h"
+#include "qqnxcamera_p.h"
+#include "qfile.h"
+
+#include <private/qmediastoragelocation_p.h>
+
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qfuture.h>
+#include <QtCore/qpromise.h>
+#include <QtCore/qthread.h>
+
+#include <camera/camera_api.h>
+
+using namespace Qt::Literals::StringLiterals;
+
+static QString formatExtension(QImageCapture::FileFormat format)
+{
+ switch (format) {
+ case QImageCapture::JPEG:
+ return u"jpg"_s;
+ case QImageCapture::PNG:
+ return u"png"_s;
+ case QImageCapture::WebP:
+ case QImageCapture::Tiff:
+ case QImageCapture::UnspecifiedFormat:
+ break;
+ }
+
+ return {};
+}
+
+static QString resolveFileName(const QString &fileName, QImageCapture::FileFormat format)
+{
+ const QString extension = formatExtension(format);
+
+ if (extension.isEmpty())
+ return {};
+
+ if (fileName.isEmpty()) {
+ return QMediaStorageLocation::generateFileName(QString(),
+ QStandardPaths::PicturesLocation, extension);
+ }
+
+ if (fileName.endsWith(extension))
+ return QFileInfo(fileName).canonicalFilePath();
+
+ QString path = fileName;
+ path.append(u".%1"_s.arg(extension));
+
+ return QFileInfo(path).canonicalFilePath();
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxImageCapture::QQnxImageCapture(QImageCapture *parent)
+ : QPlatformImageCapture(parent)
+{
+}
+
+bool QQnxImageCapture::isReadyForCapture() const
+{
+ return m_camera && m_camera->isActive();
+}
+
+int QQnxImageCapture::capture(const QString &fileName)
+{
+ if (!isReadyForCapture()) {
+ Q_EMIT error(-1, QImageCapture::NotReadyError, QPlatformImageCapture::msgCameraNotReady());
+ return -1;
+ }
+
+ // default to PNG format if no format has been specified
+ const QImageCapture::FileFormat format =
+ m_settings.format() == QImageCapture::UnspecifiedFormat
+ ? QImageCapture::PNG : m_settings.format();
+
+ const QString resolvedFileName = resolveFileName(fileName, format);
+
+ if (resolvedFileName.isEmpty()) {
+ const QString errorMessage = (u"Invalid file format: %1"_s).arg(
+ QImageCapture::fileFormatName(format));
+
+ Q_EMIT error(-1, QImageCapture::NotSupportedFeatureError, errorMessage);
+ return -1;
+ }
+
+ const int id = m_lastId++;
+
+ auto callback = [this, id, fn=std::move(resolvedFileName)](const QVideoFrame &frame) {
+ saveFrame(id, frame, fn);
+ };
+
+ m_camera->requestVideoFrame(std::move(callback));
+
+ return id;
+}
+
+int QQnxImageCapture::captureToBuffer()
+{
+ if (!isReadyForCapture()) {
+ Q_EMIT error(-1, QImageCapture::NotReadyError, QPlatformImageCapture::msgCameraNotReady());
+ return -1;
+ }
+
+ const int id = m_lastId++;
+
+ auto callback = [this, id](const QVideoFrame &frame) { decodeFrame(id, frame); };
+
+ m_camera->requestVideoFrame(std::move(callback));
+
+ return id;
+}
+
+QFuture<QImage> QQnxImageCapture::decodeFrame(int id, const QVideoFrame &frame)
+{
+ if (!frame.isValid()) {
+ Q_EMIT error(id, QImageCapture::NotReadyError, u"Invalid frame"_s);
+ return {};
+ }
+
+ QPromise<QImage> promise;
+ QFuture<QImage> future = promise.future();
+
+ // converting a QVideoFrame to QImage is an expensive operation
+ // run it on a background thread to prevent it from stalling the UI
+ auto runner = [frame, promise=std::move(promise)]() mutable {
+ promise.start();
+ promise.addResult(frame.toImage());
+ promise.finish();
+ };
+
+ auto *worker = QThread::create(std::move(runner));
+
+ auto onFinished = [this, worker, id, future]() mutable {
+ worker->deleteLater();
+
+ if (future.isValid()) {
+ Q_EMIT imageCaptured(id, future.result());
+ } else {
+ qWarning("QQnxImageCapture: failed to capture image to buffer");
+ }
+ };
+
+ connect(worker, &QThread::finished, this, std::move(onFinished));
+
+ Q_EMIT imageExposed(id);
+
+ worker->start();
+
+ return future;
+}
+
+void QQnxImageCapture::saveFrame(int id, const QVideoFrame &frame, const QString &fileName)
+{
+ QFuture<QImage> decodeFuture = decodeFrame(id, frame);
+
+ if (decodeFuture.isCanceled())
+ return;
+
+ QPromise<bool> promise;
+ QFuture<bool> saveFuture = promise.future();
+
+ // writing a QImage to disk is a _very_ expensive operation
+ // run it on a background thread to prevent it from stalling the UI
+ auto runner = [future=std::move(decodeFuture),
+ promise=std::move(promise), fileName]() mutable {
+ promise.start();
+ promise.addResult(future.result().save(fileName));
+ promise.finish();
+ };
+
+ auto *worker = QThread::create(std::move(runner));
+
+ auto onFinished = [this, worker, id, future=std::move(saveFuture), fn=std::move(fileName)]() {
+ worker->deleteLater();
+
+ if (future.isValid() && future.result())
+ Q_EMIT imageSaved(id, fn);
+ else
+ Q_EMIT error(id, QImageCapture::NotSupportedFeatureError, u"Failed to save image"_s);
+ };
+
+ connect(worker, &QThread::finished, this, std::move(onFinished));
+
+ worker->start();
+}
+
+QImageEncoderSettings QQnxImageCapture::imageSettings() const
+{
+ return m_settings;
+}
+
+void QQnxImageCapture::setImageSettings(const QImageEncoderSettings &settings)
+{
+ m_settings = settings;
+}
+
+void QQnxImageCapture::setCaptureSession(QQnxMediaCaptureSession *captureSession)
+{
+ if (m_session == captureSession)
+ return;
+
+ if (m_session)
+ m_session->disconnect(this);
+
+ m_session = captureSession;
+
+ if (m_session) {
+ connect(m_session, &QQnxMediaCaptureSession::cameraChanged,
+ this, &QQnxImageCapture::onCameraChanged);
+ }
+
+ onCameraChanged();
+}
+
+void QQnxImageCapture::onCameraChanged()
+{
+ if (m_camera)
+ m_camera->disconnect(this);
+
+ m_camera = m_session ? static_cast<QQnxPlatformCamera*>(m_session->camera()) : nullptr;
+
+ if (m_camera) {
+ connect(m_camera, &QQnxPlatformCamera::activeChanged,
+ this, &QQnxImageCapture::onCameraChanged);
+ }
+
+ updateReadyForCapture();
+}
+
+void QQnxImageCapture::onCameraActiveChanged(bool active)
+{
+ Q_UNUSED(active);
+
+ updateReadyForCapture();
+}
+
+void QQnxImageCapture::updateReadyForCapture()
+{
+ const bool readyForCapture = isReadyForCapture();
+
+ if (m_lastReadyForCapture == readyForCapture)
+ return;
+
+ m_lastReadyForCapture = readyForCapture;
+
+ Q_EMIT readyForCaptureChanged(m_lastReadyForCapture);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnximagecapture_p.cpp"
diff --git a/src/plugins/multimedia/qnx/camera/qqnximagecapture_p.h b/src/plugins/multimedia/qnx/camera/qqnximagecapture_p.h
new file mode 100644
index 000000000..832039654
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnximagecapture_p.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQnxImageCapture_H
+#define QQnxImageCapture_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformimagecapture_p.h>
+
+#include <QtCore/qfuture.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxMediaCaptureSession;
+class QQnxPlatformCamera;
+
+class QThread;
+
+class QQnxImageCapture : public QPlatformImageCapture
+{
+ Q_OBJECT
+public:
+ explicit QQnxImageCapture(QImageCapture *parent);
+
+ bool isReadyForCapture() const override;
+
+ int capture(const QString &fileName) override;
+ int captureToBuffer() override;
+
+ QImageEncoderSettings imageSettings() const override;
+ void setImageSettings(const QImageEncoderSettings &settings) override;
+
+ void setCaptureSession(QQnxMediaCaptureSession *session);
+
+private:
+ QFuture<QImage> decodeFrame(int id, const QVideoFrame &frame);
+ void saveFrame(int id, const QVideoFrame &frame, const QString &fileName);
+
+ void onCameraChanged();
+ void onCameraActiveChanged(bool active);
+ void updateReadyForCapture();
+
+ QQnxMediaCaptureSession *m_session = nullptr;
+ QQnxPlatformCamera *m_camera = nullptr;
+
+ int m_lastId = 0;
+ QImageEncoderSettings m_settings;
+
+ bool m_lastReadyForCapture = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/camera/qqnxplatformcamera.cpp b/src/plugins/multimedia/qnx/camera/qqnxplatformcamera.cpp
new file mode 100644
index 000000000..b604f4561
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxplatformcamera.cpp
@@ -0,0 +1,426 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#undef QT_NO_CONTEXTLESS_CONNECT // Remove after porting connect() calls
+
+#include "qqnxplatformcamera_p.h"
+#include "qqnxcameraframebuffer_p.h"
+#include "qqnxmediacapturesession_p.h"
+#include "qqnxvideosink_p.h"
+
+#include <qcameradevice.h>
+#include <qmediadevices.h>
+
+#include <private/qmediastoragelocation_p.h>
+#include <private/qvideoframe_p.h>
+
+#include <camera/camera_api.h>
+#include <camera/camera_3a.h>
+
+#include <algorithm>
+#include <array>
+
+#include <dlfcn.h>
+
+struct FocusModeMapping
+{
+ QCamera::FocusMode qt;
+ camera_focusmode_t qnx;
+};
+
+constexpr std::array<FocusModeMapping, 6> focusModes {{
+ { QCamera::FocusModeAuto, CAMERA_FOCUSMODE_CONTINUOUS_AUTO },
+ { QCamera::FocusModeAutoFar, CAMERA_FOCUSMODE_CONTINUOUS_AUTO },
+ { QCamera::FocusModeInfinity, CAMERA_FOCUSMODE_CONTINUOUS_AUTO },
+ { QCamera::FocusModeAutoNear, CAMERA_FOCUSMODE_CONTINUOUS_MACRO },
+ { QCamera::FocusModeHyperfocal, CAMERA_FOCUSMODE_EDOF },
+ { QCamera::FocusModeManual, CAMERA_FOCUSMODE_MANUAL },
+}};
+
+template <typename Mapping, typename From, typename To, size_t N>
+static constexpr To convert(const std::array<Mapping, N> &mapping,
+ From Mapping::* from, To Mapping::* to, From value, To defaultValue)
+{
+ for (const Mapping &m : mapping) {
+ const auto fromValue = m.*from;
+ const auto toValue = m.*to;
+
+ if (value == fromValue)
+ return toValue;
+ }
+
+ return defaultValue;
+}
+
+static constexpr camera_focusmode_t qnxFocusMode(QCamera::FocusMode mode)
+{
+ return convert(focusModes, &FocusModeMapping::qt,
+ &FocusModeMapping::qnx, mode, CAMERA_FOCUSMODE_CONTINUOUS_AUTO);
+}
+
+static constexpr QCamera::FocusMode qtFocusMode(camera_focusmode_t mode)
+{
+ return convert(focusModes, &FocusModeMapping::qnx,
+ &FocusModeMapping::qt, mode, QCamera::FocusModeAuto);
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxPlatformCamera::QQnxPlatformCamera(QCamera *parent)
+ : QPlatformCamera(parent)
+{
+ if (parent)
+ setCamera(parent->cameraDevice());
+ else
+ setCamera(QMediaDevices::defaultVideoInput());
+}
+
+QQnxPlatformCamera::~QQnxPlatformCamera()
+{
+ stop();
+}
+
+bool QQnxPlatformCamera::isActive() const
+{
+ return m_qnxCamera && m_qnxCamera->isActive();
+}
+
+void QQnxPlatformCamera::setActive(bool active)
+{
+ if (active)
+ start();
+ else
+ stop();
+}
+
+void QQnxPlatformCamera::start()
+{
+ if (!m_qnxCamera || isActive())
+ return;
+
+ if (m_session)
+ m_videoSink = m_session->videoSink();
+
+ m_qnxCamera->start();
+
+ Q_EMIT activeChanged(true);
+}
+
+void QQnxPlatformCamera::stop()
+{
+ if (!m_qnxCamera)
+ return;
+
+ m_qnxCamera->stop();
+
+ m_videoSink = nullptr;
+
+ Q_EMIT activeChanged(false);
+}
+
+void QQnxPlatformCamera::setCamera(const QCameraDevice &camera)
+{
+ if (m_cameraDevice == camera)
+ return;
+
+ const auto cameraUnit = static_cast<camera_unit_t>(camera.id().toUInt());
+
+ m_qnxCamera = std::make_unique<QQnxCamera>(cameraUnit);
+
+ connect(m_qnxCamera.get(), &QQnxCamera::focusModeChanged,
+ [this](camera_focusmode_t mode) { Q_EMIT focusModeChanged(qtFocusMode(mode)); });
+ connect(m_qnxCamera.get(), &QQnxCamera::customFocusPointChanged,
+ this, &QQnxPlatformCamera::customFocusPointChanged);
+ connect(m_qnxCamera.get(), &QQnxCamera::frameAvailable,
+ this, &QQnxPlatformCamera::onFrameAvailable, Qt::QueuedConnection);
+
+ m_cameraDevice = camera;
+
+ updateCameraFeatures();
+}
+
+bool QQnxPlatformCamera::setCameraFormat(const QCameraFormat &format)
+{
+ const QSize resolution = format.resolution();
+
+ if (resolution.isEmpty()) {
+ qWarning("QQnxPlatformCamera: invalid resolution requested");
+ return false;
+ }
+
+ return m_qnxCamera->setCameraFormat(resolution.width(),
+ resolution.height(), format.maxFrameRate());
+}
+
+void QQnxPlatformCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
+{
+ if (m_session == session)
+ return;
+
+ m_session = static_cast<QQnxMediaCaptureSession *>(session);
+}
+
+bool QQnxPlatformCamera::isFocusModeSupported(QCamera::FocusMode mode) const
+{
+ if (!m_qnxCamera)
+ return false;
+
+ return m_qnxCamera->supportedFocusModes().contains(::qnxFocusMode(mode));
+}
+
+void QQnxPlatformCamera::setFocusMode(QCamera::FocusMode mode)
+{
+ if (!m_qnxCamera)
+ return;
+
+ m_qnxCamera->setFocusMode(::qnxFocusMode(mode));
+}
+
+void QQnxPlatformCamera::setCustomFocusPoint(const QPointF &point)
+{
+ if (!m_qnxCamera)
+ return;
+
+ m_qnxCamera->setCustomFocusPoint(point);
+}
+
+void QQnxPlatformCamera::setFocusDistance(float distance)
+{
+ if (!m_qnxCamera)
+ return;
+
+ const int maxDistance = m_qnxCamera->maxFocusStep();
+
+ if (maxDistance < 0)
+ return;
+
+ const int qnxDistance = maxDistance * std::min(distance, 1.0f);
+
+ m_qnxCamera->setManualFocusStep(qnxDistance);
+}
+
+void QQnxPlatformCamera::zoomTo(float factor, float)
+{
+ if (!m_qnxCamera)
+ return;
+
+ const uint32_t minZoom = m_qnxCamera->minimumZoomLevel();
+ const uint32_t maxZoom = m_qnxCamera->maximumZoomLevel();
+
+ if (maxZoom <= minZoom)
+ return;
+
+ // QNX has an integer based API. Interpolate between the levels according to the factor we get
+ const float max = maxZoomFactor();
+ const float min = minZoomFactor();
+
+ if (max <= min)
+ return;
+
+ factor = qBound(min, factor, max) - min;
+
+ const uint32_t zoom = minZoom
+ + static_cast<uint32_t>(qRound(factor*(maxZoom - minZoom)/(max - min)));
+
+ if (m_qnxCamera->setZoomFactor(zoom))
+ zoomFactorChanged(factor);
+}
+
+void QQnxPlatformCamera::setExposureCompensation(float ev)
+{
+ if (!m_qnxCamera)
+ return;
+
+ m_qnxCamera->setEvOffset(ev);
+}
+
+int QQnxPlatformCamera::isoSensitivity() const
+{
+ if (!m_qnxCamera)
+ return 0;
+
+ return m_qnxCamera->manualIsoSensitivity();
+}
+
+void QQnxPlatformCamera::setManualIsoSensitivity(int value)
+{
+ if (!m_qnxCamera)
+ return;
+
+ const uint32_t isoValue = std::max(0, value);
+
+ m_qnxCamera->setManualIsoSensitivity(isoValue);
+}
+
+void QQnxPlatformCamera::setManualExposureTime(float seconds)
+{
+ if (!m_qnxCamera)
+ return;
+
+ m_qnxCamera->setManualExposureTime(seconds);
+}
+
+float QQnxPlatformCamera::exposureTime() const
+{
+ if (!m_qnxCamera)
+ return 0.0;
+
+ return static_cast<float>(m_qnxCamera->manualExposureTime());
+}
+
+bool QQnxPlatformCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
+{
+ if (m_maxColorTemperature != 0)
+ return true;
+
+ return mode == QCamera::WhiteBalanceAuto;
+}
+
+void QQnxPlatformCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
+{
+ if (!m_qnxCamera)
+ return;
+
+ if (mode == QCamera::WhiteBalanceAuto) {
+ m_qnxCamera->setWhiteBalanceMode(CAMERA_WHITEBALANCEMODE_AUTO);
+ } else {
+ m_qnxCamera->setWhiteBalanceMode(CAMERA_WHITEBALANCEMODE_MANUAL);
+ setColorTemperature(colorTemperatureForWhiteBalance(mode));
+ }
+}
+
+void QQnxPlatformCamera::setColorTemperature(int temperature)
+{
+ if (!m_qnxCamera)
+ return;
+
+ const auto normalizedTemp = std::clamp<uint32_t>(std::max(0, temperature),
+ m_minColorTemperature, m_maxColorTemperature);
+
+ if (m_qnxCamera->hasContinuousWhiteBalanceValues()) {
+ m_qnxCamera->setManualWhiteBalance(normalizedTemp);
+ } else {
+ uint32_t delta = std::numeric_limits<uint32_t>::max();
+ uint32_t closestTemp = 0;
+
+ for (uint32_t value : m_qnxCamera->supportedWhiteBalanceValues()) {
+ const auto &[min, max] = std::minmax(value, normalizedTemp);
+ const uint32_t currentDelta = max - min;
+
+ if (currentDelta < delta) {
+ closestTemp = value;
+ delta = currentDelta;
+ }
+ }
+
+ m_qnxCamera->setManualWhiteBalance(closestTemp);
+ }
+}
+
+bool QQnxPlatformCamera::startVideoRecording()
+{
+ if (!m_qnxCamera) {
+ qWarning("QQnxPlatformCamera: cannot start video recording - no no camera assigned");
+ return false;
+ }
+
+ if (!isVideoEncodingSupported()) {
+ qWarning("QQnxPlatformCamera: cannot start video recording - not supported");
+ return false;
+ }
+
+ if (!m_qnxCamera->isActive()) {
+ qWarning("QQnxPlatformCamera: cannot start video recording - camera not started");
+ return false;
+ }
+
+ const QString container = m_encoderSettings.mimeType().preferredSuffix();
+ const QString location = QMediaStorageLocation::generateFileName(m_outputUrl.toLocalFile(),
+ QStandardPaths::MoviesLocation, container);
+
+#if 0
+ {
+ static void *libScreen = nullptr;
+
+ if (!libScreen)
+ libScreen = dlopen("/usr/lib/libscreen.so.1", RTLD_GLOBAL);
+ }
+#endif
+
+ qDebug() << "Recording to" << location;
+ return m_qnxCamera->startVideoRecording(location);
+}
+
+void QQnxPlatformCamera::requestVideoFrame(VideoFrameCallback cb)
+{
+ m_videoFrameRequests.emplace_back(std::move(cb));
+}
+
+bool QQnxPlatformCamera::isVideoEncodingSupported() const
+{
+ return m_qnxCamera && m_qnxCamera->hasFeature(CAMERA_FEATURE_VIDEO);
+}
+
+void QQnxPlatformCamera::setOutputUrl(const QUrl &url)
+{
+ m_outputUrl = url;
+}
+
+void QQnxPlatformCamera::setMediaEncoderSettings(const QMediaEncoderSettings &settings)
+{
+ m_encoderSettings = settings;
+}
+
+void QQnxPlatformCamera::updateCameraFeatures()
+{
+ if (!m_qnxCamera)
+ return;
+
+ QCamera::Features features = {};
+
+ if (m_qnxCamera->hasFeature(CAMERA_FEATURE_REGIONFOCUS))
+ features |= QCamera::Feature::CustomFocusPoint;
+
+ supportedFeaturesChanged(features);
+
+ minimumZoomFactorChanged(m_qnxCamera->minimumZoomLevel());
+ maximumZoomFactorChanged(m_qnxCamera->maximumZoomLevel());
+
+ const QList<uint32_t> wbValues = m_qnxCamera->supportedWhiteBalanceValues();
+
+ if (wbValues.isEmpty()) {
+ m_minColorTemperature = m_maxColorTemperature = 0;
+ } else {
+ const auto &[minTemp, maxTemp] = std::minmax_element(wbValues.begin(), wbValues.end());
+
+ m_minColorTemperature = *minTemp;
+ m_maxColorTemperature = *maxTemp;
+ }
+}
+
+void QQnxPlatformCamera::onFrameAvailable()
+{
+ if (!m_videoSink)
+ return;
+
+ std::unique_ptr<QQnxCameraFrameBuffer> currentFrameBuffer = m_qnxCamera->takeCurrentFrame();
+
+ if (!currentFrameBuffer)
+ return;
+
+ QVideoFrameFormat format(currentFrameBuffer->size(), currentFrameBuffer->pixelFormat());
+ const QVideoFrame actualFrame =
+ QVideoFramePrivate::createFrame(std::move(currentFrameBuffer), std::move(format));
+
+ m_videoSink->setVideoFrame(actualFrame);
+
+ if (!m_videoFrameRequests.empty()) {
+ VideoFrameCallback cb = std::move(m_videoFrameRequests.front());
+ m_videoFrameRequests.pop_front();
+ cb(actualFrame);
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxplatformcamera_p.cpp"
diff --git a/src/plugins/multimedia/qnx/camera/qqnxplatformcamera_p.h b/src/plugins/multimedia/qnx/camera/qqnxplatformcamera_p.h
new file mode 100644
index 000000000..3cbd17a4f
--- /dev/null
+++ b/src/plugins/multimedia/qnx/camera/qqnxplatformcamera_p.h
@@ -0,0 +1,113 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQNXPLATFORMCAMERA_H
+#define QQNXPLATFORMCAMERA_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqnxcamera_p.h"
+
+#include <private/qplatformcamera_p.h>
+#include <private/qplatformmediarecorder_p.h>
+
+#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qurl.h>
+
+#include <deque>
+#include <functional>
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxPlatformCameraFrameBuffer;
+class QQnxMediaCaptureSession;
+class QQnxVideoSink;
+class QQnxCameraFrameBuffer;
+
+class QQnxPlatformCamera : public QPlatformCamera
+{
+ Q_OBJECT
+public:
+ explicit QQnxPlatformCamera(QCamera *parent);
+ ~QQnxPlatformCamera();
+
+ bool isActive() const override;
+ void setActive(bool active) override;
+ void start();
+ void stop();
+
+ void setCamera(const QCameraDevice &camera) override;
+
+ bool setCameraFormat(const QCameraFormat &format) override;
+
+ void setCaptureSession(QPlatformMediaCaptureSession *session) override;
+
+ bool isFocusModeSupported(QCamera::FocusMode mode) const override;
+ void setFocusMode(QCamera::FocusMode mode) override;
+
+ void setCustomFocusPoint(const QPointF &point) override;
+
+ void setFocusDistance(float distance) override;
+
+ void zoomTo(float newZoomFactor, float rate = -1.) override;
+
+ void setExposureCompensation(float ev) override;
+
+ int isoSensitivity() const override;
+ void setManualIsoSensitivity(int value) override;
+ void setManualExposureTime(float seconds) override;
+ float exposureTime() const override;
+
+ bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const override;
+ void setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) override;
+ void setColorTemperature(int temperature) override;
+
+ void setOutputUrl(const QUrl &url);
+ void setMediaEncoderSettings(const QMediaEncoderSettings &settings);
+
+ bool startVideoRecording();
+
+ using VideoFrameCallback = std::function<void(const QVideoFrame&)>;
+ void requestVideoFrame(VideoFrameCallback cb);
+
+private:
+ void updateCameraFeatures();
+ void setColorTemperatureInternal(unsigned temp);
+
+ bool isVideoEncodingSupported() const;
+
+ void onFrameAvailable();
+
+ QQnxMediaCaptureSession *m_session = nullptr;
+ QQnxVideoSink *m_videoSink = nullptr;
+
+ QCameraDevice m_cameraDevice;
+
+ QUrl m_outputUrl;
+
+ QMediaEncoderSettings m_encoderSettings;
+
+ uint32_t m_minColorTemperature = 0;
+ uint32_t m_maxColorTemperature = 0;
+
+ QMutex m_currentFrameMutex;
+
+ std::unique_ptr<QQnxCamera> m_qnxCamera;
+ std::unique_ptr<QQnxCameraFrameBuffer> m_currentFrame;
+
+ std::deque<VideoFrameCallback> m_videoFrameRequests;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp
new file mode 100644
index 000000000..00a20bbd7
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder.cpp
@@ -0,0 +1,284 @@
+// 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 "qqnxaudiorecorder_p.h"
+#include "qqnxmediaeventthread_p.h"
+
+#include <QtCore/qcoreapplication.h>
+
+#include <private/qmediastoragelocation_p.h>
+
+#include <mm/renderer.h>
+
+#include <sys/stat.h>
+#include <sys/strm.h>
+
+static QByteArray buildDevicePath(const QByteArray &deviceId, const QMediaEncoderSettings &settings)
+{
+ QByteArray devicePath = QByteArrayLiteral("snd:/dev/snd/") + deviceId + QByteArrayLiteral("?");
+
+ if (settings.audioSampleRate() > 0)
+ devicePath += QByteArrayLiteral("frate=") + QByteArray::number(settings.audioSampleRate());
+
+ if (settings.audioChannelCount() > 0)
+ devicePath += QByteArrayLiteral("nchan=") + QByteArray::number(settings.audioChannelCount());
+
+ return devicePath;
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxAudioRecorder::QQnxAudioRecorder(QObject *parent)
+ : QObject(parent)
+{
+ openConnection();
+}
+
+QQnxAudioRecorder::~QQnxAudioRecorder()
+{
+ stop();
+ closeConnection();
+}
+
+void QQnxAudioRecorder::openConnection()
+{
+ static int idCounter = 0;
+
+ m_connection = ConnectionUniquePtr { mmr_connect(nullptr) };
+
+ if (!m_connection) {
+ qWarning("QQnxAudioRecorder: Unable to connect to the multimedia renderer");
+ return;
+ }
+
+ m_id = idCounter++;
+
+ char contextName[256];
+
+ std::snprintf(contextName, sizeof contextName, "QQnxAudioRecorder_%d_%llu",
+ m_id, QCoreApplication::applicationPid());
+
+ m_context = ContextUniquePtr { mmr_context_create(m_connection.get(),
+ contextName, 0, S_IRWXU|S_IRWXG|S_IRWXO) };
+
+ if (m_context) {
+ startMonitoring();
+ } else {
+ qWarning("QQnxAudioRecorder: Unable to create context");
+ closeConnection();
+ }
+}
+
+void QQnxAudioRecorder::closeConnection()
+{
+ m_context.reset();
+ m_context.reset();
+
+ stopMonitoring();
+}
+
+void QQnxAudioRecorder::attach()
+{
+ if (isAttached())
+ return;
+
+ const QString container = m_encoderSettings.mimeType().preferredSuffix();
+ const QString location = QMediaStorageLocation::generateFileName(m_outputUrl.toLocalFile(),
+ QStandardPaths::MusicLocation, container);
+
+ m_audioId = mmr_output_attach(m_context.get(), qPrintable(location), "file");
+
+ if (m_audioId == -1) {
+ qWarning("QQnxAudioRecorder: mmr_output_attach() for file failed");
+ return;
+ }
+
+ configureOutputBitRate();
+
+ const QByteArray devicePath = buildDevicePath(m_inputDeviceId, m_encoderSettings);
+
+ if (mmr_input_attach(m_context.get(), devicePath.constData(), "track") != 0) {
+ qWarning("QQnxAudioRecorder: mmr_input_attach() failed");
+ detach();
+ } else {
+ Q_EMIT actualLocationChanged(location);
+ }
+}
+
+void QQnxAudioRecorder::detach()
+{
+ if (!isAttached())
+ return;
+
+ mmr_input_detach(m_context.get());
+ mmr_output_detach(m_context.get(), m_audioId);
+
+ m_audioId = -1;
+}
+
+void QQnxAudioRecorder::configureOutputBitRate()
+{
+ const int bitRate = m_encoderSettings.audioBitRate();
+
+ if (!isAttached() || bitRate <= 0)
+ return;
+
+ char buf[12];
+ std::snprintf(buf, sizeof buf, "%d", bitRate);
+
+ strm_dict_t *dict = strm_dict_new();
+ dict = strm_dict_set(dict, "audio_bitrate", buf);
+
+ if (mmr_output_parameters(m_context.get(), m_audioId, dict) != 0)
+ qWarning("mmr_output_parameters: setting bitrate failed");
+}
+
+bool QQnxAudioRecorder::isAttached() const
+{
+ return m_context && m_audioId != -1;
+}
+
+void QQnxAudioRecorder::setInputDeviceId(const QByteArray &id)
+{
+ m_inputDeviceId = id;
+}
+
+void QQnxAudioRecorder::setOutputUrl(const QUrl &url)
+{
+ m_outputUrl = url;
+}
+
+void QQnxAudioRecorder::setMediaEncoderSettings(const QMediaEncoderSettings &settings)
+{
+ m_encoderSettings = settings;
+}
+
+void QQnxAudioRecorder::record()
+{
+ if (!isAttached()) {
+ attach();
+
+ if (!isAttached())
+ return;
+ }
+
+ if (mmr_play(m_context.get()) != 0)
+ qWarning("QQnxAudioRecorder: mmr_play() failed");
+}
+
+void QQnxAudioRecorder::stop()
+{
+ if (!isAttached())
+ return;
+
+ mmr_stop(m_context.get());
+
+ detach();
+}
+
+void QQnxAudioRecorder::startMonitoring()
+{
+ m_eventThread = std::make_unique<QQnxMediaEventThread>(m_context.get());
+
+ connect(m_eventThread.get(), &QQnxMediaEventThread::eventPending,
+ this, &QQnxAudioRecorder::readEvents);
+
+ m_eventThread->setObjectName(QStringLiteral("MmrAudioEventThread-") + QString::number(m_id));
+ m_eventThread->start();
+}
+
+void QQnxAudioRecorder::stopMonitoring()
+{
+ if (m_eventThread)
+ m_eventThread.reset();
+}
+
+void QQnxAudioRecorder::readEvents()
+{
+ while (const mmr_event_t *event = mmr_event_get(m_context.get())) {
+ if (event->type == MMR_EVENT_NONE)
+ break;
+
+ switch (event->type) {
+ case MMR_EVENT_STATUS:
+ handleMmEventStatus(event);
+ break;
+ case MMR_EVENT_STATE:
+ handleMmEventState(event);
+ break;
+ case MMR_EVENT_ERROR:
+ handleMmEventError(event);
+ break;
+ case MMR_EVENT_METADATA:
+ case MMR_EVENT_NONE:
+ case MMR_EVENT_OVERFLOW:
+ case MMR_EVENT_WARNING:
+ case MMR_EVENT_PLAYLIST:
+ case MMR_EVENT_INPUT:
+ case MMR_EVENT_OUTPUT:
+ case MMR_EVENT_CTXTPAR:
+ case MMR_EVENT_TRKPAR:
+ case MMR_EVENT_OTHER:
+ break;
+ }
+ }
+
+ if (m_eventThread)
+ m_eventThread->signalRead();
+}
+
+void QQnxAudioRecorder::handleMmEventStatus(const mmr_event_t *event)
+{
+ if (!event || event->type != MMR_EVENT_STATUS)
+ return;
+
+ if (!event->pos_str)
+ return;
+
+ const QByteArray valueBa(event->pos_str);
+
+ bool ok;
+ const qint64 duration = valueBa.toLongLong(&ok);
+
+ if (!ok)
+ qCritical("Could not parse duration from '%s'", valueBa.constData());
+ else
+ durationChanged(duration);
+}
+
+void QQnxAudioRecorder::handleMmEventState(const mmr_event_t *event)
+{
+ if (!event || event->type != MMR_EVENT_STATE)
+ return;
+
+ switch (event->state) {
+ case MMR_STATE_DESTROYED:
+ case MMR_STATE_IDLE:
+ case MMR_STATE_STOPPED:
+ Q_EMIT stateChanged(QMediaRecorder::StoppedState);
+ break;
+ case MMR_STATE_PLAYING:
+ Q_EMIT stateChanged(QMediaRecorder::RecordingState);
+ break;
+ }
+}
+
+void QQnxAudioRecorder::handleMmEventError(const mmr_event_t *event)
+{
+ if (!event)
+ return;
+
+ // When playback is explicitly stopped using mmr_stop(), mm-renderer
+ // generates a STATE event. When the end of media is reached, an ERROR
+ // event is generated and the error code contained in the event information
+ // is set to MMR_ERROR_NONE. When an error causes playback to stop,
+ // the error code is set to something else.
+ if (event->details.error.info.error_code == MMR_ERROR_NONE) {
+ //TODO add error
+ Q_EMIT stateChanged(QMediaRecorder::StoppedState);
+ detach();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxaudiorecorder_p.cpp"
diff --git a/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder_p.h b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder_p.h
new file mode 100644
index 000000000..f343cee14
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxaudiorecorder_p.h
@@ -0,0 +1,103 @@
+// 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
+#ifndef QQNXAUDIORECORDER_H
+#define QQNXAUDIORECORDER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "mmrenderertypes.h"
+
+#include <QByteArray>
+#include <QUrl>
+
+#include <QtCore/qobject.h>
+#include <QtCore/qtconfigmacros.h>
+
+#include <QtMultimedia/qmediarecorder.h>
+
+#include <private/qplatformmediarecorder_p.h>
+
+#include <mm/renderer.h>
+#include <mm/renderer/types.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxMediaEventThread;
+
+class QQnxAudioRecorder : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit QQnxAudioRecorder(QObject *parent = nullptr);
+ ~QQnxAudioRecorder();
+
+ void setInputDeviceId(const QByteArray &id);
+ void setOutputUrl(const QUrl &url);
+ void setMediaEncoderSettings(const QMediaEncoderSettings &settings);
+
+ void record();
+ void stop();
+
+Q_SIGNALS:
+ void stateChanged(QMediaRecorder::RecorderState state);
+ void durationChanged(qint64 durationMs);
+ void actualLocationChanged(const QUrl &location);
+
+private:
+ void openConnection();
+ void closeConnection();
+ void attach();
+ void detach();
+ void configureOutputBitRate();
+ void startMonitoring();
+ void stopMonitoring();
+ void readEvents();
+ void handleMmEventStatus(const mmr_event_t *event);
+ void handleMmEventState(const mmr_event_t *event);
+ void handleMmEventError(const mmr_event_t *event);
+
+ bool isAttached() const;
+
+ struct ContextDeleter
+ {
+ void operator()(mmr_context_t *ctx) { if (ctx) mmr_context_destroy(ctx); }
+ };
+
+ struct ConnectionDeleter
+ {
+ void operator()(mmr_connection_t *conn) { if (conn) mmr_disconnect(conn); }
+ };
+
+ using ContextUniquePtr = std::unique_ptr<mmr_context_t, ContextDeleter>;
+ ContextUniquePtr m_context;
+
+ using ConnectionUniquePtr = std::unique_ptr<mmr_connection_t, ConnectionDeleter>;
+ ConnectionUniquePtr m_connection;
+
+ int m_id = -1;
+ int m_audioId = -1;
+
+ QByteArray m_inputDeviceId;
+
+ QUrl m_outputUrl;
+
+ QMediaEncoderSettings m_encoderSettings;
+
+ std::unique_ptr<QQnxMediaEventThread> m_eventThread;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession.cpp b/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession.cpp
new file mode 100644
index 000000000..d73ca7e54
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession.cpp
@@ -0,0 +1,121 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnxmediacapturesession_p.h"
+
+#include "qqnxaudioinput_p.h"
+#include "qqnxplatformcamera_p.h"
+#include "qqnximagecapture_p.h"
+#include "qqnxmediarecorder_p.h"
+#include "qqnxvideosink_p.h"
+#include "qvideosink.h"
+
+QT_BEGIN_NAMESPACE
+
+QQnxMediaCaptureSession::QQnxMediaCaptureSession()
+ : QPlatformMediaCaptureSession()
+{
+}
+
+QQnxMediaCaptureSession::~QQnxMediaCaptureSession()
+{
+}
+
+QPlatformCamera *QQnxMediaCaptureSession::camera()
+{
+ return m_camera;
+}
+
+void QQnxMediaCaptureSession::setCamera(QPlatformCamera *camera)
+{
+ if (camera == m_camera)
+ return;
+
+ if (m_camera)
+ m_camera->setCaptureSession(nullptr);
+
+ m_camera = static_cast<QQnxPlatformCamera *>(camera);
+
+ if (m_camera)
+ m_camera->setCaptureSession(this);
+
+ emit cameraChanged();
+}
+
+QPlatformImageCapture *QQnxMediaCaptureSession::imageCapture()
+{
+ return m_imageCapture;
+}
+
+void QQnxMediaCaptureSession::setImageCapture(QPlatformImageCapture *imageCapture)
+{
+ if (m_imageCapture == imageCapture)
+ return;
+
+ if (m_imageCapture)
+ m_imageCapture->setCaptureSession(nullptr);
+
+ m_imageCapture = static_cast<QQnxImageCapture *>(imageCapture);
+
+ if (m_imageCapture)
+ m_imageCapture->setCaptureSession(this);
+
+ emit imageCaptureChanged();
+}
+
+QPlatformMediaRecorder *QQnxMediaCaptureSession::mediaRecorder()
+{
+ return m_mediaRecorder;
+}
+
+void QQnxMediaCaptureSession::setMediaRecorder(QPlatformMediaRecorder *mediaRecorder)
+{
+ if (m_mediaRecorder == mediaRecorder)
+ return;
+
+ if (m_mediaRecorder)
+ m_mediaRecorder->setCaptureSession(nullptr);
+
+ m_mediaRecorder = static_cast<QQnxMediaRecorder *>(mediaRecorder);
+
+ if (m_mediaRecorder)
+ m_mediaRecorder->setCaptureSession(this);
+
+ emit encoderChanged();
+}
+
+void QQnxMediaCaptureSession::setAudioInput(QPlatformAudioInput *input)
+{
+ if (m_audioInput == input)
+ return;
+
+ m_audioInput = static_cast<QQnxAudioInput*>(input);
+}
+
+void QQnxMediaCaptureSession::setVideoPreview(QVideoSink *sink)
+{
+ auto qnxSink = sink ? static_cast<QQnxVideoSink *>(sink->platformVideoSink()) : nullptr;
+ if (m_videoSink == qnxSink)
+ return;
+ m_videoSink = qnxSink;
+}
+
+void QQnxMediaCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
+{
+ if (m_audioOutput == output)
+ return;
+ m_audioOutput = output;
+}
+
+QQnxAudioInput * QQnxMediaCaptureSession::audioInput() const
+{
+ return m_audioInput;
+}
+
+QQnxVideoSink * QQnxMediaCaptureSession::videoSink() const
+{
+ return m_videoSink;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxmediacapturesession_p.cpp"
diff --git a/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession_p.h b/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession_p.h
new file mode 100644
index 000000000..551416a61
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxmediacapturesession_p.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQNXMEDIACAPTURESESSION_H
+#define QQNXMEDIACAPTURESESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QObject>
+
+#include <private/qplatformmediacapture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxAudioInput;
+class QQnxPlatformCamera;
+class QQnxImageCapture;
+class QQnxMediaRecorder;
+class QQnxVideoSink;
+
+class QQnxMediaCaptureSession : public QPlatformMediaCaptureSession
+{
+ Q_OBJECT
+
+public:
+ QQnxMediaCaptureSession();
+ ~QQnxMediaCaptureSession();
+
+ QPlatformCamera *camera() override;
+ void setCamera(QPlatformCamera *camera) override;
+
+ QPlatformImageCapture *imageCapture() override;
+ void setImageCapture(QPlatformImageCapture *imageCapture) override;
+
+ QPlatformMediaRecorder *mediaRecorder() override;
+ void setMediaRecorder(QPlatformMediaRecorder *mediaRecorder) override;
+
+ void setAudioInput(QPlatformAudioInput *input) override;
+
+ void setVideoPreview(QVideoSink *sink) override;
+
+ void setAudioOutput(QPlatformAudioOutput *output) override;
+
+ QQnxAudioInput *audioInput() const;
+
+ QQnxVideoSink *videoSink() const;
+
+private:
+ QQnxPlatformCamera *m_camera = nullptr;
+ QQnxImageCapture *m_imageCapture = nullptr;
+ QQnxMediaRecorder *m_mediaRecorder = nullptr;
+ QQnxAudioInput *m_audioInput = nullptr;
+ QPlatformAudioOutput *m_audioOutput = nullptr;
+ QQnxVideoSink *m_videoSink = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/capture/qqnxmediarecorder.cpp b/src/plugins/multimedia/qnx/capture/qqnxmediarecorder.cpp
new file mode 100644
index 000000000..62ac030db
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxmediarecorder.cpp
@@ -0,0 +1,115 @@
+// Copyright (C) 2016 Research In Motion
+// 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
+
+#undef QT_NO_CONTEXTLESS_CONNECT // Remove after porting connect() calls
+
+#include "qqnxmediarecorder_p.h"
+
+#include "qqnxplatformcamera_p.h"
+#include "qqnxaudioinput_p.h"
+#include "qqnxcamera_p.h"
+#include "qqnxmediacapturesession_p.h"
+
+#include <private/qplatformcamera_p.h>
+
+#include <QDebug>
+#include <QUrl>
+
+QT_BEGIN_NAMESPACE
+
+QQnxMediaRecorder::QQnxMediaRecorder(QMediaRecorder *parent)
+ : QPlatformMediaRecorder(parent)
+{
+}
+
+bool QQnxMediaRecorder::isLocationWritable(const QUrl &/*location*/) const
+{
+ return true;
+}
+
+void QQnxMediaRecorder::setCaptureSession(QQnxMediaCaptureSession *session)
+{
+ m_session = session;
+}
+
+void QQnxMediaRecorder::record(QMediaEncoderSettings &settings)
+{
+ if (!m_session)
+ return;
+
+ m_audioRecorder.disconnect();
+
+ if (hasCamera()) {
+ startVideoRecording(settings);
+ } else {
+ QObject::connect(&m_audioRecorder, &QQnxAudioRecorder::durationChanged,
+ [this](qint64 d) { durationChanged(d); });
+
+ QObject::connect(&m_audioRecorder, &QQnxAudioRecorder::stateChanged,
+ [this](QMediaRecorder::RecorderState s) { stateChanged(s); });
+
+ QObject::connect(&m_audioRecorder, &QQnxAudioRecorder::actualLocationChanged,
+ [this](const QUrl &l) { actualLocationChanged(l); });
+
+ startAudioRecording(settings);
+ }
+}
+
+void QQnxMediaRecorder::stop()
+{
+ if (hasCamera()) {
+ stopVideoRecording();
+ } else {
+ m_audioRecorder.stop();
+ }
+}
+
+void QQnxMediaRecorder::startAudioRecording(QMediaEncoderSettings &settings)
+{
+ if (!m_session)
+ return;
+
+ QQnxAudioInput *audioInput = m_session->audioInput();
+
+ if (!audioInput)
+ return;
+
+ m_audioRecorder.setInputDeviceId(audioInput->device.id());
+ m_audioRecorder.setMediaEncoderSettings(settings);
+ m_audioRecorder.setOutputUrl(outputLocation());
+ m_audioRecorder.record();
+}
+
+void QQnxMediaRecorder::startVideoRecording(QMediaEncoderSettings &settings)
+{
+ if (!hasCamera())
+ return;
+
+ auto *camera = static_cast<QQnxPlatformCamera*>(m_session->camera());
+
+ camera->setMediaEncoderSettings(settings);
+ camera->setOutputUrl(outputLocation());
+
+ if (camera->startVideoRecording())
+ stateChanged(QMediaRecorder::RecordingState);
+}
+
+void QQnxMediaRecorder::stopVideoRecording()
+{
+ if (!hasCamera())
+ return;
+
+ auto *camera = static_cast<QQnxPlatformCamera*>(m_session->camera());
+
+ camera->stop();
+
+ stateChanged(QMediaRecorder::StoppedState);
+}
+
+bool QQnxMediaRecorder::hasCamera() const
+{
+ return m_session && m_session->camera();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/capture/qqnxmediarecorder_p.h b/src/plugins/multimedia/qnx/capture/qqnxmediarecorder_p.h
new file mode 100644
index 000000000..8b3ea21d3
--- /dev/null
+++ b/src/plugins/multimedia/qnx/capture/qqnxmediarecorder_p.h
@@ -0,0 +1,51 @@
+// 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
+#ifndef QQNXMEDIARECORDER_H
+#define QQNXMEDIARECORDER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqnxaudiorecorder_p.h"
+
+#include <private/qplatformmediarecorder_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxMediaCaptureSession;
+
+class QQnxMediaRecorder : public QPlatformMediaRecorder
+{
+public:
+ explicit QQnxMediaRecorder(QMediaRecorder *parent);
+
+ bool isLocationWritable(const QUrl &location) const override;
+
+ void record(QMediaEncoderSettings &settings) override;
+ void stop() override;
+
+ void setCaptureSession(QQnxMediaCaptureSession *session);
+
+private:
+ bool hasCamera() const;
+
+ void startAudioRecording(QMediaEncoderSettings &settings);
+ void startVideoRecording(QMediaEncoderSettings &settings);
+ void stopVideoRecording();
+
+ QQnxAudioRecorder m_audioRecorder;
+
+ QQnxMediaCaptureSession *m_session = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/common/mmrenderertypes.h b/src/plugins/multimedia/qnx/common/mmrenderertypes.h
new file mode 100644
index 000000000..f1d498388
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/mmrenderertypes.h
@@ -0,0 +1,95 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef MMRENDERERTYPES_H
+#define MMRENDERERTYPES_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <mm/renderer.h>
+#include <mm/renderer/types.h>
+
+extern "C" {
+// ### replace with proper include: mm/renderer/events.h
+typedef enum mmr_state {
+ MMR_STATE_DESTROYED,
+ MMR_STATE_IDLE,
+ MMR_STATE_STOPPED,
+ MMR_STATE_PLAYING
+} mmr_state_t;
+
+typedef enum mmr_event_type {
+ MMR_EVENT_NONE,
+ MMR_EVENT_ERROR,
+ MMR_EVENT_STATE,
+ MMR_EVENT_OVERFLOW,
+ MMR_EVENT_WARNING,
+ MMR_EVENT_STATUS,
+ MMR_EVENT_METADATA,
+ MMR_EVENT_PLAYLIST,
+ MMR_EVENT_INPUT,
+ MMR_EVENT_OUTPUT,
+ MMR_EVENT_CTXTPAR,
+ MMR_EVENT_TRKPAR,
+ MMR_EVENT_OTHER
+} mmr_event_type_t;
+
+typedef struct mmr_event {
+ mmr_event_type_t type;
+ mmr_state_t state;
+ int speed;
+ union mmr_event_details {
+
+ struct mmr_event_state {
+ mmr_state_t oldstate;
+ int oldspeed;
+ } state;
+
+ struct mmr_event_error {
+ mmr_error_info_t info;
+ } error;
+
+ struct mmr_event_warning {
+ const char *str;
+ const strm_string_t *obj;
+ } warning;
+
+ struct mmr_event_metadata {
+ unsigned index;
+ } metadata;
+
+ struct mmr_event_trkparam {
+ unsigned index;
+ } trkparam;
+
+ struct mmr_event_playlist {
+ unsigned start;
+ unsigned end;
+ unsigned length;
+ } playlist;
+
+ struct mmr_event_output {
+ unsigned id;
+ } output;
+ } details;
+
+ const strm_string_t* pos_obj;
+ const char* pos_str;
+ const strm_dict_t* data;
+ const char* objname;
+ void* usrdata;
+} mmr_event_t;
+
+const mmr_event_t* mmr_event_get(mmr_context_t *ctxt);
+
+}
+
+#endif
diff --git a/src/plugins/multimedia/qnx/common/qqnxaudioinput.cpp b/src/plugins/multimedia/qnx/common/qqnxaudioinput.cpp
new file mode 100644
index 000000000..fff3cf1eb
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxaudioinput.cpp
@@ -0,0 +1,25 @@
+// 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 "qqnxaudioinput_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQnxAudioInput::QQnxAudioInput(QAudioInput *parent)
+ : QPlatformAudioInput(parent)
+{
+}
+
+QQnxAudioInput::~QQnxAudioInput()
+{
+}
+
+void QQnxAudioInput::setAudioDevice(const QAudioDevice &info)
+{
+ if (info == device)
+ return;
+
+ device = info;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/common/qqnxaudioinput_p.h b/src/plugins/multimedia/qnx/common/qqnxaudioinput_p.h
new file mode 100644
index 000000000..62a573cc1
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxaudioinput_p.h
@@ -0,0 +1,33 @@
+// 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
+
+#ifndef QQNXAUDIOINPUT_P_H
+#define QQNXAUDIOINPUT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <private/qplatformaudioinput_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class Q_MULTIMEDIA_EXPORT QQnxAudioInput : public QPlatformAudioInput
+{
+public:
+ explicit QQnxAudioInput(QAudioInput *parent);
+ ~QQnxAudioInput();
+
+ void setAudioDevice(const QAudioDevice &device) override;
+};
+
+QT_END_NAMESPACE
+#endif
diff --git a/src/plugins/multimedia/qnx/common/qqnxaudiooutput.cpp b/src/plugins/multimedia/qnx/common/qqnxaudiooutput.cpp
new file mode 100644
index 000000000..76f8fbafd
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxaudiooutput.cpp
@@ -0,0 +1,52 @@
+// Copyright (C) 2016 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 "qqnxaudiooutput_p.h"
+
+#include <private/qqnxaudiodevice_p.h>
+
+#include <qaudiodevice.h>
+#include <qaudiooutput.h>
+
+#include <QtCore/qloggingcategory.h>
+
+static Q_LOGGING_CATEGORY(qLcMediaAudioOutput, "qt.multimedia.audiooutput")
+
+QT_BEGIN_NAMESPACE
+
+QQnxAudioOutput::QQnxAudioOutput(QAudioOutput *parent)
+ : QPlatformAudioOutput(parent)
+{
+}
+
+QQnxAudioOutput::~QQnxAudioOutput()
+{
+}
+
+void QQnxAudioOutput::setVolume(float vol)
+{
+ if (vol == volume)
+ return;
+ vol = volume;
+ q->volumeChanged(vol);
+}
+
+void QQnxAudioOutput::setMuted(bool m)
+{
+ if (muted == m)
+ return;
+ muted = m;
+ q->mutedChanged(muted);
+}
+
+void QQnxAudioOutput::setAudioDevice(const QAudioDevice &info)
+{
+ if (info == device)
+ return;
+ qCDebug(qLcMediaAudioOutput) << "setAudioDevice" << info.description() << info.isNull();
+ device = info;
+
+ // ### handle device changes
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/common/qqnxaudiooutput_p.h b/src/plugins/multimedia/qnx/common/qqnxaudiooutput_p.h
new file mode 100644
index 000000000..2ae5844e6
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxaudiooutput_p.h
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 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
+
+#ifndef QQNXAUDIOOUTPUT_P_H
+#define QQNXAUDIOOUTPUT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtmultimediaglobal_p.h>
+#include <private/qplatformaudiooutput_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAudioDevice;
+class QAudioOutput;
+
+class Q_MULTIMEDIA_EXPORT QQnxAudioOutput : public QPlatformAudioOutput
+{
+public:
+ explicit QQnxAudioOutput(QAudioOutput *parent);
+ ~QQnxAudioOutput();
+
+ void setAudioDevice(const QAudioDevice &) override;
+ void setVolume(float volume) override;
+ void setMuted(bool muted) override;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/common/qqnxmediaeventthread.cpp b/src/plugins/multimedia/qnx/common/qqnxmediaeventthread.cpp
new file mode 100644
index 000000000..f0cc9b1c0
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxmediaeventthread.cpp
@@ -0,0 +1,98 @@
+// Copyright (C) 2017 QNX Software Systems. All rights reserved.
+// Copyright (C) 2021 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqnxmediaeventthread_p.h"
+
+#include <QtCore/QDebug>
+
+#include <errno.h>
+#include <mm/renderer/types.h>
+#include <sys/neutrino.h>
+
+extern "C" {
+// ### Include mm/renderer/events.h once we have it
+int mmr_event_arm(mmr_context_t *ctxt,
+ struct sigevent const *sev);
+}
+
+QT_BEGIN_NAMESPACE
+
+static const int c_mmrCode = _PULSE_CODE_MINAVAIL + 0;
+static const int c_readCode = _PULSE_CODE_MINAVAIL + 1;
+static const int c_quitCode = _PULSE_CODE_MINAVAIL + 2;
+
+QQnxMediaEventThread::QQnxMediaEventThread(mmr_context_t *context)
+ : QThread(),
+ m_mmrContext(context)
+{
+ if (Q_UNLIKELY((m_channelId = ChannelCreate(_NTO_CHF_DISCONNECT
+ | _NTO_CHF_UNBLOCK
+ | _NTO_CHF_PRIVATE)) == -1)) {
+ qFatal("QQnxMediaEventThread: Can't continue without a channel");
+ }
+
+ if (Q_UNLIKELY((m_connectionId = ConnectAttach(0, 0, m_channelId,
+ _NTO_SIDE_CHANNEL, 0)) == -1)) {
+ ChannelDestroy(m_channelId);
+ qFatal("QQnxMediaEventThread: Can't continue without a channel connection");
+ }
+
+ SIGEV_PULSE_INIT(&m_mmrEvent, m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_mmrCode, 0);
+}
+
+QQnxMediaEventThread::~QQnxMediaEventThread()
+{
+ // block until thread terminates
+ shutdown();
+
+ ConnectDetach(m_connectionId);
+ ChannelDestroy(m_channelId);
+}
+
+void QQnxMediaEventThread::run()
+{
+ int armResult = mmr_event_arm(m_mmrContext, &m_mmrEvent);
+ if (armResult > 0)
+ emit eventPending();
+
+ while (1) {
+ struct _pulse msg;
+ memset(&msg, 0, sizeof(msg));
+ int receiveId = MsgReceive(m_channelId, &msg, sizeof(msg), nullptr);
+ if (receiveId == 0) {
+ if (msg.code == c_mmrCode) {
+ emit eventPending();
+ } else if (msg.code == c_readCode) {
+ armResult = mmr_event_arm(m_mmrContext, &m_mmrEvent);
+ if (armResult > 0)
+ emit eventPending();
+ } else if (msg.code == c_quitCode) {
+ break;
+ } else {
+ qWarning() << Q_FUNC_INFO << "Unexpected pulse" << msg.code;
+ }
+ } else if (receiveId > 0) {
+ qWarning() << Q_FUNC_INFO << "Unexpected message" << msg.code;
+ } else {
+ qWarning() << Q_FUNC_INFO << "MsgReceive error" << strerror(errno);
+ }
+ }
+}
+
+void QQnxMediaEventThread::signalRead()
+{
+ MsgSendPulse(m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_readCode, 0);
+}
+
+void QQnxMediaEventThread::shutdown()
+{
+ MsgSendPulse(m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_quitCode, 0);
+
+ // block until thread terminates
+ wait();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxmediaeventthread_p.cpp"
diff --git a/src/plugins/multimedia/qnx/common/qqnxmediaeventthread_p.h b/src/plugins/multimedia/qnx/common/qqnxmediaeventthread_p.h
new file mode 100644
index 000000000..a622fcb62
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxmediaeventthread_p.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2017 QNX Software Systems. All rights reserved.
+// Copyright (C) 2021 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QQNXMEDIAEVENTTHREAD_P_H
+#define QQNXMEDIAEVENTTHREAD_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QThread>
+
+#include <sys/neutrino.h>
+#include <sys/siginfo.h>
+
+QT_BEGIN_NAMESPACE
+
+typedef struct mmr_context mmr_context_t;
+
+class QQnxMediaEventThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ QQnxMediaEventThread(mmr_context_t *context);
+ ~QQnxMediaEventThread() override;
+
+ void signalRead();
+
+protected:
+ void run() override;
+
+Q_SIGNALS:
+ void eventPending();
+
+private:
+ void shutdown();
+
+ int m_channelId;
+ int m_connectionId;
+ struct sigevent m_mmrEvent;
+ mmr_context_t *m_mmrContext;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQnxMediaEventThread_H
diff --git a/src/plugins/multimedia/qnx/common/qqnxwindowgrabber.cpp b/src/plugins/multimedia/qnx/common/qqnxwindowgrabber.cpp
new file mode 100644
index 000000000..28f16b70a
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxwindowgrabber.cpp
@@ -0,0 +1,435 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqnxwindowgrabber_p.h"
+
+#include <QAbstractEventDispatcher>
+#include <QDebug>
+#include <QGuiApplication>
+#include <QImage>
+#include <QThread>
+#include <qpa/qplatformnativeinterface.h>
+
+#include <QOpenGLContext>
+#include <QOpenGLFunctions>
+
+#include <rhi/qrhi.h>
+
+#include <cstring>
+
+#include <EGL/egl.h>
+#include <errno.h>
+
+QT_BEGIN_NAMESPACE
+
+static PFNEGLCREATEIMAGEKHRPROC s_eglCreateImageKHR;
+static PFNEGLDESTROYIMAGEKHRPROC s_eglDestroyImageKHR;
+
+class QQnxWindowGrabberImage
+{
+public:
+ QQnxWindowGrabberImage();
+ ~QQnxWindowGrabberImage();
+
+ bool initialize(screen_context_t screenContext);
+
+ QQnxWindowGrabber::BufferView getBuffer(screen_window_t window, const QSize &size);
+ GLuint getTexture(screen_window_t window, const QSize &size);
+
+private:
+ bool grab(screen_window_t window);
+ bool resize(const QSize &size);
+
+ QSize m_size;
+ screen_pixmap_t m_pixmap;
+ screen_buffer_t m_pixmapBuffer;
+ EGLImageKHR m_eglImage;
+ GLuint m_glTexture;
+ unsigned char *m_bufferAddress;
+ int m_bufferStride;
+};
+
+QQnxWindowGrabber::QQnxWindowGrabber(QObject *parent)
+ : QObject(parent),
+ m_windowParent(nullptr),
+ m_window(nullptr),
+ m_screenContext(nullptr),
+ m_rhi(nullptr),
+ m_active(false),
+ m_eglImageSupported(false),
+ m_startPending(false)
+{
+ // grab the window frame with 60 frames per second
+ m_timer.setInterval(1000/60);
+
+ connect(&m_timer, &QTimer::timeout, this, &QQnxWindowGrabber::triggerUpdate);
+
+ QCoreApplication::eventDispatcher()->installNativeEventFilter(this);
+
+ // Use of EGL images can be disabled by setting QQNX_MM_DISABLE_EGLIMAGE_SUPPORT to something
+ // non-zero. This is probably useful only to test that this path still works since it results
+ // in a high CPU load.
+ if (!s_eglCreateImageKHR && qgetenv("QQNX_MM_DISABLE_EGLIMAGE_SUPPORT").toInt() == 0) {
+ s_eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
+ s_eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
+ }
+
+ QPlatformNativeInterface *const nativeInterface = QGuiApplication::platformNativeInterface();
+ if (nativeInterface) {
+ m_screenContext = static_cast<screen_context_t>(
+ nativeInterface->nativeResourceForIntegration("screenContext"));
+ }
+
+ // Create a parent window for the window whose content will be grabbed. Since the
+ // window is only a buffer conduit, the characteristics of the parent window are
+ // irrelevant. The contents of the window can be grabbed so long as the window
+ // joins the parent window's group and the parent window is in this process.
+ // Using the window that displays this content isn't possible because there's no
+ // way to reliably retrieve it from this code or any calling code.
+ screen_create_window(&m_windowParent, m_screenContext);
+ screen_create_window_group(m_windowParent, nullptr);
+}
+
+QQnxWindowGrabber::~QQnxWindowGrabber()
+{
+ screen_destroy_window(m_windowParent);
+ QCoreApplication::eventDispatcher()->removeNativeEventFilter(this);
+}
+
+void QQnxWindowGrabber::setFrameRate(int frameRate)
+{
+ m_timer.setInterval(1000/frameRate);
+}
+
+void QQnxWindowGrabber::setWindowId(const QByteArray &windowId)
+{
+ m_windowId = windowId;
+}
+
+void QQnxWindowGrabber::setRhi(QRhi *rhi)
+{
+ m_rhi = rhi;
+
+ checkForEglImageExtension();
+}
+
+void QQnxWindowGrabber::start()
+{
+ if (m_active)
+ return;
+
+ if (!m_window) {
+ m_startPending = true;
+ return;
+ }
+
+ m_startPending = false;
+
+ if (!m_screenContext)
+ screen_get_window_property_pv(m_window, SCREEN_PROPERTY_CONTEXT, reinterpret_cast<void**>(&m_screenContext));
+
+ m_timer.start();
+
+ m_active = true;
+}
+
+void QQnxWindowGrabber::stop()
+{
+ if (!m_active)
+ return;
+
+ resetBuffers();
+
+ m_timer.stop();
+
+ m_active = false;
+}
+
+void QQnxWindowGrabber::pause()
+{
+ m_timer.stop();
+}
+
+void QQnxWindowGrabber::resume()
+{
+ if (!m_active)
+ return;
+
+ m_timer.start();
+}
+
+void QQnxWindowGrabber::forceUpdate()
+{
+ if (!m_active)
+ return;
+
+ triggerUpdate();
+}
+
+bool QQnxWindowGrabber::handleScreenEvent(screen_event_t screen_event)
+{
+
+ int eventType;
+ if (screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType) != 0) {
+ qWarning() << "QQnxWindowGrabber: Failed to query screen event type";
+ return false;
+ }
+
+ if (eventType != SCREEN_EVENT_CREATE)
+ return false;
+
+ screen_window_t window = 0;
+ if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&window) != 0) {
+ qWarning() << "QQnxWindowGrabber: Failed to query window property";
+ return false;
+ }
+
+ const int maxIdStrLength = 128;
+ char idString[maxIdStrLength];
+ if (screen_get_window_property_cv(window, SCREEN_PROPERTY_ID_STRING, maxIdStrLength, idString) != 0) {
+ qWarning() << "QQnxWindowGrabber: Failed to query window ID string";
+ return false;
+ }
+
+ // Grab windows that have a non-empty ID string and a matching window id to grab
+ if (idString[0] != '\0' && m_windowId == idString) {
+ m_window = window;
+
+ if (m_startPending)
+ start();
+ }
+
+ return false;
+}
+
+bool QQnxWindowGrabber::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
+{
+ if (eventType == "screen_event_t") {
+ const screen_event_t event = static_cast<screen_event_t>(message);
+ return handleScreenEvent(event);
+ }
+
+ return false;
+}
+
+QByteArray QQnxWindowGrabber::windowGroupId() const
+{
+ char groupName[256];
+ memset(groupName, 0, sizeof(groupName));
+ screen_get_window_property_cv(m_windowParent,
+ SCREEN_PROPERTY_GROUP,
+ sizeof(groupName) - 1,
+ groupName);
+ return QByteArray(groupName);
+}
+
+bool QQnxWindowGrabber::isEglImageSupported() const
+{
+ return m_eglImageSupported;
+}
+
+void QQnxWindowGrabber::checkForEglImageExtension()
+{
+ m_eglImageSupported = false;
+
+ if (!m_rhi || m_rhi->backend() != QRhi::OpenGLES2)
+ return;
+
+ const EGLDisplay defaultDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ const char *vendor = eglQueryString(defaultDisplay, EGL_VENDOR);
+
+ if (vendor && std::strstr(vendor, "VMWare"))
+ return;
+
+ const char *eglExtensions = eglQueryString(defaultDisplay, EGL_EXTENSIONS);
+
+ if (!eglExtensions)
+ return;
+
+ m_eglImageSupported = std::strstr(eglExtensions, "EGL_KHR_image")
+ && s_eglCreateImageKHR
+ && s_eglDestroyImageKHR;
+}
+
+void QQnxWindowGrabber::triggerUpdate()
+{
+ int size[2] = { 0, 0 };
+
+ const int result = screen_get_window_property_iv(m_window, SCREEN_PROPERTY_SOURCE_SIZE, size);
+
+ if (result != 0) {
+ resetBuffers();
+ qWarning() << "QQnxWindowGrabber: cannot get window size:" << strerror(errno);
+ return;
+ }
+
+ if (m_size.width() != size[0] || m_size.height() != size[1])
+ m_size = QSize(size[0], size[1]);
+
+ emit updateScene(m_size);
+}
+
+bool QQnxWindowGrabber::selectBuffer()
+{
+ // If we're using egl images we need to double buffer since the gpu may still be using the last
+ // video frame. If we're not, it doesn't matter since the data is immediately copied.
+ if (isEglImageSupported())
+ std::swap(m_frontBuffer, m_backBuffer);
+
+ if (m_frontBuffer)
+ return true;
+
+ auto frontBuffer = std::make_unique<QQnxWindowGrabberImage>();
+
+ if (!frontBuffer->initialize(m_screenContext))
+ return false;
+
+ m_frontBuffer = std::move(frontBuffer);
+
+ return true;
+}
+
+int QQnxWindowGrabber::getNextTextureId()
+{
+ if (!selectBuffer())
+ return 0;
+
+ return m_frontBuffer->getTexture(m_window, m_size);
+}
+
+QQnxWindowGrabber::BufferView QQnxWindowGrabber::getNextBuffer()
+{
+ if (!selectBuffer())
+ return {};
+
+ return m_frontBuffer->getBuffer(m_window, m_size);
+}
+
+void QQnxWindowGrabber::resetBuffers()
+{
+ m_frontBuffer.reset();
+ m_backBuffer.reset();
+}
+
+QQnxWindowGrabberImage::QQnxWindowGrabberImage()
+ : m_pixmap(0),
+ m_pixmapBuffer(0),
+ m_eglImage(0),
+ m_glTexture(0),
+ m_bufferAddress(nullptr),
+ m_bufferStride(0)
+{
+}
+
+QQnxWindowGrabberImage::~QQnxWindowGrabberImage()
+{
+ if (m_glTexture)
+ glDeleteTextures(1, &m_glTexture);
+ if (m_eglImage)
+ s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage);
+ if (m_pixmap)
+ screen_destroy_pixmap(m_pixmap);
+}
+
+bool QQnxWindowGrabberImage::initialize(screen_context_t screenContext)
+{
+ if (screen_create_pixmap(&m_pixmap, screenContext) != 0) {
+ qWarning() << "QQnxWindowGrabber: cannot create pixmap:" << strerror(errno);
+ return false;
+ }
+ const int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_READ | SCREEN_USAGE_NATIVE;
+ screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_USAGE, &usage);
+
+ // XXX as a matter of fact, the underlying buffer is BGRX8888 (according to
+ // QNX, screen formats can be loose on the ARGB ordering) - as there is no
+ // SCREEN_FORMAT_BGRX8888 constant, we use SCREEN_FORMAT_RGBX8888, which
+ // carries the same depth and allows us to use the buffer.
+ const int format = SCREEN_FORMAT_RGBX8888;
+ screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_FORMAT, &format);
+
+ return true;
+}
+
+bool QQnxWindowGrabberImage::resize(const QSize &newSize)
+{
+ if (m_pixmapBuffer) {
+ screen_destroy_pixmap_buffer(m_pixmap);
+ m_pixmapBuffer = 0;
+ m_bufferAddress = 0;
+ m_bufferStride = 0;
+ }
+
+ const int size[2] = { newSize.width(), newSize.height() };
+
+ screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_BUFFER_SIZE, size);
+
+ if (screen_create_pixmap_buffer(m_pixmap) == 0) {
+ screen_get_pixmap_property_pv(m_pixmap, SCREEN_PROPERTY_RENDER_BUFFERS,
+ reinterpret_cast<void**>(&m_pixmapBuffer));
+ screen_get_buffer_property_pv(m_pixmapBuffer, SCREEN_PROPERTY_POINTER,
+ reinterpret_cast<void**>(&m_bufferAddress));
+ screen_get_buffer_property_iv(m_pixmapBuffer, SCREEN_PROPERTY_STRIDE, &m_bufferStride);
+ m_size = newSize;
+
+ return true;
+ } else {
+ m_size = QSize();
+ return false;
+ }
+}
+
+bool QQnxWindowGrabberImage::grab(screen_window_t window)
+{
+ const int rect[] = { 0, 0, m_size.width(), m_size.height() };
+ return screen_read_window(window, m_pixmapBuffer, 1, rect, 0) == 0;
+}
+
+QQnxWindowGrabber::BufferView QQnxWindowGrabberImage::getBuffer(
+ screen_window_t window, const QSize &size)
+{
+ if (size != m_size && !resize(size))
+ return {};
+
+ if (!m_bufferAddress || !grab(window))
+ return {};
+
+ return {
+ .width = m_size.width(),
+ .height = m_size.height(),
+ .stride = m_bufferStride,
+ .data = m_bufferAddress
+ };
+}
+
+GLuint QQnxWindowGrabberImage::getTexture(screen_window_t window, const QSize &size)
+{
+ if (size != m_size) {
+ // create a brand new texture to be the KHR image sibling, as
+ // previously used textures cannot be reused with new KHR image
+ // sources - note that glDeleteTextures handles nullptr gracefully
+ glDeleteTextures(1, &m_glTexture);
+ glGenTextures(1, &m_glTexture);
+
+ glBindTexture(GL_TEXTURE_2D, m_glTexture);
+ if (m_eglImage) {
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, 0);
+ s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage);
+ m_eglImage = 0;
+ }
+ if (!resize(size))
+ return 0;
+ m_eglImage = s_eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT,
+ EGL_NATIVE_PIXMAP_KHR, m_pixmap, 0);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage);
+ }
+
+ if (!m_pixmap || !grab(window))
+ return 0;
+
+ return m_glTexture;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxwindowgrabber_p.cpp"
diff --git a/src/plugins/multimedia/qnx/common/qqnxwindowgrabber_p.h b/src/plugins/multimedia/qnx/common/qqnxwindowgrabber_p.h
new file mode 100644
index 000000000..1ffd96b63
--- /dev/null
+++ b/src/plugins/multimedia/qnx/common/qqnxwindowgrabber_p.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQnxWindowGrabber_H
+#define QQnxWindowGrabber_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/eglext.h>
+#include <QAbstractNativeEventFilter>
+#include <QObject>
+#include <QSize>
+#include <QTimer>
+
+#include <memory>
+
+#include <screen/screen.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhi;
+class QQnxWindowGrabberImage;
+
+class QQnxWindowGrabber : public QObject, public QAbstractNativeEventFilter
+{
+ Q_OBJECT
+
+public:
+ struct BufferView
+ {
+ int width = -1;
+ int height = -1;
+ int stride = -1;
+
+ unsigned char *data = nullptr;
+
+ static constexpr int pixelSize = 4; // BGRX8888;
+ };
+
+ explicit QQnxWindowGrabber(QObject *parent = 0);
+ ~QQnxWindowGrabber();
+
+ void setFrameRate(int frameRate);
+
+ void setWindowId(const QByteArray &windowId);
+
+ void setRhi(QRhi *rhi);
+
+ void start();
+ void stop();
+
+ void pause();
+ void resume();
+
+ void forceUpdate();
+
+ bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override;
+
+ bool handleScreenEvent(screen_event_t event);
+
+ QByteArray windowGroupId() const;
+
+ bool isEglImageSupported() const;
+
+ int getNextTextureId();
+ BufferView getNextBuffer();
+
+signals:
+ void updateScene(const QSize &size);
+
+private slots:
+ void triggerUpdate();
+
+private:
+ bool selectBuffer();
+ void resetBuffers();
+ void checkForEglImageExtension();
+
+ QTimer m_timer;
+
+ QByteArray m_windowId;
+
+ screen_window_t m_windowParent;
+ screen_window_t m_window;
+ screen_context_t m_screenContext;
+
+ std::unique_ptr<QQnxWindowGrabberImage> m_frontBuffer;
+ std::unique_ptr<QQnxWindowGrabberImage> m_backBuffer;
+
+ QSize m_size;
+
+ QRhi *m_rhi;
+
+ bool m_active;
+ bool m_eglImageSupported;
+ bool m_startPending;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata.cpp b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata.cpp
new file mode 100644
index 000000000..fcd535814
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata.cpp
@@ -0,0 +1,262 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnxmediametadata_p.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qstringlist.h>
+
+#include <mm/renderer/types.h>
+#include <sys/neutrino.h>
+#include <sys/strm.h>
+
+extern "C" {
+// ### include this properly from mm/renderer/events.h once the toolchain is fixed
+extern strm_dict_t* mmr_metadata_split(strm_dict_t const *md,
+ const char *type,
+ unsigned idx);
+}
+
+static const char *strm_string_getx(const strm_string_t *sstr, const char *defaultValue)
+{
+ return sstr ? strm_string_get(sstr) : defaultValue;
+}
+
+QT_BEGIN_NAMESPACE
+
+QQnxMediaMetaData::QQnxMediaMetaData()
+{
+ clear();
+}
+
+static const char * titleKey = "md_title_name";
+static const char * artistKey = "md_title_artist";
+static const char * commentKey = "md_title_comment";
+static const char * genreKey = "md_title_genre";
+static const char * yearKey = "md_title_year";
+static const char * durationKey = "md_title_duration";
+static const char * bitRateKey = "md_title_bitrate";
+static const char * sampleKey = "md_title_samplerate";
+static const char * albumKey = "md_title_album";
+static const char * trackKey = "md_title_track";
+static const char * widthKey = "md_video_width";
+static const char * heightKey = "md_video_height";
+static const char * mediaTypeKey = "md_title_mediatype";
+static const char * pixelWidthKey = "md_video_pixel_width";
+static const char * pixelHeightKey = "md_video_pixel_height";
+static const char * seekableKey = "md_title_seekable";
+static const char * trackSampleKey = "sample_rate";
+static const char * trackBitRateKey = "bitrate";
+static const char * trackWidthKey = "width";
+static const char * trackHeightKey = "height";
+static const char * trackPixelWidthKey = "pixel_width";
+static const char * trackPixelHeightKey = "pixel_height";
+
+static const int mediaTypeAudioFlag = 4;
+static const int mediaTypeVideoFlag = 2;
+
+bool QQnxMediaMetaData::update(const strm_dict_t *dict)
+{
+ if (!dict) {
+ clear();
+ return true;
+ }
+
+ const strm_string_t *value;
+
+ value = strm_dict_find_rstr(dict, durationKey);
+ m_duration = QByteArray(strm_string_getx(value, "0")).toLongLong();
+
+ value = strm_dict_find_rstr(dict, mediaTypeKey);
+ m_mediaType = QByteArray(strm_string_getx(value, "-1")).toInt();
+
+ value = strm_dict_find_rstr(dict, titleKey);
+ m_title = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr)));
+
+ value = strm_dict_find_rstr(dict, seekableKey);
+ m_seekable = (strcmp(strm_string_getx(value, "1"), "0") != 0);
+
+ value = strm_dict_find_rstr(dict, artistKey);
+ m_artist = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr)));
+
+ value = strm_dict_find_rstr(dict, commentKey);
+ m_comment = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr)));
+
+ value = strm_dict_find_rstr(dict, genreKey);
+ m_genre = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr)));
+
+ value = strm_dict_find_rstr(dict, yearKey);
+ m_year = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(dict, albumKey);
+ m_album = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr)));
+
+ value = strm_dict_find_rstr(dict, trackKey);
+ m_track = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ strm_dict_t *at = mmr_metadata_split(dict, "audio", 0);
+ if (at) {
+ value = strm_dict_find_rstr(at, trackSampleKey);
+ m_sampleRate = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(at, trackBitRateKey);
+ m_audioBitRate = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ strm_dict_destroy(at);
+ } else {
+ value = strm_dict_find_rstr(dict, sampleKey);
+ m_sampleRate = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(dict, bitRateKey);
+ m_audioBitRate = QByteArray(strm_string_getx(value, "0")).toInt();
+ }
+
+ strm_dict_t *vt = mmr_metadata_split(dict, "video", 0);
+ if (vt) {
+ value = strm_dict_find_rstr(vt, trackWidthKey);
+ m_width = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(vt, trackHeightKey);
+ m_height = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(vt, trackPixelWidthKey);
+ m_pixelWidth = QByteArray(strm_string_getx(value, "1")).toFloat();
+
+ value = strm_dict_find_rstr(vt, trackPixelHeightKey);
+ m_pixelHeight = QByteArray(strm_string_getx(value, "1")).toFloat();
+
+ strm_dict_destroy(vt);
+ } else {
+ value = strm_dict_find_rstr(dict, widthKey);
+ m_width = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(dict, heightKey);
+ m_height = QByteArray(strm_string_getx(value, "0")).toInt();
+
+ value = strm_dict_find_rstr(dict, pixelWidthKey);
+ m_pixelWidth = QByteArray(strm_string_getx(value, "1")).toFloat();
+
+ value = strm_dict_find_rstr(dict, pixelHeightKey);
+ m_pixelHeight = QByteArray(strm_string_getx(value, "1")).toFloat();
+ }
+
+ return true;
+}
+
+void QQnxMediaMetaData::clear()
+{
+ strm_dict_t *dict;
+ dict = strm_dict_new();
+ update(dict);
+ strm_dict_destroy(dict);
+}
+
+qlonglong QQnxMediaMetaData::duration() const
+{
+ return m_duration;
+}
+
+// Handling of pixel aspect ratio
+//
+// If the pixel aspect ratio is different from 1:1, it means the video needs to be stretched in
+// order to look natural.
+// For example, if the pixel width is 2, and the pixel height is 1, it means a video of 300x200
+// pixels needs to be displayed as 600x200 to look correct.
+// In order to support this the easiest way, we simply pretend that the actual size of the video
+// is 600x200, which will cause the video to be displayed in an aspect ratio of 3:1 instead of 3:2,
+// and therefore look correct.
+
+int QQnxMediaMetaData::height() const
+{
+ return m_height * m_pixelHeight;
+}
+
+int QQnxMediaMetaData::width() const
+{
+ return m_width * m_pixelWidth;
+}
+
+bool QQnxMediaMetaData::hasVideo() const
+{
+ // By default, assume no video if we can't extract the information
+ if (m_mediaType == -1)
+ return false;
+
+ return (m_mediaType & mediaTypeVideoFlag);
+}
+
+bool QQnxMediaMetaData::hasAudio() const
+{
+ // By default, assume audio only if we can't extract the information
+ if (m_mediaType == -1)
+ return true;
+
+ return (m_mediaType & mediaTypeAudioFlag);
+}
+
+QString QQnxMediaMetaData::title() const
+{
+ return m_title;
+}
+
+bool QQnxMediaMetaData::isSeekable() const
+{
+ return m_seekable;
+}
+
+QString QQnxMediaMetaData::artist() const
+{
+ return m_artist;
+}
+
+QString QQnxMediaMetaData::comment() const
+{
+ return m_comment;
+}
+
+QString QQnxMediaMetaData::genre() const
+{
+ return m_genre;
+}
+
+int QQnxMediaMetaData::year() const
+{
+ return m_year;
+}
+
+QString QQnxMediaMetaData::mediaType() const
+{
+ if (hasVideo())
+ return QLatin1String("video");
+ else if (hasAudio())
+ return QLatin1String("audio");
+ else
+ return QString();
+}
+
+int QQnxMediaMetaData::audioBitRate() const
+{
+ return m_audioBitRate;
+}
+
+int QQnxMediaMetaData::sampleRate() const
+{
+ return m_sampleRate;
+}
+
+QString QQnxMediaMetaData::album() const
+{
+ return m_album;
+}
+
+int QQnxMediaMetaData::track() const
+{
+ return m_track;
+}
+
+QSize QQnxMediaMetaData::resolution() const
+{
+ return QSize(width(), height());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata_p.h b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata_p.h
new file mode 100644
index 000000000..db7639dc5
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediametadata_p.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQnxMediaMetaData_H
+#define QQnxMediaMetaData_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/QSize>
+#include <QtCore/QString>
+
+typedef struct strm_dict strm_dict_t;
+
+QT_BEGIN_NAMESPACE
+
+class QQnxMediaMetaData
+{
+public:
+ QQnxMediaMetaData();
+ bool update(const strm_dict_t *dict);
+ void clear();
+
+ // Duration in milliseconds
+ qlonglong duration() const;
+
+ int height() const;
+ int width() const;
+ bool hasVideo() const;
+ bool hasAudio() const;
+ bool isSeekable() const;
+
+ QString title() const;
+ QString artist() const;
+ QString comment() const;
+ QString genre() const;
+ int year() const;
+ QString mediaType() const;
+ int audioBitRate() const;
+ int sampleRate() const;
+ QString album() const;
+ int track() const;
+ QSize resolution() const;
+
+private:
+ qlonglong m_duration;
+ int m_height;
+ int m_width;
+ int m_mediaType;
+ float m_pixelWidth;
+ float m_pixelHeight;
+ bool m_seekable;
+ QString m_title;
+ QString m_artist;
+ QString m_comment;
+ QString m_genre;
+ int m_year;
+ int m_audioBitRate;
+ int m_sampleRate;
+ QString m_album;
+ int m_track;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer.cpp b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer.cpp
new file mode 100644
index 000000000..14b190836
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer.cpp
@@ -0,0 +1,887 @@
+// Copyright (C) 2016 Research In Motion
+// Copyright (C) 2021 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnxmediaplayer_p.h"
+#include "qqnxvideosink_p.h"
+#include "qqnxmediautil_p.h"
+#include "qqnxmediaeventthread_p.h"
+#include "qqnxwindowgrabber_p.h"
+
+#include <private/qhwvideobuffer_p.h>
+#include <private/qvideoframe_p.h>
+
+#include <QtCore/qabstracteventdispatcher.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/quuid.h>
+#include <mm/renderer.h>
+#include <qmediaplayer.h>
+#include <qqnxaudiooutput_p.h>
+#include <qaudiooutput.h>
+
+#include <errno.h>
+#include <sys/strm.h>
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <tuple>
+
+static constexpr int rateToSpeed(qreal rate)
+{
+ return std::floor(rate * 1000);
+}
+
+static constexpr qreal speedToRate(int speed)
+{
+ return std::floor(speed / 1000.0);
+}
+
+static constexpr int normalizeVolume(float volume)
+{
+ return std::clamp<int>(std::floor(volume * 100.0), 0, 100);
+}
+
+static std::tuple<int, int, bool> parseBufferLevel(const QString &value)
+{
+ if (value.isEmpty())
+ return {};
+
+ const int slashPos = value.indexOf('/');
+ if (slashPos <= 0)
+ return {};
+
+ bool ok = false;
+ const int level = value.left(slashPos).toInt(&ok);
+ if (!ok || level < 0)
+ return {};
+
+ const int capacity = value.mid(slashPos + 1).toInt(&ok);
+ if (!ok || capacity < 0)
+ return {};
+
+ return { level, capacity, true };
+}
+
+class QnxTextureBuffer : public QHwVideoBuffer
+{
+public:
+ QnxTextureBuffer(QQnxWindowGrabber *QQnxWindowGrabber)
+ : QHwVideoBuffer(QVideoFrame::RhiTextureHandle)
+ {
+ m_windowGrabber = QQnxWindowGrabber;
+ m_handle = 0;
+ }
+
+ void unmap() override {}
+
+ MapData map(QtVideo::MapMode /*mode*/) override
+ {
+ return {};
+ }
+
+ quint64 textureHandle(QRhi *, int plane) const override
+ {
+ if (plane != 0)
+ return 0;
+ if (!m_handle) {
+ const_cast<QnxTextureBuffer*>(this)->m_handle = m_windowGrabber->getNextTextureId();
+ }
+ return m_handle;
+ }
+
+private:
+ QQnxWindowGrabber *m_windowGrabber;
+ quint64 m_handle;
+};
+
+class QnxRasterBuffer : public QAbstractVideoBuffer
+{
+public:
+ QnxRasterBuffer(QQnxWindowGrabber *windowGrabber) { m_windowGrabber = windowGrabber; }
+
+ MapData map(QtVideo::MapMode mode) override
+ {
+ if (mode != QtVideo::MapMode::ReadOnly)
+ return {};
+
+ if (buffer.data) {
+ qWarning("QnxRasterBuffer: need to unmap before mapping");
+ return {};
+ }
+
+ buffer = m_windowGrabber->getNextBuffer();
+
+ return {
+ .planeCount = 1,
+ .bytesPerLine = { buffer.stride },
+ .data = { buffer.data },
+ .dataSize = { buffer.width * buffer.height * buffer.pixelSize }
+ };
+ }
+
+ void unmap() override
+ {
+ buffer = {};
+ }
+
+ QVideoFrameFormat format() const override { return {}; }
+
+private:
+ QQnxWindowGrabber *m_windowGrabber;
+ QQnxWindowGrabber::BufferView buffer;
+};
+
+QT_BEGIN_NAMESPACE
+
+QQnxMediaPlayer::QQnxMediaPlayer(QMediaPlayer *parent)
+ : QObject(parent)
+ , QPlatformMediaPlayer(parent)
+ , m_windowGrabber(new QQnxWindowGrabber(this))
+{
+ m_flushPositionTimer.setSingleShot(true);
+ m_flushPositionTimer.setInterval(100);
+
+ connect(&m_flushPositionTimer, &QTimer::timeout, this, &QQnxMediaPlayer::flushPosition);
+
+ connect(m_windowGrabber, &QQnxWindowGrabber::updateScene, this, &QQnxMediaPlayer::updateScene);
+
+ openConnection();
+}
+
+QQnxMediaPlayer::~QQnxMediaPlayer()
+{
+ stop();
+ detach();
+ closeConnection();
+}
+
+void QQnxMediaPlayer::openConnection()
+{
+ static int idCounter = 0;
+
+ m_connection = mmr_connect(nullptr);
+ if (!m_connection) {
+ emitPError(QString::fromLatin1("Unable to connect to the multimedia renderer"));
+ return;
+ }
+
+ m_id = idCounter++;
+ m_contextName = QString::fromLatin1("QQnxMediaPlayer_%1_%2").arg(m_id)
+ .arg(QCoreApplication::applicationPid());
+ m_context = mmr_context_create(m_connection, m_contextName.toLatin1(),
+ 0, S_IRWXU|S_IRWXG|S_IRWXO);
+ if (!m_context) {
+ emitPError(QString::fromLatin1("Unable to create context"));
+ closeConnection();
+ return;
+ }
+
+ startMonitoring();
+}
+
+void QQnxMediaPlayer::handleMmEventState(const mmr_event_t *event)
+{
+ if (!event || event->type != MMR_EVENT_STATE)
+ return;
+
+ switch (event->state) {
+ case MMR_STATE_DESTROYED:
+ break;
+ case MMR_STATE_IDLE:
+ mediaStatusChanged(QMediaPlayer::NoMedia);
+ stateChanged(QMediaPlayer::StoppedState);
+ detachVideoOutput();
+ detachInput();
+ break;
+ case MMR_STATE_STOPPED:
+ stateChanged(QMediaPlayer::StoppedState);
+ m_windowGrabber->stop();
+
+ if (m_platformVideoSink)
+ m_platformVideoSink->setVideoFrame({});
+ break;
+ case MMR_STATE_PLAYING:
+ if (event->speed == 0) {
+ stateChanged(QMediaPlayer::PausedState);
+ m_windowGrabber->pause();
+ } else if (state() == QMediaPlayer::PausedState) {
+ m_windowGrabber->resume();
+ stateChanged(QMediaPlayer::PlayingState);
+ } else {
+ m_windowGrabber->start();
+ stateChanged(QMediaPlayer::PlayingState);
+ }
+
+ if (event->speed != m_speed) {
+ m_speed = event->speed;
+
+ if (state() != QMediaPlayer::PausedState)
+ m_configuredSpeed = m_speed;
+
+ playbackRateChanged(::speedToRate(m_speed));
+ }
+ break;
+ }
+}
+
+void QQnxMediaPlayer::handleMmEventStatus(const mmr_event_t *event)
+{
+ if (!event || event->type != MMR_EVENT_STATUS)
+ return;
+
+ if (event->data)
+ handleMmEventStatusData(event->data);
+
+ // update pos
+ if (!event->pos_str || isPendingPositionFlush())
+ return;
+
+ const QByteArray valueBa(event->pos_str);
+
+ bool ok;
+ const qint64 position = valueBa.toLongLong(&ok);
+
+ if (!ok)
+ qCritical("Could not parse position from '%s'", valueBa.constData());
+ else
+ handleMmPositionChanged(position);
+}
+
+void QQnxMediaPlayer::handleMmEventStatusData(const strm_dict_t *data)
+{
+ if (!data)
+ return;
+
+ const auto getValue = [data](const char *key) -> QString {
+ const strm_string_t *value = strm_dict_find_rstr(data, key);
+
+ if (!value)
+ return {};
+
+ return QString::fromUtf8(strm_string_get(value));
+ };
+
+ // update bufferProgress
+ const QString bufferLevel = getValue("bufferlevel");
+
+ if (!bufferLevel.isEmpty()) {
+ const auto & [level, capacity, ok] = ::parseBufferLevel(bufferLevel);
+
+ if (ok)
+ updateBufferLevel(level, capacity);
+ else
+ qCritical("Could not parse buffer capacity from '%s'", qUtf8Printable(bufferLevel));
+ }
+
+ // update MediaStatus
+ const QString bufferStatus = getValue("bufferstatus");
+ const QString suspended = getValue("suspended");
+
+ if (suspended == QStringLiteral("yes"))
+ mediaStatusChanged(QMediaPlayer::StalledMedia);
+ else if (bufferStatus == QStringLiteral("buffering"))
+ mediaStatusChanged(QMediaPlayer::BufferingMedia);
+ else if (bufferStatus == QStringLiteral("playing"))
+ mediaStatusChanged(QMediaPlayer::BufferedMedia);
+}
+
+void QQnxMediaPlayer::handleMmEventError(const mmr_event_t *event)
+{
+ if (!event)
+ return;
+
+ // When playback is explicitly stopped using mmr_stop(), mm-renderer
+ // generates a STATE event. When the end of media is reached, an ERROR
+ // event is generated and the error code contained in the event information
+ // is set to MMR_ERROR_NONE. When an error causes playback to stop,
+ // the error code is set to something else.
+ if (event->details.error.info.error_code == MMR_ERROR_NONE) {
+ mediaStatusChanged(QMediaPlayer::EndOfMedia);
+ stateChanged(QMediaPlayer::StoppedState);
+ }
+}
+
+void QQnxMediaPlayer::closeConnection()
+{
+ stopMonitoring();
+
+ if (m_context) {
+ mmr_context_destroy(m_context);
+ m_context = nullptr;
+ m_contextName.clear();
+ }
+
+ if (m_connection) {
+ mmr_disconnect(m_connection);
+ m_connection = nullptr;
+ }
+}
+
+QByteArray QQnxMediaPlayer::resourcePathForUrl(const QUrl &url)
+{
+ // If this is a local file, mmrenderer expects the file:// prefix and an absolute path.
+ // We treat URLs without scheme as local files, most likely someone just forgot to set the
+ // file:// prefix when constructing the URL.
+ if (url.isLocalFile() || url.scheme().isEmpty()) {
+ const QString relativeFilePath = url.scheme().isEmpty() ? url.path() : url.toLocalFile();
+ const QFileInfo fileInfo(relativeFilePath);
+ return QFile::encodeName(QStringLiteral("file://") + fileInfo.absoluteFilePath());
+
+ // HTTP or similar URL
+ } else {
+ return url.toEncoded();
+ }
+}
+
+void QQnxMediaPlayer::attach()
+{
+ // Should only be called in detached state
+ if (isInputAttached())
+ return;
+
+ if (!m_media.isValid() || !m_context) {
+ mediaStatusChanged(QMediaPlayer::NoMedia);
+ return;
+ }
+
+ resetMonitoring();
+
+ if (!(attachVideoOutput() && attachAudioOutput() && attachInput())) {
+ detach();
+ return;
+ }
+
+ mediaStatusChanged(QMediaPlayer::LoadedMedia);
+}
+
+bool QQnxMediaPlayer::attachVideoOutput()
+{
+ if (isVideoOutputAttached()) {
+ qWarning() << "QQnxMediaPlayer: Video output already attached!";
+ return true;
+ }
+
+ if (!m_context) {
+ qWarning() << "QQnxMediaPlayer: No media player context!";
+ return false;
+ }
+
+ const QByteArray windowGroupId = m_windowGrabber->windowGroupId();
+ if (windowGroupId.isEmpty()) {
+ qWarning() << "QQnxMediaPlayer: Unable to find window group";
+ return false;
+ }
+
+ static int winIdCounter = 0;
+
+ const QString windowName = QStringLiteral("QQnxVideoSink_%1_%2")
+ .arg(winIdCounter++)
+ .arg(QCoreApplication::applicationPid());
+
+ m_windowGrabber->setWindowId(windowName.toLatin1());
+
+ if (m_platformVideoSink)
+ m_windowGrabber->setRhi(m_platformVideoSink->rhi());
+
+ // Start with an invisible window, because we just want to grab the frames from it.
+ const QString videoDeviceUrl = QStringLiteral("screen:?winid=%1&wingrp=%2&initflags=invisible&nodstviewport=1")
+ .arg(windowName, QString::fromLatin1(windowGroupId));
+
+ m_videoId = mmr_output_attach(m_context, videoDeviceUrl.toLatin1(), "video");
+
+ if (m_videoId == -1) {
+ qWarning() << "mmr_output_attach() for video failed";
+ return false;
+ }
+
+ return true;
+}
+
+bool QQnxMediaPlayer::attachAudioOutput()
+{
+ if (isAudioOutputAttached()) {
+ qWarning() << "QQnxMediaPlayer: Audio output already attached!";
+ return true;
+ }
+
+ const QByteArray defaultAudioDevice = qgetenv("QQNX_RENDERER_DEFAULT_AUDIO_SINK");
+
+ m_audioId = mmr_output_attach(m_context,
+ defaultAudioDevice.isEmpty() ? "snd:" : defaultAudioDevice.constData(), "audio");
+
+ if (m_audioId == -1) {
+ emitMmError("mmr_output_attach() for audio failed");
+
+ return false;
+ }
+
+ return true;
+}
+
+bool QQnxMediaPlayer::attachInput()
+{
+ if (isInputAttached())
+ return true;
+
+ const QByteArray resourcePath = resourcePathForUrl(m_media);
+
+ if (resourcePath.isEmpty())
+ return false;
+
+ if (mmr_input_attach(m_context, resourcePath.constData(), "track") != 0) {
+ emitMmError(QStringLiteral("mmr_input_attach() failed for ")
+ + QString::fromUtf8(resourcePath));
+
+ mediaStatusChanged(QMediaPlayer::InvalidMedia);
+
+ return false;
+ }
+
+ m_inputAttached = true;
+
+ return true;
+}
+
+void QQnxMediaPlayer::detach()
+{
+ if (!m_context)
+ return;
+
+ if (isVideoOutputAttached())
+ detachVideoOutput();
+
+ if (isAudioOutputAttached())
+ detachAudioOutput();
+
+ if (isInputAttached())
+ detachInput();
+
+ resetMonitoring();
+}
+
+void QQnxMediaPlayer::detachVideoOutput()
+{
+ m_windowGrabber->stop();
+
+ if (m_platformVideoSink)
+ m_platformVideoSink->setVideoFrame({});
+
+ if (isVideoOutputAttached())
+ mmr_output_detach(m_context, m_videoId);
+
+ m_videoId = -1;
+}
+
+void QQnxMediaPlayer::detachAudioOutput()
+{
+ if (isAudioOutputAttached())
+ mmr_output_detach(m_context, m_audioId);
+
+ m_audioId = -1;
+}
+
+void QQnxMediaPlayer::detachInput()
+{
+ if (isInputAttached())
+ mmr_input_detach(m_context);
+
+ m_inputAttached = false;
+}
+
+bool QQnxMediaPlayer::isVideoOutputAttached() const
+{
+ return m_videoId != -1;
+}
+
+bool QQnxMediaPlayer::isAudioOutputAttached() const
+{
+ return m_audioId != -1;
+}
+
+bool QQnxMediaPlayer::isInputAttached() const
+{
+ return m_inputAttached;
+}
+
+void QQnxMediaPlayer::updateScene(const QSize &size)
+{
+ if (!m_platformVideoSink)
+ return;
+
+ QVideoFrameFormat format(size, QVideoFrameFormat::Format_BGRX8888);
+
+ const QVideoFrame actualFrame = m_windowGrabber->isEglImageSupported()
+ ? QVideoFramePrivate::createFrame(std::make_unique<QnxTextureBuffer>(m_windowGrabber),
+ std::move(format))
+ : QVideoFramePrivate::createFrame(std::make_unique<QnxRasterBuffer>(m_windowGrabber),
+ std::move(format));
+
+ m_platformVideoSink->setVideoFrame(actualFrame);
+}
+
+qint64 QQnxMediaPlayer::duration() const
+{
+ return m_metaData.duration();
+}
+
+qint64 QQnxMediaPlayer::position() const
+{
+ return m_position;
+}
+
+void QQnxMediaPlayer::setPosition(qint64 position)
+{
+ if (m_position == position)
+ return;
+
+ m_pendingPosition = position;
+ m_flushPositionTimer.start();
+}
+
+void QQnxMediaPlayer::setPositionInternal(qint64 position)
+{
+ if (!m_context || !m_metaData.isSeekable() || mediaStatus() == QMediaPlayer::NoMedia)
+ return;
+
+ if (mmr_seek(m_context, QString::number(position).toLatin1()) != 0)
+ emitMmError("Seeking failed");
+}
+
+void QQnxMediaPlayer::flushPosition()
+{
+ setPositionInternal(m_pendingPosition);
+}
+
+bool QQnxMediaPlayer::isPendingPositionFlush() const
+{
+ return m_flushPositionTimer.isActive();
+}
+
+void QQnxMediaPlayer::setDeferredSpeedEnabled(bool enabled)
+{
+ m_deferredSpeedEnabled = enabled;
+}
+
+bool QQnxMediaPlayer::isDeferredSpeedEnabled() const
+{
+ return m_deferredSpeedEnabled;
+}
+
+void QQnxMediaPlayer::setVolume(float volume)
+{
+ const int normalizedVolume = ::normalizeVolume(volume);
+
+ if (m_volume == normalizedVolume)
+ return;
+
+ m_volume = normalizedVolume;
+
+ if (!m_muted)
+ updateVolume();
+}
+
+void QQnxMediaPlayer::setMuted(bool muted)
+{
+ if (m_muted == muted)
+ return;
+
+ m_muted = muted;
+
+ updateVolume();
+}
+
+void QQnxMediaPlayer::updateVolume()
+{
+ if (!m_context || m_audioId == -1)
+ return;
+
+ const int volume = m_muted ? 0 : m_volume;
+
+ char buf[] = "100";
+ std::snprintf(buf, sizeof buf, "%d", volume);
+
+ strm_dict_t * dict = strm_dict_new();
+ dict = strm_dict_set(dict, "volume", buf);
+
+ if (mmr_output_parameters(m_context, m_audioId, dict) != 0)
+ emitMmError("mmr_output_parameters: Setting volume failed");
+}
+
+void QQnxMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
+{
+ QAudioOutput *out = output ? output->q : nullptr;
+ if (m_audioOutput == out)
+ return;
+
+ if (m_audioOutput)
+ disconnect(m_audioOutput.get());
+ m_audioOutput = out;
+ if (m_audioOutput) {
+ connect(out, &QAudioOutput::volumeChanged, this, &QQnxMediaPlayer::setVolume);
+ connect(out, &QAudioOutput::mutedChanged, this, &QQnxMediaPlayer::setMuted);
+ }
+ setVolume(out ? out->volume() : 1.);
+ setMuted(out ? out->isMuted() : true);
+}
+
+float QQnxMediaPlayer::bufferProgress() const
+{
+ // mm-renderer has buffer properties "status" and "level"
+ // QMediaPlayer's buffer status maps to mm-renderer's buffer level
+ return m_bufferLevel/100.0f;
+}
+
+bool QQnxMediaPlayer::isAudioAvailable() const
+{
+ return m_metaData.hasAudio();
+}
+
+bool QQnxMediaPlayer::isVideoAvailable() const
+{
+ return m_metaData.hasVideo();
+}
+
+bool QQnxMediaPlayer::isSeekable() const
+{
+ return m_metaData.isSeekable();
+}
+
+QMediaTimeRange QQnxMediaPlayer::availablePlaybackRanges() const
+{
+ // We can't get this information from the mmrenderer API yet, so pretend we can seek everywhere
+ return QMediaTimeRange(0, m_metaData.duration());
+}
+
+qreal QQnxMediaPlayer::playbackRate() const
+{
+ return ::speedToRate(m_speed);
+}
+
+void QQnxMediaPlayer::setPlaybackRate(qreal rate)
+{
+ if (!m_context)
+ return;
+
+ const int speed = ::rateToSpeed(rate);
+
+ if (m_speed == speed)
+ return;
+
+ // defer setting the playback speed for when play() is called to prevent
+ // mm-renderer from inadvertently transitioning into play state
+ if (isDeferredSpeedEnabled() && state() != QMediaPlayer::PlayingState) {
+ m_deferredSpeed = speed;
+ return;
+ }
+
+ if (mmr_speed_set(m_context, speed) != 0)
+ emitMmError("mmr_speed_set failed");
+}
+
+QUrl QQnxMediaPlayer::media() const
+{
+ return m_media;
+}
+
+const QIODevice *QQnxMediaPlayer::mediaStream() const
+{
+ // Always 0, we don't support QIODevice streams
+ return 0;
+}
+
+void QQnxMediaPlayer::setMedia(const QUrl &media, QIODevice *stream)
+{
+ Q_UNUSED(stream); // not supported
+
+ stop();
+ detach();
+
+ stateChanged(QMediaPlayer::StoppedState);
+ mediaStatusChanged(QMediaPlayer::LoadingMedia);
+
+ m_media = media;
+
+ updateMetaData(nullptr);
+ attach();
+}
+
+void QQnxMediaPlayer::play()
+{
+ if (!m_media.isValid() || !m_connection || !m_context || m_audioId == -1) {
+ stateChanged(QMediaPlayer::StoppedState);
+ return;
+ }
+
+ if (state() == QMediaPlayer::PlayingState)
+ return;
+
+ setDeferredSpeedEnabled(false);
+
+ if (m_deferredSpeed) {
+ setPlaybackRate(::speedToRate(*m_deferredSpeed));
+ m_deferredSpeed = {};
+ } else {
+ setPlaybackRate(::speedToRate(m_configuredSpeed));
+ }
+
+ setDeferredSpeedEnabled(true);
+
+ // Un-pause the state when it is paused
+ if (state() == QMediaPlayer::PausedState) {
+ return;
+ }
+
+
+ if (mediaStatus() == QMediaPlayer::EndOfMedia)
+ setPositionInternal(0);
+
+ resetMonitoring();
+ updateVolume();
+
+ if (mmr_play(m_context) != 0) {
+ stateChanged(QMediaPlayer::StoppedState);
+ emitMmError("mmr_play() failed");
+ return;
+ }
+
+ stateChanged(QMediaPlayer::PlayingState);
+}
+
+void QQnxMediaPlayer::pause()
+{
+ if (state() != QMediaPlayer::PlayingState)
+ return;
+
+ setPlaybackRate(0);
+}
+
+void QQnxMediaPlayer::stop()
+{
+ if (!m_context
+ || state() == QMediaPlayer::StoppedState
+ || mediaStatus() == QMediaPlayer::NoMedia)
+ return;
+
+ // mm-renderer does not rewind by default
+ setPositionInternal(0);
+
+ mmr_stop(m_context);
+}
+
+void QQnxMediaPlayer::setVideoSink(QVideoSink *videoSink)
+{
+ m_platformVideoSink = videoSink
+ ? static_cast<QQnxVideoSink *>(videoSink->platformVideoSink())
+ : nullptr;
+}
+
+void QQnxMediaPlayer::startMonitoring()
+{
+ m_eventThread = new QQnxMediaEventThread(m_context);
+
+ connect(m_eventThread, &QQnxMediaEventThread::eventPending,
+ this, &QQnxMediaPlayer::readEvents);
+
+ m_eventThread->setObjectName(QStringLiteral("MmrEventThread-") + QString::number(m_id));
+ m_eventThread->start();
+}
+
+void QQnxMediaPlayer::stopMonitoring()
+{
+ delete m_eventThread;
+ m_eventThread = nullptr;
+}
+
+void QQnxMediaPlayer::resetMonitoring()
+{
+ m_bufferLevel = 0;
+ m_position = 0;
+ m_speed = 0;
+}
+
+void QQnxMediaPlayer::handleMmPositionChanged(qint64 newPosition)
+{
+ m_position = newPosition;
+
+ if (state() == QMediaPlayer::PausedState)
+ m_windowGrabber->forceUpdate();
+
+ positionChanged(m_position);
+}
+
+void QQnxMediaPlayer::updateBufferLevel(int level, int capacity)
+{
+ m_bufferLevel = capacity == 0 ? 0 : level / static_cast<float>(capacity) * 100.0f;
+ m_bufferLevel = qBound(0, m_bufferLevel, 100);
+ bufferProgressChanged(m_bufferLevel/100.0f);
+}
+
+void QQnxMediaPlayer::updateMetaData(const strm_dict *dict)
+{
+ m_metaData.update(dict);
+
+ durationChanged(m_metaData.duration());
+ audioAvailableChanged(m_metaData.hasAudio());
+ videoAvailableChanged(m_metaData.hasVideo());
+ seekableChanged(m_metaData.isSeekable());
+}
+
+void QQnxMediaPlayer::emitMmError(const char *msg)
+{
+ emitMmError(QString::fromUtf8(msg));
+}
+
+void QQnxMediaPlayer::emitMmError(const QString &msg)
+{
+ int errorCode = MMR_ERROR_NONE;
+ const QString errorMessage = mmErrorMessage(msg, m_context, &errorCode);
+ emit error(errorCode, errorMessage);
+}
+
+void QQnxMediaPlayer::emitPError(const QString &msg)
+{
+ const QString errorMessage = QString::fromLatin1("%1: %2").arg(msg).arg(QString::fromUtf8(strerror(errno)));
+ emit error(errno, errorMessage);
+}
+
+
+void QQnxMediaPlayer::readEvents()
+{
+ while (const mmr_event_t *event = mmr_event_get(m_context)) {
+ if (event->type == MMR_EVENT_NONE)
+ break;
+
+ switch (event->type) {
+ case MMR_EVENT_STATUS:
+ handleMmEventStatus(event);
+ break;
+ case MMR_EVENT_STATE:
+ handleMmEventState(event);
+ break;
+ case MMR_EVENT_METADATA:
+ updateMetaData(event->data);
+ break;
+ case MMR_EVENT_ERROR:
+ handleMmEventError(event);
+ break;
+ case MMR_EVENT_NONE:
+ case MMR_EVENT_OVERFLOW:
+ case MMR_EVENT_WARNING:
+ case MMR_EVENT_PLAYLIST:
+ case MMR_EVENT_INPUT:
+ case MMR_EVENT_OUTPUT:
+ case MMR_EVENT_CTXTPAR:
+ case MMR_EVENT_TRKPAR:
+ case MMR_EVENT_OTHER:
+ break;
+ }
+ }
+
+ if (m_eventThread)
+ m_eventThread->signalRead();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxmediaplayer_p.cpp"
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer_p.h b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer_p.h
new file mode 100644
index 000000000..c570a6334
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediaplayer_p.h
@@ -0,0 +1,167 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQnxMediaPlayer_H
+#define QQnxMediaPlayer_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqnxmediametadata_p.h"
+#include "mmrenderertypes.h"
+
+#include <private/qplatformmediaplayer_p.h>
+#include <QtCore/qabstractnativeeventfilter.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qtimer.h>
+
+#include <mm/renderer.h>
+#include <mm/renderer/types.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxVideoSink;
+class QQnxMediaEventThread;
+class QQnxWindowGrabber;
+
+class QQnxMediaPlayer : public QObject
+ , public QPlatformMediaPlayer
+{
+ Q_OBJECT
+public:
+ explicit QQnxMediaPlayer(QMediaPlayer *parent = nullptr);
+ ~QQnxMediaPlayer();
+
+ qint64 duration() const override;
+
+ qint64 position() const override;
+ void setPosition(qint64 position) override;
+
+ void setAudioOutput(QPlatformAudioOutput *) override;
+
+ float bufferProgress() const override;
+
+ bool isAudioAvailable() const override;
+ bool isVideoAvailable() const override;
+
+ bool isSeekable() const override;
+
+ QMediaTimeRange availablePlaybackRanges() const override;
+
+ qreal playbackRate() const override;
+ void setPlaybackRate(qreal rate) override;
+
+ QUrl media() const override;
+ const QIODevice *mediaStream() const override;
+ void setMedia(const QUrl &media, QIODevice *stream) override;
+
+ void play() override;
+ void pause() override;
+ void stop() override;
+
+ void setVideoSink(QVideoSink *videoSink);
+
+private Q_SLOTS:
+ void setVolume(float volume);
+ void setMuted(bool muted);
+ void readEvents();
+
+private:
+ void startMonitoring();
+ void stopMonitoring();
+ void resetMonitoring();
+
+ void openConnection();
+ void emitMmError(const char *msg);
+ void emitMmError(const QString &msg);
+ void emitPError(const QString &msg);
+
+ void handleMmPositionChanged(qint64 newPosition);
+ void updateBufferLevel(int level, int capacity);
+ void updateMetaData(const strm_dict_t *dict);
+
+ void handleMmEventState(const mmr_event_t *event);
+ void handleMmEventStatus(const mmr_event_t *event);
+ void handleMmEventStatusData(const strm_dict_t *data);
+ void handleMmEventError(const mmr_event_t *event);
+
+ QByteArray resourcePathForUrl(const QUrl &url);
+
+ void closeConnection();
+ void attach();
+
+ bool attachVideoOutput();
+ bool attachAudioOutput();
+ bool attachInput();
+
+ void detach();
+ void detachVideoOutput();
+ void detachAudioOutput();
+ void detachInput();
+
+ bool isVideoOutputAttached() const;
+ bool isAudioOutputAttached() const;
+ bool isInputAttached() const;
+
+ void updateScene(const QSize &size);
+
+ void updateVolume();
+
+ void setPositionInternal(qint64 position);
+ void flushPosition();
+
+ bool isPendingPositionFlush() const;
+
+ void setDeferredSpeedEnabled(bool enabled);
+ bool isDeferredSpeedEnabled() const;
+
+ mmr_context_t *m_context = nullptr;
+ mmr_connection_t *m_connection = nullptr;
+
+ QString m_contextName;
+
+ int m_id = -1;
+ int m_audioId = -1;
+ int m_volume = 50; // range is 0-100
+
+ QUrl m_media;
+ QPointer<QAudioOutput> m_audioOutput;
+ QPointer<QQnxVideoSink> m_platformVideoSink;
+
+ QQnxMediaMetaData m_metaData;
+
+ qint64 m_position = 0;
+ qint64 m_pendingPosition = 0;
+
+ int m_bufferLevel = 0;
+
+ int m_videoId = -1;
+
+ QTimer m_flushPositionTimer;
+
+ QQnxMediaEventThread *m_eventThread = nullptr;
+
+ int m_speed = 1000;
+ int m_configuredSpeed = 1000;
+
+ std::optional<int> m_deferredSpeed;
+
+ QQnxWindowGrabber* m_windowGrabber = nullptr;
+
+ bool m_inputAttached = false;
+ bool m_muted = false;
+ bool m_deferredSpeedEnabled = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil.cpp b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil.cpp
new file mode 100644
index 000000000..074989642
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qqnxmediautil_p.h"
+
+#include <QDebug>
+#include <QDir>
+#include <QFile>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonValue>
+#include <QMutex>
+#include <QMutex>
+#include <QString>
+#include <QXmlStreamReader>
+
+#include <mm/renderer.h>
+#include <mm/renderer/types.h>
+
+QT_BEGIN_NAMESPACE
+
+struct MmError {
+ int errorCode;
+ const char *name;
+};
+
+#define MM_ERROR_ENTRY(error) { error, #error }
+static const MmError mmErrors[] = {
+ MM_ERROR_ENTRY(MMR_ERROR_NONE),
+ MM_ERROR_ENTRY(MMR_ERROR_UNKNOWN ),
+ MM_ERROR_ENTRY(MMR_ERROR_INVALID_PARAMETER ),
+ MM_ERROR_ENTRY(MMR_ERROR_INVALID_STATE),
+ MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_VALUE),
+ MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_MEDIA_TYPE),
+ MM_ERROR_ENTRY(MMR_ERROR_MEDIA_PROTECTED),
+ MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_OPERATION),
+ MM_ERROR_ENTRY(MMR_ERROR_READ),
+ MM_ERROR_ENTRY(MMR_ERROR_WRITE),
+ MM_ERROR_ENTRY(MMR_ERROR_MEDIA_UNAVAILABLE),
+ MM_ERROR_ENTRY(MMR_ERROR_MEDIA_CORRUPTED),
+ MM_ERROR_ENTRY(MMR_ERROR_OUTPUT_UNAVAILABLE),
+ MM_ERROR_ENTRY(MMR_ERROR_NO_MEMORY),
+ MM_ERROR_ENTRY(MMR_ERROR_RESOURCE_UNAVAILABLE),
+ MM_ERROR_ENTRY(MMR_ERROR_MEDIA_DRM_NO_RIGHTS),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_CORRUPTED_DATA_STORE),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OUTPUT_PROTECTION),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_HDMI),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_DISPLAYPORT),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_DVI),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_ANALOG_VIDEO),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_ANALOG_AUDIO),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_TOSLINK),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_SPDIF),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_BLUETOOTH),
+ MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_WIRELESSHD),
+};
+static const unsigned int numMmErrors = sizeof(mmErrors) / sizeof(MmError);
+
+template <typename T, size_t N>
+constexpr size_t countof(T (&)[N])
+{
+ return N;
+}
+
+QString keyValueMapsLocation()
+{
+ QByteArray qtKeyValueMaps = qgetenv("QT_KEY_VALUE_MAPS");
+ if (qtKeyValueMaps.isNull())
+ return QString::fromUtf8("/etc/qt/keyvaluemaps");
+ else
+ return QString::fromUtf8(qtKeyValueMaps);
+}
+
+QJsonObject loadMapObject(const QString &keyValueMapPath)
+{
+ QFile mapFile(keyValueMapsLocation() + keyValueMapPath);
+ if (mapFile.open(QIODevice::ReadOnly)) {
+ QByteArray mapFileContents = mapFile.readAll();
+ QJsonDocument mapDocument = QJsonDocument::fromJson(mapFileContents);
+ if (mapDocument.isObject()) {
+ QJsonObject mapObject = mapDocument.object();
+ return mapObject;
+ }
+ }
+ return QJsonObject();
+}
+
+QString mmErrorMessage(const QString &msg, mmr_context_t *context, int *errorCode)
+{
+ const mmr_error_info_t * const mmError = mmr_error_info(context);
+
+ if (errorCode)
+ *errorCode = mmError->error_code;
+
+ if (mmError->error_code < numMmErrors) {
+ return QString::fromLatin1("%1: %2 (code %3)").arg(msg).arg(QString::fromUtf8(mmErrors[mmError->error_code].name))
+ .arg(mmError->error_code);
+ } else {
+ return QString::fromLatin1("%1: Unknown error code %2").arg(msg).arg(mmError->error_code);
+ }
+}
+
+bool checkForDrmPermission()
+{
+ QDir sandboxDir = QDir::home(); // always returns 'data' directory
+ sandboxDir.cdUp(); // change to app sandbox directory
+
+ QFile file(sandboxDir.filePath(QString::fromUtf8("app/native/bar-descriptor.xml")));
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "checkForDrmPermission: Unable to open bar-descriptor.xml";
+ return false;
+ }
+
+ QXmlStreamReader reader(&file);
+ while (!reader.atEnd()) {
+ reader.readNextStartElement();
+ if (reader.name() == QLatin1String("action")
+ || reader.name() == QLatin1String("permission")) {
+ if (reader.readElementText().trimmed() == QLatin1String("access_protected_media"))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil_p.h b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil_p.h
new file mode 100644
index 000000000..7b709142f
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxmediautil_p.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef MMRENDERERUTIL_H
+#define MMRENDERERUTIL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtMultimedia/qaudio.h>
+
+typedef struct mmr_context mmr_context_t;
+
+QT_BEGIN_NAMESPACE
+
+class QString;
+
+QString mmErrorMessage(const QString &msg, mmr_context_t *context, int * errorCode = 0);
+
+bool checkForDrmPermission();
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink.cpp b/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink.cpp
new file mode 100644
index 000000000..18d4d1828
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2016 Research In Motion
+// Copyright (C) 2021 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qqnxvideosink_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQnxVideoSink::QQnxVideoSink(QVideoSink *parent)
+ : QPlatformVideoSink(parent)
+{
+}
+
+void QQnxVideoSink::setRhi(QRhi *rhi)
+{
+ m_rhi = rhi;
+}
+
+QRhi *QQnxVideoSink::rhi() const
+{
+ return m_rhi;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qqnxvideosink_p.cpp"
diff --git a/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink_p.h b/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink_p.h
new file mode 100644
index 000000000..2cc7990db
--- /dev/null
+++ b/src/plugins/multimedia/qnx/mediaplayer/qqnxvideosink_p.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2016 Research In Motion
+// Copyright (C) 2021 The Qt Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QQNXVIDFEOSINK_P_H
+#define QQNXVIDFEOSINK_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformvideosink_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxWindowGrabber;
+class QVideoSink;
+
+class QQnxVideoSink : public QPlatformVideoSink
+{
+ Q_OBJECT
+public:
+ explicit QQnxVideoSink(QVideoSink *parent = 0);
+
+ void setRhi(QRhi *) override;
+
+ QRhi *rhi() const;
+
+private:
+ QRhi *m_rhi = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/qnx.json b/src/plugins/multimedia/qnx/qnx.json
new file mode 100644
index 000000000..38df228ef
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qnx.json
@@ -0,0 +1,3 @@
+{
+ "Keys": [ "qnx" ]
+}
diff --git a/src/plugins/multimedia/qnx/qqnxformatinfo.cpp b/src/plugins/multimedia/qnx/qqnxformatinfo.cpp
new file mode 100644
index 000000000..77492e80d
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxformatinfo.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2021 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 "qqnxformatinfo_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QQnxFormatInfo::QQnxFormatInfo()
+{
+ // ### This is probably somewhat correct for encoding, but should be checked
+ encoders = {
+ { QMediaFormat::MPEG4,
+ { QMediaFormat::AudioCodec::AAC },
+ { QMediaFormat::VideoCodec::H264 } },
+ { QMediaFormat::Mpeg4Audio,
+ { QMediaFormat::AudioCodec::AAC },
+ {} },
+ { QMediaFormat::Wave,
+ { QMediaFormat::AudioCodec::Wave },
+ {} },
+ { QMediaFormat::AAC,
+ { QMediaFormat::AudioCodec::AAC },
+ {} },
+ };
+
+ // ### There can apparently be more codecs and demuxers installed on the system as plugins
+ // Need to find a way to determine the list at compile time or runtime
+ decoders = encoders;
+
+ // ###
+ imageFormats << QImageCapture::JPEG;
+}
+
+QQnxFormatInfo::~QQnxFormatInfo() = default;
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/qqnxformatinfo_p.h b/src/plugins/multimedia/qnx/qqnxformatinfo_p.h
new file mode 100644
index 000000000..aae3a026a
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxformatinfo_p.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2021 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
+
+#ifndef QQNXFORMATINFO_H
+#define QQNXFORMATINFO_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformmediaformatinfo_p.h>
+#include <qhash.h>
+#include <qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxFormatInfo : public QPlatformMediaFormatInfo
+{
+public:
+ QQnxFormatInfo();
+ ~QQnxFormatInfo();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/qqnxmediaintegration.cpp b/src/plugins/multimedia/qnx/qqnxmediaintegration.cpp
new file mode 100644
index 000000000..8567a69fd
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxmediaintegration.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2021 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 "qqnxmediaintegration_p.h"
+#include "qqnxmediacapturesession_p.h"
+#include "qqnxmediarecorder_p.h"
+#include "qqnxformatinfo_p.h"
+#include "qqnxvideodevices_p.h"
+#include "qqnxvideosink_p.h"
+#include "qqnxmediaplayer_p.h"
+#include "qqnximagecapture_p.h"
+#include "qqnxplatformcamera_p.h"
+#include <QtMultimedia/private/qplatformmediaplugin_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxMediaPlugin : public QPlatformMediaPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QPlatformMediaPlugin_iid FILE "qnx.json")
+
+public:
+ QQnxMediaPlugin()
+ : QPlatformMediaPlugin()
+ {}
+
+ QPlatformMediaIntegration* create(const QString &name) override
+ {
+ if (name == u"qnx")
+ return new QQnxMediaIntegration;
+ return nullptr;
+ }
+};
+
+QQnxMediaIntegration::QQnxMediaIntegration() : QPlatformMediaIntegration(QLatin1String("qnx")) { }
+
+QPlatformMediaFormatInfo *QQnxMediaIntegration::createFormatInfo()
+{
+ return new QQnxFormatInfo;
+}
+
+QPlatformVideoDevices *QQnxMediaIntegration::createVideoDevices()
+{
+ return new QQnxVideoDevices(this);
+}
+
+QMaybe<QPlatformVideoSink *> QQnxMediaIntegration::createVideoSink(QVideoSink *sink)
+{
+ return new QQnxVideoSink(sink);
+}
+
+QMaybe<QPlatformMediaPlayer *> QQnxMediaIntegration::createPlayer(QMediaPlayer *parent)
+{
+ return new QQnxMediaPlayer(parent);
+}
+
+QMaybe<QPlatformMediaCaptureSession *> QQnxMediaIntegration::createCaptureSession()
+{
+ return new QQnxMediaCaptureSession();
+}
+
+QMaybe<QPlatformMediaRecorder *> QQnxMediaIntegration::createRecorder(QMediaRecorder *parent)
+{
+ return new QQnxMediaRecorder(parent);
+}
+
+QMaybe<QPlatformCamera *> QQnxMediaIntegration::createCamera(QCamera *parent)
+{
+ return new QQnxPlatformCamera(parent);
+}
+
+QMaybe<QPlatformImageCapture *> QQnxMediaIntegration::createImageCapture(QImageCapture *parent)
+{
+ return new QQnxImageCapture(parent);
+}
+
+QT_END_NAMESPACE
+
+#include "qqnxmediaintegration.moc"
diff --git a/src/plugins/multimedia/qnx/qqnxmediaintegration_p.h b/src/plugins/multimedia/qnx/qqnxmediaintegration_p.h
new file mode 100644
index 000000000..60fafc246
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxmediaintegration_p.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2021 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
+
+#ifndef QQnxMediaIntegration_H
+#define QQnxMediaIntegration_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformmediaintegration_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxPlayerInterface;
+class QQnxFormatInfo;
+
+class QQnxMediaIntegration : public QPlatformMediaIntegration
+{
+public:
+ QQnxMediaIntegration();
+
+ QMaybe<QPlatformVideoSink *> createVideoSink(QVideoSink *sink) override;
+
+ QMaybe<QPlatformMediaPlayer *> createPlayer(QMediaPlayer *parent) override;
+
+ QMaybe<QPlatformMediaCaptureSession *> createCaptureSession() override;
+
+ QMaybe<QPlatformMediaRecorder *> createRecorder(QMediaRecorder *parent) override;
+
+ QMaybe<QPlatformCamera *> createCamera(QCamera *parent) override;
+
+ QMaybe<QPlatformImageCapture *> createImageCapture(QImageCapture *parent) override;
+
+protected:
+ QPlatformMediaFormatInfo *createFormatInfo() override;
+
+ QPlatformVideoDevices *createVideoDevices() override;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/multimedia/qnx/qqnxvideodevices.cpp b/src/plugins/multimedia/qnx/qqnxvideodevices.cpp
new file mode 100644
index 000000000..ea0cfd956
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxvideodevices.cpp
@@ -0,0 +1,111 @@
+// Copyright (C) 2021 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 "qqnxvideodevices_p.h"
+#include "qqnxcamera_p.h"
+#include "private/qcameradevice_p.h"
+#include "qcameradevice.h"
+
+#include <qdir.h>
+#include <qdebug.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+static QVideoFrameFormat::PixelFormat fromCameraFrametype(camera_frametype_t type)
+{
+ switch (type) {
+ default:
+ case CAMERA_FRAMETYPE_UNSPECIFIED:
+ return QVideoFrameFormat::Format_Invalid;
+ case CAMERA_FRAMETYPE_NV12:
+ return QVideoFrameFormat::Format_NV12;
+ case CAMERA_FRAMETYPE_RGB8888:
+ return QVideoFrameFormat::Format_ARGB8888;
+ case CAMERA_FRAMETYPE_JPEG:
+ return QVideoFrameFormat::Format_Jpeg;
+ case CAMERA_FRAMETYPE_GRAY8:
+ return QVideoFrameFormat::Format_Y8;
+ case CAMERA_FRAMETYPE_CBYCRY:
+ return QVideoFrameFormat::Format_UYVY;
+ case CAMERA_FRAMETYPE_YCBCR420P:
+ return QVideoFrameFormat::Format_YUV420P;
+ case CAMERA_FRAMETYPE_YCBYCR:
+ return QVideoFrameFormat::Format_YUYV;
+ }
+}
+
+static std::optional<QCameraDevice> createCameraDevice(camera_unit_t unit, bool isDefault)
+{
+ const QQnxCamera camera(unit);
+
+ if (!camera.isValid()) {
+ qWarning() << "Invalid camera unit:" << unit;
+ return {};
+ }
+
+ auto *p = new QCameraDevicePrivate;
+
+ p->id = QByteArray::number(camera.unit());
+ p->description = camera.name();
+ p->isDefault = isDefault;
+
+ const QList<camera_frametype_t> frameTypes = camera.supportedVfFrameTypes();
+
+ for (camera_res_t res : camera.supportedVfResolutions()) {
+ const QSize resolution(res.width, res.height);
+
+ p->photoResolutions.append(resolution);
+
+ for (camera_frametype_t frameType : camera.supportedVfFrameTypes()) {
+ const QVideoFrameFormat::PixelFormat pixelFormat = fromCameraFrametype(frameType);
+
+ if (pixelFormat == QVideoFrameFormat::Format_Invalid)
+ continue;
+
+ auto *f = new QCameraFormatPrivate;
+ p->videoFormats.append(f->create());
+
+ f->resolution = resolution;
+ f->pixelFormat = pixelFormat;
+ f->minFrameRate = 1.e10;
+
+ for (double fr : camera.specifiedVfFrameRates(frameType, res)) {
+ if (fr < f->minFrameRate)
+ f->minFrameRate = fr;
+ if (fr > f->maxFrameRate)
+ f->maxFrameRate = fr;
+ }
+ }
+ }
+
+ return p->create();
+}
+
+QQnxVideoDevices::QQnxVideoDevices(QPlatformMediaIntegration *integration)
+ : QPlatformVideoDevices(integration)
+{
+}
+
+QList<QCameraDevice> QQnxVideoDevices::videoDevices() const
+{
+ QList<QCameraDevice> cameras;
+
+ bool isDefault = true;
+
+ for (const camera_unit_t cameraUnit : QQnxCamera::supportedUnits()) {
+ const std::optional<QCameraDevice> cameraDevice = createCameraDevice(cameraUnit, isDefault);
+
+ if (!cameraDevice)
+ continue;
+
+ cameras.append(*cameraDevice);
+
+ isDefault = false;
+ }
+
+ return cameras;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/qnx/qqnxvideodevices_p.h b/src/plugins/multimedia/qnx/qqnxvideodevices_p.h
new file mode 100644
index 000000000..cc2284e57
--- /dev/null
+++ b/src/plugins/multimedia/qnx/qqnxvideodevices_p.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 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
+
+#ifndef QQNXVIDEODEVICES_H
+#define QQNXVIDEODEVICES_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformvideodevices_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQnxVideoDevices : public QPlatformVideoDevices
+{
+public:
+ explicit QQnxVideoDevices(QPlatformMediaIntegration *integration);
+
+ QList<QCameraDevice> videoDevices() const override;
+};
+
+QT_END_NAMESPACE
+
+#endif