summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@qt.io>2021-08-04 09:31:06 +0200
committerLars Knoll <lars.knoll@qt.io>2021-08-04 09:31:06 +0200
commit7fdbe3bf80d75670223ae1d8fa4d5bac1250bb0c (patch)
treede926179b35e8aae031b371683fac43c4a8d3794
parent7f6bd7996d588eab3b371fb369d09bc1a29950c4 (diff)
parent86357212fe49dd3471d398a4dc8605c6cdf46406 (diff)
Merge remote-tracking branch 'origin/dev' into 6.2
Bring fixes from the dev branch into 6.2. We'll probably need one final downmerge of dev into 6.2 before moving over to a regular cherry-picking model. Change-Id: I47dd493df3379d1ee916dce7be2d48a756bf3668
-rw-r--r--examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml33
-rw-r--r--examples/multimedia/video/qmlvideo/main.cpp8
-rw-r--r--examples/multimedia/video/qmlvideofx/main.cpp8
-rw-r--r--examples/multimedia/video/recorder/CameraSelect.qml2
-rw-r--r--examples/multimediawidgets/player/player.cpp30
-rw-r--r--examples/multimediawidgets/player/player.h1
-rw-r--r--examples/multimediawidgets/player/playercontrols.cpp8
-rw-r--r--examples/multimediawidgets/player/playercontrols.h6
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java14
-rw-r--r--src/multimedia/CMakeLists.txt4
-rw-r--r--src/multimedia/audio/qaudio.cpp32
-rw-r--r--src/multimedia/audio/qaudio.h2
-rw-r--r--src/multimedia/platform/android/audio/qandroidaudiodecoder.cpp432
-rw-r--r--src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h (renamed from src/multimediaquick/qquickvideooutput_render_p.h)130
-rw-r--r--src/multimedia/platform/android/audio/qandroidaudiosource.cpp7
-rw-r--r--src/multimedia/platform/android/common/qandroidmultimediautils.cpp15
-rw-r--r--src/multimedia/platform/android/common/qandroidvideooutput.cpp129
-rw-r--r--src/multimedia/platform/android/common/qandroidvideooutput_p.h73
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidcamera.cpp21
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidcamera_p.h1
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp7
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp32
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h1
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidmediacapturesession.cpp12
-rw-r--r--src/multimedia/platform/android/mediacapture/qandroidmediaencoder.cpp5
-rw-r--r--src/multimedia/platform/android/qandroidintegration.cpp6
-rw-r--r--src/multimedia/platform/android/qandroidintegration_p.h1
-rw-r--r--src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp13
-rw-r--r--src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h1
-rw-r--r--src/multimedia/platform/darwin/audio/avfaudiodecoder.mm4
-rw-r--r--src/multimedia/platform/darwin/audio/qcoreaudioutils.mm3
-rw-r--r--src/multimedia/platform/darwin/camera/avfcamera.mm9
-rw-r--r--src/multimedia/platform/gstreamer/common/qgst_p.h22
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstpipeline.cpp92
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstpipeline_p.h16
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreameraudioinput.cpp23
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreameraudioinput_p.h3
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreameraudiooutput.cpp44
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreameraudiooutput_p.h9
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp147
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h9
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp89
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h3
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp207
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h16
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideosink.cpp68
-rw-r--r--src/multimedia/platform/gstreamer/common/qgstreamervideosink_p.h7
-rw-r--r--src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp19
-rw-r--r--src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp12
-rw-r--r--src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacapture.cpp16
-rw-r--r--src/multimedia/platform/gstreamer/mediacapture/qgstreamermediaencoder.cpp17
-rw-r--r--src/multimedia/platform/qplatformmediaplayer_p.h22
-rw-r--r--src/multimedia/platform/windows/common/mfmetadata.cpp10
-rw-r--r--src/multimedia/platform/windows/common/qwindowsmultimediautils.cpp57
-rw-r--r--src/multimedia/platform/windows/common/qwindowsmultimediautils_p.h4
-rw-r--r--src/multimedia/platform/windows/player/mfplayercontrol.cpp26
-rw-r--r--src/multimedia/platform/windows/player/mfplayercontrol_p.h6
-rw-r--r--src/multimedia/platform/windows/player/mfplayersession.cpp287
-rw-r--r--src/multimedia/platform/windows/player/mfplayersession_p.h20
-rw-r--r--src/multimedia/playback/qmediaplayer.cpp2
-rw-r--r--src/multimedia/shaders/externalsampler.frag18
-rw-r--r--src/multimedia/shaders/externalsampler.vert20
-rw-r--r--src/multimedia/shaders/externalsampler_gles.frag22
-rw-r--r--src/multimedia/shaders/uyvy.frag4
-rw-r--r--src/multimedia/shaders/yuyv.frag4
-rw-r--r--src/multimedia/video/qvideoframe.cpp9
-rw-r--r--src/multimedia/video/qvideoframe.h2
-rw-r--r--src/multimedia/video/qvideoframeformat.cpp5
-rw-r--r--src/multimedia/video/qvideoframeformat.h4
-rw-r--r--src/multimedia/video/qvideotexturehelper.cpp62
-rw-r--r--src/multimedia/video/qvideotexturehelper_p.h2
-rw-r--r--src/multimediaquick/CMakeLists.txt1
-rw-r--r--src/multimediaquick/qquickvideooutput.cpp265
-rw-r--r--src/multimediaquick/qquickvideooutput_p.h60
-rw-r--r--src/multimediaquick/qquickvideooutput_render.cpp239
-rw-r--r--src/multimediaquick/qsgvideonode_p.cpp22
-rw-r--r--src/multimediawidgets/qvideowidget.cpp122
-rw-r--r--src/multimediawidgets/qvideowidget.h1
-rw-r--r--src/multimediawidgets/qvideowidget_p.h7
-rw-r--r--tests/auto/integration/qaudiosink/tst_qaudiosink.cpp2
-rw-r--r--tests/auto/integration/qaudiosource/tst_qaudiosource.cpp2
-rw-r--r--tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp7
-rw-r--r--tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp61
-rw-r--r--tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp2
-rw-r--r--tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp2
85 files changed, 1964 insertions, 1284 deletions
diff --git a/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml b/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml
index 09e76527c..5c04d1848 100644
--- a/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml
+++ b/examples/multimedia/video/mediaplayer/PlaybackSeekControl.qml
@@ -69,40 +69,21 @@ Item {
Layout.minimumWidth: 50
Layout.minimumHeight: 18
horizontalAlignment: Text.AlignRight
- text: "0:00.0"
+ text: {
+ var m = Math.floor(mediaPlayer.position / 60000)
+ var ms = (mediaPlayer.position / 1000 - m * 60).toFixed(1)
+ return `${m}:${ms.padStart(4, 0)}`
+ }
}
Slider {
id: mediaSlider
Layout.fillWidth: true
- enabled: false
+ enabled: mediaPlayer.seekable
to: 1.0
- value: 0.0
+ value: mediaPlayer.position / mediaPlayer.duration
onMoved: mediaPlayer.setPosition(value * mediaPlayer.duration)
-
- Connections {
- target: mediaPlayer
- function onSeekableChanged() { mediaSlider.enabled = mediaPlayer.seekable }
- }
- }
- }
-
- Timer {
- interval: 100; repeat: true; running: true;
- onTriggered: {
- if (mediaPlayerState != MediaPlayer.StoppedState) {
- mediaSlider.value = mediaPlayer.position / mediaPlayer.duration
- mediaTime.text = function(){
- var m = Math.floor(mediaPlayer.position / 60000)
- var ms = (mediaPlayer.position / 1000 - m * 60).toFixed(1)
- return `${m}:${ms.padStart(4, 0)}`
- }()
- } else {
- mediaSlider.value = 0.0
- mediaTime.text = "0:00.0"
- }
-
}
}
}
diff --git a/examples/multimedia/video/qmlvideo/main.cpp b/examples/multimedia/video/qmlvideo/main.cpp
index ea5d41033..e288c0ae4 100644
--- a/examples/multimedia/video/qmlvideo/main.cpp
+++ b/examples/multimedia/video/qmlvideo/main.cpp
@@ -73,14 +73,6 @@ int main(int argc, char *argv[])
PerformanceMonitor::qmlRegisterTypes();
#endif
-#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
- auto permission = QPermission::WriteStorage;
- if (QCoreApplication::requestPermission(permission).result() != QPermission::Authorized) {
- qWarning() << "Couldn't get 'WriteStorage' permission!";
- return -1;
- }
-#endif
-
QString source1, source2;
qreal volume = 0.5;
QStringList args = app.arguments();
diff --git a/examples/multimedia/video/qmlvideofx/main.cpp b/examples/multimedia/video/qmlvideofx/main.cpp
index 6be3623ef..814c85368 100644
--- a/examples/multimedia/video/qmlvideofx/main.cpp
+++ b/examples/multimedia/video/qmlvideofx/main.cpp
@@ -69,14 +69,6 @@ int main(int argc, char *argv[])
PerformanceMonitor::qmlRegisterTypes();
#endif
-#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0)
- auto permission = QPermission::WriteStorage;
- if (QCoreApplication::requestPermission(permission).result() != QPermission::Authorized) {
- qWarning() << "Couldn't get 'WriteStorage' permission!";
- return -1;
- }
-#endif
-
QUrl fileName;
qreal volume = 0.5;
QStringList args = app.arguments();
diff --git a/examples/multimedia/video/recorder/CameraSelect.qml b/examples/multimedia/video/recorder/CameraSelect.qml
index 61c834f34..982a44a8b 100644
--- a/examples/multimedia/video/recorder/CameraSelect.qml
+++ b/examples/multimedia/video/recorder/CameraSelect.qml
@@ -60,7 +60,7 @@ Row {
Camera {
id: camera
- active: available
+ active: available && selected != null
}
MediaDevices { id: mediaDevices }
diff --git a/examples/multimediawidgets/player/player.cpp b/examples/multimediawidgets/player/player.cpp
index 3005f2e4f..d616fd813 100644
--- a/examples/multimediawidgets/player/player.cpp
+++ b/examples/multimediawidgets/player/player.cpp
@@ -337,6 +337,26 @@ void Player::metaDataChanged()
}
}
+QString Player::trackName(const QMediaMetaData &metaData, int index)
+{
+ QString name;
+ QString title = metaData.stringValue(QMediaMetaData::Title);
+ QLocale::Language lang = metaData.value(QMediaMetaData::Language).value<QLocale::Language>();
+
+ if (title.isEmpty()) {
+ if (lang == QLocale::Language::AnyLanguage)
+ name = tr("Track %1").arg(index+1);
+ else
+ name = QLocale::languageToString(lang);
+ } else {
+ if (lang == QLocale::Language::AnyLanguage)
+ name = title;
+ else
+ name = QString("%1 - [%2]").arg(title).arg(QLocale::languageToString(lang));
+ }
+ return name;
+}
+
void Player::tracksChanged()
{
m_audioTracks->clear();
@@ -345,19 +365,19 @@ void Player::tracksChanged()
const auto audioTracks = m_player->audioTracks();
for (int i = 0; i < audioTracks.size(); ++i)
- m_audioTracks->addItem(audioTracks.at(i).stringValue(QMediaMetaData::Language), i);
+ m_audioTracks->addItem(trackName(audioTracks.at(i), i), i);
m_audioTracks->setCurrentIndex(m_player->activeAudioTrack());
const auto videoTracks = m_player->videoTracks();
for (int i = 0; i < videoTracks.size(); ++i)
- m_videoTracks->addItem(videoTracks.at(i).stringValue(QMediaMetaData::Language), i);
+ m_videoTracks->addItem(trackName(videoTracks.at(i), i), i);
m_videoTracks->setCurrentIndex(m_player->activeVideoTrack());
m_subtitleTracks->addItem(QString::fromUtf8("No subtitles"), -1);
const auto subtitleTracks = m_player->subtitleTracks();
for (int i = 0; i < subtitleTracks.size(); ++i)
- m_subtitleTracks->addItem(subtitleTracks.at(i).stringValue(QMediaMetaData::Language), i);
- m_subtitleTracks->setCurrentIndex(m_player->activeSubtitleTrack());
+ m_subtitleTracks->addItem(trackName(subtitleTracks.at(i), i), i);
+ m_subtitleTracks->setCurrentIndex(m_player->activeSubtitleTrack() + 1);
}
void Player::previousClicked()
@@ -469,7 +489,7 @@ void Player::selectAudioStream()
void Player::selectVideoStream()
{
- int stream = m_audioTracks->currentData().toInt();
+ int stream = m_videoTracks->currentData().toInt();
m_player->setActiveVideoTrack(stream);
}
diff --git a/examples/multimediawidgets/player/player.h b/examples/multimediawidgets/player/player.h
index cba34d973..0be2282f8 100644
--- a/examples/multimediawidgets/player/player.h
+++ b/examples/multimediawidgets/player/player.h
@@ -118,6 +118,7 @@ private:
void setStatusInfo(const QString &info);
void handleCursor(QMediaPlayer::MediaStatus status);
void updateDurationInfo(qint64 currentInfo);
+ QString trackName(const QMediaMetaData &metaData, int index);
QMediaPlayer *m_player = nullptr;
QAudioOutput *m_audioOutput = nullptr;
diff --git a/examples/multimediawidgets/player/playercontrols.cpp b/examples/multimediawidgets/player/playercontrols.cpp
index 671c7eaf1..73cf9ae21 100644
--- a/examples/multimediawidgets/player/playercontrols.cpp
+++ b/examples/multimediawidgets/player/playercontrols.cpp
@@ -138,18 +138,18 @@ void PlayerControls::setState(QMediaPlayer::PlaybackState state)
}
}
-int PlayerControls::volume() const
+float PlayerControls::volume() const
{
qreal linearVolume = QAudio::convertVolume(m_volumeSlider->value() / qreal(100),
QAudio::LogarithmicVolumeScale,
QAudio::LinearVolumeScale);
- return qRound(linearVolume * 100);
+ return linearVolume;
}
-void PlayerControls::setVolume(int volume)
+void PlayerControls::setVolume(float volume)
{
- qreal logarithmicVolume = QAudio::convertVolume(volume / qreal(100),
+ qreal logarithmicVolume = QAudio::convertVolume(volume,
QAudio::LinearVolumeScale,
QAudio::LogarithmicVolumeScale);
diff --git a/examples/multimediawidgets/player/playercontrols.h b/examples/multimediawidgets/player/playercontrols.h
index 3021e4859..927f72608 100644
--- a/examples/multimediawidgets/player/playercontrols.h
+++ b/examples/multimediawidgets/player/playercontrols.h
@@ -68,13 +68,13 @@ public:
explicit PlayerControls(QWidget *parent = nullptr);
QMediaPlayer::PlaybackState state() const;
- int volume() const;
+ float volume() const;
bool isMuted() const;
qreal playbackRate() const;
public slots:
void setState(QMediaPlayer::PlaybackState state);
- void setVolume(int volume);
+ void setVolume(float volume);
void setMuted(bool muted);
void setPlaybackRate(float rate);
@@ -84,7 +84,7 @@ signals:
void stop();
void next();
void previous();
- void changeVolume(int volume);
+ void changeVolume(float volume);
void changeMuting(bool muting);
void changeRate(qreal rate);
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
index 3c7a0ab46..7823a72e8 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
@@ -50,6 +50,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.MediaRecorder;
public class QtAudioDeviceManager
{
@@ -102,6 +103,19 @@ public class QtAudioDeviceManager
return getAudioDevices(AudioManager.GET_DEVICES_INPUTS);
}
+ private static boolean setAudioInput(MediaRecorder recorder, int id)
+ {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ final AudioDeviceInfo[] audioDevices =
+ m_audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+ for (AudioDeviceInfo deviceInfo : audioDevices) {
+ if (deviceInfo.getId() == id)
+ return recorder.setPreferredDevice(deviceInfo);
+ }
+ }
+ return false;
+ }
+
private static String audioDeviceTypeToString(int type)
{
switch (type)
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index 8c811131f..778083e37 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -302,6 +302,7 @@ qt_internal_extend_target(Multimedia CONDITION ANDROID
platform/android/audio/qandroidaudiodevice.cpp platform/android/audio/qandroidaudiodevice_p.h
platform/android/audio/qopenslesengine.cpp platform/android/audio/qopenslesengine_p.h
platform/android/common/qandroidaudiooutput_p.h
+ platform/android/audio/qandroidaudiodecoder.cpp platform/android/audio/qandroidaudiodecoder_p.h
platform/android/common/qandroidglobal_p.h
platform/android/common/qandroidmultimediautils.cpp platform/android/common/qandroidmultimediautils_p.h
platform/android/common/qandroidvideosink.cpp platform/android/common/qandroidvideosink_p.h
@@ -333,6 +334,7 @@ qt_internal_extend_target(Multimedia CONDITION ANDROID
Qt::CorePrivate
PUBLIC_LIBRARIES
OpenSLES
+ mediandk
Qt::Core
Qt::Network
Qt::OpenGL
@@ -479,6 +481,8 @@ qt_internal_add_shaders(Multimedia "shaders"
"/qt-project.org/multimedia/shaders"
FILES
"shaders/vertex.vert"
+ "shaders/externalsampler.vert"
+ "shaders/externalsampler.frag@glsl,100es,shaders/externalsampler_gles.frag"
"shaders/abgr.frag"
"shaders/argb.frag"
"shaders/bgra.frag"
diff --git a/src/multimedia/audio/qaudio.cpp b/src/multimedia/audio/qaudio.cpp
index 0d7fd8dea..33d491541 100644
--- a/src/multimedia/audio/qaudio.cpp
+++ b/src/multimedia/audio/qaudio.cpp
@@ -110,8 +110,6 @@ namespace QAudio
{
/*!
- \fn qreal QAudio::convertVolume(qreal volume, VolumeScale from, VolumeScale to)
-
Converts an audio \a volume \a from a volume scale \a to another, and returns the result.
Depending on the context, different scales are used to represent audio volume. All Qt Multimedia
@@ -133,27 +131,27 @@ namespace QAudio
\sa VolumeScale, QMediaPlayer::setVolume(), QAudioSink::setVolume(),
QAudioSource::setVolume(), QSoundEffect::setVolume(), QMediaRecorder::setVolume()
*/
-qreal convertVolume(qreal volume, VolumeScale from, VolumeScale to)
+float convertVolume(float volume, VolumeScale from, VolumeScale to)
{
switch (from) {
case LinearVolumeScale:
- volume = qMax(qreal(0), volume);
+ volume = qMax(float(0), volume);
switch (to) {
case LinearVolumeScale:
return volume;
case CubicVolumeScale:
- return qPow(volume, qreal(1 / 3.0));
+ return qPow(volume, float(1 / 3.0));
case LogarithmicVolumeScale:
return 1 - std::exp(-volume * LOG100);
case DecibelVolumeScale:
if (volume < 0.001)
- return qreal(-200);
+ return float(-200);
else
- return qreal(20.0) * std::log10(volume);
+ return float(20.0) * std::log10(volume);
}
break;
case CubicVolumeScale:
- volume = qMax(qreal(0), volume);
+ volume = qMax(float(0), volume);
switch (to) {
case LinearVolumeScale:
return volume * volume * volume;
@@ -163,13 +161,13 @@ qreal convertVolume(qreal volume, VolumeScale from, VolumeScale to)
return 1 - std::exp(-volume * volume * volume * LOG100);
case DecibelVolumeScale:
if (volume < 0.001)
- return qreal(-200);
+ return float(-200);
else
- return qreal(3.0 * 20.0) * std::log10(volume);
+ return float(3.0 * 20.0) * std::log10(volume);
}
break;
case LogarithmicVolumeScale:
- volume = qMax(qreal(0), volume);
+ volume = qMax(float(0), volume);
switch (to) {
case LinearVolumeScale:
if (volume > 0.99)
@@ -180,29 +178,29 @@ qreal convertVolume(qreal volume, VolumeScale from, VolumeScale to)
if (volume > 0.99)
return 1;
else
- return qPow(-std::log(1 - volume) / LOG100, qreal(1 / 3.0));
+ return qPow(-std::log(1 - volume) / LOG100, float(1 / 3.0));
case LogarithmicVolumeScale:
return volume;
case DecibelVolumeScale:
if (volume < 0.001)
- return qreal(-200);
+ return float(-200);
else if (volume > 0.99)
return 0;
else
- return qreal(20.0) * std::log10(-std::log(1 - volume) / LOG100);
+ return float(20.0) * std::log10(-std::log(1 - volume) / LOG100);
}
break;
case DecibelVolumeScale:
switch (to) {
case LinearVolumeScale:
- return qPow(qreal(10.0), volume / qreal(20.0));
+ return qPow(float(10.0), volume / float(20.0));
case CubicVolumeScale:
- return qPow(qreal(10.0), volume / qreal(3.0 * 20.0));
+ return qPow(float(10.0), volume / float(3.0 * 20.0));
case LogarithmicVolumeScale:
if (qFuzzyIsNull(volume))
return 1;
else
- return 1 - std::exp(-qPow(qreal(10.0), volume / qreal(20.0)) * LOG100);
+ return 1 - std::exp(-qPow(float(10.0), volume / float(20.0)) * LOG100);
case DecibelVolumeScale:
return volume;
}
diff --git a/src/multimedia/audio/qaudio.h b/src/multimedia/audio/qaudio.h
index ad824761d..868ef2f6a 100644
--- a/src/multimedia/audio/qaudio.h
+++ b/src/multimedia/audio/qaudio.h
@@ -63,7 +63,7 @@ namespace QAudio
DecibelVolumeScale
};
- Q_MULTIMEDIA_EXPORT qreal convertVolume(qreal volume, VolumeScale from, VolumeScale to);
+ Q_MULTIMEDIA_EXPORT float convertVolume(float volume, VolumeScale from, VolumeScale to);
}
#ifndef QT_NO_DEBUG_STREAM
diff --git a/src/multimedia/platform/android/audio/qandroidaudiodecoder.cpp b/src/multimedia/platform/android/audio/qandroidaudiodecoder.cpp
new file mode 100644
index 000000000..d50b468bc
--- /dev/null
+++ b/src/multimedia/platform/android/audio/qandroidaudiodecoder.cpp
@@ -0,0 +1,432 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "qandroidaudiodecoder_p.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnienvironment.h>
+#include <QTimer>
+#include <QFile>
+#include <QDir>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+QT_BEGIN_NAMESPACE
+
+static const char tempFile[] = "encoded.tmp";
+static const char tempPath[] = "/storage/emulated/0/data/local/tmp/audiodecoder/";
+constexpr int dequeueTimeout = 5000;
+Q_LOGGING_CATEGORY(adLogger, "QAndroidAudioDecoder")
+
+Decoder::Decoder()
+ : m_format(AMediaFormat_new())
+{}
+
+Decoder::~Decoder()
+{
+ if (m_codec) {
+ AMediaCodec_delete(m_codec);
+ m_codec = nullptr;
+ }
+
+ if (m_extractor) {
+ AMediaExtractor_delete(m_extractor);
+ m_extractor = nullptr;
+ }
+
+ if (m_format) {
+ AMediaFormat_delete(m_format);
+ m_format = nullptr;
+ }
+}
+
+void Decoder::stop()
+{
+ const media_status_t err = AMediaCodec_stop(m_codec);
+ if (err != AMEDIA_OK)
+ qCWarning(adLogger) << "stop() error: " << err;
+}
+
+void Decoder::setSource(const QUrl &source)
+{
+ if (!m_extractor)
+ m_extractor = AMediaExtractor_new();
+
+ int fd = -1;
+ if (source.path().contains(QLatin1String("content"))) {
+ fd = QJniObject::callStaticMethod<jint>("org/qtproject/qt/android/QtNative",
+ "openFdForContentUrl",
+ "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)I",
+ QNativeInterface::QAndroidApplication::context(),
+ QJniObject::fromString(source.path()).object(),
+ QJniObject::fromString(QLatin1String("r")).object());
+ } else {
+ fd = open(source.path().toStdString().c_str(), O_RDONLY);
+ }
+
+ if (fd < 0) {
+ emit error(QAudioDecoder::ResourceError, tr("Invalid fileDescriptor for source."));
+ return;
+ }
+
+ const int size = QFile(source.toString()).size();
+ media_status_t status = AMediaExtractor_setDataSourceFd(m_extractor, fd, 0, size);
+ close(fd);
+
+ if (status != AMEDIA_OK) {
+ if (m_extractor) {
+ AMediaExtractor_delete(m_extractor);
+ m_extractor = nullptr;
+ }
+ emit error(QAudioDecoder::ResourceError, tr("Setting source for Audio Decoder failed."));
+ }
+}
+
+void Decoder::createDecoder()
+{
+ // get encoded format for decoder
+ m_format = AMediaExtractor_getTrackFormat(m_extractor, 0);
+
+ const char *mime;
+ if (!AMediaFormat_getString(m_format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+ if (m_extractor) {
+ AMediaExtractor_delete(m_extractor);
+ m_extractor = nullptr;
+ }
+ emit error(QAudioDecoder::FormatError, tr("Format not supported by Audio Decoder."));
+
+ return;
+ }
+
+ // get audio duration from source
+ int64_t durationUs;
+ AMediaFormat_getInt64(m_format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
+ emit durationChanged(durationUs / 1000);
+
+ // set default output audio format from input file
+ if (!m_outputFormat.isValid()) {
+ int32_t sampleRate;
+ AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate);
+ m_outputFormat.setSampleRate(sampleRate);
+ int32_t channelCount;
+ AMediaFormat_getInt32(m_format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &channelCount);
+ m_outputFormat.setChannelCount(channelCount);
+ m_outputFormat.setSampleFormat(QAudioFormat::Int16);
+ }
+
+ m_codec = AMediaCodec_createDecoderByType(mime);
+}
+
+void Decoder::doDecode() {
+ createDecoder();
+
+ media_status_t status = AMediaCodec_configure(m_codec, m_format, nullptr /* surface */,
+ nullptr /* crypto */, 0);
+
+ if (status != AMEDIA_OK) {
+ emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed configuration."));
+ return;
+ }
+
+ status = AMediaCodec_start(m_codec);
+ if (status != AMEDIA_OK) {
+ emit error(QAudioDecoder::ResourceError, tr("Audio Decoder failed to start."));
+ return;
+ }
+
+ AMediaExtractor_selectTrack(m_extractor, 0);
+
+ m_inputEOS = false;
+ while (!m_inputEOS) {
+ // handle input buffer
+ const ssize_t bufferIdx = AMediaCodec_dequeueInputBuffer(m_codec, dequeueTimeout);
+
+ if (bufferIdx >= 0) {
+ size_t bufferSize = {};
+ uint8_t *buffer = AMediaCodec_getInputBuffer(m_codec, bufferIdx, &bufferSize);
+ const int sample = AMediaExtractor_readSampleData(m_extractor, buffer, bufferSize);
+ if (sample < 0) {
+ m_inputEOS = true;
+ break;
+ }
+
+ const int64_t presentationTimeUs = AMediaExtractor_getSampleTime(m_extractor);
+ AMediaCodec_queueInputBuffer(m_codec, bufferIdx, 0, sample, presentationTimeUs,
+ m_inputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+ AMediaExtractor_advance(m_extractor);
+
+ // handle output buffer
+ AMediaCodecBufferInfo info;
+ ssize_t idx = AMediaCodec_dequeueOutputBuffer(m_codec, &info, dequeueTimeout);
+ if (idx >= 0) {
+ if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
+ break;
+
+ if (info.size > 0) {
+ size_t bufferSize;
+ const uint8_t *bufferData = AMediaCodec_getOutputBuffer(m_codec, idx,
+ &bufferSize);
+ const QByteArray data((const char*)(bufferData + info.offset), info.size);
+ auto audioBuffer = QAudioBuffer(data, m_outputFormat, presentationTimeUs);
+ if (presentationTimeUs > 0)
+ emit positionChanged(std::move(audioBuffer), presentationTimeUs / 1000);
+ AMediaCodec_releaseOutputBuffer(m_codec, idx, false);
+ }
+ } else {
+ // The outputIndex doubles as a status return if its value is < 0
+ switch (idx) {
+ case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
+ qCWarning(adLogger) << "dequeueOutputBuffer() status: try again later";
+ break;
+ case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
+ qCWarning(adLogger) << "dequeueOutputBuffer() status: output buffers changed";
+ break;
+ case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED:
+ m_format = AMediaCodec_getOutputFormat(m_codec);
+ qCWarning(adLogger) << "dequeueOutputBuffer() status: outputFormat changed";
+ break;
+ }
+ }
+ } else {
+ qCWarning(adLogger) << "dequeueInputBuffer() status: invalid buffer idx " << bufferIdx;
+ }
+ }
+
+ emit finished();
+}
+
+QAndroidAudioDecoder::QAndroidAudioDecoder(QAudioDecoder *parent)
+ : QPlatformAudioDecoder(parent),
+ m_decoder(new Decoder())
+{
+ connect(m_decoder, &Decoder::positionChanged, this, &QAndroidAudioDecoder::positionChanged);
+ connect(m_decoder, &Decoder::durationChanged, this, &QAndroidAudioDecoder::durationChanged);
+ connect(m_decoder, &Decoder::error, this, &QAndroidAudioDecoder::error);
+ connect(m_decoder, &Decoder::finished, this, &QAndroidAudioDecoder::finished);
+}
+
+QAndroidAudioDecoder::~QAndroidAudioDecoder()
+{
+ m_decoder->thread()->exit();
+ m_decoder->deleteLater();
+}
+
+void QAndroidAudioDecoder::setSource(const QUrl &fileName)
+{
+ if (!requestPermissions())
+ return;
+
+ if (isDecoding())
+ return;
+
+ m_device = nullptr;
+ m_error = QAudioDecoder::NoError;
+
+ if (m_source != fileName) {
+ m_source = fileName;
+ m_decoder->setSource(m_source);
+ sourceChanged();
+ }
+}
+
+void QAndroidAudioDecoder::setSourceDevice(QIODevice *device)
+{
+ if (isDecoding())
+ return;
+
+ m_source.clear();
+ if (m_device != device) {
+ m_device = device;
+
+ if (!requestPermissions())
+ return;
+
+ sourceChanged();
+ }
+}
+
+void QAndroidAudioDecoder::start()
+{
+ if (isDecoding())
+ return;
+
+ setIsDecoding(true);
+ m_position = -1;
+
+ QThread *threadDecoder = new QThread(this);
+ m_decoder->moveToThread(threadDecoder);
+ threadDecoder->start();
+ decode();
+}
+
+void QAndroidAudioDecoder::stop()
+{
+ if (!isDecoding())
+ return;
+
+ m_decoder->stop();
+
+ QMutexLocker locker(&m_buffersMutex);
+ m_position = -1;
+ m_audioBuffer.clear();
+ locker.unlock();
+ setIsDecoding(false);
+}
+
+QAudioBuffer QAndroidAudioDecoder::read()
+{
+ QMutexLocker locker(&m_buffersMutex);
+ if (m_buffersAvailable && !m_audioBuffer.isEmpty()) {
+ --m_buffersAvailable;
+ return m_audioBuffer.takeFirst();
+ }
+
+ // no buffers available
+ return {};
+}
+
+bool QAndroidAudioDecoder::bufferAvailable() const
+{
+ QMutexLocker locker(&m_buffersMutex);
+ return m_buffersAvailable;
+}
+
+qint64 QAndroidAudioDecoder::position() const
+{
+ QMutexLocker locker(&m_buffersMutex);
+ return m_position;
+}
+
+qint64 QAndroidAudioDecoder::duration() const
+{
+ QMutexLocker locker(&m_buffersMutex);
+ return m_duration;
+}
+
+void QAndroidAudioDecoder::positionChanged(QAudioBuffer audioBuffer, qint64 position)
+{
+ QMutexLocker locker(&m_buffersMutex);
+ m_audioBuffer.append(audioBuffer);
+ m_position = position;
+ m_buffersAvailable++;
+ locker.unlock();
+ emit bufferReady();
+ emit QPlatformAudioDecoder::positionChanged(position);
+}
+
+void QAndroidAudioDecoder::durationChanged(qint64 duration)
+{
+ QMutexLocker locker(&m_buffersMutex);
+ m_duration = duration;
+ locker.unlock();
+ emit QPlatformAudioDecoder::durationChanged(duration);
+}
+
+void QAndroidAudioDecoder::error(const QAudioDecoder::Error err, const QString &errorString)
+{
+ QMutexLocker locker(&m_buffersMutex);
+ m_error = err;
+ locker.unlock();
+ emit QPlatformAudioDecoder::error(err, errorString);
+}
+
+void QAndroidAudioDecoder::finished()
+{
+ stop();
+ // remove temp file when decoding is finished
+ QFile(QString::fromUtf8(tempPath).append(QString::fromUtf8(tempFile))).remove();
+ emit QPlatformAudioDecoder::finished();
+}
+
+bool QAndroidAudioDecoder::requestPermissions()
+{
+ const auto writeRes = QCoreApplication::requestPermission(QPermission::WriteStorage);
+ if (writeRes.result() == QPermission::Authorized)
+ return true;
+
+ return false;
+}
+
+void QAndroidAudioDecoder::decode()
+{
+ if (m_device) {
+ connect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
+ if (m_device->bytesAvailable())
+ readDevice();
+ } else {
+ QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
+ }
+}
+
+bool QAndroidAudioDecoder::createTempFile()
+{
+ QFile file = QFile(QString::fromUtf8(tempPath).append(QString::fromUtf8(tempFile)));
+ if (!QDir().mkpath(QString::fromUtf8(tempPath)) || !file.open(QIODevice::WriteOnly)) {
+ emit error(QAudioDecoder::ResourceError,
+ QString::fromUtf8("Error while creating or opening tmp file"));
+ return false;
+ }
+
+ QDataStream out;
+ out.setDevice(&file);
+ out << m_deviceBuffer;
+ file.close();
+
+ m_deviceBuffer.clear();
+ m_decoder->setSource(file.fileName());
+
+ return true;
+}
+
+void QAndroidAudioDecoder::readDevice() {
+ m_deviceBuffer.append(m_device->readAll());
+ if (m_device->atEnd()) {
+ disconnect(m_device, &QIODevice::readyRead, this, &QAndroidAudioDecoder::readDevice);
+ if (!createTempFile()) {
+ m_deviceBuffer.clear();
+ stop();
+ return;
+ }
+ QTimer::singleShot(0, m_decoder, &Decoder::doDecode);
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/multimediaquick/qquickvideooutput_render_p.h b/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h
index beabd456e..cc6e15637 100644
--- a/src/multimediaquick/qquickvideooutput_render_p.h
+++ b/src/multimedia/platform/android/audio/qandroidaudiodecoder_p.h
@@ -1,7 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
-** Copyright (C) 2016 Research In Motion
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
@@ -38,8 +37,8 @@
**
****************************************************************************/
-#ifndef QQUICKVIDEOOUTPUT_RENDER_P_H
-#define QQUICKVIDEOOUTPUT_RENDER_P_H
+#ifndef QANDROIDAUDIODECODER_P_H
+#define QANDROIDAUDIODECODER_P_H
//
// W A R N I N G
@@ -51,74 +50,101 @@
//
// We mean it.
//
+#include "private/qplatformaudiodecoder_p.h"
-#include <QtQuick/qquickitem.h>
-#include <QtQuick/qsgnode.h>
-#include <private/qsgvideonode_p.h>
-
+#include <QtCore/qurl.h>
#include <QtCore/qmutex.h>
+#include <QThread>
+
+#include "media/NdkMediaCodec.h"
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaFormat.h"
+#include "media/NdkMediaError.h"
-QT_BEGIN_NAMESPACE
-class QVideoSink;
-class QObject;
-class QQuickVideoOutput;
+QT_USE_NAMESPACE
-class QQuickVideoBackend
+class Decoder : public QObject
{
+ Q_OBJECT
public:
- QQuickVideoBackend(QQuickVideoOutput *parent);
- ~QQuickVideoBackend();
-
- void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &changeData);
- QSize nativeSize() const;
- void updateGeometry();
- QSGNode *updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *data);
- QVideoSink *videoSink() const;
- QRectF adjustedViewport() const;
-
- friend class QSGVideoItemSurface;
- void present(const QVideoFrame &frame);
+ Decoder();
+ ~Decoder();
+
+public slots:
+ void setSource(const QUrl &source);
+ void doDecode();
void stop();
- void releaseResources();
- void invalidateSceneGraph();
+signals:
+ void positionChanged(const QAudioBuffer &buffer, qint64 position);
+ void durationChanged(const qint64 duration);
+ void error(const QAudioDecoder::Error error, const QString &errorString);
+ void finished();
private:
- QQuickVideoOutput *q;
+ void createDecoder();
- mutable QVideoSink *m_sink = nullptr;
- QVideoFrameFormat m_surfaceFormat;
+ AMediaCodec *m_codec = nullptr;
+ AMediaExtractor *m_extractor = nullptr;
+ AMediaFormat *m_format = nullptr;
- QVideoFrame m_frame;
- QVideoFrame m_frameOnFlush;
- bool m_frameChanged;
- QMutex m_frameMutex;
- QRectF m_renderedRect; // Destination pixel coordinates, clipped
- QRectF m_sourceTextureRect; // Source texture coordinates
+ QAudioFormat m_outputFormat;
+ bool m_inputEOS;
};
-namespace {
-inline bool qIsDefaultAspect(int o)
+class QAndroidAudioDecoder : public QPlatformAudioDecoder
{
- return (o % 180) == 0;
-}
+ Q_OBJECT
+public:
+ QAndroidAudioDecoder(QAudioDecoder *parent);
+ virtual ~QAndroidAudioDecoder();
-/*
- * Return the orientation normalized to 0-359
- */
-inline int qNormalizedOrientation(int o)
-{
- // Negative orientations give negative results
- int o2 = o % 360;
- if (o2 < 0)
- o2 += 360;
- return o2;
-}
+ QUrl source() const override { return m_source; }
+ void setSource(const QUrl &fileName) override;
+
+ QIODevice *sourceDevice() const override { return m_device; }
+ void setSourceDevice(QIODevice *device) override;
+
+ void start() override;
+ void stop() override;
+
+ QAudioBuffer read() override;
+ bool bufferAvailable() const override;
+
+ qint64 position() const override;
+ qint64 duration() const override;
-}
+private slots:
+ void positionChanged(QAudioBuffer audioBuffer, qint64 position);
+ void durationChanged(qint64 duration);
+ void error(const QAudioDecoder::Error error, const QString &errorString);
+ void readDevice();
+ void finished();
+
+private:
+ bool requestPermissions();
+ bool createTempFile();
+ void decode();
+
+ QIODevice *m_device = nullptr;
+ Decoder *m_decoder;
+
+ QList<QAudioBuffer> m_audioBuffer;
+ QUrl m_source;
+
+ QAudioDecoder::Error m_error = QAudioDecoder::NoError;
+
+ mutable QMutex m_buffersMutex;
+ qint64 m_position = -1;
+ qint64 m_duration = -1;
+ long long m_presentationTimeUs = 0;
+ int m_buffersAvailable = 0;
+
+ QByteArray m_deviceBuffer;
+};
QT_END_NAMESPACE
-#endif
+#endif // QANDROIDAUDIODECODER_P_H
diff --git a/src/multimedia/platform/android/audio/qandroidaudiosource.cpp b/src/multimedia/platform/android/audio/qandroidaudiosource.cpp
index e898f8369..c7eaf57ad 100644
--- a/src/multimedia/platform/android/audio/qandroidaudiosource.cpp
+++ b/src/multimedia/platform/android/audio/qandroidaudiosource.cpp
@@ -41,6 +41,7 @@
#include "qopenslesengine_p.h"
#include <private/qaudiohelpers_p.h>
+#include <QtCore/private/qandroidextras_p.h>
#include <qbuffer.h>
#include <qdebug.h>
@@ -61,12 +62,12 @@ static bool hasRecordingPermission()
if (QNativeInterface::QAndroidApplication::sdkVersion() < 23)
return true;
- const QPermission::PermissionType key(QPermission::Microphone);
+ const QtAndroidPrivate::PermissionType key(QtAndroidPrivate::Microphone);
// Permission already granted?
- if (QCoreApplication::checkPermission(key).result() == QPermission::Authorized)
+ if (QtAndroidPrivate::checkPermission(key).result() == QtAndroidPrivate::Authorized)
return true;
- if (QCoreApplication::requestPermission(key).result() != QPermission::Authorized) {
+ if (QtAndroidPrivate::requestPermission(key).result() != QtAndroidPrivate::Authorized) {
qDebug("Microphone permission denied by user!");
return false;
}
diff --git a/src/multimedia/platform/android/common/qandroidmultimediautils.cpp b/src/multimedia/platform/android/common/qandroidmultimediautils.cpp
index 79c01a68f..51c08006f 100644
--- a/src/multimedia/platform/android/common/qandroidmultimediautils.cpp
+++ b/src/multimedia/platform/android/common/qandroidmultimediautils.cpp
@@ -42,6 +42,7 @@
#include <qlist.h>
#include <QtCore/qcoreapplication.h>
+#include <QtCore/private/qandroidextras_p.h>
QT_BEGIN_NAMESPACE
@@ -108,16 +109,16 @@ AndroidCamera::ImageFormat qt_androidImageFormatFromPixelFormat(QVideoFrameForma
}
}
-static bool androidRequestPermission(QPermission::PermissionType key)
+static bool androidRequestPermission(QtAndroidPrivate::PermissionType key)
{
if (QNativeInterface::QAndroidApplication::sdkVersion() < 23)
return true;
// Permission already granted?
- if (QCoreApplication::checkPermission(key).result() == QPermission::Authorized)
+ if (QtAndroidPrivate::checkPermission(key).result() == QtAndroidPrivate::Authorized)
return true;
- if (QCoreApplication::requestPermission(key).result() != QPermission::Authorized)
+ if (QtAndroidPrivate::requestPermission(key).result() != QtAndroidPrivate::Authorized)
return false;
return true;
@@ -125,7 +126,7 @@ static bool androidRequestPermission(QPermission::PermissionType key)
bool qt_androidRequestCameraPermission()
{
- if (!androidRequestPermission(QPermission::Camera)) {
+ if (!androidRequestPermission(QtAndroidPrivate::Camera)) {
qCDebug(qtAndroidMediaPlugin, "Camera permission denied by user!");
return false;
}
@@ -135,7 +136,7 @@ bool qt_androidRequestCameraPermission()
bool qt_androidRequestRecordingPermission()
{
- if (!androidRequestPermission(QPermission::Microphone)) {
+ if (!androidRequestPermission(QtAndroidPrivate::Microphone)) {
qCDebug(qtAndroidMediaPlugin, "Microphone permission denied by user!");
return false;
}
@@ -145,8 +146,8 @@ bool qt_androidRequestRecordingPermission()
bool qt_androidRequestWriteStoragePermission()
{
- if (!androidRequestPermission(QPermission::WriteStorage)) {
- qCDebug(qtAndroidMediaPlugin, "WriteStorage permission denied by user!");
+ if (!androidRequestPermission(QtAndroidPrivate::Storage)) {
+ qCDebug(qtAndroidMediaPlugin, "Storage permission denied by user!");
return false;
}
diff --git a/src/multimedia/platform/android/common/qandroidvideooutput.cpp b/src/multimedia/platform/android/common/qandroidvideooutput.cpp
index 0ef662b48..957734d85 100644
--- a/src/multimedia/platform/android/common/qandroidvideooutput.cpp
+++ b/src/multimedia/platform/android/common/qandroidvideooutput.cpp
@@ -94,84 +94,39 @@ void OpenGLResourcesDeleter::deleteThisHelper()
delete this;
}
-class AndroidTextureVideoBuffer : public QAbstractVideoBuffer
+QAbstractVideoBuffer::MapData AndroidTextureVideoBuffer::map(QVideoFrame::MapMode mode)
{
-public:
- AndroidTextureVideoBuffer(QAndroidTextureVideoOutput *output, const QSize &size)
- : QAbstractVideoBuffer(QVideoFrame::NoHandle)
- , m_output(output)
- , m_size(size)
- {
+ MapData mapData;
+ if (!rhi && m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly && updateFrame()) {
+ m_mapMode = mode;
+ m_image = m_output->m_fbo->toImage();
+
+ mapData.nPlanes = 1;
+ mapData.bytesPerLine[0] = m_image.bytesPerLine();
+ mapData.size[0] = static_cast<int>(m_image.sizeInBytes());
+ mapData.data[0] = m_image.bits();
}
- virtual ~AndroidTextureVideoBuffer() {}
-
- QVideoFrame::MapMode mapMode() const override { return m_mapMode; }
-
- MapData map(QVideoFrame::MapMode mode) override
- {
- MapData mapData;
- if (m_mapMode == QVideoFrame::NotMapped && mode == QVideoFrame::ReadOnly && updateFrame()) {
- m_mapMode = mode;
- m_image = m_output->m_fbo->toImage();
-
- mapData.nPlanes = 1;
- mapData.bytesPerLine[0] = m_image.bytesPerLine();
- mapData.size[0] = static_cast<int>(m_image.sizeInBytes());
- mapData.data[0] = m_image.bits();
- }
-
- return mapData;
- }
-
- void unmap() override
- {
- m_image = QImage();
- m_mapMode = QVideoFrame::NotMapped;
- }
-
- quint64 textureHandle(int plane) const override
- {
- qDebug() << "AndroidTextureVideoBuffer::textureHandle()";
- if (plane != 0)
- return 0;
- AndroidTextureVideoBuffer *that = const_cast<AndroidTextureVideoBuffer*>(this);
- if (!that->updateFrame())
- return 0;
-
- return m_output->m_fbo->texture();
- }
-
-private:
- bool updateFrame()
- {
- // Even though the texture was updated in a previous call, we need to re-check
- // that this has not become a stale buffer, e.g., if the output size changed or
- // has since became invalid.
- if (!m_output->m_nativeSize.isValid())
- return false;
-
- // Size changed
- if (m_output->m_nativeSize != m_size)
- return false;
-
- // In the unlikely event that we don't have a valid fbo, but have a valid size,
- // force an update.
- const bool forceUpdate = !m_output->m_fbo;
-
- if (m_textureUpdated && !forceUpdate)
- return true;
-
- // update the video texture (called from the render thread)
- return (m_textureUpdated = m_output->renderFrameToFbo());
- }
+ return mapData;
+}
- QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
- QAndroidTextureVideoOutput *m_output = nullptr;
- QImage m_image;
- QSize m_size;
- bool m_textureUpdated = false;
-};
+quint64 AndroidTextureVideoBuffer::textureHandle(int plane) const
+{
+ if (plane != 0 || !rhi)
+ return 0;
+
+ m_output->ensureCommonGLResources();
+ m_output->m_surfaceTexture->updateTexImage();
+ m_externalMatrix = m_output->m_surfaceTexture->getTransformMatrix();
+ // flip it back, see http://androidxref.com/9.0.0_r3/xref/frameworks/native/libs/gui/GLConsumer.cpp#866
+ // (NB our matrix ctor takes row major)
+ static const QMatrix4x4 flipV(1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, -1.0f, 0.0f, 1.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ m_externalMatrix *= flipV;
+ return m_output->m_externalTex;
+}
QAndroidTextureVideoOutput::QAndroidTextureVideoOutput(QObject *parent)
: QAndroidVideoOutput(parent)
@@ -284,8 +239,19 @@ void QAndroidTextureVideoOutput::onFrameAvailable()
if (!m_nativeSize.isValid() || !m_sink)
return;
- QAbstractVideoBuffer *buffer = new AndroidTextureVideoBuffer(this, m_nativeSize);
- QVideoFrame frame(buffer, QVideoFrameFormat(m_nativeSize, QVideoFrameFormat::Format_ARGB32_Premultiplied));
+ QVideoFrameFormat::PixelFormat format = QVideoFrameFormat::Format_ARGB32_Premultiplied;
+#ifdef QANDROIDVIDEOUTPUT_NO_DIRECT_TEXTURE_USAGE
+ QRhi *rhi = nullptr;
+#else
+ QRhi *rhi = m_sink ? m_sink->rhi() : nullptr;
+#endif
+ if (rhi && rhi->backend() != QRhi::OpenGLES2)
+ rhi = nullptr;
+ if (rhi)
+ format = QVideoFrameFormat::Format_SamplerExternalOES;
+
+ auto *buffer = new AndroidTextureVideoBuffer(rhi, this, m_nativeSize);
+ QVideoFrame frame(buffer, QVideoFrameFormat(m_nativeSize, format));
m_sink->newVideoFrame(frame);
}
@@ -329,7 +295,7 @@ bool QAndroidTextureVideoOutput::renderFrameToFbo()
m_glContext->makeCurrent(m_offscreenSurface);
- createGLResources();
+ ensureFboGLResources();
m_surfaceTexture->updateTexImage();
@@ -386,9 +352,9 @@ bool QAndroidTextureVideoOutput::renderFrameToFbo()
return true;
}
-void QAndroidTextureVideoOutput::createGLResources()
+void QAndroidTextureVideoOutput::ensureCommonGLResources()
{
- Q_ASSERT(QOpenGLContext::currentContext() != NULL);
+ Q_ASSERT(QOpenGLContext::currentContext());
if (!m_glDeleter)
m_glDeleter = new OpenGLResourcesDeleter;
@@ -398,6 +364,11 @@ void QAndroidTextureVideoOutput::createGLResources()
glGenTextures(1, &m_externalTex);
m_surfaceTexture->attachToGLContext(m_externalTex);
}
+}
+
+void QAndroidTextureVideoOutput::ensureFboGLResources()
+{
+ ensureCommonGLResources();
if (!m_fbo || m_fbo->size() != m_nativeSize) {
delete m_fbo;
diff --git a/src/multimedia/platform/android/common/qandroidvideooutput_p.h b/src/multimedia/platform/android/common/qandroidvideooutput_p.h
index 701df8b8f..b651c1dcc 100644
--- a/src/multimedia/platform/android/common/qandroidvideooutput_p.h
+++ b/src/multimedia/platform/android/common/qandroidvideooutput_p.h
@@ -54,9 +54,16 @@
#include <qobject.h>
#include <qsize.h>
#include <qmutex.h>
+#include <private/qabstractvideobuffer_p.h>
+#include <qmatrix4x4.h>
QT_BEGIN_NAMESPACE
+// Enable this to prevent using the external texture directly (bound as
+// GL_TEXTURE_EXTERNAL_OES), but rather do a readback on every frame and
+// upload the QImage data into a plain 2D texture.
+//#define QANDROIDVIDEOUTPUT_NO_DIRECT_TEXTURE_USAGE
+
class AndroidSurfaceTexture;
class AndroidSurfaceHolder;
class QOpenGLFramebufferObject;
@@ -64,6 +71,7 @@ class QOpenGLShaderProgram;
class QWindow;
class QOpenGLContext;
class QVideoSink;
+class QRhi;
class QAndroidVideoOutput : public QObject
{
@@ -126,7 +134,8 @@ private Q_SLOTS:
private:
void initSurfaceTexture();
bool renderFrameToFbo();
- void createGLResources();
+ void ensureCommonGLResources();
+ void ensureFboGLResources();
QMutex m_mutex;
void clearSurfaceTexture();
@@ -147,6 +156,68 @@ private:
friend class AndroidTextureVideoBuffer;
};
+
+class AndroidTextureVideoBuffer : public QAbstractVideoBuffer
+{
+public:
+ AndroidTextureVideoBuffer(QRhi *rhi, QAndroidTextureVideoOutput *output, const QSize &size)
+ : QAbstractVideoBuffer(rhi ? QVideoFrame::RhiTextureHandle : QVideoFrame::NoHandle, rhi)
+ , m_output(output)
+ , m_size(size)
+ {
+ }
+
+ virtual ~AndroidTextureVideoBuffer() {}
+
+ QVideoFrame::MapMode mapMode() const override { return m_mapMode; }
+
+ MapData map(QVideoFrame::MapMode mode) override;
+
+ void unmap() override
+ {
+ m_image = QImage();
+ m_mapMode = QVideoFrame::NotMapped;
+ }
+
+ quint64 textureHandle(int plane) const override;
+
+ QMatrix4x4 externalTextureMatrix() const
+ {
+ return m_externalMatrix;
+ }
+
+private:
+ bool updateFrame()
+ {
+ // Even though the texture was updated in a previous call, we need to re-check
+ // that this has not become a stale buffer, e.g., if the output size changed or
+ // has since became invalid.
+ if (!m_output->m_nativeSize.isValid())
+ return false;
+
+ // Size changed
+ if (m_output->m_nativeSize != m_size)
+ return false;
+
+ // In the unlikely event that we don't have a valid fbo, but have a valid size,
+ // force an update.
+ const bool forceUpdate = !m_output->m_fbo;
+
+ if (m_textureUpdated && !forceUpdate)
+ return true;
+
+ // update the video texture (called from the render thread)
+ return (m_textureUpdated = m_output->renderFrameToFbo());
+ }
+
+ QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped;
+ QAndroidTextureVideoOutput *m_output = nullptr;
+ QImage m_image;
+ QSize m_size;
+ mutable QMatrix4x4 m_externalMatrix;
+ bool m_textureUpdated = false;
+};
+
QT_END_NAMESPACE
#endif // QANDROIDVIDEOOUTPUT_H
diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamera.cpp b/src/multimedia/platform/android/mediacapture/qandroidcamera.cpp
index e9233d297..53ba0d998 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidcamera.cpp
+++ b/src/multimedia/platform/android/mediacapture/qandroidcamera.cpp
@@ -51,11 +51,6 @@ QAndroidCamera::QAndroidCamera(QCamera *camera)
: QPlatformCamera(camera)
{
Q_ASSERT(camera);
-
- m_recalculateTimer = new QTimer(this);
- m_recalculateTimer->setInterval(1000);
- m_recalculateTimer->setSingleShot(true);
- connect(m_recalculateTimer, SIGNAL(timeout()), this, SLOT(onRecalculateTimeOut()));
}
QAndroidCamera::~QAndroidCamera()
@@ -97,22 +92,17 @@ void QAndroidCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
m_service = captureSession;
if (!m_service) {
- m_cameraSession = nullptr;
disconnect(m_cameraSession,nullptr,this,nullptr);
+ m_cameraSession = nullptr;
return;
}
m_cameraSession = m_service->cameraSession();
Q_ASSERT(m_cameraSession);
- connect(m_cameraSession, SIGNAL(statusChanged(QCamera::Status)),
- this, SIGNAL(statusChanged(QCamera::Status)));
-
- connect(m_cameraSession, SIGNAL(stateChanged(QCamera::State)),
- this, SIGNAL(stateChanged(QCamera::State)));
-
- connect(m_cameraSession, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString)));
-
+ connect(m_cameraSession, &QAndroidCameraSession::activeChanged, this, &QAndroidCamera::activeChanged);
+ connect(m_cameraSession, &QAndroidCameraSession::error, this, &QAndroidCamera::error);
+ connect(m_cameraSession, &QAndroidCameraSession::opened, this, &QAndroidCamera::onCameraOpened);
}
void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
@@ -163,8 +153,7 @@ bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode) const
void QAndroidCamera::onCameraOpened()
{
Q_ASSERT(m_cameraSession);
- connect(m_cameraSession->camera(), SIGNAL(previewSizeChanged()),
- this, SLOT(setCameraFocusArea()));
+ connect(m_cameraSession->camera(), &AndroidCamera::previewSizeChanged, this, &QAndroidCamera::setCameraFocusArea);
m_supportedFocusModes.clear();
m_continuousPictureFocusSupported = false;
diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamera_p.h b/src/multimedia/platform/android/mediacapture/qandroidcamera_p.h
index e4f65640e..caea9503d 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidcamera_p.h
+++ b/src/multimedia/platform/android/mediacapture/qandroidcamera_p.h
@@ -101,7 +101,6 @@ private Q_SLOTS:
private:
QAndroidCameraSession *m_cameraSession = nullptr;
QAndroidMediaCaptureSession *m_service = nullptr;
- QTimer *m_recalculateTimer = nullptr;
QList<QCamera::FocusMode> m_supportedFocusModes;
bool m_continuousPictureFocusSupported = false;
diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp b/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp
index 74dd19d47..80e585a8d 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp
+++ b/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp
@@ -341,14 +341,12 @@ QList<AndroidCamera::FpsRange> QAndroidCameraSession::getSupportedPreviewFpsRang
bool QAndroidCameraSession::startPreview()
{
- if (!m_camera)
+ if (!m_camera || !m_videoOutput)
return false;
if (m_previewStarted)
return true;
- Q_ASSERT(m_videoOutput);
-
if (!m_videoOutput->isReady())
return true; // delay starting until the video output is ready
@@ -622,7 +620,8 @@ void QAndroidCameraSession::onCameraPreviewFailedToStart()
void QAndroidCameraSession::onCameraPreviewStopped()
{
- setActive(false);
+ if (!m_previewStarted)
+ setActive(false);
setReadyForCapture(false);
}
diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp
index 17c3c1f64..9b3ee40af 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp
+++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp
@@ -57,7 +57,6 @@ QAndroidCaptureSession::QAndroidCaptureSession()
: QObject()
, m_mediaRecorder(0)
, m_cameraSession(0)
- , m_audioSource(AndroidMediaRecorder::DefaultAudioSource)
, m_duration(0)
, m_state(QMediaRecorder::StoppedState)
, m_outputFormat(AndroidMediaRecorder::DefaultOutputFormat)
@@ -102,26 +101,7 @@ void QAndroidCaptureSession::setCameraSession(QAndroidCameraSession *cameraSessi
void QAndroidCaptureSession::setAudioInput(QPlatformAudioInput *input)
{
- if (m_audioInput == input)
- return;
-
m_audioInput = input;
-
- QString id = input ? QString::fromLatin1(input->device.id()) : QString();
- if (id == QLatin1String("default"))
- m_audioSource = AndroidMediaRecorder::DefaultAudioSource;
- else if (id == QLatin1String("mic"))
- m_audioSource = AndroidMediaRecorder::Mic;
- else if (id == QLatin1String("voice_uplink"))
- m_audioSource = AndroidMediaRecorder::VoiceUplink;
- else if (id == QLatin1String("voice_downlink"))
- m_audioSource = AndroidMediaRecorder::VoiceDownlink;
- else if (id == QLatin1String("voice_call"))
- m_audioSource = AndroidMediaRecorder::VoiceCall;
- else if (id == QLatin1String("voice_recognition"))
- m_audioSource = AndroidMediaRecorder::VoiceRecognition;
- else
- m_audioSource = AndroidMediaRecorder::DefaultAudioSource;
}
QMediaRecorder::RecorderState QAndroidCaptureSession::state() const
@@ -140,6 +120,11 @@ void QAndroidCaptureSession::start(const QMediaEncoderSettings &, const QUrl &ou
m_mediaRecorder = nullptr;
}
+ if (!m_cameraSession && !m_audioInput) {
+ Q_EMIT error(QMediaRecorder::ResourceError, QLatin1String("Audio Input device not set"));
+ return;
+ }
+
const bool granted = m_cameraSession
? m_cameraSession->requestRecordingPermission()
: qt_androidRequestRecordingPermission();
@@ -159,8 +144,11 @@ void QAndroidCaptureSession::start(const QMediaEncoderSettings &, const QUrl &ou
m_mediaRecorder->setCamera(m_cameraSession->camera());
m_mediaRecorder->setAudioSource(AndroidMediaRecorder::Camcorder);
m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera);
- } else {
- m_mediaRecorder->setAudioSource(m_audioSource);
+ }
+
+ if (m_audioInput) {
+ m_mediaRecorder->setAudioInput(m_audioInput->device.id());
+ m_mediaRecorder->setAudioSource(AndroidMediaRecorder::DefaultAudioSource);
}
// Set output format
diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h
index 239cf49ad..23c96d867 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h
+++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h
@@ -160,7 +160,6 @@ private:
QAndroidCameraSession *m_cameraSession;
QPlatformAudioInput *m_audioInput = nullptr;
- AndroidMediaRecorder::AudioSource m_audioSource;
QMediaStorageLocation m_mediaStorageLocation;
diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediacapturesession.cpp b/src/multimedia/platform/android/mediacapture/qandroidmediacapturesession.cpp
index cdc81d0d7..ee0fd9f8d 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidmediacapturesession.cpp
+++ b/src/multimedia/platform/android/mediacapture/qandroidmediacapturesession.cpp
@@ -52,6 +52,7 @@ QT_BEGIN_NAMESPACE
QAndroidMediaCaptureSession::QAndroidMediaCaptureSession()
: m_captureSession(new QAndroidCaptureSession())
+ , m_cameraSession(new QAndroidCameraSession())
{
}
@@ -71,15 +72,10 @@ QPlatformCamera *QAndroidMediaCaptureSession::camera()
void QAndroidMediaCaptureSession::setCamera(QPlatformCamera *camera)
{
- if (!m_cameraSession) {
- if (camera) {
- m_cameraSession = new QAndroidCameraSession;
- m_captureSession->setCameraSession(m_cameraSession);
- }
- } else if (!camera){
+ if (camera) {
+ m_captureSession->setCameraSession(m_cameraSession);
+ } else {
m_captureSession->setCameraSession(nullptr);
- delete m_cameraSession;
- m_cameraSession = nullptr;
}
QAndroidCamera *control = static_cast<QAndroidCamera *>(camera);
diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediaencoder.cpp b/src/multimedia/platform/android/mediacapture/qandroidmediaencoder.cpp
index defe48297..7daa89262 100644
--- a/src/multimedia/platform/android/mediacapture/qandroidmediaencoder.cpp
+++ b/src/multimedia/platform/android/mediacapture/qandroidmediaencoder.cpp
@@ -57,12 +57,13 @@ bool QAndroidMediaEncoder::isLocationWritable(const QUrl &location) const
QMediaRecorder::RecorderState QAndroidMediaEncoder::state() const
{
- return m_session->state();
+ return m_session ? m_session->state() : QMediaRecorder::StoppedState;
}
qint64 QAndroidMediaEncoder::duration() const
{
- return m_session->duration();
+ return m_session ? m_session->duration() : 0;
+
}
void QAndroidMediaEncoder::applySettings(const QMediaEncoderSettings &settings)
diff --git a/src/multimedia/platform/android/qandroidintegration.cpp b/src/multimedia/platform/android/qandroidintegration.cpp
index ad0e9e4b6..5299275fa 100644
--- a/src/multimedia/platform/android/qandroidintegration.cpp
+++ b/src/multimedia/platform/android/qandroidintegration.cpp
@@ -54,6 +54,7 @@
#include "private/qandroidmediaplayer_p.h"
#include "private/qandroidaudiooutput_p.h"
#include "private/qandroidvideosink_p.h"
+#include "private/qandroidaudiodecoder_p.h"
QT_BEGIN_NAMESPACE
@@ -77,6 +78,11 @@ QPlatformMediaDevices *QAndroidIntegration::devices()
return m_devices;
}
+QPlatformAudioDecoder *QAndroidIntegration::createAudioDecoder(QAudioDecoder *decoder)
+{
+ return new QAndroidAudioDecoder(decoder);
+}
+
QPlatformMediaFormatInfo *QAndroidIntegration::formatInfo()
{
if (!m_formatInfo)
diff --git a/src/multimedia/platform/android/qandroidintegration_p.h b/src/multimedia/platform/android/qandroidintegration_p.h
index ca387dbc9..a56881988 100644
--- a/src/multimedia/platform/android/qandroidintegration_p.h
+++ b/src/multimedia/platform/android/qandroidintegration_p.h
@@ -66,6 +66,7 @@ public:
QPlatformMediaDevices *devices() override;
QPlatformMediaFormatInfo *formatInfo() override;
+ QPlatformAudioDecoder *createAudioDecoder(QAudioDecoder *decoder) override;
QPlatformMediaCaptureSession *createCaptureSession() override;
QPlatformMediaPlayer *createPlayer(QMediaPlayer *player) override;
QPlatformCamera *createCamera(QCamera *camera) override;
diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp
index 5f99d5820..a79e45406 100644
--- a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp
+++ b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp
@@ -234,6 +234,19 @@ void AndroidMediaRecorder::setAudioSource(AudioSource source)
m_mediaRecorder.callMethod<void>("setAudioSource", "(I)V", int(source));
}
+bool AndroidMediaRecorder::setAudioInput(const QByteArray &id)
+{
+ const bool ret = QJniObject::callStaticMethod<jboolean>("org/qtproject/qt/android/multimedia/QtAudioDeviceManager",
+ "setAudioInput",
+ "(Landroid/media/MediaRecorder;I)Z",
+ m_mediaRecorder.object(),
+ id.toInt());
+ if (!ret)
+ qCWarning(QLoggingCategory("mediarecorder")) << "No default input device was set";
+
+ return ret;
+}
+
void AndroidMediaRecorder::setCamera(AndroidCamera *camera)
{
QJniObject cam = camera->getCameraObject();
diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h
index a9d597869..1db6297a8 100644
--- a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h
+++ b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h
@@ -161,6 +161,7 @@ public:
void setAudioEncodingBitRate(int bitRate);
void setAudioSamplingRate(int samplingRate);
void setAudioSource(AudioSource source);
+ bool setAudioInput(const QByteArray &id);
void setCamera(AndroidCamera *camera);
void setVideoEncoder(VideoEncoder encoder);
diff --git a/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm b/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
index ecea99f74..eb91223c8 100644
--- a/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
+++ b/src/multimedia/platform/darwin/audio/avfaudiodecoder.mm
@@ -215,6 +215,10 @@ QAudioFormat qt_format_for_audio_track(AVAssetTrack *track)
const AudioStreamBasicDescription* const asbd =
CMAudioFormatDescriptionGetStreamBasicDescription(desc);
format = CoreAudioUtils::toQAudioFormat(*asbd);
+ // AudioStreamBasicDescription's mBitsPerChannel is 0 for compressed formats
+ // In this case set default Int16 sample format
+ if (asbd->mBitsPerChannel == 0)
+ format.setSampleFormat(QAudioFormat::Int16);
return format;
}
diff --git a/src/multimedia/platform/darwin/audio/qcoreaudioutils.mm b/src/multimedia/platform/darwin/audio/qcoreaudioutils.mm
index dc3f27218..8faa353c7 100644
--- a/src/multimedia/platform/darwin/audio/qcoreaudioutils.mm
+++ b/src/multimedia/platform/darwin/audio/qcoreaudioutils.mm
@@ -96,9 +96,6 @@ QAudioFormat CoreAudioUtils::toQAudioFormat(AudioStreamBasicDescription const& s
break;
}
- if (format == QAudioFormat::Unknown)
- return audioFormat;
-
audioFormat.setSampleFormat(format);
audioFormat.setSampleRate(sf.mSampleRate);
audioFormat.setChannelCount(sf.mChannelsPerFrame);
diff --git a/src/multimedia/platform/darwin/camera/avfcamera.mm b/src/multimedia/platform/darwin/camera/avfcamera.mm
index 527f5e615..f60d2bd83 100644
--- a/src/multimedia/platform/darwin/camera/avfcamera.mm
+++ b/src/multimedia/platform/darwin/camera/avfcamera.mm
@@ -669,8 +669,13 @@ bool AVFCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
return true;
if (mode != QCamera::ExposureManual)
return false;
- AVCaptureDevice *captureDevice = device();
- return captureDevice && [captureDevice isExposureModeSupported:AVCaptureExposureModeCustom];
+
+ if (@available(macOS 10.15, *)) {
+ AVCaptureDevice *captureDevice = device();
+ return captureDevice && [captureDevice isExposureModeSupported:AVCaptureExposureModeCustom];
+ }
+
+ return false;
}
void AVFCamera::applyFlashSettings()
diff --git a/src/multimedia/platform/gstreamer/common/qgst_p.h b/src/multimedia/platform/gstreamer/common/qgst_p.h
index 8036fb8b9..3f0321916 100644
--- a/src/multimedia/platform/gstreamer/common/qgst_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgst_p.h
@@ -480,28 +480,6 @@ public:
void sendEvent(GstEvent *event) const { gst_element_send_event(element(), event); }
void sendEos() const { sendEvent(gst_event_new_eos()); }
- bool seek(qint64 pos, double rate)
- {
- return gst_element_seek(element(), rate, GST_FORMAT_TIME,
- GstSeekFlags(GST_SEEK_FLAG_FLUSH),
- GST_SEEK_TYPE_SET, pos,
- GST_SEEK_TYPE_SET, -1);
- }
- qint64 duration() const
- {
- gint64 d;
- if (!gst_element_query_duration(element(), GST_FORMAT_TIME, &d))
- return 0.;
- return d;
- }
- qint64 position() const
- {
- gint64 pos;
- if (!gst_element_query_position(element(), GST_FORMAT_TIME, &pos))
- return 0.;
- return pos;
- }
-
template<auto Member, typename T>
void onPadAdded(T *instance) {
struct Impl {
diff --git a/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp b/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
index a8853803c..f6026a6fa 100644
--- a/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstpipeline.cpp
@@ -63,6 +63,11 @@ public:
QList<QGstreamerSyncMessageFilter*> syncFilters;
QList<QGstreamerBusMessageFilter*> busFilters;
QProperty<bool> inStoppedState;
+ mutable qint64 m_position = 0;
+ double m_rate = 1.;
+
+ int m_configCounter = 0;
+ GstState m_savedState = GST_STATE_NULL;
QGstPipelinePrivate(GstBus* bus, QObject* parent = 0);
~QGstPipelinePrivate();
@@ -260,6 +265,93 @@ void QGstPipeline::removeMessageFilter(QGstreamerBusMessageFilter *filter)
d->removeMessageFilter(filter);
}
+void QGstPipeline::beginConfig()
+{
+ if (!d)
+ return;
+ Q_ASSERT(!isNull());
+
+ ++d->m_configCounter;
+ if (d->m_configCounter > 1)
+ return;
+
+ d->m_savedState = state();
+ if (d->m_savedState == GST_STATE_PLAYING)
+ setStateSync(GST_STATE_PAUSED);
+}
+
+void QGstPipeline::endConfig()
+{
+ if (!d)
+ return;
+ Q_ASSERT(!isNull());
+
+ --d->m_configCounter;
+ if (d->m_configCounter)
+ return;
+
+ if (d->m_savedState != GST_STATE_NULL)
+ flush();
+ if (d->m_savedState == GST_STATE_PLAYING)
+ setStateSync(GST_STATE_PLAYING);
+ d->m_savedState = GST_STATE_NULL;
+}
+
+void QGstPipeline::flush()
+{
+ seek(position(), d->m_rate);
+}
+
+bool QGstPipeline::seek(qint64 pos, double rate)
+{
+ // always adjust the rate, so it can be set before playback starts
+ // setting position needs a loaded media file that's seekable
+ d->m_rate = rate;
+ bool success = gst_element_seek(element(), rate, GST_FORMAT_TIME,
+ GstSeekFlags(GST_SEEK_FLAG_FLUSH),
+ GST_SEEK_TYPE_SET, pos,
+ GST_SEEK_TYPE_SET, -1);
+ if (!success)
+ return false;
+
+ d->m_position = pos;
+ return true;
+}
+
+bool QGstPipeline::setPlaybackRate(double rate)
+{
+ if (rate == d->m_rate)
+ return false;
+ seek(position(), rate);
+ return true;
+}
+
+double QGstPipeline::playbackRate() const
+{
+ return d->m_rate;
+}
+
+bool QGstPipeline::setPosition(qint64 pos)
+{
+ return seek(pos, d->m_rate);
+}
+
+qint64 QGstPipeline::position() const
+{
+ gint64 pos;
+ if (gst_element_query_position(element(), GST_FORMAT_TIME, &pos))
+ d->m_position = pos;
+ return d->m_position;
+}
+
+qint64 QGstPipeline::duration() const
+{
+ gint64 d;
+ if (!gst_element_query_duration(element(), GST_FORMAT_TIME, &d))
+ return 0.;
+ return d;
+}
+
QT_END_NAMESPACE
#include "qgstpipeline.moc"
diff --git a/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h b/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
index c67e84251..019925af0 100644
--- a/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstpipeline_p.h
@@ -102,6 +102,9 @@ public:
void dumpGraph(const char *fileName)
{
+ if (isNull())
+ return;
+
#if 1 //def QT_GST_CAPTURE_DEBUG
GST_DEBUG_BIN_TO_DOT_FILE(bin(),
GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL |
@@ -112,6 +115,19 @@ public:
#endif
}
+ void beginConfig();
+ void endConfig();
+
+ void flush();
+
+ bool seek(qint64 pos, double rate);
+ bool setPlaybackRate(double rate);
+ double playbackRate() const;
+
+ bool setPosition(qint64 pos);
+ qint64 position() const;
+
+ qint64 duration() const;
};
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreameraudioinput.cpp b/src/multimedia/platform/gstreamer/common/qgstreameraudioinput.cpp
index 46e941a00..f406462e4 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreameraudioinput.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioinput.cpp
@@ -111,18 +111,8 @@ void QGstreamerAudioInput::setAudioDevice(const QAudioDevice &device)
qCDebug(qLcMediaAudioInput) << "setAudioInput" << device.description() << device.isNull();
m_audioDevice = device;
- if (gstPipeline.isNull() || gstPipeline.state() != GST_STATE_PLAYING) {
- changeAudioInput();
- return;
- }
-
- auto pad = audioVolume.staticPad("src");
- pad.addProbe<&QGstreamerAudioInput::prepareAudioInputChange>(this, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM);
-}
+ gstPipeline.beginConfig();
-bool QGstreamerAudioInput::changeAudioInput()
-{
- qCDebug(qLcMediaAudioInput) << "Changing audio Input";
QGstElement newSrc;
auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioDevice.handle());
if (deviceInfo && deviceInfo->gstDevice)
@@ -138,16 +128,7 @@ bool QGstreamerAudioInput::changeAudioInput()
audioSrc.link(audioVolume);
audioSrc.setState(GST_STATE_PAUSED);
- return true;
-}
-
-void QGstreamerAudioInput::prepareAudioInputChange(const QGstPad &/*pad*/)
-{
- qCDebug(qLcMediaAudioInput) << "Reconfiguring audio Input";
-
- gstPipeline.setStateSync(GST_STATE_PAUSED);
- changeAudioInput();
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
QAudioDevice QGstreamerAudioInput::audioInput() const
diff --git a/src/multimedia/platform/gstreamer/common/qgstreameraudioinput_p.h b/src/multimedia/platform/gstreamer/common/qgstreameraudioinput_p.h
index 5a6a330ac..b45f96b6c 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreameraudioinput_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioinput_p.h
@@ -92,9 +92,6 @@ Q_SIGNALS:
void volumeChanged(int);
private:
- void prepareAudioInputChange(const QGstPad &pad);
- bool changeAudioInput();
-
float m_volume = 1.;
bool m_muted = false;
diff --git a/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput.cpp b/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput.cpp
index a7a531e7f..0060d20fc 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput.cpp
@@ -89,60 +89,32 @@ void QGstreamerAudioOutput::setPipeline(const QGstPipeline &pipeline)
gstPipeline = pipeline;
}
-bool QGstreamerAudioOutput::setAudioOutput(const QAudioDevice &info)
+void QGstreamerAudioOutput::setAudioDevice(const QAudioDevice &info)
{
if (info == m_audioOutput)
- return true;
+ return;
qCDebug(qLcMediaAudioOutput) << "setAudioOutput" << info.description() << info.isNull();
m_audioOutput = info;
- if (gstPipeline.isNull() || gstPipeline.state() != GST_STATE_PLAYING)
- return changeAudioOutput();
+ gstPipeline.beginConfig();
- auto pad = audioVolume.staticPad("src");
- pad.addProbe<&QGstreamerAudioOutput::prepareAudioOutputChange>(this, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM);
-
- return true;
-}
+ audioSink.setStateSync(GST_STATE_NULL);
+ gstAudioOutput.remove(audioSink);
-bool QGstreamerAudioOutput::changeAudioOutput()
-{
- qCDebug(qLcMediaAudioOutput) << "Changing audio output";
QGstElement newSink;
auto *deviceInfo = static_cast<const QGStreamerAudioDeviceInfo *>(m_audioOutput.handle());
- if (!deviceInfo)
- newSink = QGstElement("fakesink", "fakeaudiosink");
- else if (deviceInfo->gstDevice)
+ if (deviceInfo && deviceInfo->gstDevice)
newSink = gst_device_create_element(deviceInfo->gstDevice , "audiosink");
if (newSink.isNull())
newSink = QGstElement("autoaudiosink", "audiosink");
- gstAudioOutput.remove(audioSink);
audioSink = newSink;
gstAudioOutput.add(audioSink);
audioVolume.link(audioSink);
+ audioSink.setState(GST_STATE_PAUSED);
- return true;
-}
-
-void QGstreamerAudioOutput::prepareAudioOutputChange(const QGstPad &/*pad*/)
-{
- qCDebug(qLcMediaAudioOutput) << "Reconfiguring audio output";
-
- auto state = gstPipeline.state();
- if (state == GST_STATE_PLAYING)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
- audioSink.setStateSync(GST_STATE_NULL);
- changeAudioOutput();
- audioSink.setStateSync(GST_STATE_PAUSED);
- if (state == GST_STATE_PLAYING)
- gstPipeline.setStateSync(state);
-}
-
-QAudioDevice QGstreamerAudioOutput::audioOutput() const
-{
- return m_audioOutput;
+ gstPipeline.endConfig();
}
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput_p.h b/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput_p.h
index fc1a8b8db..c3b33c4f4 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreameraudiooutput_p.h
@@ -73,11 +73,7 @@ public:
QGstreamerAudioOutput(QAudioOutput *parent);
~QGstreamerAudioOutput();
- bool setAudioOutput(const QAudioDevice &);
- QAudioDevice audioOutput() const;
-
- void setAudioDevice(const QAudioDevice &) override
- { setAudioOutput(device); }
+ void setAudioDevice(const QAudioDevice &) override;
void setVolume(float volume) override;
void setMuted(bool muted) override;
@@ -90,9 +86,6 @@ Q_SIGNALS:
void volumeChanged(int);
private:
- void prepareAudioOutputChange(const QGstPad &pad);
- bool changeAudioOutput();
-
QAudioDevice m_audioOutput;
// Gst elements
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
index f1aa64a5a..ec0a679bd 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer.cpp
@@ -116,11 +116,6 @@ float QGstreamerMediaPlayer::bufferProgress() const
return m_bufferProgress/100.;
}
-bool QGstreamerMediaPlayer::isSeekable() const
-{
- return true;
-}
-
QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const
{
return QMediaTimeRange();
@@ -128,16 +123,13 @@ QMediaTimeRange QGstreamerMediaPlayer::availablePlaybackRanges() const
qreal QGstreamerMediaPlayer::playbackRate() const
{
- return m_playbackRate;
+ return playerPipeline.playbackRate();
}
void QGstreamerMediaPlayer::setPlaybackRate(qreal rate)
{
- if (rate == m_playbackRate)
- return;
- m_playbackRate = rate;
- playerPipeline.seek(playerPipeline.position(), m_playbackRate);
- emit playbackRateChanged(rate);
+ if (playerPipeline.setPlaybackRate(rate))
+ playbackRateChanged(rate);
}
void QGstreamerMediaPlayer::setPosition(qint64 pos)
@@ -146,7 +138,7 @@ void QGstreamerMediaPlayer::setPosition(qint64 pos)
if (pos == currentPos)
return;
playerPipeline.finishStateChange();
- playerPipeline.seek(pos*1e6, m_playbackRate);
+ playerPipeline.setPosition(pos*1e6);
qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << pos << playerPipeline.position()/1e6;
if (mediaStatus() == QMediaPlayer::EndOfMedia)
mediaStatusChanged(QMediaPlayer::LoadedMedia);
@@ -160,12 +152,18 @@ void QGstreamerMediaPlayer::play()
*playerPipeline.inStoppedState() = false;
if (mediaStatus() == QMediaPlayer::EndOfMedia) {
- playerPipeline.seek(0, m_playbackRate);
+ playerPipeline.setPosition(0);
updatePosition();
}
qCDebug(qLcMediaPlayer) << "play().";
int ret = playerPipeline.setState(GST_STATE_PLAYING);
+ if (m_requiresSeekOnPlay) {
+ // Flushing the pipeline is required to get track changes
+ // immediately, when they happen while paused.
+ playerPipeline.flush();
+ m_requiresSeekOnPlay = false;
+ }
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the playing state.";
if (mediaStatus() == QMediaPlayer::LoadedMedia)
@@ -182,13 +180,13 @@ void QGstreamerMediaPlayer::pause()
positionUpdateTimer.stop();
if (*playerPipeline.inStoppedState()) {
*playerPipeline.inStoppedState() = false;
- playerPipeline.seek(playerPipeline.position(), m_playbackRate);
+ playerPipeline.flush();
}
int ret = playerPipeline.setState(GST_STATE_PAUSED);
if (ret == GST_STATE_CHANGE_FAILURE)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
if (mediaStatus() == QMediaPlayer::EndOfMedia) {
- playerPipeline.seek(0, m_playbackRate);
+ playerPipeline.setPosition(0);
mediaStatusChanged(QMediaPlayer::BufferedMedia);
}
updatePosition();
@@ -210,7 +208,7 @@ void QGstreamerMediaPlayer::stopOrEOS(bool eos)
if (!ret)
qCDebug(qLcMediaPlayer) << "Unable to set the pipeline to the stopped state.";
if (!eos)
- playerPipeline.seek(0, m_playbackRate);
+ playerPipeline.setPosition(0);
updatePosition();
emit stateChanged(QMediaPlayer::StoppedState);
mediaStatusChanged(eos ? QMediaPlayer::EndOfMedia : QMediaPlayer::LoadedMedia);
@@ -284,7 +282,7 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
case GST_STATE_VOID_PENDING:
case GST_STATE_NULL:
case GST_STATE_READY:
- setSeekable(false);
+ seekableChanged(false);
break;
case GST_STATE_PAUSED:
{
@@ -295,9 +293,6 @@ bool QGstreamerMediaPlayer::processBusMessage(const QGstreamerMessage &message)
parseStreamsAndMetadata();
- if (!qFuzzyCompare(m_playbackRate, qreal(1.0)))
- playerPipeline.seek(playerPipeline.position(), m_playbackRate);
-
emit tracksChanged();
mediaStatusChanged(QMediaPlayer::LoadedMedia);
}
@@ -393,26 +388,17 @@ void QGstreamerMediaPlayer::decoderPadAdded(const QGstElement &src, const QGstPa
qCDebug(qLcMediaPlayer) << " " << caps.toString();
TrackType streamType = NTrackTypes;
- QGstElement output;
if (type.startsWith("video/x-raw")) {
streamType = VideoStream;
- output = gstVideoOutput->gstElement();
} else if (type.startsWith("audio/x-raw")) {
streamType = AudioStream;
- if (gstAudioOutput)
- output = gstAudioOutput->gstElement();
} else if (type.startsWith("text/")) {
streamType = SubtitleStream;
} else {
qCWarning(qLcMediaPlayer) << "Ignoring unknown media stream:" << pad.name() << type;
return;
}
- if (!selectorIsConnected[streamType] && !output.isNull()) {
- playerPipeline.add(output);
- inputSelector[streamType].link(output);
- output.setState(GST_STATE_PAUSED);
- selectorIsConnected[streamType] = true;
- }
+ connectOutput(streamType);
QGstPad sinkPad = inputSelector[streamType].getRequestPad("sink_%u");
if (!pad.link(sinkPad))
@@ -453,38 +439,65 @@ void QGstreamerMediaPlayer::decoderPadRemoved(const QGstElement &src, const QGst
Q_ASSERT(m_streams[streamType].indexOf(peer) != -1);
m_streams[streamType].removeAll(peer);
- if (m_streams[streamType].size() == 0)
+ if (m_streams[streamType].size() == 0) {
removeOutput(TrackType(streamType));
+ if (streamType == AudioStream)
+ audioAvailableChanged(false);
+ else if (streamType == VideoStream)
+ videoAvailableChanged(false);
+ }
if (!prerolling)
- emit tracksChanged();
+ tracksChanged();
}
void QGstreamerMediaPlayer::removeAllOutputs()
{
for (int i = 0; i < NTrackTypes; ++i) {
removeOutput(TrackType(i));
- for (QGstPad pad : qAsConst(m_streams[i])) {
+ for (const QGstPad &pad : qAsConst(m_streams[i])) {
inputSelector[i].releaseRequestPad(pad);
}
m_streams[i].clear();
}
+ audioAvailableChanged(false);
+ videoAvailableChanged(false);
+}
+
+void QGstreamerMediaPlayer::connectOutput(TrackType t)
+{
+ if (selectorIsConnected[t])
+ return;
+
+ QGstElement e;
+ if (t == AudioStream)
+ e = gstAudioOutput->gstElement();
+ else if (t == VideoStream)
+ e = gstVideoOutput->gstElement();
+ if (!e.isNull()) {
+ qCDebug(qLcMediaPlayer) << "connecting output for track type" << t;
+ playerPipeline.add(e);
+ inputSelector[t].link(e);
+ e.setState(GST_STATE_PAUSED);
+ selectorIsConnected[t] = true;
+ }
}
void QGstreamerMediaPlayer::removeOutput(TrackType t)
{
- if (selectorIsConnected[t]) {
- QGstElement e;
- if (t == AudioStream)
- e = gstAudioOutput->gstElement();
- else if (t == VideoStream)
- e = gstVideoOutput->gstElement();
- if (!e.isNull()) {
- qCDebug(qLcMediaPlayer) << "removing output for track type" << t;
- e.setState(GST_STATE_NULL);
- playerPipeline.remove(e);
- selectorIsConnected[t] = false;
- }
+ if (!selectorIsConnected[t])
+ return;
+
+ QGstElement e;
+ if (t == AudioStream)
+ e = gstAudioOutput->gstElement();
+ else if (t == VideoStream)
+ e = gstVideoOutput->gstElement();
+ if (!e.isNull()) {
+ qCDebug(qLcMediaPlayer) << "removing output for track type" << t;
+ e.setState(GST_STATE_NULL);
+ playerPipeline.remove(e);
+ selectorIsConnected[t] = false;
}
}
@@ -496,6 +509,10 @@ void QGstreamerMediaPlayer::uridecodebinElementAddedCallback(GstElement */*uride
if (G_OBJECT_TYPE(child) == that->decodebinType) {
qCDebug(qLcMediaPlayer) << " -> setting post-stream-topology property";
c.set("post-stream-topology", true);
+ } else if (!qstrcmp(gst_element_get_name(child), "source")) {
+ GstBaseSrc *src = GST_BASE_SRC(child);
+ bool seekable = src && GST_BASE_SRC_GET_CLASS(src)->is_seekable(src);
+ that->seekableChanged(seekable);
}
}
@@ -519,6 +536,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
src = QGstElement();
decoder = QGstElement();
removeAllOutputs();
+ seekableChanged(false);
if (m_duration != 0) {
m_duration = 0;
@@ -546,6 +564,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
src.link(decoder);
m_appSrc->setup(m_stream);
+ seekableChanged(!stream->isSequential());
} else {
// use uridecodebin
decoder = QGstElement("uridecodebin", "uridecoder");
@@ -574,6 +593,7 @@ void QGstreamerMediaPlayer::setMedia(const QUrl &content, QIODevice *stream)
qCWarning(qLcMediaPlayer) << "Unable to set the pipeline to the paused state.";
}
+ playerPipeline.setPosition(0);
positionChanged(0);
}
@@ -581,8 +601,17 @@ void QGstreamerMediaPlayer::setAudioOutput(QPlatformAudioOutput *output)
{
if (gstAudioOutput == output)
return;
+ playerPipeline.beginConfig();
+ if (gstAudioOutput) {
+ removeOutput(AudioStream);
+ gstAudioOutput->setPipeline({});
+ }
gstAudioOutput = static_cast<QGstreamerAudioOutput *>(output);
- // ### Connect it if we're already running!
+ if (gstAudioOutput) {
+ gstAudioOutput->setPipeline(playerPipeline);
+ connectOutput(AudioStream);
+ }
+ playerPipeline.endConfig();
}
QMediaMetaData QGstreamerMediaPlayer::metaData() const
@@ -595,15 +624,6 @@ void QGstreamerMediaPlayer::setVideoSink(QVideoSink *sink)
gstVideoOutput->setVideoSink(sink);
}
-void QGstreamerMediaPlayer::setSeekable(bool seekable)
-{
- qCDebug(qLcMediaPlayer) << Q_FUNC_INFO << seekable;
- if (seekable == m_seekable)
- return;
- m_seekable = seekable;
- emit seekableChanged(m_seekable);
-}
-
static QGstStructure endOfChain(const QGstStructure &s)
{
QGstStructure e = s;
@@ -699,6 +719,8 @@ QMediaMetaData QGstreamerMediaPlayer::trackMetaData(QPlatformMediaPlayer::TrackT
GstTagList *tagList;
g_object_get(s.at(index).object(), "tags", &tagList, nullptr);
+ if (!tagList)
+ return {};
QMediaMetaData md = QGstreamerMetaData::fromGstTagList(tagList);
return md;
@@ -736,17 +758,10 @@ void QGstreamerMediaPlayer::setActiveTrack(QPlatformMediaPlayer::TrackType type,
return;
selector.set("active-pad", streams.at(index));
// seek to force an immediate change of the stream
- playerPipeline.seek(playerPipeline.position(), m_playbackRate);
-}
-
-bool QGstreamerMediaPlayer::isAudioAvailable() const
-{
- return !m_streams[AudioStream].isEmpty();
-}
-
-bool QGstreamerMediaPlayer::isVideoAvailable() const
-{
- return !m_streams[VideoStream].isEmpty();
+ if (playerPipeline.state() == GST_STATE_PLAYING)
+ playerPipeline.flush();
+ else
+ m_requiresSeekOnPlay = true;
}
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
index 2d939a0f8..f73bc3c78 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamermediaplayer_p.h
@@ -84,10 +84,6 @@ public:
float bufferProgress() const override;
- bool isAudioAvailable() const override;
- bool isVideoAvailable() const override;
-
- bool isSeekable() const override;
QMediaTimeRange availablePlaybackRanges() const override;
qreal playbackRate() const override;
@@ -125,8 +121,8 @@ private:
void decoderPadAdded(const QGstElement &src, const QGstPad &pad);
void decoderPadRemoved(const QGstElement &src, const QGstPad &pad);
static void uridecodebinElementAddedCallback(GstElement *uridecodebin, GstElement *child, QGstreamerMediaPlayer *that);
- void setSeekable(bool seekable);
void parseStreamsAndMetadata();
+ void connectOutput(TrackType t);
void removeOutput(TrackType t);
void removeAllOutputs();
@@ -140,8 +136,7 @@ private:
QIODevice *m_stream = nullptr;
bool prerolling = false;
- double m_playbackRate = 1.;
- bool m_seekable = false;
+ bool m_requiresSeekOnPlay = false;
qint64 m_duration = 0;
QTimer positionUpdateTimer;
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
index 626e8af53..7e77aa099 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput.cpp
@@ -67,27 +67,56 @@ QGstreamerVideoOutput::QGstreamerVideoOutput(QObject *parent)
QGstreamerVideoOutput::~QGstreamerVideoOutput()
{
+ gstVideoOutput.setStateSync(GST_STATE_NULL);
}
void QGstreamerVideoOutput::setVideoSink(QVideoSink *sink)
{
- auto *videoSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr;
- if (videoSink == m_videoWindow)
+ auto *gstVideoSink = sink ? static_cast<QGstreamerVideoSink *>(sink->platformVideoSink()) : nullptr;
+ if (gstVideoSink == m_videoWindow)
return;
if (m_videoWindow)
m_videoWindow->setPipeline({});
- m_videoWindow = videoSink;
+ m_videoWindow = gstVideoSink;
if (m_videoWindow)
m_videoWindow->setPipeline(gstPipeline);
- auto state = gstPipeline.state();
- if (state == GST_STATE_PLAYING)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
- sinkChanged();
- if (state == GST_STATE_PLAYING)
- gstPipeline.setState(GST_STATE_PLAYING);
+ QGstElement gstSink;
+ if (m_videoWindow) {
+ gstSink = m_videoWindow->gstSink();
+ isFakeSink = false;
+ } else {
+ gstSink = QGstElement("fakesink", "fakevideosink");
+ isFakeSink = true;
+ }
+
+ if (videoSink == gstSink)
+ return;
+
+ gstPipeline.beginConfig();
+ if (!videoSink.isNull()) {
+ videoSink.setStateSync(GST_STATE_NULL);
+ gstVideoOutput.remove(videoSink);
+ }
+ videoSink = gstSink;
+ gstVideoOutput.add(videoSink);
+
+ videoConvert.link(videoSink);
+ GstEvent *event = gst_event_new_reconfigure();
+ gst_element_send_event(videoSink.element(), event);
+ videoSink.setState(GST_STATE_PAUSED);
+
+ gstPipeline.endConfig();
+
+ qCDebug(qLcMediaVideoOutput) << "sinkChanged" << gstSink.name();
+
+ GST_DEBUG_BIN_TO_DOT_FILE(gstPipeline.bin(),
+ GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE |
+ GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES),
+ videoSink.name());
+
}
void QGstreamerVideoOutput::setPipeline(const QGstPipeline &pipeline)
@@ -103,9 +132,7 @@ void QGstreamerVideoOutput::linkSubtitleStream(QGstElement src)
if (src == subtitleSrc)
return;
- auto state = gstPipeline.state();
- if (state == GST_STATE_PLAYING)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
if (!subtitleSrc.isNull()) {
subtitleSrc.unlink(subTitleQueue);
@@ -119,7 +146,7 @@ void QGstreamerVideoOutput::linkSubtitleStream(QGstElement src)
qCDebug(qLcMediaVideoOutput) << "link subtitle stream 1 failed";
}
- gstPipeline.setState(state);
+ gstPipeline.endConfig();
}
void QGstreamerVideoOutput::setIsPreview()
@@ -133,40 +160,4 @@ void QGstreamerVideoOutput::setIsPreview()
videoQueue.set("max-size-time", 0);
}
-void QGstreamerVideoOutput::sinkChanged()
-{
- QGstElement gstSink;
- if (m_videoWindow) {
- gstSink = m_videoWindow->gstSink();
- isFakeSink = false;
- } else {
- gstSink = QGstElement("fakesink", "fakevideosink");
- isFakeSink = true;
- }
-
- if (videoSink == gstSink)
- return;
-
- if (!videoSink.isNull()) {
- videoSink.setStateSync(GST_STATE_NULL);
- gstVideoOutput.remove(videoSink);
- }
- videoSink = gstSink;
- gstVideoOutput.add(videoSink);
-
- videoConvert.link(videoSink);
- GstEvent *event = gst_event_new_reconfigure();
- gst_element_send_event(videoSink.element(), event);
- videoSink.setState(GST_STATE_PAUSED);
-
- gstPipeline.setState(GST_STATE_PLAYING);
- qCDebug(qLcMediaVideoOutput) << "sinkChanged" << gstSink.name();
-
- GST_DEBUG_BIN_TO_DOT_FILE(gstPipeline.bin(),
- GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE |
- GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES),
- videoSink.name());
-}
-
-
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
index 26fde3c4f..2cadb9cc3 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooutput_p.h
@@ -82,9 +82,6 @@ public:
void setIsPreview();
private:
- void sinkChanged();
-
-private:
QVideoSink *m_videoSink = nullptr;
QPointer<QGstreamerVideoSink> m_videoWindow;
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp
index 1c2048723..55299215b 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp
@@ -64,157 +64,8 @@ static constexpr ElementMap elementMap[] =
{ "xcb", "ximagesink" },
// wayland
- { "wayland", "vaapisink" }
-};
-
-class QGstreamerSinkProperties
-{
-public:
- virtual ~QGstreamerSinkProperties() = default;
-
- virtual void reset() = 0;
- virtual bool setBrightness(float brightness) = 0;
- virtual bool setContrast(float contrast) = 0;
- virtual bool setHue(float hue) = 0;
- virtual bool setSaturation(float saturation) = 0;
- virtual void setAspectRatioMode(Qt::AspectRatioMode mode) = 0;
-};
-
-class QXVImageSinkProperties : public QGstreamerSinkProperties
-{
-public:
- QXVImageSinkProperties(const QGstElement &sink)
- : m_videoSink(sink)
- {
- auto *klass = G_OBJECT_GET_CLASS(m_videoSink.object());
- m_hasForceAspectRatio = g_object_class_find_property(klass, "force-aspect-ratio");
- m_hasBrightness = g_object_class_find_property(klass, "brightness");
- m_hasContrast = g_object_class_find_property(klass, "contrast");
- m_hasHue = g_object_class_find_property(klass, "hue");
- m_hasSaturation = g_object_class_find_property(klass, "saturation");
- }
-
- void reset() override
- {
- setAspectRatioMode(m_aspectRatioMode);
- setBrightness(m_brightness);
- setContrast(m_contrast);
- setHue(m_hue);
- setSaturation(m_saturation);
- }
-
- bool setBrightness(float brightness) override
- {
- m_brightness = brightness;
- if (m_hasBrightness)
- m_videoSink.set("brightness", (int)brightness * 1000);
-
- return m_hasBrightness;
- }
-
- bool setContrast(float contrast) override
- {
- m_contrast = contrast;
- if (m_hasContrast)
- m_videoSink.set("contrast", (int)contrast * 1000);
-
- return m_hasContrast;
- }
-
- bool setHue(float hue) override
- {
- m_hue = hue;
- if (m_hasHue)
- m_videoSink.set("hue", (int)hue * 1000);
-
- return m_hasHue;
- }
-
- bool setSaturation(float saturation) override
- {
- m_saturation = saturation;
- if (m_hasSaturation)
- m_videoSink.set("saturation", (int)saturation * 1000);
-
- return m_hasSaturation;
- }
-
- void setAspectRatioMode(Qt::AspectRatioMode mode) override
- {
- m_aspectRatioMode = mode;
- if (m_hasForceAspectRatio)
- m_videoSink.set("force-aspect-ratio", (mode == Qt::KeepAspectRatio));
- }
-
-protected:
-
- QGstElement m_videoSink;
- bool m_hasForceAspectRatio = false;
- bool m_hasBrightness = false;
- bool m_hasContrast = false;
- bool m_hasHue = false;
- bool m_hasSaturation = false;
- Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
- float m_brightness = 0;
- float m_contrast = 0;
- float m_hue = 0;
- float m_saturation = 0;
-};
-
-class QVaapiSinkProperties : public QXVImageSinkProperties
-{
-public:
- QVaapiSinkProperties(QGstElement sink)
- : QXVImageSinkProperties(sink)
- {
- // Set default values.
- m_contrast = 1;
- m_saturation = 1;
- }
-
- bool setBrightness(float brightness) override
- {
- m_brightness = brightness;
- if (m_hasBrightness) {
- gfloat v = brightness;
- m_videoSink.set("brightness", v);
- }
-
- return m_hasBrightness;
- }
-
- bool setContrast(float contrast) override
- {
- m_contrast = contrast;
- if (m_hasContrast) {
- gfloat v = contrast + 1; // [-1, 1] -> [0,2]
- m_videoSink.set("contrast", v);
- }
-
- return m_hasContrast;
- }
-
- bool setHue(float hue) override
- {
- m_hue = hue;
- if (m_hasHue) {
- gfloat v = hue * 180; // [-1,1] -> [-180,180]
- m_videoSink.set("hue", v);
- }
-
- return m_hasHue;
- }
-
- bool setSaturation(float saturation) override
- {
- m_saturation = saturation;
- if (m_hasSaturation) {
- gfloat v = saturation + 1; // [-100,100] -> [0,2]
- m_videoSink.set("saturation", v);
- }
-
- return m_hasSaturation;
- }
+ { "wayland", "vaapisink" },
+ { "wayland", "waylandsink" }
};
static bool qt_gst_element_is_functioning(QGstElement element)
@@ -246,7 +97,7 @@ static QGstElement findBestVideoSink()
// We need a native window ID to use the GstVideoOverlay interface.
// Bail out if the Qt platform plugin in use cannot provide a sensible WId.
- if (platform != QLatin1String("xcb"))
+ if (platform != QLatin1String("xcb") && platform != QLatin1String("wayland"))
return {};
QGstElement choice;
@@ -269,10 +120,8 @@ static QGstElement findBestVideoSink()
}
gst_plugin_feature_list_free(list);
- if (choice.isNull()) {
- qWarning() << "Could not find a valid video sink";
- choice = QGstElement("fakesink", "fakesink");
- }
+ if (choice.isNull())
+ qWarning() << "Could not find a valid windowed video sink";
return choice;
}
@@ -293,7 +142,6 @@ QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent, const QByteArray
QGstreamerVideoOverlay::~QGstreamerVideoOverlay()
{
if (!m_videoSink.isNull()) {
- delete m_sinkProperties;
QGstPad pad = m_videoSink.staticPad("sink");
removeProbeFromPad(pad.pad());
}
@@ -314,10 +162,9 @@ void QGstreamerVideoOverlay::setVideoSink(QGstElement sink)
QGstPad pad = m_videoSink.staticPad("sink");
addProbeToPad(pad.pad());
- QByteArray sinkName(sink.name());
- bool isVaapi = sinkName.startsWith("vaapisink");
- delete m_sinkProperties;
- m_sinkProperties = isVaapi ? new QVaapiSinkProperties(sink) : new QXVImageSinkProperties(sink);
+ auto *klass = G_OBJECT_GET_CLASS(m_videoSink.object());
+ m_hasForceAspectRatio = g_object_class_find_property(klass, "force-aspect-ratio");
+ m_hasFullscreen = g_object_class_find_property(klass, "fullscreen");
}
QSize QGstreamerVideoOverlay::nativeVideoSize() const
@@ -330,11 +177,12 @@ void QGstreamerVideoOverlay::setWindowHandle(WId id)
m_windowId = id;
if (!m_videoSink.isNull() && GST_IS_VIDEO_OVERLAY(m_videoSink.object())) {
- gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink.object()), id);
applyRenderRect();
// Properties need to be reset when changing the winId.
- m_sinkProperties->reset();
+ setAspectRatioMode(m_aspectRatioMode);
+ setFullScreen(m_fullScreen);
+ applyRenderRect();
}
}
@@ -359,6 +207,11 @@ void QGstreamerVideoOverlay::applyRenderRect()
y = renderRect.y();
w = renderRect.width();
h = renderRect.height();
+ QSize scaledVideo = m_nativeVideoSize.scaled(w, h, m_aspectRatioMode);
+ x += (w - scaledVideo.width())/2;
+ y += (h - scaledVideo.height())/2;
+ w = scaledVideo.width();
+ h = scaledVideo.height();
}
if (!m_videoSink.isNull() && GST_IS_VIDEO_OVERLAY(m_videoSink.object()))
@@ -371,32 +224,30 @@ void QGstreamerVideoOverlay::probeCaps(GstCaps *caps)
if (size != m_nativeVideoSize) {
m_nativeVideoSize = size;
emit nativeVideoSizeChanged();
+ applyRenderRect();
}
}
void QGstreamerVideoOverlay::setAspectRatioMode(Qt::AspectRatioMode mode)
{
- m_sinkProperties->setAspectRatioMode(mode);
-}
-
-void QGstreamerVideoOverlay::setBrightness(float brightness)
-{
- m_sinkProperties->setBrightness(brightness);
-}
-
-void QGstreamerVideoOverlay::setContrast(float contrast)
-{
- m_sinkProperties->setContrast(contrast);
+ m_aspectRatioMode = mode;
+ if (m_hasForceAspectRatio)
+ m_videoSink.set("force-aspect-ratio", (mode == Qt::KeepAspectRatio));
}
-void QGstreamerVideoOverlay::setHue(float hue)
+void QGstreamerVideoOverlay::setFullScreen(bool fullscreen)
{
- m_sinkProperties->setHue(hue);
+ m_fullScreen = fullscreen;
+ if (m_hasFullscreen)
+ m_videoSink.set("fullscreen", fullscreen);
}
-void QGstreamerVideoOverlay::setSaturation(float saturation)
+bool QGstreamerVideoOverlay::processSyncMessage(const QGstreamerMessage &message)
{
- m_sinkProperties->setSaturation(saturation);
+ if (!gst_is_video_overlay_prepare_window_handle_message(message.rawMessage()))
+ return false;
+ gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink.object()), m_windowId);
+ return true;
}
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h
index 2d428c89b..bdc97e279 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h
@@ -61,6 +61,7 @@ QT_BEGIN_NAMESPACE
class QGstreamerSinkProperties;
class Q_MULTIMEDIA_EXPORT QGstreamerVideoOverlay
: public QObject
+ , public QGstreamerSyncMessageFilter
, private QGstreamerBufferProbe
{
Q_OBJECT
@@ -75,13 +76,12 @@ public:
void setWindowHandle(WId id);
void setRenderRectangle(const QRect &rect);
- Qt::AspectRatioMode aspectRatioMode() const;
void setAspectRatioMode(Qt::AspectRatioMode mode);
+ void setFullScreen(bool fullscreen);
- void setBrightness(float brightness);
- void setContrast(float contrast);
- void setHue(float hue);
- void setSaturation(float saturation);
+ bool processSyncMessage(const QGstreamerMessage &message) override;
+
+ bool isNull() const { return m_videoSink.isNull(); }
Q_SIGNALS:
void nativeVideoSizeChanged();
@@ -94,7 +94,11 @@ private:
QGstElement m_videoSink;
QSize m_nativeVideoSize;
- QGstreamerSinkProperties *m_sinkProperties = nullptr;
+ bool m_hasForceAspectRatio = false;
+ bool m_hasFullscreen = false;
+ Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
+ bool m_fullScreen = false;
+
WId m_windowId = 0;
QRect renderRect;
};
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideosink.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideosink.cpp
index d4b8f4616..17aeec957 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideosink.cpp
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideosink.cpp
@@ -52,7 +52,18 @@ QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent)
: QPlatformVideoSink(parent)
{
sinkBin = QGstBin("videoSinkBin");
- gstPreprocess = QGstElement("identity");
+ // This is a hack for some iMX platforms. Thos require the use of a special video
+ // conversion element in the pipeline before the video sink, as they unfortunately
+ // output some proprietary format from the decoder even though it's marked as
+ // a regular supported video/x-raw format.
+ //
+ // To fix this, simply insert the element into the pipeline if it's available. Otherwise
+ // we simply use an identity element.
+ auto imxVideoConvert = QGstElement("imxvideoconvert_g2d");
+ if (!imxVideoConvert.isNull())
+ gstPreprocess = imxVideoConvert;
+ else
+ gstPreprocess = QGstElement("identity");
sinkBin.add(gstPreprocess);
sinkBin.addGhostPad(gstPreprocess, "sink");
createOverlay();
@@ -61,6 +72,7 @@ QGstreamerVideoSink::QGstreamerVideoSink(QVideoSink *parent)
QGstreamerVideoSink::~QGstreamerVideoSink()
{
+ setPipeline(QGstPipeline());
delete m_videoOverlay;
delete m_videoRenderer;
}
@@ -71,6 +83,17 @@ QGstElement QGstreamerVideoSink::gstSink()
return sinkBin;
}
+void QGstreamerVideoSink::setPipeline(QGstPipeline pipeline)
+{
+ if (pipeline != gstPipeline) {
+ if (!gstPipeline.isNull())
+ gstPipeline.removeMessageFilter(m_videoOverlay);
+ }
+ gstPipeline = pipeline;
+ if (!gstPipeline.isNull())
+ gstPipeline.installMessageFilter(m_videoOverlay);
+}
+
void QGstreamerVideoSink::setWinId(WId id)
{
if (m_windowId == id)
@@ -100,26 +123,6 @@ void QGstreamerVideoSink::setAspectRatioMode(Qt::AspectRatioMode mode)
m_videoOverlay->setAspectRatioMode(mode);
}
-void QGstreamerVideoSink::setBrightness(float brightness)
-{
- m_videoOverlay->setBrightness(brightness);
-}
-
-void QGstreamerVideoSink::setContrast(float contrast)
-{
- m_videoOverlay->setContrast(contrast);
-}
-
-void QGstreamerVideoSink::setHue(float hue)
-{
- m_videoOverlay->setHue(hue);
-}
-
-void QGstreamerVideoSink::setSaturation(float saturation)
-{
- m_videoOverlay->setSaturation(saturation);
-}
-
void QGstreamerVideoSink::setFullScreen(bool fullScreen)
{
if (fullScreen == m_fullScreen)
@@ -127,6 +130,7 @@ void QGstreamerVideoSink::setFullScreen(bool fullScreen)
m_fullScreen = fullScreen;
if (!m_windowId)
updateSinkElement();
+ m_videoOverlay->setFullScreen(fullScreen);
}
QSize QGstreamerVideoSink::nativeSize() const
@@ -150,23 +154,27 @@ void QGstreamerVideoSink::createRenderer()
void QGstreamerVideoSink::updateSinkElement()
{
- auto state = gstPipeline.isNull() ? GST_STATE_NULL : gstPipeline.state();
- if (state == GST_STATE_PLAYING)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ QGstElement newSink;
+ if (!m_videoOverlay->isNull() && (m_fullScreen || m_windowId)) {
+ newSink = m_videoOverlay->videoSink();
+ } else {
+ newSink = m_videoRenderer->gstVideoSink();
+ }
+
+ if (newSink == gstVideoSink)
+ return;
+
+ gstPipeline.beginConfig();
if (!gstVideoSink.isNull()) {
gstVideoSink.setStateSync(GST_STATE_NULL);
sinkBin.remove(gstVideoSink);
}
- if (m_fullScreen || m_windowId)
- gstVideoSink = m_videoOverlay->videoSink();
- else
- gstVideoSink = m_videoRenderer->gstVideoSink();
+ gstVideoSink = newSink;
sinkBin.add(gstVideoSink);
gstPreprocess.link(gstVideoSink);
gstVideoSink.setState(GST_STATE_PAUSED);
- if (state == GST_STATE_PLAYING)
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
diff --git a/src/multimedia/platform/gstreamer/common/qgstreamervideosink_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideosink_p.h
index 8bffe3f7c..2f40dab77 100644
--- a/src/multimedia/platform/gstreamer/common/qgstreamervideosink_p.h
+++ b/src/multimedia/platform/gstreamer/common/qgstreamervideosink_p.h
@@ -82,16 +82,11 @@ public:
void setAspectRatioMode(Qt::AspectRatioMode mode) override;
- void setBrightness(float brightness) override;
- void setContrast(float contrast) override;
- void setHue(float hue) override;
- void setSaturation(float saturation) override;
-
QGstElement gstSink();
bool isReady() const { return m_windowId != 0; }
- void setPipeline(QGstPipeline pipeline) { gstPipeline = pipeline; }
+ void setPipeline(QGstPipeline pipeline);
private:
void createOverlay();
diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp
index 99a44b70e..02a2214cf 100644
--- a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp
+++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercamera.cpp
@@ -101,10 +101,7 @@ void QGstreamerCamera::setCamera(const QCameraDevice &camera)
m_cameraDevice = camera;
- bool havePipeline = !gstPipeline.isNull();
-
- if (havePipeline)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
Q_ASSERT(!gstCamera.isNull());
@@ -128,10 +125,8 @@ void QGstreamerCamera::setCamera(const QCameraDevice &camera)
gstCamera.setState(GST_STATE_PAUSED);
- if (havePipeline) {
- gstPipeline.dumpGraph("setCamera");
- gstPipeline.setState(GST_STATE_PLAYING);
- }
+ gstPipeline.endConfig();
+ gstPipeline.dumpGraph("setCamera");
updateCameraProperties();
}
@@ -170,13 +165,9 @@ bool QGstreamerCamera::setCameraFormat(const QCameraFormat &format)
{
if (!m_cameraDevice.videoFormats().contains(format))
return false;
- bool havePipeline = !gstPipeline.isNull();
- auto state = havePipeline ? gstPipeline.state() : GST_STATE_NULL;
- if (havePipeline)
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
setCameraFormatInternal(format);
- if (havePipeline)
- gstPipeline.setStateSync(state);
+ gstPipeline.endConfig();
return true;
}
diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp
index 4570f1d69..17404de7c 100644
--- a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp
+++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp
@@ -257,8 +257,10 @@ void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *ses
pendingImages.clear();
passImage = false;
cameraActive = false;
+ gstPipeline.beginConfig();
bin.setStateSync(GST_STATE_NULL);
gstPipeline.remove(bin);
+ gstPipeline.endConfig();
gstPipeline = {};
}
@@ -267,8 +269,10 @@ void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *ses
return;
gstPipeline = captureSession->pipeline();
+ gstPipeline.beginConfig();
gstPipeline.add(bin);
bin.setStateSync(GST_STATE_READY);
+ gstPipeline.endConfig();
connect(m_session, &QPlatformMediaCaptureSession::cameraChanged, this, &QGstreamerImageCapture::onCameraChanged);
onCameraChanged();
}
@@ -342,13 +346,13 @@ void QGstreamerImageCapture::unlink()
return;
if (gstPipeline.isNull())
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
videoSrcPad.unlinkPeer();
m_session->releaseVideoPad(videoSrcPad);
videoSrcPad = {};
bin.setStateSync(GST_STATE_READY);
bin.lockState(true);
- gstPipeline.setStateSync(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
void QGstreamerImageCapture::link()
@@ -357,12 +361,12 @@ void QGstreamerImageCapture::link()
return;
if (!bin.staticPad("sink").peer().isNull() || gstPipeline.isNull())
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
videoSrcPad = m_session->getVideoPad();
videoSrcPad.link(bin.staticPad("sink"));
bin.lockState(false);
bin.setState(GST_STATE_PAUSED);
- gstPipeline.setStateSync(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
QImageEncoderSettings QGstreamerImageCapture::imageSettings() const
diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacapture.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacapture.cpp
index 237b2a505..b5c3249fe 100644
--- a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacapture.cpp
+++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacapture.cpp
@@ -84,7 +84,7 @@ void QGstreamerMediaCapture::setCamera(QPlatformCamera *camera)
if (gstCamera == control)
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
if (gstVideoTee.isNull()) {
gstVideoTee = QGstElement("tee", "videotee");
@@ -111,7 +111,7 @@ void QGstreamerMediaCapture::setCamera(QPlatformCamera *camera)
camera.setState(GST_STATE_PAUSED);
}
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
emit cameraChanged();
gstPipeline.dumpGraph("camera");
@@ -128,7 +128,7 @@ void QGstreamerMediaCapture::setImageCapture(QPlatformImageCapture *imageCapture
if (m_imageCapture == control)
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
if (m_imageCapture)
m_imageCapture->setCaptureSession(nullptr);
@@ -137,7 +137,7 @@ void QGstreamerMediaCapture::setImageCapture(QPlatformImageCapture *imageCapture
if (m_imageCapture)
m_imageCapture->setCaptureSession(this);
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
emit imageCaptureChanged();
}
@@ -168,7 +168,7 @@ void QGstreamerMediaCapture::setAudioInput(QPlatformAudioInput *input)
{
if (gstAudioInput == input)
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
if (gstAudioInput) {
gstAudioOutput->setPipeline({});
gstAudioInput = nullptr;
@@ -198,7 +198,7 @@ void QGstreamerMediaCapture::setAudioInput(QPlatformAudioInput *input)
gstAudioOutputPad.link(gstAudioOutput->gstElement().staticPad("sink"));
}
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
void QGstreamerMediaCapture::setVideoPreview(QVideoSink *sink)
@@ -210,7 +210,7 @@ void QGstreamerMediaCapture::setAudioOutput(QPlatformAudioOutput *output)
{
if (gstAudioOutput == output)
return;
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
if (gstAudioOutput) {
gstAudioOutput->setPipeline({});
@@ -234,7 +234,7 @@ void QGstreamerMediaCapture::setAudioOutput(QPlatformAudioOutput *output)
gstAudioOutputPad.link(gstAudioOutput->gstElement().staticPad("sink"));
}
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediaencoder.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediaencoder.cpp
index 210e6d3b6..71c9d3a39 100644
--- a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediaencoder.cpp
+++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediaencoder.cpp
@@ -251,8 +251,8 @@ void QGstreamerMediaEncoder::record(const QMediaEncoderSettings &)
Q_ASSERT(!actualSink.isEmpty());
gstFileSink.set("location", QFile::encodeName(actualSink.toLocalFile()).constData());
- //auto state = gstPipeline.state();
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
+
gstEncoder.lockState(false);
gstFileSink.lockState(false);
@@ -272,7 +272,8 @@ void QGstreamerMediaEncoder::record(const QMediaEncoderSettings &)
gstEncoder.setState(GST_STATE_PAUSED);
gstFileSink.setState(GST_STATE_PAUSED);
- gstPipeline.setState(GST_STATE_PLAYING);
+
+ gstPipeline.endConfig();
m_duration.start();
heartbeat.start();
@@ -308,7 +309,8 @@ void QGstreamerMediaEncoder::stop()
qCDebug(qLcMediaEncoder) << "stop";
heartbeat.stop();
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
+
if (!audioSrcPad.isNull()) {
audioSrcPad.unlinkPeer();
m_session->releaseAudioPad(audioSrcPad);
@@ -320,7 +322,8 @@ void QGstreamerMediaEncoder::stop()
videoSrcPad = {};
}
- gstPipeline.setState(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
+
//with live sources it's necessary to send EOS even to pipeline
//before going to STOPPED state
qCDebug(qLcMediaEncoder) << ">>>>>>>>>>>>> sending EOS";
@@ -336,12 +339,12 @@ void QGstreamerMediaEncoder::finalize()
qCDebug(qLcMediaEncoder) << "finalize";
// The filesink can only be used once, replace it with a new one
- gstPipeline.setStateSync(GST_STATE_PAUSED);
+ gstPipeline.beginConfig();
gstEncoder.lockState(true);
gstFileSink.lockState(true);
gstEncoder.setState(GST_STATE_NULL);
gstFileSink.setState(GST_STATE_NULL);
- gstPipeline.setStateSync(GST_STATE_PLAYING);
+ gstPipeline.endConfig();
}
void QGstreamerMediaEncoder::applySettings(const QMediaEncoderSettings &settings)
diff --git a/src/multimedia/platform/qplatformmediaplayer_p.h b/src/multimedia/platform/qplatformmediaplayer_p.h
index dc2be4db3..cc9297229 100644
--- a/src/multimedia/platform/qplatformmediaplayer_p.h
+++ b/src/multimedia/platform/qplatformmediaplayer_p.h
@@ -77,8 +77,8 @@ public:
virtual float bufferProgress() const = 0;
- virtual bool isAudioAvailable() const = 0;
- virtual bool isVideoAvailable() const = 0;
+ virtual bool isAudioAvailable() const { return m_audioAvailable; }
+ virtual bool isVideoAvailable() const { return m_videoAvailable; }
virtual bool isSeekable() const { return m_seekable; }
@@ -113,8 +113,18 @@ public:
void durationChanged(qint64 duration) { player->durationChanged(duration); }
void positionChanged(qint64 position) { player->positionChanged(position); }
- void audioAvailableChanged(bool audioAvailable) { player->hasAudioChanged(audioAvailable); }
- void videoAvailableChanged(bool videoAvailable) { player->hasVideoChanged(videoAvailable); }
+ void audioAvailableChanged(bool audioAvailable) {
+ if (m_audioAvailable == audioAvailable)
+ return;
+ m_audioAvailable = audioAvailable;
+ player->hasAudioChanged(audioAvailable);
+ }
+ void videoAvailableChanged(bool videoAvailable) {
+ if (m_videoAvailable == videoAvailable)
+ return;
+ m_videoAvailable = videoAvailable;
+ player->hasVideoChanged(videoAvailable);
+ }
void seekableChanged(bool seekable) {
if (m_seekable == seekable)
return;
@@ -139,7 +149,9 @@ private:
QMediaPlayer *player = nullptr;
QMediaPlayer::MediaStatus m_status = QMediaPlayer::NoMedia;
QMediaPlayer::PlaybackState m_state = QMediaPlayer::StoppedState;
- bool m_seekable = true;
+ bool m_seekable = false;
+ bool m_videoAvailable = false;
+ bool m_audioAvailable = false;
};
QT_END_NAMESPACE
diff --git a/src/multimedia/platform/windows/common/mfmetadata.cpp b/src/multimedia/platform/windows/common/mfmetadata.cpp
index a75fad7d4..ebc884759 100644
--- a/src/multimedia/platform/windows/common/mfmetadata.cpp
+++ b/src/multimedia/platform/windows/common/mfmetadata.cpp
@@ -40,12 +40,14 @@
#include <qmediametadata.h>
#include <qdatetime.h>
#include <qimage.h>
+#include <quuid.h>
#include <mfapi.h>
#include <mfidl.h>
#include <propvarutil.h>
#include <propkey.h>
+#include "qwindowsmultimediautils_p.h"
#include "mfmetadata_p.h"
//#define DEBUG_MEDIAFOUNDATION
@@ -208,10 +210,10 @@ static QVariant metaDataValue(IPropertyStore *content, const PROPERTYKEY &key)
} else if (key == PKEY_Media_Duration) {
// duration is provided in 100-nanosecond units, convert to milliseconds
value = (value.toLongLong() + 10000) / 10000;
- } else if (key == PKEY_Audio_Format || key == PKEY_Video_Compression) {
- GUID guid;
- if (SUCCEEDED(CLSIDFromString((const WCHAR*)value.toString().utf16(), &guid)))
- value = nameForGUID(guid);
+ } else if (key == PKEY_Video_Compression) {
+ value = int(QWindowsMultimediaUtils::codecForVideoFormat(value.toUuid()));
+ } else if (key == PKEY_Audio_Format) {
+ value = int(QWindowsMultimediaUtils::codecForAudioFormat(value.toUuid()));
} else if (key == PKEY_Video_FrameHeight /*Resolution*/) {
QSize res;
res.setHeight(value.toUInt());
diff --git a/src/multimedia/platform/windows/common/qwindowsmultimediautils.cpp b/src/multimedia/platform/windows/common/qwindowsmultimediautils.cpp
index 9e6ee9343..a9f8c1dfd 100644
--- a/src/multimedia/platform/windows/common/qwindowsmultimediautils.cpp
+++ b/src/multimedia/platform/windows/common/qwindowsmultimediautils.cpp
@@ -102,6 +102,36 @@ GUID QWindowsMultimediaUtils::videoFormatForCodec(QMediaFormat::VideoCodec codec
}
}
+QMediaFormat::VideoCodec QWindowsMultimediaUtils::codecForVideoFormat(GUID format)
+{
+ if (format == MFVideoFormat_MPG1)
+ return QMediaFormat::VideoCodec::MPEG1;
+ if (format == MFVideoFormat_MPEG2)
+ return QMediaFormat::VideoCodec::MPEG2;
+ if (format == MFVideoFormat_MP4V
+ || format == MFVideoFormat_M4S2
+ || format == MFVideoFormat_MP4S
+ || format == MFVideoFormat_MP43)
+ return QMediaFormat::VideoCodec::MPEG4;
+ if (format == MFVideoFormat_H264)
+ return QMediaFormat::VideoCodec::H264;
+ if (format == MFVideoFormat_H265)
+ return QMediaFormat::VideoCodec::H265;
+ if (format == MFVideoFormat_VP80)
+ return QMediaFormat::VideoCodec::VP8;
+ if (format == MFVideoFormat_VP90)
+ return QMediaFormat::VideoCodec::VP9;
+ if (format == MFVideoFormat_AV1)
+ return QMediaFormat::VideoCodec::AV1;
+ if (format == MFVideoFormat_WMV1
+ || format == MFVideoFormat_WMV2
+ || format == MFVideoFormat_WMV3)
+ return QMediaFormat::VideoCodec::WMV;
+ if (format == MFVideoFormat_MJPG)
+ return QMediaFormat::VideoCodec::MotionJPEG;
+ return QMediaFormat::VideoCodec::Unspecified;
+}
+
GUID QWindowsMultimediaUtils::audioFormatForCodec(QMediaFormat::AudioCodec codec)
{
switch (codec) {
@@ -130,6 +160,33 @@ GUID QWindowsMultimediaUtils::audioFormatForCodec(QMediaFormat::AudioCodec codec
}
}
+QMediaFormat::AudioCodec QWindowsMultimediaUtils::codecForAudioFormat(GUID format)
+{
+ if (format == MFAudioFormat_MP3)
+ return QMediaFormat::AudioCodec::MP3;
+ if (format == MFAudioFormat_AAC)
+ return QMediaFormat::AudioCodec::AAC;
+ if (format == MFAudioFormat_ALAC)
+ return QMediaFormat::AudioCodec::ALAC;
+ if (format == MFAudioFormat_FLAC)
+ return QMediaFormat::AudioCodec::FLAC;
+ if (format == MFAudioFormat_Vorbis)
+ return QMediaFormat::AudioCodec::Vorbis;
+ if (format == MFAudioFormat_PCM)
+ return QMediaFormat::AudioCodec::Wave;
+ if (format == MFAudioFormat_Opus)
+ return QMediaFormat::AudioCodec::Opus;
+ if (format == MFAudioFormat_Dolby_AC3)
+ return QMediaFormat::AudioCodec::AC3;
+ if (format == MFAudioFormat_Dolby_DDPlus)
+ return QMediaFormat::AudioCodec::EAC3;
+ if (format == MFAudioFormat_WMAudioV8
+ || format == MFAudioFormat_WMAudioV9
+ || format == MFAudioFormat_WMAudio_Lossless)
+ return QMediaFormat::AudioCodec::WMA;
+ return QMediaFormat::AudioCodec::Unspecified;
+}
+
GUID QWindowsMultimediaUtils::containerForVideoFileFormat(QMediaFormat::FileFormat format)
{
switch (format) {
diff --git a/src/multimedia/platform/windows/common/qwindowsmultimediautils_p.h b/src/multimedia/platform/windows/common/qwindowsmultimediautils_p.h
index b19f5fd2e..100059cf0 100644
--- a/src/multimedia/platform/windows/common/qwindowsmultimediautils_p.h
+++ b/src/multimedia/platform/windows/common/qwindowsmultimediautils_p.h
@@ -64,8 +64,12 @@ namespace QWindowsMultimediaUtils {
GUID videoFormatForCodec(QMediaFormat::VideoCodec codec);
+ QMediaFormat::VideoCodec codecForVideoFormat(GUID format);
+
GUID audioFormatForCodec(QMediaFormat::AudioCodec codec);
+ QMediaFormat::AudioCodec codecForAudioFormat(GUID format);
+
GUID containerForVideoFileFormat(QMediaFormat::FileFormat format);
GUID containerForAudioFileFormat(QMediaFormat::FileFormat format);
diff --git a/src/multimedia/platform/windows/player/mfplayercontrol.cpp b/src/multimedia/platform/windows/player/mfplayercontrol.cpp
index 90b6de66f..b114e3264 100644
--- a/src/multimedia/platform/windows/player/mfplayercontrol.cpp
+++ b/src/multimedia/platform/windows/player/mfplayercontrol.cpp
@@ -175,6 +175,11 @@ void MFPlayerControl::handleStatusChanged()
refreshState();
}
+void MFPlayerControl::handleTracksChanged()
+{
+ tracksChanged();
+}
+
void MFPlayerControl::handleVideoAvailable()
{
if (m_videoAvailable)
@@ -300,3 +305,24 @@ void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString&
stop();
emit error(int(errorCode), errorString);
}
+
+void MFPlayerControl::setActiveTrack(TrackType type, int index)
+{
+ m_session->setActiveTrack(type, index);
+}
+
+int MFPlayerControl::activeTrack(TrackType type)
+{
+ return m_session->activeTrack(type);
+}
+
+int MFPlayerControl::trackCount(TrackType type)
+{
+ return m_session->trackCount(type);
+}
+
+QMediaMetaData MFPlayerControl::trackMetaData(TrackType type, int trackNumber)
+{
+ return m_session->trackMetaData(type, trackNumber);
+}
+
diff --git a/src/multimedia/platform/windows/player/mfplayercontrol_p.h b/src/multimedia/platform/windows/player/mfplayercontrol_p.h
index 995181626..e8467ab8d 100644
--- a/src/multimedia/platform/windows/player/mfplayercontrol_p.h
+++ b/src/multimedia/platform/windows/player/mfplayercontrol_p.h
@@ -103,7 +103,13 @@ public:
void setVideoSink(QVideoSink *sink) override;
+ void setActiveTrack(TrackType type, int index) override;
+ int activeTrack(TrackType type) override;
+ int trackCount(TrackType type) override;
+ QMediaMetaData trackMetaData(TrackType type, int trackNumber) override;
+
void handleStatusChanged();
+ void handleTracksChanged();
void handleVideoAvailable();
void handleAudioAvailable();
void handleDurationUpdate(qint64 duration);
diff --git a/src/multimedia/platform/windows/player/mfplayersession.cpp b/src/multimedia/platform/windows/player/mfplayersession.cpp
index 619f7b17a..3d01050da 100644
--- a/src/multimedia/platform/windows/player/mfplayersession.cpp
+++ b/src/multimedia/platform/windows/player/mfplayersession.cpp
@@ -239,6 +239,7 @@ void MFPlayerSession::handleMediaSourceReady()
//convert from 100 nanosecond to milisecond
emit durationUpdate(qint64(m_duration / 10000));
setupPlaybackTopology(mediaSource, sourcePD);
+ tracksChanged();
sourcePD->Release();
} else {
changeStatus(QMediaPlayer::InvalidMedia);
@@ -246,26 +247,49 @@ void MFPlayerSession::handleMediaSourceReady()
}
}
-MFPlayerSession::MediaType MFPlayerSession::getStreamType(IMFStreamDescriptor *stream) const
+bool MFPlayerSession::getStreamInfo(IMFStreamDescriptor *stream,
+ MFPlayerSession::MediaType *type,
+ QString *name,
+ QString *language) const
{
- if (!stream)
- return Unknown;
-
- struct SafeRelease {
- IMFMediaTypeHandler *ptr = nullptr;
- ~SafeRelease() { if (ptr) ptr->Release(); }
- } typeHandler;
- if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler.ptr))) {
+ if (!stream || !type || !name || !language)
+ return false;
+
+ *type = Unknown;
+ *name = QString();
+ *language = QString();
+
+ IMFMediaTypeHandler *typeHandler = nullptr;
+
+ if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler))) {
+
+ UINT32 len = 0;
+ if (SUCCEEDED(stream->GetStringLength(MF_SD_STREAM_NAME, &len)) && len > 0) {
+ WCHAR *wstr = new WCHAR[len+1];
+ if (SUCCEEDED(stream->GetString(MF_SD_STREAM_NAME, wstr, len+1, &len))) {
+ *name = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
+ }
+ delete []wstr;
+ }
+ if (SUCCEEDED(stream->GetStringLength(MF_SD_LANGUAGE, &len)) && len > 0) {
+ WCHAR *wstr = new WCHAR[len+1];
+ if (SUCCEEDED(stream->GetString(MF_SD_LANGUAGE, wstr, len+1, &len))) {
+ *language = QString::fromUtf16(reinterpret_cast<const char16_t *>(wstr));
+ }
+ delete []wstr;
+ }
+
GUID guidMajorType;
- if (SUCCEEDED(typeHandler.ptr->GetMajorType(&guidMajorType))) {
+ if (SUCCEEDED(typeHandler->GetMajorType(&guidMajorType))) {
if (guidMajorType == MFMediaType_Audio)
- return Audio;
+ *type = Audio;
else if (guidMajorType == MFMediaType_Video)
- return Video;
+ *type = Video;
}
+ typeHandler->Release();
}
- return Unknown;
+ return *type != Unknown;
}
void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD)
@@ -288,64 +312,76 @@ void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentat
return;
}
- // Remember output node id for a first video stream
- TOPOID outputNodeId = -1;
-
// For each stream, create the topology nodes and add them to the topology.
DWORD succeededCount = 0;
- for (DWORD i = 0; i < cSourceStreams; i++)
- {
- BOOL fSelected = FALSE;
+ for (DWORD i = 0; i < cSourceStreams; i++) {
+ BOOL selected = FALSE;
bool streamAdded = false;
IMFStreamDescriptor *streamDesc = NULL;
- HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &fSelected, &streamDesc);
+ HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &selected, &streamDesc);
if (SUCCEEDED(hr)) {
// The media might have multiple audio and video streams,
// only use one of each kind, and only if it is selected by default.
- MediaType mediaType = getStreamType(streamDesc);
- if (mediaType != Unknown
- && ((m_mediaTypes & mediaType) == 0) // Check if this type isn't already added
- && fSelected) {
-
- IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc);
- if (sourceNode) {
- IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0);
- if (outputNode) {
- bool connected = false;
- if (mediaType == Audio) {
- if (!m_audioSampleGrabberNode)
- connected = setupAudioSampleGrabber(topology, sourceNode, outputNode);
- } else if (mediaType == Video && outputNodeId == -1) {
- // Remember video output node ID.
- outputNode->GetTopoNodeID(&outputNodeId);
- }
-
- if (!connected)
- hr = sourceNode->ConnectOutput(0, outputNode, 0);
-
- if (FAILED(hr)) {
- emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false);
- } else {
- streamAdded = true;
- succeededCount++;
- m_mediaTypes |= mediaType;
- switch (mediaType) {
- case Audio:
- emit audioAvailable();
- break;
- case Video:
- emit videoAvailable();
- break;
+ MediaType mediaType = Unknown;
+ QString streamName;
+ QString streamLanguage;
+
+ if (getStreamInfo(streamDesc, &mediaType, &streamName, &streamLanguage)) {
+
+ QPlatformMediaPlayer::TrackType trackType = (mediaType == Audio) ?
+ QPlatformMediaPlayer::AudioStream : QPlatformMediaPlayer::VideoStream;
+
+ QLocale::Language lang = streamLanguage.isEmpty() ?
+ QLocale::Language::AnyLanguage : QLocale(streamLanguage).language();
+
+ QMediaMetaData metaData;
+ metaData.insert(QMediaMetaData::Title, streamName);
+ metaData.insert(QMediaMetaData::Language, lang);
+
+ m_trackInfo[trackType].metaData.append(metaData);
+ m_trackInfo[trackType].nativeIndexes.append(i);
+
+ if (((m_mediaTypes & mediaType) == 0) && selected) { // Check if this type isn't already added
+ IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc);
+ if (sourceNode) {
+ IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0);
+ if (outputNode) {
+ bool connected = false;
+ if (mediaType == Audio) {
+ if (!m_audioSampleGrabberNode)
+ connected = setupAudioSampleGrabber(topology, sourceNode, outputNode);
}
+ sourceNode->GetTopoNodeID(&m_trackInfo[trackType].sourceNodeId);
+ outputNode->GetTopoNodeID(&m_trackInfo[trackType].outputNodeId);
+
+ if (!connected)
+ hr = sourceNode->ConnectOutput(0, outputNode, 0);
+
+ if (FAILED(hr)) {
+ emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false);
+ } else {
+ m_trackInfo[trackType].currentIndex = m_trackInfo[trackType].nativeIndexes.count() - 1;
+ streamAdded = true;
+ succeededCount++;
+ m_mediaTypes |= mediaType;
+ switch (mediaType) {
+ case Audio:
+ emit audioAvailable();
+ break;
+ case Video:
+ emit videoAvailable();
+ break;
+ }
+ }
+ outputNode->Release();
}
- outputNode->Release();
+ sourceNode->Release();
}
- sourceNode->Release();
}
}
- if (fSelected && !streamAdded)
+ if (selected && !streamAdded)
sourcePD->DeselectStream(i);
streamDesc->Release();
@@ -356,12 +392,13 @@ void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentat
changeStatus(QMediaPlayer::InvalidMedia);
emit error(QMediaPlayer::ResourceError, tr("Unable to play."), true);
} else {
- if (outputNodeId != -1) {
- topology = insertMFT(topology, outputNodeId);
- }
+ if (m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId != -1)
+ topology = insertMFT(topology, m_trackInfo[QPlatformMediaPlayer::VideoStream].outputNodeId);
hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology);
- if (FAILED(hr)) {
+ if (SUCCEEDED(hr)) {
+ m_updatingTopology = true;
+ } else {
changeStatus(QMediaPlayer::InvalidMedia);
emit error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true);
}
@@ -982,6 +1019,7 @@ void MFPlayerSession::stop(bool immediate)
scrub(false);
if (SUCCEEDED(m_session->Stop())) {
+
m_state.setCommand(CmdStop);
m_pendingState = CmdPending;
if (m_status != QMediaPlayer::EndOfMedia) {
@@ -1011,6 +1049,12 @@ void MFPlayerSession::start()
if (m_scrubbing)
scrub(false);
+ if (m_restorePosition >= 0) {
+ m_position = m_restorePosition;
+ if (!m_updatingTopology)
+ m_restorePosition = -1;
+ }
+
PROPVARIANT varStart;
InitPropVariantFromInt64(m_position, &varStart);
@@ -1586,6 +1630,7 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent)
updatePendingCommands(CmdStop);
break;
case MESessionPaused:
+ m_position = position() * 10000;
updatePendingCommands(CmdPause);
break;
case MEReconnectStart:
@@ -1690,6 +1735,9 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent)
if (SUCCEEDED(MFGetService(m_session, MR_STREAM_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl))))
setVolumeInternal(m_muted ? 0 : m_volume);
+
+ m_updatingTopology = false;
+ stop();
}
}
}
@@ -1772,6 +1820,14 @@ void MFPlayerSession::clear()
m_request.command = CmdNone;
m_request.prevCmd = CmdNone;
+ for (int i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
+ m_trackInfo[i].metaData.clear();
+ m_trackInfo[i].nativeIndexes.clear();
+ m_trackInfo[i].currentIndex = -1;
+ m_trackInfo[i].sourceNodeId = -1;
+ m_trackInfo[i].outputNodeId = -1;
+ }
+
if (!m_metaData.isEmpty()) {
m_metaData.clear();
emit metaDataChanged();
@@ -1826,3 +1882,112 @@ void MFPlayerSession::setVideoSink(QVideoSink *sink)
{
m_videoRendererControl->setSink(sink);
}
+
+void MFPlayerSession::setActiveTrack(QPlatformMediaPlayer::TrackType type, int index)
+{
+ if (!m_session)
+ return;
+
+ // Only audio track selection is currently supported.
+ if (type != QPlatformMediaPlayer::AudioStream)
+ return;
+
+ const auto &nativeIndexes = m_trackInfo[type].nativeIndexes;
+
+ if (index < -1 || index >= nativeIndexes.count() || index == m_trackInfo[type].currentIndex)
+ return;
+
+ IMFTopology *topology = nullptr;
+
+ if (SUCCEEDED(m_session->GetFullTopology(MFSESSION_GETFULLTOPOLOGY_CURRENT, 0, &topology))) {
+
+ m_restorePosition = position() * 10000;
+
+ if (m_state.command == CmdStart)
+ stop();
+
+ if (m_trackInfo[type].outputNodeId != -1) {
+ IMFTopologyNode *node = nullptr;
+ if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].outputNodeId, &node))) {
+ topology->RemoveNode(node);
+ node->Release();
+ m_trackInfo[type].outputNodeId = -1;
+ }
+ }
+ if (m_trackInfo[type].sourceNodeId != -1) {
+ IMFTopologyNode *node = nullptr;
+ if (SUCCEEDED(topology->GetNodeByID(m_trackInfo[type].sourceNodeId, &node))) {
+ topology->RemoveNode(node);
+ node->Release();
+ m_trackInfo[type].sourceNodeId = -1;
+ }
+ }
+
+ IMFMediaSource *mediaSource = m_sourceResolver->mediaSource();
+
+ IMFPresentationDescriptor *sourcePD = nullptr;
+ if (SUCCEEDED(mediaSource->CreatePresentationDescriptor(&sourcePD))) {
+
+ if (m_trackInfo[type].currentIndex >= 0 && m_trackInfo[type].currentIndex < nativeIndexes.count())
+ sourcePD->DeselectStream(nativeIndexes.at(m_trackInfo[type].currentIndex));
+
+ m_trackInfo[type].currentIndex = index;
+
+ if (index == -1) {
+ m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology);
+ } else {
+ int nativeIndex = nativeIndexes.at(index);
+ sourcePD->SelectStream(nativeIndex);
+
+ IMFStreamDescriptor *streamDesc = nullptr;
+ BOOL selected = FALSE;
+
+ if (SUCCEEDED(sourcePD->GetStreamDescriptorByIndex(nativeIndex, &selected, &streamDesc))) {
+ IMFTopologyNode *sourceNode = addSourceNode(topology, mediaSource, sourcePD, streamDesc);
+ if (sourceNode) {
+ IMFTopologyNode *outputNode = addOutputNode(MFPlayerSession::Audio, topology, 0);
+ if (outputNode) {
+ if (SUCCEEDED(sourceNode->ConnectOutput(0, outputNode, 0))) {
+ sourceNode->GetTopoNodeID(&m_trackInfo[type].sourceNodeId);
+ outputNode->GetTopoNodeID(&m_trackInfo[type].outputNodeId);
+ m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology);
+ }
+ outputNode->Release();
+ }
+ sourceNode->Release();
+ }
+ streamDesc->Release();
+ }
+ }
+ m_updatingTopology = true;
+ sourcePD->Release();
+ }
+ topology->Release();
+ }
+}
+
+int MFPlayerSession::activeTrack(QPlatformMediaPlayer::TrackType type)
+{
+ if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes)
+ return -1;
+ return m_trackInfo[type].currentIndex;
+}
+
+int MFPlayerSession::trackCount(QPlatformMediaPlayer::TrackType type)
+{
+ if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes)
+ return -1;
+ return m_trackInfo[type].metaData.count();
+}
+
+QMediaMetaData MFPlayerSession::trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber)
+{
+ if (type < 0 || type >= QPlatformMediaPlayer::NTrackTypes)
+ return {};
+
+ if (trackNumber < 0 || trackNumber >= m_trackInfo[type].metaData.count())
+ return {};
+
+ return m_trackInfo[type].metaData.at(trackNumber);
+}
+
diff --git a/src/multimedia/platform/windows/player/mfplayersession_p.h b/src/multimedia/platform/windows/player/mfplayersession_p.h
index 18a4b02fa..621fe4f26 100644
--- a/src/multimedia/platform/windows/player/mfplayersession_p.h
+++ b/src/multimedia/platform/windows/player/mfplayersession_p.h
@@ -126,7 +126,13 @@ public:
void setVideoSink(QVideoSink *sink);
+ void setActiveTrack(QPlatformMediaPlayer::TrackType type, int index);
+ int activeTrack(QPlatformMediaPlayer::TrackType type);
+ int trackCount(QPlatformMediaPlayer::TrackType);
+ QMediaMetaData trackMetaData(QPlatformMediaPlayer::TrackType type, int trackNumber);
+
void statusChanged() { if (m_playerControl) m_playerControl->handleStatusChanged(); }
+ void tracksChanged() { if (m_playerControl) m_playerControl->handleTracksChanged(); }
void audioAvailable() { if (m_playerControl) m_playerControl->handleAudioAvailable(); }
void videoAvailable() { if (m_playerControl) m_playerControl->handleVideoAvailable(); }
void durationUpdate(qint64 duration) { if (m_playerControl) m_playerControl->handleDurationUpdate(duration); }
@@ -160,7 +166,9 @@ private:
IMFAudioStreamVolume *m_volumeControl;
IPropertyStore *m_netsourceStatistics;
qint64 m_position = 0;
+ qint64 m_restorePosition = -1;
UINT64 m_duration = 0;
+ bool m_updatingTopology = false;
enum Command
{
@@ -219,6 +227,16 @@ private:
float m_pendingRate;
void updatePendingCommands(Command command);
+ struct TrackInfo
+ {
+ QList<QMediaMetaData> metaData;
+ QList<int> nativeIndexes;
+ int currentIndex = -1;
+ TOPOID sourceNodeId = -1;
+ TOPOID outputNodeId = -1;
+ };
+ TrackInfo m_trackInfo[QPlatformMediaPlayer::NTrackTypes];
+
QMediaPlayer::MediaStatus m_status;
bool m_canScrub;
float m_volume = 1.;
@@ -231,7 +249,7 @@ private:
void createSession();
void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD);
- MediaType getStreamType(IMFStreamDescriptor *stream) const;
+ bool getStreamInfo(IMFStreamDescriptor *stream, MFPlayerSession::MediaType *type, QString *name, QString *language) const;
IMFTopologyNode* addSourceNode(IMFTopology* topology, IMFMediaSource* source,
IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc);
IMFTopologyNode* addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID);
diff --git a/src/multimedia/playback/qmediaplayer.cpp b/src/multimedia/playback/qmediaplayer.cpp
index c8a81d09e..a390afac3 100644
--- a/src/multimedia/playback/qmediaplayer.cpp
+++ b/src/multimedia/playback/qmediaplayer.cpp
@@ -350,6 +350,8 @@ bool QMediaPlayer::hasVideo() const
/*!
Returns true if the media is seekable. Most file based media files are seekable,
but live streams usually are not.
+
+ \sa position
*/
bool QMediaPlayer::isSeekable() const
{
diff --git a/src/multimedia/shaders/externalsampler.frag b/src/multimedia/shaders/externalsampler.frag
new file mode 100644
index 000000000..6431ea773
--- /dev/null
+++ b/src/multimedia/shaders/externalsampler.frag
@@ -0,0 +1,18 @@
+#version 440
+
+layout(location = 0) in vec2 texCoord;
+layout(location = 0) out vec4 fragColor;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 matrix;
+ mat4 colorMatrix;
+ float opacity;
+ float width;
+} ubuf;
+
+layout(binding = 1) uniform sampler2D plane1Texture;
+
+void main()
+{
+ fragColor = vec4(1., 0., 0., 0.);
+}
diff --git a/src/multimedia/shaders/externalsampler.vert b/src/multimedia/shaders/externalsampler.vert
new file mode 100644
index 000000000..44e09fc70
--- /dev/null
+++ b/src/multimedia/shaders/externalsampler.vert
@@ -0,0 +1,20 @@
+#version 440
+
+layout(location = 0) in vec4 vertexPosition;
+layout(location = 1) in vec2 vertexTexCoord;
+
+layout(location = 0) out vec2 texCoord;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 matrix;
+ mat4 colorMatrix;
+ float opacity;
+ float width;
+} ubuf;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main() {
+ texCoord = (ubuf.colorMatrix * vec4(vertexTexCoord, 0.0, 1.0)).xy;
+ gl_Position = ubuf.matrix * vertexPosition;
+}
diff --git a/src/multimedia/shaders/externalsampler_gles.frag b/src/multimedia/shaders/externalsampler_gles.frag
new file mode 100644
index 000000000..23f0b1f9f
--- /dev/null
+++ b/src/multimedia/shaders/externalsampler_gles.frag
@@ -0,0 +1,22 @@
+#extension GL_OES_EGL_image_external : require
+precision highp float;
+precision highp int;
+
+struct buf
+{
+ mat4 matrix;
+ mat4 colorMatrix;
+ float opacity;
+ float width;
+};
+
+uniform buf ubuf;
+
+uniform samplerExternalOES plane1Texture;
+
+varying vec2 texCoord;
+
+void main()
+{
+ gl_FragColor = texture2D(plane1Texture, texCoord).rgba * ubuf.opacity;
+}
diff --git a/src/multimedia/shaders/uyvy.frag b/src/multimedia/shaders/uyvy.frag
index 8132407ca..059b8ee2f 100644
--- a/src/multimedia/shaders/uyvy.frag
+++ b/src/multimedia/shaders/uyvy.frag
@@ -14,8 +14,8 @@ layout(binding = 1) uniform sampler2D plane1Texture;
void main()
{
- float x = texCoord.x * ubuf.width;
- bool rightSubPixel = bool(int(x) & 1);
+ int x = int(floor(texCoord.x * ubuf.width));
+ bool rightSubPixel = (x/2*2 != x);
float Y = rightSubPixel ? texture(plane1Texture, texCoord).a : texture(plane1Texture, texCoord).g;
vec2 UV = texture(plane1Texture, texCoord).br;
fragColor = ubuf.colorMatrix * vec4(Y, UV, 1.0) * ubuf.opacity;
diff --git a/src/multimedia/shaders/yuyv.frag b/src/multimedia/shaders/yuyv.frag
index c4dd5e6ff..ab68b2bea 100644
--- a/src/multimedia/shaders/yuyv.frag
+++ b/src/multimedia/shaders/yuyv.frag
@@ -14,8 +14,8 @@ layout(binding = 1) uniform sampler2D plane1Texture;
void main()
{
- float x = texCoord.x * ubuf.width;
- bool rightSubPixel = bool(int(x) & 1);
+ int x = int(floor(texCoord.x * ubuf.width));
+ bool rightSubPixel = (x/2*2 != x);
float Y = rightSubPixel ? texture(plane1Texture, texCoord).r : texture(plane1Texture, texCoord).b;
vec2 UV = texture(plane1Texture, texCoord).ga;
fragColor = ubuf.colorMatrix * vec4(Y, UV, 1.0) * ubuf.opacity;
diff --git a/src/multimedia/video/qvideoframe.cpp b/src/multimedia/video/qvideoframe.cpp
index 1c9c3f224..e82c874d9 100644
--- a/src/multimedia/video/qvideoframe.cpp
+++ b/src/multimedia/video/qvideoframe.cpp
@@ -176,6 +176,14 @@ QVideoFrame::QVideoFrame(QAbstractVideoBuffer *buffer, const QVideoFrameFormat &
}
/*!
+ \internal
+*/
+QAbstractVideoBuffer *QVideoFrame::videoBuffer() const
+{
+ return d ? d->buffer : nullptr;
+}
+
+/*!
Constructs a video frame of the given pixel \a format and \a size in pixels.
The \a bytesPerLine (stride) is the length of each scan line in bytes, and \a bytes is the total
@@ -448,6 +456,7 @@ bool QVideoFrame::map(QVideoFrame::MapMode mode)
case QVideoFrameFormat::Format_Y8:
case QVideoFrameFormat::Format_Y16:
case QVideoFrameFormat::Format_Jpeg:
+ case QVideoFrameFormat::Format_SamplerExternalOES:
// Single plane or opaque format.
break;
case QVideoFrameFormat::Format_YUV420P:
diff --git a/src/multimedia/video/qvideoframe.h b/src/multimedia/video/qvideoframe.h
index feb4e2b6d..b0ffe9629 100644
--- a/src/multimedia/video/qvideoframe.h
+++ b/src/multimedia/video/qvideoframe.h
@@ -129,6 +129,8 @@ public:
QImage toImage() const;
QVideoFrame(QAbstractVideoBuffer *buffer, const QVideoFrameFormat &format);
+
+ QAbstractVideoBuffer *videoBuffer() const;
private:
QExplicitlySharedDataPointer<QVideoFramePrivate> d;
};
diff --git a/src/multimedia/video/qvideoframeformat.cpp b/src/multimedia/video/qvideoframeformat.cpp
index 6e3d2d9e4..940fdaf98 100644
--- a/src/multimedia/video/qvideoframeformat.cpp
+++ b/src/multimedia/video/qvideoframeformat.cpp
@@ -548,9 +548,9 @@ QString QVideoFrameFormat::fragmentShaderFileName() const
/*!
\internal
*/
-QByteArray QVideoFrameFormat::uniformData(const QMatrix4x4 &transform, float opacity) const
+void QVideoFrameFormat::updateUniformData(QByteArray *dst, const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity) const
{
- return QVideoTextureHelper::uniformData(*this, transform, opacity);
+ QVideoTextureHelper::updateUniformData(dst, *this, frame, transform, opacity);
}
@@ -623,6 +623,7 @@ QImage::Format QVideoFrameFormat::imageFormatFromPixelFormat(QVideoFrameFormat::
case QVideoFrameFormat::Format_P016:
case QVideoFrameFormat::Format_Jpeg:
case QVideoFrameFormat::Format_Invalid:
+ case QVideoFrameFormat::Format_SamplerExternalOES:
return QImage::Format_Invalid;
}
return QImage::Format_Invalid;
diff --git a/src/multimedia/video/qvideoframeformat.h b/src/multimedia/video/qvideoframeformat.h
index 7c2ff0933..bfab9ebb3 100644
--- a/src/multimedia/video/qvideoframeformat.h
+++ b/src/multimedia/video/qvideoframeformat.h
@@ -54,6 +54,7 @@ QT_BEGIN_NAMESPACE
class QDebug;
class QVideoFrameFormatPrivate;
+class QVideoFrame;
class QMatrix4x4;
QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QVideoFrameFormatPrivate, Q_MULTIMEDIA_EXPORT)
@@ -91,6 +92,7 @@ public:
Format_P010,
Format_P016,
+ Format_SamplerExternalOES,
Format_Jpeg,
};
#ifndef Q_QDOC
@@ -160,7 +162,7 @@ public:
QString vertexShaderFileName() const;
QString fragmentShaderFileName() const;
- QByteArray uniformData(const QMatrix4x4 &transform, float opacity) const;
+ void updateUniformData(QByteArray *dst, const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity) const;
static PixelFormat pixelFormatFromImageFormat(QImage::Format format);
static QImage::Format imageFormatFromPixelFormat(PixelFormat format);
diff --git a/src/multimedia/video/qvideotexturehelper.cpp b/src/multimedia/video/qvideotexturehelper.cpp
index 93a56bdf8..cca4720ae 100644
--- a/src/multimedia/video/qvideotexturehelper.cpp
+++ b/src/multimedia/video/qvideotexturehelper.cpp
@@ -39,7 +39,9 @@
#include "qvideotexturehelper_p.h"
#include "qvideoframe.h"
-
+#ifdef Q_OS_ANDROID
+#include <private/qandroidvideooutput_p.h>
+#endif
QT_BEGIN_NAMESPACE
namespace QVideoTextureHelper
@@ -223,6 +225,13 @@ static const TextureDescription descriptions[QVideoFrameFormat::NPixelFormats] =
{ { 1, 1 }, { 2, 2 }, { 1, 1 } }
},
#endif
+ // Format_SamplerExternalOES
+ {
+ 1, 0,
+ [](int, int) { return 0; },
+ { QRhiTexture::RGBA8, QRhiTexture::UnknownFormat, QRhiTexture::UnknownFormat },
+ { { 1, 1 }, { 1, 1 }, { 1, 1 } }
+ },
// Format_Jpeg
{ 1, 0,
[](int, int) { return 0; },
@@ -237,8 +246,15 @@ const TextureDescription *textureDescription(QVideoFrameFormat::PixelFormat form
return descriptions + format;
}
-QString vertexShaderFileName(QVideoFrameFormat::PixelFormat /*format*/)
+QString vertexShaderFileName(QVideoFrameFormat::PixelFormat format)
{
+ Q_UNUSED(format);
+
+#ifdef Q_OS_ANDROID
+ if (format == QVideoFrameFormat::Format_SamplerExternalOES)
+ return QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.vert.qsb");
+#endif
+
return QStringLiteral(":/qt-project.org/multimedia/shaders/vertex.vert.qsb");
}
@@ -283,6 +299,11 @@ QString fragmentShaderFileName(QVideoFrameFormat::PixelFormat format)
return QStringLiteral(":/qt-project.org/multimedia/shaders/nv12.frag.qsb");
case QVideoFrameFormat::Format_NV21:
return QStringLiteral(":/qt-project.org/multimedia/shaders/nv21.frag.qsb");
+ case QVideoFrameFormat::Format_SamplerExternalOES:
+#ifdef Q_OS_ANDROID
+ return QStringLiteral(":/qt-project.org/multimedia/shaders/externalsampler.frag.qsb");
+#endif
+ // fallthrough
default:
return QString();
}
@@ -348,13 +369,15 @@ static QMatrix4x4 yuvColorCorrectionMatrix(float brightness, float contrast, flo
}
#endif
-QByteArray uniformData(const QVideoFrameFormat &format, const QMatrix4x4 &transform, float opacity)
+void updateUniformData(QByteArray *dst, const QVideoFrameFormat &format, const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity)
{
+ Q_UNUSED(frame);
+
QMatrix4x4 cmat;
switch (format.pixelFormat()) {
case QVideoFrameFormat::Format_Invalid:
case QVideoFrameFormat::Format_Jpeg:
- return QByteArray();
+ return;
case QVideoFrameFormat::Format_ARGB32:
case QVideoFrameFormat::Format_ARGB32_Premultiplied:
@@ -384,16 +407,24 @@ QByteArray uniformData(const QVideoFrameFormat &format, const QMatrix4x4 &transf
case QVideoFrameFormat::Format_P016:
cmat = colorMatrix(format.yCbCrColorSpace());
break;
+ case QVideoFrameFormat::Format_SamplerExternalOES:
+#ifdef Q_OS_ANDROID
+ {
+ // get Android specific transform for the externalsampler texture
+ if (auto *buffer = static_cast<AndroidTextureVideoBuffer *>(frame.videoBuffer()))
+ cmat = buffer->externalTextureMatrix();
+ }
+#endif
+ break;
}
- // { matrix4x4, colorMatrix, opacity, planeWidth[3] }
- QByteArray buf(64*2 + 4 + 4, Qt::Uninitialized);
- char *data = buf.data();
+ // { matrix, colorMatrix, opacity, width }
+ Q_ASSERT(dst->size() >= 64 + 64 + 4 + 4);
+ char *data = dst->data();
memcpy(data, transform.constData(), 64);
memcpy(data + 64, cmat.constData(), 64);
memcpy(data + 64 + 64, &opacity, 4);
float width = format.frameWidth();
memcpy(data + 64 + 64 + 4, &width, 4);
- return buf;
}
int updateRhiTextures(QVideoFrame frame, QRhi *rhi, QRhiResourceUpdateBatch *resourceUpdates, QRhiTexture **textures)
@@ -409,11 +440,20 @@ int updateRhiTextures(QVideoFrame frame, QRhi *rhi, QRhiResourceUpdateBatch *res
planeSizes[plane] = QSize(size.width()/description->sizeScale[plane].x, size.height()/description->sizeScale[plane].y);
if (frame.handleType() == QVideoFrame::RhiTextureHandle) {
+ QRhiTexture::Flags textureFlags = {};
+ if (pixelFormat == QVideoFrameFormat::Format_SamplerExternalOES) {
+#ifdef Q_OS_ANDROID
+ if (rhi->backend() == QRhi::OpenGLES2)
+ textureFlags |= QRhiTexture::ExternalOES;
+#endif
+ }
for (int plane = 0; plane < description->nplanes; ++plane) {
quint64 nativeTexture = frame.textureHandle(plane);
- Q_ASSERT(nativeTexture);
- textures[plane] = rhi->newTexture(description->textureFormat[plane], planeSizes[plane], 1, {});
- textures[plane]->createFrom({nativeTexture, 0});
+ if (!nativeTexture)
+ qWarning("Texture from QVideoFrame is 0, this cannot be right");
+ textures[plane] = rhi->newTexture(description->textureFormat[plane], planeSizes[plane], 1, textureFlags);
+ if (!textures[plane]->createFrom({nativeTexture, 0}))
+ qWarning("Failed to initialize QRhiTexture wrapper for native texture object %llu", nativeTexture);
}
return description->nplanes;
}
diff --git a/src/multimedia/video/qvideotexturehelper_p.h b/src/multimedia/video/qvideotexturehelper_p.h
index 7c331f0ff..9f8d95d3a 100644
--- a/src/multimedia/video/qvideotexturehelper_p.h
+++ b/src/multimedia/video/qvideotexturehelper_p.h
@@ -84,7 +84,7 @@ Q_MULTIMEDIA_EXPORT const TextureDescription *textureDescription(QVideoFrameForm
Q_MULTIMEDIA_EXPORT QString vertexShaderFileName(QVideoFrameFormat::PixelFormat format);
Q_MULTIMEDIA_EXPORT QString fragmentShaderFileName(QVideoFrameFormat::PixelFormat format);
-Q_MULTIMEDIA_EXPORT QByteArray uniformData(const QVideoFrameFormat &format, const QMatrix4x4 &transform, float opacity);
+Q_MULTIMEDIA_EXPORT void updateUniformData(QByteArray *dst, const QVideoFrameFormat &format, const QVideoFrame &frame, const QMatrix4x4 &transform, float opacity);
Q_MULTIMEDIA_EXPORT int updateRhiTextures(QVideoFrame frame, QRhi *rhi,
QRhiResourceUpdateBatch *resourceUpdates, QRhiTexture **textures);
diff --git a/src/multimediaquick/CMakeLists.txt b/src/multimediaquick/CMakeLists.txt
index eb85d0047..a7c9d468e 100644
--- a/src/multimediaquick/CMakeLists.txt
+++ b/src/multimediaquick/CMakeLists.txt
@@ -24,7 +24,6 @@ qt_internal_add_qml_module(MultimediaQuickPrivate
qquickimagepreviewprovider.cpp qquickimagepreviewprovider_p.h
# qquickplaylist.cpp qquickplaylist_p.h
qquickvideooutput.cpp qquickvideooutput_p.h
- qquickvideooutput_render.cpp qquickvideooutput_render_p.h
qsgvideonode_p.cpp qsgvideonode_p.h
qsgvideotexture.cpp qsgvideotexture_p.h
qtmultimediaquickglobal_p.h
diff --git a/src/multimediaquick/qquickvideooutput.cpp b/src/multimediaquick/qquickvideooutput.cpp
index 14b9e43d2..7af8b895e 100644
--- a/src/multimediaquick/qquickvideooutput.cpp
+++ b/src/multimediaquick/qquickvideooutput.cpp
@@ -39,13 +39,15 @@
****************************************************************************/
#include "qquickvideooutput_p.h"
-#include "qquickvideooutput_render_p.h"
#include <private/qvideooutputorientationhandler_p.h>
#include <QtMultimedia/qmediaplayer.h>
#include <QtMultimedia/qmediacapturesession.h>
#include <private/qfactoryloader_p.h>
#include <QtCore/qloggingcategory.h>
#include <qvideosink.h>
+#include <QtQuick/QQuickWindow>
+#include <private/qquickwindow_p.h>
+#include <qsgvideonode_p.h>
QT_BEGIN_NAMESPACE
@@ -124,19 +126,20 @@ Q_LOGGING_CATEGORY(qLcVideo, "qt.multimedia.video")
*/
QQuickVideoOutput::QQuickVideoOutput(QQuickItem *parent) :
- QQuickItem(parent),
- m_geometryDirty(true),
- m_orientation(0),
- m_autoOrientation(false),
- m_screenOrientationHandler(nullptr)
+ QQuickItem(parent)
{
setFlag(ItemHasContents, true);
- createBackend();
+
+ m_sink = new QVideoSink(this);
+ qRegisterMetaType<QVideoFrameFormat>();
+ QObject::connect(m_sink, SIGNAL(newVideoFrame(const QVideoFrame &)),
+ this, SLOT(_q_newFrame(const QVideoFrame &)), Qt::QueuedConnection);
+
+ initRhiForSink();
}
QQuickVideoOutput::~QQuickVideoOutput()
{
- m_backend.reset();
}
/*!
@@ -153,17 +156,7 @@ QQuickVideoOutput::~QQuickVideoOutput()
QVideoSink *QQuickVideoOutput::videoSink() const
{
- return m_backend ? m_backend->videoSink() : nullptr;
-}
-
-bool QQuickVideoOutput::createBackend()
-{
- m_backend.reset(new QQuickVideoBackend(this));
-
- // Since new backend has been created needs to update its geometry.
- m_geometryDirty = true;
-
- return true;
+ return m_sink;
}
/*!
@@ -200,10 +193,7 @@ void QQuickVideoOutput::setFillMode(FillMode mode)
void QQuickVideoOutput::_q_newFrame(const QVideoFrame &frame)
{
- if (!m_backend)
- return;
-
- m_backend->present(frame);
+ present(frame);
QSize size = frame.size();
if (!qIsDefaultAspect(m_orientation)) {
size.transpose();
@@ -251,9 +241,7 @@ void QQuickVideoOutput::_q_updateGeometry()
m_contentRect.moveCenter(rect.center());
}
- if (m_backend)
- m_backend->updateGeometry();
-
+ updateGeometry();
if (m_contentRect != oldContentRect)
emit contentRectChanged();
@@ -409,46 +397,21 @@ QRectF QQuickVideoOutput::sourceRect() const
{
// We might have to transpose back
QSizeF size = m_nativeSize;
- if (!qIsDefaultAspect(m_orientation)) {
+ if (!size.isValid())
+ return {};
+
+ if (!qIsDefaultAspect(m_orientation))
size.transpose();
- }
- // No backend? Just assume no viewport.
- if (!m_nativeSize.isValid() || !m_backend) {
- return QRectF(QPointF(), size);
- }
// Take the viewport into account for the top left position.
// m_nativeSize is already adjusted to the viewport, as it originates
// from QVideoFrameFormat::viewport(), which includes pixel aspect ratio
- const QRectF viewport = m_backend->adjustedViewport();
+ const QRectF viewport = adjustedViewport();
Q_ASSERT(viewport.size() == size);
return QRectF(viewport.topLeft(), size);
}
-QSGNode *QQuickVideoOutput::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
-{
- _q_updateGeometry();
-
- if (!m_backend)
- return nullptr;
-
- return m_backend->updatePaintNode(oldNode, data);
-}
-
-void QQuickVideoOutput::itemChange(QQuickItem::ItemChange change,
- const QQuickItem::ItemChangeData &changeData)
-{
- if (m_backend)
- m_backend->itemChange(change, changeData);
-}
-
-void QQuickVideoOutput::releaseResources()
-{
- if (m_backend)
- m_backend->releaseResources();
-}
-
void QQuickVideoOutput::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
{
Q_UNUSED(newGeometry);
@@ -465,8 +428,12 @@ void QQuickVideoOutput::geometryChange(const QRectF &newGeometry, const QRectF &
void QQuickVideoOutput::_q_invalidateSceneGraph()
{
- if (m_backend)
- m_backend->invalidateSceneGraph();
+ invalidateSceneGraph();
+}
+
+void QQuickVideoOutput::_q_sceneGraphInitialized()
+{
+ initRhiForSink();
}
/*!
@@ -494,4 +461,186 @@ void QQuickVideoOutput::setFlushMode(FlushMode mode)
emit flushModeChanged();
}
+void QQuickVideoOutput::releaseResources()
+{
+ // Called on the gui thread when the window is closed or changed.
+ invalidateSceneGraph();
+}
+
+void QQuickVideoOutput::invalidateSceneGraph()
+{
+ // Called on the render thread, e.g. when the context is lost.
+ // QMutexLocker lock(&m_frameMutex);
+ initRhiForSink();
+}
+
+void QQuickVideoOutput::initRhiForSink()
+{
+ QRhi *rhi = m_window ? QQuickWindowPrivate::get(m_window)->rhi : nullptr;
+ m_sink->setRhi(rhi);
+}
+
+void QQuickVideoOutput::itemChange(QQuickItem::ItemChange change,
+ const QQuickItem::ItemChangeData &changeData)
+{
+ if (change != QQuickItem::ItemSceneChange)
+ return;
+
+ if (changeData.window == m_window)
+ return;
+ if (m_window)
+ disconnect(m_window);
+ m_window = changeData.window;
+
+ if (m_window) {
+ // We want to receive the signals in the render thread
+ QObject::connect(m_window, &QQuickWindow::sceneGraphInitialized, this, &QQuickVideoOutput::_q_sceneGraphInitialized,
+ Qt::DirectConnection);
+ QObject::connect(m_window, &QQuickWindow::sceneGraphInvalidated,
+ this, &QQuickVideoOutput::_q_invalidateSceneGraph, Qt::DirectConnection);
+ }
+ initRhiForSink();
+}
+
+QSize QQuickVideoOutput::nativeSize() const
+{
+ return m_surfaceFormat.viewport().size();
+}
+
+void QQuickVideoOutput::updateGeometry()
+{
+ const QRectF viewport = m_surfaceFormat.viewport();
+ const QSizeF frameSize = m_surfaceFormat.frameSize();
+ const QRectF normalizedViewport(viewport.x() / frameSize.width(),
+ viewport.y() / frameSize.height(),
+ viewport.width() / frameSize.width(),
+ viewport.height() / frameSize.height());
+ const QRectF rect(0, 0, width(), height());
+ if (nativeSize().isEmpty()) {
+ m_renderedRect = rect;
+ m_sourceTextureRect = normalizedViewport;
+ } else if (fillMode() == QQuickVideoOutput::Stretch) {
+ m_renderedRect = rect;
+ m_sourceTextureRect = normalizedViewport;
+ } else if (fillMode() == QQuickVideoOutput::PreserveAspectFit) {
+ m_sourceTextureRect = normalizedViewport;
+ m_renderedRect = contentRect();
+ } else if (fillMode() == QQuickVideoOutput::PreserveAspectCrop) {
+ m_renderedRect = rect;
+ const qreal contentHeight = contentRect().height();
+ const qreal contentWidth = contentRect().width();
+
+ // Calculate the size of the source rectangle without taking the viewport into account
+ const qreal relativeOffsetLeft = -contentRect().left() / contentWidth;
+ const qreal relativeOffsetTop = -contentRect().top() / contentHeight;
+ const qreal relativeWidth = rect.width() / contentWidth;
+ const qreal relativeHeight = rect.height() / contentHeight;
+
+ // Now take the viewport size into account
+ const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
+ const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
+ const qreal totalWidth = normalizedViewport.width() * relativeWidth;
+ const qreal totalHeight = normalizedViewport.height() * relativeHeight;
+
+ if (qIsDefaultAspect(orientation())) {
+ m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
+ totalWidth, totalHeight);
+ } else {
+ m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
+ totalHeight, totalWidth);
+ }
+ }
+
+ if (m_surfaceFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop) {
+ qreal top = m_sourceTextureRect.top();
+ m_sourceTextureRect.setTop(m_sourceTextureRect.bottom());
+ m_sourceTextureRect.setBottom(top);
+ }
+
+ if (m_surfaceFormat.isMirrored()) {
+ qreal left = m_sourceTextureRect.left();
+ m_sourceTextureRect.setLeft(m_sourceTextureRect.right());
+ m_sourceTextureRect.setRight(left);
+ }
+}
+
+QSGNode *QQuickVideoOutput::updatePaintNode(QSGNode *oldNode,
+ QQuickItem::UpdatePaintNodeData *data)
+{
+ Q_UNUSED(data);
+ _q_updateGeometry();
+
+ QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode);
+
+ QMutexLocker lock(&m_frameMutex);
+
+ if (m_frameChanged) {
+ if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
+ qCDebug(qLcVideo) << "updatePaintNode: deleting old video node because frame format changed";
+ delete videoNode;
+ videoNode = nullptr;
+ }
+
+ if (!m_frame.isValid()) {
+ qCDebug(qLcVideo) << "updatePaintNode: no frames yet";
+ m_frameChanged = false;
+ return nullptr;
+ }
+
+ if (!videoNode) {
+ // Get a node that supports our frame. The surface is irrelevant, our
+ // QSGVideoItemSurface supports (logically) anything.
+ updateGeometry();
+ videoNode = new QSGVideoNode(m_surfaceFormat);
+ qCDebug(qLcVideo) << "updatePaintNode: Video node created. Handle type:" << m_frame.handleType();
+ }
+ }
+
+ if (!videoNode) {
+ m_frameChanged = false;
+ m_frame = QVideoFrame();
+ return nullptr;
+ }
+
+ // Negative rotations need lots of %360
+ videoNode->setTexturedRectGeometry(m_renderedRect, m_sourceTextureRect,
+ qNormalizedOrientation(orientation()));
+ if (m_frameChanged) {
+ videoNode->setCurrentFrame(m_frame);
+
+ if ((flushMode() == QQuickVideoOutput::FirstFrame && !m_frameOnFlush.isValid())
+ || flushMode() == QQuickVideoOutput::LastFrame) {
+ m_frameOnFlush = m_frame;
+ }
+
+ //don't keep the frame for more than really necessary
+ m_frameChanged = false;
+ m_frame = QVideoFrame();
+ }
+ return videoNode;
+}
+
+QRectF QQuickVideoOutput::adjustedViewport() const
+{
+ return m_surfaceFormat.viewport();
+}
+
+void QQuickVideoOutput::present(const QVideoFrame &frame)
+{
+ m_frameMutex.lock();
+ m_surfaceFormat = frame.surfaceFormat();
+ m_frame = frame.isValid() ? frame : m_frameOnFlush;
+ m_frameChanged = true;
+ m_frameMutex.unlock();
+
+ update();
+}
+
+void QQuickVideoOutput::stop()
+{
+ present(QVideoFrame());
+}
+
QT_END_NAMESPACE
+
+#include "moc_qquickvideooutput_p.cpp"
diff --git a/src/multimediaquick/qquickvideooutput_p.h b/src/multimediaquick/qquickvideooutput_p.h
index dd5f4b29f..181f273dc 100644
--- a/src/multimediaquick/qquickvideooutput_p.h
+++ b/src/multimediaquick/qquickvideooutput_p.h
@@ -56,15 +56,17 @@
#include <QtCore/qsharedpointer.h>
#include <QtQuick/qquickitem.h>
#include <QtCore/qpointer.h>
+#include <QtCore/qmutex.h>
#include <private/qtmultimediaquickglobal_p.h>
+#include <qvideoframe.h>
+#include <qvideoframeformat.h>
QT_BEGIN_NAMESPACE
class QQuickVideoBackend;
class QVideoOutputOrientationHandler;
class QVideoSink;
-class QVideoFrame;
class Q_MULTIMEDIAQUICK_EXPORT QQuickVideoOutput : public QQuickItem
{
@@ -134,29 +136,71 @@ protected:
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
void releaseResources() override;
+private:
+ QSize nativeSize() const;
+ void updateGeometry();
+ QRectF adjustedViewport() const;
+
+ friend class QSGVideoItemSurface;
+ void present(const QVideoFrame &frame);
+ void stop();
+
+ void invalidateSceneGraph();
+
+ void initRhiForSink();
+
private Q_SLOTS:
void _q_newFrame(const QVideoFrame &);
void _q_updateGeometry();
void _q_screenOrientationChanged(int);
void _q_invalidateSceneGraph();
+ void _q_sceneGraphInitialized();
private:
- bool createBackend();
-
QSize m_nativeSize;
- bool m_geometryDirty;
+ bool m_geometryDirty = true;
QRectF m_lastRect; // Cache of last rect to avoid recalculating geometry
QRectF m_contentRect; // Destination pixel coordinates, unclipped
- int m_orientation;
- bool m_autoOrientation;
- QVideoOutputOrientationHandler *m_screenOrientationHandler;
+ int m_orientation = 0;
+ bool m_autoOrientation = false;
+ QVideoOutputOrientationHandler *m_screenOrientationHandler = nullptr;
- QScopedPointer<QQuickVideoBackend> m_backend;
+ QPointer<QQuickWindow> m_window;
+ QVideoSink *m_sink = nullptr;
+ QVideoFrameFormat m_surfaceFormat;
+
+ QVideoFrame m_frame;
+ QVideoFrame m_frameOnFlush;
+ bool m_frameChanged = false;
+ QMutex m_frameMutex;
+ QRectF m_renderedRect; // Destination pixel coordinates, clipped
+ QRectF m_sourceTextureRect; // Source texture coordinates
FlushMode m_flushMode = EmptyFrame;
};
+namespace {
+
+inline bool qIsDefaultAspect(int o)
+{
+ return (o % 180) == 0;
+}
+
+/*
+ * Return the orientation normalized to 0-359
+ */
+inline int qNormalizedOrientation(int o)
+{
+ // Negative orientations give negative results
+ int o2 = o % 360;
+ if (o2 < 0)
+ o2 += 360;
+ return o2;
+}
+
+}
+
QT_END_NAMESPACE
#endif // QQUICKVIDEOOUTPUT_P_H
diff --git a/src/multimediaquick/qquickvideooutput_render.cpp b/src/multimediaquick/qquickvideooutput_render.cpp
deleted file mode 100644
index 042d11c6f..000000000
--- a/src/multimediaquick/qquickvideooutput_render.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Copyright (C) 2016 Research In Motion
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** 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.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include "qquickvideooutput_render_p.h"
-#include "qquickvideooutput_p.h"
-#include <QtCore/qobject.h>
-#include <QtCore/qloggingcategory.h>
-#include <private/qsgvideonode_p.h>
-#include <qvideosink.h>
-
-#include <QtQuick/QQuickWindow>
-#include <private/qquickwindow_p.h>
-#include <QtCore/QRunnable>
-
-QT_BEGIN_NAMESPACE
-
-Q_DECLARE_LOGGING_CATEGORY(qLcVideo)
-
-QQuickVideoBackend::QQuickVideoBackend(QQuickVideoOutput *parent)
- : q(parent),
- m_frameChanged(false)
-{
-}
-
-QQuickVideoBackend::~QQuickVideoBackend()
-{
- delete m_sink;
-}
-
-void QQuickVideoBackend::releaseResources()
-{
- // Called on the gui thread when the window is closed or changed.
- invalidateSceneGraph();
-}
-
-void QQuickVideoBackend::invalidateSceneGraph()
-{
- // Called on the render thread, e.g. when the context is lost.
-// QMutexLocker lock(&m_frameMutex);
-}
-
-void QQuickVideoBackend::itemChange(QQuickItem::ItemChange change,
- const QQuickItem::ItemChangeData &changeData)
-{
- if (change == QQuickItem::ItemSceneChange) {
- if (changeData.window)
- QObject::connect(changeData.window, SIGNAL(sceneGraphInvalidated()),
- q, SLOT(_q_invalidateSceneGraph()), Qt::DirectConnection);
- }
-}
-
-QSize QQuickVideoBackend::nativeSize() const
-{
- return m_surfaceFormat.viewport().size();
-}
-
-void QQuickVideoBackend::updateGeometry()
-{
- const QRectF viewport = m_surfaceFormat.viewport();
- const QSizeF frameSize = m_surfaceFormat.frameSize();
- const QRectF normalizedViewport(viewport.x() / frameSize.width(),
- viewport.y() / frameSize.height(),
- viewport.width() / frameSize.width(),
- viewport.height() / frameSize.height());
- const QRectF rect(0, 0, q->width(), q->height());
- if (nativeSize().isEmpty()) {
- m_renderedRect = rect;
- m_sourceTextureRect = normalizedViewport;
- } else if (q->fillMode() == QQuickVideoOutput::Stretch) {
- m_renderedRect = rect;
- m_sourceTextureRect = normalizedViewport;
- } else if (q->fillMode() == QQuickVideoOutput::PreserveAspectFit) {
- m_sourceTextureRect = normalizedViewport;
- m_renderedRect = q->contentRect();
- } else if (q->fillMode() == QQuickVideoOutput::PreserveAspectCrop) {
- m_renderedRect = rect;
- const qreal contentHeight = q->contentRect().height();
- const qreal contentWidth = q->contentRect().width();
-
- // Calculate the size of the source rectangle without taking the viewport into account
- const qreal relativeOffsetLeft = -q->contentRect().left() / contentWidth;
- const qreal relativeOffsetTop = -q->contentRect().top() / contentHeight;
- const qreal relativeWidth = rect.width() / contentWidth;
- const qreal relativeHeight = rect.height() / contentHeight;
-
- // Now take the viewport size into account
- const qreal totalOffsetLeft = normalizedViewport.x() + relativeOffsetLeft * normalizedViewport.width();
- const qreal totalOffsetTop = normalizedViewport.y() + relativeOffsetTop * normalizedViewport.height();
- const qreal totalWidth = normalizedViewport.width() * relativeWidth;
- const qreal totalHeight = normalizedViewport.height() * relativeHeight;
-
- if (qIsDefaultAspect(q->orientation())) {
- m_sourceTextureRect = QRectF(totalOffsetLeft, totalOffsetTop,
- totalWidth, totalHeight);
- } else {
- m_sourceTextureRect = QRectF(totalOffsetTop, totalOffsetLeft,
- totalHeight, totalWidth);
- }
- }
-
- if (m_surfaceFormat.scanLineDirection() == QVideoFrameFormat::BottomToTop) {
- qreal top = m_sourceTextureRect.top();
- m_sourceTextureRect.setTop(m_sourceTextureRect.bottom());
- m_sourceTextureRect.setBottom(top);
- }
-
- if (m_surfaceFormat.isMirrored()) {
- qreal left = m_sourceTextureRect.left();
- m_sourceTextureRect.setLeft(m_sourceTextureRect.right());
- m_sourceTextureRect.setRight(left);
- }
-}
-
-QSGNode *QQuickVideoBackend::updatePaintNode(QSGNode *oldNode,
- QQuickItem::UpdatePaintNodeData *data)
-{
- Q_UNUSED(data);
- QSGVideoNode *videoNode = static_cast<QSGVideoNode *>(oldNode);
-
- QMutexLocker lock(&m_frameMutex);
-
- if (m_frameChanged) {
- if (videoNode && videoNode->pixelFormat() != m_frame.pixelFormat()) {
- qCDebug(qLcVideo) << "updatePaintNode: deleting old video node because frame format changed";
- delete videoNode;
- videoNode = nullptr;
- }
-
- if (!m_frame.isValid()) {
- qCDebug(qLcVideo) << "updatePaintNode: no frames yet";
- m_frameChanged = false;
- return nullptr;
- }
-
- if (!videoNode) {
- // Get a node that supports our frame. The surface is irrelevant, our
- // QSGVideoItemSurface supports (logically) anything.
- updateGeometry();
- videoNode = new QSGVideoNode(m_surfaceFormat);
- qCDebug(qLcVideo) << "updatePaintNode: Video node created. Handle type:" << m_frame.handleType();
- }
- }
-
- if (!videoNode) {
- m_frameChanged = false;
- m_frame = QVideoFrame();
- return nullptr;
- }
-
- // Negative rotations need lots of %360
- videoNode->setTexturedRectGeometry(m_renderedRect, m_sourceTextureRect,
- qNormalizedOrientation(q->orientation()));
- if (m_frameChanged) {
- videoNode->setCurrentFrame(m_frame);
-
- if ((q->flushMode() == QQuickVideoOutput::FirstFrame && !m_frameOnFlush.isValid())
- || q->flushMode() == QQuickVideoOutput::LastFrame) {
- m_frameOnFlush = m_frame;
- }
-
- //don't keep the frame for more than really necessary
- m_frameChanged = false;
- m_frame = QVideoFrame();
- }
- return videoNode;
-}
-
-QVideoSink *QQuickVideoBackend::videoSink() const
-{
- if (!m_sink) {
- m_sink = new QVideoSink(q);
- if (q->window())
- m_sink->setRhi(QQuickWindowPrivate::get(q->window())->rhi);
- qRegisterMetaType<QVideoFrameFormat>();
- QObject::connect(m_sink, SIGNAL(newVideoFrame(const QVideoFrame &)),
- q, SLOT(_q_newFrame(const QVideoFrame &)), Qt::QueuedConnection);
- }
- return m_sink;
-}
-
-QRectF QQuickVideoBackend::adjustedViewport() const
-{
- return m_surfaceFormat.viewport();
-}
-
-void QQuickVideoBackend::present(const QVideoFrame &frame)
-{
- m_frameMutex.lock();
- m_surfaceFormat = frame.surfaceFormat();
- m_frame = frame.isValid() ? frame : m_frameOnFlush;
- m_frameChanged = true;
- m_frameMutex.unlock();
-
- q->update();
-}
-
-void QQuickVideoBackend::stop()
-{
- present(QVideoFrame());
-}
-
-QT_END_NAMESPACE
diff --git a/src/multimediaquick/qsgvideonode_p.cpp b/src/multimediaquick/qsgvideonode_p.cpp
index fc952e09b..9445f3d40 100644
--- a/src/multimediaquick/qsgvideonode_p.cpp
+++ b/src/multimediaquick/qsgvideonode_p.cpp
@@ -122,11 +122,14 @@ void QSGVideoNode::setTexturedRectGeometry(const QRectF &rect, const QRectF &tex
markDirty(DirtyGeometry);
}
+class QSGVideoMaterial;
+
class QSGVideoMaterialRhiShader : public QSGMaterialShader
{
public:
- QSGVideoMaterialRhiShader(const QVideoFrameFormat &format)
- : m_format(format)
+ QSGVideoMaterialRhiShader(const QSGVideoMaterial *material, const QVideoFrameFormat &format)
+ : m_material(material),
+ m_format(format)
{
setShaderFileName(VertexStage, m_format.vertexShaderFileName());
setShaderFileName(FragmentStage, m_format.fragmentShaderFileName());
@@ -139,6 +142,7 @@ public:
QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
protected:
+ const QSGVideoMaterial *m_material = nullptr;
QVideoFrameFormat m_format;
float m_planeWidth[3] = {0, 0, 0};
QMatrix4x4 m_colorMatrix;
@@ -155,7 +159,7 @@ public:
}
[[nodiscard]] QSGMaterialShader *createShader(QSGRendererInterface::RenderMode) const override {
- return new QSGVideoMaterialRhiShader(m_format);
+ return new QSGVideoMaterialRhiShader(this, m_format);
}
int compare(const QSGMaterial *other) const override {
@@ -233,8 +237,12 @@ bool QSGVideoMaterialRhiShader::updateUniformData(RenderState &state, QSGMateria
m->updateBlending();
}
- QByteArray *buf = state.uniformData();
- *buf = m_format.uniformData(state.combinedMatrix(), state.opacity());
+ // Do this here, not in updateSampledImage. First, with multiple textures we want to
+ // do this once. More importantly, on some platforms (Android) the externalMatrix is
+ // updated by this function and we need that already in updateUniformData.
+ m->updateTextures(state.rhi(), state.resourceUpdateBatch());
+
+ m_format.updateUniformData(state.uniformData(), m_material->m_frame, state.combinedMatrix(), state.opacity());
return true;
}
@@ -242,14 +250,12 @@ bool QSGVideoMaterialRhiShader::updateUniformData(RenderState &state, QSGMateria
void QSGVideoMaterialRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
{
+ Q_UNUSED(state);
Q_UNUSED(oldMaterial);
if (binding < 1 || binding > 3)
return;
auto m = static_cast<QSGVideoMaterial *>(newMaterial);
-
- m->updateTextures(state.rhi(), state.resourceUpdateBatch());
-
*texture = m->m_textures[binding - 1].data();
}
diff --git a/src/multimediawidgets/qvideowidget.cpp b/src/multimediawidgets/qvideowidget.cpp
index 659c83ece..fa54d7874 100644
--- a/src/multimediawidgets/qvideowidget.cpp
+++ b/src/multimediawidgets/qvideowidget.cpp
@@ -64,13 +64,6 @@ using namespace Qt;
QT_BEGIN_NAMESPACE
-
-void QVideoWidgetPrivate::_q_dimensionsChanged()
-{
- q_func()->updateGeometry();
- q_func()->update();
-}
-
void QVideoWidgetPrivate::_q_newFrame(const QVideoFrame &frame)
{
lastFrame = frame;
@@ -189,120 +182,6 @@ void QVideoWidget::setFullScreen(bool fullScreen)
\sa isFullScreen()
*/
-#if 0
-/*!
- \property QVideoWidget::brightness
- \brief an adjustment to the brightness of displayed video.
-
- Valid brightness values range between -1. and 1., the default is 0.
-*/
-
-float QVideoWidget::brightness() const
-{
- return d_func()->videoSink->brightness();
-}
-
-void QVideoWidget::setBrightness(float brightness)
-{
- Q_D(QVideoWidget);
- d->videoSink->setBrightness(brightness);
- float boundedBrightness = qBound(-1., brightness, 1.);
-
- if (boundedBrightness == d->videoSink->brightness())
- return;
-
- d->videoSink->setBrightness(boundedBrightness);
- emit brightnessChanged(boundedBrightness);
-}
-
-/*!
- \fn QVideoWidget::brightnessChanged(float brightness)
-
- Signals that a video widgets's \a brightness adjustment has changed.
-
- \sa brightness()
-*/
-
-/*!
- \property QVideoWidget::contrast
- \brief an adjustment to the contrast of displayed video.
-
- Valid contrast values range between -1. and 1., the default is 0.
-
-*/
-
-float QVideoWidget::contrast() const
-{
- return d_func()->videoSink->contrast();
-}
-
-void QVideoWidget::setContrast(float contrast)
-{
- Q_D(QVideoWidget);
- d->videoSink->setContrast(contrast);
-}
-
-/*!
- \fn QVideoWidget::contrastChanged(float contrast)
-
- Signals that a video widgets's \a contrast adjustment has changed.
-
- \sa contrast()
-*/
-
-/*!
- \property QVideoWidget::hue
- \brief an adjustment to the hue of displayed video.
-
- Valid hue values range between -1. and 1., the default is 0.
-*/
-
-float QVideoWidget::hue() const
-{
- return d_func()->videoSink->hue();
-}
-
-void QVideoWidget::setHue(float hue)
-{
- Q_D(QVideoWidget);
- d->videoSink->setHue(hue);
-}
-
-/*!
- \fn QVideoWidget::hueChanged(float hue)
-
- Signals that a video widgets's \a hue has changed.
-
- \sa hue()
-*/
-
-/*!
- \property QVideoWidget::saturation
- \brief an adjustment to the saturation of displayed video.
-
- Valid saturation values range between -1. and 1., the default is 0.
-*/
-
-float QVideoWidget::saturation() const
-{
- return d_func()->videoSink->saturation();
-}
-
-void QVideoWidget::setSaturation(float saturation)
-{
- Q_D(QVideoWidget);
- d->videoSink->setSaturation(saturation);
-}
-
-/*!
- \fn QVideoWidget::saturationChanged(float saturation)
-
- Signals that a video widgets's \a saturation has changed.
-
- \sa saturation()
-*/
-#endif
-
/*!
Returns the size hint for the current back end,
if there is one, or else the size hint from QWidget.
@@ -362,7 +241,6 @@ void QVideoWidget::showEvent(QShowEvent *event)
*/
void QVideoWidget::hideEvent(QHideEvent *event)
{
- // ### maybe suspend video decoding???
QWidget::hideEvent(event);
}
diff --git a/src/multimediawidgets/qvideowidget.h b/src/multimediawidgets/qvideowidget.h
index 57c132b9b..8ccc93ad7 100644
--- a/src/multimediawidgets/qvideowidget.h
+++ b/src/multimediawidgets/qvideowidget.h
@@ -91,7 +91,6 @@ protected:
private:
Q_DECLARE_PRIVATE(QVideoWidget)
- Q_PRIVATE_SLOT(d_func(), void _q_dimensionsChanged())
Q_PRIVATE_SLOT(d_func(), void _q_newFrame(const QVideoFrame &))
};
diff --git a/src/multimediawidgets/qvideowidget_p.h b/src/multimediawidgets/qvideowidget_p.h
index e425ff315..1b82ea574 100644
--- a/src/multimediawidgets/qvideowidget_p.h
+++ b/src/multimediawidgets/qvideowidget_p.h
@@ -70,15 +70,10 @@ public:
QVideoFrame lastFrame;
QVideoSink *videoSink = nullptr;
+ QRect targetRect;
bool createBackend();
- void _q_brightnessChanged(int brightness);
- void _q_contrastChanged(int contrast);
- void _q_hueChanged(int hue);
- void _q_saturationChanged(int saturation);
- void _q_fullScreenChanged(bool fullScreen);
- void _q_dimensionsChanged();
void _q_newFrame(const QVideoFrame &frame);
};
diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
index 875cebcfa..836569e2a 100644
--- a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
+++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
@@ -254,7 +254,7 @@ void tst_QAudioSink::format()
QVERIFY2((requested.sampleRate() == actual.sampleRate()),
QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toUtf8().constData());
QVERIFY2((requested.sampleFormat() == actual.sampleFormat()),
- QString("sampleFormat: requested=%1, actual=%2").arg(requested.sampleFormat()).arg(actual.sampleFormat()).toUtf8().constData());
+ QString("sampleFormat: requested=%1, actual=%2").arg((ushort)requested.sampleFormat()).arg((ushort)actual.sampleFormat()).toUtf8().constData());
QVERIFY(requested == actual);
}
diff --git a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
index 098f634bb..ddeddcea9 100644
--- a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
+++ b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
@@ -204,7 +204,7 @@ void tst_QAudioSource::format()
QVERIFY2((requested.sampleRate() == actual.sampleRate()),
QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toUtf8().constData());
QVERIFY2((requested.sampleFormat() == actual.sampleFormat()),
- QString("sampleFormat: requested=%1, actual=%2").arg(requested.sampleFormat()).arg(actual.sampleFormat()).toUtf8().constData());
+ QString("sampleFormat: requested=%1, actual=%2").arg((ushort)requested.sampleFormat()).arg((ushort)actual.sampleFormat()).toUtf8().constData());
QCOMPARE(actual, requested);
}
diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
index 17cde0802..224f8385f 100644
--- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
+++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
@@ -33,6 +33,7 @@
#include <QtCore/qurl.h>
#include <QtCore/qlocale.h>
#include <QDebug>
+#include <QVideoSink>
#include <private/qplatformcamera_p.h>
#include <private/qplatformimagecapture_p.h>
@@ -174,10 +175,6 @@ void tst_QCameraBackend::testCameraStates()
QCOMPARE(camera.isActive(), false);
- // Camera should not startup with a null QCameraDevice as device
- camera.start();
- QCOMPARE(camera.isActive(), false);
-
if (noCamera)
QSKIP("No camera available");
camera.setCameraDevice(QMediaDevices::defaultVideoInput());
@@ -247,6 +244,8 @@ void tst_QCameraBackend::testCameraCapture()
if (noCamera)
QSKIP("No camera available");
+ QVideoSink sink;
+ session.setVideoOutput(&sink);
camera.start();
QTRY_VERIFY(imageCapture.isReadyForCapture());
diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
index 2194f977d..dcbaf5626 100644
--- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
+++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
@@ -77,6 +77,9 @@ private slots:
void metadata();
void playerStateAtEOS();
void playFromBuffer();
+ void audioVideoAvailable();
+ void isSeekable();
+ void positionAfterSeek();
private:
QUrl selectVideoFile(const QStringList& mediaCandidates);
@@ -1085,6 +1088,64 @@ void tst_QMediaPlayerBackend::playFromBuffer()
QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames)));
}
+void tst_QMediaPlayerBackend::audioVideoAvailable()
+{
+ if (localVideoFile.isEmpty())
+ QSKIP("No supported video file");
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ QSignalSpy hasVideoSpy(&player, SIGNAL(hasVideoChanged(bool)));
+ QSignalSpy hasAudioSpy(&player, SIGNAL(hasAudioChanged(bool)));
+ player.setVideoOutput(&surface);
+ player.setSource(localVideoFile);
+ QTRY_VERIFY(player.hasVideo());
+ QTRY_VERIFY(player.hasAudio());
+ QCOMPARE(hasVideoSpy.count(), 1);
+ QCOMPARE(hasAudioSpy.count(), 1);
+ player.setSource(QUrl());
+ QTRY_VERIFY(!player.hasVideo());
+ QTRY_VERIFY(!player.hasAudio());
+ QCOMPARE(hasVideoSpy.count(), 2);
+ QCOMPARE(hasAudioSpy.count(), 2);
+}
+
+void tst_QMediaPlayerBackend::isSeekable()
+{
+ if (localVideoFile.isEmpty())
+ QSKIP("No supported video file");
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(localVideoFile);
+ QTRY_VERIFY(player.isSeekable());
+}
+
+void tst_QMediaPlayerBackend::positionAfterSeek()
+{
+ if (localVideoFile.isEmpty())
+ QSKIP("No supported video file");
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(localVideoFile);
+ player.pause();
+ player.setPosition(500);
+ QTRY_VERIFY(player.position() == 500);
+ player.setPosition(700);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() == 700);
+ player.play();
+ QTRY_VERIFY(player.position() > 700);
+ player.setPosition(200);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() < 700);
+}
+
QTEST_MAIN(tst_QMediaPlayerBackend)
#include "tst_qmediaplayerbackend.moc"
diff --git a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
index 9b42ee891..003c131ae 100644
--- a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
+++ b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp
@@ -395,7 +395,7 @@ void tst_QGraphicsVideoItem::paint()
graphicsScene.addItem(item);
QGraphicsView graphicsView(&graphicsScene);
graphicsView.show();
- QVERIFY(item->waitForPaint(1));
+ QVERIFY(item->paintCount() || item->waitForPaint(1));
auto *sink = item->videoSink();
Q_ASSERT(sink);
diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
index 90f7d3164..75ec4c93c 100644
--- a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
+++ b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp
@@ -86,8 +86,6 @@ void tst_QVideoWidget::nullObject()
{
QtTestVideoWidget widget;
- QVERIFY(widget.sizeHint().isEmpty());
-
widget.show();
QVERIFY(QTest::qWaitForWindowExposed(&widget));