vdr-plugin-softhddevice-drm-gles 1.4.0
test_pes.cpp
Go to the documentation of this file.
1
18#include <catch2/catch_test_macros.hpp>
19#include "../pes.h"
20
21extern "C" {
22#include <libavutil/avutil.h>
23}
24
25// Helper function to create a minimal valid PES header
26// PES packet structure:
27// - Start code prefix: 0x000001 (3 bytes)
28// - Stream ID: 1 byte
29// - PES packet length: 2 bytes
30// - Optional PES header fields
31std::vector<uint8_t> createBasicPesHeader(uint8_t streamId, bool withPts = false, uint16_t pesLength = 0) {
32 std::vector<uint8_t> data;
33
34 // Start code prefix
35 data.push_back(0x00);
36 data.push_back(0x00);
37 data.push_back(0x01);
38
39 // Stream ID
40 data.push_back(streamId);
41
42 // PES packet length (0 = unspecified)
43 data.push_back((pesLength >> 8) & 0xFF);
44 data.push_back(pesLength & 0xFF);
45
46 // PES extension
47 data.push_back(0x80); // '10'xxxxxx (no PES scrambling control, PES priority, data alignment indicator, copyright, original or copy)
48
49 // PES header flags
50 if (withPts) {
51 data.push_back(0x80); // PTS_DTS_flags = '10' (PTS only: no ESCR flag, ES rate flag, DSM trick mode flag, additional copy info flag, PES CRC flag, PES extension flag)
52 data.push_back(0x05); // PES header data length (5 bytes for PTS)
53
54 // PTS value (5 bytes) - example: 9000 (0.1 second at 90kHz)
55 data.push_back(0x21); // '0010' (marker bits) + high 3 bits of PTS + marker bit
56 data.push_back(0x00);
57 data.push_back(0x01); // marker bit
58 data.push_back(0x46);
59 data.push_back(0x51); // marker bit + low 15 bits
60 } else {
61 data.push_back(0x00); // No PTS/DTS, ESCR flag, ES rate flag, DSM trick mode flag, additional copy info flag, PES CRC flag, PES extension flag
62 data.push_back(0x00); // PES header data length = 0
63 }
64
65 return data;
66}
67
68// Helper to create a PES packet with MPEG2 video payload
69std::vector<uint8_t> createMpeg2PesPacket() {
70 auto data = createBasicPesHeader(0xE0, true); // Video stream 0
71
72 // MPEG2 video start code: 0x000001B3
73 data.push_back(0x00);
74 data.push_back(0x00);
75 data.push_back(0x01);
76 data.push_back(0xB3); // MPEG2 sequence header
77
78 // Add some dummy payload
79 for (int i = 0; i < 20; i++) {
80 data.push_back(0x00);
81 }
82
83 return data;
84}
85
86// Helper to create a PES packet with H.264 video payload
87std::vector<uint8_t> createH264PesPacket(bool withLeadingZero = false) {
88 auto data = createBasicPesHeader(0xE0, true); // Video stream 0
89
90 if (withLeadingZero) {
91 data.push_back(0x00); // Leading zero byte
92 }
93
94 // H.264 NAL unit start code: 0x00000109
95 data.push_back(0x00);
96 data.push_back(0x00);
97 data.push_back(0x01);
98 data.push_back(0x09); // H.264 access unit delimiter
99 data.push_back(0x10); // Marker byte
100
101 // Add more data to pass array bounds check
102 for (int i = 0; i < 20; i++) {
103 data.push_back(0x00);
104 }
105
106 return data;
107}
108
109// Helper to create a PES packet with HEVC video payload
110std::vector<uint8_t> createHevcPesVideoPacket(bool withLeadingZero = false) {
111 auto data = createBasicPesHeader(0xE0, true); // Video stream 0
112
113 if (withLeadingZero) {
114 data.push_back(0x00); // Leading zero byte
115 }
116
117 // HEVC NAL unit start code: 0x00000146
118 data.push_back(0x00);
119 data.push_back(0x00);
120 data.push_back(0x01);
121 data.push_back(0x46); // HEVC access unit delimiter
122 data.push_back(0x10); // Marker byte
123
124 // Add more data to pass array bounds check
125 for (int i = 0; i < 20; i++) {
126 data.push_back(0x00);
127 }
128
129 return data;
130}
131
132// Helper to create a PES packet with audio payload
133std::vector<uint8_t> createAudioPesPacket() {
134 auto data = createBasicPesHeader(0xC0, false); // Audio stream 0
135
136 // Add some audio payload
137 for (int i = 0; i < 20; i++) {
138 data.push_back(0xFF);
139 }
140
141 return data;
142}
143
144TEST_CASE("cPesVideo - Basic construction", "[pes]") {
145 SECTION("Construct with valid data") {
146 auto data = createBasicPesHeader(0xE0);
147 cPesVideo pes(data.data(), data.size());
148
149 REQUIRE(pes.IsValid());
150 }
151
152 SECTION("Construct with empty data") {
153 uint8_t data[] = {};
154 cPesVideo pes(data, 0);
155
156 REQUIRE(!pes.IsValid());
157 }
158}
159
160TEST_CASE("cPesVideo - Header validation", "[pes]") {
161 SECTION("Valid PES header") {
162 auto data = createBasicPesHeader(0xE0);
163 cPesVideo pes(data.data(), data.size());
164
165 REQUIRE(pes.IsValid());
166 }
167
168 SECTION("Invalid PES header - wrong start code") {
169 std::vector<uint8_t> data = {0x00, 0x00, 0x02, 0xE0, 0x00, 0x00};
170 cPesVideo pes(data.data(), data.size());
171
172 REQUIRE(!pes.IsValid());
173 }
174
175 SECTION("Invalid PES header - one byte too short") {
176 std::vector<uint8_t> data = {0x00, 0x00, 0x01, 0xE0, 0x00, 0x10, 0xAA, 0xBB};
177 cPesVideo pes(data.data(), data.size());
178
179 REQUIRE(!pes.IsValid());
180 }
181
182 SECTION("Stream type without PES extension") {
183 std::vector<uint8_t> data;
184
185 // Start code prefix
186 data.push_back(0x00);
187 data.push_back(0x00);
188 data.push_back(0x01);
189
190 // Stream ID
191 data.push_back(0xBE);
192
193 // PES packet length (0 = unspecified)
194 data.push_back(0x00);
195 data.push_back(0x01);
196
197 // payload
198 data.push_back(0xAA);
199
200 cPesVideo pes(data.data(), data.size());
201
202 REQUIRE(!pes.IsValid());
203 }
204}
205
206TEST_CASE("cPesVideo - Stream type detection", "[pes]") {
207 SECTION("Video stream detection") {
208 // Video streams have stream IDs 0xE0-0xEF
209 for (uint8_t id = 0xE0; id <= 0xEF; id++) {
210 auto data = createBasicPesHeader(id);
211 cPesVideo pes(data.data(), data.size());
212
213 REQUIRE(pes.IsValid());
214 }
215 }
216
217 SECTION("Audio stream detection") {
218 // Audio streams have stream IDs 0xC0-0xCF
219 for (uint8_t id = 0xC0; id <= 0xCF; id++) {
220 auto data = createBasicPesHeader(id);
221 cPesVideo pes(data.data(), data.size());
222
223 REQUIRE(!pes.IsValid());
224 }
225 }
226
227 SECTION("Neither audio nor video") {
228 auto data = createBasicPesHeader(0xBD); // Private stream 1
229 cPesVideo pes(data.data(), data.size());
230
231 REQUIRE(!pes.IsValid());
232 }
233}
234
235
236TEST_CASE("cPesVideo - PTS handling", "[pes]") {
237 SECTION("Get PTS from packet with PTS") {
238 auto data = createBasicPesHeader(0xE0, true);
239 cPesVideo pes(data.data(), data.size());
240
241 int64_t pts = pes.GetPts();
242
243 REQUIRE(pts == 9000);
244 }
245
246 SECTION("Get PTS from packet without PTS") {
247 auto data = createBasicPesHeader(0xE0, false);
248 cPesVideo pes(data.data(), data.size());
249
250 int64_t pts = pes.GetPts();
251
252 REQUIRE(pts == AV_NOPTS_VALUE);
253 }
254}
255
256TEST_CASE("cPesVideo - Payload extraction", "[pes]") {
257 SECTION("Get payload from MPEG2 packet") {
258 auto data = createMpeg2PesPacket();
259 cPesVideo pes(data.data(), data.size());
260
261 const uint8_t* payload = pes.GetPayload();
262 int payloadSize = pes.GetPayloadSize();
263
264 // Verify start code is present in payload
265 REQUIRE(payload != nullptr);
266 REQUIRE(payload[0] == 0x00);
267 REQUIRE(payload[1] == 0x00);
268 REQUIRE(payload[2] == 0x01);
269
270 REQUIRE(payloadSize == 24); // 4 bytes start code + 20 bytes dummy payload
271 }
272
273 SECTION("Payload size consistency") {
274 auto data = createMpeg2PesPacket();
275 cPesVideo pes(data.data(), data.size());
276
277 const uint8_t* payload = pes.GetPayload();
278 int payloadSize = pes.GetPayloadSize();
279
280 // Verify payload + header size equals total size
281 int headerSize = payload - data.data();
282 REQUIRE(headerSize + payloadSize == static_cast<int>(data.size()));
283 }
284}
285
286TEST_CASE("cPesVideo - Packet length", "[pes]") {
287 SECTION("Get packet length for unbounded MPEG2 (length field = 0)") {
288 auto data = createMpeg2PesPacket();
289 cPesVideo pes(data.data(), data.size());
290
291 REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
292 }
293
294 SECTION("Get packet length for unbounded H.264 (length field = 0)") {
295 auto data = createH264PesPacket(false);
296 cPesVideo pes(data.data(), data.size());
297
298 REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
299 }
300
301 SECTION("Get packet length with specified length field") {
302 // Create a PES packet with a specific length
303 // PES length field specifies bytes after the length field itself
304 uint16_t pesPayloadLength = 20; // Header data (3 bytes) + actual payload
305 auto data = createBasicPesHeader(0xE0, false, pesPayloadLength);
306
307 // Add some payload to match the specified length
308 for (int i = data.size() - 6; i < pesPayloadLength; i++) {
309 data.push_back(0x00);
310 }
311
312 cPesVideo pes(data.data(), data.size());
313
314 REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
315 }
316
317 SECTION("Get packet length for packet with PTS and specified length") {
318 // PTS takes 5 bytes, so header data length = 5
319 // Total PES header = 9 (fixed header) + 5 (PTS) = 14 bytes
320 // If we want total packet of 50 bytes, length field = 50 - 6 = 44
321 uint16_t pesPayloadLength = 44;
322 auto data = createBasicPesHeader(0xE0, true, pesPayloadLength);
323
324 // Add payload to make total packet 50 bytes
325 int currentSize = data.size();
326 int targetTotalSize = 6 + pesPayloadLength;
327 for (int i = currentSize; i < targetTotalSize; i++) {
328 data.push_back(0x00);
329 }
330
331 cPesVideo pes(data.data(), data.size());
332
333 REQUIRE(pes.GetPacketLength() == 50);
334 }
335
336 SECTION("Get packet length when input buffer is larger than PES packet") {
337 // Create a PES packet with specified length
338 uint16_t pesPayloadLength = 20;
339 auto data = createBasicPesHeader(0xE0, false, pesPayloadLength);
340
341 // Add payload matching the PES length
342 for (int i = data.size() - 6; i < pesPayloadLength; i++) {
343 data.push_back(0xAA);
344 }
345
346 // Add extra data beyond the PES packet (simulating buffer with multiple packets)
347 for (int i = 0; i < 50; i++) {
348 data.push_back(0xFF);
349 }
350
351 cPesVideo pes(data.data(), data.size());
352
353 REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
354 REQUIRE(pes.GetPacketLength() < static_cast<int>(data.size()));
355 }
356
357 SECTION("Unbounded packet with buffer larger than actual data") {
358 // Create an unbounded packet (length field = 0)
359 auto data = createBasicPesHeader(0xE0, false, 0);
360
361 // Add some actual payload
362 for (int i = 0; i < 30; i++) {
363 data.push_back(0xAA);
364 }
365
366 // Store the actual data size
367 int actualSize = data.size();
368
369 // Add extra buffer space (simulating oversized buffer)
370 for (int i = 0; i < 50; i++) {
371 data.push_back(0xFF);
372 }
373
374 cPesVideo pes(data.data(), data.size());
375
376 REQUIRE(pes.GetPacketLength() == static_cast<int>(data.size()));
377 REQUIRE(pes.GetPacketLength() > actualSize);
378 }
379
380 SECTION("Get packet length for audio packet with specified length") {
381 // Audio packets typically have bounded length
382 uint16_t pesPayloadLength = 30;
383 auto data = createBasicPesHeader(0xC0, false, pesPayloadLength);
384
385 // Add audio payload
386 for (int i = data.size() - 6; i < pesPayloadLength; i++) {
387 data.push_back(0xFF);
388 }
389
390 cPesAudio pes(data.data(), data.size());
391
392 REQUIRE(pes.GetPacketLength() == 6 + pesPayloadLength);
393 }
394}
395
396TEST_CASE("cPesAudio - Audio stream handling", "[pes]") {
397 SECTION("Audio stream validation") {
398 auto data = createAudioPesPacket();
399 cPesAudio pes(data.data(), data.size());
400
401 REQUIRE(pes.IsValid());
402 }
403
404 SECTION("Private stream validation (0xBD)") {
405 auto data = createBasicPesHeader(0xBD, false);
406 cPesAudio pes(data.data(), data.size());
407
408 REQUIRE(pes.IsValid());
409 }
410}
411
412TEST_CASE("cPesVideo - Edge cases", "[pes]") {
413 SECTION("Parse very short packet") {
414 std::vector<uint8_t> data = {0x00, 0x00, 0x01, 0xE0};
415 cPesVideo pes(data.data(), data.size());
416
417 // Should not crash but packet is invalid due to insufficient length
418 REQUIRE(!pes.IsValid());
419 }
420
421 SECTION("Parse packet with no payload") {
422 auto data = createBasicPesHeader(0xE0);
423 cPesVideo pes(data.data(), data.size());
424
425 // Packet is valid but has no payload
426 REQUIRE(pes.IsValid());
427 }
428}
429
430// ============================================================================
431// cReassemblyBufferVideo Tests
432// ============================================================================
433
434TEST_CASE("cReassemblyBufferVideo - MPEG2 codec detection", "[reassembly][video]") {
435 SECTION("Detect MPEG2 video codec") {
437
438 // Create MPEG2 video frame: 0x000001B3
439 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0xB3, 0x00, 0x00, 0x00, 0x00};
440
441 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == true);
442 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_MPEG2VIDEO);
443 }
444}
445
446TEST_CASE("cReassemblyBufferVideo - H.264 codec detection", "[reassembly][video]") {
447 SECTION("Detect H.264 without leading zero") {
449
450 // H.264 NAL unit: 0x00000109
451 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64};
452
453 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == true);
454 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_H264);
455 }
456
457 SECTION("Detect H.264 with leading zero") {
459
460 // H.264 NAL unit with leading zero: 0x0000000109
461 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64};
462
463 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == true);
464 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_H264);
465 }
466}
467
468TEST_CASE("cReassemblyBufferVideo - HEVC codec detection", "[reassembly][video]") {
469 SECTION("Detect HEVC without leading zero") {
471
472 // HEVC NAL unit: 0x00000146
473 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0x46, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40};
474
475 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == true);
476 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_HEVC);
477 }
478
479 SECTION("Detect HEVC with leading zero") {
481
482 // HEVC NAL unit with leading zero: 0x0000000146
483 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01, 0x46, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40};
484
485 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == true);
486 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_HEVC);
487 }
488}
489
490TEST_CASE("cReassemblyBufferVideo - Unknown codec", "[reassembly][video]") {
491 SECTION("No start code present") {
493
494 std::vector<uint8_t> fragment = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
495
496 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == false);
497 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_NONE);
498 }
499
500 SECTION("Start code present but unknown codec type") {
502
503 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00};
504
505 REQUIRE(buffer.ParseCodecHeader(fragment.data(), fragment.size()) == false);
506 REQUIRE(buffer.GetCodec() == AV_CODEC_ID_NONE);
507 }
508}
509
510TEST_CASE("cReassemblyBufferVideo - HasLeadingZero detection", "[reassembly][video]") {
511 SECTION("Detect leading zero with H.264") {
513
514 // H.264 with leading zero: 0x00 00 00 01 09
515 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00};
516
517 REQUIRE(buffer.HasLeadingZero(fragment.data(), fragment.size()) == true);
518 }
519
520 SECTION("No leading zero - normal start code") {
522
523 // Normal start code: 0x00 00 01
524 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0x09, 0x10};
525
526 REQUIRE(buffer.HasLeadingZero(fragment.data(), fragment.size()) == false);
527 }
528
529 SECTION("Leading zero with HEVC") {
531
532 // HEVC with leading zero: 0x00 00 00 01 46
533 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01, 0x46, 0x10};
534
535 REQUIRE(buffer.HasLeadingZero(fragment.data(), fragment.size()) == true);
536 }
537
538 SECTION("Data too short") {
540
541 // Too short to have leading zero
542 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01};
543
544 REQUIRE(buffer.HasLeadingZero(fragment.data(), fragment.size()) == false);
545 }
546
547 SECTION("First byte not zero") {
549
550 // First byte not zero
551 std::vector<uint8_t> fragment = {0xFF, 0x00, 0x00, 0x01, 0x09};
552
553 REQUIRE(buffer.HasLeadingZero(fragment.data(), fragment.size()) == false);
554 }
555}
556
557TEST_CASE("cReassemblyBufferVideo - Push and drain", "[reassembly][video]") {
558 SECTION("Push video data and create AVPacket") {
560
561 // Create MPEG2 video frame
562 std::vector<uint8_t> fragment = {0x00, 0x00, 0x01, 0xB3, 0xAA, 0xBB, 0xCC, 0xDD};
563
564 buffer.ParseCodecHeader(fragment.data(), fragment.size());
565 buffer.Push(fragment.data(), fragment.size(), 9000);
566
567 // Drain and create AVPacket
568 AVPacket *pkt = buffer.PopAvPacket();
569
570 REQUIRE(pkt != nullptr);
571 REQUIRE(pkt->pts == 9000);
572 REQUIRE(pkt->size >= 8);
573
574 av_packet_free(&pkt);
575 }
576
577 SECTION("Push H.264") {
579
580 // H.264 NAL unit with leading zero: 0x0000000109
581 std::vector<uint8_t> fragment = {0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64};
582
583 buffer.ParseCodecHeader(fragment.data(), fragment.size());
584 buffer.Push(fragment.data(), fragment.size(), 12000);
585
586 AVPacket *pkt = buffer.PopAvPacket();
587
588 REQUIRE(pkt != nullptr);
589 REQUIRE(pkt->pts == 12000);
590 // Should be 12 bytes: full fragment size (leading zero is kept)
591 REQUIRE(pkt->size == 12);
592
593 REQUIRE(pkt->data[0] == 0x00); // Leading zero
594 REQUIRE(pkt->data[1] == 0x00);
595 REQUIRE(pkt->data[2] == 0x00);
596 REQUIRE(pkt->data[3] == 0x01);
597 REQUIRE(pkt->data[4] == 0x09);
598
599 av_packet_free(&pkt);
600 }
601}
602
603// ============================================================================
604// cReassemblyBufferAudio Tests
605// ============================================================================
606
607TEST_CASE("cReassemblyBufferAudio - MP2 codec detection", "[reassembly][audio]") {
608 SECTION("Detect MP2 audio codec") {
610
611 // MP2 sync word: 0xFFEx (11 bits sync + version/layer)
612 // Example: 0xFFF3 (sync) + bitrate/samplerate info
613 std::vector<uint8_t> fragment = { 0xFF, 0xF3, 0x44, 0xC0 }; // MP2 header
614
615 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_MP2);
616 }
617}
618
619TEST_CASE("cReassemblyBufferAudio - AC3 codec detection", "[reassembly][audio]") {
620 SECTION("Detect AC3 audio codec") {
622
623 // AC3 sync word: 0x0B77
624 std::vector<uint8_t> fragment = { 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00 }; // AC3 header
625
626 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_AC3);
627 }
628
629 SECTION("Detect E-AC3 audio codec") {
631
632 // E-AC3 sync word: 0x0B77 with bitstream ID > 10
633 std::vector<uint8_t> fragment = { 0x0B, 0x77, 0x00, 0x00, 0x00, 0x51 }; // E-AC3 header (byte 5 > 0x50)
634
635 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_EAC3);
636 }
637}
638
639TEST_CASE("cReassemblyBufferAudio - AAC LATM codec detection", "[reassembly][audio]") {
640 SECTION("Detect AAC LATM codec") {
642
643 // LATM sync word: 0x2B7 at 11 highest bits
644 std::vector<uint8_t> fragment = { 0x56, 0xE0, 0x00 }; // 0x2B7 << (24-11) = 0x56E000
645
646 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_AAC_LATM);
647 }
648}
649
650TEST_CASE("cReassemblyBufferAudio - ADTS codec detection", "[reassembly][audio]") {
651 SECTION("Detect ADTS codec") {
653
654 // ADTS sync word: 0xFFF
655 std::vector<uint8_t> fragment = { 0xFF, 0xF1, 0x50, 0x80, 0x00, 0x1F, 0xFC }; // ADTS header
656
657 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_AAC);
658 }
659}
660
661TEST_CASE("cReassemblyBufferAudio - Private stream handling", "[reassembly][audio]") {
662 SECTION("AC3 in private stream (not audio stream)") {
664
665 // AC3 can appear in private stream (0xBD) - still detected the same way
666 std::vector<uint8_t> fragment = { 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00 }; // AC3 header
667
668 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_AC3);
669 }
670}
671
672TEST_CASE("cReassemblyBufferAudio - Unknown codec", "[reassembly][audio]") {
673 SECTION("Garbage data returns NONE") {
675
676 std::vector<uint8_t> fragment = { 0x00, 0x00, 0x00 };
677
678 REQUIRE(buffer.DetectCodecFromSyncWord(fragment.data(), fragment.size()) == AV_CODEC_ID_NONE);
679 }
680}
681
682// ============================================================================
683// cReassemblyBufferAudio - FindSyncWord Tests
684// ============================================================================
685
686TEST_CASE("cReassemblyBufferAudio - FindSyncWord at start", "[reassembly][audio][syncword]") {
687 SECTION("Find MP2 sync word at position 0") {
689
690 // MP2 frame at the beginning
691 std::vector<uint8_t> data = { 0xFF, 0xF3, 0x44, 0xC0 };
692
693 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
694
695 REQUIRE(result.codecId == AV_CODEC_ID_MP2);
696 REQUIRE(result.pos == 0);
697 }
698}
699
700TEST_CASE("cReassemblyBufferAudio - FindSyncWord with offset", "[reassembly][audio][syncword]") {
701 SECTION("Find AC3 sync word at position 10") {
703
704 std::vector<uint8_t> data = {
705 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // 10 bytes garbage
706 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00 // AC3 sync word
707 };
708
709 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
710
711 REQUIRE(result.codecId == AV_CODEC_ID_AC3);
712 REQUIRE(result.pos == 10);
713 }
714
715 SECTION("Find LATM sync word in the middle") {
717
718 std::vector<uint8_t> data = {
719 0x00, 0x00, 0x00, // garbage
720 0x56, 0xE0, 0x00, // LATM sync word
721 0xFF, 0xFF // more data
722 };
723
724 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
725
726 REQUIRE(result.codecId == AV_CODEC_ID_AAC_LATM);
727 REQUIRE(result.pos == 3);
728 }
729}
730
731TEST_CASE("cReassemblyBufferAudio - FindSyncWord no match", "[reassembly][audio][syncword]") {
732 SECTION("No sync word in garbage data") {
734
735 std::vector<uint8_t> data = {
736 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
737 };
738
739 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
740
741 REQUIRE(result.codecId == AV_CODEC_ID_NONE);
742 REQUIRE(result.pos == -1);
743 }
744
745 SECTION("Empty data") {
747
748 std::vector<uint8_t> data = {};
749
750 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
751
752 REQUIRE(result.codecId == AV_CODEC_ID_NONE);
753 REQUIRE(result.pos == -1);
754 }
755
756 SECTION("Data too short for any codec") {
758
759 // Only 2 bytes - too short for any codec (min is 3)
760 std::vector<uint8_t> data = { 0xFF, 0xF1 };
761
762 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
763
764 REQUIRE(result.codecId == AV_CODEC_ID_NONE);
765 REQUIRE(result.pos == -1);
766 }
767}
768
769TEST_CASE("cReassemblyBufferAudio - FindSyncWord multiple candidates", "[reassembly][audio][syncword]") {
770 SECTION("Returns first valid sync word when multiple present") {
772
773 // AC3 sync word followed by MP2 sync word
774 std::vector<uint8_t> data = {
775 0x00, 0x00, // garbage
776 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00, // AC3 sync word at position 2
777 0xFF, 0xF3, 0x44, 0xC0 // MP2 sync word at position 8
778 };
779
780 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
781
782 // Should find the first one (AC3)
783 REQUIRE(result.codecId == AV_CODEC_ID_AC3);
784 REQUIRE(result.pos == 2);
785 }
786
787 SECTION("Find sync word when partial match exists earlier") {
789
790 // Partial AC3 sync (0x0B only) followed by full AC3 sync
791 std::vector<uint8_t> data = {
792 0x0B, 0x00, // partial match (not 0x0B77)
793 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00 // full AC3 sync word at position 2
794 };
795
796 SyncWordInfo result = buffer.FindSyncWord(data.data(), data.size());
797
798 REQUIRE(result.codecId == AV_CODEC_ID_AC3);
799 REQUIRE(result.pos == 2);
800 }
801}
802
803TEST_CASE("cReassemblyBufferAudio - TruncateBufferUntilFirstValidData at start", "[reassembly][audio][consecutive]") {
804 SECTION("Two consecutive LATM frames at position 0") {
806
807 // First LATM frame: sync word (0x56E0) + length (5 bytes payload) = 3 + 5 = 8 bytes total
808 // Second LATM frame: sync word (0x56E0) + length (3 bytes payload) = 3 + 3 = 6 bytes total
809 std::vector<uint8_t> data = {
810 0x56, 0xE0, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, // First frame (8 bytes)
811 0x56, 0xE0, 0x03, 0x11, 0x22, 0x33 // Second frame (6 bytes)
812 };
813
814 buffer.Push(data.data(), data.size(), 0);
815
816 REQUIRE(buffer.GetSize() == 14);
817 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_AAC_LATM);
818 }
819}
820
821TEST_CASE("cReassemblyBufferAudio - TruncateBufferUntilFirstValidData with offset", "[reassembly][audio][consecutive]") {
822 SECTION("Two consecutive LATM frames after garbage data") {
824
825 std::vector<uint8_t> data = {
826 0x00, 0x01, 0x02, 0x03, 0x04, // 5 bytes garbage
827 0x56, 0xE0, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, // First frame at pos 5 (7 bytes)
828 0x56, 0xE0, 0x02, 0x11, 0x22, 0x00 // Second frame (6 bytes)
829 };
830
831 buffer.Push(data.data(), data.size(), 0);
832
833 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_AAC_LATM);
834 REQUIRE(buffer.GetSize() == 13);
835 }
836
837 SECTION("Two consecutive LATM frames after false positive sync word") {
839
840 std::vector<uint8_t> data = {
841 0x56, 0xE0, 0x02, 0xAA, 0xBB, // LATM frame (5 bytes)
842 0x00, 0x00, 0x00, 0x00, // Garbage - no sync word at expected position
843 0x56, 0xE0, 0x02, 0x11, 0x22, // First real LATM frame at pos 9 (5 bytes)
844 0x56, 0xE0, 0x03, 0x44, 0x55, 0x66 // Second real LATM frame (6 bytes)
845 };
846
847 buffer.Push(data.data(), data.size(), 0);
848
849 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_AAC_LATM);
850 REQUIRE(buffer.GetSize() == 11);
851 }
852}
853
854TEST_CASE("cReassemblyBufferAudio - TruncateBufferUntilFirstValidData edge cases", "[reassembly][audio][consecutive]") {
855 SECTION("Only one LATM frame present") {
857
858 std::vector<uint8_t> data = {
859 0x56, 0xE0, 0x05, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE // Only one frame (8 bytes)
860 };
861
862 buffer.Push(data.data(), data.size(), 0);
863
864 REQUIRE(buffer.GetSize() == 8);
865 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
866 }
867
868 SECTION("First frame incomplete at end of buffer") {
870
871 // Frame header indicates 10 bytes payload, but only 5 bytes available
872 std::vector<uint8_t> data = {
873 0x56, 0xE0, 0x0A, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE // Header says 10 bytes, but only 5 follow
874 };
875
876 buffer.Push(data.data(), data.size(), 0);
877
878 REQUIRE(buffer.GetSize() == 8);
879 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
880 }
881
882 SECTION("Second frame incomplete") {
884
885 std::vector<uint8_t> data = {
886 0x56, 0xE0, 0x02, 0xAA, 0xBB, // First complete frame (5 bytes)
887 0x56, 0xE0 // Second frame header only (incomplete)
888 };
889
890 buffer.Push(data.data(), data.size(), 0);
891
892 REQUIRE(buffer.GetSize() == 7);
893 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
894 }
895
896 SECTION("Wrong codec for second frame") {
898
899 std::vector<uint8_t> data = {
900 0x56, 0xE0, 0x02, 0xAA, 0xBB, // LATM frame (5 bytes)
901 0x0B, 0x77, 0x00, 0x00, 0x00, 0x00 // AC3 sync word (wrong codec)
902 };
903
904 buffer.Push(data.data(), data.size(), 0);
905
906 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
907 REQUIRE(buffer.GetSize() == 6);
908 }
909
910 SECTION("Wrong codec for second frame, and first frame contains a sync word in payload, and second sync word is not long enough") {
912
913 std::vector<uint8_t> data = {
914 0x56, 0xE0, 0x03, 0x56, 0xE0, 0xAA, // LATM frame (6 bytes)
915 0x0B, 0x77, 0x00, 0x00, 0x00 // AC3 sync word (wrong codec)
916 };
917
918 buffer.Push(data.data(), data.size(), 0);
919
920 REQUIRE(buffer.GetSize() == 11);
921 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
922 }
923
924 SECTION("Only header") {
926
927 std::vector<uint8_t> data = {
928 0x56, 0xE0, 0x02
929 };
930
931 buffer.Push(data.data(), data.size(), 0);
932
933 REQUIRE(buffer.GetSize() == 3);
934 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
935 }
936}
937
938TEST_CASE("cReassemblyBufferAudio - TruncateBufferUntilFirstValidData no sync word", "[reassembly][audio][consecutive]") {
939 SECTION("No sync word in data") {
941
942 std::vector<uint8_t> data = {
943 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
944 };
945
946 buffer.Push(data.data(), data.size(), 0);
947
948 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
949 REQUIRE(buffer.GetSize() == 6);
950 }
951
952 SECTION("Empty data") {
954
955 std::vector<uint8_t> data = {};
956
957 buffer.Push(data.data(), data.size(), 0);
958
959 REQUIRE(buffer.GetSize() == 0);
960 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_NONE);
961 }
962}
963
964TEST_CASE("cReassemblyBufferAudio - TruncateBufferUntilFirstValidData with maximum length", "[reassembly][audio][consecutive]") {
965 SECTION("LATM frame with maximum 13-bit length field") {
967
968 // Maximum length in 13 bits: 0x1FFF = 8191 bytes
969 // First frame: 0x56, 0xFF, 0xFF = sync word + length 0x1FFF
970 // Total first frame size = 3 + 8191 = 8194 bytes
971 std::vector<uint8_t> data;
972 data.push_back(0x56);
973 data.push_back(0xFF);
974 data.push_back(0xFF);
975 data.resize(8194, 0xAA); // Fill first frame with dummy data
976
977 // Second frame: small frame
978 data.push_back(0x56);
979 data.push_back(0xE0);
980 data.push_back(0x01);
981 data.push_back(0xBB);
982 data.push_back(0x00);
983 data.push_back(0x00);
984
985 buffer.Push(data.data(), data.size(), 0);
986
987 REQUIRE(buffer.GetSize() == data.size());
988 REQUIRE(buffer.TruncateBufferUntilFirstValidData() == AV_CODEC_ID_AAC_LATM);
989 }
990}
991
992// ============================================================================
993// cReassemblyBufferAudio - GetFrameSize Tests
994// ============================================================================
995
996TEST_CASE("cReassemblyBufferAudio - GetFrameSize LATM codec", "[reassembly][audio][framesize]") {
998
999 SECTION("LATM frame with 0 byte payload") {
1000 // Frame size = ((0xE0 & 0x1F) << 8) + 0x00 + 3 = 3 bytes
1001 std::vector<uint8_t> data = {0x56, 0xE0, 0x00};
1002 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC_LATM, data.data());
1003 REQUIRE(frameSize == 3);
1004 }
1005
1006 SECTION("LATM frame with 256 byte payload") {
1007 // Frame size = ((0xE1 & 0x1F) << 8) + 0x00 + 3 = 259 bytes
1008 std::vector<uint8_t> data = {0x56, 0xE1, 0x00};
1009 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC_LATM, data.data());
1010 REQUIRE(frameSize == 259);
1011 }
1012
1013 SECTION("LATM frame with maximum 13-bit payload (8191 bytes)") {
1014 // 0x1FFF = 8191, encoded as 0x56 0xFF 0xFF
1015 // Frame size = ((0xFF & 0x1F) << 8) + 0xFF + 3 = 8194 bytes
1016 std::vector<uint8_t> data = {0x56, 0xFF, 0xFF};
1017 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC_LATM, data.data());
1018 REQUIRE(frameSize == 8194);
1019 }
1020}
1021
1022TEST_CASE("cReassemblyBufferAudio - GetFrameSize AAC/ADTS codec", "[reassembly][audio][framesize]") {
1024
1025 SECTION("ADTS frame with minimum size") {
1026 // Frame length field spans bytes 3-5: ((data[3] & 0x03) << 11) | (data[4] << 3) | ((data[5] & 0xE0) >> 5)
1027 // Minimum: 0x0007 (7 bytes) = header only
1028 std::vector<uint8_t> data = {0xFF, 0xF1, 0x50, 0x00, 0x00, 0xE0, 0x00};
1029 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC, data.data());
1030 REQUIRE(frameSize == 7);
1031 }
1032
1033 SECTION("ADTS frame with 100 byte total size") {
1034 // Frame size = 100 bytes
1035 // Encoding: 100 = 0x0064 = 0b000 0000 0110 0100
1036 // data[3] = 0x00 (bits 12-11)
1037 // data[4] = 0x0C (bits 10-3 = 0x0C = 12)
1038 // data[5] = 0x80 (bits 2-0 in upper 3 bits = 100)
1039 std::vector<uint8_t> data = {0xFF, 0xF1, 0x50, 0x00, 0x0C, 0x80, 0x00};
1040 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC, data.data());
1041 REQUIRE(frameSize == 100);
1042 }
1043
1044 SECTION("ADTS frame with 1024 byte total size") {
1045 // Frame size = 1024 bytes = 0x0400 = 0b0 10000000 000
1046 // 13-bit field: bits 12-11=00, bits 10-3=0x80, bits 2-0=000
1047 // data[3] = 0x00 (bits 12-11)
1048 // data[4] = 0x80 (bits 10-3)
1049 // data[5] = 0x00 (bits 2-0)
1050 std::vector<uint8_t> data = {0xFF, 0xF1, 0x50, 0x00, 0x80, 0x00, 0x00};
1051 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC, data.data());
1052 REQUIRE(frameSize == 1024);
1053 }
1054
1055 SECTION("ADTS frame with maximum 13-bit size (8191 bytes)") {
1056 // Frame size = 8191 bytes = 0x1FFF = 0b1 1111 1111 1111
1057 // data[3] = 0x03 (bits 12-11 = 11)
1058 // data[4] = 0xFF (bits 10-3 = 11111111)
1059 // data[5] = 0xE0 (bits 2-0 = 111)
1060 std::vector<uint8_t> data = {0xFF, 0xF1, 0x50, 0x03, 0xFF, 0xE0, 0x00};
1061 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AAC, data.data());
1062 REQUIRE(frameSize == 8191);
1063 }
1064}
1065
1066TEST_CASE("cReassemblyBufferAudio - GetFrameSize AC3 codec", "[reassembly][audio][framesize]") {
1068
1069 SECTION("AC3 frame - 48 kHz, smallest frame (128 bytes)") {
1070 // fscod = 01 (48 kHz), frmsizcod = 0 (first entry in table)
1071 // Frame size = Ac3FrameSizeTable[0][1] * 2 = 69 * 2 = 138 bytes
1072 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x40, 0x00};
1073 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data());
1074 REQUIRE(frameSize == 138);
1075 }
1076
1077 SECTION("AC3 frame - 44.1 kHz, smallest frame") {
1078 // fscod = 00 (44.1 kHz), frmsizcod = 0
1079 // Frame size = Ac3FrameSizeTable[0][0] * 2 = 64 * 2 = 128 bytes
1080 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x00, 0x00};
1081 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data());
1082 REQUIRE(frameSize == 128);
1083 }
1084
1085 SECTION("AC3 frame - 32 kHz, smallest frame") {
1086 // fscod = 10 (32 kHz), frmsizcod = 0
1087 // Frame size = Ac3FrameSizeTable[0][2] * 2 = 96 * 2 = 192 bytes
1088 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x80, 0x00};
1089 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data());
1090 REQUIRE(frameSize == 192);
1091 }
1092
1093 SECTION("AC3 frame - 48 kHz, largest frame (frmsizcod=37)") {
1094 // fscod = 01 (48 kHz), frmsizcod = 37
1095 // Frame size = Ac3FrameSizeTable[37][1] * 2 = 1394 * 2 = 2788 bytes
1096 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x65, 0x00};
1097 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data());
1098 REQUIRE(frameSize == 2788);
1099 }
1100
1101 SECTION("AC3 frame - 44.1 kHz, mid-range frame (frmsizcod=18)") {
1102 // fscod = 00 (44.1 kHz), frmsizcod = 18
1103 // Frame size = Ac3FrameSizeTable[18][0] * 2 = 320 * 2 = 640 bytes
1104 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x12, 0x00};
1105 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data());
1106 REQUIRE(frameSize == 640);
1107 }
1108}
1109
1110TEST_CASE("cReassemblyBufferAudio - GetFrameSize AC3 error conditions", "[reassembly][audio][framesize]") {
1112
1113 SECTION("AC3 invalid sample rate (fscod=11)") {
1114 // fscod = 11 (invalid), should throw
1115 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0xC0, 0x00};
1116 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data()), std::invalid_argument);
1117 }
1118
1119 SECTION("AC3 invalid frame size code (frmsizcod=38)") {
1120 // frmsizcod = 38 (out of range), should throw
1121 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x26, 0x00};
1122 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data()), std::invalid_argument);
1123 }
1124
1125 SECTION("AC3 invalid frame size code (frmsizcod=63)") {
1126 // frmsizcod = 63 (maximum invalid), should throw
1127 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x3F, 0x00};
1128 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_AC3, data.data()), std::invalid_argument);
1129 }
1130}
1131
1132TEST_CASE("cReassemblyBufferAudio - GetFrameSize E-AC3 codec", "[reassembly][audio][framesize]") {
1134
1135 SECTION("E-AC3 frame - minimum size (1 word = 2 bytes)") {
1136 // Frame size = (((data[2] & 0x07) << 8) + data[3] + 1) * 2
1137 // data[2] = 0x00, data[3] = 0x00 → (0 + 0 + 1) * 2 = 2 bytes
1138 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0x00, 0x51};
1139 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_EAC3, data.data());
1140 REQUIRE(frameSize == 2);
1141 }
1142
1143 SECTION("E-AC3 frame - 100 words (200 bytes)") {
1144 // (99 + 1) * 2 = 200 bytes
1145 // 99 = 0x63, data[2] = 0x00, data[3] = 0x63
1146 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x63, 0x00, 0x51};
1147 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_EAC3, data.data());
1148 REQUIRE(frameSize == 200);
1149 }
1150
1151 SECTION("E-AC3 frame - 512 words (1024 bytes)") {
1152 // (511 + 1) * 2 = 1024 bytes
1153 // 511 = 0x1FF, data[2] = 0x01, data[3] = 0xFF
1154 std::vector<uint8_t> data = {0x0B, 0x77, 0x01, 0xFF, 0x00, 0x51};
1155 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_EAC3, data.data());
1156 REQUIRE(frameSize == 1024);
1157 }
1158
1159 SECTION("E-AC3 frame - maximum 11-bit size (2047 words = 4094 bytes)") {
1160 // (2046 + 1) * 2 = 4094 bytes
1161 // 2046 = 0x7FE, data[2] = 0x07, data[3] = 0xFE
1162 std::vector<uint8_t> data = {0x0B, 0x77, 0x07, 0xFE, 0x00, 0x51};
1163 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_EAC3, data.data());
1164 REQUIRE(frameSize == 4094);
1165 }
1166}
1167
1168TEST_CASE("cReassemblyBufferAudio - GetFrameSize E-AC3 error conditions", "[reassembly][audio][framesize]") {
1170
1171 SECTION("E-AC3 invalid fscod/fscod2 combination") {
1172 // data[4] & 0xF0 == 0xF0 is invalid
1173 std::vector<uint8_t> data = {0x0B, 0x77, 0x00, 0x00, 0xF0, 0x51};
1174 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_EAC3, data.data()), std::invalid_argument);
1175 }
1176}
1177
1178TEST_CASE("cReassemblyBufferAudio - GetFrameSize MP2 codec", "[reassembly][audio][framesize]") {
1180
1181 SECTION("MP2 MPEG1 Layer 2, 128kbps, 44.1kHz, no padding") {
1182 // Bitrate index 8 (128k), Sample index 0 (44.1k), no padding
1183 // Expected: (144 * 128000) / 44100 = 417 bytes
1184 std::vector<uint8_t> data = {0xFF, 0xFD, 0x80, 0x00};
1185 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1186 REQUIRE(frameSize == 417);
1187 }
1188
1189 SECTION("MP2 MPEG1 Layer 2, 128kbps, 44.1kHz, with padding") {
1190 // Bitrate index 8 (128k), Sample index 0 (44.1k), with padding
1191 // Expected: (144 * 128000) / 44100 + 1 = 418 bytes
1192 std::vector<uint8_t> data = {0xFF, 0xFD, 0x82, 0x00};
1193 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1194 REQUIRE(frameSize == 418);
1195 }
1196
1197 SECTION("MP2 MPEG1 Layer 2, 192kbps, 48kHz, no padding") {
1198 // Bitrate index 10 (192k), Sample index 1 (48k), no padding
1199 // Expected: (144 * 192000) / 48000 = 576 bytes
1200 std::vector<uint8_t> data = {0xFF, 0xFD, 0xA4, 0x00};
1201 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1202 REQUIRE(frameSize == 576);
1203 }
1204
1205 SECTION("MP2 MPEG2 Layer 2, 64kbps, 24kHz, no padding") {
1206 // MPEG2 (bit 3 clear, bit 4 set), Bitrate index 8 (64k), Sample index 1 (24k)
1207 // Expected: (144 * 64000) / 24000 = 384 bytes
1208 std::vector<uint8_t> data = {0xFF, 0xF5, 0x84, 0x00};
1209 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1210 REQUIRE(frameSize == 384);
1211 }
1212
1213 SECTION("MP2 MPEG1 Layer 3, 128kbps, 44.1kHz, no padding") {
1214 // Bitrate index 9 (128k for Layer 3), Sample index 0 (44.1k)
1215 // Expected: (144 * 128000) / 44100 = 417 bytes
1216 std::vector<uint8_t> data = {0xFF, 0xFB, 0x90, 0x00};
1217 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1218 REQUIRE(frameSize == 417);
1219 }
1220
1221 SECTION("MP2 MPEG1 Layer 1, 128kbps, 44.1kHz, no padding") {
1222 // Bitrate index 4 (128k for Layer 1), Sample index 0 (44.1k)
1223 // Expected: ((12 * 128000) / 44100) * 4 = 34 * 4 = 136 bytes
1224 std::vector<uint8_t> data = {0xFF, 0xFF, 0x40, 0x00};
1225 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1226 REQUIRE(frameSize == 136);
1227 }
1228
1229 SECTION("MP2 MPEG1 Layer 1, 128kbps, 44.1kHz, with padding") {
1230 // Bitrate index 4 (128k for Layer 1), Sample index 0 (44.1k), with padding
1231 // Expected: ((12 * 128000) / 44100 + 1) * 4 = 35 * 4 = 140 bytes
1232 std::vector<uint8_t> data = {0xFF, 0xFF, 0x42, 0x00};
1233 int frameSize = buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data());
1234 REQUIRE(frameSize == 140);
1235 }
1236}
1237
1238TEST_CASE("cReassemblyBufferAudio - GetFrameSize MP2 error conditions", "[reassembly][audio][framesize]") {
1240
1241 SECTION("MP2 invalid sample rate (index 3)") {
1242 // Header: 0xFF 0xFD 0x5C 0x00 (sample rate index = 11)
1243 std::vector<uint8_t> data = {0xFF, 0xFD, 0x5C, 0x00};
1244 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data()), std::invalid_argument);
1245 }
1246
1247 SECTION("MP2 invalid bit rate (index 0)") {
1248 // Header: 0xFF 0xFD 0x04 0x00 (bitrate index = 0, which is forbidden)
1249 std::vector<uint8_t> data = {0xFF, 0xFD, 0x04, 0x00};
1250 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data()), std::invalid_argument);
1251 }
1252
1253 SECTION("MP2 invalid bit rate (index 15)") {
1254 // Header: 0xFF 0xFD 0xF0 0x00 (bitrate index = 15, which is forbidden)
1255 std::vector<uint8_t> data = {0xFF, 0xFD, 0xF0, 0x00};
1256 REQUIRE_THROWS_AS(buffer.GetFrameSizeForCodec(AV_CODEC_ID_MP2, data.data()), std::invalid_argument);
1257 }
1258}
1259
1260// ============================================================================
1261// cPtsTrackingBuffer Tests
1262// ============================================================================
1263
1264TEST_CASE("cPtsTrackingBuffer - Basic Push and GetPts", "[ptstracking]") {
1265 SECTION("Push data with PTS and retrieve it") {
1266 cPtsTrackingBuffer buffer("TEST");
1267
1268 std::vector<uint8_t> data = {0xAA, 0xBB, 0xCC, 0xDD};
1269 buffer.Push(data.data(), data.size(), 1000);
1270
1271 REQUIRE(buffer.GetSize() == 4);
1272 REQUIRE(buffer.GetPts() == 1000);
1273 }
1274
1275 SECTION("Push multiple data chunks with different PTS") {
1276 cPtsTrackingBuffer buffer("TEST");
1277
1278 std::vector<uint8_t> data1 = {0xAA, 0xBB};
1279 std::vector<uint8_t> data2 = {0xCC, 0xDD};
1280 std::vector<uint8_t> data3 = {0xEE, 0xFF};
1281
1282 buffer.Push(data1.data(), data1.size(), 1000);
1283 buffer.Push(data2.data(), data2.size(), 2000);
1284 buffer.Push(data3.data(), data3.size(), 3000);
1285
1286 REQUIRE(buffer.GetSize() == 6);
1287 REQUIRE(buffer.GetPts() == 1000); // Should return first PTS
1288 }
1289
1290 SECTION("Empty buffer returns AV_NOPTS_VALUE") {
1291 cPtsTrackingBuffer buffer("TEST");
1292
1293 REQUIRE(buffer.GetPts() == AV_NOPTS_VALUE);
1294 }
1295}
1296
1297TEST_CASE("cPtsTrackingBuffer - Erase basic functionality", "[ptstracking][erase]") {
1298 SECTION("Erase from buffer with single PTS entry") {
1299 cPtsTrackingBuffer buffer("TEST");
1300
1301 // Buffer: [0-9] with PTS at position 0
1302 std::vector<uint8_t> data(10, 0xAA);
1303 buffer.Push(data.data(), data.size(), 1000);
1304
1305 // Erase first 5 bytes
1306 buffer.Erase(5);
1307
1308 REQUIRE(buffer.GetSize() == 5);
1309 REQUIRE(buffer.GetPts() == 1000); // PTS should be preserved at position 0
1310 }
1311
1312 SECTION("Erase entire buffer") {
1313 cPtsTrackingBuffer buffer("TEST");
1314
1315 std::vector<uint8_t> data(10, 0xAA);
1316 buffer.Push(data.data(), data.size(), 1000);
1317
1318 buffer.Erase(10);
1319
1320 REQUIRE(buffer.GetSize() == 0);
1321 }
1322}
1323
1324TEST_CASE("cPtsTrackingBuffer - Erase with multiple PTS entries", "[ptstracking][erase]") {
1325 SECTION("Erase exactly at PTS boundary") {
1326 cPtsTrackingBuffer buffer("TEST");
1327
1328 // Position 0: PTS=1000
1329 std::vector<uint8_t> data1(10, 0xAA);
1330 buffer.Push(data1.data(), data1.size(), 1000);
1331
1332 // Position 10: PTS=2000
1333 std::vector<uint8_t> data2(10, 0xBB);
1334 buffer.Push(data2.data(), data2.size(), 2000);
1335 // Erase exactly 10 bytes (up to but not including position 10)
1336 buffer.Erase(10);
1337
1338 REQUIRE(buffer.GetSize() == 10);
1339 REQUIRE(buffer.GetPts() == 2000); // Position 10 became position 0 with its PTS
1340 }
1341
1342 SECTION("Erase removes old PTS, keeps and adjusts newer PTS") {
1343 cPtsTrackingBuffer buffer("TEST");
1344
1345 // Position 0: PTS=1000
1346 std::vector<uint8_t> data1 = {0x00, 0x01, 0x02, 0x03, 0x04};
1347 buffer.Push(data1.data(), data1.size(), 1000);
1348
1349 // Position 5: PTS=2000
1350 std::vector<uint8_t> data2 = {0x05, 0x06, 0x07, 0x08, 0x09};
1351 buffer.Push(data2.data(), data2.size(), 2000);
1352 // Position 10: PTS=3000
1353 std::vector<uint8_t> data3 = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E};
1354 buffer.Push(data3.data(), data3.size(), 3000);
1355
1356 // Erase first 5 bytes (removes data with PTS 1000)
1357 buffer.Erase(5);
1358
1359 REQUIRE(buffer.GetSize() == 10);
1360 REQUIRE(buffer.GetPts() == 2000); // Position 5 became position 0, keeping PTS 2000
1361 }
1362
1363 SECTION("Erase preserves PTS when erasing before next PTS entry") {
1364 cPtsTrackingBuffer buffer("TEST");
1365
1366 // Position 0: PTS=1000
1367 std::vector<uint8_t> data1(10, 0xAA);
1368 buffer.Push(data1.data(), data1.size(), 1000);
1369
1370 // Position 10: PTS=2000
1371 std::vector<uint8_t> data2(10, 0xBB);
1372 buffer.Push(data2.data(), data2.size(), 2000);
1373 // Erase 7 bytes (less than position 10)
1374 buffer.Erase(7);
1375
1376 REQUIRE(buffer.GetSize() == 13);
1377 REQUIRE(buffer.GetPts() == 1000); // Should preserve PTS 1000 at new position 0
1378 }
1379}
1380
1381TEST_CASE("cPtsTrackingBuffer - Erase PTS inheritance logic", "[ptstracking][erase]") {
1382 SECTION("PTS is preserved at new position 0 when erasing between PTS entries") {
1383 cPtsTrackingBuffer buffer("TEST");
1384
1385 // Position 0: PTS=1000
1386 std::vector<uint8_t> data1(10, 0xAA);
1387 buffer.Push(data1.data(), data1.size(), 1000);
1388
1389 // Position 10: PTS=2000
1390 std::vector<uint8_t> data2(10, 0xBB);
1391 buffer.Push(data2.data(), data2.size(), 2000);
1392 // Position 20: PTS=3000
1393 std::vector<uint8_t> data3(10, 0xCC);
1394 buffer.Push(data3.data(), data3.size(), 3000);
1395
1396 // Erase 12 bytes (removes position 0 and part of data before position 10)
1397 // After erase: data from position 12 onwards remains
1398 // Position 12 had no PTS entry, should inherit from largest removed PTS (2000)
1399 buffer.Erase(12);
1400
1401 REQUIRE(buffer.GetSize() == 18);
1402 REQUIRE(buffer.GetPts() == 2000);
1403 }
1404}
1405
1406TEST_CASE("cPtsTrackingBuffer - Erase with fragmented frames", "[ptstracking][erase]") {
1407 SECTION("Simulates removing partial frame while preserving PTS") {
1408 cPtsTrackingBuffer buffer("TEST");
1409
1410 // Frame 1 (complete): position 0-49, PTS=1000
1411 std::vector<uint8_t> frame1(50, 0xAA);
1412 buffer.Push(frame1.data(), frame1.size(), 1000);
1413
1414 // Frame 2 (complete): position 50-149, PTS=2000
1415 std::vector<uint8_t> frame2(100, 0xBB);
1416 buffer.Push(frame2.data(), frame2.size(), 2000);
1417 // Frame 3 (partial): position 150-199, PTS=3000
1418 std::vector<uint8_t> frame3_part(50, 0xCC);
1419 buffer.Push(frame3_part.data(), frame3_part.size(), 3000);
1420
1421 // Drain frame 1 and part of frame 2
1422 buffer.Erase(80);
1423
1424 REQUIRE(buffer.GetSize() == 120);
1425 // Remaining data (partial frame 2 + frame 3) should have PTS from frame 2's start
1426 REQUIRE(buffer.GetPts() == 2000);
1427 }
1428
1429 SECTION("Multiple erases progressively consume data") {
1430 cPtsTrackingBuffer buffer("TEST");
1431
1432 // Add data with PTS entries
1433 std::vector<uint8_t> data1(10, 0xAA);
1434 buffer.Push(data1.data(), data1.size(), 1000);
1435
1436 std::vector<uint8_t> data2(10, 0xBB);
1437 buffer.Push(data2.data(), data2.size(), 2000);
1438
1439 std::vector<uint8_t> data3(10, 0xCC);
1440 buffer.Push(data3.data(), data3.size(), 3000);
1441
1442 // First erase
1443 buffer.Erase(5);
1444 REQUIRE(buffer.GetSize() == 25);
1445 REQUIRE(buffer.GetPts() == 1000);
1446
1447 // Second erase
1448 buffer.Erase(8);
1449 REQUIRE(buffer.GetSize() == 17);
1450 REQUIRE(buffer.GetPts() == 2000);
1451
1452 // Third erase
1453 buffer.Erase(12);
1454 REQUIRE(buffer.GetSize() == 5);
1455 REQUIRE(buffer.GetPts() == 3000);
1456 }
1457}
1458
1459TEST_CASE("cPtsTrackingBuffer - Erase edge cases", "[ptstracking][erase]") {
1460 SECTION("Erase 0 bytes does nothing") {
1461 cPtsTrackingBuffer buffer("TEST");
1462
1463 std::vector<uint8_t> data(10, 0xAA);
1464 buffer.Push(data.data(), data.size(), 1000);
1465
1466 buffer.Erase(0);
1467
1468 REQUIRE(buffer.GetSize() == 10);
1469 REQUIRE(buffer.GetPts() == 1000);
1470 }
1471
1472 SECTION("Erase single byte") {
1473 cPtsTrackingBuffer buffer("TEST");
1474
1475 std::vector<uint8_t> data = {0xAA};
1476 buffer.Push(data.data(), data.size(), 1000);
1477
1478 std::vector<uint8_t> data2 = {0xBB};
1479 buffer.Push(data2.data(), data2.size(), 2000);
1480
1481 buffer.Erase(1);
1482
1483 REQUIRE(buffer.GetSize() == 1);
1484 REQUIRE(buffer.GetPts() == 2000);
1485 }
1486}
1487
1488TEST_CASE("cPtsTrackingBuffer - Complex scenarios", "[ptstracking][erase]") {
1489 SECTION("Interleaved push and erase operations") {
1490 cPtsTrackingBuffer buffer("TEST");
1491
1492 // Initial data
1493 std::vector<uint8_t> data1(20, 0xAA);
1494 buffer.Push(data1.data(), data1.size(), 1000);
1495
1496 // Erase some
1497 buffer.Erase(5);
1498 REQUIRE(buffer.GetSize() == 15);
1499 REQUIRE(buffer.GetPts() == 1000);
1500
1501 // Add more data
1502 std::vector<uint8_t> data2(10, 0xBB);
1503 buffer.Push(data2.data(), data2.size(), 2000);
1504 REQUIRE(buffer.GetSize() == 25);
1505
1506 // Erase across PTS boundary
1507 buffer.Erase(18);
1508 REQUIRE(buffer.GetSize() == 7);
1509 REQUIRE(buffer.GetPts() == 2000);
1510 }
1511
1512 SECTION("Three PTS entries, erase middle one") {
1513 cPtsTrackingBuffer buffer("TEST");
1514
1515 // Position 0: PTS=1000
1516 std::vector<uint8_t> data1(10, 0xAA);
1517 buffer.Push(data1.data(), data1.size(), 1000);
1518
1519 // Position 10: PTS=2000
1520 std::vector<uint8_t> data2(10, 0xBB);
1521 buffer.Push(data2.data(), data2.size(), 2000);
1522 // Position 20: PTS=3000
1523 std::vector<uint8_t> data3(10, 0xCC);
1524 buffer.Push(data3.data(), data3.size(), 3000);
1525
1526 // Erase 15 bytes (removes first PTS, middle of second chunk)
1527 buffer.Erase(15);
1528
1529 REQUIRE(buffer.GetSize() == 15);
1530 REQUIRE(buffer.GetPts() == 2000); // Should inherit from position 10
1531 }
1532
1533 SECTION("Large buffer with many PTS entries") {
1534 cPtsTrackingBuffer buffer("TEST");
1535
1536 // Add 10 chunks of 100 bytes each with different PTS
1537 for (int i = 0; i < 10; i++) {
1538 std::vector<uint8_t> data(100, static_cast<uint8_t>(i));
1539 buffer.Push(data.data(), data.size(), 1000 * (i + 1));
1540 }
1541
1542 REQUIRE(buffer.GetSize() == 1000);
1543 REQUIRE(buffer.GetPts() == 1000);
1544
1545 // Erase 450 bytes (removes first 4 chunks and half of 5th)
1546 buffer.Erase(450);
1547
1548 REQUIRE(buffer.GetSize() == 550);
1549 REQUIRE(buffer.GetPts() == 5000); // Should be from 5th chunk
1550 }
1551}
1552
1553TEST_CASE("cPtsTrackingBuffer - Reset functionality", "[ptstracking]") {
1554 SECTION("Reset clears all data and PTS") {
1555 cPtsTrackingBuffer buffer("TEST");
1556
1557 std::vector<uint8_t> data1(10, 0xAA);
1558 buffer.Push(data1.data(), data1.size(), 1000);
1559
1560 std::vector<uint8_t> data2(10, 0xBB);
1561 buffer.Push(data2.data(), data2.size(), 2000);
1562
1563 buffer.Reset();
1564
1565 REQUIRE(buffer.GetSize() == 0);
1566 REQUIRE(buffer.GetPts() == AV_NOPTS_VALUE);
1567 }
1568}
Audio PES packet parser.
Definition: pes.h:83
Video PES packet parser.
Definition: pes.h:70
bool IsValid()
Check if the PES packet is valid.
Definition: pes.cpp:256
int GetPayloadSize()
Get the size of the PES payload.
Definition: pes.cpp:324
int GetPacketLength()
Get the total length of the PES packet.
Definition: pes.cpp:346
const uint8_t * GetPayload()
Get a pointer to the PES payload data.
Definition: pes.cpp:311
int64_t GetPts()
Get the Presentation Time Stamp (PTS) from the PES header.
Definition: pes.cpp:295
Buffer that tracks PTS values at specific byte positions.
Definition: pes.h:99
int GetSize()
Definition: pes.h:107
void Push(const uint8_t *, int, int64_t)
Push data into the PTS tracking buffer.
Definition: pes.cpp:650
void Reset()
Definition: pes.h:106
void Erase(size_t)
Erase data from the beginning of the buffer.
Definition: pes.cpp:670
int64_t GetPts()
Get the PTS value for the current buffer position.
Definition: pes.cpp:707
Audio stream reassembly buffer.
Definition: pes.h:172
AVCodecID TruncateBufferUntilFirstValidData()
Truncate buffer until the first valid audio frame.
Definition: pes.cpp:499
AVCodecID DetectCodecFromSyncWord(const uint8_t *, int)
Detect audio codec from sync word pattern.
Definition: pes.cpp:595
SyncWordInfo FindSyncWord(const uint8_t *, int)
Find the first audio sync word in data.
Definition: pes.cpp:573
int GetFrameSizeForCodec(AVCodecID, const uint8_t *)
Get the frame size for a given codec and frame header.
Definition: pes.cpp:619
Video stream reassembly buffer.
Definition: pes.h:143
bool HasLeadingZero(const uint8_t *, int)
Check if video data has a leading zero byte before the start code.
Definition: pes.cpp:444
bool ParseCodecHeader(const uint8_t *, int)
Parse video codec header to detect codec type.
Definition: pes.cpp:411
AVPacket * PopAvPacket() override
Definition: pes.h:146
size_t GetSize()
Definition: pes.h:126
virtual void Push(const uint8_t *data, int size, int64_t pts)
Definition: pes.h:123
AVCodecID GetCodec()
Definition: pes.h:128
#define AV_NOPTS_VALUE
Definition: misc.h:35
Information about a detected audio sync word.
Definition: pes.h:161
AVCodecID codecId
Detected codec ID.
Definition: pes.h:162
int pos
Position of sync word in buffer.
Definition: pes.h:163
std::vector< uint8_t > createHevcPesVideoPacket(bool withLeadingZero=false)
Definition: test_pes.cpp:110
std::vector< uint8_t > createBasicPesHeader(uint8_t streamId, bool withPts=false, uint16_t pesLength=0)
Definition: test_pes.cpp:31
std::vector< uint8_t > createMpeg2PesPacket()
Definition: test_pes.cpp:69
std::vector< uint8_t > createAudioPesPacket()
Definition: test_pes.cpp:133
std::vector< uint8_t > createH264PesPacket(bool withLeadingZero=false)
Definition: test_pes.cpp:87
TEST_CASE("cPesVideo - Basic construction", "[pes]")
Definition: test_pes.cpp:144