Added basic parsing of ATSC Pipes.
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 37s

This commit is contained in:
feyris-tan 2026-05-22 21:22:31 +02:00
parent 64b1639e2f
commit f4193297d2
6 changed files with 333 additions and 3 deletions

View File

@ -1,4 +1,5 @@
using skyscraper5.Skyscraper.IO;
using skyscraper8.Atsc.A330;
using System;
using System.Collections.Generic;
using System.Linq;
@ -30,7 +31,7 @@ namespace skyscraper8.Atsc.A322
byte b = ms.ReadUInt8();
pointer |= (((b >> 2) & 0x3F) << 7);
OfiDescription ofi = (OfiDescription)(b & 0x3);
switch(ofi)
switch (ofi)
{
case OfiDescription.NoExtensionMode:
break;
@ -57,8 +58,93 @@ namespace skyscraper8.Atsc.A322
}
}
throw new NotImplementedException("Actually do the ATSC packets here.")
while (ms.GetAvailableBytes() > 0)
{
switch(stateMachineState)
{
case 0: //Initial state, nothing parsed yet.
if (pointer == 8191)
{
return; //no new packet to be found in this BBFRAME.
}
ms.Position = pointer + addToPointer;
neededBuffer = null;
neededBufferOffset = 0;
currentAlpPacket = null;
stateMachineState = 1;
break;
case 1: //Prepare get Base Header for ALP packet encapsulation
neededBuffer = new byte[2];
stateMachineState = 2;
neededBufferOffset = 0;
break;
case 2: //Get Base Header for ALP packet encapsulation
int readSucessful = ms.Read(neededBuffer, neededBufferOffset, neededBuffer.Length - neededBufferOffset);
neededBufferOffset += readSucessful;
if (neededBufferOffset == neededBuffer.Length)
{
stateMachineState = 3;
}
break;
case 3: //Parse current ALP packet
currentAlpPacket = new AlpPacket(neededBuffer);
if (!currentAlpPacket.PayloadConfiguration)
{
if (currentAlpPacket.HeaderMode.Value)
{
throw new NotImplementedException("Single Packet Header not yet implemented.");
}
}
else
{
if (!currentAlpPacket.SegmentationConcatenation.Value)
{
throw new NotImplementedException("Segmentation Header not yet implemented.");
}
else
{
throw new NotImplementedException("Concatenation Header not yet implemented.");
}
}
stateMachineState = 4;
break;
case 4: //Prepate get packet payload
neededBufferOffset = 0;
neededBuffer = new byte[currentAlpPacket.Length];
stateMachineState = 5;
break;
case 5: //Get packet payload
int readSuccesful2 = ms.Read(neededBuffer, neededBufferOffset, neededBuffer.Length - neededBufferOffset);
neededBufferOffset += readSuccesful2;
if (neededBufferOffset == neededBuffer.Length)
{
stateMachineState = 6;
}
break;
case 6: //Deliver packet
DeliverPacket(currentAlpPacket, neededBuffer);
stateMachineState = 1;
break;
default:
throw new NotImplementedException(String.Format("ATSC PLP Baseband Parser State machine state #{0}", stateMachineState));
}
}
}
private void DeliverPacket(AlpPacket? currentAlpPacket, byte[]? neededBuffer)
{
Console.WriteLine("PacketType = {0} \t Length = {1}", currentAlpPacket.PacketType, neededBuffer.Length);
}
internal void OnSyncLoss()
{
stateMachineState = 0;
}
private AlpPacket currentAlpPacket;
private int stateMachineState;
private byte[] neededBuffer;
private int neededBufferOffset;
}
internal enum OfiDescription

View File

