From 1e13c556bacc84c2785c70db0a624d8449140273 Mon Sep 17 00:00:00 2001 From: fey Date: Thu, 3 Sep 2026 21:30:59 +0200 Subject: [PATCH] Added basic ISDB-T SDT parsing. --- .../Dvb/Descriptors/0x48_ServiceDescriptor.cs | 2 +- skyscraper8/Dvb/Psi/Pid0x11Decoder.cs | 8 +- skyscraper8/Dvb/Psi/SdtParser.cs | 22 +---- .../EventHandlers/IIsdbSdtEventHandler.cs | 10 ++ skyscraper8/Isdb/Matenro.cs | 63 +++++++++--- skyscraper8/Isdb/Parsers/IsdbSdtParser.cs | 96 +++++++++++++++++++ skyscraper8/Isdb/Tables/IsdbSdtService.cs | 33 +++++++ .../Skyscraper/Scraper/Storage/DataStorage.cs | 2 + .../Storage/Filesystem/FilesystemStorage.cs | 8 ++ .../InMemory/InMemoryScraperStorage.cs | 14 +++ 10 files changed, 217 insertions(+), 41 deletions(-) create mode 100644 skyscraper8/Isdb/EventHandlers/IIsdbSdtEventHandler.cs create mode 100644 skyscraper8/Isdb/Parsers/IsdbSdtParser.cs create mode 100644 skyscraper8/Isdb/Tables/IsdbSdtService.cs diff --git a/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs b/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs index 7ca962b..0a76617 100644 --- a/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs +++ b/skyscraper8/Dvb/Descriptors/0x48_ServiceDescriptor.cs @@ -12,7 +12,7 @@ using skyscraper5.Skyscraper.Text; namespace skyscraper5.Dvb.Descriptors { [SkyscraperPlugin] - [TsDescriptor(0x48,"SDT","Astra SGT")] + [TsDescriptor(0x48,"SDT","Astra SGT", "ISDB SDT")] [BannedTable("TSDT","PMT")] public class ServiceDescriptor : TsDescriptor { diff --git a/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs b/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs index 1dedd24..728c2c5 100644 --- a/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs +++ b/skyscraper8/Dvb/Psi/Pid0x11Decoder.cs @@ -9,7 +9,7 @@ using skyscraper8.Skyscraper; namespace skyscraper5.Dvb.Psi { - class Pid0x11Decoder : IPsiProcessor, IMultistandardTable + class Pid0x11Decoder : IPsiProcessor { public Pid0x11Decoder(IBatEventHandler batEventHandler, ISdtEventHandler sdtEventHandler) @@ -38,11 +38,5 @@ namespace skyscraper5.Dvb.Psi return; } } - - public void SetStandard(STD_TYPE standard) - { - SdtParser.SetStandard(standard); - BatParser.SetStandard(standard); - } } } diff --git a/skyscraper8/Dvb/Psi/SdtParser.cs b/skyscraper8/Dvb/Psi/SdtParser.cs index d499c59..2f07da1 100644 --- a/skyscraper8/Dvb/Psi/SdtParser.cs +++ b/skyscraper8/Dvb/Psi/SdtParser.cs @@ -15,7 +15,7 @@ using skyscraper8.Skyscraper; namespace skyscraper5.Dvb.Psi { - class SdtParser : IPsiProcessor, IMultistandardTable + class SdtParser : IPsiProcessor { public ISdtEventHandler EventHandler { get; } @@ -83,7 +83,7 @@ namespace skyscraper5.Dvb.Psi SdtService child = new SdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); byte[] descriptorsData = ms.ReadBytes(descriptorsLoopLength); - IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, GetTableType()); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, "SDT"); foreach (TsDescriptor dvbDescriptor in unpackDescriptors) { string name = dvbDescriptor.GetType().Name; @@ -214,23 +214,5 @@ namespace skyscraper5.Dvb.Psi throw new NotImplementedException(name); } } - - private STD_TYPE standard; - public void SetStandard(STD_TYPE standard) - { - this.standard = standard; - } - - private string GetTableType() - { - if (IMultistandardTable.IsIsdb(standard)) - { - return "ISDB SDT"; - } - else - { - return "SDT"; - } - } } } diff --git a/skyscraper8/Isdb/EventHandlers/IIsdbSdtEventHandler.cs b/skyscraper8/Isdb/EventHandlers/IIsdbSdtEventHandler.cs new file mode 100644 index 0000000..4e4b823 --- /dev/null +++ b/skyscraper8/Isdb/EventHandlers/IIsdbSdtEventHandler.cs @@ -0,0 +1,10 @@ +using skyscraper8.Isdb.Tables; + +namespace skyscraper8.Isdb.EventHandlers; + +public interface IIsdbSdtEventHandler +{ + void SetNetworkId(ushort originalNetworkId); + void SetTransportStreamId(ushort transportStreamId); + void OnSdtService(ushort transportStreamId, ushort originalNetworkId, IsdbSdtService child); +} \ No newline at end of file diff --git a/skyscraper8/Isdb/Matenro.cs b/skyscraper8/Isdb/Matenro.cs index f4859dd..abbca17 100644 --- a/skyscraper8/Isdb/Matenro.cs +++ b/skyscraper8/Isdb/Matenro.cs @@ -1,3 +1,4 @@ +using System.Reflection.Metadata; using log4net; using log4net.Repository.Hierarchy; using skyscraper5.Dvb.Descriptors; @@ -18,7 +19,7 @@ using skyscraper8.Skyscraper.Scraper.Storage; namespace skyscraper8.Isdb; -public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISdtEventHandler, IEitEventHandler, IRstEventHandler, ITdtEventHandler, ITotEventHandler, IIsdbPmtEventHandler, IBitEventHandler, IIsdbSubtitleHandler, IAutodetectionEventHandler +public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, IEitEventHandler, IRstEventHandler, ITdtEventHandler, ITotEventHandler, IIsdbPmtEventHandler, IBitEventHandler, IIsdbSubtitleHandler, IAutodetectionEventHandler, IIsdbSdtEventHandler { public TsContext DvbContext { get; } public DataStorage DataStorage { get; } @@ -42,10 +43,6 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd LogEvent = logEvent; logger = LogManager.GetLogger(this.GetType().Name); - - - Pid0x11Decoder pid0X11Decoder = new Pid0x11Decoder(this, this); - pid0X11Decoder.SetStandard(STD_TYPE.STD_ISDBT); EitParser eitParser = new EitParser(this); eitParser.SetStandard(STD_TYPE.STD_ISDBT); @@ -62,7 +59,7 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd dvbContext.RegisterPacketProcessor(0x0000, new PsiDecoder(0x0000, new PatParser(this))); dvbContext.RegisterPacketProcessor(0x0001, new PsiDecoder(0x0001, new CatParser(this))); - dvbContext.RegisterPacketProcessor(0x0011, new PsiDecoder(0x0011, pid0X11Decoder)); + dvbContext.RegisterPacketProcessor(0x0011, new PsiDecoder(0x0011, new IsdbSdtParser(this))); dvbContext.RegisterPacketProcessor(0x0012, new PsiDecoder(0x0012, eitParser)); dvbContext.RegisterPacketProcessor(0x0013, new PsiDecoder(0x0013, rstParser)); dvbContext.RegisterPacketProcessor(0x0014, new PsiDecoder(0x0014, tdtParser)); @@ -109,15 +106,36 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd pmtPidsAssociated[pmtPid] = true; } } - - public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, SdtService sdtService) - { - throw new NotImplementedException(); - } - + + private ushort? competingNetworkId; public void SetNetworkId(ushort networkId) { - this._networkId = networkId; + if (_networkId.HasValue) + { + if (_networkId.Value == networkId) + { + //Die neue Network ID ist die selbe, wie die, die wir kennen, also alles ok. + return; + } + else + { + //Die neue Network ID konkurriert! + if (competingNetworkId.HasValue) + { + return; + } + else + { + logger.WarnFormat("Multiple network IDs are announced for this stream: {0} <-> {1}", networkId, _networkId.Value); + competingNetworkId = networkId; + } + } + } + else + { + //Network ID noch nicht bekannt, kann also gefahrlos gesetzt werden. + this._networkId = networkId; + } } public void SetTransportStreamId(ushort transportStreamId) @@ -137,6 +155,25 @@ public class Matenro : IPatEventHandler, ICatEventHandler, IBatEventHandler, ISd throw new NotImplementedException(); } + private List _knownSdts; + public void OnSdtService(ushort transportStreamId, ushort originalNetworkId, IsdbSdtService child) + { + long key = originalNetworkId; + key <<= 16; + key += transportStreamId; + key <<= 16; + key += child.ServiceId; + if (_knownSdts == null) + _knownSdts = new List(); + if (!_knownSdts.Contains(key)) + { + logger.InfoFormat("SDT Service {0},{1}: {2}", originalNetworkId, transportStreamId, child.TryGetName()); + DataStorage.UpsertIsdbSdt(transportStreamId, originalNetworkId, child); + _knownSdts.Add(key); + } + } + + public void OnEitEvent(EitEvent eitEvent) { throw new NotImplementedException(); diff --git a/skyscraper8/Isdb/Parsers/IsdbSdtParser.cs b/skyscraper8/Isdb/Parsers/IsdbSdtParser.cs new file mode 100644 index 0000000..730d333 --- /dev/null +++ b/skyscraper8/Isdb/Parsers/IsdbSdtParser.cs @@ -0,0 +1,96 @@ +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; +using skyscraper5.Mpeg2; +using skyscraper5.Skyscraper.IO; +using skyscraper8.Isdb.EventHandlers; +using skyscraper8.Isdb.Tables; + +namespace skyscraper8.Isdb.Psi; + +public class IsdbSdtParser : IPsiProcessor +{ + public IsdbSdtParser(IIsdbSdtEventHandler handler) + { + this.EventHandler = handler; + } + + public IIsdbSdtEventHandler EventHandler { get; set; } + + public void GatherPsi(PsiSection section, int sourcePid) + { + MemoryStream ms = new MemoryStream(section.GetDataCopy(),false); + + byte tableId = ms.ReadUInt8(); + if (tableId != 0x42 && tableId != 0x46) + return; + + ushort readUInt16Be = ms.ReadUInt16BE(); + bool sectionSyntaxIndicator = (readUInt16Be & 0x8000) != 0; + if (!sectionSyntaxIndicator) + return; + int sectionLength = (readUInt16Be & 0x0fff); + + ushort transportStreamId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + int versionNumber = (readUInt8 & 0x3e); + bool currentNextIndicator = (readUInt8 & 0x01) != 0; + + byte sectionNumber = ms.ReadUInt8(); + + byte lastSectionNumber = ms.ReadUInt8(); + + ushort originalNetworkId = ms.ReadUInt16BE(); + ms.ReadUInt8(); //reserved future use + + if (tableId == 0x42) + { + //0x42 meaning current mux + EventHandler.SetNetworkId(originalNetworkId); + EventHandler.SetTransportStreamId(transportStreamId); + } + + while (ms.GetAvailableBytes() > 4) + { + ReadIsdbService(ms, transportStreamId, originalNetworkId); + } + + uint crc32 = ms.ReadUInt32BE(); + } + + private void ReadIsdbService(MemoryStream ms, ushort transportStreamId, ushort originalNetworkId) + { + ushort serviceId = ms.ReadUInt16BE(); + + byte readUInt8 = ms.ReadUInt8(); + bool eitScheduleFlag = (readUInt8 & 0x02) != 0; + bool eitPresentFollowingFlag = (readUInt8 & 0x01) != 0; + + ushort readUInt16Be = ms.ReadUInt16BE(); + int rs = (readUInt16Be & 0xe000) >> 13; + RunningStatus runningStatus = (RunningStatus)rs; + bool freeCaMode = (readUInt16Be & 0x1000) != 0; + int descriptorsLoopLength = readUInt16Be & 0xfff; + + IsdbSdtService child = new IsdbSdtService(serviceId, eitScheduleFlag, eitPresentFollowingFlag, runningStatus, freeCaMode); + + byte[] descriptorsData = ms.ReadBytes(descriptorsLoopLength); + IEnumerable unpackDescriptors = TsDescriptorUnpacker.GetInstance().UnpackDescriptors(descriptorsData, "ISDB SDT"); + foreach (TsDescriptor dvbDescriptor in unpackDescriptors) + { + switch (dvbDescriptor) + { + case ServiceDescriptor sd: + if (child.ServiceDescriptor == null) + child.ServiceDescriptor = sd; + else + throw new NotImplementedException("Multiple service descriptors are not yet implemented."); + break; + default: + throw new NotImplementedException("Unknown ISDB SDTdescriptor type: " + dvbDescriptor.GetType().Name); + } + } + EventHandler.OnSdtService(transportStreamId, originalNetworkId, child); + + } +} \ No newline at end of file diff --git a/skyscraper8/Isdb/Tables/IsdbSdtService.cs b/skyscraper8/Isdb/Tables/IsdbSdtService.cs new file mode 100644 index 0000000..3369709 --- /dev/null +++ b/skyscraper8/Isdb/Tables/IsdbSdtService.cs @@ -0,0 +1,33 @@ +using skyscraper5.Dvb.Descriptors; +using skyscraper5.Dvb.Psi.Model; + +namespace skyscraper8.Isdb.Tables; + +public class IsdbSdtService +{ + public ushort ServiceId { get; } + public bool EitScheduleFlag { get; } + public bool EitPresentFollowingFlag { get; } + public RunningStatus RunningStatus { get; } + public bool FreeCaMode { get; } + public ServiceDescriptor ServiceDescriptor { get; set; } + + public IsdbSdtService(ushort serviceId, bool eitScheduleFlag, bool eitPresentFollowingFlag, RunningStatus runningStatus, bool freeCaMode) + { + ServiceId = serviceId; + EitScheduleFlag = eitScheduleFlag; + EitPresentFollowingFlag = eitPresentFollowingFlag; + RunningStatus = runningStatus; + FreeCaMode = freeCaMode; + } + + public string TryGetName() + { + if (ServiceDescriptor != null) + { + return ServiceDescriptor.ServiceName; + } + + return "???"; + } +} \ No newline at end of file diff --git a/skyscraper8/Skyscraper/Scraper/Storage/DataStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/DataStorage.cs index 345b521..dc59cc5 100644 --- a/skyscraper8/Skyscraper/Scraper/Storage/DataStorage.cs +++ b/skyscraper8/Skyscraper/Scraper/Storage/DataStorage.cs @@ -221,5 +221,7 @@ namespace skyscraper8.Skyscraper.Scraper.Storage /// /// void UpsertBit(Bit bit); + + void UpsertIsdbSdt(ushort transportStreamId, ushort originalNetworkId, IsdbSdtService child); } } diff --git a/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemStorage.cs index bf11fec..fe781b2 100644 --- a/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemStorage.cs +++ b/skyscraper8/Skyscraper/Scraper/Storage/Filesystem/FilesystemStorage.cs @@ -1828,6 +1828,14 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.Filesystem File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(bit, jsonSerializerSettings)); } + public void UpsertIsdbSdt(ushort transportStreamId, ushort originalNetworkId, IsdbSdtService child) + { + string combine = Path.Combine(rootDirectory.FullName, "SDT", "ISDB", originalNetworkId.ToString(), transportStreamId.ToString() , child.ServiceId + ".json"); + FileInfo fi = new FileInfo(combine); + fi.Directory.EnsureExists(); + File.WriteAllText(fi.FullName, JsonConvert.SerializeObject(child, jsonSerializerSettings)); + } + public RfSpectrumData GetRfSpectrum(Guid jobGuid) { string filename = Path.Combine(rootDirectory.FullName, "rf", jobGuid.ToString() + ".rf"); diff --git a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs index ff50ebf..7565b27 100644 --- a/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs +++ b/skyscraper8/Skyscraper/Scraper/Storage/InMemory/InMemoryScraperStorage.cs @@ -1959,5 +1959,19 @@ namespace skyscraper5.Skyscraper.Scraper.Storage.InMemory _bits = new Bit[ushort.MaxValue]; _bits[bit.OriginalNetworkId] = bit; } + + private Dictionary _isdbSdtServices; + public void UpsertIsdbSdt(ushort transportStreamId, ushort originalNetworkId, IsdbSdtService child) + { + long key = originalNetworkId; + key <<= 16; + key += transportStreamId; + key <<= 16; + key += child.ServiceId; + + if (_isdbSdtServices == null) + _isdbSdtServices = new Dictionary(); + _isdbSdtServices[key] = child; + } } }