Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
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
6#include <qloggingcategory.h>
7
9
10static Q_LOGGING_CATEGORY(qLcStreamDecoder, "qt.multimedia.ffmpeg.streamdecoder");
11
12namespace QFFmpeg {
13
15 : m_codec(codec),
16 m_absSeekPos(absSeekPos),
17 m_trackType(MediaDataHolder::trackTypeFromMediaType(codec.context()->codec_type))
18{
19 qCDebug(qLcStreamDecoder) << "Create stream decoder, trackType" << m_trackType
20 << "absSeekPos:" << absSeekPos;
22}
23
25{
26 avcodec_flush_buffers(m_codec.context());
27}
28
30{
31 decode({});
32}
33
35{
36 m_packets.enqueue(packet);
37
39}
40
42{
43 auto packet = m_packets.dequeue();
44
45 auto decodePacket = [this](Packet packet) {
47 decodeSubtitle(packet);
48 else
49 decodeMedia(packet);
50 };
51
52 if (packet.isValid() && packet.loopOffset().index != m_offset.index) {
53 decodePacket({});
54
55 qCDebug(qLcStreamDecoder) << "flush buffers due to new loop:" << packet.loopOffset().index;
56
57 avcodec_flush_buffers(m_codec.context());
58 m_offset = packet.loopOffset();
59 }
60
61 decodePacket(packet);
62
63 setAtEnd(!packet.isValid());
64
65 if (packet.isValid())
66 emit packetProcessed(packet);
67
68 scheduleNextStep(false);
69}
70
72{
73 return m_trackType;
74}
75
77{
78 if (frame.sourceId() != id())
79 return;
80
81 --m_pendingFramesCount;
82 Q_ASSERT(m_pendingFramesCount >= 0);
83
85}
86
88{
89 constexpr qint32 maxPendingFramesCount = 3;
90 constexpr qint32 maxPendingAudioFramesCount = 9;
91
92 const auto maxCount = m_trackType == QPlatformMediaPlayer::AudioStream
93 ? maxPendingAudioFramesCount
95 ? maxPendingFramesCount * 2 /*main packet and closing packet*/
96 : maxPendingFramesCount;
97
98 return !m_packets.empty() && m_pendingFramesCount < maxCount
100}
101
102void StreamDecoder::onFrameFound(Frame frame)
103{
104 if (frame.isValid() && frame.absoluteEnd() < m_absSeekPos)
105 return;
106
107 Q_ASSERT(m_pendingFramesCount >= 0);
108 ++m_pendingFramesCount;
110}
111
112void StreamDecoder::decodeMedia(Packet packet)
113{
114 auto sendPacketResult = sendAVPacket(packet);
115
116 if (sendPacketResult == AVERROR(EAGAIN)) {
117 // Doc says:
118 // AVERROR(EAGAIN): input is not accepted in the current state - user
119 // must read output with avcodec_receive_frame() (once
120 // all output is read, the packet should be resent, and
121 // the call will not fail with EAGAIN).
122 receiveAVFrames();
123 sendPacketResult = sendAVPacket(packet);
124
125 if (sendPacketResult != AVERROR(EAGAIN))
126 qWarning() << "Unexpected ffmpeg behavior";
127 }
128
129 if (sendPacketResult == 0)
130 receiveAVFrames();
131}
132
133int StreamDecoder::sendAVPacket(Packet packet)
134{
135 return avcodec_send_packet(m_codec.context(), packet.isValid() ? packet.avPacket() : nullptr);
136}
137
138void StreamDecoder::receiveAVFrames()
139{
140 while (true) {
141 auto avFrame = makeAVFrame();
142
143 const auto receiveFrameResult = avcodec_receive_frame(m_codec.context(), avFrame.get());
144
145 if (receiveFrameResult == AVERROR_EOF || receiveFrameResult == AVERROR(EAGAIN))
146 break;
147
148 if (receiveFrameResult < 0) {
149 emit error(QMediaPlayer::FormatError, err2str(receiveFrameResult));
150 break;
151 }
152
153 onFrameFound({ m_offset, std::move(avFrame), m_codec, 0, id() });
154 }
155}
156
157void StreamDecoder::decodeSubtitle(Packet packet)
158{
159 if (!packet.isValid())
160 return;
161 // qCDebug(qLcDecoder) << " decoding subtitle" << "has delay:" <<
162 // (codec->codec->capabilities & AV_CODEC_CAP_DELAY);
163 AVSubtitle subtitle;
164 memset(&subtitle, 0, sizeof(subtitle));
165 int gotSubtitle = 0;
166
167 const int res =
168 avcodec_decode_subtitle2(m_codec.context(), &subtitle, &gotSubtitle, packet.avPacket());
169 // qCDebug(qLcDecoder) << " subtitle got:" << res << gotSubtitle << subtitle.format <<
170 // Qt::hex << (quint64)subtitle.pts;
171 if (res < 0 || !gotSubtitle)
172 return;
173
174 // apparently the timestamps in the AVSubtitle structure are not always filled in
175 // if they are missing, use the packets pts and duration values instead
177 if (subtitle.pts == AV_NOPTS_VALUE) {
178 start = m_codec.toUs(packet.avPacket()->pts);
179 end = start + m_codec.toUs(packet.avPacket()->duration);
180 } else {
181 auto pts = timeStampUs(subtitle.pts, AVRational{ 1, AV_TIME_BASE });
182 start = *pts + qint64(subtitle.start_display_time) * 1000;
183 end = *pts + qint64(subtitle.end_display_time) * 1000;
184 }
185
186 if (end <= start) {
187 qWarning() << "Invalid subtitle time";
188 return;
189 }
190 // qCDebug(qLcDecoder) << " got subtitle (" << start << "--" << end << "):";
192 for (uint i = 0; i < subtitle.num_rects; ++i) {
193 const auto *r = subtitle.rects[i];
194 // qCDebug(qLcDecoder) << " subtitletext:" << r->text << "/" << r->ass;
195 if (i)
196 text += QLatin1Char('\n');
197 if (r->text)
198 text += QString::fromUtf8(r->text);
199 else {
200 const char *ass = r->ass;
201 int nCommas = 0;
202 while (*ass) {
203 if (nCommas == 8)
204 break;
205 if (*ass == ',')
206 ++nCommas;
207 ++ass;
208 }
209 text += QString::fromUtf8(ass);
210 }
211 }
214 text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
215 if (text.endsWith(QLatin1Char('\n')))
216 text.chop(1);
217
218 onFrameFound({ m_offset, text, start, end - start, id() });
219
220 // TODO: maybe optimize
221 onFrameFound({ m_offset, QString(), end, 0, id() });
222}
223} // namespace QFFmpeg
224
226
227#include "moc_qffmpegstreamdecoder_p.cpp"
AVCodecContext * context() const
qint64 toUs(qint64 ts) const
void error(int code, const QString &errorString)
void scheduleNextStep(bool allowDoImmediatelly=true)
StreamDecoder(const Codec &codec, qint64 absSeekPos)
bool canDoNextStep() const override
void onFrameProcessed(Frame frame)
void packetProcessed(Packet)
void requestHandleFrame(Frame frame)
QPlatformMediaPlayer::TrackType trackType() const
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3794
void chop(qsizetype n)
Removes n characters from the end of the string.
Definition qstring.cpp:6180
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5857
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5350
QString text
AVFrameUPtr makeAVFrame()
Definition qffmpeg_p.h:119
QString err2str(int errnum)
Definition qffmpeg_p.h:56
std::optional< qint64 > timeStampUs(qint64 ts, AVRational base)
Definition qffmpeg_p.h:46
Combined button and popup list for selecting options.
static void * context
QMediaFormat::AudioCodec codec
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLboolean r
[2]
GLuint GLuint end
GLuint start
GLuint res
GLsizei maxCount
Definition qopenglext.h:677
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define emit
int qint32
Definition qtypes.h:44
unsigned int uint
Definition qtypes.h:29
long long qint64
Definition qtypes.h:55
QObject::connect nullptr
QFrame frame
[0]
\inmodule QtCore \reentrant
Definition qchar.h:17