Shaka Packager SDK
Loading...
Searching...
No Matches
es_parser_teletext.cc
1// Copyright 2020 Google Inc. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/media/formats/mp2t/es_parser_teletext.h>
8
9#include <packager/media/base/bit_reader.h>
10#include <packager/media/base/timestamp.h>
11#include <packager/media/formats/mp2t/es_parser_teletext_tables.h>
12#include <packager/media/formats/mp2t/mp2t_common.h>
13#include <iostream>
14
15namespace shaka {
16namespace media {
17namespace mp2t {
18
19namespace {
20
21constexpr const char* kRegionTeletextPrefix = "ttx_";
22
23const uint8_t EBU_TELETEXT_WITH_SUBTITLING = 0x03;
24const int kPayloadSize = 40;
25const int kNumTriplets = 13;
26
27template <typename T>
28constexpr T bit(T value, const size_t bit_pos) {
29 return (value >> bit_pos) & 0x1;
30}
31
32uint8_t ReadHamming(BitReader& reader) {
33 uint8_t bits;
34 RCHECK(reader.ReadBits(8, &bits));
35 return TELETEXT_HAMMING_8_4[bits];
36}
37
38bool Hamming_24_18(const uint32_t value, uint32_t& out_result) {
39 uint32_t result = value;
40
41 uint8_t test = 0;
42 for (uint8_t i = 0; i < 23; i++) {
43 test ^= ((result >> i) & 0x01) * (i + 0x21);
44 }
45 test ^= ((result >> 0x17) & 0x01) * 0x20;
46
47 if ((test & 0x1f) != 0x1f) {
48 if ((test & 0x20) == 0x20) {
49 return false;
50 }
51 result ^= 1 << (0x1e - test);
52 }
53
54 out_result = (result & 0x000004) >> 2 | (result & 0x000070) >> 3 |
55 (result & 0x007f00) >> 4 | (result & 0x7f0000) >> 5;
56 return true;
57}
58
59bool ParseSubtitlingDescriptor(
60 const uint8_t* descriptor,
61 const size_t size,
62 std::unordered_map<uint16_t, std::string>& result) {
63 BitReader reader(descriptor, size);
64 RCHECK(reader.SkipBits(8));
65
66 size_t data_size;
67 RCHECK(reader.ReadBits(8, &data_size));
68 RCHECK(data_size + 2 <= size);
69
70 for (size_t i = 0; i < data_size; i += 8) {
71 uint32_t lang_code;
72 RCHECK(reader.ReadBits(24, &lang_code));
73 uint8_t ignored_teletext_type;
74 RCHECK(reader.ReadBits(5, &ignored_teletext_type));
75 uint8_t magazine_number;
76 RCHECK(reader.ReadBits(3, &magazine_number));
77 if (magazine_number == 0) {
78 magazine_number = 8;
79 }
80
81 uint8_t page_number_tens;
82 RCHECK(reader.ReadBits(4, &page_number_tens));
83 uint8_t page_number_units;
84 RCHECK(reader.ReadBits(4, &page_number_units));
85 const uint8_t page_number = page_number_tens * 10 + page_number_units;
86
87 std::string lang(3, '\0');
88 lang[0] = static_cast<char>((lang_code >> 16) & 0xff);
89 lang[1] = static_cast<char>((lang_code >> 8) & 0xff);
90 lang[2] = static_cast<char>((lang_code >> 0) & 0xff);
91
92 const uint16_t index = magazine_number * 100 + page_number;
93 result.emplace(index, std::move(lang));
94 }
95
96 return true;
97}
98
99} // namespace
100
101EsParserTeletext::EsParserTeletext(const uint32_t pid,
102 const NewStreamInfoCB& new_stream_info_cb,
103 const EmitTextSampleCB& emit_sample_cb,
104 const uint8_t* descriptor,
105 const size_t descriptor_length)
106 : EsParser(pid),
107 new_stream_info_cb_(new_stream_info_cb),
108 emit_sample_cb_(emit_sample_cb),
109 magazine_(0),
110 page_number_(0),
111 charset_code_(0),
112 current_charset_{},
113 last_pts_(-1),
114 last_end_pts_(-1),
115 inside_sample_(false) {
116 if (!ParseSubtitlingDescriptor(descriptor, descriptor_length, languages_)) {
117 LOG(ERROR) << "Unable to parse teletext_descriptor";
118 }
119 UpdateCharset();
120}
121
122bool EsParserTeletext::Parse(const uint8_t* buf,
123 int size,
124 int64_t pts,
125 int64_t dts) {
126 if (!sent_info_) {
127 sent_info_ = true;
128 auto info = std::make_shared<TextStreamInfo>(pid(), kMpeg2Timescale,
129 kInfiniteDuration, kCodecText,
130 "", "", 0, 0, "");
131 for (const auto& pair : languages_) {
132 info->AddSubStream(pair.first, {pair.second});
133 }
134
135 new_stream_info_cb_(info);
136 }
137
138 return ParseInternal(buf, size, pts);
139}
140
141bool EsParserTeletext::Flush() {
142 std::vector<uint16_t> keys;
143 for (const auto& entry : page_state_) {
144 keys.push_back(entry.first);
145 }
146
147 for (const auto key : keys) {
148 SendCueEnd(key, last_pts_);
149 }
150
151 return true;
152}
153
154void EsParserTeletext::Reset() {
155 page_state_.clear();
156 magazine_ = 0;
157 page_number_ = 0;
158 sent_info_ = false;
159 charset_code_ = 0;
160 inside_sample_ = false;
161 UpdateCharset();
162}
163
164bool EsParserTeletext::ParseInternal(const uint8_t* data,
165 const size_t size,
166 const int64_t pts) {
167 BitReader reader(data, size);
168 RCHECK(reader.SkipBits(8));
169 std::vector<TextRow> rows;
170 uint8_t last_magazine_seen = 0;
171
172 while (reader.bits_available()) {
173 uint8_t data_unit_id;
174 RCHECK(reader.ReadBits(8, &data_unit_id));
175
176 uint8_t data_unit_length;
177 RCHECK(reader.ReadBits(8, &data_unit_length));
178
179 if (data_unit_id != EBU_TELETEXT_WITH_SUBTITLING) {
180 RCHECK(reader.SkipBytes(data_unit_length));
181 continue;
182 }
183
184 if (data_unit_length != 44) {
185 // Teletext data unit length is always 44 bytes
186 LOG(ERROR) << "Bad Teletext data length";
187 break;
188 }
189
190 RCHECK(reader.SkipBits(16));
191
192 uint16_t address_bits;
193 RCHECK(reader.ReadBits(16, &address_bits));
194
195 uint8_t magazine = bit(address_bits, 14) + 2 * bit(address_bits, 12) +
196 4 * bit(address_bits, 10);
197
198 if (magazine == 0) {
199 magazine = 8;
200 }
201
202 last_magazine_seen = magazine;
203
204 const uint8_t packet_nr =
205 (bit(address_bits, 8) + 2 * bit(address_bits, 6) +
206 4 * bit(address_bits, 4) + 8 * bit(address_bits, 2) +
207 16 * bit(address_bits, 0));
208 const uint8_t* data_block = reader.current_byte_ptr();
209 RCHECK(reader.SkipBytes(40));
210
211 TextRow row;
212 if (ParseDataBlock(pts, data_block, packet_nr, magazine, row)) {
213 DVLOG(2) << "pts=" << pts << " row=" << row.row_number << " text=\""
214 << row.fragment.body << "\"";
215 rows.emplace_back(std::move(row));
216 }
217 }
218
219 // If magazine_ and page_number_ were never set (no packet 0), use fallback
220 // values Use the last magazine seen and default page 88 (subtitle page
221 // convention)
222 if (magazine_ == 0 && page_number_ == 0 && last_magazine_seen != 0) {
223 DVLOG(1) << "No packet 0 found, using fallback: magazine="
224 << static_cast<int>(last_magazine_seen) << " page=88";
225 magazine_ = last_magazine_seen;
226 page_number_ = 88;
227 }
228
229 const uint16_t index = magazine_ * 100 + page_number_;
230 DVLOG(2) << "Calculating index: magazine_=" << static_cast<int>(magazine_)
231 << " page_number_=" << static_cast<int>(page_number_)
232 << " index=" << index;
233 if (rows.empty()) {
234 SendTextHeartBeat(index, pts);
235 return true;
236 }
237
238 auto page_state_itr = page_state_.find(index);
239 if (page_state_itr == page_state_.end()) {
240 DVLOG(2) << "index=" << index << " create TextBlock pts=" << pts;
241 page_state_.emplace(index, TextBlock{std::move(rows), {}, pts});
242 } else {
243 for (auto& row : rows) {
244 auto& page_state_lines = page_state_itr->second.rows;
245 page_state_lines.emplace_back(std::move(row));
246 }
247 rows.clear();
248 }
249 SendCueStart(index);
250 return true;
251}
252
253bool EsParserTeletext::ParseDataBlock(const int64_t pts,
254 const uint8_t* data_block,
255 const uint8_t packet_nr,
256 const uint8_t magazine,
257 TextRow& row) {
258 DVLOG(2) << "ParseDataBlock: packet_nr=" << static_cast<int>(packet_nr)
259 << " magazine=" << static_cast<int>(magazine);
260 if (packet_nr == 0) {
261 BitReader reader(data_block, 32);
262
263 const uint8_t page_number_units = ReadHamming(reader);
264 const uint8_t page_number_tens = ReadHamming(reader);
265 if (page_number_units == 0xf || page_number_tens == 0xf) {
266 RCHECK(reader.SkipBits(40));
267 return false;
268 }
269 const uint8_t page_number = 10 * page_number_tens + page_number_units;
270 const uint16_t index = magazine * 100 + page_number;
271
272 DVLOG(1) << "Packet 0: Setting magazine_=" << static_cast<int>(magazine)
273 << " page_number_=" << static_cast<int>(page_number)
274 << " index=" << index;
275 last_pts_ = pts; // This should ideally be done for each index.
276 page_number_ = page_number;
277 magazine_ = magazine;
278
279 RCHECK(reader.SkipBits(8));
280 const uint8_t erase_code_s4 = ReadHamming(reader) >> 3;
281 RCHECK(reader.SkipBits(24));
282 if (erase_code_s4 == 1) {
283 SendCueEnd(index, last_pts_);
284 }
285
286 const uint8_t subcode_c11_c14 = ReadHamming(reader);
287 const uint8_t subcode_c11_serial = subcode_c11_c14 & 1;
288 const uint8_t charset_code = subcode_c11_c14 >> 1;
289 DVLOG(2) << "index=" << index << " pts=" << pts << " erase page "
290 << int(erase_code_s4) << " charset=" << int(charset_code)
291 << " serial=" << int(subcode_c11_serial);
292 if (charset_code != charset_code_) {
293 DVLOG(2) << "pts=" << pts << " new charset_code " << int(charset_code);
294 charset_code_ = charset_code;
295 UpdateCharset();
296 }
297 page_state_.emplace(index, TextBlock{{}, {}, last_pts_});
298 DVLOG(2) << "index=" << index
299 << " create TextBlock triggered by packet 0 pts=" << pts;
300 return false;
301 } else if (packet_nr == 26) {
302 DVLOG(3) << "pts=" << pts << " packet26";
303 ParsePacket26(data_block);
304 return false;
305 } else if (packet_nr > 26) {
306 return false;
307 }
308
309 inside_sample_ = true;
310 const uint16_t index = magazine_ * 100 + page_number_;
311 const auto page_state_itr = page_state_.find(index);
312 if (page_state_itr != page_state_.cend()) {
313 if (page_state_itr->second.rows.empty()) {
314 const auto old_pts = page_state_itr->second.pts;
315 if (pts != old_pts) {
316 page_state_itr->second.pts = pts;
317 }
318 }
319 }
320 row = BuildRow(data_block, packet_nr);
321 return true;
322}
323
324void EsParserTeletext::UpdateCharset() {
325 memcpy(current_charset_, TELETEXT_CHARSET_G0_LATIN,
326 sizeof(TELETEXT_CHARSET_G0_LATIN));
327 DVLOG(2) << "update charset: " << int(charset_code_);
328 if (charset_code_ > 7) {
329 return;
330 }
331 const auto teletext_national_subset =
332 static_cast<TELETEXT_NATIONAL_SUBSET>(charset_code_);
333
334 switch (teletext_national_subset) {
335 case TELETEXT_NATIONAL_SUBSET::ENGLISH:
336 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_ENGLISH);
337 break;
338 case TELETEXT_NATIONAL_SUBSET::FRENCH:
339 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_FRENCH);
340 break;
341 case TELETEXT_NATIONAL_SUBSET::SWEDISH_FINNISH_HUNGARIAN:
342 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_SWEDISH_FINNISH_HUNGARIAN);
343 break;
344 case TELETEXT_NATIONAL_SUBSET::CZECH_SLOVAK:
345 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_CZECH_SLOVAK);
346 break;
347 case TELETEXT_NATIONAL_SUBSET::GERMAN:
348 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_GERMAN);
349 break;
350 case TELETEXT_NATIONAL_SUBSET::PORTUGUESE_SPANISH:
351 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_PORTUGUESE_SPANISH);
352 break;
353 case TELETEXT_NATIONAL_SUBSET::ITALIAN:
354 UpdateNationalSubset(TELETEXT_NATIONAL_SUBSET_ITALIAN);
355 break;
356 case TELETEXT_NATIONAL_SUBSET::NONE:
357 default:
358 break;
359 }
360}
361
362// SendCueStart emits a text sample with body and ttx_cue_duration_placeholder
363// since the duration is not yet known. More importantly, the role of the
364// sample is set to kCueStart.
365void EsParserTeletext::SendCueStart(const uint16_t index) {
366 DVLOG(2) << "SendCueStart: index=" << index;
367 auto page_state_itr = page_state_.find(index);
368
369 if (page_state_itr == page_state_.end()) {
370 return;
371 }
372
373 if (page_state_itr->second.rows.empty()) {
374 return;
375 }
376
377 inside_sample_ = true;
378
379 const auto& pending_rows = page_state_itr->second.rows;
380 const auto pts_start = page_state_itr->second.pts;
381 const auto pts_end = pts_start + ttx_cue_duration_placeholder;
382
383 TextSettings text_settings;
384 std::shared_ptr<TextSample> text_sample;
385 std::vector<TextFragment> sub_fragments;
386
387 if (pending_rows.size() == 1) {
388 // This is a single line of formatted text.
389 // Propagate row number/2 and alignment
390 const float line_nr = float(pending_rows[0].row_number) / 2.0;
391 text_settings.line = TextNumber(line_nr, TextUnitType::kLines);
392 text_settings.region = kRegionTeletextPrefix + std::to_string(int(line_nr));
393 text_settings.text_alignment = pending_rows[0].alignment;
394 text_sample = std::make_shared<TextSample>(
395 "", pts_start, pts_end, text_settings, pending_rows[0].fragment,
396 TextSampleRole::kCueStart);
397 DVLOG(3) << "SendCueStart: setting sub_stream_index=" << index;
398 text_sample->set_sub_stream_index(index);
399 DVLOG(2) << "emit 1 row pts=" << pts_start << " end=" << pts_end
400 << " sub_stream_index=" << text_sample->sub_stream_index();
401 emit_sample_cb_(text_sample);
402 page_state_itr->second.rows
403 .clear(); // clear rows, but keep packet26 page_state
404 inside_sample_ = false;
405 return;
406 } else {
407 int32_t latest_row_nr = -1;
408 bool last_double_height = false;
409 bool new_sample = true;
410 for (const auto& row : pending_rows) {
411 int row_nr = row.row_number;
412 bool double_height = row.double_height;
413 int row_step = last_double_height ? 2 : 1;
414 if (latest_row_nr != -1) { // Not the first row
415 if (row_nr != latest_row_nr + row_step) {
416 // Send what has been collected since not adjacent
417 text_sample = std::make_shared<TextSample>(
418 "", pts_start, pts_end, text_settings,
419 TextFragment({}, sub_fragments), TextSampleRole::kCueStart);
420 text_sample->set_sub_stream_index(index);
421 DVLOG(2) << "emit non-adjacent pts=" << pts_start;
422 emit_sample_cb_(text_sample);
423 new_sample = true;
424 } else {
425 // Add a newline and the next row to the current sample
426 sub_fragments.push_back(TextFragment({}, true));
427 sub_fragments.push_back(row.fragment);
428 new_sample = false;
429 }
430 }
431 if (new_sample) {
432 const float line_nr = float(row.row_number) / 2.0;
433 text_settings.line = TextNumber(line_nr, TextUnitType::kLines);
434 text_settings.region =
435 kRegionTeletextPrefix + std::to_string(int(line_nr));
436 text_settings.text_alignment = row.alignment;
437 sub_fragments.clear();
438 sub_fragments.push_back(row.fragment);
439 }
440 last_double_height = double_height;
441 latest_row_nr = row_nr;
442 }
443 }
444
445 text_sample = std::make_shared<TextSample>(
446 "", pts_start, pts_end, text_settings, TextFragment({}, sub_fragments),
447 TextSampleRole::kCueStart);
448
449 text_sample->set_sub_stream_index(index);
450 DVLOG(2) << "emit final cue pts=" << pts_start;
451 emit_sample_cb_(text_sample);
452
453 page_state_itr->second.rows.clear();
454 DVLOG(3) << "clear rows, but keep packet26 page_state index=" << index;
455}
456
457// SendCueEnd emits a text sample with role kCueEnd to signal cue end
458void EsParserTeletext::SendCueEnd(const uint16_t index, const int64_t pts_end) {
459 auto page_state_itr = page_state_.find(index);
460 if (page_state_itr != page_state_.end()) {
461 DVLOG(2) << "index=" << index << " erasing state at pts=" << pts_end;
462 page_state_.erase(index);
463 }
464
465 if (last_pts_ == -1) {
466 last_pts_ = pts_end;
467 return;
468 }
469 if (pts_end == last_end_pts_) {
470 return;
471 }
472
473 DVLOG(2) << "index=" << index << " sending cueEnd pts=" << pts_end;
474 TextSettings text_settings;
475 auto end_sample = std::make_shared<TextSample>(
476 "", pts_end, pts_end, text_settings, TextFragment({}, ""),
477 TextSampleRole::kCueEnd);
478 end_sample->set_sub_stream_index(index);
479 DVLOG(2) << "emit cue end at pts=" << pts_end;
480 emit_sample_cb_(end_sample);
481 last_pts_ = pts_end;
482 last_end_pts_ = pts_end;
483 inside_sample_ = false;
484}
485
486// SendTextHeartBeat emits a text sample with role kTextHeartBeat
487void EsParserTeletext::SendTextHeartBeat(const uint16_t index,
488 const int64_t pts) {
489 if (last_pts_ == -1) {
490 last_pts_ = pts;
491 return;
492 }
493 if (pts == last_pts_) {
494 return;
495 }
496
497 TextSettings text_settings;
498 auto heartbeat_sample = std::make_shared<TextSample>(
499 "", pts, pts, text_settings, TextFragment({}, ""),
500 TextSampleRole::kTextHeartBeat);
501 heartbeat_sample->set_sub_stream_index(index);
502 DVLOG(3) << "emit text heartbeat at pts=" << pts;
503 emit_sample_cb_(heartbeat_sample);
504 last_pts_ = pts;
505}
506
507// BuildRow builds a row with alignment information.
508EsParserTeletext::TextRow EsParserTeletext::BuildRow(const uint8_t* data_block,
509 const uint8_t row) const {
510 std::string next_string;
511 next_string.reserve(kPayloadSize * 2);
512
513 const uint16_t index = magazine_ * 100 + page_number_;
514 const auto page_state_itr = page_state_.find(index);
515
516 const std::unordered_map<uint8_t, std::string>* column_replacement_map =
517 nullptr;
518 if (page_state_itr != page_state_.cend()) {
519 const auto row_itr =
520 page_state_itr->second.packet_26_replacements.find(row);
521 if (row_itr != page_state_itr->second.packet_26_replacements.cend()) {
522 column_replacement_map = &(row_itr->second);
523 }
524 }
525
526 int32_t start_pos = 0;
527 int32_t end_pos = 0;
528 bool double_height = false;
529 TextFragmentStyle text_style = TextFragmentStyle();
530 text_style.color = "white";
531 text_style.backgroundColor = "black";
532 bool non_space_found = false;
533 // A typical 40 character line looks like:
534 // doubleHeight, [color] spaces, Start, Start, text, End, End, spaces
535 for (size_t i = 0; i < kPayloadSize; ++i) {
536 if (column_replacement_map) {
537 const auto column_itr = column_replacement_map->find(i);
538 if (column_itr != column_replacement_map->cend()) {
539 DVLOG(3) << "packet26 replacing col " << int(i) << " with "
540 << column_itr->second;
541 next_string.append(column_itr->second);
542 continue;
543 }
544 }
545
546 char next_char =
547 static_cast<char>(TELETEXT_BITREVERSE_8[data_block[i]] & 0x7f);
548
549 if (next_char < 0x20) {
550 // Here are control characters, which are not printable.
551 // These include colors, double-height, flashing, etc.
552 // We only handle one-foreground color and double-height.
553 switch (next_char) {
554 case 0x0: // Alpha Black (not included in Level 1.5)
555 // color = ColorBlack
556 break;
557 case 0x1:
558 text_style.color = "red";
559 break;
560 case 0x2:
561 text_style.color = "green";
562 break;
563 case 0x3:
564 text_style.color = "yellow";
565 break;
566 case 0x4:
567 text_style.color = "blue";
568 break;
569 case 0x5:
570 text_style.color = "magenta";
571 break;
572 case 0x6:
573 text_style.color = "cyan";
574 break;
575 case 0x7:
576 text_style.color = "white";
577 break;
578 case 0x08: // Flash (not handled)
579 break;
580 case 0x09: // Steady (not handled)
581 break;
582 case 0xa: // End Box
583 end_pos = i - 1;
584 break;
585 case 0xb: // Start Box, typically twice due to double height
586 start_pos = i + 1;
587 continue; // Do not propagate as a space
588 case 0xc: // Normal size
589 break;
590 case 0xd: // Double height, typically always used
591 double_height = true;
592 break;
593 case 0x1c: // Black background (not handled)
594 break;
595 case 0x1d: // Set background color from text color.
596 text_style.backgroundColor = text_style.color;
597 text_style.color = "black"; // Avoid having same as background
598 break;
599 default:
600 // Rest of codes below 0x20 are not part of Level 1.5 or related to
601 // mosaic graphics (non-text)
602 break;
603 }
604 next_char =
605 0x20; // These characters result in a space if between start and end
606 }
607 if (start_pos == 0 ||
608 end_pos != 0) { // Not between start and end or at start
609 continue;
610 }
611 if (!non_space_found) {
612 if (next_char == 0x20) {
613 continue;
614 }
615 non_space_found = true;
616 }
617 const std::string replacement(current_charset_[next_char - 0x20]);
618 next_string.append(replacement);
619 }
620 if (end_pos == 0) {
621 end_pos = kPayloadSize - 1;
622 }
623
624 // Using start_pos and end_pos we approximated alignment of text
625 // depending on the number of spaces to the left and right of the text.
626 auto left_right_diff = start_pos - (kPayloadSize - 1 - end_pos);
627 TextAlignment alignment;
628 if (left_right_diff > 4) {
629 alignment = TextAlignment::kRight;
630 } else if (left_right_diff < -4) {
631 alignment = TextAlignment::kLeft;
632 } else {
633 alignment = TextAlignment::kCenter;
634 }
635 const auto text_row = TextRow(
636 {alignment, row, double_height, {TextFragment(text_style, next_string)}});
637
638 return text_row;
639}
640
641void EsParserTeletext::ParsePacket26(const uint8_t* data_block) {
642 const uint16_t index = magazine_ * 100 + page_number_;
643 auto page_state_itr = page_state_.find(index);
644 if (page_state_itr == page_state_.end()) {
645 DVLOG(2) << "index=" << index
646 << " create TextBlock triggered by packet 26 pts=" << last_pts_;
647 page_state_.emplace(index, TextBlock{{}, {}, last_pts_});
648 }
649 auto& replacement_map = page_state_[index].packet_26_replacements;
650
651 uint8_t row = 0;
652
653 std::vector<uint32_t> x26_triplets;
654 x26_triplets.reserve(kNumTriplets);
655 for (uint8_t i = 1; i < kPayloadSize; i += 3) {
656 const uint32_t bytes = (TELETEXT_BITREVERSE_8[data_block[i + 2]] << 16) |
657 (TELETEXT_BITREVERSE_8[data_block[i + 1]] << 8) |
658 TELETEXT_BITREVERSE_8[data_block[i]];
659 uint32_t triplet;
660 if (Hamming_24_18(bytes, triplet)) {
661 x26_triplets.emplace_back(triplet);
662 }
663 }
664
665 for (const auto triplet : x26_triplets) {
666 const uint8_t mode = (triplet & 0x7c0) >> 6;
667 const uint8_t address = triplet & 0x3f;
668 const uint8_t row_address_group = (address >= 0x28) && (address <= 0x3f);
669
670 if ((mode == 0x4) && (row_address_group == 0x1)) {
671 row = address - 0x28;
672 if (row == 0x0) {
673 row = 0x18;
674 }
675 }
676
677 if (mode >= 0x11 && mode <= 0x1f && row_address_group == 0x1) {
678 break;
679 }
680
681 const uint8_t data = (triplet & 0x3f800) >> 11;
682
683 if (mode == 0x0f && row_address_group == 0x0 && data > 0x1f) {
684 SetPacket26ReplacementString(replacement_map, row, address,
685 reinterpret_cast<const char*>(
686 TELETEXT_CHARSET_G2_LATIN[data - 0x20]));
687 }
688
689 if (mode == 0x10 && row_address_group == 0x0 && data == 0x40) {
690 SetPacket26ReplacementString(replacement_map, row, address, "@");
691 }
692
693 if (mode < 0x11 || mode > 0x1f || row_address_group != 0x0) {
694 continue;
695 }
696
697 if (data >= 0x41 && data <= 0x5a) {
698 SetPacket26ReplacementString(
699 replacement_map, row, address,
700 reinterpret_cast<const char*>(
701 TELETEXT_G2_LATIN_ACCENTS[mode - 0x11][data - 0x41]));
702
703 } else if (data >= 0x61 && data <= 0x7a) {
704 SetPacket26ReplacementString(
705 replacement_map, row, address,
706 reinterpret_cast<const char*>(
707 TELETEXT_G2_LATIN_ACCENTS[mode - 0x11][data - 0x47]));
708
709 } else if ((data & 0x7f) >= 0x20) {
710 SetPacket26ReplacementString(
711 replacement_map, row, address,
712 reinterpret_cast<const char*>(
713 TELETEXT_CHARSET_G0_LATIN[(data & 0x7f) - 0x20]));
714 }
715 }
716}
717
718void EsParserTeletext::UpdateNationalSubset(
719 const uint8_t national_subset[13][3]) {
720 for (size_t i = 0; i < 13; ++i) {
721 const size_t position = TELETEXT_NATIONAL_CHAR_INDEX_G0[i];
722 memcpy(current_charset_[position], national_subset[i], 3);
723 }
724}
725
726void EsParserTeletext::SetPacket26ReplacementString(
727 RowColReplacementMap& replacement_map,
728 const uint8_t row,
729 const uint8_t column,
730 std::string&& replacement_string) {
731 auto replacement_map_itr = replacement_map.find(row);
732 if (replacement_map_itr == replacement_map.cend()) {
733 replacement_map.emplace(row, std::unordered_map<uint8_t, std::string>{});
734 }
735 auto& column_map = replacement_map[row];
736 column_map.emplace(column, std::move(replacement_string));
737}
738
739} // namespace mp2t
740} // namespace media
741} // namespace shaka
All the methods that are virtual are virtual for mocking.