Revision control
Copy as Markdown
// 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 "qffmpegrenderer_p.h"
#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
namespace QFFmpeg {
Q_STATIC_LOGGING_CATEGORY(qLcRenderer, "qt.multimedia.ffmpeg.renderer");
Renderer::Renderer(const PlaybackEngineObjectID &id, const TimeController &tc)
: PlaybackEngineObject(id), m_sessionCtx{ tc }
{
}
TrackPosition Renderer::seekPosition() const
{
return TrackPosition(m_sessionCtx.seekPos);
}
TrackPosition Renderer::lastPosition() const
{
return TrackPosition(m_sessionCtx.lastPosition);
}
void Renderer::setPlaybackRate(float rate)
{
invokePriorityMethod([this, rate]() {
m_sessionCtx.timeController.setPlaybackRate(rate);
onPlaybackRateChanged();
scheduleNextStep();
});
}
void Renderer::doForceStep()
{
if (m_isStepForced.testAndSetOrdered(false, true))
invokePriorityMethod([this]() {
// maybe set m_forceStepMaxPos
if (isAtEnd()) {
setForceStepDone();
}
else {
m_sessionCtx.explicitNextFrameTime = SteadyClock::now();
scheduleNextStep();
}
});
}
bool Renderer::isStepForced() const
{
return m_isStepForced;
}
void Renderer::setTimeController(const TimeController &tc)
{
Q_ASSERT(tc.isStarted());
invokePriorityMethod([this, tc]() {
m_sessionCtx.timeController = tc;
scheduleNextStep();
});
}
void Renderer::seek(quint64 sessionId, const TimeController &tc, const LoopOffset &offset)
{
updateSession(sessionId, [this, tc, offset]() {
m_sessionCtx = { tc, offset.loopIndex };
// don't clean m_isStepForced, otherwise a single frame might not be forced on pause
seekInternal();
});
}
void Renderer::onFinalFrameReceived(PlaybackEngineObjectID sourceID)
{
if (checkSessionID(sourceID.sessionID))
render({});
}
void Renderer::render(Frame frame)
{
if (frame.isValid() && !checkSessionID(frame.sourceID().sessionID)) {
qCDebug(qLcRenderer) << "Frame session outdated. Source id:" << frame.sourceID() << "current id:" << id();
// else don't need to report
return;
}
const bool frameOutdated = frame.isValid() && frame.absoluteEnd() < seekPosition();
if (frameOutdated) {
qCDebug(qLcRenderer) << "frame outdated! absEnd:" << frame.absoluteEnd().get() << "absPts"
<< frame.absolutePts().get() << "seekPos:" << seekPosition().get();
Q_EMIT frameProcessed(std::move(frame));
return;
}
m_sessionCtx.frames.enqueue(std::move(frame));
if (m_sessionCtx.frames.size() == 1)
scheduleNextStep();
}
void Renderer::onPauseChanged()
{
m_sessionCtx.timeController.setPaused(isPaused());
PlaybackEngineObject::onPauseChanged();
}
bool Renderer::canDoNextStep() const
{
if (m_sessionCtx.frames.empty())
return false;
// do the step even if the TC is not started;
// may be changed if the case is found.
if (m_isStepForced)
return true;
if (!m_sessionCtx.timeController.isStarted())
return false;
return PlaybackEngineObject::canDoNextStep();
}
float Renderer::playbackRate() const
{
return m_sessionCtx.timeController.playbackRate();
}
Renderer::TimePoint Renderer::nextTimePoint() const
{
using namespace std::chrono_literals;
if (m_sessionCtx.frames.empty())
return PlaybackEngineObject::nextTimePoint();
if (m_sessionCtx.explicitNextFrameTime)
return *m_sessionCtx.explicitNextFrameTime;
if (m_sessionCtx.frames.front().isValid())
return m_sessionCtx.timeController.timeFromPosition(
m_sessionCtx.frames.front().absolutePts());
if (m_sessionCtx.lastFrameEnd > TrackPosition(0))
return m_sessionCtx.timeController.timeFromPosition(m_sessionCtx.lastFrameEnd);
return PlaybackEngineObject::nextTimePoint();
}
bool Renderer::setForceStepDone()
{
if (!m_isStepForced.testAndSetOrdered(true, false))
return false;
m_sessionCtx.explicitNextFrameTime.reset();
Q_EMIT forceStepDone();
return true;
}
void Renderer::doNextStep()
{
Frame frame = m_sessionCtx.frames.front();
if (setForceStepDone()) {
// if (frame.isValid() && frame.pts() > m_forceStepMaxPos) {
// scheduleNextStep();
// return;
// }
}
const auto result = renderInternal(frame);
const bool frameIsValid = frame.isValid();
if (result.done) {
m_sessionCtx.explicitNextFrameTime.reset();
m_sessionCtx.frames.dequeue();
if (frameIsValid) {
m_sessionCtx.lastPosition.storeRelease(
std::max(frame.absolutePts(), lastPosition()).get());
// TODO: get rid of m_lastFrameEnd or m_seekPos
m_sessionCtx.lastFrameEnd = frame.absoluteEnd();
m_sessionCtx.seekPos.storeRelaxed(m_sessionCtx.lastFrameEnd.get());
const auto loopIndex = frame.loopOffset().loopIndex;
if (m_sessionCtx.loopIndex < loopIndex) {
m_sessionCtx.loopIndex = loopIndex;
Q_EMIT loopChanged(id(), frame.loopOffset().loopStartTimeUs, m_sessionCtx.loopIndex);
}
Q_EMIT frameProcessed(std::move(frame));
} else {
m_sessionCtx.lastPosition.storeRelease(
std::max(m_sessionCtx.lastFrameEnd, lastPosition()).get());
}
} else {
m_sessionCtx.explicitNextFrameTime = SteadyClock::now() + result.recheckInterval;
}
setAtEnd(result.done && !frameIsValid);
scheduleNextStep();
}
std::chrono::microseconds Renderer::frameDelay(const Frame &frame, TimePoint timePoint) const
{
return std::chrono::duration_cast<std::chrono::microseconds>(
timePoint - m_sessionCtx.timeController.timeFromPosition(frame.absolutePts()));
}
void Renderer::changeRendererTime(std::chrono::microseconds offset)
{
const auto now = SteadyClock::now();
const auto pos = m_sessionCtx.timeController.positionFromTime(now);
m_sessionCtx.timeController.sync(now + offset, pos);
Q_EMIT synchronized(id(), now + offset, pos);
}
} // namespace QFFmpeg
QT_END_NAMESPACE
#include "moc_qffmpegrenderer_p.cpp"