Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qffmpegdemuxer.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include <qloggingcategory.h>
6
8
9// 4 sec for buffering. TODO: maybe move to env var customization
10static constexpr qint64 MaxBufferedDurationUs = 4'000'000;
11
12// around 4 sec of hdr video
13static constexpr qint64 MaxBufferedSize = 32 * 1024 * 1024;
14
15namespace QFFmpeg {
16
17static Q_LOGGING_CATEGORY(qLcDemuxer, "qt.multimedia.ffmpeg.demuxer");
18
19static qint64 streamTimeToUs(const AVStream *stream, qint64 time)
20{
22
23 const auto res = mul(time * 1000000, stream->time_base);
24 return res ? *res : time;
25}
26
27static qint64 packetEndPos(const AVStream *stream, const Packet &packet)
28{
29 return packet.loopOffset().pos
30 + streamTimeToUs(stream, packet.avPacket()->pts + packet.avPacket()->duration);
31}
32
33Demuxer::Demuxer(AVFormatContext *context, const PositionWithOffset &posWithOffset,
34 const StreamIndexes &streamIndexes, int loops)
35 : m_context(context), m_posWithOffset(posWithOffset), m_loops(loops)
36{
37 qCDebug(qLcDemuxer) << "Create demuxer."
38 << "pos:" << posWithOffset.pos << "loop offset:" << posWithOffset.offset.pos
39 << "loop index:" << posWithOffset.offset.index << "loops:" << loops;
40
41 Q_ASSERT(m_context);
42
43 for (auto i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
44 if (streamIndexes[i] >= 0) {
45 const auto trackType = static_cast<QPlatformMediaPlayer::TrackType>(i);
46 qCDebug(qLcDemuxer) << "Activate demuxing stream" << i << ", trackType:" << trackType;
47 m_streams[streamIndexes[i]] = { trackType };
48 }
49 }
50}
51
53{
54 ensureSeeked();
55
56 Packet packet(m_posWithOffset.offset, AVPacketUPtr{ av_packet_alloc() }, id());
57 if (av_read_frame(m_context, packet.avPacket()) < 0) {
58 ++m_posWithOffset.offset.index;
59
60 const auto loops = m_loops.loadAcquire();
61 if (loops >= 0 && m_posWithOffset.offset.index >= loops) {
62 qCDebug(qLcDemuxer) << "finish demuxing";
63
64 if (!std::exchange(m_buffered, true))
66
67 setAtEnd(true);
68 } else {
69 m_seeked = false;
70 m_posWithOffset.pos = 0;
71 m_posWithOffset.offset.pos = m_maxPacketsEndPos;
72 m_maxPacketsEndPos = 0;
73
74 ensureSeeked();
75
76 qCDebug(qLcDemuxer) << "Demuxer loops changed. Index:" << m_posWithOffset.offset.index
77 << "Offset:" << m_posWithOffset.offset.pos;
78
79 scheduleNextStep(false);
80 }
81
82 return;
83 }
84
85 auto &avPacket = *packet.avPacket();
86
87 const auto streamIndex = avPacket.stream_index;
88 const auto stream = m_context->streams[streamIndex];
89
90 auto it = m_streams.find(streamIndex);
91 if (it != m_streams.end()) {
92 auto &streamData = it->second;
93
94 const auto endPos = packetEndPos(stream, packet);
95 m_maxPacketsEndPos = qMax(m_maxPacketsEndPos, endPos);
96
97 // Increase buffered metrics as the packet has been processed.
98
99 streamData.bufferedDuration += streamTimeToUs(stream, avPacket.duration);
100 streamData.bufferedSize += avPacket.size;
101 streamData.maxSentPacketsPos = qMax(streamData.maxSentPacketsPos, endPos);
102 updateStreamDataLimitFlag(streamData);
103
104 if (!m_buffered && streamData.isDataLimitReached) {
105 m_buffered = true;
107 }
108
109 if (!m_firstPacketFound) {
110 m_firstPacketFound = true;
111 const auto pos = streamTimeToUs(stream, avPacket.pts);
112 emit firstPacketFound(std::chrono::steady_clock::now(), pos);
113 }
114
115 auto signal = signalByTrackType(it->second.trackType);
116 emit (this->*signal)(packet);
117 }
118
119 scheduleNextStep(false);
120}
121
123{
124 Q_ASSERT(packet.isValid());
125
126 if (packet.sourceId() != id())
127 return;
128
129 auto &avPacket = *packet.avPacket();
130
131 const auto streamIndex = avPacket.stream_index;
132 const auto stream = m_context->streams[streamIndex];
133 auto it = m_streams.find(streamIndex);
134
135 if (it != m_streams.end()) {
136 auto &streamData = it->second;
137
138 // Decrease buffered metrics as new data (the packet) has been received (buffered)
139
140 streamData.bufferedDuration -= streamTimeToUs(stream, avPacket.duration);
141 streamData.bufferedSize -= avPacket.size;
142 streamData.maxProcessedPacketPos =
143 qMax(streamData.maxProcessedPacketPos, packetEndPos(stream, packet));
144
145 Q_ASSERT(it->second.bufferedDuration >= 0);
146 Q_ASSERT(it->second.bufferedSize >= 0);
147
148 updateStreamDataLimitFlag(streamData);
149 }
150
152}
153
155{
156 auto isDataLimitReached = [](const auto &streamIndexToData) {
157 return streamIndexToData.second.isDataLimitReached;
158 };
159
160 // Demuxer waits:
161 // - if it's paused
162 // - if the end has been reached
163 // - if streams are empty (probably, should be handled on the initialization)
164 // - if at least one of the streams has reached the data limit (duration or size)
165
166 return PlaybackEngineObject::canDoNextStep() && !isAtEnd() && !m_streams.empty()
167 && std::none_of(m_streams.begin(), m_streams.end(), isDataLimitReached);
168}
169
170void Demuxer::ensureSeeked()
171{
172 if (std::exchange(m_seeked, true))
173 return;
174
175 if ((m_context->ctx_flags & AVFMTCTX_UNSEEKABLE) == 0) {
176 const qint64 seekPos = m_posWithOffset.pos * AV_TIME_BASE / 1000000;
177 auto err = av_seek_frame(m_context, -1, seekPos, AVSEEK_FLAG_BACKWARD);
178
179 if (err < 0) {
180 qCWarning(qLcDemuxer) << "Failed to seek, pos" << seekPos;
181
182 // Drop an error of seeking to initial position of streams with undefined duration.
183 // This needs improvements.
184 if (seekPos != 0 || m_context->duration > 0)
186 QLatin1StringView("Failed to seek: ") + err2str(err));
187 }
188 }
189
190 setAtEnd(false);
191}
192
208
209void Demuxer::setLoops(int loopsCount)
210{
211 qCDebug(qLcDemuxer) << "setLoops to demuxer" << loopsCount;
212 m_loops.storeRelease(loopsCount);
213}
214
215void Demuxer::updateStreamDataLimitFlag(StreamData &streamData)
216{
217 const auto packetsPosDiff = streamData.maxSentPacketsPos - streamData.maxProcessedPacketPos;
218 streamData.isDataLimitReached =
219 streamData.bufferedDuration >= MaxBufferedDurationUs
220 || (streamData.bufferedDuration == 0 && packetsPosDiff >= MaxBufferedDurationUs)
221 || streamData.bufferedSize >= MaxBufferedSize;
222}
223
224} // namespace QFFmpeg
225
227
228#include "moc_qffmpegdemuxer_p.cpp"
T loadAcquire() const noexcept
void storeRelease(T newValue) noexcept
Demuxer(AVFormatContext *context, const PositionWithOffset &posWithOffset, const StreamIndexes &streamIndexes, int loops)
void packetsBuffered()
void setLoops(int loopsCount)
void doNextStep() override
void firstPacketFound(TimePoint tp, qint64 trackPos)
void requestProcessVideoPacket(Packet)
void(Demuxer::*)(Packet) RequestingSignal
void onPacketProcessed(Packet)
void requestProcessAudioPacket(Packet)
void requestProcessSubtitlePacket(Packet)
static RequestingSignal signalByTrackType(QPlatformMediaPlayer::TrackType trackType)
bool canDoNextStep() const override
void scheduleNextStep(bool allowDoImmediatelly=true)
QSet< QString >::iterator it
auto signal
QString err2str(int errnum)
Definition qffmpeg_p.h:64
static qint64 packetEndPos(const AVStream *stream, const Packet &packet)
std::unique_ptr< AVPacket, AVDeleter< decltype(&av_packet_free), &av_packet_free > > AVPacketUPtr
Definition qffmpeg_p.h:141
std::array< int, 3 > StreamIndexes
std::optional< qint64 > mul(qint64 a, AVRational b)
Definition qffmpeg_p.h:39
static qint64 streamTimeToUs(const AVStream *stream, qint64 time)
Combined button and popup list for selecting options.
static void * context
DBusConnection const char DBusError * error
EGLStreamKHR stream
static QT_BEGIN_NAMESPACE constexpr qint64 MaxBufferedDurationUs
static constexpr qint64 MaxBufferedSize
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint res
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
long long qint64
Definition qtypes.h:60
AVPacket * avPacket() const
bool isValid() const
const LoopOffset & loopOffset() const
quint64 sourceId() const