Began working on Threemedia OTT.
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 2m29s

This commit is contained in:
feyris-tan 2026-06-27 22:40:14 +02:00
parent 57c45c215e
commit 6e5bf2d2db
9 changed files with 319 additions and 13 deletions

View File

@ -134,7 +134,7 @@ namespace skyscraper5.Dvb.DataBroadcasting
{ {
if (!announcedLlcTraffic) if (!announcedLlcTraffic)
{ {
logger.InfoFormat("Detected LLC/SNAP traffic on PID {0:x4}", sourcePid); //logger.InfoFormat("Detected LLC/SNAP traffic on PID {0:x4}", sourcePid);
announcedLlcTraffic = true; announcedLlcTraffic = true;
} }

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
namespace skyscraper8.Ietf.FLUTE
{
internal class CdsCompletionPollRequestExtension : Validatable
{
public CdsCompletionPollRequestExtension(byte[] extensionBuffer)
{
MemoryStream ms = new MemoryStream(extensionBuffer, false);
if (ms.GetAvailableBytes() < 1)
{
Valid = false;
return;
}
byte byteA = ms.ReadUInt8();
this.Version = (byteA & 0xf0) >> 4;
if (ms.GetAvailableBytes() < 1)
{
Valid = false;
return;
}
byte byteB = ms.ReadUInt8();
PollSequence = (byteA & 0x0f) << 8;
PollSequence += byteB;
if (ms.GetAvailableBytes() < 4)
{
Valid = false;
return;
}
PollMask = ms.ReadUInt32BE();
Valid = true;
}
public uint PollMask { get; set; }
public int PollSequence { get; set; }
public int Version { get; private set; }
}
}

View File

@ -9,13 +9,28 @@ namespace skyscraper8.Ietf.FLUTE
{ {
internal class FecHeader internal class FecHeader
{ {
public FecHeader(MemoryStream ms) public FecHeader(MemoryStream ms, byte codepoint)
{ {
SourceBlockNumber = ms.ReadUInt16BE(); switch (codepoint)
EncodingSymbolId = ms.ReadUInt16BE(); {
case 0:
SourceBlockNumber = ms.ReadUInt16BE();
EncodingSymbolId = ms.ReadUInt16BE();
break;
case 129:
SourceBlockNumber = ms.ReadUInt32BE();
SourceBlockLength = ms.ReadUInt16BE();
EncodingSymbolId = ms.ReadUInt16BE();
break;
default:
throw new NotImplementedException(String.Format("Unknown ALC CodePoint value {0}", codepoint));
}
} }
public ushort SourceBlockNumber { get; } public ushort? SourceBlockLength { get; set; }
public uint SourceBlockNumber { get; }
public ushort EncodingSymbolId { get; } public ushort EncodingSymbolId { get; }
} }
} }

View File

@ -72,13 +72,13 @@ namespace skyscraper8.Ietf.FLUTE
} }
if (blocks == null) if (blocks == null)
blocks = new Dictionary<Tuple<ushort, ushort>, FluteBlock>(); blocks = new Dictionary<Tuple<uint, ushort>, FluteBlock>();
if (lctFrame.Atsc3Compliant) if (lctFrame.Atsc3Compliant)
{ {
if (blocks.Count == 0) if (blocks.Count == 0)
{ {
Tuple<ushort, ushort> fakeKey = new Tuple<ushort, ushort>(12345, 6789); Tuple<uint, ushort> fakeKey = new Tuple<uint, ushort>(12345, 6789);
FluteBlock fakeBlock = new FluteBlock(0, 0, new byte[transferLength]); FluteBlock fakeBlock = new FluteBlock(0, 0, new byte[transferLength]);
blocks.Add(fakeKey, fakeBlock); blocks.Add(fakeKey, fakeBlock);
} }
@ -92,7 +92,7 @@ namespace skyscraper8.Ietf.FLUTE
if (lctFrame.FecHeader == null) if (lctFrame.FecHeader == null)
return; return;
ushort sbn = lctFrame.FecHeader.SourceBlockNumber; uint sbn = lctFrame.FecHeader.SourceBlockNumber;
ushort esi = lctFrame.FecHeader.EncodingSymbolId; ushort esi = lctFrame.FecHeader.EncodingSymbolId;
//FluteBlock? fluteBlock = blocks.Find(x => //FluteBlock? fluteBlock = blocks.Find(x =>
@ -122,7 +122,7 @@ namespace skyscraper8.Ietf.FLUTE
blocksDeduplicated++; blocksDeduplicated++;
}*/ }*/
Tuple<ushort, ushort> coordinate = new Tuple<ushort, ushort>(sbn, esi); Tuple<uint, ushort> coordinate = new Tuple<uint, ushort>(sbn, esi);
if (blocks.ContainsKey(coordinate)) if (blocks.ContainsKey(coordinate))
{ {
blocksDeduplicated++; blocksDeduplicated++;
@ -222,19 +222,19 @@ namespace skyscraper8.Ietf.FLUTE
} }
private long transferLength; private long transferLength;
private Dictionary<Tuple<ushort,ushort>,FluteBlock> blocks; private Dictionary<Tuple<uint,ushort>,FluteBlock> blocks;
private uint blocksDeduplicated; private uint blocksDeduplicated;
public class FluteBlock public class FluteBlock
{ {
public FluteBlock(ushort sourceBlockNumer, ushort encodingSymbolId, byte[] payload) public FluteBlock(uint sourceBlockNumer, ushort encodingSymbolId, byte[] payload)
{ {
SourceBlockNumer = sourceBlockNumer; SourceBlockNumer = sourceBlockNumer;
EncodingSymbolId = encodingSymbolId; EncodingSymbolId = encodingSymbolId;
Payload = payload; Payload = payload;
} }
public ushort SourceBlockNumer; public uint SourceBlockNumer;
public ushort EncodingSymbolId; public ushort EncodingSymbolId;
public byte[] Payload; public byte[] Payload;

View File

@ -53,7 +53,7 @@ namespace skyscraper8.Ietf.FLUTE
//DVB-NIP sample: "Z:\Persönliches\Satellitescommunity\Skyscraper Test Fixture\astra1_11141h_gse_nip.ts" //DVB-NIP sample: "Z:\Persönliches\Satellitescommunity\Skyscraper Test Fixture\astra1_11141h_gse_nip.ts"
if (!isAtsc3) if (!isAtsc3)
{ {
this.FecHeader = new FecHeader(ms); this.FecHeader = new FecHeader(ms, this.Codepoint);
} }
else else
{ {

View File

@ -83,6 +83,9 @@ namespace skyscraper8.Ietf.FLUTE
case 64: case 64:
this.FecObjectTransmissionInformation = new FecObjectTransmissionInformation(extensionBuffer); this.FecObjectTransmissionInformation = new FecObjectTransmissionInformation(extensionBuffer);
break; break;
case 65:
this.CdsCompletionPollRequest = new CdsCompletionPollRequestExtension(extensionBuffer);
break;
case 68: case 68:
this.NipActualCarrierInformation = new NipActualCarrierInformation(extensionBuffer); this.NipActualCarrierInformation = new NipActualCarrierInformation(extensionBuffer);
break; break;
@ -98,10 +101,14 @@ namespace skyscraper8.Ietf.FLUTE
Valid = true; Valid = true;
} }
public CdsCompletionPollRequestExtension CdsCompletionPollRequest { get; set; }
private ulong ReadField(Stream stream, int bits) private ulong ReadField(Stream stream, int bits)
{ {
switch(bits) switch(bits)
{ {
case 0:
return 0;
case 16: case 16:
return stream.ReadUInt16BE(); return stream.ReadUInt16BE();
case 32: case 32:

View File

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper8.Ietf.FLUTE;
namespace skyscraper8.ThreemediaOtt
{
internal class ThreemediaContentFragment : IDisposable
{
public void IngestPacket(LctFrame lctFrame)
{
int totalBlocks = lctFrame.FecHeader.SourceBlockLength.Value;
if (blocks == null)
blocks = new byte[totalBlocks][];
int targetBlock = lctFrame.FecHeader.EncodingSymbolId;
if (targetBlock >= blocks.Length)
return;
if (blocks[targetBlock] != null)
return;
blocks[targetBlock] = lctFrame.Payload;
}
private byte[][] blocks;
public void Dispose()
{
if (blocks != null)
{
for (int i = 0; i < blocks.Length; i++)
{
blocks[i] = null;
}
}
blocks = null;
}
public bool IsComplete()
{
if (blocks == null)
return false;
for (int i = 0; i < blocks.Length; i++)
{
if (blocks[i] == null)
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using log4net;
using skyscraper5.Ietf.Rfc768;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper;
using skyscraper8.Ietf.FLUTE;
namespace skyscraper8.ThreemediaOtt
{
[SkyscraperPlugin]
internal class ThreemediaOttHandler : ISkyscraperMpePlugin
{
private static readonly IPAddress THREEMDIA_DESTINATION_IP = IPAddress.Parse("224.1.2.1");
private static readonly ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Name);
public void ConnectToStorage(object[] connector)
{
throw new NotImplementedException();
}
private SkyscraperContext context;
public void SetContext(DateTime? currentTime, object skyscraperContext)
{
if (context != null)
{
context = skyscraperContext as SkyscraperContext;
}
}
public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
if (!internetHeader.IsDestinationMulticast)
return false;
if (!internetHeader.DestinationAddress.Equals(THREEMDIA_DESTINATION_IP))
return false;
if (internetHeader.Protocol != 17)
return false;
if (ipv4Packet[2] != 0x04)
return false;
if (ipv4Packet[3] != 0x61)
return false;
if (ipv4Packet[8] != 0x10)
return false;
return true;
}
private Dictionary<ushort, ThreemediaSession> sessions;
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
UserDatagram udpPacket = new UserDatagram(ipv4Packet);
LctFrame lctFrame = new LctFrame(udpPacket.Payload, false);
if (lctFrame.CloseSessionFlag)
{
AssembleSegment(udpPacket.DestinationPort);
return;
}
else
{
IngestPacket(udpPacket.DestinationPort, lctFrame);
return;
}
}
private void IngestPacket(ushort udpPacketDestinationPort, LctFrame lctFrame)
{
if (sessions == null)
sessions = new Dictionary<ushort, ThreemediaSession>();
ThreemediaSession selectedSession = null;
if (sessions.ContainsKey(udpPacketDestinationPort))
{
selectedSession = sessions[udpPacketDestinationPort];
}
else
{
selectedSession = new ThreemediaSession();
sessions[udpPacketDestinationPort] = selectedSession;
}
selectedSession.IngestPacket(lctFrame);
}
private void AssembleSegment(ushort destinationPort)
{
if (sessions == null)
return;
if (!sessions.ContainsKey(destinationPort))
{
return;
}
ThreemediaSession selectedSession = sessions[destinationPort];
if (!selectedSession.IsComplete())
{
logger.WarnFormat(String.Format("The session on Port {0} is incomplete and can not be recovered.", destinationPort));
selectedSession.Dispose();
return;
}
throw new NotImplementedException();
}
public bool StopProcessingAfterThis()
{
return true;
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using skyscraper8.Ietf.FLUTE;
namespace skyscraper8.ThreemediaOtt
{
internal class ThreemediaSession : IDisposable
{
public void IngestPacket(LctFrame lctFrame)
{
int numFragments = lctFrame.LctHeader.CdsCompletionPollRequest.PollSequence;
if (fragments == null)
fragments = new ThreemediaContentFragment[numFragments];
int currentFragment = (int)lctFrame.LctHeader.TransportObjectIdentifier;
currentFragment--;
if (fragments[currentFragment] == null)
fragments[currentFragment] = new ThreemediaContentFragment();
fragments[currentFragment].IngestPacket(lctFrame);
}
private ThreemediaContentFragment[] fragments;
public bool IsComplete()
{
if (fragments == null)
return false;
for (int i = 0; i < fragments.Length; i++)
{
if (fragments[i] == null)
return false;
if (!fragments[i].IsComplete())
return false;
}
return true;
}
public void Dispose()
{
if (fragments != null)
{
for (int i = 0; i < fragments.Length; i++)
{
fragments[i].Dispose();
fragments[i] = null;
}
}
fragments = null;
}
}
}