}
#include <unistd.h>
+#include <QThread>
+
#include "pipewireproduce_p.h"
#include "vaapiutils_p.h"
PipeWireBaseEncodedStream::Encoder m_encoder;
std::optional<quint8> m_quality;
PipeWireBaseEncodedStream::EncodingPreference m_encodingPreference;
+ PipeWireBaseEncodedStream::State m_state = PipeWireBaseEncodedStream::Idle;
std::unique_ptr<QThread> m_produceThread;
std::unique_ptr<PipeWireProduce> m_produce;
PipeWireBaseEncodedStream::State PipeWireBaseEncodedStream::state() const
{
- if (isActive()) {
- return Recording;
- } else if (d->m_produceThread && d->m_produce->m_deactivated && d->m_produceThread->isRunning()) {
- return Rendering;
- }
-
- return Idle;
+ return d->m_state;
}
PipeWireBaseEncodedStream::PipeWireBaseEncodedStream(QObject *parent)
PipeWireBaseEncodedStream::~PipeWireBaseEncodedStream()
{
- setActive(false);
+ stop();
- if (d->m_fd) {
- close(*d->m_fd);
+ if (d->m_produceThread) {
+ d->m_produceThread->wait();
}
}
return;
d->m_nodeId = nodeId;
- refresh();
Q_EMIT nodeIdChanged(nodeId);
}
close(*d->m_fd);
}
d->m_fd = fd;
- refresh();
Q_EMIT fdChanged(fd);
}
void PipeWireBaseEncodedStream::setActive(bool active)
{
- if (d->m_active == active)
- return;
+ if (active) {
+ start();
+ } else {
+ stop();
- d->m_active = active;
- refresh();
- Q_EMIT activeChanged(active);
+ if (d->m_produceThread) {
+ d->m_produceThread->wait();
+ }
+ }
}
-std::optional<quint8> PipeWireBaseEncodedStream::quality() const
+void PipeWireBaseEncodedStream::start()
{
- return d->m_quality;
-}
+ if (d->m_nodeId == 0) {
+ qCWarning(PIPEWIRERECORD_LOGGING) << "Cannot start recording on a stream without a node ID";
+ return;
+ }
-void PipeWireBaseEncodedStream::setQuality(quint8 quality)
-{
- d->m_quality = quality;
- if (d->m_produce) {
- d->m_produce->setQuality(d->m_quality);
+ if (d->m_produceThread || d->m_state != Idle) {
+ return;
}
+
+ d->m_produceThread = std::make_unique<QThread>();
+ d->m_produceThread->setObjectName("PipeWireProduce::input");
+ d->m_produce = makeProduce();
+ d->m_produce->setQuality(d->m_quality);
+ d->m_produce->setMaxPendingFrames(d->m_maxPendingFrames);
+ d->m_produce->setEncodingPreference(d->m_encodingPreference);
+ d->m_produce->moveToThread(d->m_produceThread.get());
+ d->m_produceThread->start();
+ QMetaObject::invokeMethod(d->m_produce.get(), &PipeWireProduce::initialize, Qt::QueuedConnection);
+
+ connect(d->m_produce.get(), &PipeWireProduce::started, this, [this]() {
+ d->m_active = true;
+ Q_EMIT activeChanged(true);
+ d->m_state = Recording;
+ Q_EMIT stateChanged();
+ });
+
+ connect(d->m_produce.get(), &PipeWireProduce::finished, this, [this]() {
+ d->m_active = false;
+ Q_EMIT activeChanged(false);
+ d->m_state = Idle;
+ Q_EMIT stateChanged();
+ });
+
+ connect(d->m_produceThread.get(), &QThread::finished, this, [this]() {
+ d->m_produce.reset();
+ d->m_produceThread.reset();
+ d->m_nodeId = 0;
+
+ if (d->m_fd) {
+ close(d->m_fd.value());
+ }
+ });
}
-void PipeWireBaseEncodedStream::refresh()
+void PipeWireBaseEncodedStream::stop()
{
if (d->m_produceThread) {
QMetaObject::invokeMethod(d->m_produce.get(), &PipeWireProduce::deactivate, Qt::QueuedConnection);
- d->m_produceThread->wait();
-
- d->m_produce.reset();
- d->m_produceThread.reset();
}
- if (d->m_active && d->m_nodeId > 0) {
- d->m_produceThread = std::make_unique<QThread>();
- d->m_produceThread->setObjectName("PipeWireProduce::input");
- d->m_produce = makeProduce();
+ d->m_state = PipeWireBaseEncodedStream::Rendering;
+ Q_EMIT stateChanged();
+}
+
+std::optional<quint8> PipeWireBaseEncodedStream::quality() const
+{
+ return d->m_quality;
+}
+
+void PipeWireBaseEncodedStream::setQuality(quint8 quality)
+{
+ d->m_quality = quality;
+ if (d->m_produce) {
d->m_produce->setQuality(d->m_quality);
- d->m_produce->setMaxPendingFrames(d->m_maxPendingFrames);
- d->m_produce->setEncodingPreference(d->m_encodingPreference);
- d->m_produce->moveToThread(d->m_produceThread.get());
- d->m_produceThread->start();
- QMetaObject::invokeMethod(d->m_produce.get(), &PipeWireProduce::initialize, Qt::QueuedConnection);
}
-
- Q_EMIT stateChanged();
}
void PipeWireBaseEncodedStream::setEncoder(Encoder encoder)
* Transfers the ownership of the fd, will close it when it's done with it.
*/
Q_PROPERTY(uint fd READ fd WRITE setFd NOTIFY fdChanged)
- Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
+ Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
Q_PROPERTY(State state READ state NOTIFY stateChanged)
Q_PROPERTY(Encoder encoder READ encoder WRITE setEncoder NOTIFY encoderChanged)
int maxBufferSize() const;
bool isActive() const;
- void setActive(bool active);
+ /**
+ * Set the active state of recording.
+ *
+ * @deprecated Since 6.4, use the separate `start()`/`stop()`calls instead.
+ * This function now just calls `start()`/`stop()`.
+ *
+ * @note When calling `setActive(false)`, unlike `stop()`, this function will
+ * block until the internal encoding threads are finished.
+ */
+ KPIPEWIRE_DEPRECATED void setActive(bool active);
+
+ /**
+ * Request to start recording.
+ *
+ * This will create everything required to perform recording, like a PipeWire
+ * stream and an encoder, then start receiving frames from the stream and
+ * encoding those.
+ *
+ * This requires a valid node ID to be set and that the current state is Idle.
+ *
+ * Note that recording all happens on separate threads, this method only
+ * starts the process, only when state() returns Recording is recording
+ * actually happening.
+ */
+ Q_INVOKABLE void start();
+ /**
+ * Request to stop recording.
+ *
+ * This will terminate receiving frames from PipeWire and do any cleanup
+ * necessary to fully terminate recording after that.
+ *
+ * Note that after this request, there may still be some processing required
+ * due to internal queues. As long as state() does not return Idle processing
+ * is still happening and teardown has not been completed.
+ */
+ Q_INVOKABLE void stop();
/**
* The quality used for encoding.
virtual std::unique_ptr<PipeWireProduce> makeProduce() = 0;
EncodingPreference encodingPreference();
- void refresh();
QScopedPointer<PipeWireEncodedStreamPrivate> d;
};
}
encoded->setEncoder(enc);
}
- encoded->setActive(true);
+ encoded->start();
QObject::connect(encoded, &PipeWireEncodedStream::newPacket, qGuiApp, [](const PipeWireEncodedStream::Packet &packet) {
qDebug() << "packet received" << packet.data().size() << "key:" << packet.isKeyFrame();
});
QObject::connect(encoded, &PipeWireEncodedStream::cursorChanged, qGuiApp, [](const PipeWireCursor &cursor) {
qDebug() << "cursor received. position:" << cursor.position << "hotspot:" << cursor.hotspot << "image:" << cursor.texture;
});
+ QObject::connect(encoded, &PipeWireEncodedStream::stateChanged, qGuiApp, [encoded]() {
+ switch (encoded->state()) {
+ case PipeWireEncodedStream::Recording:
+ qDebug() << "Started recording";
+ break;
+ case PipeWireEncodedStream::Rendering:
+ qDebug() << "Stopped recording, flushing remaining frames";
+ break;
+ case PipeWireEncodedStream::Idle:
+ qDebug() << "Recording finished, quitting";
+ exit(0);
+ break;
+ }
+ });
QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, encoded, [encoded] {
- encoded->setActive(false);
- exit(0);
+ encoded->stop();
});
return;
}
});
QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, pwStream, [pwStream] {
pwStream->setActive(false);
- exit(0);
});
}