DiscordCoreAPI
A Discord bot library written in C++, with custom asynchronous coroutines.
Loading...
Searching...
No Matches
Demuxers.hpp
Go to the documentation of this file.
1/*
2 MIT License
3
4 DiscordCoreAPI, A bot library for Discord, written in C++, and featuring explicit multithreading through the usage of custom, asynchronous C++ CoRoutines.
5
6 Copyright 2022, 2023 Chris M. (RealTimeChris)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26/// Demuxers.hpp - Header file for the Demuxer classes.
27/// Jun 8, 2023
28/// https://discordcoreapi.com
29/// \file Demuxers.hpp
30
31#pragma once
32
34#include <opus/opus.h>
35#include <fstream>
36
37namespace DiscordCoreAPI {
38
39 namespace DiscordCoreInternal {
40
41 /**
42 * \addtogroup discord_core_internal
43 * @{
44 */
45
46 static constexpr uint32_t segmentId{ 0x18538067 };
47 static constexpr uint8_t simpleBlockId{ 0xA3 };
48 static constexpr uint8_t opusTrackId{ 0x81 };
49
50 static constexpr uint8_t ffLog2Tab[]{ 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
51 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
52 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
53 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
54 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 };
55
56 /// @brief A class for demuxing Matroska-contained audio data.
58 public:
59 /// @brief Constructor for MatroskaDemuxer.
60 inline MatroskaDemuxer() = default;
61
62 /// @brief Writes data to the Matroska demuxer.
63 /// @param dataNew The data to be written.
64 inline void writeData(std::basic_string_view<uint8_t> dataNew) {
65 data = dataNew;
66 }
67
68 /// @brief Collects the next frame from the demuxer.
69 /// @param frameNew The reference to store the collected frame.
70 /// @return True if a frame was collected, false otherwise.
71 inline bool collectFrame(AudioFrameData& frameNew) {
72 if (frames.size() > 0) {
73 frameNew = std::move(frames.at(0));
74 frames.erase(frames.begin());
75 return true;
76 } else {
77 return false;
78 }
79 }
80
81 /// @brief Proceed with the demuxing process.
82 inline void proceedDemuxing() {
83 if (!doWeHaveTotalSize) {
84 if (reverseBytes<uint32_t>() != segmentId) {
85 MessagePrinter::printError<PrintMessageType::General>(
86 "Missing a Segment, which was expected at index: " + std::to_string(currentPosition) + std::string{ "..." });
87 if (!findNextId(segmentId)) {
88 if ((totalSize > 0 && static_cast<int64_t>(currentPosition) >= totalSize)) {
89 areWeDoneVal = true;
90 }
91 return;
92 }
93 MessagePrinter::printSuccess<PrintMessageType::General>("Missing Segment, found at index: " + std::to_string(currentPosition) + ".");
94 } else {
95 currentPosition += sizeof(uint32_t);
96 }
98 doWeHaveTotalSize = true;
99 }
100 while (currentPosition + 3 < data.size() && data.find(0xa3) != std::string::npos) {
101 if (currentPosition >= data.size() - 8) {
102 if ((totalSize > 0 && static_cast<int64_t>(currentPosition) >= totalSize)) {
103 areWeDoneVal = true;
104 }
105 return;
106 }
107 if (data.at(currentPosition) == simpleBlockId && ((data.at(currentPosition + 2) == opusTrackId || data.at(currentPosition + 3) == opusTrackId))) {
109 if (currentSize == 0) {
110 currentSize = static_cast<uint64_t>(collectElementSize());
111 if (static_cast<int64_t>(currentSize) >= totalSize || static_cast<int64_t>(currentSize) >= 1276 || static_cast<int64_t>(currentSize) < 4) {
113 currentSize = 0;
114 continue;
115 } else if (currentSize == static_cast<uint64_t>(-1) || currentPosition + currentSize >= data.size()) {
116 if ((totalSize > 0 && static_cast<int64_t>(currentPosition) >= totalSize)) {
117 areWeDoneVal = true;
118 }
119 return;
120 } else {
122 }
123 } else {
124 currentSize = 0;
125 }
126 } else {
128 }
129 }
130 if ((totalSize > 0 && static_cast<int64_t>(currentPosition) >= totalSize)) {
131 areWeDoneVal = true;
132 }
133 return;
134 }
135
136 /// @brief Checks if the demuxing process is complete.
137 /// @return True if demuxing is complete, false otherwise.
138 inline bool areWeDone() {
139 return areWeDoneVal;
140 }
141
142 protected:
143 std::basic_string_view<uint8_t> data{};///< Input data for demuxing.
144 std::deque<AudioFrameData> frames{};///< Queue to store collected frames.
145 bool doWeHaveTotalSize{ false };///< Flag indicating if total size has been determined.
146 bool areWeDoneVal{ false };///< Flag indicating if demuxing is complete.
147 uint64_t currentPosition{};///< Current position in the data.
148 uint64_t currentSize{};///< Current size of the element being processed.
149 int64_t totalSize{};///< Total size of the segment.
150
151 /// @brief Finds the next occurrence of the specified value in the data.
152 /// @tparam ObjectType The type of value to search for.
153 /// @param value The value to search for.
154 /// @return True if the value was found, false otherwise.
155 template<typename ObjectType> inline bool findNextId(ObjectType value) {
156 if (currentPosition + sizeof(ObjectType) >= data.size()) {
157 return false;
158 }
159 while (currentPosition + sizeof(ObjectType) < data.size()) {
160 if (reverseBytes<ObjectType>() == value) {
161 currentPosition += sizeof(ObjectType);
162 return true;
163 }
165 }
166 return false;
167 }
168
169 /// @brief Reverses the byte order of the current element being processed.
170 /// @tparam ObjectType The type of the current element.
171 /// @return The current element with reversed byte order.
172 template<typename ObjectType> inline ObjectType reverseBytes() {
173 if (data.size() <= currentPosition + sizeof(ObjectType)) {
174 return static_cast<ObjectType>(-1);
175 }
176 ObjectType newValue{};
177 std::memcpy(&newValue, &data.at(currentPosition), sizeof(ObjectType));
178 newValue = reverseByteOrder(newValue);
179 return newValue;
180 }
181
182 /// @brief Collects the size of the current element being processed.
183 /// @return The size of the current element.
184 inline int64_t collectElementSize() {
185 if (currentPosition >= data.size() - 8) {
186 return -1;
187 }
188 return collectNumber();
189 }
190
191 /// @brief Collects a number from the data.
192 /// @return The collected number.
193 inline int64_t collectNumber() {
194 int32_t read{}, n{ 1 };
195 uint64_t total{};
196 total = static_cast<uint8_t>(data.at(currentPosition++));
197
198 read = 8 - ffLog2Tab[total];
199
200 total ^= 1ull << ffLog2Tab[total];
201 while (n++ < read) {
202 total = (total << 8) | static_cast<uint8_t>(data.at(currentPosition++));
203 }
204 return static_cast<int64_t>(total);
205 }
206
207 /// @brief Parses an Opus frame.
208 inline void parseOpusFrame() {
209 AudioFrameData frameNew{};
210 frameNew.currentSize = static_cast<int64_t>(currentSize - 4);
211 frameNew += std::basic_string_view<uint8_t>{ data.data() + currentPosition + 4, static_cast<uint64_t>(frameNew.currentSize) };
212 frameNew.type = AudioFrameType::Encoded;
214 frames.emplace_back(std::move(frameNew));
215 currentSize = 0;
216 }
217 };
218
219 /// @brief A class representing an Opus packet.
221 public:
222 inline OpusPacket() = default;
223
224 /// @brief Constructor for OpusPacket.
225 /// @param newData The data for the Opus packet.
226 inline OpusPacket(jsonifier::vector<uint8_t> newData) {
227 dataVal = newData;
228 };
229
230 /// @brief Returns the size of the Opus packet data.
231 /// @return The size of the Opus packet data.
232 inline uint64_t size() {
233 return dataVal.size();
234 }
235
236 /// @brief Returns a pointer to the Opus packet data.
237 /// @return A pointer to the Opus packet data.
238 inline auto data() {
239 return dataVal.data();
240 }
241
242 protected:
243 jsonifier::vector<uint8_t> dataVal{};///< The data for the Opus packet.
244 };
245
246 /// @brief A class representing an Ogg page for demuxing.
247 class OggPage {
248 public:
249 /// @brief Constructor for OggPage.
250 /// @param newData The data for the Ogg page.
251 inline OggPage(jsonifier::vector<uint8_t>& newData) {
252 data = std::move(newData);
255 }
256
257 /// @brief Retrieves the next Opus packet from the Ogg page.
258 /// @param newPacket Reference to store the retrieved Opus packet.
259 /// @return True if an Opus packet was retrieved, false otherwise.
260 inline bool getOpusPacket(OpusPacket& newPacket) {
261 if (segmentTable.size() > 0) {
262 auto newSpace = static_cast<uint64_t>(segmentTable.front());
263 segmentTable.pop_front();
264 jsonifier::vector<uint8_t> returnValue{};
265 returnValue.resize(newSpace);
266 std::memcpy(returnValue.data(), data.data() + currentPosition, newSpace);
267 currentPosition += newSpace;
268 newPacket = returnValue;
269 return true;
270 } else {
271 return false;
272 }
273 }
274
275 /// @brief Parses the segment data of the Ogg page.
276 inline void getSegmentData() {
277 segmentCount = data.at(26);
278 currentPosition += 27;
279 for (int32_t x{}; x < segmentCount; ++x) {
280 int32_t packetLength{ data.at(27ull + x) };
281 while (data.at(27ull + x) == 255) {
282 ++x;
283 packetLength += data.at(27ull + x);
284 }
285 segmentTable.emplace_back(packetLength);
286 }
288 return;
289 }
290
291 /// @brief Returns the size of the Ogg page data.
292 /// @return The size of the Ogg page data.
293 inline uint64_t getDataSize() {
294 return data.size();
295 }
296
297 protected:
298 std::deque<int32_t> segmentTable{};///< Segment table storing Opus packet sizes.
299 jsonifier::vector<uint8_t> data{};///< The data for the Ogg page.
300 uint64_t totalPacketSize{};///< Total size of Opus packets in the page.
301 uint64_t currentPosition{};///< Current position in the page data.
302 int32_t segmentCount{};///< Number of segments in the Ogg page.
303
304 /// @brief Verifies that the data represents a valid Ogg page.
305 inline void verifyAsOggPage() {
306 while (data.at(currentPosition) != 'O' || data.at(currentPosition + 1) != 'g' || data.at(currentPosition + 2) != 'g' || data.at(currentPosition + 3) != 'S') {
308 if (currentPosition >= data.size()) {
309 return;
310 }
311 }
312 }
313 };
314
315 /// @brief A class for demuxing Ogg-contained audio data.
317 public:
318 inline OggDemuxer() = default;
319
320 /// @brief Collects the next audio frame from the demuxer.
321 /// @param frameNew The reference to store the collected frame.
322 /// @return True if a frame was collected, false otherwise.
323 inline bool collectFrame(AudioFrameData& frameNew) {
324 if (frames.size() > 0) {
325 frameNew = std::move(frames.front());
326 frames.pop_front();
327 return true;
328 } else {
329 return false;
330 }
331 }
332
333 /// @brief Writes data to the Ogg demuxer and processes it.
334 /// @param inputData The data to be written and processed.
335 inline void writeData(std::string_view inputData) {
336 uint64_t pos = 0;
337 data.clear();
338 data.resize(inputData.size());
339 std::memcpy(data.data(), inputData.data(), inputData.size());
340 uint64_t collectedLength{};
341 while (pos < inputData.size()) {
342 uint64_t oggPos = inputData.find("OggS", pos);
343 if (oggPos != std::string::npos) {
344 uint64_t nextOggPos = inputData.find("OggS", oggPos + 1);
345 if (nextOggPos != std::string::npos) {
346 collectedLength += nextOggPos - oggPos;
347 jsonifier::vector<uint8_t> newerString{};
348 newerString.resize(nextOggPos - oggPos);
349 std::memcpy(newerString.data(), data.data() + oggPos, nextOggPos - oggPos);
350 pages.emplace_back(newerString);
351 pos = nextOggPos;
352 } else {
353 jsonifier::vector<uint8_t> newerString{};
354 newerString.resize(inputData.size() - collectedLength);
355 std::memcpy(newerString.data(), data.data() + oggPos, inputData.size() - collectedLength);
356 pages.emplace_back(newerString);
357 pos = collectedLength;
358 break;
359 }
360 } else {
361 jsonifier::vector<uint8_t> newerString{};
362 newerString.resize(inputData.size() - collectedLength);
363 std::memcpy(newerString.data(), data.data() + oggPos, inputData.size() - collectedLength);
364 pages.emplace_back(newerString);
365 break;
366 }
367 }
368 return;
369 }
370
371 /// @brief Proceeds with the demuxing process.
372 /// @return True if demuxing is successful, false otherwise.
373 inline bool proceedDemuxing() {
374 while (1) {
375 if (!processOggPage()) {
376 return false;
377 }
378 }
379 return true;
380 }
381
382 protected:
383 jsonifier::string_base<uint8_t> data{};///< Input data for demuxing.
384 std::deque<AudioFrameData> frames{};///< Queue to store collected audio frames.
385 std::deque<OpusPacket> packets{};///< Queue to store Opus packets.
386 std::deque<OggPage> pages{};///< Queue to store Ogg pages.
387
388 /// @brief Processes an Ogg page for demuxing.
389 /// @return True if processing is successful, false if there are no more pages.
390 inline bool processOggPage() {
391 if (pages.empty()) {
392 return false;
393 }
394
395 processPages();
397
398 return true;
399 }
400
401 /// @brief Processes Opus packets extracted from Ogg pages.
402 inline void processPackets() {
403 while (!packets.empty()) {
404 OpusPacket newPacket = packets.front();
405 packets.pop_front();
406 AudioFrameData newFrame{};
407 newFrame += std::basic_string_view<uint8_t>{ newPacket.data(), newPacket.size() };
408 newFrame.currentSize = static_cast<int64_t>(newPacket.size());
409 newFrame.type = AudioFrameType::Encoded;
410 frames.emplace_back(std::move(newFrame));
411 }
412 }
413
414 /// @brief Processes Ogg pages to extract Opus packets.
415 inline void processPages() {
416 while (!pages.empty()) {
417 OggPage page = pages.front();
418 pages.pop_front();
419 OpusPacket newPacket{};
420 while (page.getOpusPacket(newPacket)) {
421 packets.emplace_back(newPacket);
422 }
423 }
424 }
425 };
426
427 /**@}*/
428 }
429}
@ Encoded
Encoded audio data.
ReturnType reverseByteOrder(ReturnType net)
Reverses the byte order of a value if needed, based on the endianness.
Definition: Etf.hpp:61
A class for demuxing Matroska-contained audio data.
Definition: Demuxers.hpp:57
bool doWeHaveTotalSize
Flag indicating if total size has been determined.
Definition: Demuxers.hpp:145
void writeData(std::basic_string_view< uint8_t > dataNew)
Writes data to the Matroska demuxer.
Definition: Demuxers.hpp:64
void proceedDemuxing()
Proceed with the demuxing process.
Definition: Demuxers.hpp:82
int64_t totalSize
Total size of the segment.
Definition: Demuxers.hpp:149
MatroskaDemuxer()=default
Constructor for MatroskaDemuxer.
uint64_t currentSize
Current size of the element being processed.
Definition: Demuxers.hpp:148
bool areWeDoneVal
Flag indicating if demuxing is complete.
Definition: Demuxers.hpp:146
int64_t collectElementSize()
Collects the size of the current element being processed.
Definition: Demuxers.hpp:184
ObjectType reverseBytes()
Reverses the byte order of the current element being processed.
Definition: Demuxers.hpp:172
int64_t collectNumber()
Collects a number from the data.
Definition: Demuxers.hpp:193
bool findNextId(ObjectType value)
Finds the next occurrence of the specified value in the data.
Definition: Demuxers.hpp:155
std::basic_string_view< uint8_t > data
Input data for demuxing.
Definition: Demuxers.hpp:143
bool areWeDone()
Checks if the demuxing process is complete.
Definition: Demuxers.hpp:138
bool collectFrame(AudioFrameData &frameNew)
Collects the next frame from the demuxer.
Definition: Demuxers.hpp:71
std::deque< AudioFrameData > frames
Queue to store collected frames.
Definition: Demuxers.hpp:144
uint64_t currentPosition
Current position in the data.
Definition: Demuxers.hpp:147
A class representing an Opus packet.
Definition: Demuxers.hpp:220
uint64_t size()
Returns the size of the Opus packet data.
Definition: Demuxers.hpp:232
jsonifier::vector< uint8_t > dataVal
The data for the Opus packet.
Definition: Demuxers.hpp:243
auto data()
Returns a pointer to the Opus packet data.
Definition: Demuxers.hpp:238
OpusPacket(jsonifier::vector< uint8_t > newData)
Constructor for OpusPacket.
Definition: Demuxers.hpp:226
A class representing an Ogg page for demuxing.
Definition: Demuxers.hpp:247
bool getOpusPacket(OpusPacket &newPacket)
Retrieves the next Opus packet from the Ogg page.
Definition: Demuxers.hpp:260
void verifyAsOggPage()
Verifies that the data represents a valid Ogg page.
Definition: Demuxers.hpp:305
void getSegmentData()
Parses the segment data of the Ogg page.
Definition: Demuxers.hpp:276
uint64_t getDataSize()
Returns the size of the Ogg page data.
Definition: Demuxers.hpp:293
int32_t segmentCount
Number of segments in the Ogg page.
Definition: Demuxers.hpp:302
uint64_t currentPosition
Current position in the page data.
Definition: Demuxers.hpp:301
OggPage(jsonifier::vector< uint8_t > &newData)
Constructor for OggPage.
Definition: Demuxers.hpp:251
std::deque< int32_t > segmentTable
Segment table storing Opus packet sizes.
Definition: Demuxers.hpp:298
jsonifier::vector< uint8_t > data
The data for the Ogg page.
Definition: Demuxers.hpp:299
uint64_t totalPacketSize
Total size of Opus packets in the page.
Definition: Demuxers.hpp:300
A class for demuxing Ogg-contained audio data.
Definition: Demuxers.hpp:316
void processPackets()
Processes Opus packets extracted from Ogg pages.
Definition: Demuxers.hpp:402
std::deque< AudioFrameData > frames
Queue to store collected audio frames.
Definition: Demuxers.hpp:384
jsonifier::string_base< uint8_t > data
Input data for demuxing.
Definition: Demuxers.hpp:383
bool proceedDemuxing()
Proceeds with the demuxing process.
Definition: Demuxers.hpp:373
bool collectFrame(AudioFrameData &frameNew)
Collects the next audio frame from the demuxer.
Definition: Demuxers.hpp:323
bool processOggPage()
Processes an Ogg page for demuxing.
Definition: Demuxers.hpp:390
void processPages()
Processes Ogg pages to extract Opus packets.
Definition: Demuxers.hpp:415
std::deque< OggPage > pages
Queue to store Ogg pages.
Definition: Demuxers.hpp:386
std::deque< OpusPacket > packets
Queue to store Opus packets.
Definition: Demuxers.hpp:385
void writeData(std::string_view inputData)
Writes data to the Ogg demuxer and processes it.
Definition: Demuxers.hpp:335
Represents a single frame of audio data.
Definition: Utilities.hpp:367
int64_t currentSize
The current size of the allocated memory.
Definition: Utilities.hpp:371