Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegstreamdecoder.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
6#include "playbackengine/qffmpegmediadataholder_p.h"
7#include <QtCore/qloggingcategory.h>
8
10
11Q_STATIC_LOGGING_CATEGORY(qLcStreamDecoder, "qt.multimedia.ffmpeg.streamdecoder");
12
13namespace QFFmpeg {
14
15StreamDecoder::StreamDecoder(const PlaybackEngineObjectID &id, const CodecContext &codecContext,
16 TrackPosition absSeekPos)
17 : PlaybackEngineObject(id),
18 m_codecContext(codecContext),
19 m_trackType(MediaDataHolder::trackTypeFromMediaType(codecContext.context()->codec_type)),
20 m_sessionCtx{ absSeekPos }
21{
22 qCDebug(qLcStreamDecoder) << "Create stream decoder, trackType" << m_trackType
23 << "absSeekPos:" << absSeekPos.get();
24 Q_ASSERT(m_trackType != QPlatformMediaPlayer::NTrackTypes);
25}
26
28{
29 avcodec_flush_buffers(m_codecContext.context());
30}
31
32void StreamDecoder::seek(quint64 sessionID, TrackPosition pos, const LoopOffset &offset)
33{
34 updateSession(sessionID, [this, pos, offset]() {
35 m_sessionCtx = { offset.loopStartTimeUs.asDuration() + pos };
36 avcodec_flush_buffers(m_codecContext.context());
37 });
38}
39
40void StreamDecoder::onFinalPacketReceived(PlaybackEngineObjectID sourceID)
41{
42 if (checkSessionID(sourceID.sessionID))
43 decode({});
44}
45
46void StreamDecoder::decode(Packet packet)
47{
48 if (packet.isValid() && !checkSessionID(packet.sourceID().sessionID)) {
49 qCDebug(qLcStreamDecoder) << "Packet session outdated. Source id:" << packet.sourceID()
50 << "current id" << id();
51 // no need to report packetProcessed: demuxer must be cleaned up
52 return;
53 }
54
55 m_sessionCtx.packets.enqueue(std::move(packet));
56 scheduleNextStep();
57}
58
60{
61 Packet packet = m_sessionCtx.packets.dequeue();
62
63 auto decodePacket = [this](const Packet &packet) {
64 if (trackType() == QPlatformMediaPlayer::SubtitleStream)
65 decodeSubtitle(packet);
66 else
67 decodeMedia(packet);
68 };
69
70 if (packet.isValid() && packet.loopOffset().loopIndex != m_sessionCtx.offset.loopIndex) {
71 decodePacket({});
72
73 qCDebug(qLcStreamDecoder) << "flush buffers due to new loop:"
74 << packet.loopOffset().loopIndex;
75
76 avcodec_flush_buffers(m_codecContext.context());
77 m_sessionCtx.offset = packet.loopOffset();
78 }
79
80 decodePacket(packet);
81
82 setAtEnd(!packet.isValid());
83
84 if (packet.isValid())
85 emit packetProcessed(std::move(packet));
86
87 scheduleNextStep();
88}
89
91{
92 return m_trackType;
93}
94
96{
97 switch (type) {
98
100 return 3;
102 return 9;
104 return 6; /*main packet and closing packet*/
105 default:
107 }
108}
109
110void StreamDecoder::onFrameProcessed(const Frame &frame)
111{
112 if (!checkID(frame.sourceID()))
113 return;
114
115 --m_sessionCtx.pendingFramesCount;
116 Q_ASSERT(m_sessionCtx.pendingFramesCount >= 0);
117
118 scheduleNextStep();
119}
120
122{
123 const qint32 maxCount = maxQueueSize(m_trackType);
124
125 return !m_sessionCtx.packets.empty() && m_sessionCtx.pendingFramesCount < maxCount
126 && PlaybackEngineObject::canDoNextStep();
127}
128
129void StreamDecoder::onFrameFound(const Frame &frame)
130{
131 if (frame.isValid() && frame.absoluteEnd() < m_sessionCtx.absSeekPos)
132 return;
133
134 Q_ASSERT(m_sessionCtx.pendingFramesCount >= 0);
135 ++m_sessionCtx.pendingFramesCount;
136 emit requestHandleFrame(frame);
137}
138
139void StreamDecoder::decodeMedia(const Packet &packet)
140{
141 auto sendPacketResult = sendAVPacket(packet);
142
143 if (sendPacketResult == AVERROR(EAGAIN)) {
144 // Doc says:
145 // AVERROR(EAGAIN): input is not accepted in the current state - user
146 // must read output with avcodec_receive_frame() (once
147 // all output is read, the packet should be resent, and
148 // the call will not fail with EAGAIN).
149 receiveAVFrames();
150 sendPacketResult = sendAVPacket(packet);
151
152 if (sendPacketResult != AVERROR(EAGAIN))
153 qWarning() << "Unexpected FFmpeg behavior";
154 }
155
156 if (sendPacketResult == 0)
157 receiveAVFrames(!packet.isValid());
158}
159
160int StreamDecoder::sendAVPacket(const Packet &packet)
161{
162 return avcodec_send_packet(m_codecContext.context(), packet.isValid() ? packet.avPacket() : nullptr);
163}
164
165void StreamDecoder::receiveAVFrames(bool flushPacket)
166{
167 while (true) {
168 auto avFrame = makeAVFrame();
169
170 const auto receiveFrameResult = avcodec_receive_frame(m_codecContext.context(), avFrame.get());
171
172 if (receiveFrameResult == AVERROR_EOF || receiveFrameResult == AVERROR(EAGAIN)) {
173 if (flushPacket && receiveFrameResult == AVERROR(EAGAIN)) {
174 // The documentation says that in the EAGAIN state output is not available. The new
175 // input must be sent. It does not say that this state can also be returned for
176 // Android MediaCodec when the ff_AMediaCodec_dequeueOutputBuffer call times out.
177 // The flush packet means it is the end of the stream. No more packets are available,
178 // so getting EAGAIN is unexpected here. At this point, the EAGAIN status was probably
179 // caused by a timeout in the ffmpeg implementation, not by too few packets. That is
180 // why there will be another try of calling avcodec_receive_frame
181 qWarning() << "Unexpected FFmpeg behavior: EAGAIN state for avcodec_receive_frame "
182 << "at end of the stream";
183 flushPacket = false;
184 continue;
185 }
186 break;
187 }
188
189 if (receiveFrameResult < 0) {
190 emit error(QMediaPlayer::FormatError, err2str(receiveFrameResult));
191 break;
192 }
193
194
195 // Avoid starvation on FFmpeg decoders with fixed size frame pool
196 if (m_trackType == QPlatformMediaPlayer::VideoStream)
197 avFrame = copyFromHwPool(std::move(avFrame));
198
199 onFrameFound({ m_sessionCtx.offset, std::move(avFrame), m_codecContext, id() });
200 }
201}
202
203void StreamDecoder::decodeSubtitle(const Packet &packet)
204{
205 if (!packet.isValid())
206 return;
207 // qCDebug(qLcDecoder) << " decoding subtitle" << "has delay:" <<
208 // (codec->codec->capabilities & AV_CODEC_CAP_DELAY);
209 AVSubtitle subtitle;
210 memset(&subtitle, 0, sizeof(subtitle));
211 int gotSubtitle = 0;
212
213 const int res =
214 avcodec_decode_subtitle2(m_codecContext.context(), &subtitle, &gotSubtitle, packet.avPacket());
215 // qCDebug(qLcDecoder) << " subtitle got:" << res << gotSubtitle << subtitle.format <<
216 // Qt::hex << (quint64)subtitle.pts;
217 if (res < 0 || !gotSubtitle)
218 return;
219
220 // apparently the timestamps in the AVSubtitle structure are not always filled in
221 // if they are missing, use the packets pts and duration values instead
222 TrackPosition start = 0, end = 0;
223 if (subtitle.pts == AV_NOPTS_VALUE) {
224 start = m_codecContext.toTrackPosition(AVStreamPosition(packet.avPacket()->pts));
225 end = start + m_codecContext.toTrackDuration(AVStreamDuration(packet.avPacket()->duration));
226 } else {
227 auto pts = timeStampUs(subtitle.pts, AVRational{ 1, AV_TIME_BASE });
228 start = TrackPosition(*pts + qint64(subtitle.start_display_time) * 1000);
229 end = TrackPosition(*pts + qint64(subtitle.end_display_time) * 1000);
230 }
231
232 if (end <= start) {
233 qWarning() << "Invalid subtitle time";
234 return;
235 }
236 // qCDebug(qLcDecoder) << " got subtitle (" << start << "--" << end << "):";
237 QString text;
238 for (uint i = 0; i < subtitle.num_rects; ++i) {
239 const auto *r = subtitle.rects[i];
240 // qCDebug(qLcDecoder) << " subtitletext:" << r->text << "/" << r->ass;
241 if (i)
242 text += QLatin1Char('\n');
243 if (r->text)
244 text += QString::fromUtf8(r->text);
245 else {
246 const char *ass = r->ass;
247 int nCommas = 0;
248 while (*ass) {
249 if (nCommas == 8)
250 break;
251 if (*ass == ',')
252 ++nCommas;
253 ++ass;
254 }
255 text += QString::fromUtf8(ass);
256 }
257 }
258 text.replace(QLatin1String("\\N"), QLatin1String("\n"));
259 text.replace(QLatin1String("\\n"), QLatin1String("\n"));
260 text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
261 if (text.endsWith(QLatin1Char('\n')))
262 text.chop(1);
263
264 onFrameFound({ m_sessionCtx.offset, text, start, end - start, id() });
265
266 // TODO: maybe optimize
267 onFrameFound({ m_sessionCtx.offset, QString(), end, TrackDuration(0), id() });
268}
269} // namespace QFFmpeg
270
271QT_END_NAMESPACE
272
273#include "moc_qffmpegstreamdecoder_p.cpp"
bool canDoNextStep() const override
void onFrameProcessed(const Frame &frame)
void onFinalPacketReceived(PlaybackEngineObjectID sourceID)
void seek(quint64 sessionID, TrackPosition pos, const LoopOffset &offset)
QPlatformMediaPlayer::TrackType trackType() const
QT_MANGLE_NAMESPACE(QMacScreenCaptureStreamDelegate) QMacScreenCaptureStreamDelegate
Combined button and popup list for selecting options.