Revision control

++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qgstreamervideooutput_p.h"
++
++ #include <QtMultimedia/qvideosink.h>
++
++ #include <QtCore/qloggingcategory.h>
++
++ #include <common/qgstreamervideosink_p.h>
++ #include <common/qgstsubtitlesink_p.h>
++
++ Q_STATIC_LOGGING_CATEGORY(qLcMediaVideoOutput, "qt.multimedia.videooutput");
++
++ QT_BEGIN_NAMESPACE
++
++ static QGstElement makeVideoConvertScale(const char *name)
++ {
++ QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale");
++ if (factory) // videoconvertscale is only available in gstreamer 1.20
++ return QGstElement::createFromFactory(factory, name);
++
++ return QGstBin::createFromPipelineDescription("videoconvert ! videoscale", name,
++ /*ghostUnlinkedPads=*/true);
++ }
++
++ q23::expected<QGstreamerVideoOutput *, QString> QGstreamerVideoOutput::create(QObject *parent)
++ {
++ QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale");
++
++ static std::optional<QString> elementCheck = []() -> std::optional<QString> {
++ std::optional<QString> error = qGstErrorMessageIfElementsNotAvailable("fakesink", "queue");
++ if (error)
++ return error;
++
++ QGstElementFactoryHandle factory = QGstElement::findFactory("videoconvertscale");
++ if (factory)
++ return std::nullopt;
++
++ return qGstErrorMessageIfElementsNotAvailable("videoconvert", "videoscale");
++ }();
++
++ if (elementCheck)
++ return q23::unexpected{ *elementCheck };
++
++ return new QGstreamerVideoOutput(parent);
++ }
++
++ QGstreamerVideoOutput::QGstreamerVideoOutput(QObject *parent)
++ : QObject(parent),
++ m_outputBin{
++ QGstBin::create("videoOutput"),
++ },
++ m_videoQueue{
++ QGstElement::createFromFactory("queue", "videoQueue"),
++ },
++ m_videoConvertScale{
++ makeVideoConvertScale("videoConvertScale"),
++ },
++ m_videoSink{
++ QGstElement::createFromFactory("fakesink", "fakeVideoSink"),
++ }
++ {
++ m_videoSink.set("sync", true);
++
++ m_outputBin.add(m_videoQueue, m_videoConvertScale, m_videoSink);
++ qLinkGstElements(m_videoQueue, m_videoConvertScale, m_videoSink);
++
++ m_subtitleSink = QGstSubtitleSink::createSink(this);
++
++ m_outputBin.addGhostPad(m_videoQueue, "sink");
++ }
++
++ QGstreamerVideoOutput::~QGstreamerVideoOutput()
++ {
++ QObject::disconnect(m_subtitleConnection);
++ m_outputBin.setStateSync(GST_STATE_NULL);
++ }
++
++ void QGstreamerVideoOutput::setVideoSink(QGstreamerRelayVideoSink *sink)
++ {
++ using namespace std::chrono_literals;
++
++ if (sink == m_gstVideoSink)
++ return;
++
++ m_gstVideoSink = sink;
++ if (m_gstVideoSink) {
++ m_gstVideoSink->setActive(m_isActive);
++ if (m_nativeSize.isValid())
++ m_gstVideoSink->setNativeSize(m_nativeSize);
++ }
++ QGstElement videoSink;
++ if (m_gstVideoSink) {
++ videoSink = m_gstVideoSink->gstSink();
++ } else {
++ videoSink = QGstElement::createFromFactory("fakesink", "fakevideosink");
++ Q_ASSERT(videoSink);
++ videoSink.set("sync", true);
++ }
++
++ QObject::disconnect(m_subtitleConnection);
++ if (sink) {
++ m_subtitleConnection = QObject::connect(this, &QGstreamerVideoOutput::subtitleChanged, sink,
++ [sink](const QString &subtitle) {
++ sink->setSubtitleText(subtitle);
++ });
++ sink->setSubtitleText(m_lastSubtitleString);
++ }
++
++ if (m_videoSink == videoSink)
++ return;
++
++ m_videoConvertScale.src().modifyPipelineInIdleProbe([&] {
++ if (m_videoSink)
++ m_outputBin.stopAndRemoveElements(m_videoSink);
++
++ m_videoSink = std::move(videoSink);
++ m_outputBin.add(m_videoSink);
++
++ qLinkGstElements(m_videoConvertScale, m_videoSink);
++
++ GstEvent *event = gst_event_new_reconfigure();
++ gst_element_send_event(m_videoSink.element(), event);
++ m_videoSink.syncStateWithParent();
++ });
++
++ qCDebug(qLcMediaVideoOutput) << "sinkChanged" << m_videoSink.name();
++ m_videoConvertScale.dumpPipelineGraph(m_videoSink.name().constData());
++ }
++
++ void QGstreamerVideoOutput::setActive(bool isActive)
++ {
++ if (m_isActive == isActive)
++ return;
++
++ m_isActive = isActive;
++ if (m_gstVideoSink)
++ m_gstVideoSink->setActive(isActive);
++ }
++
++ void QGstreamerVideoOutput::updateNativeSize()
++ {
++ if (!m_gstVideoSink)
++ return;
++
++ m_gstVideoSink->setNativeSize(qRotatedFrameSize(m_nativeSize, m_rotation));
++ }
++
++ void QGstreamerVideoOutput::setIsPreview()
++ {
++ // configures the queue to be fast and lightweight for camera preview
++ // also avoids blocking the queue in case we have an encodebin attached to the tee as well
++ m_videoQueue.set("leaky", 2 /*downstream*/);
++ m_videoQueue.set("silent", true);
++ m_videoQueue.set("max-size-buffers", int(1));
++ m_videoQueue.set("max-size-bytes", unsigned(0));
++ m_videoQueue.set("max-size-time", uint64_t(0));
++ }
++
++ void QGstreamerVideoOutput::flushSubtitles()
++ {
++ if (m_subtitleSink) {
++ auto pad = m_subtitleSink.staticPad("sink");
++ auto *event = gst_event_new_flush_start();
++ pad.sendEvent(event);
++ event = gst_event_new_flush_stop(false);
++ pad.sendEvent(event);
++ }
++ }
++
++ void QGstreamerVideoOutput::setNativeSize(QSize sz)
++ {
++ m_nativeSize = sz;
++ updateNativeSize();
++ }
++
++ void QGstreamerVideoOutput::setRotation(QtVideo::Rotation rot)
++ {
++ m_rotation = rot;
++ updateNativeSize();
++ }
++
++ void QGstreamerVideoOutput::updateSubtitle(QString string)
++ {
++ // GStreamer thread
++
++ QMetaObject::invokeMethod(this, [this, string = std::move(string)]() mutable {
++ m_lastSubtitleString = string;
++ Q_EMIT subtitleChanged(std::move(string));
++ });
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qgstreamervideooutput_p.cpp"