Added an option to receive ATSC 3.0 STLTP.
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 48s

This commit is contained in:
feyris-tan 2026-05-20 22:34:41 +02:00
parent f7aa6ecf67
commit d33a0003ef
6 changed files with 240 additions and 31 deletions

View File

@ -0,0 +1,140 @@
using skyscraper5.Ietf.Rfc768;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper.IO;
using skyscraper5.Skyscraper.Plugins;
using skyscraper5.Skyscraper.Scraper;
using skyscraper8.Ietf.Rfc3550_RTP;
using skyscraper8.Skyscraper.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Atsc.A324
{
[SkyscraperPlugin]
internal class StltpReceiver : ISkyscraperMpePlugin
{
public bool CanHandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
if (internetHeader.Protocol != 0x11)
return false;
if (!internetHeader.IsDestinationMulticast)
return false;
if (ipv4Packet[8] != 0x80)
return false;
if ((ipv4Packet[9] & 0x7f) != 97)
return false;
return true;
}
public void ConnectToStorage(object[] connector)
{
throw new NotImplementedException();
}
private bool processed;
private ushort expectedRtpSequenceNumber;
private int currentIpPacketBufferOffset;
private byte[] currentIpPacketBuffer;
private int bytesNeeded;
private int stateMachineState;
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
processed = false;
UserDatagram udpPacket = new UserDatagram(ipv4Packet);
RtpPacket rtpPacket = new RtpPacket(udpPacket.Payload);
if (rtpPacket.RtpPayloadType != MediaFormatEnum.DynamicRtpType97)
return;
if (expectedRtpSequenceNumber != rtpPacket.RtpSequenceNumber)
{
//sync loss
currentIpPacketBufferOffset = 0;
if (currentIpPacketBuffer != null)
Array.Clear(currentIpPacketBuffer);
bytesNeeded = 0;
stateMachineState = 0;
}
expectedRtpSequenceNumber = rtpPacket.RtpSequenceNumber;
expectedRtpSequenceNumber++;
MemoryStream ms = new MemoryStream(rtpPacket.RtpPayload);
while (ms.GetAvailableBytes() > 0)
{
switch (stateMachineState)
{
case 0: //Initial state, nothing processed yet and not yet synced either.
if (rtpPacket.RtpMarker)
{
uint packetOffset = rtpPacket.RtpSynchronizationSourceIdentifier & 0x0000ffff;
ms.Position = packetOffset;
stateMachineState = 1;
break;
}
else
{
//The packet can not be used for sync, so we can drop it.
processed = true;
return;
}
case 1: //Prepare begin reading of the first four bytes of next IP packet.
if (currentIpPacketBuffer == null)
currentIpPacketBuffer = new byte[ushort.MaxValue];
Array.Clear(currentIpPacketBuffer);
bytesNeeded = 4;
currentIpPacketBufferOffset = 0;
stateMachineState = 2;
break;
case 2: //Read the first four byte of the next IP packet.
int readAmount = ms.Read(currentIpPacketBuffer, currentIpPacketBufferOffset, bytesNeeded);
bytesNeeded -= readAmount;
currentIpPacketBufferOffset += readAmount;
if (bytesNeeded == 0)
{
//Initial four bytes completed, now we know how long the packet is.
bytesNeeded = currentIpPacketBuffer[2] << 8 | currentIpPacketBuffer[3];
bytesNeeded -= 4;
stateMachineState = 3;
}
break;
case 3: //Read the IP packet until it's complete
int readAmount2 = ms.Read(currentIpPacketBuffer, currentIpPacketBufferOffset, bytesNeeded);
bytesNeeded -= readAmount2;
currentIpPacketBufferOffset += readAmount2;
if (bytesNeeded == 0)
{
//Packet complete!
stateMachineState = 4;
}
break;
case 4: //Deliver the packet
currentIpPacketBuffer[20] = 0x69;
currentIpPacketBuffer[21] = 0x69;
context.OnIpDatagram(0x1fff, currentIpPacketBuffer.AsSpan().Slice(0, currentIpPacketBufferOffset).ToArray());
stateMachineState = 1;
break;
default:
throw new NotImplementedException(string.Format("Unknown state in STLTP Receiver finite automata: {0}", stateMachineState));
}
}
processed = true;
}
private SkyscraperContext context;
public void SetContext(DateTime? currentTime, object skyscraperContext)
{
this.context = skyscraperContext as SkyscraperContext;
}
public bool StopProcessingAfterThis()
{
return processed;
}
}
}

View File

