public static int GetPageNumber(TeletextPacketPayload packet) { var address = (TeletextHamming.UnHamming84(packet.Address[1]) << 4) | TeletextHamming.UnHamming84(packet.Address[0]); var m = address & 0x7; if (m == 0) { m = 8; } var y = (address >> 3) & 0x1f; if (y == 0) { var i = (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]); var flagSubtitle = (TeletextHamming.UnHamming84(packet.Data[5]) & 0x08) >> 3; if (flagSubtitle == 1 && i < 0xff) { var bcdPage = (m << 8) | (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]); return(BcdToDec((ulong)bcdPage)); } } return(-1); }
public static void ProcessTelxPacket(DataUnitT dataUnitId, TeletextPacketPayload packet, ulong timestamp, TeletextRunSettings teletextRunSettings, int targetPageNumberBcd, int targetPageNumberDec) { // variable names conform to ETS 300 706, chapter 7.1.2 var address = (TeletextHamming.UnHamming84(packet.Address[1]) << 4) | TeletextHamming.UnHamming84(packet.Address[0]); var m = address & 0x7; if (m == 0) { m = 8; } var y = (address >> 3) & 0x1f; var designationCode = y > 25 ? TeletextHamming.UnHamming84(packet.Data[0]) : 0x00; if (y == 0) // PAGE HEADER { // CC map var i = (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]); var flagSubtitle = (TeletextHamming.UnHamming84(packet.Data[5]) & 0x08) >> 3; CcMap[i] |= (byte)(flagSubtitle << (m - 1)); // Page number and control bits var pageNumber = (m << 8) | (TeletextHamming.UnHamming84(packet.Data[1]) << 4) | TeletextHamming.UnHamming84(packet.Data[0]); var charset = ((TeletextHamming.UnHamming84(packet.Data[7]) & 0x08) | (TeletextHamming.UnHamming84(packet.Data[7]) & 0x04) | (TeletextHamming.UnHamming84(packet.Data[7]) & 0x02)) >> 1; //uint8_t flag_suppress_header = unham_8_4(packet.data[6]) & 0x01; //uint8_t flag_inhibit_display = (unham_8_4(packet.data[6]) & 0x08) >> 3; // ETS 300 706, chapter 9.3.1.3: // When set to '1' the service is designated to be in Serial mode and the transmission of a page is terminated // by the next page header with a different page number. // When set to '0' the service is designated to be in Parallel mode and the transmission of a page is terminated // by the next page header with a different page number but the same magazine number. // The same setting shall be used for all page headers in the service. // ETS 300 706, chapter 7.2.1: Page is terminated by and excludes the next page header packet // having the same magazine address in parallel transmission mode, or any magazine address in serial transmission mode. _transmissionMode = (TransmissionMode)(TeletextHamming.UnHamming84(packet.Data[7]) & 0x01); // FIXME: Well, this is not ETS 300 706 kosher, however we are interested in DATA_UNIT_EBU_TELETEXT_SUBTITLE only if (_transmissionMode == TransmissionMode.TransmissionModeParallel && dataUnitId != DataUnitT.DataUnitEbuTeletextSubtitle) { return; } if (_receivingData && (_transmissionMode == TransmissionMode.TransmissionModeSerial && Page(pageNumber) != Page(targetPageNumberBcd) || _transmissionMode == TransmissionMode.TransmissionModeParallel && Page(pageNumber) != Page(targetPageNumberBcd) && m == Magazine(targetPageNumberBcd)) ) { _receivingData = false; return; } // Page transmission is terminated, however now we are waiting for our new page if (targetPageNumberBcd != pageNumber) { return; } // Now we have the beginning of page transmission; if there is page_buffer pending, process it if (_pageBuffer.Tainted) { // it would be nice, if subtitle hides on previous video frame, so we contract 40 ms (1 frame @25 fps) _pageBuffer.HideTimestamp = timestamp - 40; ProcessPage(_pageBuffer, teletextRunSettings, targetPageNumberDec); } _pageBuffer.ShowTimestamp = timestamp; _pageBuffer.HideTimestamp = 0; _pageBuffer.Text = new int[25, 40]; _pageBuffer.Tainted = false; _receivingData = true; _primaryCharset.G0X28 = (int)BoolT.Undef; var c = _primaryCharset.G0M29 != (int)BoolT.Undef ? _primaryCharset.G0M29 : charset; RemapG0Charset(c); /* * // I know -- not needed; in subtitles we will never need disturbing teletext page status bar * // displaying tv station name, current time etc. * if (flag_suppress_header == NO) { * for (uint8_t i = 14; i < 40; i++) page_buffer.text[y,i] = telx_to_ucs2(packet.data[i]); * //page_buffer.tainted = YES; * } */ } else if (m == Magazine(targetPageNumberBcd) && y >= 1 && y <= 23 && _receivingData) { // ETS 300 706, chapter 9.4.1: Packets X/26 at presentation Levels 1.5, 2.5, 3.5 are used for addressing // a character location and overwriting the existing character defined on the Level 1 page // ETS 300 706, annex B.2.2: Packets with Y = 26 shall be transmitted before any packets with Y = 1 to Y = 25; // so page_buffer.text[y,i] may already contain any character received // in frame number 26, skip original G0 character for (var i = 0; i < 40; i++) { if (_pageBuffer.Text[y, i] == 0x00) { _pageBuffer.Text[y, i] = TelxToUcs2(packet.Data[i]); } } _pageBuffer.Tainted = true; } else if (m == Magazine(targetPageNumberBcd) && y == 26 && _receivingData) { // ETS 300 706, chapter 12.3.2: X/26 definition var x26Row = 0; var triplets = new uint[13]; var j = 0; for (var i = 1; i < 40; i += 3, j++) { triplets[j] = TeletextHamming.UnHamming2418((packet.Data[i + 2] << 16) | (packet.Data[i + 1] << 8) | packet.Data[i]); } for (var j2 = 0; j2 < 13; j2++) { if (triplets[j2] == 0xffffffff) { // invalid data (HAM24/18 uncorrectable error detected), skip group _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplets[j2]}"); continue; } var data = (triplets[j2] & 0x3f800) >> 11; var mode = (triplets[j2] & 0x7c0) >> 6; var address2 = triplets[j2] & 0x3f; var rowAddressGroup = address2 >= 40 && address2 <= 63; // ETS 300 706, chapter 12.3.1, table 27: set active position if (mode == 0x04 && rowAddressGroup) { x26Row = (int)(address2 - 40); if (x26Row == 0) { x26Row = 24; } } // ETS 300 706, chapter 12.3.1, table 27: termination marker if (mode >= 0x11 && mode <= 0x1f && rowAddressGroup) { break; } // ETS 300 706, chapter 12.3.1, table 27: character from G2 set int x26Col; if (mode == 0x0f && !rowAddressGroup) { x26Col = (int)address2; if (data > 31) { _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2[0, data - 0x20]; } } // ETS 300 706, chapter 12.3.1, table 27: G0 character with diacritical mark if (mode >= 0x11 && mode <= 0x1f && !rowAddressGroup) { x26Col = (int)address2; if (data >= 65 && data <= 90) // A-Z { _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2Accents[mode - 0x11, data - 65]; } else if (data >= 97 && data <= 122) // a-z { _pageBuffer.Text[x26Row, x26Col] = TeletextTables.G2Accents[mode - 0x11, data - 71]; } else // other { _pageBuffer.Text[x26Row, x26Col] = TelxToUcs2((byte)data); } } } } else if (m == Magazine(targetPageNumberBcd) && y == 28 && _receivingData) { // TODO: // ETS 300 706, chapter 9.4.7: Packet X/28/4 // Where packets 28/0 and 28/4 are both transmitted as part of a page, packet 28/0 takes precedence over 28/4 for all but the color map entry coding. if (designationCode == 0 || designationCode == 4) { // ETS 300 706, chapter 9.4.2: Packet X/28/0 Format 1 // ETS 300 706, chapter 9.4.7: Packet X/28/4 uint triplet0 = TeletextHamming.UnHamming2418((packet.Data[3] << 16) | (packet.Data[2] << 8) | packet.Data[1]); if (triplet0 == 0xffffffff) { // invalid data (HAM24/18 uncorrectable error detected), skip group _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplet0}"); } else { // ETS 300 706, chapter 9.4.2: Packet X/28/0 Format 1 only if ((triplet0 & 0x0f) == 0x00) { _primaryCharset.G0X28 = (int)((triplet0 & 0x3f80) >> 7); RemapG0Charset(_primaryCharset.G0X28); } } } } else if (m == Magazine(targetPageNumberBcd) && y == 29) { // TODO: // ETS 300 706, chapter 9.5.1 Packet M/29/0 // Where M/29/0 and M/29/4 are transmitted for the same magazine, M/29/0 takes precedence over M/29/4. if (designationCode == 0 || designationCode == 4) { // ETS 300 706, chapter 9.5.1: Packet M/29/0 // ETS 300 706, chapter 9.5.3: Packet M/29/4 uint triplet0 = TeletextHamming.UnHamming2418((packet.Data[3] << 16) | (packet.Data[2] << 8) | packet.Data[1]); if (triplet0 == 0xffffffff) { // invalid data (HAM24/18 uncorrectable error detected), skip group _config.LogError($"! Unrecoverable data error; UNHAM24/18()={triplet0}"); } else { // ETS 300 706, table 11: Coding of Packet M/29/0 // ETS 300 706, table 13: Coding of Packet M/29/4 if ((triplet0 & 0xff) == 0x00) { _primaryCharset.G0M29 = (int)((triplet0 & 0x3f80) >> 7); // X/28 takes precedence over M/29 if (_primaryCharset.G0X28 == (int)BoolT.Undef) { RemapG0Charset(_primaryCharset.G0M29); } } } } } else if (m == 8 && y == 30) { // ETS 300 706, chapter 9.8: Broadcast Service Data Packets if (!_states.ProgrammeInfoProcessed) { // ETS 300 706, chapter 9.8.1: Packet 8/30 Format 1 if (TeletextHamming.UnHamming84(packet.Data[0]) < 2) { _config.LogInfoNoNewLine("- Programme Identification Data = "); for (var i = 20; i < 40; i++) { var c = TelxToUcs2(packet.Data[i]); // strip any control codes from PID, eg. TVP station if (c < 0x20) { continue; } _config.LogInfoNoNewLine(Ucs2ToUtf8(c)); } _config.LogInfo(string.Empty); // OMG! ETS 300 706 stores timestamp in 7 bytes in Modified Julian Day in BCD format + HH:MM:SS in BCD format // + timezone as 5-bit count of half-hours from GMT with 1-bit sign // In addition all decimals are incremented by 1 before transmission. long t = 0; // 1st step: BCD to Modified Julian Day t += (packet.Data[10] & 0x0f) * 10000; t += ((packet.Data[11] & 0xf0) >> 4) * 1000; t += (packet.Data[11] & 0x0f) * 100; t += ((packet.Data[12] & 0xf0) >> 4) * 10; t += packet.Data[12] & 0x0f; t -= 11111; // 2nd step: conversion Modified Julian Day to unix timestamp t = (t - 40587) * 86400; // 3rd step: add time t += 3600 * (((packet.Data[13] & 0xf0) >> 4) * 10 + (packet.Data[13] & 0x0f)); t += 60 * (((packet.Data[14] & 0xf0) >> 4) * 10 + (packet.Data[14] & 0x0f)); t += ((packet.Data[15] & 0xf0) >> 4) * 10 + (packet.Data[15] & 0x0f); t -= 40271; // 4th step: conversion to time_t var span = TimeSpan.FromTicks(t * TimeSpan.TicksPerSecond); var t2 = new DateTime(1970, 1, 1).Add(span); var localTime = TimeZoneInfo.ConvertTimeFromUtc(t2, TimeZoneInfo.Local); _config.LogInfo($"- Programme Timestamp (UTC) = {localTime.ToLongDateString()} {localTime.ToLongTimeString()}"); _config.LogInfo($"- Transmission mode = {(_transmissionMode == TransmissionMode.TransmissionModeSerial ? "serial" : "parallel")}"); _states.ProgrammeInfoProcessed = true; } } } }