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
qaudio_parsing_support.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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// Qt-Security score:critical reason:data-parser
4
6
9
10#include <QtCore/qbytearrayview.h>
11
12#include <array>
13
15
17
18namespace {
19
20constexpr std::byte operator""_byte(unsigned long long value)
21{
22 return std::byte(value);
23}
24
25// QSpan::operator[] asserts on out-of-bounds access, so every read below is
26// bounds-checked in debug builds.
27constexpr quint8 byteAt(QSpan<const std::byte> data, qsizetype index)
28{
29 return std::to_integer<quint8>(data[index]);
30}
31
32// Returns true if data begins with marker.
33bool hasMarker(QSpan<const std::byte> data, QByteArrayView marker)
34{
35 return QByteArrayView(data).startsWith(marker);
36}
37
38// Scans data[from..] for a frame sync word: a 0xFF byte whose successor matches
39// secondByteValue under secondByteMask, and whose header frameSize() accepts.
40// headerSize is the number of bytes frameSize() needs in order to decide.
41std::optional<qsizetype> findSyncWord(QSpan<const std::byte> data, qsizetype from,
42 std::byte secondByteMask, std::byte secondByteValue,
43 qsizetype headerSize,
44 qsizetype (*frameSize)(QSpan<const std::byte>, qsizetype))
45{
46 const QByteArrayView view(data);
47 const qsizetype last = view.size() - headerSize;
48
49 for (qsizetype i = qMax<qsizetype>(from, 0); i <= last;) {
50 const qsizetype sync = view.indexOf(char(0xFF), i);
51 if (sync < 0 || sync > last)
52 return std::nullopt;
53 if ((data[sync + 1] & secondByteMask) == secondByteValue && frameSize(data, sync) > 0) {
54 return sync;
55 }
56 i = sync + 1;
57 }
58 return std::nullopt;
59}
60
61// Reads 1-4 bytes as an unsigned big-endian integer.
62quint32 fromBigEndian(QSpan<const std::byte> data)
63{
64 Q_ASSERT(data.size() <= 4);
65 quint32 value = 0;
66 for (std::byte b : data)
67 value = (value << 8) | std::to_integer<quint32>(b);
68 return value;
69}
70
71// Reads 1-4 bytes as an unsigned little-endian integer.
72quint32 fromLittleEndian(QSpan<const std::byte> data)
73{
74 Q_ASSERT(data.size() <= 4);
75 quint32 value = 0;
76 for (qsizetype i = data.size() - 1; i >= 0; --i)
77 value = (value << 8) | std::to_integer<quint32>(data[i]);
78 return value;
79}
80
81// CRC-8 with polynomial x^8 + x^2 + x + 1 (0x07) and initial value 0, as used
82// by the FLAC frame header.
83quint8 crc8(QSpan<const std::byte> data)
84{
85 quint8 crc = 0;
86 for (std::byte b : data) {
87 crc ^= std::to_integer<quint8>(b);
88 for (int bit = 0; bit < 8; ++bit)
89 crc = (crc & 0x80) ? quint8((crc << 1) ^ 0x07) : quint8(crc << 1);
90 }
91 return crc;
92}
93
94// Returns the length in bytes of the FLAC frame header starting at
95// data[offset], including its trailing CRC-8 byte, or 0 if the header is
96// invalid or runs past the end of data. A FLAC frame header is between 6 and
97// 16 bytes long, so a truncated tail is reported as invalid rather than
98// guessed at.
99qsizetype flacFrameHeaderSize(QSpan<const std::byte> data, qsizetype offset)
100{
101 if (offset < 0)
102 return 0;
103
104 const QSpan<const std::byte> frame = drop(data, offset);
105 if (frame.size() < 6) // 4 header bytes + 1 coded-number byte + CRC-8
106 return 0;
107
108 // 14-bit sync code 0b11111111111110, then a reserved bit that must be 0,
109 // then the blocking strategy bit.
110 if (frame[0] != 0xFF_byte || (frame[1] & 0xFE_byte) != 0xF8_byte)
111 return 0;
112
113 const quint8 blockSizeCode = byteAt(frame, 2) >> 4;
114 const quint8 sampleRateCode = byteAt(frame, 2) & 0x0F;
115 const quint8 channelCode = byteAt(frame, 3) >> 4;
116 const quint8 sampleSizeCode = (byteAt(frame, 3) >> 1) & 0x07;
117
118 // Block size code 0, sample rate code 15, channel assignments above 10 and
119 // sample size code 3 are all reserved, as is the last bit of byte 3.
120 if (blockSizeCode == 0 || sampleRateCode == 0x0F || channelCode > 10
121 || sampleSizeCode == 3 || (byteAt(frame, 3) & 0x01) != 0) {
122 return 0;
123 }
124
125 // The coded frame or sample number uses a UTF-8 style variable length
126 // encoding of one to seven bytes.
127 const std::byte codedNumberLead = frame[4];
128 qsizetype codedNumberSize = 0;
129 if ((codedNumberLead & 0x80_byte) == 0x00_byte)
130 codedNumberSize = 1;
131 else if ((codedNumberLead & 0xE0_byte) == 0xC0_byte)
132 codedNumberSize = 2;
133 else if ((codedNumberLead & 0xF0_byte) == 0xE0_byte)
134 codedNumberSize = 3;
135 else if ((codedNumberLead & 0xF8_byte) == 0xF0_byte)
136 codedNumberSize = 4;
137 else if ((codedNumberLead & 0xFC_byte) == 0xF8_byte)
138 codedNumberSize = 5;
139 else if ((codedNumberLead & 0xFE_byte) == 0xFC_byte)
140 codedNumberSize = 6;
141 else if (codedNumberLead == 0xFE_byte)
142 codedNumberSize = 7;
143 else
144 return 0;
145
146 if (frame.size() < 4 + codedNumberSize)
147 return 0;
148 const bool continuationBytesValid =
149 ranges::all_of(frame.subspan(5, codedNumberSize - 1), [](std::byte b) {
150 return (b & std::byte{ 0xC0 }) == std::byte{ 0x80 };
151 });
152 if (!continuationBytesValid)
153 return 0;
154
155 qsizetype headerSize = 4 + codedNumberSize;
156
157 // Block size codes 6 and 7, and sample rate codes 12 to 14, carry their
158 // value in extra bytes placed after the coded number.
159 if (blockSizeCode == 6)
160 headerSize += 1;
161 else if (blockSizeCode == 7)
162 headerSize += 2;
163
164 if (sampleRateCode == 12)
165 headerSize += 1;
166 else if (sampleRateCode == 13 || sampleRateCode == 14)
167 headerSize += 2;
168
169 // The CRC-8 covers every header byte before it. Checking it is what makes
170 // a sync scan reliable, since the sync code alone occurs often in the
171 // compressed subframe data that follows.
172 if (frame.size() < headerSize + 1)
173 return 0;
174 if (crc8(take(frame, headerSize)) != byteAt(frame, headerSize))
175 return 0;
176
177 return headerSize + 1;
178}
179
180} // namespace
181
182AudioCodec sniffCodec(QSpan<const std::byte> header)
183{
184 if (header.size() < 4)
185 return AudioCodec::Unknown;
186
187 // ID3 tag -> MP3
188 if (hasMarker(header, "ID3"))
189 return AudioCodec::Mp3;
190
191 // AAC ADTS sync word. Checked before the MPEG audio sync word below, whose
192 // 0xFFE0 mask also matches an ADTS header. The two are told apart by the
193 // layer bits: MPEG Layer III uses 01, ADTS always leaves them 00.
194 if (header[0] == 0xFF_byte && (header[1] & 0xF6_byte) == 0xF0_byte)
195 return AudioCodec::Aac;
196
197 // MPEG audio sync word (0xFFE0 mask) -> MP3
198 if (header[0] == 0xFF_byte && (header[1] & 0xE0_byte) == 0xE0_byte)
199 return AudioCodec::Mp3;
200
201 // FLAC stream marker
202 if (hasMarker(header, "fLaC"))
203 return AudioCodec::Flac;
204
205 // Ogg container. The identification packet on the first page says whether
206 // the stream is Vorbis or Opus. If it has not arrived yet, assume Opus,
207 // which is the more common of the two on the web.
208 if (hasMarker(header, "OggS")) {
209 const std::optional<OggStreamInfo> info = oggStreamInfo(header);
210 return info ? info->codec : AudioCodec::Opus;
211 }
212
213 // WAV / RIFF - uncompressed
214 if (hasMarker(header, "RIFF"))
215 return AudioCodec::Wav;
216
217 return AudioCodec::Unknown;
218}
219
221{
222 return codec == AudioCodec::Mp3 || codec == AudioCodec::Aac || codec == AudioCodec::Flac;
223}
224
225std::optional<OggStreamInfo> oggStreamInfo(QSpan<const std::byte> data)
226{
227 // Ogg page header: "OggS", then 22 more bytes, then the segment table
228 // whose length is given by the segment count at offset 26.
229 if (data.size() < 27 || !hasMarker(data, "OggS"))
230 return std::nullopt;
231
232 const qsizetype packetOffset = 27 + byteAt(data, 26);
233 const QSpan<const std::byte> packet = drop(data, packetOffset);
234 // Both identification packets are longer than this, but 16 bytes covers
235 // the magic, the channel count and the sample rate, which is all we read.
236 if (packet.size() < 16)
237 return std::nullopt;
238
239 // Both Vorbis and Opus store the sample rate as a little-endian 32-bit
240 // value at offset 12 of the identification packet.
241 const auto rateAt12 = [&packet] {
242 return int(fromLittleEndian(packet.subspan(12, 4)));
243 };
244
245 // Vorbis identification packet: 0x01 followed by "vorbis".
246 if (hasMarker(packet, QByteArrayView("\x01vorbis", 7))) {
247 OggStreamInfo info;
249 info.numChannels = byteAt(packet, 11);
250 info.sampleRate = rateAt12();
251 if (info.sampleRate <= 0 || info.numChannels <= 0)
252 return std::nullopt;
253 return info;
254 }
255
256 // Opus identification header.
257 if (hasMarker(packet, "OpusHead")) {
258 OggStreamInfo info;
260 info.numChannels = byteAt(packet, 9);
261 info.sampleRate = rateAt12();
262 if (info.sampleRate <= 0 || info.numChannels <= 0)
263 return std::nullopt;
264 return info;
265 }
266
267 return std::nullopt;
268}
269
270std::optional<FlacStreamInfo> flacStreamInfo(QSpan<const std::byte> data)
271{
272 // "fLaC" marker + 4-byte block header + 34-byte STREAMINFO = 42 bytes minimum
273 if (data.size() < 42 || !hasMarker(data, "fLaC"))
274 return std::nullopt;
275
276 // Block header byte: bit 7 = last-metadata-block flag, bits [6:0] = block type.
277 // Block type 0 is STREAMINFO, which must be the first metadata block.
278 if ((data[4] & 0x7F_byte) != 0x00_byte)
279 return std::nullopt;
280
281 const qsizetype blockLength = fromBigEndian(data.subspan(5, 3));
282 if (blockLength < 34 || data.size() < 8 + blockLength)
283 return std::nullopt;
284
285 FlacStreamInfo info;
286 // STREAMINFO data starts at byte 8.
287 // Sample rate: top 20 bits spanning bytes [18..20]
288 info.sampleRate = fromBigEndian(data.subspan(18, 3)) >> 4;
289 // Number of channels: next 3 bits (bits [3:1] of byte 20)
290 info.numChannels = ((byteAt(data, 20) >> 1) & 0x7) + 1;
291 // Total samples: the 36 bits after the 5-bit sample size, which is the low
292 // 4 bits of byte 21 followed by bytes [22..25].
293 info.totalSamples = (qint64(byteAt(data, 21) & 0x0F) << 32)
294 | (qint64(byteAt(data, 22)) << 24) | (byteAt(data, 23) << 16)
295 | (byteAt(data, 24) << 8) | byteAt(data, 25);
296 return info;
297}
298
299std::optional<qsizetype> flacAudioOffset(QSpan<const std::byte> data)
300{
301 if (data.size() < 8 || !hasMarker(data, "fLaC"))
302 return std::nullopt;
303
304 qsizetype offset = 4; // skip "fLaC"
305 while (offset + 4 <= data.size()) {
306 const bool isLast = (data[offset] & 0x80_byte) != 0x00_byte;
307 const qsizetype blockLength = fromBigEndian(data.subspan(offset + 1, 3));
308 offset += 4 + blockLength;
309 if (isLast)
310 return offset <= data.size() ? std::optional(offset) : std::nullopt;
311 }
312 return std::nullopt; // metadata blocks not fully buffered yet
313}
314
315std::optional<qsizetype> findFlacSync(QSpan<const std::byte> data, qsizetype from)
316{
317 const QByteArrayView view(data);
318 for (qsizetype i = qMax<qsizetype>(from, 0); i < view.size();) {
319 const qsizetype sync = view.indexOf(char(0xFF), i);
320 if (sync < 0)
321 return std::nullopt;
322 if (flacFrameHeaderSize(data, sync) > 0)
323 return sync;
324 i = sync + 1;
325 }
326 return std::nullopt;
327}
328
329qsizetype mpegFrameSize(QSpan<const std::byte> data, qsizetype offset)
330{
331 if (offset < 0)
332 return 0;
333
334 const QSpan<const std::byte> header = take(drop(data, offset), 4);
335 if (header.size() < 4)
336 return 0;
337
338 if (header[0] != 0xFF_byte || (header[1] & 0xE0_byte) != 0xE0_byte)
339 return 0;
340
341 const int versionBits = (byteAt(header, 1) >> 3) & 0x3; // 3=MPEG1, 2=MPEG2, 0=MPEG2.5
342 const int layerBits = (byteAt(header, 1) >> 1) & 0x3; // 1=LayerIII
343 const int bitrateIndex = (byteAt(header, 2) >> 4) & 0xF;
344 const int sampleRateIndex = (byteAt(header, 2) >> 2) & 0x3;
345 const int padding = (byteAt(header, 2) >> 1) & 0x1;
346
347 if (layerBits != 1)
348 return 0; // only Layer III
349 if (sampleRateIndex == 3)
350 return 0; // reserved sample rate
351 if (bitrateIndex == 0 || bitrateIndex == 15)
352 return 0; // free-format / bad index
353
354 static constexpr std::array<int, 16> bitratesV1 = // MPEG1 Layer III (kbps)
355 { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 };
356 static constexpr std::array<int, 16> bitratesV2 = // MPEG2/2.5 Layer III (kbps)
357 { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 };
358
359 static constexpr std::array<std::array<int, 3>, 4> sampleRates = {{
360 { 11025, 12000, 8000 }, // MPEG 2.5 (versionBits == 0)
361 { 0, 0, 0 }, // reserved (versionBits == 1)
362 { 22050, 24000, 16000 }, // MPEG 2 (versionBits == 2)
363 { 44100, 48000, 32000 }, // MPEG 1 (versionBits == 3)
364 }};
365
366 const int sampleRate = sampleRates[versionBits][sampleRateIndex];
367 if (sampleRate == 0)
368 return 0;
369
370 const bool isMpeg1 = (versionBits == 3);
371 const int bitrateBps = (isMpeg1 ? bitratesV1 : bitratesV2)[bitrateIndex] * 1000;
372 if (bitrateBps == 0)
373 return 0;
374
375 // MPEG1 Layer III: frameSize = floor(144 * bitrate / sampleRate) + padding
376 // MPEG2/2.5 Layer III: frameSize = floor( 72 * bitrate / sampleRate) + padding
377 const int coefficient = isMpeg1 ? 144 : 72;
378 const qsizetype frameSize = (coefficient * bitrateBps / sampleRate) + padding;
379 return frameSize > 4 ? frameSize : 0; // must be larger than the header itself
380}
381
382std::optional<qsizetype> findMpegSync(QSpan<const std::byte> data, qsizetype from)
383{
384 return findSyncWord(data, from, 0xE0_byte, 0xE0_byte, 4, mpegFrameSize);
385}
386
387qsizetype adtsFrameSize(QSpan<const std::byte> data, qsizetype offset)
388{
389 if (offset < 0)
390 return 0;
391
392 const QSpan<const std::byte> header = take(drop(data, offset), 7);
393 if (header.size() < 7)
394 return 0;
395
396 // 12-bit sync word: 0xFFF
397 if (header[0] != 0xFF_byte || (header[1] & 0xF0_byte) != 0xF0_byte)
398 return 0;
399
400 // aac_frame_length is a 13-bit field at bits [30:18] of the ADTS header
401 // that gives the total byte length including the header itself.
402 const qsizetype frameLength = ((byteAt(header, 3) & 0x03) << 11) | (byteAt(header, 4) << 3)
403 | (byteAt(header, 5) >> 5);
404 return frameLength >= 7 ? frameLength : 0;
405}
406
407std::optional<qsizetype> findAdtsSync(QSpan<const std::byte> data, qsizetype from)
408{
409 return findSyncWord(data, from, 0xF0_byte, 0xF0_byte, 7, adtsFrameSize);
410}
411
412} // namespace QtMultimediaPrivate::ParsingSupport
413
414QT_END_NAMESPACE
Combined button and popup list for selecting options.
std::optional< qsizetype > findAdtsSync(QSpan< const std::byte > data, qsizetype from)
std::optional< OggStreamInfo > oggStreamInfo(QSpan< const std::byte > data)
std::optional< qsizetype > findFlacSync(QSpan< const std::byte > data, qsizetype from)
AudioCodec sniffCodec(QSpan< const std::byte > header)
std::optional< qsizetype > findMpegSync(QSpan< const std::byte > data, qsizetype from)
qsizetype adtsFrameSize(QSpan< const std::byte > data, qsizetype offset)
std::optional< FlacStreamInfo > flacStreamInfo(QSpan< const std::byte > data)
std::optional< qsizetype > flacAudioOffset(QSpan< const std::byte > data)
qsizetype mpegFrameSize(QSpan< const std::byte > data, qsizetype offset)