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