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
4#include "playbackengine/qffmpegdemuxer_p.h"
5#include <qloggingcategory.h>
6
7QT_BEGIN_NAMESPACE
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{
21 Q_ASSERT(stream);
22
23 const auto res = mul(a: time * 1000000, b: 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, time: packet.avPacket()->pts + packet.avPacket()->duration);
31}
32
33static bool isPacketWithinStreamDuration(const AVFormatContext *context, const Packet &packet)
34{
35 const qint64 streamDuration = context->streams[packet.avPacket()->stream_index]->duration;
36 if (streamDuration <= 0 || context->duration_estimation_method != AVFMT_DURATION_FROM_STREAM)
37 return true; // Stream duration shouldn't or doesn't need to be compared to pts
38
39 return packet.avPacket()->pts <= streamDuration;
40
41 // TODO: If there is a packet that starts before the canonical end of the stream but has a
42 // malformed duration, rework doNextStep to check for eof after that packet.
43}
44
45Demuxer::Demuxer(AVFormatContext *context, const PositionWithOffset &posWithOffset,
46 const StreamIndexes &streamIndexes, int loops)
47 : m_context(context), m_posWithOffset(posWithOffset), m_loops(loops)
48{
49 qCDebug(qLcDemuxer) << "Create demuxer."
50 << "pos:" << posWithOffset.pos << "loop offset:" << posWithOffset.offset.pos
51 << "loop index:" << posWithOffset.offset.index << "loops:" << loops;
52
53 Q_ASSERT(m_context);
54
55 for (auto i = 0; i < QPlatformMediaPlayer::NTrackTypes; ++i) {
56 if (streamIndexes[i] >= 0) {
57 const auto trackType = static_cast<QPlatformMediaPlayer::TrackType>(i);
58 qCDebug(qLcDemuxer) << "Activate demuxing stream" << i << ", trackType:" << trackType;
59 m_streams[streamIndexes[i]] = { .trackType: trackType };
60 }
61 }
62}
63
64void Demuxer::doNextStep()
65{
66 ensureSeeked();
67
68 Packet packet(m_posWithOffset.offset, AVPacketUPtr{ av_packet_alloc() }, id());
69 if (av_read_frame(s: m_context, pkt: packet.avPacket()) < 0
70 || !isPacketWithinStreamDuration(context: m_context, packet)) {
71 ++m_posWithOffset.offset.index;
72
73 const auto loops = m_loops.loadAcquire();
74 if (loops >= 0 && m_posWithOffset.offset.index >= loops) {
75 qCDebug(qLcDemuxer) << "finish demuxing";
76
77 if (!std::exchange(obj&: m_buffered, new_val: true))
78 emit packetsBuffered();
79
80 setAtEnd(true);
81 } else {
82 m_seeked = false;
83 m_posWithOffset.pos = 0;
84 m_posWithOffset.offset.pos = m_maxPacketsEndPos;
85 m_maxPacketsEndPos = 0;
86
87 ensureSeeked();
88
89 qCDebug(qLcDemuxer) << "Demuxer loops changed. Index:" << m_posWithOffset.offset.index
90 << "Offset:" << m_posWithOffset.offset.pos;
91
92 scheduleNextStep(allowDoImmediatelly: false);
93 }
94
95 return;
96 }
97
98 auto &avPacket = *packet.avPacket();
99
100 const auto streamIndex = avPacket.stream_index;
101 const auto stream = m_context->streams[streamIndex];
102
103 auto it = m_streams.find(x: streamIndex);
104 if (it != m_streams.end()) {
105 auto &streamData = it->second;
106
107 const auto endPos = packetEndPos(stream, packet);
108 m_maxPacketsEndPos = qMax(a: m_maxPacketsEndPos, b: endPos);
109
110 // Increase buffered metrics as the packet has been processed.
111
112 streamData.bufferedDuration += streamTimeToUs(stream, time: avPacket.duration);
113 streamData.bufferedSize += avPacket.size;
114 streamData.maxSentPacketsPos = qMax(a: streamData.maxSentPacketsPos, b: endPos);
115 updateStreamDataLimitFlag(streamData);
116
117 if (!m_buffered && streamData.isDataLimitReached) {
118 m_buffered = true;
119 emit packetsBuffered();
120 }
121
122 if (!m_firstPacketFound) {
123 m_firstPacketFound = true;
124 const auto pos = streamTimeToUs(stream, time: avPacket.pts);
125 emit firstPacketFound(tp: std::chrono::steady_clock::now(), trackPos: pos);
126 }
127
128 auto signal = signalByTrackType(trackType: it->second.trackType);
129 emit (this->*signal)(packet);
130 }
131
132 scheduleNextStep(allowDoImmediatelly: false);
133}
134
135void Demuxer::onPacketProcessed(Packet packet)
136{
137 Q_ASSERT(packet.isValid());
138
139 if (packet.sourceId() != id())
140 return;
141
142 auto &avPacket = *packet.avPacket();
143
144 const auto streamIndex = avPacket.stream_index;
145 const auto stream = m_context->streams[streamIndex];
146 auto it = m_streams.find(x: streamIndex);
147
148 if (it != m_streams.end()) {
149 auto &streamData = it->second;
150
151 // Decrease buffered metrics as new data (the packet) has been received (buffered)
152
153 streamData.bufferedDuration -= streamTimeToUs(stream, time: avPacket.duration);
154 streamData.bufferedSize -= avPacket.size;
155 streamData.maxProcessedPacketPos =
156 qMax(a: streamData.maxProcessedPacketPos, b: packetEndPos(stream, packet));
157
158 Q_ASSERT(it->second.bufferedDuration >= 0);
159 Q_ASSERT(it->second.bufferedSize >= 0);
160
161 updateStreamDataLimitFlag(streamData);
162 }
163
164 scheduleNextStep();
165}
166
167bool Demuxer::canDoNextStep() const
168{
169 auto isDataLimitReached = [](const auto &streamIndexToData) {
170 return streamIndexToData.second.isDataLimitReached;
171 };
172
173 // Demuxer waits:
174 // - if it's paused
175 // - if the end has been reached
176 // - if streams are empty (probably, should be handled on the initialization)
177 // - if at least one of the streams has reached the data limit (duration or size)
178
179 return PlaybackEngineObject::canDoNextStep() && !isAtEnd() && !m_streams.empty()
180 && std::none_of(first: m_streams.begin(), last: m_streams.end(), pred: isDataLimitReached);
181}
182
183void Demuxer::ensureSeeked()
184{
185 if (std::exchange(obj&: m_seeked, new_val: true))
186 return;
187
188 if ((m_context->ctx_flags & AVFMTCTX_UNSEEKABLE) == 0) {
189 const qint64 seekPos = m_posWithOffset.pos * AV_TIME_BASE / 1000000;
190 auto err = av_seek_frame(s: m_context, stream_index: -1, timestamp: seekPos, AVSEEK_FLAG_BACKWARD);
191
192 if (err < 0) {
193 qCWarning(qLcDemuxer) << "Failed to seek, pos" << seekPos;
194
195 // Drop an error of seeking to initial position of streams with undefined duration.
196 // This needs improvements.
197 if (seekPos != 0 || m_context->duration > 0)
198 emit error(code: QMediaPlayer::ResourceError,
199 errorString: QLatin1StringView("Failed to seek: ") + err2str(errnum: err));
200 }
201 }
202
203 setAtEnd(false);
204}
205
206Demuxer::RequestingSignal Demuxer::signalByTrackType(QPlatformMediaPlayer::TrackType trackType)
207{
208 switch (trackType) {
209 case QPlatformMediaPlayer::TrackType::VideoStream:
210 return &Demuxer::requestProcessVideoPacket;
211 case QPlatformMediaPlayer::TrackType::AudioStream:
212 return &Demuxer::requestProcessAudioPacket;
213 case QPlatformMediaPlayer::TrackType::SubtitleStream:
214 return &Demuxer::requestProcessSubtitlePacket;
215 default:
216 Q_ASSERT(!"Unknown track type");
217 }
218
219 return nullptr;
220}
221
222void Demuxer::setLoops(int loopsCount)
223{
224 qCDebug(qLcDemuxer) << "setLoops to demuxer" << loopsCount;
225 m_loops.storeRelease(newValue: loopsCount);
226}
227
228void Demuxer::updateStreamDataLimitFlag(StreamData &streamData)
229{
230 const auto packetsPosDiff = streamData.maxSentPacketsPos - streamData.maxProcessedPacketPos;
231 streamData.isDataLimitReached =
232 streamData.bufferedDuration >= MaxBufferedDurationUs
233 || (streamData.bufferedDuration == 0 && packetsPosDiff >= MaxBufferedDurationUs)
234 || streamData.bufferedSize >= MaxBufferedSize;
235}
236
237} // namespace QFFmpeg
238
239QT_END_NAMESPACE
240
241#include "moc_qffmpegdemuxer_p.cpp"
242

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegdemuxer.cpp