@ -0,0 +1,101 @@
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Atsc.A322
{
internal class L1BasicSignalling : Validatable
{
public L1BasicSignalling(byte[] buffer)
{
InBitStream bitstream = new InBitStream(buffer);
this.Version = new Version(bitstream.ReadBitsAsUInt16(3), 0);
MimoScatteredPilotEncoding = bitstream.ReadBitAsBoolean();
LlsFlag = bitstream.ReadBitAsBoolean();
TimeInfoFlag = (TimeInfoFlagSignalingFormat)bitstream.ReadBitsAsByte(2);
ReturnChannelFlag = bitstream.ReadBitAsBoolean();
PaprReduction = (PaprReductionSignalingFormat)bitstream.ReadBitsAsByte(2);
bool frameLengthMode = bitstream.ReadBitAsBoolean();
if (!frameLengthMode)
{
FrameLength = bitstream.ReadBitsAsUint(10);
ExcessSamplesPerSymbol = bitstream.ReadBitsAsUint(13);
}
else
{
TimeOffset = bitstream.ReadBitsAsUint(16);
AdditionalSamples = bitstream.ReadBitsAsByte(7);
}
NumSubframes = bitstream.ReadBitsAsByte(8);
PreambleNumSymbols = bitstream.ReadBitsAsByte(3);
PreambleReducedCarriers = bitstream.ReadBitsAsByte(3);
L1DetailContentTag = bitstream.ReadBitsAsByte(2);
L1DetailSizeBytes = bitstream.ReadBitsAsUint(13);
L1DetailFecType = bitstream.ReadBitsAsByte(3);
L1DetailAdditionalParityMode = bitstream.ReadBitsAsByte(2);
L1DetailTotalCells = bitstream.ReadBitsAsUint(19);
FirstSubMimo = bitstream.ReadBitAsBoolean();
FirstSubMiso = bitstream.ReadBitsAsByte(2);
FirstSubFftSize = bitstream.ReadBitsAsByte(2);
FirstSubReducedCarries = bitstream.ReadBitsAsByte(3);
FirstSubGuardInterval = bitstream.ReadBitsAsByte(4);
FirstSubNumOfdmSymbols = bitstream.ReadBitsAsUint(11);
FirstSubScatteredPilotPattern = bitstream.ReadBitsAsByte(5);
FirstSubScatteredPilotBoost = bitstream.ReadBitsAsByte(3);
FirstSubSbsFirst = bitstream.ReadBitAsBoolean();
FirstSubSbsLast = bitstream.ReadBitAsBoolean();
FirstSubMimoMixed = bitstream.ReadBitAsBoolean();
Valid = true;
}
public Version Version { get; }
public bool MimoScatteredPilotEncoding { get; }
public bool LlsFlag { get; }
public TimeInfoFlagSignalingFormat TimeInfoFlag { get; }
public bool ReturnChannelFlag { get; }
public PaprReductionSignalingFormat PaprReduction { get; }
public uint? FrameLength { get; }
public uint? ExcessSamplesPerSymbol { get; }
public uint? TimeOffset { get; }
public byte AdditionalSamples { get; }
public byte NumSubframes { get; }
public byte PreambleNumSymbols { get; }
public byte PreambleReducedCarriers { get; }
public byte L1DetailContentTag { get; }
public uint L1DetailSizeBytes { get; }
public byte L1DetailFecType { get; }
public byte L1DetailAdditionalParityMode { get; }
public uint L1DetailTotalCells { get; }
public bool FirstSubMimo { get; }
public byte FirstSubMiso { get; }
public byte FirstSubFftSize { get; }
public byte FirstSubReducedCarries { get; }
public byte FirstSubGuardInterval { get; private set; }
public uint FirstSubNumOfdmSymbols { get; }
public byte FirstSubScatteredPilotPattern { get; }
public byte FirstSubScatteredPilotBoost { get; }
public bool FirstSubSbsFirst { get; }
public bool FirstSubSbsLast { get; }
public bool FirstSubMimoMixed { get; }
}
enum TimeInfoFlagSignalingFormat
{
NotIncluded,
IncludedMilliseconds,
IncludedMicroseconds,
IncludedNanoseconds,
}
enum PaprReductionSignalingFormat
{
NoPaprReduction,
ToneReservationOnly,
AceOnly,
TrAndAce
}
}

View File

