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