diff options
Diffstat (limited to 'src/plugins/multimedia/qnx')
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 |