Revision control

++ // Copyright (C) 2016 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 <QtCore/qcoreapplication.h>
++ #include <QtCore/qloggingcategory.h>
++
++ #include "qgstpipeline_p.h"
++ #include "qgst_bus_observer_p.h"
++
++ #include <thread>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_STATIC_LOGGING_CATEGORY(qLcGstPipeline, "qt.multimedia.gstpipeline");
++
++ struct QGstPipelinePrivate
++ {
++ explicit QGstPipelinePrivate(QGstBusHandle);
++ ~QGstPipelinePrivate();
++
++ std::chrono::nanoseconds m_position{};
++ double m_rate = 1.;
++ std::unique_ptr<QGstBusObserver> m_busObserver;
++ };
++
++ QGstPipelinePrivate::QGstPipelinePrivate(QGstBusHandle bus)
++ : m_busObserver{
++ std::make_unique<QGstBusObserver>(std::move(bus)),
++ }
++ {
++ Q_ASSERT(QThread::isMainThread());
++ }
++
++ QGstPipelinePrivate::~QGstPipelinePrivate()
++ {
++ m_busObserver->close();
++
++ if (m_busObserver->currentThreadIsNotifierThread())
++ return;
++
++ // The QGstPipelinePrivate is owned the the GstPipeline and can be destroyed from a gstreamer
++ // thread. In this case we cannot destroy the object immediately, but need to marshall it
++ // through the event loop of the main thread
++ QMetaObject::invokeMethod(qApp, [bus = std::move(m_busObserver)] {
++ // nothing to do, we just extend the lifetime of the bus
++ });
++ }
++
++ // QGstPipeline
++
++ QGstPipeline QGstPipeline::create(const char *name)
++ {
++ GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(gst_pipeline_new(name));
++ return adopt(pipeline);
++ }
++
++ QGstPipeline QGstPipeline::createFromFactory(const char *factory, const char *name)
++ {
++ QGstElement playbin3 = QGstElement::createFromFactory(factory, name);
++ GstPipeline *pipeline = qGstCheckedCast<GstPipeline>(playbin3.element());
++
++ return QGstPipeline::adopt(pipeline);
++ }
++
++ QGstPipeline QGstPipeline::adopt(GstPipeline *pipeline)
++ {
++ QGstPipeline wrappedObject{
++ pipeline,
++ QGstPipeline::NeedsRef,
++ };
++
++ QGstBusHandle bus{
++ gst_pipeline_get_bus(pipeline),
++ QGstBusHandle::HasRef,
++ };
++
++ auto d = std::make_unique<QGstPipelinePrivate>(std::move(bus));
++ wrappedObject.set("pipeline-private", std::move(d));
++
++ return wrappedObject;
++ }
++
++ QGstPipeline::QGstPipeline(GstPipeline *p, RefMode mode) : QGstBin(qGstCheckedCast<GstBin>(p), mode)
++ {
++ }
++
++ QGstPipeline::~QGstPipeline() = default;
++
++ void QGstPipeline::installMessageFilter(QGstreamerBusMessageFilter *filter)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ d->m_busObserver->installMessageFilter(filter);
++ }
++
++ void QGstPipeline::removeMessageFilter(QGstreamerBusMessageFilter *filter)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ d->m_busObserver->removeMessageFilter(filter);
++ }
++
++ GstStateChangeReturn QGstPipeline::setState(GstState state)
++ {
++ return gst_element_set_state(element(), state);
++ }
++
++ bool QGstPipeline::processNextPendingMessage(GstMessageType types, std::chrono::nanoseconds timeout)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ return d->m_busObserver->processNextPendingMessage(types, timeout);
++ }
++
++ bool QGstPipeline::processNextPendingMessage(std::chrono::nanoseconds timeout)
++ {
++ return processNextPendingMessage(GST_MESSAGE_ANY, timeout);
++ }
++
++ void QGstPipeline::flush()
++ {
++ seek(position(), /*flush=*/true);
++ }
++
++ void QGstPipeline::seek(std::chrono::nanoseconds pos, double rate, bool flush)
++ {
++ using namespace std::chrono_literals;
++
++ QGstPipelinePrivate *d = getPrivate();
++ // always adjust the rate, so it can be set before playback starts
++ // setting position needs a loaded media file that's seekable
++
++ qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos << "rate:" << rate
++ << (flush ? "flushing" : "not flushing");
++
++ GstSeekFlags seekFlags = flush ? GST_SEEK_FLAG_FLUSH : GST_SEEK_FLAG_NONE;
++ seekFlags = GstSeekFlags(seekFlags | GST_SEEK_FLAG_SEGMENT | GST_SEEK_FLAG_ACCURATE);
++
++ bool success = (rate > 0)
++ ? gst_element_seek(element(), rate, GST_FORMAT_TIME, seekFlags, GST_SEEK_TYPE_SET,
++ pos.count(), GST_SEEK_TYPE_END, 0)
++ : gst_element_seek(element(), rate, GST_FORMAT_TIME, seekFlags, GST_SEEK_TYPE_SET, 0,
++ GST_SEEK_TYPE_SET, pos.count());
++
++ if (!success) {
++ qDebug() << "seek: gst_element_seek failed" << pos;
++ dumpGraph("seekSeekFailed", false);
++ return;
++ }
++
++ d->m_position = pos;
++ }
++
++ void QGstPipeline::seek(std::chrono::nanoseconds pos, bool flush)
++ {
++ qCDebug(qLcGstPipeline) << "QGstPipeline::seek to" << pos;
++ seek(pos, getPrivate()->m_rate, flush);
++ }
++
++ void QGstPipeline::setPlaybackRate(double rate, bool forceFlushingSeek)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ if (rate == d->m_rate)
++ return;
++
++ d->m_rate = rate;
++
++ qCDebug(qLcGstPipeline) << "QGstPipeline::setPlaybackRate to" << rate;
++
++ applyPlaybackRate(forceFlushingSeek);
++ }
++
++ double QGstPipeline::playbackRate() const
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ return d->m_rate;
++ }
++
++ void QGstPipeline::applyPlaybackRate(bool forceFlushingSeek)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++
++ // do not GST_SEEK_FLAG_FLUSH with GST_SEEK_TYPE_NONE
++ if (!forceFlushingSeek) {
++ bool asyncChangeSuccess = waitForAsyncStateChangeComplete();
++ if (!asyncChangeSuccess) {
++ qWarning()
++ << "QGstPipeline::seek: async pipeline change in progress. Seeking impossible";
++ return;
++ }
++
++ qCDebug(qLcGstPipeline) << "QGstPipeline::applyPlaybackRate instantly";
++ bool success = gst_element_seek(
++ element(), d->m_rate, GST_FORMAT_UNDEFINED, GST_SEEK_FLAG_INSTANT_RATE_CHANGE,
++ GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
++ if (!success) {
++ qDebug() << "setPlaybackRate: gst_element_seek failed";
++ dumpGraph("applyPlaybackRateSeekFailed", false);
++ }
++ } else {
++ seek(position(), d->m_rate);
++ }
++ }
++
++ void QGstPipeline::setPosition(std::chrono::nanoseconds pos, bool flush)
++ {
++ seek(pos, flush);
++ }
++
++ std::chrono::nanoseconds QGstPipeline::position() const
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ std::optional<std::chrono::nanoseconds> pos = QGstElement::position();
++ if (pos) {
++ d->m_position = *pos;
++ qCDebug(qLcGstPipeline) << "QGstPipeline::position:"
++ << std::chrono::round<std::chrono::milliseconds>(*pos);
++ } else {
++ qDebug() << "QGstPipeline: failed to query position, using previous position";
++ dumpGraph("positionQueryFailed", false);
++ }
++
++ return d->m_position;
++ }
++
++ std::chrono::milliseconds QGstPipeline::positionInMs() const
++ {
++ using namespace std::chrono;
++ return round<milliseconds>(position());
++ }
++
++ void QGstPipeline::setPositionAndRate(std::chrono::nanoseconds pos, double rate)
++ {
++ QGstPipelinePrivate *d = getPrivate();
++ d->m_rate = rate;
++ seek(pos, rate);
++ }
++
++ std::optional<std::chrono::nanoseconds>
++ QGstPipeline::queryPosition(std::chrono::nanoseconds timeout) const
++ {
++ using namespace std::chrono_literals;
++ using namespace std::chrono;
++
++ std::chrono::nanoseconds totalSleepTime{};
++
++ for (;;) {
++ std::optional<nanoseconds> dur = QGstElement::duration();
++ if (dur)
++ return dur;
++
++ if (totalSleepTime >= timeout)
++ return std::nullopt;
++ std::this_thread::sleep_for(20ms);
++ totalSleepTime += 20ms;
++ }
++ }
++
++ std::optional<std::chrono::nanoseconds>
++ QGstPipeline::queryDuration(std::chrono::nanoseconds timeout) const
++ {
++ using namespace std::chrono_literals;
++ using namespace std::chrono;
++
++ std::chrono::nanoseconds totalSleepTime{};
++
++ for (;;) {
++ std::optional<nanoseconds> dur = QGstElement::duration();
++ if (dur)
++ return dur;
++
++ if (totalSleepTime >= timeout)
++ return std::nullopt;
++
++ std::this_thread::sleep_for(20ms);
++ totalSleepTime += 20ms;
++ }
++ }
++
++ std::optional<std::pair<std::chrono::nanoseconds, std::chrono::nanoseconds>>
++ QGstPipeline::queryPositionAndDuration(std::chrono::nanoseconds timeout) const
++ {
++ using namespace std::chrono_literals;
++ using namespace std::chrono;
++
++ std::chrono::nanoseconds totalSleepTime{};
++
++ std::optional<nanoseconds> dur;
++ std::optional<nanoseconds> pos;
++
++ for (;;) {
++ if (!dur)
++ dur = QGstElement::duration();
++ if (!pos)
++ pos = QGstElement::position();
++
++ if (dur && pos)
++ return std::pair{ *dur, *pos };
++
++ if (totalSleepTime >= timeout)
++ return std::nullopt;
++
++ std::this_thread::sleep_for(20ms);
++ totalSleepTime += 20ms;
++ }
++ }
++
++ void QGstPipeline::seekToEndWithEOS()
++ {
++ QGstPipelinePrivate *d = getPrivate();
++
++ gst_element_seek(element(), d->m_rate, GST_FORMAT_TIME, GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_END,
++ 0, GST_SEEK_TYPE_END, 0);
++ }
++
++ QGstPipelinePrivate *QGstPipeline::getPrivate() const
++ {
++ QGstPipelinePrivate *ret = getObject<QGstPipelinePrivate>("pipeline-private");
++ Q_ASSERT(ret);
++ return ret;
++ }
++
++ QT_END_NAMESPACE