libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
cea608.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_PROAV
13#include <cstdint>
14#include <promeki/color.h>
15#include <promeki/namespace.h>
16
17PROMEKI_NAMESPACE_BEGIN
18
66struct Cea608 {
67 // -- Odd parity ---------------------------------------------
68
72 static constexpr uint8_t withOddParity(uint8_t c) {
73 uint8_t v = static_cast<uint8_t>(c & 0x7F);
74 unsigned ones = 0;
75 for (int i = 0; i < 7; ++i) {
76 if (v & (1u << i)) ++ones;
77 }
78 if ((ones & 1u) == 0u) v = static_cast<uint8_t>(v | 0x80);
79 return v;
80 }
81
84 static constexpr bool checkOddParity(uint8_t c) {
85 unsigned ones = 0;
86 for (int i = 0; i < 8; ++i) {
87 if (c & (1u << i)) ++ones;
88 }
89 return (ones & 1u) != 0u;
90 }
91
93 static constexpr uint8_t stripParity(uint8_t c) { return static_cast<uint8_t>(c & 0x7F); }
94
106 static constexpr bool checkOddParityPair(uint8_t b1, uint8_t b2) {
107 return checkOddParity(b1) && checkOddParity(b2);
108 }
109
110 // -- Channel selectors (pre-parity first-byte values) -------
111
120 enum class Channel : uint8_t {
121 CC1 = 0,
122 CC2 = 1,
123 CC3 = 2,
124 CC4 = 3,
125 };
126
131 static constexpr bool isSecondChannelOfField(Channel ch) {
132 return ch == Channel::CC2 || ch == Channel::CC4;
133 }
134
139 static constexpr bool isFieldTwo(Channel ch) {
140 return ch == Channel::CC3 || ch == Channel::CC4;
141 }
142
151 static constexpr uint8_t Cc1MiscFirstByte = 0x14;
152
153 // -- Misc control codes (second-byte values) ----------------
154
155 static constexpr uint8_t MiscRCL = 0x20;
156 static constexpr uint8_t MiscBS = 0x21;
157 static constexpr uint8_t MiscDER = 0x24;
158 static constexpr uint8_t MiscRU2 = 0x25;
159 static constexpr uint8_t MiscRU3 = 0x26;
160 static constexpr uint8_t MiscRU4 = 0x27;
161 static constexpr uint8_t MiscFON = 0x28;
162 static constexpr uint8_t MiscRDC = 0x29;
163 static constexpr uint8_t MiscTR = 0x2A;
164 static constexpr uint8_t MiscRTD = 0x2B;
165 static constexpr uint8_t MiscEDM = 0x2C;
166 static constexpr uint8_t MiscCR = 0x2D;
167 static constexpr uint8_t MiscENM = 0x2E;
168 static constexpr uint8_t MiscEOC = 0x2F;
169
170 // -- Convenience: pre-parity (b1, b2) for CC1 control pairs --
171
173 static constexpr uint8_t RclB1 = Cc1MiscFirstByte;
174 static constexpr uint8_t RclB2 = MiscRCL;
175
177 static constexpr uint8_t EocB1 = Cc1MiscFirstByte;
178 static constexpr uint8_t EocB2 = MiscEOC;
179
181 static constexpr uint8_t EdmB1 = Cc1MiscFirstByte;
182 static constexpr uint8_t EdmB2 = MiscEDM;
183
185 static constexpr uint8_t EnmB1 = Cc1MiscFirstByte;
186 static constexpr uint8_t EnmB2 = MiscENM;
187
188 // -- PAC (Preamble Address Code) ----------------------------
189
196 static constexpr uint8_t PacRow15Col0WhiteB1 = 0x14;
197 static constexpr uint8_t PacRow15Col0WhiteB2 = 0x70;
198
199 // -- CEA-608 colour palette ---------------------------------
200
231 enum class CaptionColor : uint8_t {
232 White = 0,
233 Green = 1,
234 Blue = 2,
235 Cyan = 3,
236 Red = 4,
237 Yellow = 5,
238 Magenta = 6,
239 Black = 7,
240 };
241
246 static constexpr size_t CaptionColorCount = 8;
247
262 static constexpr size_t FgCaptionColorCount = 7;
263
280 static Color::List palette();
281
283 struct PacAttr {
285 int row = 15;
291 int indentCol = 0;
293 CaptionColor color = CaptionColor::White;
297 bool italic = false;
299 bool underline = false;
300 };
301
320 static void encodePac(const PacAttr &attr, uint8_t &b1, uint8_t &b2);
321
331 static bool isPac(uint8_t b1, uint8_t b2);
332
339 static bool decodePac(uint8_t b1, uint8_t b2, PacAttr &out);
340
341 // -- Mid-row codes ------------------------------------------
342
357 static void encodeMidRow(CaptionColor color, bool italic, bool underline, uint8_t &b1,
358 uint8_t &b2);
359
364 static bool isMidRow(uint8_t b1, uint8_t b2);
365
371 static bool decodeMidRow(uint8_t b1, uint8_t b2, CaptionColor &outColor, bool &outItalic,
372 bool &outUnderline);
373
374 // -- Background attribute codes -----------------------------
375 //
376 // Background attribute codes (CTA-608-E §6.2 Table 3) live
377 // at @c b1=0x10 (CC1 channel 1) with @c b2 in @c [0x20, 0x2F].
378 // They set the background colour and opacity for subsequent
379 // characters on the row. Doubled per spec like other
380 // control codes. This is part of the §6.2 "extended"
381 // attribute set — older 608 decoders that don't recognise
382 // them treat the bytes as no-ops, so emitting them is safe.
383
397 static void encodeBgAttribute(CaptionColor color, bool semiTransparent, uint8_t &b1,
398 uint8_t &b2);
399
404 static bool isBgAttribute(uint8_t b1, uint8_t b2);
405
412 static bool decodeBgAttribute(uint8_t b1, uint8_t b2, CaptionColor &outColor,
413 bool &outSemiTransparent);
414
430 static bool isBt(uint8_t b1, uint8_t b2);
431
442 static void encodeBackgroundTransparency(uint8_t &b1, uint8_t &b2) {
443 b1 = Cc1ExtAttrB1;
444 b2 = BtB2;
445 }
446
447 // -- Tab Offset codes (CTA-608-E §6.2 Table 3) -------------
448 //
449 // Tab Offset codes nudge the pen position 1, 2, or 3
450 // columns to the right of where a PAC just landed it.
451 // They're the fine-grained complement to PAC's
452 // multiples-of-4 indent slots: PAC indent 8 + Tab
453 // Offset T2 places a row at column 10 (8 + 2). The
454 // encoder uses them to honour @ref SubtitleAnchor's
455 // horizontal half (Center / Right) when the cue's
456 // computed start column isn't already a multiple of 4.
457
465 static constexpr uint8_t Cc1ExtAttrB1 = 0x17;
466
470 static constexpr uint8_t TabOffsetT1 = 0x21;
471 static constexpr uint8_t TabOffsetT2 = 0x22;
472 static constexpr uint8_t TabOffsetT3 = 0x23;
473
501 static void encodeTabOffset(int columns, uint8_t &b1, uint8_t &b2);
502
507 static bool isTabOffset(uint8_t b1, uint8_t b2);
508
518 static bool decodeTabOffset(uint8_t b1, uint8_t b2, int &outColumns);
519
520 // -- Foreground Attribute Codes (FA / FAU, BT) --------------
521 //
522 // CTA-608-E §6.2 Table 3: optional "extended decoder"
523 // codes that supplement the standard 7-colour PAC + mid-
524 // row foreground palette with a Black-foreground entry,
525 // and supplement the BG attribute set with a "Background
526 // Transparent" entry. All three live in CC1's @c 0x17
527 // first-byte family (CC2 form uses @c 0x1F via
528 // @ref applyChannel).
529 //
530 // Per spec, each FA / FAU pair is preceded by a literal
531 // space and "incorporates an automatic BS" on extended
532 // decoders so the visual effect is a single space
533 // tinted with the new colour. The encoder honours this
534 // when emitting Black-foreground spans; the decoder
535 // applies the colour to subsequent characters without
536 // synthesising the BS (the upstream space + BS pair
537 // are a backward-compat hack for standard decoders).
538 //
539 // Field 2 doesn't remap these — §8.4(a)(b) only covers
540 // @c 0x14 ↔ 0x15 and @c 0x1C ↔ 0x1D. CC3 / CC4 use
541 // the same 0x17 / 0x1F first bytes as CC1 / CC2.
542
544 static constexpr uint8_t BtB2 = 0x2D;
546 static constexpr uint8_t FaB2 = 0x2E;
548 static constexpr uint8_t FauB2 = 0x2F;
549
559 static void encodeFgBlack(bool underline, uint8_t &b1, uint8_t &b2) {
560 b1 = Cc1ExtAttrB1;
561 b2 = underline ? FauB2 : FaB2;
562 }
563
567 static bool isFgBlack(uint8_t b1, uint8_t b2) {
568 return b1 == Cc1ExtAttrB1 && (b2 == FaB2 || b2 == FauB2);
569 }
570
575 static bool decodeFgBlack(uint8_t b1, uint8_t b2, bool &outUnderline) {
576 if (!isFgBlack(b1, b2)) return false;
577 outUnderline = (b2 == FauB2);
578 return true;
579 }
580
581 // -- Null pair (no-op filler) -------------------------------
582
588 static constexpr uint8_t NullB1 = 0x00;
589 static constexpr uint8_t NullB2 = 0x00;
590
591 // -- Doubled-control-code detection -------------------------
592
599 static constexpr bool isControlPair(uint8_t b1, uint8_t b2) {
600 (void)b2;
601 return b1 >= 0x10 && b1 <= 0x1F;
602 }
603
604 // -- Channel retargeting (CTA-608-E §8.4) -------------------
605
642 static constexpr uint8_t applyChannel(uint8_t b1, uint8_t b2, Channel channel) {
643 if (b1 < 0x10 || b1 > 0x1F) return b1;
644 uint8_t out = b1;
645 if (isSecondChannelOfField(channel)) {
646 out = static_cast<uint8_t>(out | 0x08);
647 }
648 if (isFieldTwo(channel) && b2 >= 0x20 && b2 <= 0x2F) {
649 if (out == 0x14) out = 0x15;
650 else if (out == 0x1C) out = 0x1D;
651 }
652 return out;
653 }
654
664 static constexpr void applyChannelInPlace(uint8_t &b1, uint8_t b2, Channel channel) {
665 b1 = applyChannel(b1, b2, channel);
666 }
667
668 // -- XDS framing predicates (CTA-608-E §9.3) ----------------
669
675 static constexpr bool isXdsControl(uint8_t b1) {
676 return b1 >= 0x01 && b1 <= 0x0E;
677 }
678
683 static constexpr bool isXdsTerminator(uint8_t b1, uint8_t b2) {
684 (void)b2;
685 return b1 == 0x0F;
686 }
687
688 // -- Predicates ---------------------------------------------
689
697 static constexpr bool isBasicChar(uint8_t c) {
698 const uint8_t v = stripParity(c);
699 return v >= 0x20 && v <= 0x7F;
700 }
701
702 Cea608() = delete;
703};
704
737struct Cea608PairDeduper {
743 bool accept(uint8_t b1, uint8_t b2) {
744 if (!Cea608::isControlPair(b1, b2)) {
745 // Informational pair clears the dedup window;
746 // a control pair following an info pair is
747 // always a fresh logical event.
748 _hasPending = false;
749 return true;
750 }
751 if (_hasPending && b1 == _lastB1 && b2 == _lastB2) {
752 // Second copy of the same control pair —
753 // suppress it and clear the window so a
754 // *third* copy (rare, but seen in real
755 // streams) is recognised as a new event.
756 _hasPending = false;
757 return false;
758 }
759 _lastB1 = b1;
760 _lastB2 = b2;
761 _hasPending = true;
762 return true;
763 }
764
769 void reset() {
770 _lastB1 = 0;
771 _lastB2 = 0;
772 _hasPending = false;
773 }
774
775 private:
776 uint8_t _lastB1 = 0;
777 uint8_t _lastB2 = 0;
778 bool _hasPending = false;
779};
780
781PROMEKI_NAMESPACE_END
782
783#endif // PROMEKI_ENABLE_PROAV