diff options
author | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-10-03 14:55:07 +0300 |
---|---|---|
committer | Tarja Sundqvist <tarja.sundqvist@qt.io> | 2023-10-03 14:55:07 +0300 |
commit | e5a555ca80cb64e92f2e5fa921ad2a92afccd53d (patch) | |
tree | 875604e410f7db073bbcdaaac93255b9df6e738c | |
parent | 6186737e9f3a6b4b3fcbc5840b80baeb91cf13fc (diff) | |
parent | 3dac584901aff0e9e45476b5a7a38a6b1c3d1d0c (diff) |
Merge remote-tracking branch 'origin/tqtc/lts-6.2.7' into tqtc/lts-6.2-opensource
Change-Id: I84dc3d05ddb2f33794df200e5583f5c67abcc0b3
25 files changed, 370 insertions, 716 deletions
diff --git a/.cmake.conf b/.cmake.conf index 6f32ba4d5..07d90b8ad 100644 --- a/.cmake.conf +++ b/.cmake.conf @@ -1,2 +1,2 @@ -set(QT_REPO_MODULE_VERSION "6.2.6") +set(QT_REPO_MODULE_VERSION "6.2.7") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "") diff --git a/coin/module_config.yaml b/coin/module_config.yaml index 7733000cb..c118553ca 100644 --- a/coin/module_config.yaml +++ b/coin/module_config.yaml @@ -1,4 +1,5 @@ version: 2 +alias: qtmultimedia accept_configuration: condition: property property: features diff --git a/examples/multimedia/video/doc/src/qmlvideo.qdoc b/examples/multimedia/video/doc/src/qmlvideo.qdoc index b8ab25d03..e3edf39e7 100644 --- a/examples/multimedia/video/doc/src/qmlvideo.qdoc +++ b/examples/multimedia/video/doc/src/qmlvideo.qdoc @@ -54,7 +54,7 @@ The \c main.qml file creates a UI which includes the following items: \list \li Two \c Button instances, each of which displays a filename, and can be - used to launch a \c FileBrowser. + used to launch a \c FileDialog. \li An exit \c Button. \li A \c SceneSelectionPanel, which is a flickable list displaying the available scenes. diff --git a/examples/multimedia/video/qmlvideo/CMakeLists.txt b/examples/multimedia/video/qmlvideo/CMakeLists.txt index c3eff7876..2eb9bc82d 100644 --- a/examples/multimedia/video/qmlvideo/CMakeLists.txt +++ b/examples/multimedia/video/qmlvideo/CMakeLists.txt @@ -79,7 +79,6 @@ set(qmlvideo_resource_files "qml/qmlvideo/CameraSpin.qml" "qml/qmlvideo/Content.qml" "qml/qmlvideo/ErrorDialog.qml" - "qml/qmlvideo/FileBrowser.qml" "qml/qmlvideo/Scene.qml" "qml/qmlvideo/SceneBasic.qml" "qml/qmlvideo/SceneDrag.qml" diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml deleted file mode 100644 index 31684368e..000000000 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml +++ /dev/null @@ -1,419 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:BSD$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** BSD License Usage -** Alternatively, you may use this file under the terms of the BSD license -** as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of The Qt Company Ltd nor the names of its -** contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick -import Qt.labs.folderlistmodel - -Rectangle { - id: fileBrowser - color: "transparent" - - property string folder - - property int itemHeight: Math.min(parent.width, parent.height) / 15 - property int buttonHeight: Math.min(parent.width, parent.height) / 12 - - signal fileSelected(string file) - - function selectFile(file) { - if (file !== "") { - folder = loader.item.folders.folder - fileBrowser.fileSelected(file) - } - loader.sourceComponent = undefined - } - - Loader { - id: loader - } - - function show() { - loader.sourceComponent = fileBrowserComponent - loader.item.parent = fileBrowser - loader.item.anchors.fill = fileBrowser - loader.item.folder = fileBrowser.folder - } - - Component { - id: fileBrowserComponent - - Rectangle { - id: root - color: "black" - property bool showFocusHighlight: false - property variant folders: folders1 - property variant view: view1 - property alias folder: folders1.folder - property color textColor: "white" - - FolderListModel { - id: folders1 - folder: folder - } - - FolderListModel { - id: folders2 - folder: folder - } - - SystemPalette { - id: palette - } - - Component { - id: folderDelegate - - Rectangle { - id: wrapper - function launch() { - var path = "file://"; - if (filePath.length > 2 && filePath[1] === ':') // Windows drive logic, see QUrl::fromLocalFile() - path += '/'; - path += filePath; - if (folders.isFolder(index)) - down(path); - else - fileBrowser.selectFile(path) - } - width: root.width - height: folderImage.height - color: "transparent" - - Rectangle { - id: highlight - visible: false - anchors.fill: parent - anchors.leftMargin: 5 - anchors.rightMargin: 5 - color: "#212121" - } - - Item { - id: folderImage - width: itemHeight - height: itemHeight - Image { - id: folderPicture - source: "qrc:/folder.png" - width: itemHeight * 0.9 - height: itemHeight * 0.9 - anchors.left: parent.left - anchors.margins: 5 - visible: folders.isFolder(index) - } - } - - Text { - id: nameText - anchors.fill: parent; - verticalAlignment: Text.AlignVCenter - text: fileName - anchors.leftMargin: itemHeight + 10 - color: (wrapper.ListView.isCurrentItem && root.showFocusHighlight) ? palette.highlightedText : textColor - elide: Text.ElideRight - } - - MouseArea { - id: mouseRegion - anchors.fill: parent - onPressed: { - root.showFocusHighlight = false; - wrapper.ListView.view.currentIndex = index; - } - onClicked: { if (folders === wrapper.ListView.view.model) launch() } - } - - states: [ - State { - name: "pressed" - when: mouseRegion.pressed - PropertyChanges { target: highlight; visible: true } - PropertyChanges { target: nameText; color: palette.highlightedText } - } - ] - } - } - - ListView { - id: view1 - anchors.top: titleBar.bottom - anchors.bottom: cancelButton.top - width: parent.width - model: folders1 - delegate: folderDelegate - highlight: Rectangle { - color: "#212121" - visible: root.showFocusHighlight && view1.count != 0 - width: view1.currentItem == null ? 0 : view1.currentItem.width - } - highlightMoveVelocity: 1000 - pressDelay: 100 - focus: true - state: "current" - states: [ - State { - name: "current" - PropertyChanges { target: view1; x: 0 } - }, - State { - name: "exitLeft" - PropertyChanges { target: view1; x: -root.width } - }, - State { - name: "exitRight" - PropertyChanges { target: view1; x: root.width } - } - ] - transitions: [ - Transition { - to: "current" - SequentialAnimation { - NumberAnimation { properties: "x"; duration: 250 } - } - }, - Transition { - NumberAnimation { properties: "x"; duration: 250 } - NumberAnimation { properties: "x"; duration: 250 } - } - ] - Keys.onPressed: root.keyPressed(event.key) - } - - ListView { - id: view2 - anchors.top: titleBar.bottom - anchors.bottom: parent.bottom - x: parent.width - width: parent.width - model: folders2 - delegate: folderDelegate - highlight: Rectangle { - color: "#212121" - visible: root.showFocusHighlight && view2.count != 0 - width: view1.currentItem == null ? 0 : view1.currentItem.width - } - highlightMoveVelocity: 1000 - pressDelay: 100 - states: [ - State { - name: "current" - PropertyChanges { target: view2; x: 0 } - }, - State { - name: "exitLeft" - PropertyChanges { target: view2; x: -root.width } - }, - State { - name: "exitRight" - PropertyChanges { target: view2; x: root.width } - } - ] - transitions: [ - Transition { - to: "current" - SequentialAnimation { - NumberAnimation { properties: "x"; duration: 250 } - } - }, - Transition { - NumberAnimation { properties: "x"; duration: 250 } - } - ] - Keys.onPressed: root.keyPressed(event.key) - } - - Rectangle { - width: parent.width - height: buttonHeight + 10 - anchors.bottom: parent.bottom - color: "black" - } - - Rectangle { - id: cancelButton - width: parent.width - height: buttonHeight - color: "#212121" - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: 5 - radius: buttonHeight / 15 - - Text { - anchors.fill: parent - text: "Cancel" - color: "white" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - MouseArea { - anchors.fill: parent - onClicked: fileBrowser.selectFile("") - } - } - - Keys.onPressed: { - root.keyPressed(event.key); - if (event.key === Qt.Key_Return || event.key === Qt.Key_Select || event.key === Qt.Key_Right) { - view.currentItem.launch(); - event.accepted = true; - } else if (event.key === Qt.Key_Left) { - up(); - } - } - - - Rectangle { - id: titleBar - width: parent.width - height: buttonHeight + 10 - anchors.top: parent.top - color: "black" - - Rectangle { - width: parent.width; - height: buttonHeight - color: "#212121" - anchors.margins: 5 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - radius: buttonHeight / 15 - - Rectangle { - id: upButton - width: buttonHeight - height: buttonHeight - color: "transparent" - Image { - width: itemHeight - height: itemHeight - anchors.centerIn: parent - source: "qrc:/up.png" - } - MouseArea { id: upRegion; anchors.centerIn: parent - width: buttonHeight - height: buttonHeight - onClicked: up() - } - states: [ - State { - name: "pressed" - when: upRegion.pressed - PropertyChanges { target: upButton; color: palette.highlight } - } - ] - } - - Text { - anchors.left: upButton.right; anchors.right: parent.right; height: parent.height - anchors.leftMargin: 5; anchors.rightMargin: 5 - text: folders.folder - color: "white" - elide: Text.ElideLeft; - horizontalAlignment: Text.AlignLeft; - verticalAlignment: Text.AlignVCenter - } - } - } - - function down(path) { - if (folders == folders1) { - view = view2 - folders = folders2; - view1.state = "exitLeft"; - } else { - view = view1 - folders = folders1; - view2.state = "exitLeft"; - } - view.x = root.width; - view.state = "current"; - view.focus = true; - folders.folder = path; - } - - function up() { - var path = folders.parentFolder; - if (path.toString().length === 0 || path.toString() === 'file:') - return; - if (folders == folders1) { - view = view2 - folders = folders2; - view1.state = "exitRight"; - } else { - view = view1 - folders = folders1; - view2.state = "exitRight"; - } - view.x = -root.width; - view.state = "current"; - view.focus = true; - folders.folder = path; - } - - function keyPressed(key) { - switch (key) { - case Qt.Key_Up: - case Qt.Key_Down: - case Qt.Key_Left: - case Qt.Key_Right: - root.showFocusHighlight = true; - break; - default: - // do nothing - break; - } - } - } - } -} diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml index 4289f79f1..e8c200301 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml @@ -49,6 +49,7 @@ ****************************************************************************/ import QtQuick +import QtQuick.Dialogs Rectangle { id: root @@ -114,7 +115,10 @@ Rectangle { textColorSelected: "white" height: d.buttonHeight text: (root.source1 == "") ? "Select file 1" : root.source1 - onClicked: fileBrowser1.show() + onClicked: { + fileBrowser.setFirstSource = true + fileBrowser.open() + } } Button { @@ -130,7 +134,10 @@ Rectangle { textColorSelected: "white" height: d.buttonHeight text: (root.source2 == "") ? "Select file 2" : root.source2 - onClicked: fileBrowser2.show() + onClicked: { + fileBrowser.setFirstSource = false + fileBrowser.open() + } } Button { @@ -237,26 +244,15 @@ Rectangle { ignoreUnknownSignals: true } - FileBrowser { - id: fileBrowser1 - anchors.fill: root - onFolderChanged: fileBrowser2.folder = folder - Component.onCompleted: fileSelected.connect(root.openFile1) - } - - FileBrowser { - id: fileBrowser2 - anchors.fill: root - onFolderChanged: fileBrowser1.folder = folder - Component.onCompleted: fileSelected.connect(root.openFile2) - } - - function openFile1(path) { - root.source1 = path - } - - function openFile2(path) { - root.source2 = path + FileDialog { + id: fileBrowser + property bool setFirstSource + onAccepted: { + if (setFirstSource) + root.source1 = currentFile + else + root.source2 = currentFile + } } ErrorDialog { @@ -270,8 +266,7 @@ Rectangle { // Called from main() once root properties have been set function init() { performanceLoader.init() - fileBrowser1.folder = videoPath - fileBrowser2.folder = videoPath + fileBrowser.currentFolder = videoPath } function qmlFramePainted() { diff --git a/examples/multimedia/video/qmlvideo/qmlvideo.pro b/examples/multimedia/video/qmlvideo/qmlvideo.pro index 7a6bbccff..b16368f7c 100644 --- a/examples/multimedia/video/qmlvideo/qmlvideo.pro +++ b/examples/multimedia/video/qmlvideo/qmlvideo.pro @@ -44,7 +44,6 @@ resources.files = \ qml/qmlvideo/CameraSpin.qml \ qml/qmlvideo/Content.qml \ qml/qmlvideo/ErrorDialog.qml \ - qml/qmlvideo/FileBrowser.qml \ qml/qmlvideo/Scene.qml \ qml/qmlvideo/SceneBasic.qml \ qml/qmlvideo/SceneDrag.qml \ diff --git a/examples/multimedia/video/recorder/AudioInputSelect.qml b/examples/multimedia/video/recorder/AudioInputSelect.qml index bb79f2793..757eab98f 100644 --- a/examples/multimedia/video/recorder/AudioInputSelect.qml +++ b/examples/multimedia/video/recorder/AudioInputSelect.qml @@ -60,7 +60,7 @@ Row { MediaDevices { id: mediaDevices } - AudioInput { id: audioInput; muted: false } + AudioInput { id: audioInput; muted: !audioSwitch.checked } Switch { id: audioSwitch; diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java index ce5dd5008..753586f21 100644 --- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java +++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java @@ -48,6 +48,7 @@ import java.io.FileInputStream; import android.content.Context; import android.media.MediaPlayer; import android.media.MediaFormat; +import android.media.PlaybackParams; import android.media.AudioAttributes; import android.media.TimedText; import android.net.Uri; @@ -759,4 +760,28 @@ public class QtAndroidMediaPlayer Log.w(TAG, exception); } } + + public boolean setPlaybackRate(float rate) + { + PlaybackParams playbackParams = mMediaPlayer.getPlaybackParams(); + playbackParams.setSpeed(rate); + // According to discussion under the patch from QTBUG-61115: At least with DirectShow + // and GStreamer, it changes both speed and pitch. (...) need to be consistent + if (rate != 0.0) + playbackParams.setPitch(Math.abs(rate)); + + try { + mMediaPlayer.setPlaybackParams(playbackParams); + } catch (IllegalStateException | IllegalArgumentException e) { + Log.e(TAG, "Cannot set playback rate " + rate + " :" + e.toString()); + return false; + } + + if ((mState & State.Started) == 0 && mMediaPlayer.isPlaying()) { + setState(State.Started); + startProgressWatcher(); + } + + return true; + } } diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp index 4dad80dce..075d6a603 100644 --- a/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp +++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp @@ -41,6 +41,7 @@ #include "androidcamera_p.h" #include "qandroidcamerasession_p.h" +#include "qaudioinput.h" #include "androidmediaplayer_p.h" #include "androidmultimediautils_p.h" #include "qandroidmultimediautils_p.h" @@ -97,7 +98,21 @@ void QAndroidCaptureSession::setCameraSession(QAndroidCameraSession *cameraSessi void QAndroidCaptureSession::setAudioInput(QPlatformAudioInput *input) { + if (m_audioInput == input) + return; + + if (m_audioInput) { + disconnect(m_audioInputChanged); + } + m_audioInput = input; + + if (m_audioInput) { + m_audioInputChanged = connect(m_audioInput->q, &QAudioInput::deviceChanged, this, [this]() { + if (m_state == QMediaRecorder::RecordingState) + m_mediaRecorder->setAudioInput(m_audioInput->device.id()); + }); + } } void QAndroidCaptureSession::setAudioOutput(QPlatformAudioOutput *output) diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h index e91e5b210..2f3a4fb53 100644 --- a/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h +++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h @@ -179,6 +179,7 @@ private: QList<QSize> m_supportedResolutions; QList<qreal> m_supportedFramerates; + QMetaObject::Connection m_audioInputChanged; QMetaObject::Connection m_connOpenCamera; QMetaObject::Connection m_connActiveChangedCamera; diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp index 01a57c298..1df5e393b 100644 --- a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer.cpp @@ -249,43 +249,24 @@ void QAndroidMediaPlayer::updateAvailablePlaybackRanges() qreal QAndroidMediaPlayer::playbackRate() const { - if (mHasPendingPlaybackRate || - (mState & (AndroidMediaPlayer::Initialized - | AndroidMediaPlayer::Prepared - | AndroidMediaPlayer::Started - | AndroidMediaPlayer::Paused - | AndroidMediaPlayer::PlaybackCompleted - | AndroidMediaPlayer::Error)) == 0) { - return mPendingPlaybackRate; - } - - return mMediaPlayer->playbackRate(); + return mCurrentPlaybackRate; } void QAndroidMediaPlayer::setPlaybackRate(qreal rate) { - if ((mState & (AndroidMediaPlayer::Initialized - | AndroidMediaPlayer::Prepared - | AndroidMediaPlayer::Started - | AndroidMediaPlayer::Paused - | AndroidMediaPlayer::PlaybackCompleted - | AndroidMediaPlayer::Error)) == 0) { - if (mPendingPlaybackRate != rate) { - mPendingPlaybackRate = rate; + if (mState != AndroidMediaPlayer::Started) { + // If video isn't playing, changing speed rate may start it automatically + // It need to be postponed + if (mCurrentPlaybackRate != rate) { + mCurrentPlaybackRate = rate; mHasPendingPlaybackRate = true; Q_EMIT playbackRateChanged(rate); } return; } - bool succeeded = mMediaPlayer->setPlaybackRate(rate); - - if (mHasPendingPlaybackRate) { - mHasPendingPlaybackRate = false; - mPendingPlaybackRate = qreal(1.0); - if (!succeeded) - Q_EMIT playbackRateChanged(playbackRate()); - } else if (succeeded) { + if (mMediaPlayer->setPlaybackRate(rate)) { + mCurrentPlaybackRate = rate; Q_EMIT playbackRateChanged(rate); } } @@ -416,6 +397,14 @@ void QAndroidMediaPlayer::play() updateAudioDevice(); + if (mHasPendingPlaybackRate) { + mHasPendingPlaybackRate = false; + if (mMediaPlayer->setPlaybackRate(mCurrentPlaybackRate)) + return; + mCurrentPlaybackRate = mMediaPlayer->playbackRate(); + Q_EMIT playbackRateChanged(mCurrentPlaybackRate); + } + mMediaPlayer->play(); } @@ -462,6 +451,10 @@ void QAndroidMediaPlayer::stop() return; } + if (mCurrentPlaybackRate != 1.) + // Playback rate need to by reapplied + mHasPendingPlaybackRate = true; + if (mVideoOutput) mVideoOutput->stop(); @@ -981,8 +974,6 @@ void QAndroidMediaPlayer::flushPendingStates() setVolume(mPendingVolume); if (mPendingMute != -1) setMuted((mPendingMute == 1)); - if (mHasPendingPlaybackRate) - setPlaybackRate(mPendingPlaybackRate); switch (newState) { case QMediaPlayer::PlayingState: diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h index b8e187a08..6221e994d 100644 --- a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayer_p.h @@ -137,7 +137,7 @@ private: int mPendingMute = -1; bool mReloadingMedia = false; int mActiveStateChangeNotifiers = 0; - qreal mPendingPlaybackRate = 1.; + qreal mCurrentPlaybackRate = 1.; bool mHasPendingPlaybackRate = false; // we need this because the rate can theoretically be negative QMap<TrackType, QList<QAndroidMetaData>> mTracksMetadata; diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp index 1378cfbeb..079ccb42a 100644 --- a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp +++ b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp @@ -296,34 +296,7 @@ bool AndroidMediaPlayer::setPlaybackRate(qreal rate) return false; } - QJniObject player = mMediaPlayer.callObjectMethod("getMediaPlayerHandle", - "()Landroid/media/MediaPlayer;"); - if (player.isValid()) { - QJniObject playbackParams = player.callObjectMethod("getPlaybackParams", - "()Landroid/media/PlaybackParams;"); - if (playbackParams.isValid()) { - playbackParams.callObjectMethod("setSpeed", "(F)Landroid/media/PlaybackParams;", - jfloat(rate)); - // pitch can only be > 0 - if (!qFuzzyIsNull(rate)) - playbackParams.callObjectMethod("setPitch", "(F)Landroid/media/PlaybackParams;", - jfloat(qAbs(rate))); - - QJniEnvironment env; - auto methodId = env->GetMethodID(player.objectClass(), "setPlaybackParams", - "(Landroid/media/PlaybackParams;)V"); - env->CallVoidMethod(player.object(), methodId, playbackParams.object()); - - if (env.checkAndClearExceptions()) { - qWarning() << "Invalid playback rate" << rate; - return false; - } else { - return true; - } - } - } - - return false; + return mMediaPlayer.callMethod<jboolean>("setPlaybackRate", "(F)V", jfloat(rate)); } void AndroidMediaPlayer::setDisplay(AndroidSurfaceTexture *surfaceTexture) diff --git a/src/multimedia/platform/darwin/camera/avfcamera.mm b/src/multimedia/platform/darwin/camera/avfcamera.mm index a0f890a39..0b8790ea4 100644 --- a/src/multimedia/platform/darwin/camera/avfcamera.mm +++ b/src/multimedia/platform/darwin/camera/avfcamera.mm @@ -129,6 +129,22 @@ bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCamera::ExposureM #endif // defined(Q_OS_IOS) +bool isFlashAvailable(AVCaptureDevice* captureDevice) { + if (@available(macOS 10.15, *)) { + return [captureDevice isFlashAvailable]; + } + + return true; +} + +bool isTorchAvailable(AVCaptureDevice* captureDevice) { + if (@available(macOS 10.15, *)) { + return [captureDevice isTorchAvailable]; + } + + return true; +} + } // Unnamed namespace. @@ -589,14 +605,7 @@ bool AVFCamera::isFlashReady() const if (!isFlashModeSupported(flashMode())) return false; -#ifdef Q_OS_IOS - // AVCaptureDevice's docs: - // "The flash may become unavailable if, for example, - // the device overheats and needs to cool off." - return [captureDevice isFlashAvailable]; -#endif - - return true; + return isFlashAvailable(captureDevice); } void AVFCamera::setTorchMode(QCamera::TorchMode mode) @@ -691,37 +700,51 @@ void AVFCamera::applyFlashSettings() if (captureDevice.hasFlash) { auto mode = flashMode(); + + auto setAvFlashModeSafe = [&captureDevice](AVCaptureFlashMode avFlashMode) { + // Note, in some cases captureDevice.hasFlash == false even though + // no there're no supported flash modes. + if ([captureDevice isFlashModeSupported:avFlashMode]) + captureDevice.flashMode = avFlashMode; + else + qDebugCamera() << Q_FUNC_INFO << "Attempt to setup unsupported flash mode " << avFlashMode; + }; + if (mode == QCamera::FlashOff) { captureDevice.flashMode = AVCaptureFlashModeOff; } else { -#ifdef Q_OS_IOS - if (![captureDevice isFlashAvailable]) { + if (isFlashAvailable(captureDevice)) { + if (mode == QCamera::FlashOn) + setAvFlashModeSafe(AVCaptureFlashModeOn); + else if (mode == QCamera::FlashAuto) + setAvFlashModeSafe(AVCaptureFlashModeAuto); + } else { qDebugCamera() << Q_FUNC_INFO << "flash is not available at the moment"; - return; } -#endif - if (mode == QCamera::FlashOn) - captureDevice.flashMode = AVCaptureFlashModeOn; - else if (mode == QCamera::FlashAuto) - captureDevice.flashMode = AVCaptureFlashModeAuto; } } if (captureDevice.hasTorch) { auto mode = torchMode(); + + auto setAvTorchModeSafe = [&captureDevice](AVCaptureTorchMode avTorchMode) { + if ([captureDevice isTorchModeSupported:avTorchMode]) + captureDevice.torchMode = avTorchMode; + else + qDebugCamera() << Q_FUNC_INFO << "Attempt to setup unsupported torch mode " << avTorchMode; + }; + if (mode == QCamera::TorchOff) { - captureDevice.torchMode = AVCaptureTorchModeOff; + setAvTorchModeSafe(AVCaptureTorchModeOff); } else { -#ifdef Q_OS_IOS - if (![captureDevice isTorchAvailable]) { + if (isTorchAvailable(captureDevice)) { + if (mode == QCamera::TorchOn) + setAvTorchModeSafe(AVCaptureTorchModeOn); + else if (mode == QCamera::TorchAuto) + setAvTorchModeSafe(AVCaptureTorchModeAuto); + } else { qDebugCamera() << Q_FUNC_INFO << "torch is not available at the moment"; - return; } -#endif - if (mode == QCamera::TorchOn) - captureDevice.torchMode = AVCaptureTorchModeOn; - else if (mode == QCamera::TorchAuto) - captureDevice.torchMode = AVCaptureTorchModeAuto; } } } diff --git a/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm b/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm index c4c661464..95d6b922f 100644 --- a/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm +++ b/src/multimedia/platform/darwin/mediaplayer/avfmediaplayer.mm @@ -127,6 +127,9 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM - (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType { + if (!m_session) + return; + [m_mimeType release]; m_mimeType = [mimeType retain]; @@ -145,18 +148,17 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain]; - __block AVFMediaPlayerObserver *blockSelf = self; - QPointer<AVFMediaPlayer> session(m_session); + __block AVFMediaPlayerObserver *blockSelf = [self retain]; // Tells the asset to load the values of any of the specified keys that are not already loaded. [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler: ^{ dispatch_async( dispatch_get_main_queue(), ^{ - if (session) - [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys]; + [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys]; [asset release]; [requestedKeys release]; + [blockSelf release]; }); }]; } @@ -193,6 +195,9 @@ static void *AVFMediaPlayerObserverCurrentItemDurationObservationContext = &AVFM - (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys { + if (!m_session) + return; + //Make sure that the value of each key has loaded successfully. for (NSString *thisKey in requestedKeys) { diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp index 18e6862ff..cf745b1a4 100644 --- a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp @@ -107,9 +107,11 @@ void QGstreamerCamera::setCamera(const QCameraDevice &camera) auto *devices = static_cast<QGstreamerMediaDevices *>(QGstreamerIntegration::instance()->devices()); auto *device = devices->videoDevice(camera.id()); gstNewCamera = gst_device_create_element(device, "camerasrc"); - QGstStructure properties = gst_device_get_properties(device); - if (properties.name() == "v4l2deviceprovider") - m_v4l2Device = QString::fromUtf8(properties["device.path"].toString()); + if (QGstStructure properties = gst_device_get_properties(device); !properties.isNull()) { + if (properties.name() == "v4l2deviceprovider") + m_v4l2Device = QString::fromUtf8(properties["device.path"].toString()); + properties.free(); + } } QCameraFormat f = findBestCameraFormat(camera); diff --git a/src/multimedia/platform/gstreamer/qgstreamermediadevices.cpp b/src/multimedia/platform/gstreamer/qgstreamermediadevices.cpp index 6e49057cf..bd75d80c3 100644 --- a/src/multimedia/platform/gstreamer/qgstreamermediadevices.cpp +++ b/src/multimedia/platform/gstreamer/qgstreamermediadevices.cpp @@ -136,52 +136,53 @@ QList<QCameraDevice> QGstreamerMediaDevices::videoInputs() const { QList<QCameraDevice> devices; - for (auto *d : qAsConst(m_videoSources)) { - QGstStructure properties = gst_device_get_properties(d); - if (!properties.isNull()) { - QCameraDevicePrivate *info = new QCameraDevicePrivate; - auto *desc = gst_device_get_display_name(d); - info->description = QString::fromUtf8(desc); - g_free(desc); - - info->id = properties["device.path"].toString(); + for (auto device : m_videoSources) { + QCameraDevicePrivate *info = new QCameraDevicePrivate; + auto *desc = gst_device_get_display_name(device.gstDevice); + info->description = QString::fromUtf8(desc); + g_free(desc); + info->id = device.id; + + if (QGstStructure properties = gst_device_get_properties(device.gstDevice); !properties.isNull()) { auto def = properties["is-default"].toBool(); info->isDefault = def && *def; - if (def) - devices.prepend(info->create()); - else - devices.append(info->create()); properties.free(); - QGstCaps caps = gst_device_get_caps(d); - if (!caps.isNull()) { - QList<QCameraFormat> formats; - QSet<QSize> photoResolutions; - - int size = caps.size(); - for (int i = 0; i < size; ++i) { - auto cap = caps.at(i); - - QSize resolution = cap.resolution(); - if (!resolution.isValid()) - continue; - - auto pixelFormat = cap.pixelFormat(); - auto frameRate = cap.frameRateRange(); - - auto *f = new QCameraFormatPrivate{ - QSharedData(), - pixelFormat, - resolution, - frameRate.min, - frameRate.max - }; - formats << f->create(); - photoResolutions.insert(resolution); - } - info->videoFormats = formats; - // ### sort resolutions? - info->photoResolutions = photoResolutions.values(); + } + + if (info->isDefault) + devices.prepend(info->create()); + else + devices.append(info->create()); + + QGstCaps caps = gst_device_get_caps(device.gstDevice); + if (!caps.isNull()) { + QList<QCameraFormat> formats; + QSet<QSize> photoResolutions; + + int size = caps.size(); + for (int i = 0; i < size; ++i) { + auto cap = caps.at(i); + + QSize resolution = cap.resolution(); + if (!resolution.isValid()) + continue; + + auto pixelFormat = cap.pixelFormat(); + auto frameRate = cap.frameRateRange(); + + auto *f = new QCameraFormatPrivate{ + QSharedData(), + pixelFormat, + resolution, + frameRate.min, + frameRate.max + }; + formats << f->create(); + photoResolutions.insert(resolution); } + info->videoFormats = formats; + // ### sort resolutions? + info->photoResolutions = photoResolutions.values(); } } return devices; @@ -203,8 +204,9 @@ void QGstreamerMediaDevices::addDevice(GstDevice *device) // qDebug() << "adding device:" << device << type << gst_device_get_display_name(device) << gst_structure_to_string(gst_device_get_properties(device)); gst_object_ref(device); if (!strcmp(type, "Video/Source")) { - m_videoSources.insert(device); + m_videoSources.push_back({device, QByteArray::number(m_idGenerator)}); videoInputsChanged(); + m_idGenerator++; } else if (!strcmp(type, "Audio/Source")) { m_audioSources.insert(device); audioInputsChanged(); @@ -220,7 +222,11 @@ void QGstreamerMediaDevices::addDevice(GstDevice *device) void QGstreamerMediaDevices::removeDevice(GstDevice *device) { // qDebug() << "removing device:" << device << gst_device_get_display_name(device); - if (m_videoSources.remove(device)) { + auto it = std::find_if(m_videoSources.begin(), m_videoSources.end(), + [=](const QGstDevice &a) { return a.gstDevice == device; }); + + if (it != m_videoSources.end()) { + m_videoSources.erase(it); videoInputsChanged(); } else if (m_audioSources.remove(device)) { audioInputsChanged(); @@ -259,7 +265,9 @@ GstDevice *QGstreamerMediaDevices::audioDevice(const QByteArray &id, QAudioDevic GstDevice *QGstreamerMediaDevices::videoDevice(const QByteArray &id) const { - return getDevice(m_videoSources, "device.path", id); + auto it = std::find_if(m_videoSources.begin(), m_videoSources.end(), + [=](const QGstDevice &a) { return a.id == id; }); + return it != m_videoSources.end() ? it->gstDevice : nullptr; } QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/qgstreamermediadevices_p.h b/src/multimedia/platform/gstreamer/qgstreamermediadevices_p.h index e3f34433f..121e080e6 100644 --- a/src/multimedia/platform/gstreamer/qgstreamermediadevices_p.h +++ b/src/multimedia/platform/gstreamer/qgstreamermediadevices_p.h @@ -55,6 +55,7 @@ #include <gst/gst.h> #include <qset.h> #include <qaudiodevice.h> +#include <vector> QT_BEGIN_NAMESPACE @@ -76,7 +77,14 @@ public: GstDevice *videoDevice(const QByteArray &id) const; private: - QSet<GstDevice *> m_videoSources; + struct QGstDevice { + GstDevice *gstDevice = nullptr; + QByteArray id; + }; + + quint64 m_idGenerator = 0; + std::vector<QGstDevice> m_videoSources; + QSet<GstDevice *> m_audioSources; QSet<GstDevice *> m_audioSinks; }; diff --git a/src/multimedia/platform/windows/common/qwindowsmfdefs.cpp b/src/multimedia/platform/windows/common/qwindowsmfdefs.cpp index 97eae9743..f62ef8ee0 100644 --- a/src/multimedia/platform/windows/common/qwindowsmfdefs.cpp +++ b/src/multimedia/platform/windows/common/qwindowsmfdefs.cpp @@ -55,6 +55,7 @@ const GUID QMM_MF_SD_STREAM_NAME = {0x4f1b099d, 0xd314, 0x41e5, {0xa7, 0x81, 0x7 const GUID QMM_MF_SD_LANGUAGE = {0xaf2180, 0xbdc2, 0x423c, {0xab, 0xca, 0xf5, 0x3, 0x59, 0x3b, 0xc1, 0x21}}; const GUID QMM_KSCATEGORY_VIDEO_CAMERA = {0xe5323777, 0xf976, 0x4f5b, {0x9b, 0x55, 0xb9, 0x46, 0x99, 0xc4, 0x6e, 0x44}}; +const GUID QMM_KSCATEGORY_SENSOR_CAMERA = {0x24e552d7, 0x6523, 0x47f7, {0xa6, 0x47, 0xd3, 0x46, 0x5b, 0xf1, 0xf5, 0xca}}; const GUID QMM_MR_POLICY_VOLUME_SERVICE = {0x1abaa2ac, 0x9d3b, 0x47c6, {0xab, 0x48, 0xc5, 0x95, 0x6, 0xde, 0x78, 0x4d}}; diff --git a/src/multimedia/platform/windows/common/qwindowsmfdefs_p.h b/src/multimedia/platform/windows/common/qwindowsmfdefs_p.h index 173c8f8f0..87ef2272e 100644 --- a/src/multimedia/platform/windows/common/qwindowsmfdefs_p.h +++ b/src/multimedia/platform/windows/common/qwindowsmfdefs_p.h @@ -73,6 +73,7 @@ extern const GUID QMM_MF_SD_STREAM_NAME; extern const GUID QMM_MF_SD_LANGUAGE; extern const GUID QMM_KSCATEGORY_VIDEO_CAMERA; +extern const GUID QMM_KSCATEGORY_SENSOR_CAMERA; extern const GUID QMM_MR_POLICY_VOLUME_SERVICE; @@ -82,6 +83,7 @@ extern "C" HRESULT WINAPI MFCreateDeviceSource(IMFAttributes *pAttributes, IMFMe #define QMM_MFSESSION_GETFULLTOPOLOGY_CURRENT 1 #define QMM_PRESENTATION_CURRENT_POSITION 0x7fffffffffffffff +#define QMM_WININET_E_CANNOT_CONNECT ((HRESULT)0x80072EFDL) #ifndef __IMFVideoProcessor_INTERFACE_DEFINED__ #define __IMFVideoProcessor_INTERFACE_DEFINED__ diff --git a/src/multimedia/platform/windows/mediacapture/qwindowsmediadevicereader.cpp b/src/multimedia/platform/windows/mediacapture/qwindowsmediadevicereader.cpp index 16414cca5..6f11f6a54 100644 --- a/src/multimedia/platform/windows/mediacapture/qwindowsmediadevicereader.cpp +++ b/src/multimedia/platform/windows/mediacapture/qwindowsmediadevicereader.cpp @@ -246,7 +246,7 @@ HRESULT QWindowsMediaDeviceReader::prepareVideoStream(DWORD mediaTypeIndex) // and the stride, which we need to convert the frame later hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, m_frameWidth, &m_stride); if (SUCCEEDED(hr)) { - + m_stride = qAbs(m_stride); UINT32 frameRateNum, frameRateDen; hr = MFGetAttributeRatio(m_videoMediaType, MF_MT_FRAME_RATE, &frameRateNum, &frameRateDen); if (SUCCEEDED(hr)) { diff --git a/src/multimedia/platform/windows/player/mfplayersession.cpp b/src/multimedia/platform/windows/player/mfplayersession.cpp index 4035bda6f..caa0a708e 100644 --- a/src/multimedia/platform/windows/player/mfplayersession.cpp +++ b/src/multimedia/platform/windows/player/mfplayersession.cpp @@ -59,6 +59,7 @@ #include "mfplayersession_p.h" #include <mferror.h> #include <nserror.h> +#include <winerror.h> #include "private/sourceresolver_p.h" #include "samplegrabber_p.h" #include "mftvideo_p.h" @@ -217,7 +218,8 @@ void MFPlayerSession::load(const QUrl &url, QIODevice *stream) createSession(); changeStatus(QMediaPlayer::LoadingMedia); m_sourceResolver->load(url, stream); - m_updateRoutingOnStart = true; + if (url.isLocalFile()) + m_updateRoutingOnStart = true; } positionChanged(position()); } @@ -241,7 +243,17 @@ void MFPlayerSession::handleSourceError(long hr) errorCode = QMediaPlayer::FormatError; errorString = tr("Unsupported media type."); break; + case MF_E_UNSUPPORTED_SCHEME: + errorCode = QMediaPlayer::ResourceError; + errorString = tr("Unsupported URL scheme."); + break; + case QMM_WININET_E_CANNOT_CONNECT: + errorCode = QMediaPlayer::NetworkError; + errorString = tr("A connection with the server could not be established."); + break; default: + qWarning() << "handleSourceError:" + << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hr); errorString = tr("Failed to load source."); break; } @@ -492,20 +504,15 @@ IMFTopologyNode* MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology } auto id = m_audioOutput->device.id(); - if (!id.isEmpty()) { - QString s = QString::fromUtf8(id); - hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, (LPCWSTR)s.utf16()); - } else { - //This is the default one that has been inserted in updateEndpoints(), - //so give the activate a hint that we want to use the device for multimedia playback - //then the media foundation will choose an appropriate one. - - //from MSDN: - //The ERole enumeration defines constants that indicate the role that the system has assigned to an audio endpoint device. - //eMultimedia: Music, movies, narration, and live music recording. - hr = activate->SetUINT32(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ROLE, eMultimedia); + if (id.isEmpty()) { + qWarning() << "No audio output"; + activate->Release(); + node->Release(); + return NULL; } + QString s = QString::fromUtf8(id); + hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, (LPCWSTR)s.utf16()); if (FAILED(hr)) { qWarning() << "Failed to set attribute for audio device" << m_audioOutput->device.description(); activate->Release(); @@ -1671,8 +1678,25 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) break; } changeStatus(QMediaPlayer::InvalidMedia); - qWarning() << "handleSessionEvent: serious error = " << hrStatus; - emit error(QMediaPlayer::ResourceError, tr("Media session serious error."), true); + qWarning() << "handleSessionEvent: serious error = " + << Qt::showbase << Qt::hex << Qt::uppercasedigits << static_cast<quint32>(hrStatus); + switch (hrStatus) { + case MF_E_NET_READ: + emit error(QMediaPlayer::NetworkError, tr("Error reading from the network."), true); + break; + case MF_E_NET_WRITE: + emit error(QMediaPlayer::NetworkError, tr("Error writing to the network."), true); + break; + case NS_E_FIREWALL: + emit error(QMediaPlayer::NetworkError, tr("Network packets might be blocked by a firewall."), true); + break; + case MF_E_MEDIAPROC_WRONGSTATE: + emit error(QMediaPlayer::ResourceError, tr("Media session state error."), true); + break; + default: + emit error(QMediaPlayer::ResourceError, tr("Media session serious error."), true); + break; + } break; case MESessionRateChanged: // If the rate change succeeded, we've already got the rate diff --git a/src/multimedia/platform/windows/qwindowsmediadevices.cpp b/src/multimedia/platform/windows/qwindowsmediadevices.cpp index 96739af1f..06eab2d9d 100644 --- a/src/multimedia/platform/windows/qwindowsmediadevices.cpp +++ b/src/multimedia/platform/windows/qwindowsmediadevices.cpp @@ -395,126 +395,130 @@ QList<QAudioDevice> QWindowsMediaDevices::audioOutputs() const return availableDevices(QAudioDevice::Output); } -QList<QCameraDevice> QWindowsMediaDevices::videoInputs() const +static std::optional<QCameraFormat> createCameraFormat(IMFMediaType *mediaFormat) { - QList<QCameraDevice> cameras; + GUID subtype = GUID_NULL; + if (FAILED(mediaFormat->GetGUID(MF_MT_SUBTYPE, &subtype))) + return {}; + + auto pixelFormat = QWindowsMultimediaUtils::pixelFormatFromMediaSubtype(subtype); + if (pixelFormat == QVideoFrameFormat::Format_Invalid) + return {}; + + UINT32 width = 0u; + UINT32 height = 0u; + if (FAILED(MFGetAttributeSize(mediaFormat, MF_MT_FRAME_SIZE, &width, &height))) + return {}; + QSize resolution{ int(width), int(height) }; + + UINT32 num = 0u; + UINT32 den = 0u; + float minFr = 0.f; + float maxFr = 0.f; - IMFAttributes *pAttributes = NULL; - IMFActivate **ppDevices = NULL; + if (SUCCEEDED(MFGetAttributeRatio(mediaFormat, MF_MT_FRAME_RATE_RANGE_MIN, &num, &den))) + minFr = float(num) / float(den); - // Create an attribute store to specify the enumeration parameters. - HRESULT hr = MFCreateAttributes(&pAttributes, 1); + if (SUCCEEDED(MFGetAttributeRatio(mediaFormat, MF_MT_FRAME_RATE_RANGE_MAX, &num, &den))) + maxFr = float(num) / float(den); + + auto *f = new QCameraFormatPrivate{ QSharedData(), pixelFormat, resolution, minFr, maxFr }; + return f->create(); +} + +static QString getString(IMFActivate *device, const IID &id) +{ + WCHAR *str = NULL; + UINT32 length = 0; + HRESULT hr = device->GetAllocatedString(id, &str, &length); if (SUCCEEDED(hr)) { - // Source type: video capture devices - hr = pAttributes->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); - - if (SUCCEEDED(hr)) { - // Enumerate devices. - UINT32 count; - hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count); - if (SUCCEEDED(hr)) { - // Iterate through devices. - for (int index = 0; index < int(count); index++) { - QCameraDevicePrivate *info = new QCameraDevicePrivate; - - IMFMediaSource *pSource = NULL; - IMFSourceReader *reader = NULL; - - WCHAR *deviceName = NULL; - UINT32 deviceNameLength = 0; - UINT32 deviceIdLength = 0; - WCHAR *deviceId = NULL; - - hr = ppDevices[index]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, - &deviceName, &deviceNameLength); - if (SUCCEEDED(hr)) - info->description = QString::fromWCharArray(deviceName); - CoTaskMemFree(deviceName); - - hr = ppDevices[index]->GetAllocatedString( - MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &deviceId, - &deviceIdLength); - if (SUCCEEDED(hr)) - info->id = QString::fromWCharArray(deviceId).toUtf8(); - CoTaskMemFree(deviceId); - - // Create the media source object. - hr = ppDevices[index]->ActivateObject( - IID_PPV_ARGS(&pSource)); - // Create the media source reader. - hr = MFCreateSourceReaderFromMediaSource(pSource, NULL, &reader); - if (SUCCEEDED(hr)) { - QList<QSize> photoResolutions; - QList<QCameraFormat> videoFormats; - - DWORD dwMediaTypeIndex = 0; - IMFMediaType *mediaFormat = NULL; - GUID subtype = GUID_NULL; - HRESULT mediaFormatResult = S_OK; - - UINT32 frameRateMin = 0u; - UINT32 frameRateMax = 0u; - UINT32 denominator = 0u; - UINT32 width = 0u; - UINT32 height = 0u; - - while (SUCCEEDED(mediaFormatResult)) { - // Loop through the supported formats for the video device - mediaFormatResult = reader->GetNativeMediaType( - (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, dwMediaTypeIndex, - &mediaFormat); - if (mediaFormatResult == MF_E_NO_MORE_TYPES) - break; - else if (SUCCEEDED(mediaFormatResult)) { - QVideoFrameFormat::PixelFormat pixelFormat = QVideoFrameFormat::Format_Invalid; - QSize resolution; - float minFr = .0; - float maxFr = .0; - - if (SUCCEEDED(mediaFormat->GetGUID(MF_MT_SUBTYPE, &subtype))) - pixelFormat = QWindowsMultimediaUtils::pixelFormatFromMediaSubtype(subtype); - - if (SUCCEEDED(MFGetAttributeSize(mediaFormat, MF_MT_FRAME_SIZE, &width, - &height))) { - resolution.rheight() = (int)height; - resolution.rwidth() = (int)width; - photoResolutions << resolution; - } - - if (SUCCEEDED(MFGetAttributeRatio(mediaFormat, MF_MT_FRAME_RATE_RANGE_MIN, - &frameRateMin, &denominator))) - minFr = qreal(frameRateMin) / denominator; - if (SUCCEEDED(MFGetAttributeRatio(mediaFormat, MF_MT_FRAME_RATE_RANGE_MAX, - &frameRateMax, &denominator))) - maxFr = qreal(frameRateMax) / denominator; - - auto *f = new QCameraFormatPrivate { QSharedData(), pixelFormat, - resolution, minFr, maxFr }; - videoFormats << f->create(); - } - ++dwMediaTypeIndex; - } - if (mediaFormat) - mediaFormat->Release(); - - info->videoFormats = videoFormats; - info->photoResolutions = photoResolutions; - } - if (reader) - reader->Release(); - cameras.append(info->create()); - } - } - for (DWORD i = 0; i < count; i++) { - if (ppDevices[i]) - ppDevices[i]->Release(); + auto qstr = QString::fromWCharArray(str); + CoTaskMemFree(str); + return qstr; + } else { + return {}; + } +} + +static std::optional<QCameraDevice> createCameraDevice(IMFActivate *device) +{ + auto info = std::make_unique<QCameraDevicePrivate>(); + info->description = getString(device, MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME); + info->id = getString(device, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK).toUtf8(); + + IMFMediaSource *source = NULL; + HRESULT hr = device->ActivateObject(IID_PPV_ARGS(&source)); + if (FAILED(hr)) + return {}; + + QWindowsIUPointer<IMFSourceReader> reader; + hr = MFCreateSourceReaderFromMediaSource(source, NULL, reader.address()); + if (FAILED(hr)) + return {}; + + QList<QSize> photoResolutions; + QList<QCameraFormat> videoFormats; + for (DWORD i = 0;; ++i) { + // Loop through the supported formats for the video device + QWindowsIUPointer<IMFMediaType> mediaFormat; + hr = reader->GetNativeMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, i, + mediaFormat.address()); + if (FAILED(hr)) + break; + + auto maybeCamera = createCameraFormat(mediaFormat.get()); + if (maybeCamera) { + videoFormats << *maybeCamera; + photoResolutions << maybeCamera->resolution(); + } + } + + info->videoFormats = videoFormats; + info->photoResolutions = photoResolutions; + return info.release()->create(); +} + +static QList<QCameraDevice> readCameraDevices(IMFAttributes *attr) +{ + QList<QCameraDevice> cameras; + UINT32 count = 0; + IMFActivate **devices = NULL; + HRESULT hr = MFEnumDeviceSources(attr, &devices, &count); + if (SUCCEEDED(hr)) { + for (UINT32 i = 0; i < count; i++) { + IMFActivate *device = devices[i]; + if (device) { + auto maybeCamera = createCameraDevice(device); + if (maybeCamera) + cameras << *maybeCamera; + + device->Release(); } - CoTaskMemFree(ppDevices); } + CoTaskMemFree(devices); + } + return cameras; +} + +QList<QCameraDevice> QWindowsMediaDevices::videoInputs() const +{ + QList<QCameraDevice> cameras; + + QWindowsIUPointer<IMFAttributes> attr; + HRESULT hr = MFCreateAttributes(attr.address(), 2); + if (FAILED(hr)) + return {}; + + hr = attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID); + if (SUCCEEDED(hr)) { + cameras << readCameraDevices(attr.get()); + + hr = attr->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_CATEGORY, + QMM_KSCATEGORY_SENSOR_CAMERA); + if (SUCCEEDED(hr)) + cameras << readCameraDevices(attr.get()); } - if (pAttributes) - pAttributes->Release(); return cameras; } diff --git a/src/multimedia/playback/qmediaplayer.h b/src/multimedia/playback/qmediaplayer.h index 0ccb0eaaf..d3e0139f6 100644 --- a/src/multimedia/playback/qmediaplayer.h +++ b/src/multimedia/playback/qmediaplayer.h @@ -146,9 +146,6 @@ public: void setVideoOutput(QObject *); QObject *videoOutput() const; -#if 0 - void setVideoOutput(const QList<QVideoSink *> &sinks); -#endif void setVideoSink(QVideoSink *sink); QVideoSink *videoSink() const; |