skyscraper8/skyscraper8/Atsc/A331/Atsc3Receiver.cs
feyris-tan 4fe40e082a
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 1m25s
The Atsc3Receiver.cs now understands ATSC3 FDTs.
2026-06-08 20:52:21 +02:00

200 lines
5.5 KiB
C#

using log4net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Ietf.Rfc768;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper;
using skyscraper8.Atsc.A331.Schema;
using skyscraper8.Ietf.FLUTE;
namespace skyscraper8.Atsc.A331
{
[SkyscraperPlugin]
[PluginPriority(3)]
internal class Atsc3Receiver : ISkyscraperMpePlugin
{
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
private static readonly IPAddress LLS_IP = IPAddress.Parse("224.0.23.60");
private static readonly ushort LLS_PORT = 4937;
private IAtsc3EventHandler eventHandler;
private bool systimeDetected;
public void ConnectToStorage(object[] connector)
{
throw new NotImplementedException();
}
public void SetContext(DateTime? currentTime, object skyscraperContext)
{
eventHandler = (IAtsc3EventHandler)skyscraperContext;
}
private bool atsc3detected;
public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
if (internetHeader.Protocol != 0x11)
return false;
if (atsc3detected && internetHeader.IsDestinationMulticast)
return true;
ushort destPort = ipv4Packet.GetUInt16BE(2);
if (nonAtsc3 != null)
{
IPEndPoint possibleEvildoer = new IPEndPoint(internetHeader.DestinationAddress, destPort);
if (nonAtsc3.Contains(possibleEvildoer))
return false;
}
if (internetHeader.DestinationAddress.Equals(LLS_IP))
{
if (destPort == LLS_PORT)
{
return true;
}
}
return false;
}
private HashSet<IPEndPoint> nonAtsc3;
private Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener> flutes;
private Atsc3FilenameTable filenames;
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
_stopProcessIndicator = false;
UserDatagram udpPacket = new UserDatagram(ipv4Packet);
IPEndPoint destination = new IPEndPoint(internetHeader.DestinationAddress, udpPacket.DestinationPort);
if (nonAtsc3 != null)
{
if (nonAtsc3.Contains(destination))
{
return;
}
}
if (internetHeader.DestinationAddress.Equals(LLS_IP) && udpPacket.DestinationPort == LLS_PORT)
{
//Handle LLS
LlsTable lls = new LlsTable(udpPacket.Payload);
HandleLls(lls);
return;
}
if (!atsc3detected)
{
return;
}
LctFrame lctFrame = new LctFrame(udpPacket.Payload, true);
if (lctFrame.Version != 1)
{
//Doesn't look like FLUTE, go away.
if (nonAtsc3 == null)
nonAtsc3 = new HashSet<IPEndPoint>();
nonAtsc3.Add(destination);
return;
}
ulong tsi = lctFrame.LctHeader.TransportSessionIdentifier;
ulong toi = lctFrame.LctHeader.TransportObjectIdentifier;
Tuple<IPAddress, ushort, ulong, ulong> fluteCoordinate = new Tuple<IPAddress, ushort, ulong, ulong>(internetHeader.DestinationAddress, udpPacket.DestinationPort, tsi, toi);
FluteListener targetListener;
if (flutes == null)
{
flutes = new Dictionary<Tuple<IPAddress, ushort, ulong, ulong>, FluteListener>();
filenames = new Atsc3FilenameTable();
}
if (flutes.ContainsKey(fluteCoordinate))
{
targetListener = flutes[fluteCoordinate];
}
else
{
FluteListener newListener = new FluteListener(internetHeader.DestinationAddress, udpPacket.DestinationPort, tsi, toi);
flutes.Add(fluteCoordinate, newListener);
targetListener = newListener;
}
targetListener.PushPacket(lctFrame);
_stopProcessIndicator = true;
if (targetListener.IsComplete())
{
GuessedFluteDataType guessedFluteDataType = FluteUtilities.GuessDataType(targetListener);
ProcessMetafile(guessedFluteDataType, targetListener, destination);
}
}
private bool ProcessMetafile(GuessedFluteDataType guessedFluteDataType, FluteListener targetListener, IPEndPoint destination)
{
switch (guessedFluteDataType)
{
case GuessedFluteDataType.Xml:
string contentsAsString = targetListener.GetContentsAsString();
if (contentsAsString.Contains("<FDT-Instance "))
{
Atsc3Fdt fdt = Atsc3Utilities.ParseFdt(contentsAsString);
filenames.LearnFilename(destination, fdt);
return true;
}
return false;
default:
throw new NotImplementedException(guessedFluteDataType.ToString());
}
}
private void HandleLls(LlsTable lls)
{
if (!lls.Valid)
return;
_stopProcessIndicator = true;
switch (lls.LlsTableId)
{
case 0x01:
//SLT
if (!atsc3detected)
{
atsc3detected = true;
eventHandler.OnAtsc3Detected();
}
sltType slt = Atsc3Utilities.UnpackSlt(lls.Payload);
eventHandler.OnAtsc3ServiceList(slt);
break;
case 0x03:
SysTimeType systime = Atsc3Utilities.UnpackSystime(lls.Payload);
if (systime.ptpPrepend != 0 && !systimeDetected)
{
logger.WarnFormat("Found valid ATSC A/331 SYSTIME, however it is not implemented yet. ");
systimeDetected = true;
}
break;
case 0xff:
//User defined, can be removed.
break;
default:
logger.InfoFormat(
"Encountered unknown ATSC A/331 LLS Table: 0x{0:X2} This means that this stream uses a version of ATSC 3 than skyscraper8 knows about. It would be great if you could share a recording of this stream so this can be implemented.", lls.LlsTableId);
break;
}
}
private bool _stopProcessIndicator;
public bool StopProcessingAfterThis()
{
return _stopProcessIndicator;
}
}
}