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