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#pragma once
31
33#include <opus/opus.h>
34#include <fstream>
35
36namespace discord_core_api {
37
38 namespace discord_core_internal {
39
40 /**
41 * \addtogroup discord_core_internal
42 * @{
43 */
44
45 static constexpr uint32_t segmentId{ 0x18538067 };
46 static constexpr uint8_t simpleBlockId{ 0xA3 };
47 static constexpr uint8_t opusTrackId{ 0x81 };
48
49 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,
50 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,
51 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,
52 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,
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 };
54
55 /// @brief A class for demuxing Matroska-contained audio data.
57 public:
58 /// @brief Constructor for matroska_demuxer.
59 inline matroska_demuxer() = default;
60
61 /// @brief Writes data to the Matroska demuxer.
62 /// @param dataNew The data to be written.
63 inline void writeData(jsonifier::string_view_base<uint8_t> dataNew) {
64 data = dataNew;
65 }
66
67 /// @brief Collects the next frame from the demuxer.
68 /// @param frameNew The reference to store the collected frame.
69 /// @return True if a frame was collected, false otherwise.
70 inline bool collectFrame(audio_frame_data& frameNew) {
71 if (frames.size() > 0) {
72 frameNew = std::move(frames.at(0));
73 frames.erase(frames.begin());
74 return true;
75 } else {
76 return false;
77 }
78 }
79
80 /// @brief Proceed with the demuxing process.
81 inline void proceedDemuxing() {
82 if (!doWeHaveTotalSize) {
83 if (reverseBytes<uint32_t>() != segmentId) {
84 message_printer::printError<print_message_type::general>(
85 jsonifier::string{ "Missing a Segment, which was expected at index: " + jsonifier::toString(currentPosition) + jsonifier::string{ "..." } });
86 if (!findNextId(segmentId)) {
87 if ((totalSize > 0 && static_cast<int64_t>(currentPosition) >= totalSize)) {
88 areWeDoneVal = true;
89 }
90 return;
91 }
92 message_printer::printSuccess<print_message_type::general>(
93 jsonifier::string{ "Missing Segment, found at index: " + jsonifier::toString(currentPosition) + "." });
94 } else {
95 currentPosition += sizeof(uint32_t);
96 }
98 doWeHaveTotalSize = true;
99 }
100 while (currentPosition + 3 < data.size() && data.find(static_cast<uint8_t>(0xa3)) != jsonifier::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 jsonifier::string_view_base<uint8_t> data{};///< Input data for demuxing.
144 std::deque<audio_frame_data> 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 object_type 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 object_type> inline bool findNextId(object_type value) {
156 if (currentPosition + sizeof(object_type) >= data.size()) {
157 return false;
158 }
159 while (currentPosition + sizeof(object_type) < data.size()) {
160 if (reverseBytes<object_type>() == value) {
161 currentPosition += sizeof(object_type);
162 return true;
163 }
165 }
166 return false;
167 }
168
169 /// @brief Reverses the byte order of the current element being processed.
170 /// @tparam object_type The type of the current element.
171 /// @return The current element with reversed byte order.
172 template<typename object_type> inline object_type reverseBytes() {
173 if (data.size() <= currentPosition + sizeof(object_type)) {
174 return static_cast<object_type>(-1);
175 }
176 object_type newValue{};
177 std::memcpy(&newValue, &data.at(currentPosition), sizeof(object_type));
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 uint64_t read{}, n{ 1 };
195 uint64_t total{};
196 total = static_cast<uint8_t>(data.at(currentPosition++));
197
198 read = 8ULL - 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 audio_frame_data frameNew{};
210 frameNew.currentSize = static_cast<int64_t>(currentSize - 4);
211 frameNew += jsonifier::string_view_base<uint8_t>{ data.data() + currentPosition + 4, static_cast<uint64_t>(frameNew.currentSize) };
212 frameNew.type = audio_frame_type::encoded;
214 frames.emplace_back(std::move(frameNew));
215 currentSize = 0;
216 }
217 };
218
219 using opus_packet = jsonifier::vector<uint8_t>;
220
221 /// @brief A class representing an Ogg page for demuxing.
222 class ogg_page {
223 public:
224 /// @brief Constructor for ogg_page.
225 /// @param newData The data for the Ogg page.
226 inline ogg_page(jsonifier::vector<uint8_t>&& newData) {
227 data = std::move(newData);
230 }
231
232 /// @brief Retrieves the next Opus packet from the Ogg page.
233 /// @param newPacket Reference to store the retrieved Opus packet.
234 /// @return True if an Opus packet was retrieved, false otherwise.
235 inline bool getOpusPacket(opus_packet& newPacket) {
236 if (segmentTable.size() > 0) {
237 auto newSpace = static_cast<uint64_t>(segmentTable.front());
238 segmentTable.pop_front();
239 newPacket.resize(newSpace);
240 std::memcpy(newPacket.data(), data.data() + currentPosition, newSpace);
241 currentPosition += newSpace;
242 return true;
243 } else {
244 return false;
245 }
246 }
247
248 /// @brief Parses the segment data of the Ogg page.
249 inline void getSegmentData() {
250 segmentCount = data.at(26);
251 currentPosition += 27;
252 for (uint64_t x{}; x < segmentCount; ++x) {
253 uint64_t packetLength{ data.at(27ULL + x) };
254 while (data.at(27ULL + x) == 255) {
255 ++x;
256 packetLength += data.at(27ULL + x);
257 }
258 segmentTable.emplace_back(packetLength);
259 }
261 return;
262 }
263
264 /// @brief Returns the size of the Ogg page data.
265 /// @return The size of the Ogg page data.
266 inline uint64_t getDataSize() {
267 return data.size();
268 }
269
270 protected:
271 std::deque<uint64_t> segmentTable{};///< Segment table storing Opus packet sizes.
272 jsonifier::vector<uint8_t> data{};///< The data for the Ogg page.
273 uint64_t totalPacketSize{};///< Total size of Opus packets in the page.
274 uint64_t currentPosition{};///< Current position in the page data.
275 uint64_t segmentCount{};///< Number of segments in the Ogg page.
276
277 /// @brief Verifies that the data represents a valid Ogg page.
278 inline void verifyAsOggPage() {
279 while (data.at(currentPosition) != 'O' || data.at(currentPosition + 1) != 'g' || data.at(currentPosition + 2) != 'g' || data.at(currentPosition + 3) != 'S') {
281 if (currentPosition >= data.size()) {
282 return;
283 }
284 }
285 }
286 };
287
288 /// @brief A class for demuxing Ogg-contained audio data.
290 public:
291 inline ogg_demuxer() = default;
292
293 /// @brief Collects the next audio frame from the demuxer.
294 /// @param frameNew The reference to store the collected frame.
295 /// @return True if a frame was collected, false otherwise.
296 inline bool collectFrame(audio_frame_data& frameNew) {
297 if (frames.size() > 0) {
298 frameNew = std::move(frames.front());
299 frames.pop_front();
300 return true;
301 } else {
302 return false;
303 }
304 }
305
306 /// @brief Writes data to the Ogg demuxer and processes it.
307 /// @param inputData The data to be written and processed.
308 inline void writeData(jsonifier::string_view inputData) {
309 uint64_t pos = 0;
310 data.clear();
311 data.resize(inputData.size());
312 std::memcpy(data.data(), inputData.data(), inputData.size());
313 uint64_t collectedLength{};
314 while (pos < inputData.size()) {
315 uint64_t oggPos = inputData.find("OggS", pos);
316 if (oggPos != jsonifier::string::npos) {
317 uint64_t nextOggPos = inputData.find("OggS", oggPos + 1);
318 if (nextOggPos != jsonifier::string::npos) {
319 collectedLength += nextOggPos - oggPos;
320 jsonifier::vector<uint8_t> newerString{};
321 newerString.resize(nextOggPos - oggPos);
322 std::memcpy(newerString.data(), data.data() + oggPos, nextOggPos - oggPos);
323 pages.emplace_back(std::move(newerString));
324 pos = nextOggPos;
325 } else {
326 jsonifier::vector<uint8_t> newerString{};
327 newerString.resize(inputData.size() - collectedLength);
328 std::memcpy(newerString.data(), data.data() + oggPos, inputData.size() - collectedLength);
329 pages.emplace_back(std::move(newerString));
330 pos = collectedLength;
331 break;
332 }
333 } else {
334 jsonifier::vector<uint8_t> newerString{};
335 newerString.resize(inputData.size() - collectedLength);
336 std::memcpy(newerString.data(), data.data() + oggPos, inputData.size() - collectedLength);
337 pages.emplace_back(std::move(newerString));
338 break;
339 }
340 }
341 return;
342 }
343
344 /// @brief Proceeds with the demuxing process.
345 /// @return True if demuxing is successful, false otherwise.
346 inline bool proceedDemuxing() {
347 while (1) {
348 if (!processOggPage()) {
349 return false;
350 }
351 }
352 return true;
353 }
354
355 protected:
356 std::deque<audio_frame_data> frames{};///< Queue to store collected audio frames.
357 jsonifier::vector<uint8_t> data{};///< Input data for demuxing.
358 std::deque<opus_packet> packets{};///< Queue to store Opus packets.
359 std::deque<ogg_page> pages{};///< Queue to store Ogg pages.
360
361 /// @brief Processes an Ogg page for demuxing.
362 /// @return True if processing is successful, false if there are no more pages.
363 inline bool processOggPage() {
364 if (pages.empty()) {
365 return false;
366 }
367
368 processPages();
370
371 return true;
372 }
373
374 /// @brief Processes Opus packets extracted from Ogg pages.
375 inline void processPackets() {
376 while (!packets.empty()) {
377 opus_packet newPacket = packets.front();
378 packets.pop_front();
379 audio_frame_data newFrame{};
380 newFrame += newPacket;
381 newFrame.currentSize = static_cast<int64_t>(newPacket.size());
382 newFrame.type = audio_frame_type::encoded;
383 frames.emplace_back(std::move(newFrame));
384 }
385 }
386
387 /// @brief Processes Ogg pages to extract Opus packets.
388 inline void processPages() {
389 while (!pages.empty()) {
390 ogg_page page = pages.front();
391 pages.pop_front();
392 opus_packet newPacket{};
393 while (page.getOpusPacket(newPacket)) {
394 packets.emplace_back(newPacket);
395 }
396 }
397 }
398 };
399
400 /**@}*/
401 }
402}
A class for demuxing Matroska-contained audio data.
Definition: Demuxers.hpp:56
jsonifier::string_view_base< uint8_t > data
Input data for demuxing.
Definition: Demuxers.hpp:143
uint64_t currentSize
Current size of the element being processed.
Definition: Demuxers.hpp:148
bool findNextId(object_type value)
Finds the next occurrence of the specified value in the data.
Definition: Demuxers.hpp:155
object_type reverseBytes()
Reverses the byte order of the current element being processed.
Definition: Demuxers.hpp:172
void writeData(jsonifier::string_view_base< uint8_t > dataNew)
Writes data to the Matroska demuxer.
Definition: Demuxers.hpp:63
void proceedDemuxing()
Proceed with the demuxing process.
Definition: Demuxers.hpp:81
bool doWeHaveTotalSize
Flag indicating if total size has been determined.
Definition: Demuxers.hpp:145
matroska_demuxer()=default
Constructor for matroska_demuxer.
bool collectFrame(audio_frame_data &frameNew)
Collects the next frame from the demuxer.
Definition: Demuxers.hpp:70
uint64_t currentPosition
Current position in the data.
Definition: Demuxers.hpp:147
std::deque< audio_frame_data > frames
Queue to store collected frames.
Definition: Demuxers.hpp:144
int64_t collectElementSize()
Collects the size of the current element being processed.
Definition: Demuxers.hpp:184
bool areWeDone()
Checks if the demuxing process is complete.
Definition: Demuxers.hpp:138
int64_t collectNumber()
Collects a number from the data.
Definition: Demuxers.hpp:193
bool areWeDoneVal
Flag indicating if demuxing is complete.
Definition: Demuxers.hpp:146
A class for demuxing Ogg-contained audio data.
Definition: Demuxers.hpp:289
void writeData(jsonifier::string_view inputData)
Writes data to the Ogg demuxer and processes it.
Definition: Demuxers.hpp:308
std::deque< ogg_page > pages
Queue to store Ogg pages.
Definition: Demuxers.hpp:359
void processPackets()
Processes Opus packets extracted from Ogg pages.
Definition: Demuxers.hpp:375
std::deque< opus_packet > packets
Queue to store Opus packets.
Definition: Demuxers.hpp:358
std::deque< audio_frame_data > frames
Queue to store collected audio frames.
Definition: Demuxers.hpp:356
jsonifier::vector< uint8_t > data
Input data for demuxing.
Definition: Demuxers.hpp:357
bool processOggPage()
Processes an Ogg page for demuxing.
Definition: Demuxers.hpp:363
void processPages()
Processes Ogg pages to extract Opus packets.
Definition: Demuxers.hpp:388
bool collectFrame(audio_frame_data &frameNew)
Collects the next audio frame from the demuxer.
Definition: Demuxers.hpp:296
bool proceedDemuxing()
Proceeds with the demuxing process.
Definition: Demuxers.hpp:346
A class representing an Ogg page for demuxing.
Definition: Demuxers.hpp:222
ogg_page(jsonifier::vector< uint8_t > &&newData)
Constructor for ogg_page.
Definition: Demuxers.hpp:226
jsonifier::vector< uint8_t > data
The data for the Ogg page.
Definition: Demuxers.hpp:272
uint64_t totalPacketSize
Total size of Opus packets in the page.
Definition: Demuxers.hpp:273
std::deque< uint64_t > segmentTable
Segment table storing Opus packet sizes.
Definition: Demuxers.hpp:271
bool getOpusPacket(opus_packet &newPacket)
Retrieves the next Opus packet from the Ogg page.
Definition: Demuxers.hpp:235
uint64_t getDataSize()
Returns the size of the Ogg page data.
Definition: Demuxers.hpp:266
void getSegmentData()
Parses the segment data of the Ogg page.
Definition: Demuxers.hpp:249
uint64_t segmentCount
Number of segments in the Ogg page.
Definition: Demuxers.hpp:275
uint64_t currentPosition
Current position in the page data.
Definition: Demuxers.hpp:274
void verifyAsOggPage()
Verifies that the data represents a valid Ogg page.
Definition: Demuxers.hpp:278
return_type reverseByteOrder(return_type net)
Reverses the byte order of a value if needed, based on the endianness.
Definition: Etf.hpp:60
The main namespace for the forward-facing interfaces.
Represents a single frame of audio data.
Definition: Utilities.hpp:381
int64_t currentSize
The current size of the allocated memory.
Definition: Utilities.hpp:384