@ -0,0 +1,32 @@
namespace skyscraper8.Ietf.Rfc3550_RTP
{
public enum MediaFormatEnum
{
PCMU = 0,
GSM = 3,
G723 = 4,
DVI4_32kbit = 5,
DVI4_64kbit = 6,
LPC = 7,
PCMA = 8,
G722 = 9,
L16Stereo = 10,
L16Mono = 11,
QCELP = 12,
ComfortNoise = 13,
MPA = 14,
G728 = 15,
DVI4_44kbit = 16,
DVI4_88kbit = 17,
G729 = 18,
CellB = 25,
Jpeg = 26,
NV = 28,
H261 = 31,
MPV = 32,
M2T = 33,
H263 = 34,
DynamicRtpType97 = 97
}
}

View File

@ -0,0 +1,64 @@
using skyscraper5.Skyscraper.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Ietf.Rfc3550_RTP
{
internal class RtpPacket
{
public RtpPacket(byte[] payload)
{
MemoryStream ms = new MemoryStream(payload, false);
byte byte1 = ms.ReadUInt8();
this.RtpVersion = new Version((byte1 & 0xc0) >> 6, 0);
this.RtpPadding = (byte1 & 0x20) != 0;
bool rtpExtension = (byte1 & 0x10) != 0;
int csrcCount = (byte1 & 0x0f);
byte byte2 = ms.ReadUInt8();
this.RtpMarker = (byte2 & 0x80) != 0;
this.RtpPayloadType = (MediaFormatEnum)(byte2 & 0x7f);
this.RtpSequenceNumber = ms.ReadUInt16BE();
this.RtpTimestamp = ms.ReadUInt32BE();
this.RtpSynchronizationSourceIdentifier = ms.ReadUInt32BE();
if (csrcCount != 0)
{
CsrcIdentifiers = new uint[csrcCount];
for (int i = 0; i < csrcCount; i++)
{
CsrcIdentifiers[i] = ms.ReadUInt32BE();
}
}
if (rtpExtension)
{
this.ExtensionHeaderId = ms.ReadUInt16BE();
ushort extensionHeaderLength = ms.ReadUInt16BE();
extensionHeaderLength *= 4;
this.ExtensionHeaderData = ms.ReadBytes(extensionHeaderLength);
}
this.RtpPayload = ms.ReadBytes(ms.GetAvailableBytes());
}
public Version RtpVersion { get; }
public bool RtpPadding { get; }
public bool RtpMarker { get; }
public MediaFormatEnum RtpPayloadType { get; }
public ushort RtpSequenceNumber { get; }
public uint RtpTimestamp { get; }
public uint RtpSynchronizationSourceIdentifier { get; }
public uint[] CsrcIdentifiers { get; private set; }
public ushort? ExtensionHeaderId { get; }
public byte[] ExtensionHeaderData { get; }
public byte[] RtpPayload { get; }
}
}

View File

@ -2,7 +2,7 @@
"profiles": {
"skyscraper8": {
"commandName": "Project",
"commandLineArgs": "salvage strat1 \"C:\\Users\\Sascha Schiemann\\Downloads\\65.0W_11304.256_V_30000_(2026-05-17 09.44.02)_dump.ts\"",
"commandLineArgs": "salvage strat1 \"Z:\\Persönliches\\Satellitescommunity\\Skyscraper Test Fixture\\65.0W_11304.256_V_30000_(2026-05-17 09.44.02)_dump.ts\"",
"remoteDebugEnabled": false
},
"Container (Dockerfile)": {

View File

@ -1,4 +1,5 @@
using System;
using skyscraper8.Ietf.Rfc3550_RTP;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
@ -118,32 +119,4 @@ namespace skyscraper8.SatIp
Video,
}
public enum MediaFormatEnum
{
PCMU = 0,
GSM = 3,
G723 = 4,
DVI4_32kbit = 5,
DVI4_64kbit = 6,
LPC = 7,
PCMA = 8,
G722 = 9,
L16Stereo = 10,
L16Mono = 11,
QCELP = 12,
ComfortNoise = 13,
MPA = 14,
G728 = 15,
DVI4_44kbit = 16,
DVI4_88kbit = 17,
G729 = 18,
CellB = 25,
Jpeg = 26,
NV = 28,
H261 = 31,
MPV = 32,
M2T = 33,
H263 = 34
}
}

View File

@ -27,7 +27,7 @@ namespace skyscraper8.Skyscraper.Net.Pcap
{
if (pcapWritersIp == null)
{
pcapWritersIp = new PcapWriter[0x1fff];
pcapWritersIp = new PcapWriter[0x2000];
EnsureOutputdirExists();
}
if (pcapWritersIp[pid] == null)