The Atsc3Receiver.cs now understands ATSC3 FDTs.
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 1m25s
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 1m25s
This commit is contained in:
parent
2e173d4156
commit
4fe40e082a
23
skyscraper8/Atsc/A331/A331Exception.cs
Normal file
23
skyscraper8/Atsc/A331/A331Exception.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
public class A331Exception : AtscException
|
||||
{
|
||||
public A331Exception()
|
||||
{
|
||||
}
|
||||
|
||||
public A331Exception(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public A331Exception(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
37
skyscraper8/Atsc/A331/Atsc3Fdt.cs
Normal file
37
skyscraper8/Atsc/A331/Atsc3Fdt.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
internal class Atsc3Fdt
|
||||
{
|
||||
public uint Expires { get; set; }
|
||||
public uint EfdtVersion { get; set; }
|
||||
|
||||
private List<Atsc3FdtFile> _files;
|
||||
public IReadOnlyList<Atsc3FdtFile> Files
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_files == null)
|
||||
return ImmutableList<Atsc3FdtFile>.Empty;
|
||||
|
||||
return _files.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFile(Atsc3FdtFile resultFile)
|
||||
{
|
||||
if (_files == null)
|
||||
_files = new List<Atsc3FdtFile>();
|
||||
|
||||
_files.Add(resultFile);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
45
skyscraper8/Atsc/A331/Atsc3FdtFile.cs
Normal file
45
skyscraper8/Atsc/A331/Atsc3FdtFile.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
internal class Atsc3FdtFile
|
||||
{
|
||||
public Atsc3FdtFile(Dictionary<string, string> result)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> entry in result)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "Content-Location":
|
||||
this.ContentLocation = entry.Value;
|
||||
break;
|
||||
case "TOI":
|
||||
this.TOI = ulong.Parse(entry.Value);
|
||||
break;
|
||||
case "Content-Length":
|
||||
this.ContentLength = int.Parse(entry.Value);
|
||||
break;
|
||||
case "Content-Type":
|
||||
this.ContentType = entry.Value;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public int ContentLength { get; set; }
|
||||
|
||||
public ulong TOI { get; set; }
|
||||
|
||||
public string ContentLocation { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
23
skyscraper8/Atsc/A331/Atsc3FilenameTable.cs
Normal file
23
skyscraper8/Atsc/A331/Atsc3FilenameTable.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
internal class Atsc3FilenameTable
|
||||
{
|
||||
private List<Tuple<IPEndPoint, ulong, string>> staticFilenames;
|
||||
public void LearnFilename(IPEndPoint endpoint, Atsc3Fdt fdt)
|
||||
{
|
||||
foreach (Atsc3FdtFile file in fdt.Files)
|
||||
{
|
||||
if (staticFilenames == null)
|
||||
staticFilenames = new List<Tuple<IPEndPoint, ulong, string>>();
|
||||
staticFilenames.Add(new Tuple<IPEndPoint, ulong, string>(endpoint, file.TOI, file.ContentLocation));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,7 @@ namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
[SkyscraperPlugin]
|
||||
[PluginPriority(3)]
|
||||
internal class Atsc3Unpacker : ISkyscraperMpePlugin
|
||||
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");
|
||||
@ -45,9 +45,17 @@ namespace skyscraper8.Atsc.A331
|
||||
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 (ipv4Packet.GetUInt16BE(2) == LLS_PORT)
|
||||
if (destPort == LLS_PORT)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -56,10 +64,22 @@ namespace skyscraper8.Atsc.A331
|
||||
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
|
||||
@ -74,6 +94,63 @@ namespace skyscraper8.Atsc.A331
|
||||
}
|
||||
|
||||
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)
|
||||
@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using skyscraper8.Atsc.A331.Schema;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using skyscraper8.Atsc.A331.Schema;
|
||||
|
||||
namespace skyscraper8.Atsc.A331
|
||||
{
|
||||
@ -36,5 +37,94 @@ namespace skyscraper8.Atsc.A331
|
||||
SysTimeType result = (SysTimeType)deserialize;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Atsc3Fdt ParseFdt(string rawXml)
|
||||
{
|
||||
Atsc3Fdt result = null;
|
||||
|
||||
NameTable nt = new NameTable();
|
||||
XmlNamespaceManager ns = new XmlNamespaceManager(nt);
|
||||
ns.AddNamespace("fdt", "ignore");
|
||||
ns.AddNamespace("afdt", "ignore");
|
||||
XmlParserContext context = new XmlParserContext(nt, ns, null, XmlSpace.None);
|
||||
XmlReaderSettings settings = new XmlReaderSettings()
|
||||
{
|
||||
ConformanceLevel = ConformanceLevel.Fragment,
|
||||
IgnoreWhitespace = true,
|
||||
IgnoreComments = true
|
||||
};
|
||||
|
||||
XmlReader xr = XmlReader.Create(new StringReader(rawXml), settings, context);
|
||||
|
||||
while (xr.Read())
|
||||
{
|
||||
switch (xr.NodeType)
|
||||
{
|
||||
case XmlNodeType.XmlDeclaration:
|
||||
//Ok, we know this is valid XML.
|
||||
break;
|
||||
case XmlNodeType.Whitespace:
|
||||
//Understandable.
|
||||
break;
|
||||
case XmlNodeType.Element:
|
||||
switch (xr.Name)
|
||||
{
|
||||
case "FDT-Instance":
|
||||
result = new Atsc3Fdt();
|
||||
Dictionary<string, string> extractXmlAttributes = ExtractXmlAttributes(xr);
|
||||
HandleFdtInstanceAttributes(result, extractXmlAttributes);
|
||||
break;
|
||||
case "fdt:File":
|
||||
Dictionary<string, string> fileAttributes = ExtractXmlAttributes(xr);
|
||||
Atsc3FdtFile resultFile = new Atsc3FdtFile(fileAttributes);
|
||||
result.AddFile(resultFile);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(xr.ToString());
|
||||
}
|
||||
break;
|
||||
case XmlNodeType.EndElement:
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(xr.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void HandleFdtInstanceAttributes(Atsc3Fdt result, Dictionary<string, string> extractXmlAttributes)
|
||||
{
|
||||
foreach (KeyValuePair<string, string> entry in extractXmlAttributes)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "Expires":
|
||||
result.Expires = uint.Parse(entry.Value);
|
||||
break;
|
||||
case "efdtVersion":
|
||||
case "afdt:efdtVersion":
|
||||
result.EfdtVersion = uint.Parse(entry.Value);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(entry.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ExtractXmlAttributes(XmlReader xr)
|
||||
{
|
||||
Dictionary<string, string> result = new Dictionary<string, string>();
|
||||
if (!xr.HasAttributes)
|
||||
return result;
|
||||
|
||||
xr.MoveToFirstAttribute();
|
||||
do
|
||||
{
|
||||
result.Add(xr.Name,xr.Value);
|
||||
} while (xr.MoveToNextAttribute());
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
skyscraper8/Atsc/AtscException.cs
Normal file
24
skyscraper8/Atsc/AtscException.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using skyscraper5;
|
||||
|
||||
namespace skyscraper8.Atsc
|
||||
{
|
||||
public class AtscException : SkyscraperException
|
||||
{
|
||||
public AtscException()
|
||||
{
|
||||
}
|
||||
|
||||
public AtscException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AtscException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
using skyscraper8.DvbNip;
|
||||
using log4net.Core;
|
||||
using MimeKit;
|
||||
using skyscraper5.Teletext.Wss;
|
||||
using skyscraper8.DvbNip;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
@ -9,8 +12,6 @@ using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MimeKit;
|
||||
using skyscraper5.Teletext.Wss;
|
||||
|
||||
namespace skyscraper8.Ietf.FLUTE
|
||||
{
|
||||
@ -73,6 +74,21 @@ namespace skyscraper8.Ietf.FLUTE
|
||||
if (blocks == null)
|
||||
blocks = new Dictionary<Tuple<ushort, ushort>, FluteBlock>();
|
||||
|
||||
if (lctFrame.Atsc3Compliant)
|
||||
{
|
||||
if (blocks.Count == 0)
|
||||
{
|
||||
Tuple<ushort, ushort> fakeKey = new Tuple<ushort, ushort>(12345, 6789);
|
||||
FluteBlock fakeBlock = new FluteBlock(0, 0, new byte[transferLength]);
|
||||
blocks.Add(fakeKey, fakeBlock);
|
||||
}
|
||||
|
||||
FluteBlock atsc3Block = blocks.First().Value;
|
||||
Array.Copy(lctFrame.Payload, 0, atsc3Block.Payload, lctFrame.Atsc3ObjectStartOffset.Value, lctFrame.Payload.Length);
|
||||
_dataWritten += lctFrame.Payload.Length;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lctFrame.FecHeader == null)
|
||||
return;
|
||||
|
||||
@ -293,5 +309,31 @@ namespace skyscraper8.Ietf.FLUTE
|
||||
|
||||
public double LastReportedProgress { get; internal set; }
|
||||
public long? TrimmedLength { get; set; }
|
||||
|
||||
internal byte[] GetFirstBlock()
|
||||
{
|
||||
if (blocks == null)
|
||||
return null;
|
||||
if (blocks.Count == 0)
|
||||
return null;
|
||||
|
||||
if (blocks.Count == 1)
|
||||
return blocks.Values.First().Payload;
|
||||
else
|
||||
{
|
||||
List<FluteBlock> blockPayloads = blocks.Values.ToList();
|
||||
blockPayloads.Sort(new FluteBlockComparer());
|
||||
return blockPayloads[0].Payload;
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetContentsAsString()
|
||||
{
|
||||
Stream stream = ToStream();
|
||||
StreamReader sr = new StreamReader(stream);
|
||||
string result = sr.ReadToEnd();
|
||||
stream.Dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,24 @@ namespace skyscraper8.Ietf.FLUTE
|
||||
return result;
|
||||
}
|
||||
|
||||
public static FDTInstanceType UnpackFluteFdt(string source)
|
||||
{
|
||||
if (fdtSerializer == null)
|
||||
fdtSerializer = new XmlSerializer(typeof(FDTInstanceType));
|
||||
|
||||
try
|
||||
{
|
||||
StringReader sr = new StringReader(source);
|
||||
FDTInstanceType result = (FDTInstanceType)fdtSerializer.Deserialize(sr);
|
||||
return result;
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static FDTInstanceType UnpackFluteFdt(Stream ms)
|
||||
{
|
||||
if (fdtSerializer == null)
|
||||
@ -62,5 +80,18 @@ namespace skyscraper8.Ietf.FLUTE
|
||||
}
|
||||
}
|
||||
|
||||
public static GuessedFluteDataType GuessDataType(FluteListener targetListener)
|
||||
{
|
||||
byte[] firstBlock = targetListener.GetFirstBlock();
|
||||
if (firstBlock[0] == 0x3c && firstBlock[1] == 0x3f && firstBlock[2] == 0x78 && firstBlock[3] == 0x6d && firstBlock[4] == 0x6c)
|
||||
return GuessedFluteDataType.Xml;
|
||||
return GuessedFluteDataType.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public enum GuessedFluteDataType
|
||||
{
|
||||
Unknown,
|
||||
Xml
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user