Skip to content

Commit

Permalink
Merge MediaManager/Session into RenderSession singleton.
Browse files Browse the repository at this point in the history
Configure via RenderContext qml gadget.
Move main logic into QML and load via QQmlApplicationEngine.
Make MediaClip listen to render signal and render if active
Add RenderWindow QML object.
  • Loading branch information
rectalogic committed Apr 22, 2024
1 parent 91e147b commit 4af04ef
Show file tree
Hide file tree
Showing 42 changed files with 788 additions and 733 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Item {
}
Component.onCompleted: {
videoClip.clipEnded.connect(MediaManager.finishEncoding);
videoClip.clipEnded.connect(RenderSession.endSession);
}
}
VideoRenderer {
Expand All @@ -67,7 +67,7 @@ and renders one second of the video frames of it's [MediaClip.source](https://me
The [MultiEffect](https://doc.qt.io/qt-6/qml-qtquick-effects-multieffect.html)
filter is applied to the VideoRenderer to desaturate it.
When the clip finishes, it's [clipEnded](https://mediafx.org/qml-mediafx-mediaclip.html#clipEnded-signal) signal triggers the
[MediaManager.finishEncoding](https://mediafx.org/qml-mediafx-mediamanager.html#finishEncoding-method) slot to end encoding.
[RenderSession.endSession](https://mediafx.org/qml-mediafx-rendersession.html#endSession-method) slot to end encoding.

See [Qt signals and slots](https://doc.qt.io/qt-6/qtqml-syntax-signals.html#connecting-signals-to-methods-and-signals).

Expand Down
2 changes: 1 addition & 1 deletion builders/Darwin/install-qt6.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ INSTALLDIR=${QTDIR%/*/*}
"qtvenv/bin/python" -m aqt install-qt mac desktop ${QT_VER} --modules qtmultimedia qtquick3d qtshadertools qtquicktimeline qtwebengine qtwebchannel qtpositioning qtquickeffectmaker -O "${INSTALLDIR}" || exit 1
"qtvenv/bin/python" -m aqt install-tool mac desktop tools_qtcreator_gui qt.tools.qtcreator_gui -O "${QTDIR}/bin" || exit 1
"qtvenv/bin/python" -m aqt install-src mac ${QT_VER} --archives qtbase qtdeclarative qtmultimedia qtquicktimeline -O "${INSTALLDIR}" || exit 1
"qtvenv/bin/python" -m aqt install-doc mac ${QT_VER} --modules qtmultimedia --archives qtquick qtmultimedia -O "${INSTALLDIR}" || exit 1
"qtvenv/bin/python" -m aqt install-doc mac ${QT_VER} --modules qtmultimedia --archives qtqml qtquick qtmultimedia -O "${INSTALLDIR}" || exit 1
find "${INSTALLDIR}/Docs/Qt-${QT_VER}" -type f -and -not -name '*.index' -delete || exit 1
)
2 changes: 1 addition & 1 deletion builders/Linux/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ RUN <<EOF
fi
python3 -m venv venv && venv/bin/pip3 install --requirement /tmp/aqtinstall-requirements.txt \
&& venv/bin/python3 -m aqt install-qt ${HOST} desktop ${QT_VER} --modules qtmultimedia qtquick3d qtshadertools qtquicktimeline qtwebengine qtwebchannel qtpositioning debug_info --archives qtbase icu qttools qtdeclarative qtmultimedia qtquick3d qtquicktimeline --outputdir /usr/local/Qt \
&& venv/bin/python3 -m aqt install-doc linux ${QT_VER} --modules qtmultimedia --archives qtquick qtmultimedia --outputdir /usr/local/Qt \
&& venv/bin/python3 -m aqt install-doc linux ${QT_VER} --modules qtmultimedia --archives qtqml qtquick qtmultimedia --outputdir /usr/local/Qt \
&& find /usr/local/Qt/Docs/Qt-${QT_VER} -type f -and -not -name '*.index' -delete \
&& curl -o iwyu-mapgen-qt.py https://raw.githubusercontent.com/include-what-you-use/include-what-you-use/clang_17/mapgen/iwyu-mapgen-qt.py \
&& python3 iwyu-mapgen-qt.py /usr/local/Qt/${QT_VER}/${ARCH}/include > "/usr/local/Qt/${QT_VER}/qt.imp"
Expand Down
7 changes: 5 additions & 2 deletions src/MediaFX/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@ configure_file("version.h.in" "version.h")

qt_add_library(mediafx STATIC
application.cpp
session.cpp
media_manager.cpp
render_context.cpp
render_session.cpp
decoder.cpp
stream.cpp
audio_stream.cpp
video_stream.cpp
encoder.cpp
output_stream.cpp
render_control.cpp
render_window.cpp
media_clip.cpp
audio_renderer.cpp
interval.cpp
Expand Down Expand Up @@ -60,6 +61,8 @@ set_property(TARGET mediafxtool PROPERTY OUTPUT_NAME mediafx)
qt_add_qml_module(mediafx
URI MediaFX
QML_FILES
app-encoder.qml
app-viewer.qml
VideoRenderer.qml
sequence.js
MediaSequence.qml
Expand Down
5 changes: 4 additions & 1 deletion src/MediaFX/VideoRenderer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ VideoOutput {
property MediaClip mediaClip

onMediaClipChanged: {
MediaManager.updateVideoSinks(internal.previousMediaClip, root.mediaClip, root.videoSink);
if (internal.previousMediaClip)
internal.previousMediaClip.removeVideoSink(root.videoSink);
if (root.mediaClip)
root.mediaClip.addVideoSink(root.videoSink);
internal.previousMediaClip = root.mediaClip;
}

Expand Down
1 change: 0 additions & 1 deletion src/MediaFX/Viewer/TransitionViewer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ ColumnLayout {
property MediaTransition mediaTransition

Component.onCompleted: {
MediaManager.window.color = MediaManager.window.palette.window;
// Create the transitionComponent context object set in main.cpp
root.mediaTransition = transitionComponent.createObject(transitionContainer, {
dest: destItem,
Expand Down
34 changes: 34 additions & 0 deletions src/MediaFX/app-encoder.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2024 Andrew Wason
// SPDX-License-Identifier: GPL-3.0-or-later

import QtQuick

RenderWindow {
id: renderWindow
width: RenderSession.renderContext.frameSize.width
height: RenderSession.renderContext.frameSize.height

Component.onCompleted: {
renderWindow.contentItem.enabled = false;
renderWindow.frameReady.connect(encoder.encode);
RenderSession.renderScene.connect(renderWindow.render);
RenderSession.sessionEnded.connect(encoder.finish);
encoder.encodingError.connect(RenderSession.fatalError);
RenderSession.beginSession();
}

Loader {
id: loader
source: RenderSession.renderContext.sourceUrl
anchors.fill: parent
Component.onCompleted: {
if (loader.status == Loader.Error)
RenderSession.fatalError();
}
}

Encoder {
id: encoder
outputFileName: RenderSession.renderContext.outputFileName
}
}
22 changes: 22 additions & 0 deletions src/MediaFX/app-viewer.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2024 Andrew Wason
// SPDX-License-Identifier: GPL-3.0-or-later

import MediaFX.Viewer
import QtQuick
import QtQuick.Controls

ApplicationWindow {
id: window
visible: true
title: "MediaFX Transition Viewer"

Component.onCompleted: {
width = viewer.implicitWidth;
height = viewer.implicitHeight;
}

TransitionViewer {
id: viewer
anchors.fill: parent
}
}
60 changes: 34 additions & 26 deletions src/MediaFX/audio_renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include "audio_renderer.h"
#include "media_manager.h"
#include "render_session.h"
#include <QAudioFormat>
#include <QObject>
#include <QQmlEngine>
#include <QQmlInfo>
#include <QmlTypeAndRevisionsRegistration>
#include <QtCore>

/*!
Expand All @@ -16,19 +18,25 @@
\brief Renders audio for a \l MediaClip.
*/
AudioRenderer::AudioRenderer(QObject* parent)
: AudioRenderer(false, parent)
: QObject(parent)
{
}

AudioRenderer::AudioRenderer(bool isRoot, QObject* parent)
: QObject(parent)
AudioRenderer::~AudioRenderer() = default;

void AudioRenderer::classBegin()
{
// We default to the root renderer as nextRenderer, unless we are root
if (!isRoot)
nextRendererInternal()->addParentRenderer(this);
// We default to the root renderer as upstreamRenderer, unless we are root.
// The root renderer is created in C++ so classBegin() is not called.
upstreamRendererInternal()->addDownstreamRenderer(this);
}

AudioRenderer::~AudioRenderer() = default;
AudioRenderer* AudioRenderer::rootAudioRenderer()
{
if (!m_rootAudioRenderer)
m_rootAudioRenderer = qmlEngine(this)->singletonInstance<RenderSession*>("MediaFX", "RenderSession")->rootAudioRenderer();
return m_rootAudioRenderer;
}

/*!
\qmlproperty real AudioRenderer::volume
Expand All @@ -50,45 +58,45 @@ void AudioRenderer::setVolume(float volume)
}

/*!
\qmlproperty real AudioRenderer::nextRenderer
\qmlproperty real AudioRenderer::upstreamRenderer
The next AudioRenderer in the chain. If not set then the root AudioRenderer is used.
The upstream AudioRenderer in the chain. If not set then the root AudioRenderer is used.
*/
void AudioRenderer::setNextRenderer(AudioRenderer* nextRenderer)
void AudioRenderer::setUpstreamRenderer(AudioRenderer* upstreamRenderer)
{
if (nextRenderer != m_nextRenderer) {
if (upstreamRenderer != m_upstreamRenderer) {
// Don't allow it to be set on the root renderer
if (this == MediaManager::singletonInstance()->audioRenderer())
if (this == rootAudioRenderer())
return;
// Avoid circular dependencies
auto renderer = nextRenderer;
auto renderer = upstreamRenderer;
while (renderer != nullptr) {
if (renderer == this) {
qmlWarning(this) << "Circular dependency in AudioRenderer";
return;
}
renderer = renderer->nextRenderer();
renderer = renderer->upstreamRenderer();
}
nextRendererInternal()->removeParentRenderer(this);
m_nextRenderer = nextRenderer;
nextRendererInternal()->addParentRenderer(this);
emit nextRendererChanged();
upstreamRendererInternal()->removeDownstreamRenderer(this);
m_upstreamRenderer = upstreamRenderer;
upstreamRendererInternal()->addDownstreamRenderer(this);
emit upstreamRendererChanged();
}
}

AudioRenderer* AudioRenderer::nextRendererInternal() const
AudioRenderer* AudioRenderer::upstreamRendererInternal()
{
return m_nextRenderer ? m_nextRenderer : MediaManager::singletonInstance()->audioRenderer();
return m_upstreamRenderer ? m_upstreamRenderer : rootAudioRenderer();
}

void AudioRenderer::addParentRenderer(AudioRenderer* parent)
void AudioRenderer::addDownstreamRenderer(AudioRenderer* parent)
{
m_parentRenderers.append(parent);
m_downstreamRenderers.append(parent);
}

void AudioRenderer::removeParentRenderer(AudioRenderer* parent)
void AudioRenderer::removeDownstreamRenderer(AudioRenderer* parent)
{
m_parentRenderers.removeAll(parent);
m_downstreamRenderers.removeAll(parent);
}

void AudioRenderer::addAudioBuffer(QAudioBuffer audioBuffer)
Expand All @@ -105,7 +113,7 @@ QAudioBuffer AudioRenderer::mix()
}

// Mix each parent and add their valid buffers
for (auto parent : m_parentRenderers) {
for (auto parent : m_downstreamRenderers) {
QAudioBuffer buffer = parent->mix();
if (buffer.isValid())
audioBuffers.append(buffer);
Expand Down
29 changes: 18 additions & 11 deletions src/MediaFX/audio_renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,52 @@
#include <QAudioBuffer>
#include <QList>
#include <QObject>
#include <QQmlParserStatus>
#include <QtQmlIntegration>

class AudioRenderer : public QObject {
class AudioRenderer : public QObject, public QQmlParserStatus {
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_PROPERTY(float volume READ volume WRITE setVolume NOTIFY volumeChanged FINAL)
Q_PROPERTY(AudioRenderer* nextRenderer READ nextRenderer WRITE setNextRenderer NOTIFY nextRendererChanged FINAL)
Q_PROPERTY(AudioRenderer* upstreamRenderer READ upstreamRenderer WRITE setUpstreamRenderer NOTIFY upstreamRendererChanged FINAL)
QML_ELEMENT

signals:
void volumeChanged();
void nextRendererChanged();
void upstreamRendererChanged();

public:
using QObject::QObject;

explicit AudioRenderer(QObject* parent = nullptr);
AudioRenderer(bool isRoot, QObject* parent = nullptr);
AudioRenderer(AudioRenderer&&) = delete;
AudioRenderer& operator=(AudioRenderer&&) = delete;
~AudioRenderer() override;

float volume() const { return m_volume; };
void setVolume(float volume);

AudioRenderer* nextRenderer() const { return m_nextRenderer; };
void setNextRenderer(AudioRenderer* nextRenderer);
AudioRenderer* upstreamRenderer() const { return m_upstreamRenderer; };
void setUpstreamRenderer(AudioRenderer* upstreamRenderer);

void addAudioBuffer(QAudioBuffer audioBuffer);
QAudioBuffer mix();

protected:
void classBegin() override;
void componentComplete() override {};

private:
Q_DISABLE_COPY(AudioRenderer);

AudioRenderer* nextRendererInternal() const;
void addParentRenderer(AudioRenderer* parent);
void removeParentRenderer(AudioRenderer* parent);
AudioRenderer* rootAudioRenderer();
AudioRenderer* upstreamRendererInternal();
void addDownstreamRenderer(AudioRenderer* parent);
void removeDownstreamRenderer(AudioRenderer* parent);

float m_volume = 1.0;
QList<QAudioBuffer> audioBuffers;
AudioRenderer* m_nextRenderer = nullptr;
QList<AudioRenderer*> m_parentRenderers;
AudioRenderer* m_upstreamRenderer = nullptr;
QList<AudioRenderer*> m_downstreamRenderers;
AudioRenderer* m_rootAudioRenderer = nullptr;
};
2 changes: 1 addition & 1 deletion src/MediaFX/doc/mediafx.qdocconf
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ outputformats = HTML
language = QML
syntaxhighlighting = true

depends = qtquick qtmultimedia
depends = qtqml qtquick qtmultimedia

sources.fileextensions = "*.cpp *.qdoc *.qml"
headers.fileextensions = "*.h"
Expand Down
Loading

0 comments on commit 4af04ef

Please sign in to comment.