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 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | // 4 sec for buffering. TODO: maybe move to env var customization |
10 | static constexpr qint64 MaxBufferedDurationUs = 4'000'000; |
11 | |
12 | // around 4 sec of hdr video |
13 | static constexpr qint64 MaxBufferedSize = 32 * 1024 * 1024; |
14 | |
15 | namespace QFFmpeg { |
16 | |
17 | static Q_LOGGING_CATEGORY(qLcDemuxer, "qt.multimedia.ffmpeg.demuxer" ); |
18 | |
19 | static 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 | |
27 | static 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 | |
33 | static 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 | |
45 | Demuxer::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 | |
64 | void 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 | |
135 | void 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 | |
167 | bool 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 | |
183 | void 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 | |
206 | Demuxer::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 | |
222 | void Demuxer::setLoops(int loopsCount) |
223 | { |
224 | qCDebug(qLcDemuxer) << "setLoops to demuxer" << loopsCount; |
225 | m_loops.storeRelease(newValue: loopsCount); |
226 | } |
227 | |
228 | void 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 | |
239 | QT_END_NAMESPACE |
240 | |
241 | #include "moc_qffmpegdemuxer_p.cpp" |
242 | |