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