@ -0,0 +1,16 @@
using skyscraper5.Skyscraper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Atsc.A322
{
internal class L1DetailSignalling : Validatable
{
public L1DetailSignalling(byte[] bytes)
{
}
}
}

View File

@ -62,6 +62,7 @@ namespace skyscraper8.Atsc.A324
throw new NotImplementedException();
}
private ushort expectedSequenceNumber;
private bool warnedAboutNonMarkedPackets;
public void HandlePacket(InternetHeader internetHeader, byte[] ipv4Packet)
{
@ -79,6 +80,8 @@ namespace skyscraper8.Atsc.A324
return;
}
EnsurePlpSync(plp, rtpPacket);
if (plp == 64)
{
HandlePreambleData(rtpPacket.RtpPayload);
@ -96,6 +99,44 @@ namespace skyscraper8.Atsc.A324
}
}
private ushort[] plpSequenceNumbers;
private void EnsurePlpSync(ushort plp, RtpPacket rtpPacket)
{
if (plpSequenceNumbers == null)
{
plpSequenceNumbers = new ushort[66];
}
if (plpSequenceNumbers[plp] != rtpPacket.RtpSequenceNumber)
{
if (plp == 64)
{
DesyncPreamble();
}
else if (plp == 65)
{
throw new NotImplementedException("desynced timing and management");
}
else if (plp <= 63)
{
plps?[plp]?.OnSyncLoss();
}
else
{
throw new NotImplementedException(String.Format("Unknown PLP: {0}", plp));
}
}
}
private void DesyncPreamble()
{
if (preambleData != null)
{
preambleData.Valid = false;
}
}
private AtscPlpBasebandParser[] plps;
private void HandlePlp(ushort plp, byte[] rtpPayload)
{
@ -115,9 +156,10 @@ namespace skyscraper8.Atsc.A324
throw new NotImplementedException();
}
private PreambleData preambleData;
private void HandlePreambleData(byte[] rtpPayload)
{
throw new NotImplementedException();
preambleData = new PreambleData(rtpPayload);
}
private SkyscraperContext context;

View File

@ -0,0 +1,40 @@
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using skyscraper8.Atsc.A322;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Atsc.A324
{
internal class PreambleData : Validatable
{
public PreambleData(byte[] buffer)
{
MemoryStream ms = new MemoryStream(buffer);
long expectedLength = ms.ReadUInt16BE();
long actualLength = ms.Length - 4;
if (actualLength != expectedLength)
{
Valid = false;
return;
}
BasicSignalling = new L1BasicSignalling(ms.ReadBytes(25));
if (!BasicSignalling.Valid)
{
Valid = false;
return;
}
long detailLength = expectedLength - 25;
DetailSignalling = new L1DetailSignalling(ms.ReadBytes(detailLength));
Valid = DetailSignalling.Valid;
}
public L1BasicSignalling BasicSignalling { get; }
public L1DetailSignalling DetailSignalling { get; }
}
}

View File

@ -0,0 +1,45 @@
using skyscraper5.Skyscraper.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace skyscraper8.Atsc.A330
{
internal class AlpPacket
{
public AlpPacket(byte[] buffer)
{
MemoryStream ms = new MemoryStream(buffer, false);
ushort first16bits = ms.ReadUInt16BE();
PacketType = (AlpPacketType)((first16bits & 0xe000) >> 13);
PayloadConfiguration = (first16bits & 0x1000) != 0;
if (!PayloadConfiguration)
{
HeaderMode = (first16bits & 0x0800) != 0;
Length = (first16bits & 0x07ff);
}
else
{
SegmentationConcatenation = (first16bits & 0x0800) != 0;
Length = (first16bits & 0x07ff);
}
}
public AlpPacketType PacketType { get; private set; }
public bool PayloadConfiguration { get; private set; }
public bool? HeaderMode { get; }
public bool? SegmentationConcatenation { get; }
public int Length { get; }
}
enum AlpPacketType
{
Ipv4 = 0,
CompressedIp = 2,
LinkLayerSignalling = 4,
PacketTypeExtension = 6,
Mpeg2Ts = 7
}
}